From 1e5b44f2c9af60d0a1d8a2198904e0e8b21e0fc9 Mon Sep 17 00:00:00 2001 From: langfingaz Date: Tue, 2 Mar 2021 14:42:52 +0100 Subject: [PATCH] work in progress: print fullNames of recent moderators Use-case: Get an overview of active users that create and start lots of meetings. Inform them that the instance is more and more used and ask for a small donation. --- src/langfingaz/main.py | 6 ++- src/langfingaz/parseMeetings.py | 61 +++++++++++++++++++++++++++++- src/langfingaz/plotMeetings.py | 15 +------- src/langfingaz/recentModerators.py | 54 ++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/langfingaz/recentModerators.py diff --git a/src/langfingaz/main.py b/src/langfingaz/main.py index d8491f0..b4c204e 100755 --- a/src/langfingaz/main.py +++ b/src/langfingaz/main.py @@ -4,6 +4,7 @@ import logging from langfingaz import plotMeetings from langfingaz import logMeetingData +from langfingaz import recentModerators def main(): @@ -17,7 +18,8 @@ def main(): '\tlog: log BBB meeting data every 5 minutes;\n' + \ '\t prints one dot after each successfully saved file\n' + \ '\tlog-verbose: log BBB meeting every 5 minute and write current status to stdout\n' + \ - '\tplot: saves a plot of the saved BBB meeting data\n' + '\tplot: saves a plot of the saved BBB meeting data\n' + \ + '\tmoderator: prints users that have recently been moderators in meetings\n' if len(argv) != 2: raise ValueError("Expected one commandline argument!\n" + usageStr) @@ -28,6 +30,8 @@ def main(): logMeetingData.v2() elif argv[1] == "plot": plotMeetings.plotMeetings() + elif argv[1] == "moderator": + recentModerators.printModerators() else: print(usageStr) exit(1) diff --git a/src/langfingaz/parseMeetings.py b/src/langfingaz/parseMeetings.py index 93b2415..eede3a6 100644 --- a/src/langfingaz/parseMeetings.py +++ b/src/langfingaz/parseMeetings.py @@ -1,8 +1,13 @@ +from functools import total_ordering from typing import List +from pathlib import Path from xml.etree import ElementTree from datetime import datetime +import logging +from langfingaz import loadData from langfingaz.util import util +from langfingaz.util import fileUtil class Meeting(object): @@ -39,8 +44,11 @@ class Meeting(object): class Attendee(object): def __init__(self, xml_attendee: ElementTree.Element): - # self.userID = xml_attendee.find('userID').text - # self.fullName = xml_attendee.find('fullName').text + userID = xml_attendee.find('userID').text + fullName = xml_attendee.find('fullName').text + self.user = User(userID=userID, fullName=fullName) + + # roles: MODERATOR, VIEWER self.role = xml_attendee.find('role').text self.isPresenter: bool = util.asBoolean(xml_attendee.find('isPresenter').text) self.isListeningOnly: bool = util.asBoolean(xml_attendee.find('isListeningOnly').text) @@ -51,6 +59,34 @@ class Attendee(object): return util.asString(self) +@total_ordering +class User(object): + def __init__(self, userID: str, fullName: str): + self.userID = userID + self.fullName = fullName + + @staticmethod + def _is_valid_operand(other): + return (hasattr(other, "userID") and + hasattr(other, "fullName")) + + def __hash__(self): + return hash(self.userID) + + def __eq__(self, other): + if not self._is_valid_operand(other): + return NotImplemented + return self.userID == other.userID + + def __lt__(self, other): + if not self._is_valid_operand(other): + return NotImplemented + return self.userID < other.userID + + def __str__(self): + return self.userID + " - " + self.fullName + + class BbbStatus(object): """ Represents the status of one BBB server at one point of time. @@ -104,3 +140,24 @@ def parseMeetingsData(dataStr: str) -> List[Meeting]: xml_meetings = tree.find('meetings') return [Meeting(xml_meeting) for xml_meeting in xml_meetings.iter('meeting')] + + +def readBbbStatiFromDir(dataDir: Path = fileUtil.getDataDir()) -> List[BbbStatus]: + """ + :return: List of BbbStatus objects, sorted by date + """ + + bbbStati: List[BbbStatus] = [] + + for file in dataDir.iterdir(): + if file.name.endswith(".xml"): + logging.debug("Reading from file " + str(file)) + dataStr, t = loadData.loadData(file) + meetings: List[Meeting] = parseMeetingsData(dataStr) + bbbStati.append(BbbStatus(meetings, t)) + + if (len(bbbStati) < 1): + print("No bbbStatus objects were found in data directory: " + str(dataDir)) + + # sort by date + return sorted(bbbStati, key=BbbStatus.getKey) diff --git a/src/langfingaz/plotMeetings.py b/src/langfingaz/plotMeetings.py index 2aa00b5..1154802 100644 --- a/src/langfingaz/plotMeetings.py +++ b/src/langfingaz/plotMeetings.py @@ -22,21 +22,10 @@ def plotMeetings(dataDir: Path = fileUtil.getDataDir()): plot BBB meetings of the last month """ - bbbStati: List[BbbStatus] = [] - - for file in dataDir.iterdir(): - if file.name.endswith(".xml"): - logging.debug("Reading from file " + str(file)) - dataStr, t = loadData.loadData(file) - meetings: List[Meeting] = parseMeetings.parseMeetingsData(dataStr) - bbbStati.append(parseMeetings.BbbStatus(meetings, t)) - if (len(bbbStati) < 1): - print("No bbbStatus objects could be read from data directory: " + str(dataDir)) + bbbStati: List[BbbStatus] = parseMeetings.readBbbStatiFromDir(dataDir) + if len(bbbStati) < 1: return - # sort by date (x-axis) - bbbStati = sorted(bbbStati, key=BbbStatus.getKey) - # filter: take only bbbStatus objects of the last month (4 weeks) # # start: datetime = bbbStati[0].pointOfTime diff --git a/src/langfingaz/recentModerators.py b/src/langfingaz/recentModerators.py new file mode 100644 index 0000000..0b04283 --- /dev/null +++ b/src/langfingaz/recentModerators.py @@ -0,0 +1,54 @@ +from datetime import datetime, timedelta +from pathlib import Path +from typing import List, Dict +import logging + +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + +from langfingaz import loadData +from langfingaz import parseMeetings +from langfingaz.parseMeetings import BbbStatus, Meeting +from langfingaz.util import fileUtil +from langfingaz.util import util + + +def getModerators(dataDir: Path = fileUtil.getDataDir()) -> Dict[parseMeetings.User, int]: + """ + Logged in users have a consistent userId and fullName. + But if they join a meeting via a link they might be asked for a name and get a new userId. + + This method goes through all BbbStati and filters for moderators. + It then counts how often a moderator was found in one BbbStati. + + If a BbbStati are saved every 5 minutes, a user might have a count of 4 if he was moderator + at just one meeting for >20 minutes + + :return: Dict[User, count-how-often-as-moderator] sorted by User.fullName + """ + + bbbStati: List[BbbStatus] = parseMeetings.readBbbStatiFromDir(dataDir) + if len(bbbStati) < 1: + return {} + + moderators: List[parseMeetings.User] = [attendee.user + for bbbStatus in bbbStati + for meeting in bbbStatus.meetings + for attendee in meeting.attendees + if attendee.role == "MODERATOR"] + + moderatorCount: Dict[parseMeetings.User, int] = {} + for moderator in moderators: + count: int = 1 + if moderator in moderatorCount: + count = moderatorCount.get(moderator) + 1 + moderatorCount[moderator] = count + + # sort by User.fullName + moderatorCount = dict(sorted(moderatorCount.items(), key=lambda item: item[0].fullName)) + return moderatorCount + + +def printModerators(): + moderators: Dict[parseMeetings.User, int] = getModerators() + [print(moderator.fullName + ":\t" + str(count)) for moderator, count in moderators.items()]