mirror of
https://codeberg.org/langfingaz/bbb-status
synced 2024-11-21 20:23:17 +01:00
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:
parent
154f2ed333
commit
1e5b44f2c9
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
54
src/langfingaz/recentModerators.py
Normal file
54
src/langfingaz/recentModerators.py
Normal 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()]
|
Loading…
Reference in New Issue
Block a user