├── .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 |
--------------------------------------------------------------------------------