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.
This commit is contained in:
Daniel Langbein 2021-03-02 14:42:52 +01:00
parent 154f2ed333
commit 1e5b44f2c9
4 changed files with 120 additions and 16 deletions

View File

@ -4,6 +4,7 @@ import logging
from langfingaz import plotMeetings from langfingaz import plotMeetings
from langfingaz import logMeetingData from langfingaz import logMeetingData
from langfingaz import recentModerators
def main(): def main():
@ -17,7 +18,8 @@ def main():
'\tlog: log BBB meeting data every 5 minutes;\n' + \ '\tlog: log BBB meeting data every 5 minutes;\n' + \
'\t prints one dot after each successfully saved file\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' + \ '\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: if len(argv) != 2:
raise ValueError("Expected one commandline argument!\n" + usageStr) raise ValueError("Expected one commandline argument!\n" + usageStr)
@ -28,6 +30,8 @@ def main():
logMeetingData.v2() logMeetingData.v2()
elif argv[1] == "plot": elif argv[1] == "plot":
plotMeetings.plotMeetings() plotMeetings.plotMeetings()
elif argv[1] == "moderator":
recentModerators.printModerators()
else: else:
print(usageStr) print(usageStr)
exit(1) exit(1)

View File

@ -1,8 +1,13 @@
from functools import total_ordering
from typing import List from typing import List
from pathlib import Path
from xml.etree import ElementTree from xml.etree import ElementTree
from datetime import datetime from datetime import datetime
import logging
from langfingaz import loadData
from langfingaz.util import util from langfingaz.util import util
from langfingaz.util import fileUtil
class Meeting(object): class Meeting(object):
@ -39,8 +44,11 @@ class Meeting(object):
class Attendee(object): class Attendee(object):
def __init__(self, xml_attendee: ElementTree.Element): def __init__(self, xml_attendee: ElementTree.Element):
# self.userID = xml_attendee.find('userID').text userID = xml_attendee.find('userID').text
# self.fullName = xml_attendee.find('fullName').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.role = xml_attendee.find('role').text
self.isPresenter: bool = util.asBoolean(xml_attendee.find('isPresenter').text) self.isPresenter: bool = util.asBoolean(xml_attendee.find('isPresenter').text)
self.isListeningOnly: bool = util.asBoolean(xml_attendee.find('isListeningOnly').text) self.isListeningOnly: bool = util.asBoolean(xml_attendee.find('isListeningOnly').text)
@ -51,6 +59,34 @@ class Attendee(object):
return util.asString(self) 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): class BbbStatus(object):
""" """
Represents the status of one BBB server at one point of time. 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') xml_meetings = tree.find('meetings')
return [Meeting(xml_meeting) for xml_meeting in xml_meetings.iter('meeting')] 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)

View File

@ -22,21 +22,10 @@ def plotMeetings(dataDir: Path = fileUtil.getDataDir()):
plot BBB meetings of the last month plot BBB meetings of the last month
""" """
bbbStati: List[BbbStatus] = [] bbbStati: List[BbbStatus] = parseMeetings.readBbbStatiFromDir(dataDir)
if len(bbbStati) < 1:
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))
return return
# sort by date (x-axis)
bbbStati = sorted(bbbStati, key=BbbStatus.getKey)
# filter: take only bbbStatus objects of the last month (4 weeks) # filter: take only bbbStatus objects of the last month (4 weeks)
# #
# start: datetime = bbbStati[0].pointOfTime # start: datetime = bbbStati[0].pointOfTime

View File

@ -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()]