From e9f612fe43148f7fcecb37a4dfcc6b51945081a6 Mon Sep 17 00:00:00 2001 From: atoav <dh@atoav.com> Date: Thu, 23 Apr 2020 15:30:34 +0200 Subject: [PATCH] Refactor, add more leaderboards (incl. Duration) --- bbbmon/__pycache__/bbbmon.cpython-37.pyc | Bin 9383 -> 10488 bytes bbbmon/bbbmon.py | 119 ++++++++++++++++------- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/bbbmon/__pycache__/bbbmon.cpython-37.pyc b/bbbmon/__pycache__/bbbmon.cpython-37.pyc index 69ead85e11db37e9ebcc685f10df53ba78d5c9c0..1d540c3a3ed4c1abcb44c95fc56ad8b6f28e9cec 100644 GIT binary patch delta 5395 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 Y0GANA0Gj}}2$v9#fTolR2MZS?04x}s;s5{u delta 4081 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 diff --git a/bbbmon/bbbmon.py b/bbbmon/bbbmon.py index 49ef461..ec3e2d0 100644 --- a/bbbmon/bbbmon.py +++ b/bbbmon/bbbmon.py @@ -3,6 +3,7 @@ import os import hashlib +from datetime import datetime, timedelta import requests import appdirs from xml.etree import cElementTree as ElementTree @@ -10,13 +11,21 @@ from typing import NewType, Optional, Tuple, Iterable - +# Default path SERVER_PROPERTIES_FILE = "/usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties" - +# Type definitions Secret = NewType('Secret', str) Url = NewType('Url', str) +FRIENDLY_KEYNAMES = { + "participantCount": "Participants", + "listenerCount": "only listening", + "voiceParticipantCount": "Mics on", + "videoCount": "Webcams on", + "moderatorCount": "Number of Moderators" +} + class XmlListConfig(list): @@ -116,7 +125,11 @@ def request_meetings(secret: Secret, bbb_url: Url) -> XmlDictConfig: exit() return xmldict + def get_meetings(secret: Secret, bbb_url: Url) -> Iterable[XmlDictConfig]: + """ + Request meetings and return a list of them. Sorted by biggest first + """ meetings = [] d = request_meetings(secret, bbb_url) @@ -143,6 +156,32 @@ def get_presenter(meeting: XmlDictConfig) -> Optional[XmlDictConfig]: return None +def get_duration(meeting: XmlDictConfig) -> timedelta: + """ + Return the duration of a meeting + """ + timestamp = int(meeting["startTime"][:-3]) + start_time = datetime.fromtimestamp(timestamp) + duration = datetime.now() - start_time + return duration + + +def strfdelta(duration: timedelta, fmt: str) -> str: + """ + Helper function for datetime.timedelta formatting, use like this: + strfdelta(delta_obj, "{days} days {hours}:{minutes}:{seconds}") + """ + d = {"days": duration.days} + d["hours"], remainder = divmod(duration.seconds, 3600) + d["minutes"], d["seconds"] = divmod(remainder, 60) + return fmt.format(**d) + + +def format_duration(meeting: XmlDictConfig) -> str: + duration = get_duration(meeting) + return strfdelta(duration, "{hours}:{minutes}") + + def get_formated_presenter_name(meeting: XmlDictConfig) -> str: """ @@ -150,42 +189,51 @@ def get_formated_presenter_name(meeting: XmlDictConfig) -> str: """ presenter = get_presenter(meeting) if presenter is not None: - return "{} ({})".format(presenter["fullName"], presenter["userID"]) + return "{:<30} ({})".format(presenter["fullName"], presenter["userID"]) else: return "no Presenter" +def print_leaderboard(meetings: Iterable[XmlDictConfig], key: str): + """ + Print a leaderboard of all meetings sorted by a given key (e.g. + participantCount) + """ + print("LEADERBOARD ({})".format(FRIENDLY_KEYNAMES[key])) + for m in meetings: + print("{:>5} {:<45} {}".format(m[key], m["meetingName"], get_formated_presenter_name(m))) + + +def print_duration_leaderboard(meetings: Iterable[XmlDictConfig]): + """ + Print a leaderboard of all meetings sorted by a given key (e.g. + participantCount) + """ + print("LEADERBOARD (Duration)") + by_duration = sorted([m for m in meetings], key=lambda x:int(get_duration(x).total_seconds()), reverse=True) + + for m in by_duration: + print("{:>5} {:<45} {}".format(format_duration(m), m["meetingName"], get_formated_presenter_name(m))) + + def print_overview(secret: Secret, bbb_url: Url): - d = request_meetings(secret, bbb_url) + """ + Get the meetings and print out an overview of the current bbb-usage + """ + meetings = get_meetings(secret, bbb_url) - # If there is more than one meeting the type is XmlListConfig otherwise XmlDictConfig - if type(d["meetings"]["meeting"]) is XmlListConfig: - n_running = len([m for m in d["meetings"]["meeting"] if m["running"] == "true"]) - n_recording = len([m for m in d["meetings"]["meeting"] if m["recording"] == "true"]) - n_participants = sum([int(m["participantCount"]) for m in d["meetings"]["meeting"] if m["running"] == "true"]) - n_listeners = sum([int(m["listenerCount"]) for m in d["meetings"]["meeting"] if m["running"] == "true"]) - n_voice = sum([int(m["voiceParticipantCount"]) for m in d["meetings"]["meeting"] if m["running"] == "true"]) - n_video = sum([int(m["videoCount"]) for m in d["meetings"]["meeting"] if m["running"] == "true"]) - n_moderator = sum([int(m["moderatorCount"]) for m in d["meetings"]["meeting"] if m["running"] == "true"]) - biggest_room = max([m for m in d["meetings"]["meeting"] if m["running"] == "true"], key=lambda x:int(x['participantCount'])) - rooms_by_size = sorted([m for m in d["meetings"]["meeting"] if m["running"] == "true"], key=lambda x:int(x['participantCount']), reverse=True) - elif type(d["meetings"]["meeting"]) is XmlDictConfig: - m = d["meetings"]["meeting"][0] - n_running = 1 - if m["recording"] == "true": - n_recording = 1 - else: - n_recording = 0 - n_participants = int(m["participantCount"]) - n_listeners = int(m["listenerCount"]) - n_voice = int(m["voiceParticipantCount"]) - n_video = int(m["videoCount"]) - n_moderator = int(m["moderatorCount"]) - biggest_room = m - else: - print("It appears there are no rooms running.") + if len(meetings) == 0: + print("There are no meetings running now.") exit() + n_running = len(meetings) + n_recording = len([m for m in meetings if m["recording"] == "true"]) + n_participants = sum([int(m["participantCount"]) for m in meetings]) + n_listeners = sum([int(m["listenerCount"]) for m in meetings]) + n_voice = sum([int(m["voiceParticipantCount"]) for m in meetings]) + n_video = sum([int(m["videoCount"]) for m in meetings]) + n_moderator = sum([int(m["moderatorCount"]) for m in meetings]) + print("MEETINGS on {}:".format(bbb_url)) print(" ├─── {:>4} running".format(n_running)) print(" └─── {:>4} recording".format(n_recording)) @@ -198,12 +246,13 @@ def print_overview(secret: Secret, bbb_url: Url): print(" └─ {:>4} moderators".format(n_moderator)) print() - print("Most participants ({}): {}, Presenter: {}".format(biggest_room["participantCount"], biggest_room["meetingName"], get_formated_presenter_name(biggest_room))) - + print_leaderboard(meetings, "participantCount") + print() + print_leaderboard(meetings, "videoCount") + print() + print_leaderboard(meetings, "voiceParticipantCount") print() - print("LEADERBOARD (Participants)") - for m in rooms_by_size: - print("{:>5} {:<45} {}".format(m["participantCount"], m["meetingName"], get_formated_presenter_name(m))) + print_duration_leaderboard(meetings) -- GitLab