Skip to content

Commit ddbdb19

Browse files
committedMar 29, 2020
Initial commit
0 parents  commit ddbdb19

25 files changed

+1305
-0
lines changed
 

‎.gitignore

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# mypy
10+
.mypy_cache
11+
12+
# Distribution / packaging
13+
.Python
14+
env/
15+
build/
16+
develop-eggs/
17+
dist/
18+
downloads/
19+
eggs/
20+
.eggs/
21+
lib/
22+
lib64/
23+
parts/
24+
sdist/
25+
var/
26+
*.egg-info/
27+
.installed.cfg
28+
*.egg
29+
.pip_cache/
30+
31+
# Unit test / coverage reports
32+
htmlcov/
33+
.tox/
34+
.coverage
35+
.coverage.*
36+
.cache
37+
nosetests.xml
38+
coverage.xml
39+
*,cover
40+
.hypothesis/
41+
html-reports/
42+
reports/
43+
44+
# virtualenv
45+
.venv/
46+
venv/
47+
ENV/
48+
*venv*
49+
50+
# Editors, etc
51+
*.DS_Store*
52+
.idea
53+
.vscode
54+
*.swp
55+
.project
56+
.pydevproject
57+
.settings/

‎LICENSE.txt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright [2015] [Accountable Care Transactions, Inc]
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

