Source code for bigbluebutton.api.attendee

"""Data structures for manageing meeting attendees"""

import logging
import webbrowser
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, Optional

from ._caching import cache
from .util import camel_to_snake, snake_to_camel, to_field_type

if TYPE_CHECKING:  # pragma: no cover
    from .meeting import Meeting

logger = logging.getLogger(__name__)


[docs]class Role(Enum): """Enumeration of roles an attendee can have""" MODERATOR = "MODERATOR" VIEWER = "VIEWER" DIAL_IN_USER = "DIAL-IN-USER"
[docs]@dataclass class Attendee: """One attendee that participates in one meeting. This object holds the information about one participant and is linked to exactly one meeting. """ meeting: "Meeting" # noqa: F821 full_name: str user_id: Optional[str] = None role: Optional[Role] = field(default=None, compare=False) is_presenter: bool = field(default=False, compare=False) is_listening_only: bool = field(default=False, compare=False) has_joined_voice: bool = field(default=False, compare=False) has_video: bool = field(default=False, compare=False) client_type: Optional[str] = field(default=None, compare=False) auth_token: Optional[str] = field(default=None, compare=False, init=False) session_token: Optional[str] = field(default=None, compare=False, init=False) url: Optional[str] = field(default=None, compare=False, init=False) def __post_init__(self): """Self-register in linked meeting.""" self.meeting.attendees[self.full_name] = self
[docs] def join(self, browser: bool = False, do_join: bool = True) -> Optional[str]: """Join an attendee corresponding to this object into the meeting it is linked to. To request the join, this method can either call the API directly and return the URL to the (HTML5) client, or only construct the API call URL and then hand it off to the default browser (if the browser argument is set to True) or return it plain without calling (if the do_join argument is set to False). In BigBlueButton's default configuration, in addition to the session token in the client URL, a valid JSESSIONID cookie is required, so using the client URL outside the original request scope only works if the server supports it. """ logger.info( f"Joining meeting {self.meeting.meeting_id} on server {self.meeting.api.name} " f"as {self.full_name}, role {self.role}" ) url_args: Dict[str, str] = {} if not self.meeting.meeting_id: raise ValueError("Cannot join meeting with unknown ID.") url_args["meetingID"] = self.meeting.meeting_id url_args["fullName"] = self.full_name if self.user_id: url_args["userID"] = self.user_id if self.role == Role.MODERATOR and self.meeting.moderator_pw: url_args["password"] = self.meeting.moderator_pw elif self.role == Role.VIEWER and self.meeting.attendee_pw: url_args["password"] = self.meeting.attendee_pw else: raise ValueError(f"Unknown role {self.role} or unavailable password") url_args["createTime"] = str(self.meeting.create_time) if browser or not do_join: url_args["redirect"] = "true" url = self.meeting.api._build_url("join", url_args) if browser: logger.info("Handing join request off to default browser") webbrowser.open(url) return url elif do_join: url_args["redirect"] = "false" logger.debug("Sending join request") res = self.meeting.api._request("join", url_args) self._update_from_response(res) return self.url
def _update_from_response(self, res: Dict[str, Any]) -> None: for name, value in res.items(): if name == "role": self.role = Role(value) else: snake_name = camel_to_snake(name) if hasattr(self, snake_name): setattr(self, snake_name, to_field_type(self, snake_name, value))
[docs] def to_dict(self, *args: str, **kwargs: str) -> Dict[str, Any]: """Return relevant data of this attendee as a dictionary. The dictionary can be used to build an XML document compatible with BigBlueButton API clients. If names of attributes are passed as positional arguments, only these attributes are returned in the dictionary. If attribute names are passed as names of keyword arguments, they are renamed to the string passed as value in the dictionary. """ res: Dict[str, Any] = {} for name, value in self.__dict__.items(): if args and name not in args and name not in kwargs: continue if name == "meeting": res["meetingID"] = self.meeting.meeting_id elif value is not None: if name in kwargs: camel_name = kwargs[name] else: camel_name = snake_to_camel(name) if isinstance(value, bool): str_value = "true" if value else "false" else: str_value = str(value) res[camel_name] = str_value return res
[docs] @classmethod def get_kwargs_from_url_args( cls, urlargs: Dict[str, str], meeting: "Meeting" ) -> Dict[str, Any]: """Construct a dictionary suitable for passing as kwargs to the constructor. The passed urlargs are expected to be a dictionary of URL arguments following the BigBlueButton HTTP API schema. This is useful to generate an attendee object from a URL call from a foreign BBB client, an API reply, or comparable things. """ kwargs: Dict[str, Any] = {} for name, value in urlargs.items(): if name == "password": # Determine role by used password if value == meeting.attendee_pw: kwargs["role"] = Role.VIEWER elif value == meeting.moderator_pw: kwargs["role"] = Role.MODERATOR else: raise ValueError("Invalid password passed, could not determine role") elif name == "meetingID": if value != meeting.meeting_id: raise ValueError("Meeting ID does not match") elif name == "createTime": if int(value) != meeting.create_time: raise ValueError("createTime does not match actual meeting parameters") else: snake_name = camel_to_snake(name) kwargs[snake_name] = to_field_type(cls, snake_name, value) return kwargs