├── .gitignore ├── LICENSE ├── README.md ├── bigbluebutton_api_python ├── __init__.py ├── bigbluebutton.py ├── core │ ├── __init__.py │ ├── apimethod.py │ ├── attendance.py │ ├── attendee.py │ ├── meeting.py │ ├── meetinginfo.py │ ├── metaData.py │ └── record.py ├── exception │ ├── __init__.py │ └── bbbexception.py ├── parameters │ ├── __init__.py │ └── bbbmodule.py ├── responses │ ├── __init__.py │ ├── apiversion.py │ ├── base.py │ ├── createmeeting.py │ ├── deleterecordings.py │ ├── endmeeting.py │ ├── getattendance.py │ ├── getmeetinginfo.py │ ├── getmeetings.py │ ├── getrecordings.py │ ├── ismeetingrunning.py │ ├── publishrecordings.py │ ├── setconfigxml.py │ └── updaterecordings.py └── util │ ├── __init__.py │ └── urlbuilder.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # .idea 104 | .idea/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BigBlueButton Python API 2 | 3 | Python wrapper for BigBlueButton api, more information about BigBlueButton api can be found [here](http://docs.bigbluebutton.org/dev/api.html 'API doc'). 4 | 5 | ## Installation 6 | The project has been uploaded to pypi, and you can view the library from [here](https://pypi.org/project/bigbluebutton-api-python/ 'pypi'). You can simply download the library by 7 | ```shell 8 | pip install bigbluebutton_api_python 9 | ``` 10 | 11 | You can also install the latest from this repo with 12 | ```shell 13 | pip install git+git://github.com/101t/bigbluebutton-api-python.git 14 | ``` 15 | 16 | 17 | ## Example 18 | Example to use the library: 19 | ```python 20 | from bigbluebutton_api_python import BigBlueButton 21 | 22 | b = BigBlueButton('your BBB server url', 'your server credential') 23 | 24 | # get api version 25 | print(b.get_api_version().get_version()) 26 | ``` 27 | ## Others Example 28 | 29 | ```python 30 | from bigbluebutton_api_python import BigBlueButton 31 | 32 | b = BigBlueButton('your BBB server url', 'your server credential') 33 | 34 | #params 35 | dict = { 'moderatorPW':'pw' } 36 | #use create meeting 37 | print(b.create_meeting ('room',params=dict)) 38 | #get info 39 | print(b.get_meeting_info('room')) 40 | #get url 41 | print(b.get_join_meeting_url('user','fake2', 'pw')) 42 | ``` 43 | More Docs [here](https://www.pydoc.io/pypi/bigbluebutton-api-python-0.0.2/autoapi/bigbluebutton/index.html). 44 | 45 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | name = "bigbluebutton_api_python" 3 | from .bigbluebutton import BigBlueButton -------------------------------------------------------------------------------- /bigbluebutton_api_python/bigbluebutton.py: -------------------------------------------------------------------------------- 1 | from .core import ApiMethod 2 | from .responses import ( 3 | ApiVersionResponse, 4 | CreateMeetingResponse, 5 | DeleteRecordingsResponse, 6 | EndMeetingResponse, 7 | GetMeetingInfoResponse, 8 | GetMeetingsResponse, 9 | GetRecordingsResponse, 10 | IsMeetingRunningResponse, 11 | PublishRecordingsResponse, 12 | SetConfigXMLResponse, 13 | UpdateRecordingsResponse, 14 | GetAttendanceResponse 15 | ) 16 | from .exception import BBBException 17 | from .util import UrlBuilder 18 | from .parameters import BBBModule 19 | import sys 20 | import json 21 | from jxmlease import parse 22 | from hashlib import sha1 23 | if sys.version_info[0] == 2: 24 | from urllib2 import urlopen ,Request 25 | from urllib import quote,urlencode 26 | else: 27 | from urllib.request import Request, urlopen 28 | from urllib.parse import urlencode 29 | from urllib.request import quote 30 | 31 | class BigBlueButton: 32 | 33 | def __init__(self, bbbServerBaseUrl, securitySalt): 34 | self.__urlBuilder = UrlBuilder(bbbServerBaseUrl, securitySalt) 35 | 36 | def get_api_version(self): 37 | response = self.__send_api_request(ApiMethod.VERSION) 38 | return ApiVersionResponse(response) 39 | 40 | def create_meeting(self, meeting_id, params={}, meta={}, slides=None): 41 | params["meetingID"] = meeting_id 42 | if meta != {}: 43 | for key, val in meta.items(): 44 | params["meta_" + key] = val 45 | if slides and isinstance(slides, BBBModule): 46 | response = self.__send_api_request(ApiMethod.CREATE, params, slides.to_xml()) 47 | else: 48 | response = self.__send_api_request(ApiMethod.CREATE, params) 49 | return CreateMeetingResponse(response) 50 | 51 | def get_join_meeting_url(self, full_name, meeting_id, password, params={}): 52 | params["fullName"] = full_name 53 | params["meetingID"] = meeting_id 54 | params["password"] = password 55 | response = self.__urlBuilder.buildUrl(ApiMethod.JOIN, params) 56 | return response 57 | 58 | def is_meeting_running(self, meeting_id): 59 | params = {"meetingID": meeting_id} 60 | response = self.__send_api_request(ApiMethod.IS_MEETING_RUNNING, params) 61 | return IsMeetingRunningResponse(response) 62 | 63 | def end_meeting(self, meeting_id, password): 64 | params = {"meetingID": meeting_id, "password": password} 65 | response = self.__send_api_request(ApiMethod.END, params) 66 | return EndMeetingResponse(response) 67 | 68 | def get_meeting_info(self, meeting_id): 69 | params = {"meetingID": meeting_id} 70 | response = self.__send_api_request(ApiMethod.GET_MEETING_INFO, params) 71 | return GetMeetingInfoResponse(response) 72 | 73 | def get_meetings(self): 74 | response = self.__send_api_request(ApiMethod.GET_MEETINGS) 75 | return GetMeetingsResponse(response) 76 | 77 | def get_recordings(self, meeting_id): 78 | params = {"meetingID": meeting_id} 79 | response = self.__send_api_request(ApiMethod.GET_RECORDINGS, params) 80 | return GetRecordingsResponse(response) 81 | 82 | def publish_recordings(self, recording_id, publish=True): 83 | params = {"recordID": recording_id, "publish": "true" if publish else "false"} 84 | response = self.__send_api_request(ApiMethod.PUBLISH_RECORDINGS, params) 85 | return PublishRecordingsResponse(response) 86 | 87 | def delete_recordings(self, recording_id): 88 | params = {"recordID": recording_id} 89 | response = self.__send_api_request(ApiMethod.DELETE_RECORDINGS, params) 90 | return DeleteRecordingsResponse(response) 91 | 92 | def update_recordings(self, recording_id, meta={}): 93 | params = {"recordID": recording_id} 94 | if meta != {}: 95 | for key, val in meta.items(): 96 | params["meta_" + key] = val 97 | response = self.__send_api_request(ApiMethod.UPDATE_RECORDINGS, params) 98 | return UpdateRecordingsResponse(response) 99 | 100 | def get_attendance(self, meeting_id, meta={}): 101 | params = {"meetingID": meeting_id} 102 | if meta != {}: 103 | for key, val in meta.items(): 104 | params["meta_" + key] = val 105 | response = self.__send_api_request(ApiMethod.GET_ATTENDANCE, params) 106 | return GetAttendanceResponse(response) 107 | 108 | # get default config.xml, if file_path is not given, this function will return response 109 | # as ElementTree.Element, otherwise it saves the response to the specific file path 110 | def get_default_config_xml(self, file_path=None): 111 | url = self.__urlBuilder.buildUrl(ApiMethod.GET_DEFAULT_CONFIG_XML) 112 | response = urlopen(url).read() 113 | if file_path is None: 114 | return response 115 | else: 116 | with open(file_path, 'w') as f: 117 | f.write(response.decode('utf-8')) 118 | 119 | def set_config_xml(self, meeting_id, xml): 120 | secret = ApiMethod.SET_CONFIG_XML + "configXML=" + quote(xml) 121 | secret += "&meetingID=" + meeting_id 122 | secret += self.__urlBuilder.securitySalt 123 | 124 | securityStr = sha1(secret.encode('utf-8')).hexdigest() 125 | 126 | response = self.__send_api_request(ApiMethod.SET_CONFIG_XML, data={"checksum": securityStr, 127 | "configXML": quote(xml), 128 | "meetingID": meeting_id}) 129 | return SetConfigXMLResponse(response) 130 | 131 | def _parse_response(self, response): 132 | # parse response to the correct form 133 | content_type = response.getheader('Content-Type') 134 | response = response.read() 135 | match content_type: 136 | case "application/json": 137 | return json.loads(response)["response"] 138 | case _: 139 | try: 140 | return parse(response)["response"] 141 | except Exception as e: 142 | raise BBBException("XMLSyntaxError", e) 143 | 144 | def __send_api_request(self, api_call, params={}, data=None): 145 | url = self.__urlBuilder.buildUrl(api_call, params) 146 | 147 | # if data is none, then we send a GET request, if not, then we send a POST request 148 | if data is None: 149 | response = urlopen(url, timeout=10) 150 | else: 151 | if isinstance(data, str): 152 | request = Request(url, data=bytes(data, "utf8"), headers={'Content-Type': 'application/xml'}) 153 | response = urlopen(request, timeout=10) 154 | else: 155 | response = urlopen(url, timeout=10, data=urlencode(data).encode()) 156 | 157 | rawXml = self._parse_response(response) 158 | # get default config xml and get attendance requests will simply return the xml file without 159 | # returncode, so it will cause an error when try to check the return code 160 | if api_call not in (ApiMethod.GET_DEFAULT_CONFIG_XML, ApiMethod.GET_ATTENDANCE): 161 | if rawXml["returncode"] == "FAILED": 162 | raise BBBException(rawXml["messageKey"], 163 | rawXml["message"]) 164 | 165 | return rawXml 166 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .apimethod import ApiMethod 2 | from .attendee import Attendee 3 | from .meeting import Meeting 4 | from .meetinginfo import MeetingInfo 5 | from .record import Record 6 | from .metaData import MetaData 7 | from .attendance import Attendance -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/apimethod.py: -------------------------------------------------------------------------------- 1 | import sys 2 | if sys.version_info[0] == 2: 3 | "2.x" 4 | from abc import ABCMeta 5 | class ApiMethod: 6 | __metaclass__ = ABCMeta 7 | VERSION = '' 8 | CREATE = 'create' 9 | JOIN = 'join' 10 | END = 'end' 11 | IS_MEETING_RUNNING = 'isMeetingRunning' 12 | GET_MEETING_INFO = 'getMeetingInfo' 13 | GET_MEETINGS = 'getMeetings' 14 | GET_DEFAULT_CONFIG_XML = 'getDefaultConfigXML' 15 | SET_CONFIG_XML = 'setConfigXML' 16 | GET_RECORDINGS = 'getRecordings' 17 | PUBLISH_RECORDINGS = 'publishRecordings' 18 | DELETE_RECORDINGS = 'deleteRecordings' 19 | UPDATE_RECORDINGS = 'updateRecordings' 20 | GET_ATTENDANCE = 'getAttendance' 21 | 22 | else: 23 | "3.x" 24 | from abc import ABC 25 | class ApiMethod(ABC): 26 | VERSION = '' 27 | CREATE = 'create' 28 | JOIN = 'join' 29 | END = 'end' 30 | IS_MEETING_RUNNING = 'isMeetingRunning' 31 | GET_MEETING_INFO = 'getMeetingInfo' 32 | GET_MEETINGS = 'getMeetings' 33 | GET_DEFAULT_CONFIG_XML = 'getDefaultConfigXML' 34 | SET_CONFIG_XML = 'setConfigXML' 35 | GET_RECORDINGS = 'getRecordings' 36 | PUBLISH_RECORDINGS = 'publishRecordings' 37 | DELETE_RECORDINGS = 'deleteRecordings' 38 | GET_ATTENDANCE = 'getAttendance' -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/attendance.py: -------------------------------------------------------------------------------- 1 | class Attendance: 2 | def __init__(self, xml): 3 | self.__extId = xml["extId"] 4 | self.__name = xml["name"] 5 | self.__startTime = xml["createdOn"] 6 | self.__endTime = xml["endedOn"] 7 | self.__users = {} 8 | if xml.get("users", []): 9 | self.set_users(xml) 10 | 11 | def set_users(self, xml): 12 | for user in xml["users"]: 13 | for attendee in xml["users"][user]["intIds"]: 14 | self.__users[xml["users"][user]["name"]] = { 15 | "registeredOn": xml["users"][user]["intIds"][attendee]["registeredOn"], 16 | "leftOn": xml["users"][user]["intIds"][attendee]["leftOn"], 17 | "userLeftFlag": xml["users"][user]["intIds"][attendee]["userLeftFlag"] 18 | } 19 | 20 | def get_users(self): 21 | return self.__users 22 | 23 | def get_name(self): 24 | return self.__name 25 | 26 | def get_starttime(self): 27 | return self.__startTime 28 | 29 | def get_endtime(self): 30 | return self.__endTime 31 | 32 | def get_meta(self, name): 33 | return self.meta.get_meta(name) 34 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/attendee.py: -------------------------------------------------------------------------------- 1 | class Attendee: 2 | def __init__(self, xml): 3 | self.__userID = xml["userID"] 4 | self.__fullName = xml["fullName"] 5 | self.__role = xml["role"] 6 | self.__isPresenter = xml["isPresenter"] == 'true' 7 | self.__isListeningOnly = xml["isListeningOnly"] == 'true' 8 | self.__hasJoinedVoice = xml["hasJoinedVoice"] == 'true' 9 | self.__hasVideo = xml["hasVideo"] == 'true' 10 | 11 | def get_userid(self): 12 | return self.__userID 13 | 14 | def get_fullname(self): 15 | return self.__fullName 16 | 17 | def get_role(self): 18 | return self.__role 19 | 20 | def is_presenter(self): 21 | return self.__isPresenter 22 | 23 | def is_listening_only(self): 24 | return self.__isListeningOnly 25 | 26 | def has_joined_voice(self): 27 | return self.__hasJoinedVoice 28 | 29 | def has_video(self): 30 | return self.__hasVideo 31 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/meeting.py: -------------------------------------------------------------------------------- 1 | from .metaData import MetaData 2 | 3 | class Meeting(object): 4 | def __init__(self, xml): 5 | self.__meetingId = xml["meetingID"] 6 | self.__meetingName = xml["meetingName"] 7 | self.__creationTime = float(xml["createTime"]) 8 | self.__creationDate = xml["createDate"] 9 | self.__voiceBridge = xml["voiceBridge"] 10 | self.__dialNumber = xml["dialNumber"] 11 | self.__attendeePassword = xml["attendeePW"] 12 | self.__moderatorPassword = xml["moderatorPW"] 13 | self.__hasBeenForciblyEnded = xml["hasBeenForciblyEnded"] == 'true' 14 | self.__isRunning = xml["running"] == 'true' 15 | self.__participantCount = int(xml["participantCount"]) 16 | self.__listenerCount = int(xml["listenerCount"]) 17 | self.__voiceParticipantCount = int(xml["voiceParticipantCount"]) 18 | self.__videoCount = int(xml["videoCount"]) 19 | self.__duration = int(xml["duration"]) 20 | self.__hasUserJoined = xml["hasUserJoined"] == 'true' 21 | self.__meta = MetaData(xml["metadata"]) 22 | 23 | def get_meetingid(self): 24 | return self.__meetingId 25 | 26 | def get_meetingname(self): 27 | return self.__meetingName 28 | 29 | def get_createtime(self): 30 | return self.__creationTime 31 | 32 | def get_createdate(self): 33 | return self.__creationDate 34 | 35 | def get_voicebridge(self): 36 | return self.__voiceBridge 37 | 38 | def get_dialnumber(self): 39 | return self.__dialNumber 40 | 41 | def get_attendeepw(self): 42 | return self.__attendeePassword 43 | 44 | def get_moderatorpw(self): 45 | return self.__moderatorPassword 46 | 47 | def get_participantcount(self): 48 | return self.__participantCount 49 | 50 | def get_listenercount(self): 51 | return self.__listenerCount 52 | 53 | def get_voiceparticipantcount(self): 54 | return self.__voiceParticipantCount 55 | 56 | def get_videocount(self): 57 | return self.__videoCount 58 | 59 | def get_duration(self): 60 | return self.__duration 61 | 62 | def has_user_joined(self): 63 | return self.__hasUserJoined 64 | 65 | def has_been_forcibly_ended(self): 66 | return self.__hasBeenForciblyEnded 67 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/meetinginfo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .meeting import Meeting 3 | 4 | 5 | class MeetingInfo(Meeting): 6 | def __init__(self, xml): 7 | if sys.version_info[0] == 2: 8 | super(MeetingInfo, self).__init__(xml) 9 | else: 10 | super().__init__(xml) 11 | 12 | self.__internalMeetingId = xml["internalMeetingID"] 13 | self.__recording = xml["recording"] == 'true' 14 | self.__startTime = float(xml["startTime"]) 15 | self.__endTime = float(xml["endTime"]) 16 | self.__maxUsers = xml["maxUsers"] 17 | self.__moderatorCount = xml["moderatorCount"] 18 | 19 | def get_internal_meetingid(self): 20 | return self.__internalMeetingId 21 | 22 | def get_start_time(self): 23 | return self.__startTime 24 | 25 | def get_end_time(self): 26 | return self.__endTime 27 | 28 | def get_max_users(self): 29 | return self.__maxUsers 30 | 31 | def get_moderator_count(self): 32 | return self.__moderatorCount 33 | 34 | def is_recording(self): 35 | return self.__recording 36 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/metaData.py: -------------------------------------------------------------------------------- 1 | class MetaData: 2 | def __init__(self, xml): 3 | self.meta = {} 4 | for element in xml: 5 | self.meta[element] = xml[element] 6 | 7 | def get_meta(self, name): 8 | if self.meta.keys().__contains__(name): 9 | return self.meta[name] 10 | return None 11 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/core/record.py: -------------------------------------------------------------------------------- 1 | from .metaData import MetaData 2 | 3 | 4 | class Record: 5 | def __init__(self, xml): 6 | self.__recordId = xml["recordID"] 7 | self.__meetingId = xml["meetingID"] 8 | self.__name = xml["name"] 9 | self.__isPublished = xml["published"] == 'true' 10 | self.__state = xml["state"] 11 | self.__startTime = xml["startTime"] 12 | 13 | self.__endTime = xml["endTime"] 14 | self.__playbackType = xml["playback"]["format"]["type"] 15 | self.__playbackUrl = xml["playback"]["format"]["url"] 16 | self.__playbackLength = xml["playback"]["format"]["length"] 17 | self.meta = MetaData(xml["metadata"]) 18 | 19 | def get_recordid(self): 20 | return self.__recordId 21 | 22 | def get_meetingid(self): 23 | return self.__meetingId 24 | 25 | def is_published(self): 26 | return self.__isPublished 27 | 28 | def get_name(self): 29 | return self.__name 30 | 31 | def get_state(self): 32 | return self.__state 33 | 34 | def get_starttime(self): 35 | return self.__startTime 36 | 37 | def get_endtime(self): 38 | return self.__endTime 39 | 40 | def get_playbacktype(self): 41 | return self.__playbackType 42 | 43 | def get_playbackurl(self): 44 | return self.__playbackUrl 45 | 46 | def get_playbacklength(self): 47 | return self.__playbackLength 48 | 49 | def get_meta(self, name): 50 | return self.meta.get_meta(name) 51 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/exception/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .bbbexception import BBBException -------------------------------------------------------------------------------- /bigbluebutton_api_python/exception/bbbexception.py: -------------------------------------------------------------------------------- 1 | 2 | class BBBException(Exception): 3 | HTTP_ERROR = "httpError" 4 | NOT_FOUND = "notFound" 5 | NO_ACTION = "noActionSpecified" 6 | ID_NOT_UNIQUE = "idNotUnique" 7 | NOT_STARTED = "notStarted" 8 | ALREADY_ENDED = "alreadyEnded" 9 | INTERNAL_ERROR = "internalError" 10 | UNREACHABLE_SERVER = "unreachableServer" 11 | INVALID_RESPONSE = "invalidResponse" 12 | GENERAL_ERROR = "generalError" 13 | CHECKSUM_ERROR = "checksumError" 14 | INVALID_RECORDING_STATE = "invalidRecordingState" 15 | CONFIG_XML_ERROR = "configXMLError" 16 | XML_SYNTAX_ERROR = "XMLSyntaxError" 17 | 18 | 19 | def __init__(self, key, message): 20 | Exception.__init__(self, message) 21 | self.key = key 22 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/parameters/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .bbbmodule import BBBModule -------------------------------------------------------------------------------- /bigbluebutton_api_python/parameters/bbbmodule.py: -------------------------------------------------------------------------------- 1 | 2 | import os, base64 3 | 4 | class BBBModule: 5 | URL = "url" 6 | FILE = "file" 7 | base64s = "base64s" 8 | 9 | def __init__(self): 10 | self.__urls = [] 11 | self.__files = [] 12 | self.__base64s = [] 13 | 14 | def add_slide(self, type, value, name = None): 15 | if type == self.URL: 16 | self.__urls.append(value) 17 | elif type == self.FILE: 18 | self.__files.append(value) 19 | elif type == self.base64s: 20 | self.__base64s.append([name, value]) 21 | 22 | def has_slides(self): 23 | return len(self.__urls) != 0 or len(self.__files) != 0 or len(self.__base64s) != 0 24 | 25 | def to_xml(self): 26 | if self.has_slides(): 27 | xml = "" 28 | xml += self.__slides_to_xml() 29 | xml += "" 30 | return xml 31 | else: 32 | return "" 33 | 34 | def __slides_to_xml(self): 35 | xml = "" 36 | for url in self.__urls: 37 | xml += "" 38 | 39 | for [name, value] in self.__base64s: 40 | xml += "" + value + "" 41 | 42 | for single_file in self.__files: 43 | if os.path.exists(single_file): 44 | xml += "" 45 | with open(single_file, 'r') as f: 46 | xml += base64.encodestring(f.read()) 47 | xml += "" 48 | 49 | xml += "" 50 | 51 | return xml 52 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .apiversion import ApiVersionResponse 3 | from .createmeeting import CreateMeetingResponse 4 | from .deleterecordings import DeleteRecordingsResponse 5 | from .endmeeting import EndMeetingResponse 6 | from .getmeetinginfo import GetMeetingInfoResponse 7 | from .getmeetings import GetMeetingsResponse 8 | from .getrecordings import GetRecordingsResponse 9 | from .ismeetingrunning import IsMeetingRunningResponse 10 | from .publishrecordings import PublishRecordingsResponse 11 | from .setconfigxml import SetConfigXMLResponse 12 | from .updaterecordings import UpdateRecordingsResponse 13 | from .getattendance import GetAttendanceResponse -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/apiversion.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | 4 | class ApiVersionResponse(BaseResponse): 5 | def get_version(self): 6 | return self.get_field("version") 7 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/base.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | if sys.version_info[0] == 2: 5 | from abc import ABCMeta 6 | 7 | class BaseResponse(dict): 8 | __metaclass__ = ABCMeta 9 | 10 | def __init__(self, rawXml): 11 | dict.__init__(self, xml=rawXml) 12 | self.rawXml = rawXml 13 | 14 | def get_field(self, label): 15 | return self.rawXml[label] 16 | 17 | def get_return_code(self): 18 | return self.get_field("returncode") 19 | 20 | def get_message_key(self): 21 | return self.get_field("messageKey") 22 | 23 | def get_message(self): 24 | return self.get_field("message") 25 | else: 26 | from abc import ABC 27 | 28 | class BaseResponse(ABC, dict): 29 | def __init__(self, rawXml): 30 | dict.__init__(self, xml=rawXml) 31 | self.rawXml = rawXml 32 | 33 | def get_field(self, label): 34 | return self.rawXml[label] 35 | 36 | def get_return_code(self): 37 | return self.get_field("returncode") 38 | 39 | def get_message_key(self): 40 | return self.get_field("messageKey") 41 | 42 | def get_message(self): 43 | return self.get_field("message") -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/createmeeting.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | from datetime import datetime 3 | 4 | class CreateMeetingResponse(BaseResponse): 5 | def get_meetingid(self): 6 | return self.get_field("meetingID") 7 | 8 | def get_attendee_pw(self): 9 | return self.get_field("attendeePW") 10 | 11 | def get_moderator_pw(self): 12 | return self.get_field("moderatorPW") 13 | 14 | def get_create_time(self): 15 | return datetime.fromtimestamp(int(self.get_field("createTime")) / 1e3).strftime('%Y-%m-%d %H:%M:%S') 16 | 17 | def has_been_forcibly_ended(self): 18 | field = self.get_field("hasBeenForciblyEnded") 19 | return field == "true" or (isinstance(field, bool) and field) 20 | 21 | def get_voice_bridge(self): 22 | return self.get_field("voiceBridge") 23 | 24 | def get_dial_number(self): 25 | return self.get_field("dialNumber") -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/deleterecordings.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class DeleteRecordingsResponse(BaseResponse): 4 | def is_deleted(self): 5 | field = self.get_field("deleted") 6 | return field == "true" or (isinstance(field, bool) and field) -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/endmeeting.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class EndMeetingResponse(BaseResponse): 4 | pass -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/getattendance.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .base import BaseResponse 3 | from ..core.attendance import Attendance 4 | 5 | class GetAttendanceResponse(BaseResponse): 6 | def __init__(self, xml): 7 | if sys.version_info[0] == 2: 8 | super(GetAttendanceResponse, self).__init__(xml) 9 | else: 10 | super().__init__(xml) 11 | def get_attendance(self): 12 | attendances = [] 13 | meetings = self.get_field("meetings")["meeting"] 14 | if type(meetings) != list: 15 | meetings = [meetings] 16 | for meeting in meetings: 17 | attendances.append(Attendance(xml=meeting)) 18 | return attendances 19 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/getmeetinginfo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .base import BaseResponse 3 | from ..core.meetinginfo import MeetingInfo 4 | from ..core.attendee import Attendee 5 | 6 | 7 | class GetMeetingInfoResponse(BaseResponse): 8 | def __init__(self, xml): 9 | if sys.version_info[0] == 2: 10 | super(GetMeetingInfoResponse, self).__init__(xml) 11 | else: 12 | super().__init__(xml) 13 | 14 | def get_meetinginfo(self): 15 | return MeetingInfo(xml=self.rawXml) 16 | 17 | def get_attendees(self): 18 | attendees = [] 19 | 20 | field = self.get_field("attendees") 21 | if field == "": 22 | return [] 23 | 24 | obj = field["attendee"] 25 | if isinstance(obj, list): 26 | for attendeeXml in obj: 27 | attendees.append(Attendee(xml=attendeeXml)) 28 | else: 29 | attendees.append(Attendee(xml=obj)) 30 | 31 | return attendees 32 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/getmeetings.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | from ..core.meeting import Meeting 3 | import jxmlease 4 | 5 | class GetMeetingsResponse(BaseResponse): 6 | def get_meetings(self): 7 | meetings = [] 8 | 9 | try: 10 | if self.get_message_key() == "noMeetings": 11 | return [] 12 | except KeyError: 13 | pass 14 | 15 | meetings_data = self.get_field("meetings")["meeting"] 16 | if isinstance(meetings_data, jxmlease.dictnode.XMLDictNode): 17 | meetings.append(Meeting(meetings_data)) 18 | else: 19 | for meetingXml in self.get_field("meetings")["meeting"]: 20 | meetings.append(Meeting(meetingXml)) 21 | return meetings 22 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/getrecordings.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | from ..core.record import Record 3 | import jxmlease 4 | 5 | 6 | 7 | class GetRecordingsResponse(BaseResponse): 8 | 9 | def get_recordings(self): 10 | recordings = [] 11 | 12 | try: 13 | if self.get_message_key() == "noRecordings": 14 | return [] 15 | except KeyError: 16 | pass 17 | 18 | recordings_data = self.get_field("recordings")["recording"] 19 | if isinstance(recordings_data, jxmlease.dictnode.XMLDictNode): 20 | recordings.append(Record(recordings_data)) 21 | else: 22 | for recordingXml in self.get_field("recordings")["recording"]: 23 | recordings.append(Record(recordingXml)) 24 | return recordings 25 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/ismeetingrunning.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class IsMeetingRunningResponse(BaseResponse): 4 | def is_meeting_running(self): 5 | field = self.get_field("running") 6 | return field == "true" or (isinstance(field, bool) and field) -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/publishrecordings.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class PublishRecordingsResponse(BaseResponse): 4 | def is_published(self): 5 | field = self.get_field("published") 6 | return field == "true" or (isinstance(field, bool) and field) 7 | -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/setconfigxml.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class SetConfigXMLResponse(BaseResponse): 4 | def get_token(self): 5 | return self.get_field("token") -------------------------------------------------------------------------------- /bigbluebutton_api_python/responses/updaterecordings.py: -------------------------------------------------------------------------------- 1 | from .base import BaseResponse 2 | 3 | class UpdateRecordingsResponse(BaseResponse): 4 | def is_updated(self): 5 | field = self.get_field("updated") 6 | return field == "true" or (isinstance(field, bool) and field) -------------------------------------------------------------------------------- /bigbluebutton_api_python/util/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .urlbuilder import UrlBuilder -------------------------------------------------------------------------------- /bigbluebutton_api_python/util/urlbuilder.py: -------------------------------------------------------------------------------- 1 | 2 | from hashlib import sha1 3 | from re import match 4 | 5 | import sys 6 | if sys.version_info[0] == 2: 7 | from urllib import quote_plus 8 | else: 9 | try: 10 | from urllib.request import quote_plus 11 | except: 12 | from urllib.parse import quote_plus 13 | 14 | 15 | class UrlBuilder: 16 | def __init__(self, bbbServerBaseUrl, securitySalt): 17 | if not match('/[http|https]:\/\/[a-zA-Z1-9.]*\/bigbluebutton\/api\//', bbbServerBaseUrl): 18 | if not bbbServerBaseUrl.startswith("http://") and not bbbServerBaseUrl.startswith("https://"): 19 | bbbServerBaseUrl = "http://" + bbbServerBaseUrl 20 | if not bbbServerBaseUrl.endswith("/bigbluebutton/api/"): 21 | bbbServerBaseUrl = bbbServerBaseUrl[:(bbbServerBaseUrl.find("/", 8) 22 | if bbbServerBaseUrl.find("/", 8) != -1 else len(bbbServerBaseUrl))] + "/bigbluebutton/api/" 23 | 24 | self.securitySalt = securitySalt 25 | self.bbbServerBaseUrl = bbbServerBaseUrl 26 | 27 | def buildUrl(self, api_call, params={}): 28 | url = self.bbbServerBaseUrl 29 | url += api_call + "?" 30 | for key, value in params.items(): 31 | if isinstance(value, bool): 32 | value = "true" if value else "false" 33 | else: 34 | value = str(value) 35 | url += key + "=" + quote_plus(value) + "&" 36 | 37 | url += "checksum=" + self.__get_checksum(api_call, params) 38 | return url 39 | 40 | def __get_checksum(self, api_call, params={}): 41 | secret_str = api_call 42 | for key, value in params.items(): 43 | if isinstance(value, bool): 44 | value = "true" if value else "false" 45 | else: 46 | value = str(value) 47 | secret_str += key + "=" + quote_plus(value) + "&" 48 | if secret_str.endswith("&"): 49 | secret_str = secret_str[:-1] 50 | secret_str += self.securitySalt 51 | return sha1(secret_str.encode('utf-8')).hexdigest() 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup(name='bigbluebutton_api_python', 6 | version='0.0.12', 7 | description='Python library that provides access to the API of BigBlueButton', 8 | author='Tarek Kalaji, Yunkai Wang, Hicham Dachir', 9 | author_email='yunkai.wang@blindsidenetworks.com', 10 | url='https://github.com/yunkaiwang/bigbluebutton-api-python', 11 | packages=find_packages(), 12 | install_requires=[ 13 | 'jxmlease' 14 | ], 15 | ) 16 | --------------------------------------------------------------------------------