‎README.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# zoomapi
2+
3+
[https://github.com/crista/zoomapi](https://github.com/crista/zoomapi)
4+
5+
Python wrapper around the [Zoom.us](http://zoom.us) REST API v2.
6+
7+
This work is based on [Zoomus](https://github.com/actmd/zoomus), but with OAuth support.
8+
9+
## Compatibility
10+
11+
Note, as this library heavily depends on the [requests](https://pypi.org/project/requests/) library, official compatibility is limited to the official compatibility of `requests`.
12+
13+
## Example Usage
14+
15+
### Create the client
16+
17+
```python
18+
import json
19+
from zoomus import ZoomClient
20+
21+
client = ZoomClient('API_KEY', 'API_SECRET')
22+
23+
user_list_response = client.user.list()
24+
user_list = json.loads(user_list_response.content)
25+
26+
for user in user_list['users']:
27+
user_id = user['id']
28+
print(json.loads(client.meeting.list(user_id=user_id).content))
29+
```
30+
31+
What one will note is that the returned object from a call using the client is a [requests](https://pypi.org/project/requests/) `Response` object. This is done so that if there is any error working with the API that one has complete control of handling all errors. As such, to actually get the list of users in the example above, one will have to load the JSON from the content of the `Response` object that is returned.
32+
33+
### Using with a manage context
34+
35+
```python
36+
with ZoomClient('API_KEY', 'API_SECRET') as client:
37+
user_list_response = client.users.list()
38+
...
39+
```
40+
41+
## Available methods
42+
43+
* client.user.create(...)
44+
* client.user.cust_create(...)
45+
* client.user.update(...)*
46+
* client.user.list(...)
47+
* client.user.pending(...)
48+
* client.user.get(...)
49+
* client.user.get_by_email(...)
50+
51+
* client.meeting.get(...)
52+
* client.meeting.end(...)
53+
* client.meeting.create(...)
54+
* client.meeting.delete(...)
55+
* client.meeting.list(...)
56+
* client.meeting.update(...)
57+
58+
* client.report.get_account_report(...)
59+
* client.report.get_user_report(...)
60+
61+
* client.webinar.create(...)
62+
* client.webinar.update(...)
63+
* client.webinar.delete(...)
64+
* client.webinar.list(...)
65+
* client.webinar.get(...)
66+
* client.webinar.end(...)
67+
* client.webinar.register(...)
68+

‎bot.ini

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[OAuth]
2+
client_id = lCkchHpkQSePsPLU6JrZsw
3+
client_secret = EBqk8HgpzXfv4tl1Zi8LwIVtI4nh6DCO
4+
5+
[JWT]
6+
api_key = AWGqDsi4SJOuZBTKNkR8Xw
7+
api_secret = RrEybd7htBLyJ7IFzfDGUbCfOY9fkY0VGc9d

‎jwtbot.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import json
2+
from zoomapi import JWTZoomClient
3+
4+
client = JWTZoomClient('AWGqDsi4SJOuZBTKNkR8Xw', 'RrEybd7htBLyJ7IFzfDGUbCfOY9fkY0VGc9d')
5+
6+
user_list_response = client.user.list()
7+
user_list = json.loads(user_list_response.content)
8+
9+
for user in user_list['users']:
10+
user_id = user['id']
11+
print(json.loads(client.meeting.list(user_id=user_id).content))
12+
13+
print ('---')
14+
15+
meetings_list = client.meeting.list(user_id='diva@metaverseink.com')
16+
print(json.loads(meetings_list.content))

‎oauthbot.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import json
2+
from zoomapi import OAuthZoomClient
3+
from configparser import ConfigParser
4+
from pyngrok import ngrok
5+
6+
parser = ConfigParser()
7+
parser.read("bot.ini")
8+
client_id = parser.get("OAuth", "client_id")
9+
client_secret = parser.get("OAuth", "client_secret")
10+
print(f'id: {client_id} secret: {client_secret}')
11+
12+
redirect_url = ngrok.connect(4000, "http")
13+
print("Redirect URL is", redirect_url)
14+
15+
client = OAuthZoomClient(client_id, client_secret, redirect_url)
16+
17+
user_response = client.user.get(id='me')
18+
user = json.loads(user_response.content)
19+
print(user)
20+
print ('---')
21+
22+
print(json.loads(client.meeting.list(user_id="me").content))
23+
client.chat_channels.list()
24+
channels = json.loads(client.chat_channels.list().content)["channels"]
25+
print(channels)
26+
for c in channels:
27+
print(c)
28+
if "test" in c.values():
29+
print("Found channel test", c["id"])
30+
print(client.chat_messages.post(to_channel=c["id"], message="Blah!"))

‎requirements-tests.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-r requirements.txt
2+
coverage==4.5.4
3+
mock==3.0.5
4+
nose==1.3.7
5+
tox==3.14.1
6+
black==19.10b0; python_version >= "3.6"
7+
pre-commit==1.20.0

‎requirements.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PyJWT==1.7.1
2+
requests==2.22.0
3+
requests_oauthlib==1.1.0
4+
configparser==5.0.0
5+
pyngrok==2.1.3

‎setup.cfg

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[bumpversion]
2+
current_version = 1.1.1
3+
commit = True
4+
tag = True
5+
6+
[bumpversion:file:setup.py]
7+
8+
[bumpversion:file:zoomapi/__init__.py]
9+
10+
[bdist_wheel]
11+
universal = 1
12+
13+
[flake8]
14+
ignore = D203
15+
exclude =
16+
.git,
17+
.tox,
18+
docs/conf.py,
19+
build,
20+
dist
21+
max-line-length = 119
22+
23+
[metadata]
24+
description-file = README.md
25+
26+
[nosetests]
27+
with-coverage = 1
28+
cover-package = zoomapi
29+
cover-branches = 1

‎setup.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import print_function, unicode_literals
2+
3+
from setuptools import setup
4+
import codecs
5+
import os
6+
import re
7+
8+
here = os.path.abspath(os.path.dirname(__file__))
9+
10+
11+
def read(file_paths, default=""):
12+
# intentionally *not* adding an encoding option to open
13+
try:
14+
with codecs.open(os.path.join(here, *file_paths), "r") as fh:
15+
return fh.read()
16+
except Exception:
17+
return default
18+
19+
20+
def find_version(file_paths):
21+
version_file = read(file_paths)
22+
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
23+
if version_match:
24+
return version_match.group(1)
25+
raise RuntimeError("Unable to find version string.")
26+
27+
28+
description = "Python client library for Zoom.us REST API v1 and v2"
29+
long_description = read("README.md", default=description)
30+
31+
setup(
32+
name="zoomus",
33+
version=find_version(["zoomus", "__init__.py"]),
34+
url="https://github.com/actmd/zoomus",
35+
license="Apache Software License",
36+
author="Zoomus Contributors",
37+
install_requires=["requests", "PyJWT"],
38+
author_email="zoomus@googlegroups.com",
39+
description=description,
40+
long_description=long_description,
41+
long_description_content_type="text/markdown",
42+
packages=["zoomus", "zoomus.components"],
43+
include_package_data=True,
44+
platforms="any",
45+
zip_safe=False,
46+
classifiers=[
47+
"Programming Language :: Python",
48+
"Programming Language :: Python :: 2",
49+
"Programming Language :: Python :: 2.7",
50+
"Programming Language :: Python :: 3",
51+
"Programming Language :: Python :: 3.5",
52+
"Programming Language :: Python :: 3.6",
53+
"Programming Language :: Python :: 3.7",
54+
"Development Status :: 5 - Production/Stable",
55+
"Natural Language :: English",
56+
"Environment :: Web Environment",
57+
"Intended Audience :: Developers",
58+
"License :: OSI Approved :: Apache Software License",
59+
"Operating System :: OS Independent",
60+
"Topic :: Internet",
61+
"Topic :: Office/Business",
62+
"Topic :: Software Development :: Libraries",
63+
],
64+
)

‎zoomapi/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Python wrapper around the Zoom.us REST API"""
2+
3+
from zoomapi.jwtclient import JWTZoomClient
4+
from zoomapi.oauthclient import OAuthZoomClient
5+
from zoomapi.client import ZoomClient
6+
7+
__all__ = ["ZoomClient", "JWTZoomClient", "OAuthZoomClient"]
8+
__version__ = "1.1.1"

‎zoomapi/bot.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import json
2+
from zoomapi import OAuthZoomClient
3+
from Config import ConfigParser
4+
from pyngrok import ngrok
5+
6+
parser = ConfigParser()
7+
parser.read("bot.ini")
8+
client_id = parser.get("OAuth", "client_id")
9+
client_secret = parser.get("OAuth", "client_secret")
10+
11+
redirect_url = ngrok.connect(4000, "http")
12+
print("Redirect URL is", redirect_url)
13+
14+
client = OAuthZoomClient(client_id, client_secret, redirect_url)
15+
16+
user_list_response = client.user.list()
17+
user_list = json.loads(user_list_response.content)
18+
19+
for user in user_list['users']:
20+
user_id = user['id']
21+
print(json.loads(client.meeting.list(user_id=user_id).content))
22+
23+
print ('---')
24+
25+
meetings_list = client.meeting.list(user_id='diva@metaverseink.com')
26+
print(json.loads(meetings_list.content))

‎zoomapi/client.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Zoom.us REST API Python Client"""
2+
3+
from zoomapi import components, util
4+
5+
API_BASE_URIS = "https://api.zoom.us/v2"
6+
7+
COMPONENT_CLASSES = {
8+
"user": components.user.UserComponentV2,
9+
"meeting": components.meeting.MeetingComponentV2,
10+
"report": components.report.ReportComponentV2,
11+
"webinar": components.webinar.WebinarComponentV2,
12+
"recording": components.recording.RecordingComponentV2,
13+
}
14+
15+
16+
class ZoomClient(util.ApiClient):
17+
"""Zoom.us REST API Python Client"""
18+
19+
"""Base URL for Zoom API"""
20+
21+
def __init__(
22+
self, api_key, api_secret, data_type="json", timeout=15
23+
):
24+
"""Create a new Zoom client
25+
26+
:param api_key: The Zooom.us API key
27+
:param api_secret: The Zoom.us API secret
28+
:param data_type: The expected return data type. Either 'json' or 'xml'
29+
:param timeout: The time out to use for API requests
30+
"""
31+
self.BASE_URI = API_BASE_URIS
32+
self.components = COMPONENT_CLASSES.copy()
33+
34+
super(ZoomClient, self).__init__(base_uri=self.BASE_URI, timeout=timeout)
35+
36+
# Setup the config details
37+
self.config = {
38+
"api_key": api_key,
39+
"api_secret": api_secret,
40+
"data_type": data_type,
41+
# "token": util.generate_jwt(api_key, api_secret),
42+
}
43+
44+
def __enter__(self):
45+
return self
46+
47+
def __exit__(self, exc_type, exc_val, exc_tb):
48+
return
49+
50+
def refresh_token(self):
51+
self.config["token"] = (
52+
util.generate_jwt(self.config["api_key"], self.config["api_secret"]),
53+
)
54+
55+
@property
56+
def api_key(self):
57+
"""The Zoom.us api_key"""
58+
return self.config.get("api_key")
59+
60+
@api_key.setter
61+
def api_key(self, value):
62+
"""Set the api_key"""
63+
self.config["api_key"] = value
64+
self.refresh_token()
65+
66+
@property
67+
def api_secret(self):
68+
"""The Zoom.us api_secret"""
69+
return self.config.get("api_secret")
70+
71+
@api_secret.setter
72+
def api_secret(self, value):
73+
"""Set the api_secret"""
74+
self.config["api_secret"] = value
75+
self.refresh_token()
76+
77+
@property
78+
def meeting(self):
79+
"""Get the meeting component"""
80+
return self.components.get("meeting")
81+
82+
@property
83+
def report(self):
84+
"""Get the report component"""
85+
return self.components.get("report")
86+
87+
@property
88+
def user(self):
89+
"""Get the user component"""
90+
return self.components.get("user")
91+
92+
@property
93+
def webinar(self):
94+
"""Get the webinar component"""
95+
return self.components.get("webinar")
96+
97+
@property
98+
def recording(self):
99+
"""Get the recording component"""
100+
return self.components.get("recording")

‎zoomapi/components/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Zoom.us REST API Python Client Components"""
2+
3+
from __future__ import absolute_import
4+
5+
from . import meeting, recording, report, user, webinar, chat_messages, chat_channels

‎zoomapi/components/base.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Zoom.us REST API Python Client"""
2+
3+
from zoomapi import util
4+
5+
class BaseComponent(util.ApiClient):
6+
"""A base component"""
7+
8+
def __init__(self, base_uri=None, config=None, timeout=15, **kwargs):
9+
"""Setup a base component
10+
11+
:param base_uri: The base URI to the API
12+
:param config: The config details
13+
:param timeout: The timeout to use for requests
14+
:param kwargs: Any other attributes. These will be added as
15+
attributes to the ApiClient object.
16+
"""
17+
super(BaseComponent, self).__init__(
18+
base_uri=base_uri, timeout=timeout, config=config, **kwargs
19+
)
20+
21+
def post_request(
22+
self, endpoint, params=None, data=None, headers=None, cookies=None
23+
):
24+
"""Helper function for POST requests
25+
26+
Since the Zoom.us API only uses POST requests and each post request
27+
must include all of the config data, this method ensures that all
28+
of that data is there
29+
30+
:param endpoint: The endpoint
31+
:param params: The URL parameters
32+
:param data: The data (either as a dict or dumped JSON string) to
33+
include with the POST
34+
:param headers: request headers
35+
:param cookies: request cookies
36+
:return: The :class:``requests.Response`` object for this request
37+
"""
38+
params = params or {}
39+
if headers is None:
40+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
41+
return super(BaseComponent, self).post_request(
42+
endpoint, params=params, data=data, headers=headers, cookies=cookies
43+
)

‎zoomapi/components/chat_channels.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Zoom.us REST API Python Client -- Chat Messages component"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
7+
class ChatChannelsComponentV2(base.BaseComponent):
8+
"""Component dealing with all chat channels related matters"""
9+
10+
def list(self, **kwargs):
11+
return self.get_request("/chat/users/me/channels")
12+

‎zoomapi/components/chat_messages.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Zoom.us REST API Python Client -- Chat Messages component"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
7+
class ChatMessagesComponentV2(base.BaseComponent):
8+
"""Component dealing with all chat messages related matters"""
9+
10+
def list(self, **kwargs):
11+
util.require_keys(kwargs, "id")
12+
return self.get_request(
13+
"/chat/users/{}/messages".format(kwargs.get("user_id")), params=kwargs
14+
)
15+
16+
def post(self, **kwargs):
17+
util.require_keys(kwargs, "message")
18+
return self.post_request("/chat/users/me/messages", data=kwargs)

‎zoomapi/components/meeting.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Zoom.us REST API Python Client"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
7+
class MeetingComponent(base.BaseComponent):
8+
"""Component dealing with all meeting related matters"""
9+
10+
def list(self, **kwargs):
11+
util.require_keys(kwargs, "host_id")
12+
if kwargs.get("start_time"):
13+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
14+
return self.post_request("/meeting/list", params=kwargs)
15+
16+
def create(self, **kwargs):
17+
util.require_keys(kwargs, ["host_id", "topic", "type"])
18+
if kwargs.get("start_time"):
19+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
20+
return self.post_request("/meeting/create", params=kwargs)
21+
22+
def update(self, **kwargs):
23+
util.require_keys(kwargs, ["id", "host_id"])
24+
if kwargs.get("start_time"):
25+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
26+
return self.post_request("/meeting/update", params=kwargs)
27+
28+
def delete(self, **kwargs):
29+
util.require_keys(kwargs, ["id", "host_id"])
30+
return self.post_request("/meeting/delete", params=kwargs)
31+
32+
def end(self, **kwargs):
33+
util.require_keys(kwargs, ["id", "host_id"])
34+
return self.post_request("/meeting/end", params=kwargs)
35+
36+
def get(self, **kwargs):
37+
util.require_keys(kwargs, ["id", "host_id"])
38+
return self.post_request("/meeting/get", params=kwargs)
39+
40+
41+
class MeetingComponentV2(base.BaseComponent):
42+
def list(self, **kwargs):
43+
util.require_keys(kwargs, "user_id")
44+
return self.get_request(
45+
"/users/{}/meetings".format(kwargs.get("user_id")), params=kwargs
46+
)
47+
48+
def create(self, **kwargs):
49+
util.require_keys(kwargs, "user_id")
50+
if kwargs.get("start_time"):
51+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
52+
return self.post_request(
53+
"/users/{}/meetings".format(kwargs.get("user_id")), params=kwargs
54+
)
55+
56+
def get(self, **kwargs):
57+
util.require_keys(kwargs, "id")
58+
return self.get_request("/meetings/{}".format(kwargs.get("id")), params=kwargs)
59+
60+
def update(self, **kwargs):
61+
util.require_keys(kwargs, "id")
62+
if kwargs.get("start_time"):
63+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
64+
return self.patch_request(
65+
"/meetings/{}".format(kwargs.get("id")), params=kwargs
66+
)
67+
68+
def delete(self, **kwargs):
69+
util.require_keys(kwargs, "id")
70+
return self.delete_request(
71+
"/meetings/{}".format(kwargs.get("id")), params=kwargs
72+
)

‎zoomapi/components/recording.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Zoom.us REST API Python Client -- Recording component"""
2+
from zoomapi import util
3+
from zoomapi.components import base
4+
5+
6+
class RecordingComponent(base.BaseComponent):
7+
"""Component dealing with all recording related matters"""
8+
9+
def list(self, **kwargs):
10+
util.require_keys(kwargs, "host_id")
11+
start = kwargs.pop("start", None)
12+
if start:
13+
kwargs["from"] = util.date_to_str(start)
14+
end = kwargs.pop("end", None)
15+
if end:
16+
kwargs["to"] = util.date_to_str(end)
17+
return self.post_request("/recording/list", params=kwargs)
18+
19+
def delete(self, **kwargs):
20+
util.require_keys(kwargs, ["meeting_id"])
21+
return self.post_request("/recording/delete", params=kwargs)
22+
23+
def get(self, **kwargs):
24+
util.require_keys(kwargs, ["meeting_id"])
25+
return self.post_request("/recording/get", params=kwargs)
26+
27+
28+
class RecordingComponentV2(base.BaseComponent):
29+
"""Component dealing with all recording related matters"""
30+
31+
def list(self, **kwargs):
32+
util.require_keys(kwargs, "user_id")
33+
start = kwargs.pop("start", None)
34+
if start:
35+
kwargs["from"] = util.date_to_str(start)
36+
end = kwargs.pop("end", None)
37+
if end:
38+
kwargs["to"] = util.date_to_str(end)
39+
return self.get_request(
40+
"/users/{}/recordings".format(kwargs.get("user_id")), params=kwargs
41+
)
42+
43+
def get(self, **kwargs):
44+
util.require_keys(kwargs, "meeting_id")
45+
return self.get_request(
46+
"/meetings/{}/recordings".format(kwargs.get("meeting_id")), params=kwargs
47+
)
48+
49+
def delete(self, **kwargs):
50+
util.require_keys(kwargs, "meeting_id")
51+
return self.delete_request(
52+
"/meetings/{}/recordings".format(kwargs.get("meeting_id")), params=kwargs
53+
)

‎zoomapi/components/report.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Zoom.us REST API Python Client -- Report component"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
class ReportComponent(base.BaseComponent):
7+
"""Component dealing with all report related matters"""
8+
9+
def get_account_report(self, **kwargs):
10+
util.require_keys(kwargs, ["start_time", "end_time"], kwargs)
11+
kwargs["from"] = util.date_to_str(kwargs["start_time"])
12+
del kwargs["start_time"]
13+
kwargs["to"] = util.date_to_str(kwargs["end_time"])
14+
del kwargs["end_time"]
15+
return self.post_request("/report/getaccountreport", params=kwargs)
16+
17+
def get_user_report(self, **kwargs):
18+
util.require_keys(kwargs, ["start_time", "end_time"], kwargs)
19+
kwargs["from"] = util.date_to_str(kwargs["start_time"])
20+
del kwargs["start_time"]
21+
kwargs["to"] = util.date_to_str(kwargs["end_time"])
22+
del kwargs["end_time"]
23+
return self.post_request("/report/getuserreport", params=kwargs)
24+
25+
26+
class ReportComponentV2(base.BaseComponent):
27+
def get_user_report(self, **kwargs):
28+
util.require_keys(kwargs, ["user_id", "start_time", "end_time"])
29+
kwargs["from"] = util.date_to_str(kwargs["start_time"])
30+
del kwargs["start_time"]
31+
kwargs["to"] = util.date_to_str(kwargs["end_time"])
32+
del kwargs["end_time"]
33+
return self.get_request(
34+
"/report/users/{}/meetings".format(kwargs.get("user_id")), params=kwargs
35+
)
36+
37+
def get_account_report(self, **kwargs):
38+
util.require_keys(kwargs, ["start_time", "end_time"])
39+
kwargs["from"] = util.date_to_str(kwargs["start_time"])
40+
del kwargs["start_time"]
41+
kwargs["to"] = util.date_to_str(kwargs["end_time"])
42+
del kwargs["end_time"]
43+
return self.get_request("/report/users", params=kwargs)

‎zoomapi/components/user.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Zoom.us REST API Python Client -- User component"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
class UserComponent(base.BaseComponent):
7+
"""Component dealing with all user related matters"""
8+
9+
def list(self, **kwargs):
10+
return self.post_request("/user/list", params=kwargs)
11+
12+
def pending(self, **kwargs):
13+
return self.post_request("/user/pending", params=kwargs)
14+
15+
def create(self, **kwargs):
16+
return self.post_request("/user/create", params=kwargs)
17+
18+
def update(self, **kwargs):
19+
util.require_keys(kwargs, "id")
20+
return self.post_request("/user/update", params=kwargs)
21+
22+
def delete(self, **kwargs):
23+
util.require_keys(kwargs, "id")
24+
return self.post_request("/user/delete", params=kwargs)
25+
26+
def cust_create(self, **kwargs):
27+
util.require_keys(kwargs, ["type", "email"])
28+
return self.post_request("/user/custcreate", params=kwargs)
29+
30+
def get(self, **kwargs):
31+
util.require_keys(kwargs, "id")
32+
return self.post_request("/user/get", params=kwargs)
33+
34+
def get_by_email(self, **kwargs):
35+
util.require_keys(kwargs, ["email", "login_type"])
36+
return self.post_request("/user/getbyemail", params=kwargs)
37+
38+
39+
class UserComponentV2(base.BaseComponent):
40+
def list(self, **kwargs):
41+
return self.get_request("/users", params=kwargs)
42+
43+
def create(self, **kwargs):
44+
return self.post_request("/users", params=kwargs)
45+
46+
def update(self, **kwargs):
47+
util.require_keys(kwargs, "id")
48+
return self.patch_request("/users/{}".format(kwargs.get("id")), params=kwargs)
49+
50+
def delete(self, **kwargs):
51+
util.require_keys(kwargs, "id")
52+
return self.delete_request("/users/{}".format(kwargs.get("id")), params=kwargs)
53+
54+
def get(self, **kwargs):
55+
util.require_keys(kwargs, "id")
56+
return self.get_request("/users/{}".format(kwargs.get("id")), params=kwargs)

‎zoomapi/components/webinar.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Zoom.us REST API Python Client -- Webinar component"""
2+
3+
from zoomapi import util
4+
from zoomapi.components import base
5+
6+
class WebinarComponent(base.BaseComponent):
7+
"""Component dealing with all webinar related matters"""
8+
9+
def list(self, **kwargs):
10+
util.require_keys(kwargs, "host_id")
11+
if kwargs.get("start_time"):
12+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
13+
return self.post_request("/webinar/list", params=kwargs)
14+
15+
def upcoming(self, **kwargs):
16+
util.require_keys(kwargs, "host_id")
17+
if kwargs.get("start_time"):
18+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
19+
return self.post_request("/webinar/list/registration", params=kwargs)
20+
21+
def create(self, **kwargs):
22+
util.require_keys(kwargs, ["host_id", "topic"])
23+
if kwargs.get("start_time"):
24+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
25+
return self.post_request("/webinar/create", params=kwargs)
26+
27+
def update(self, **kwargs):
28+
util.require_keys(kwargs, ["id", "host_id"])
29+
if kwargs.get("start_time"):
30+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
31+
return self.post_request("/webinar/update", params=kwargs)
32+
33+
def delete(self, **kwargs):
34+
util.require_keys(kwargs, ["id", "host_id"])
35+
return self.post_request("/webinar/delete", params=kwargs)
36+
37+
def end(self, **kwargs):
38+
util.require_keys(kwargs, ["id", "host_id"])
39+
return self.post_request("/webinar/end", params=kwargs)
40+
41+
def get(self, **kwargs):
42+
util.require_keys(kwargs, ["id", "host_id"])
43+
return self.post_request("/webinar/get", params=kwargs)
44+
45+
def register(self, **kwargs):
46+
util.require_keys(kwargs, ["id", "email", "first_name", "last_name"])
47+
if kwargs.get("start_time"):
48+
kwargs["start_time"] = util.date_to_str(kwargs["start_time"])
49+
return self.post_request("/webinar/register", params=kwargs)
50+
51+
52+
class WebinarComponentV2(base.BaseComponent):
53+
"""Component dealing with all webinar related matters"""
54+
55+
def list(self, **kwargs):
56+
util.require_keys(kwargs, "user_id")
57+
return self.get_request(
58+
"/users/{}/webinars".format(kwargs.get("user_id")), params=kwargs
59+
)
60+
61+
def create(self, **kwargs):
62+
util.require_keys(kwargs, "user_id")
63+
return self.post_request(
64+
"/users/{}/webinars".format(kwargs.get("user_id")), params=kwargs
65+
)
66+
67+
def update(self, **kwargs):
68+
util.require_keys(kwargs, "id")
69+
return self.patch_request(
70+
"/webinars/{}".format(kwargs.get("id")), params=kwargs
71+
)
72+
73+
def delete(self, **kwargs):
74+
util.require_keys(kwargs, "id")
75+
return self.delete_request(
76+
"/webinars/{}".format(kwargs.get("id")), params=kwargs
77+
)
78+
79+
def end(self, **kwargs):
80+
util.require_keys(kwargs, "id")
81+
return self.put_request(
82+
"/webinars/{}/status".format(kwargs.get("id")), params={"status": "end"}
83+
)
84+
85+
def get(self, **kwargs):
86+
util.require_keys(kwargs, "id")
87+
return self.get_request("/webinars/{}".format(kwargs.get("id")), params=kwargs)
88+
89+
def register(self, **kwargs):
90+
util.require_keys(kwargs, ["id", "email", "first_name", "last_name"])
91+
return self.post_request(
92+
"/webinars/{}/registrants".format(kwargs.get("id")), params=kwargs
93+
)

‎zoomapi/jwtclient.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Zoom.us REST API Python Client"""
2+
3+
from zoomapi import components, util
4+
from zoomapi.client import ZoomClient
5+
6+
API_VERSION_1 = 1
7+
API_VERSION_2 = 2
8+
9+
API_BASE_URIS = {
10+
API_VERSION_1: "https://api.zoom.us/v1",
11+
API_VERSION_2: "https://api.zoom.us/v2",
12+
}
13+
14+
COMPONENT_CLASSES = {
15+
API_VERSION_1: {
16+
"user": components.user.UserComponent,
17+
"meeting": components.meeting.MeetingComponent,
18+
"report": components.report.ReportComponent,
19+
"webinar": components.webinar.WebinarComponent,
20+
"recording": components.recording.RecordingComponent,
21+
},
22+
API_VERSION_2: {
23+
"user": components.user.UserComponentV2,
24+
"meeting": components.meeting.MeetingComponentV2,
25+
"report": components.report.ReportComponentV2,
26+
"webinar": components.webinar.WebinarComponentV2,
27+
"recording": components.recording.RecordingComponentV2,
28+
},
29+
}
30+
31+
32+
class JWTZoomClient(ZoomClient):
33+
"""Zoom.us REST API Python Client"""
34+
35+
"""Base URL for Zoom API"""
36+
37+
def __init__(
38+
self, api_key, api_secret, data_type="json", timeout=15, version=API_VERSION_2
39+
):
40+
"""Create a new Zoom client
41+
42+
:param api_key: The Zooom.us API key
43+
:param api_secret: The Zoom.us API secret
44+
:param data_type: The expected return data type. Either 'json' or 'xml'
45+
:param timeout: The time out to use for API requests
46+
"""
47+
try:
48+
BASE_URI = API_BASE_URIS[version]
49+
self.components = COMPONENT_CLASSES[version].copy()
50+
except KeyError:
51+
raise RuntimeError("API version not supported: %s" % version)
52+
53+
super(JWTZoomClient, self).__init__(base_uri=BASE_URI, timeout=timeout)
54+
55+
# Setup the config details
56+
self.config = {
57+
"api_key": api_key,
58+
"api_secret": api_secret,
59+
"data_type": data_type,
60+
"version": version,
61+
"token": util.generate_jwt(api_key, api_secret),
62+
}
63+
64+
# Instantiate the components
65+
for key in self.components.keys():
66+
self.components[key] = self.components[key](
67+
base_uri=BASE_URI, config=self.config
68+
)
69+
70+
def __enter__(self):
71+
return self
72+
73+
def __exit__(self, exc_type, exc_val, exc_tb):
74+
return
75+
76+
def refresh_token(self):
77+
self.config["token"] = (
78+
util.generate_jwt(self.config["api_key"], self.config["api_secret"]),
79+
)
80+
81+
@property
82+
def api_key(self):
83+
"""The Zoom.us api_key"""
84+
return self.config.get("api_key")
85+
86+
@api_key.setter
87+
def api_key(self, value):
88+
"""Set the api_key"""
89+
self.config["api_key"] = value
90+
self.refresh_token()
91+
92+
@property
93+
def api_secret(self):
94+
"""The Zoom.us api_secret"""
95+
return self.config.get("api_secret")
96+
97+
@api_secret.setter
98+
def api_secret(self, value):
99+
"""Set the api_secret"""
100+
self.config["api_secret"] = value
101+
self.refresh_token()
102+
103+
@property
104+
def meeting(self):
105+
"""Get the meeting component"""
106+
return self.components.get("meeting")
107+
108+
@property
109+
def report(self):
110+
"""Get the report component"""
111+
return self.components.get("report")
112+
113+
@property
114+
def user(self):
115+
"""Get the user component"""
116+
return self.components.get("user")
117+
118+
@property
119+
def webinar(self):
120+
"""Get the webinar component"""
121+
return self.components.get("webinar")
122+
123+
@property
124+
def recording(self):
125+
"""Get the recording component"""
126+
return self.components.get("recording")

‎zoomapi/oauthclient.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Zoom.us REST API Python Client"""
2+
3+
from zoomapi import components, util
4+
from zoomapi.client import ZoomClient
5+
6+
class OAuthZoomClient(ZoomClient):
7+
"""Zoom.us REST API Python Client"""
8+
9+
"""Base URL for Zoom API"""
10+
11+
def __init__(
12+
self, client_id, client_secret, redirect_url, data_type="json", timeout=15
13+
):
14+
"""Create a new Zoom client
15+
16+
:param client_id: The Zooom.us client id for this OAuth bot
17+
:param client_secret: The Zoom.us client secret for this OAuth bot
18+
:param data_type: The expected return data type. Either 'json' or 'xml'
19+
:param timeout: The time out to use for API requests
20+
"""
21+
super(OAuthZoomClient, self).__init__(api_key=client_id, api_secret=client_secret, timeout=timeout)
22+
23+
# Add the specific config details
24+
self.config["redirect_url"] = redirect_url
25+
self.config["token"] = util.get_oauth_token(client_id, client_secret, redirect_url)
26+
27+
self.components["chat_channels"] = components.chat_channels.ChatChannelsComponentV2
28+
self.components["chat_messages"] = components.chat_messages.ChatMessagesComponentV2
29+
30+
# Instantiate the components
31+
for key in self.components.keys():
32+
self.components[key] = self.components[key](
33+
base_uri=self.BASE_URI, config=self.config
34+
)
35+
36+
37+
@property
38+
def redirect_url(self):
39+
"""The Zoom.us OAuth redirect_url"""
40+
return self.config.get("redirect_url")
41+
42+
@redirect_url.setter
43+
def redirect_url(self, value):
44+
"""Set the redirect_url"""
45+
self.config["redirect_url"] = value
46+
self.refresh_token()
47+
48+
@property
49+
def chat_messages(self):
50+
"""Get the chat messages component"""
51+
return self.components.get("chat_messages")
52+
53+
@property
54+
def chat_channels(self):
55+
"""Get the chat messages component"""
56+
return self.components.get("chat_channels")

‎zoomapi/util.py

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
"""Utility classes and functions"""
2+
3+
from __future__ import absolute_import, unicode_literals
4+
5+
import contextlib
6+
import json
7+
import requests
8+
from requests_oauthlib import OAuth2Session
9+
import time
10+
import jwt
11+
from http.server import HTTPServer, BaseHTTPRequestHandler
12+
import socketserver
13+
from threading import Thread
14+
from urllib.parse import urlparse, urlencode, quote
15+
import webbrowser
16+
import os
17+
18+
class ApiClient(object):
19+
"""Simple wrapper for REST API requests"""
20+
21+
def __init__(self, base_uri=None, timeout=15, **kwargs):
22+
"""Setup a new API Client
23+
24+
:param base_uri: The base URI to the API
25+
:param timeout: The timeout to use for requests
26+
:param kwargs: Any other attributes. These will be added as
27+
attributes to the ApiClient object.
28+
"""
29+
self.base_uri = base_uri
30+
self.timeout = timeout
31+
for k, v in kwargs.items():
32+
setattr(self, k, v)
33+
34+
@property
35+
def timeout(self):
36+
"""The timeout"""
37+
return self._timeout
38+
39+
@timeout.setter
40+
def timeout(self, value):
41+
"""The default timeout"""
42+
if value is not None:
43+
try:
44+
value = int(value)
45+
except ValueError:
46+
raise ValueError("timeout value must be an integer")
47+
self._timeout = value
48+
49+
@property
50+
def base_uri(self):
51+
"""The base_uri"""
52+
return self._base_uri
53+
54+
@base_uri.setter
55+
def base_uri(self, value):
56+
"""The default base_uri"""
57+
if value and value.endswith("/"):
58+
value = value[:-1]
59+
self._base_uri = value
60+
61+
def url_for(self, endpoint):
62+
"""Get the URL for the given endpoint
63+
64+
:param endpoint: The endpoint
65+
:return: The full URL for the endpoint
66+
"""
67+
if not endpoint.startswith("/"):
68+
endpoint = "/{}".format(endpoint)
69+
if endpoint.endswith("/"):
70+
endpoint = endpoint[:-1]
71+
return self.base_uri + endpoint
72+
73+
def get_request(self, endpoint, params=None, headers=None):
74+
"""Helper function for GET requests
75+
76+
:param endpoint: The endpoint
77+
:param params: The URL parameters
78+
:param headers: request headers
79+
:return: The :class:``requests.Response`` object for this request
80+
"""
81+
if headers is None:
82+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
83+
return requests.get(
84+
self.url_for(endpoint), params=params, headers=headers, timeout=self.timeout
85+
)
86+
87+
def post_request(
88+
self, endpoint, params=None, data=None, headers=None, cookies=None
89+
):
90+
"""Helper function for POST requests
91+
92+
:param endpoint: The endpoint
93+
:param params: The URL parameters
94+
:param data: The data (either as a dict or dumped JSON string) to
95+
include with the POST
96+
:param headers: request headers
97+
:param cookies: request cookies
98+
:return: The :class:``requests.Response`` object for this request
99+
"""
100+
if data and not is_str_type(data):
101+
data = json.dumps(data)
102+
if headers is None:
103+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
104+
headers["Content-type"] = "application/json"
105+
return requests.post(
106+
self.url_for(endpoint),
107+
params=params,
108+
data=data,
109+
headers=headers,
110+
cookies=cookies,
111+
timeout=self.timeout,
112+
)
113+
114+
def patch_request(
115+
self, endpoint, params=None, data=None, headers=None, cookies=None
116+
):
117+
"""Helper function for PATCH requests
118+
119+
:param endpoint: The endpoint
120+
:param params: The URL parameters
121+
:param data: The data (either as a dict or dumped JSON string) to
122+
include with the PATCH
123+
:param headers: request headers
124+
:param cookies: request cookies
125+
:return: The :class:``requests.Response`` object for this request
126+
"""
127+
if data and not is_str_type(data):
128+
data = json.dumps(data)
129+
if headers is None:
130+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
131+
return requests.patch(
132+
self.url_for(endpoint),
133+
params=params,
134+
data=data,
135+
headers=headers,
136+
cookies=cookies,
137+
timeout=self.timeout,
138+
)
139+
140+
def delete_request(
141+
self, endpoint, params=None, data=None, headers=None, cookies=None
142+
):
143+
"""Helper function for DELETE requests
144+
145+
:param endpoint: The endpoint
146+
:param params: The URL parameters
147+
:param data: The data (either as a dict or dumped JSON string) to
148+
include with the DELETE
149+
:param headers: request headers
150+
:param cookies: request cookies
151+
:return: The :class:``requests.Response`` object for this request
152+
"""
153+
if data and not is_str_type(data):
154+
data = json.dumps(data)
155+
if headers is None:
156+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
157+
return requests.delete(
158+
self.url_for(endpoint),
159+
params=params,
160+
data=data,
161+
headers=headers,
162+
cookies=cookies,
163+
timeout=self.timeout,
164+
)
165+
166+
def put_request(self, endpoint, params=None, data=None, headers=None, cookies=None):
167+
"""Helper function for PUT requests
168+
169+
:param endpoint: The endpoint
170+
:param params: The URL paramaters
171+
:param data: The data (either as a dict or dumped JSON string) to
172+
include with the PUT
173+
:param headers: request headers
174+
:param cookies: request cookies
175+
:return: The :class:``requests.Response`` object for this request
176+
"""
177+
if data and not is_str_type(data):
178+
data = json.dumps(data)
179+
if headers is None:
180+
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
181+
return requests.put(
182+
self.url_for(endpoint),
183+
params=params,
184+
data=data,
185+
headers=headers,
186+
cookies=cookies,
187+
timeout=self.timeout,
188+
)
189+
190+
191+
@contextlib.contextmanager
192+
def ignored(*exceptions):
193+
"""Simple context manager to ignore expected Exceptions
194+
195+
:param \*exceptions: The exceptions to safely ignore
196+
"""
197+
try:
198+
yield
199+
except exceptions:
200+
pass
201+
202+
203+
def is_str_type(val):
204+
"""Check whether the input is of a string type.
205+
206+
We use this method to ensure python 2-3 capatibility.
207+
208+
:param val: The value to check wither it is a string
209+
:return: In python2 it will return ``True`` if :attr:`val` is either an
210+
instance of str or unicode. In python3 it will return ``True`` if
211+
it is an instance of str
212+
"""
213+
with ignored(NameError):
214+
return isinstance(val, basestring)
215+
return isinstance(val, str)
216+
217+
218+
def require_keys(d, keys, allow_none=True):
219+
"""Require that the object have the given keys
220+
221+
:param d: The dict the check
222+
:param keys: The keys to check :attr:`obj` for. This can either be a single
223+
string, or an iterable of strings
224+
225+
:param allow_none: Whether ``None`` values are allowed
226+
:raises:
227+
:ValueError: If any of the keys are missing from the obj
228+
"""
229+
if is_str_type(keys):
230+
keys = [keys]
231+
for k in keys:
232+
if k not in d:
233+
raise ValueError("'{}' must be set".format(k))
234+
if not allow_none and d[k] is None:
235+
raise ValueError("'{}' cannot be None".format(k))
236+
return True
237+
238+
239+
def date_to_str(d):
240+
"""Convert date and datetime objects to a string
241+
242+
Note, this does not do any timezone conversion.
243+
244+
:param d: The :class:`datetime.date` or :class:`datetime.datetime` to
245+
convert to a string
246+
:returns: The string representation of the date
247+
"""
248+
return d.strftime("%Y-%m-%dT%H:%M:%SZ")
249+
250+
251+
def generate_jwt(key, secret):
252+
header = {"alg": "HS256", "typ": "JWT"}
253+
254+
payload = {"iss": key, "exp": int(time.time() + 3600)}
255+
256+
token = jwt.encode(payload, secret, algorithm="HS256", headers=header)
257+
return token.decode("utf-8")
258+
259+
class TokenHandler(BaseHTTPRequestHandler):
260+
code = None
261+
def do_GET(self):
262+
print("GET request to " + self.path)
263+
query = urlparse(self.path).query
264+
if len(query) > 0:
265+
print("query is " + query)
266+
query_components = dict(qc.split("=") for qc in query.split("&"))
267+
TokenHandler.code = query_components["code"]
268+
print("***" + TokenHandler.code + "***")
269+
self.send_response(200)
270+
self.end_headers()
271+
272+
def http_receiver():
273+
with socketserver.TCPServer(("", 4000), TokenHandler) as httpd:
274+
print("serving at port", 4000)
275+
while TokenHandler.code == None:
276+
httpd.handle_request()
277+
print("End of http receiver")
278+
279+
def get_oauth_token(cid, client_secret, redirect_url):
280+
281+
oauth = OAuth2Session(client_id = cid, redirect_uri = redirect_url)
282+
authorization_url, state = oauth.authorization_url(
283+
'https://zoom.us/oauth/authorize')
284+
285+
print ('Going to %s to authorize access.' % authorization_url)
286+
chrome_path= r'/mnt/c/Program\ Files\ \(x86\)/Google/Chrome/Application/chrome.exe'
287+
authorization_url = authorization_url.replace('&', '\&')
288+
print(authorization_url)
289+
os.system(chrome_path + " " + authorization_url)
290+
291+
http_receiver()
292+
293+
token = oauth.fetch_token(
294+
'https://zoom.us/oauth/token',
295+
code=TokenHandler.code,
296+
client_secret=client_secret)
297+
resp = dict(token)
298+
return resp["access_token"]

0 commit comments

Comments
 (0)
Please sign in to comment.