Skip to content

Commit 71beeac

Browse files
committed
implement timed meeting status updates
1 parent 1bc1ccf commit 71beeac

File tree

2 files changed

+115
-7
lines changed

2 files changed

+115
-7
lines changed

common.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
SPEAKERS_CORNER_USER_ID = "D0n5UNEHQiajWtgdWLlNSA"
1010

1111
@lru_cache()
12-
def zoom_headers(duration: int=10) -> dict:
12+
def zoom_headers(duration: int=100) -> dict:
1313
zoom_api_key = os.getenv("ZOOM_API_KEY")
1414
zoom_api_secret = os.getenv("ZOOM_API_SECRET")
1515
token = jwt.encode(
@@ -22,10 +22,52 @@ def zoom_headers(duration: int=10) -> dict:
2222
return {'authorization': f'Bearer {token}', 'content-type': 'application/json'}
2323

2424

25+
def zoom_request(method: callable, *args, **kwargs):
26+
"""A minimal wrapper around requests for querying zoom API with error handling"""
27+
response = method(*args, **kwargs, headers=zoom_headers())
28+
if response.status_code != 204:
29+
raise RuntimeError(response.content.decode())
30+
31+
if response.content:
32+
return response.json()
33+
34+
2535
def speakers_corner_user_id() -> str:
26-
users = requests.get(ZOOM_API + "users", headers=zoom_headers()).json()["users"]
36+
users = zoom_request(requests.get, ZOOM_API + "users")["users"]
2737
sc_user_id = next(
2838
u["id"] for u in users
2939
if u["first_name"] == "Speakers'" and u["last_name"] == "Corner"
3040
)
31-
return sc_user_id
41+
return sc_user_id
42+
43+
44+
def all_meetings(user_id) -> list:
45+
"""Return all meetings by a user.
46+
47+
Handles pagination, and adds ``live: True`` to a meeting that is running (if any).
48+
"""
49+
meetings = []
50+
next_page_token = ""
51+
while True:
52+
meetings_page = zoom_request(
53+
requests.get,
54+
f"{ZOOM_API}users/{user_id}/meetings",
55+
params={"type": "scheduled", "page_size": 300, "next_page_token": next_page_token}
56+
)
57+
meetings += meetings_page["meetings"]
58+
next_page_token = meetings_page["next_page_token"]
59+
if not next_page_token:
60+
break
61+
62+
live_meetings = zoom_request(
63+
requests.get,
64+
f"{ZOOM_API}users/{user_id}/meetings",
65+
params={"type": "scheduled", "page_size": 300, "next_page_token": next_page_token}
66+
)["meetings"]
67+
68+
if live_meetings:
69+
for meeting in meetings:
70+
if meeting["id"] == live_meetings[0]["id"]:
71+
meeting["live"] = True
72+
73+
return meetings

host_key_rotation.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import requests
99
import pytz
10+
from dateutil.parser import parse
1011

1112
import common
13+
from common import zoom_request
1214

1315

1416
def host_key(timeslot: datetime.datetime) -> int:
@@ -21,12 +23,76 @@ def host_key(timeslot: datetime.datetime) -> int:
2123

2224
def update_host_key():
2325
"""Update the host key of the speakers' corner user for the upcoming hour."""
24-
response = requests.patch(
26+
zoom_request(
27+
requests.patch,
2528
common.ZOOM_API + "users/" + common.SPEAKERS_CORNER_USER_ID,
26-
headers=common.zoom_headers(),
2729
data=json.dumps({
2830
"host_key": host_key(datetime.datetime.now() + datetime.timedelta(hours=1))
2931
})
3032
)
31-
if response.status_code != 204:
32-
raise RuntimeError(response.content.decode())
33+
34+
35+
def rotate_meetings():
36+
"""Update the Speakers' corner meeting settings and statuses.
37+
38+
1. If there is an upcoming meeting in less than an hour, allow joining
39+
before host.
40+
2. Stop the running meeting if there is an upcoming one or if it runs for too long.
41+
3. Disable joining before host on recent meetings to prevent restarting.
42+
"""
43+
now = datetime.datetime.now(tz=pytz.UTC)
44+
sc_meetings = common.all_meetings(common.SPEAKERS_CORNER_USER_ID)
45+
for m in sc_meetings:
46+
m["start_time"] = parse(m["start_time"])
47+
48+
live = [m for m in sc_meetings if m["live"]]
49+
50+
try:
51+
upcoming = min(
52+
(m for m in sc_meetings if m["start_time"] > now),
53+
key=(lambda meeting: meeting["start_time"])
54+
)
55+
upcoming_start = upcoming["start_time"]
56+
except ValueError:
57+
upcoming = None
58+
upcoming_start = now + datetime.timedelta(weeks=1)
59+
60+
recent = [
61+
m for m in sc_meetings
62+
if (now > m["start_time"] > now - datetime.timedelta(hours=2))
63+
and not m["live"]
64+
]
65+
66+
starting_soon = upcoming_start - now < datetime.timedelta(hours=1)
67+
if starting_soon:
68+
common.zoom_request(
69+
requests.patch,
70+
f"{common.ZOOM_API}meetings/{upcoming['id']}",
71+
data=json.dumps({"settings": {"join_before_host": True}}),
72+
)
73+
74+
if (
75+
live
76+
and (
77+
starting_soon
78+
or live[0]["start_time"] < now - datetime.timedelta(minutes=90)
79+
)
80+
):
81+
live_id = live[0]["id"]
82+
common.zoom_request(
83+
requests.put,
84+
f"{common.ZOOM_API}meetings/{live_id}/status",
85+
data=json.dumps({"action": "end"}),
86+
)
87+
common.zoom_request(
88+
requests.patch,
89+
f"{common.ZOOM_API}meetings/{live_id}",
90+
data=json.dumps({"settings": {"join_before_host": False}}),
91+
)
92+
93+
for meeting in recent:
94+
common.zoom_request(
95+
requests.patch,
96+
f"{common.ZOOM_API}meetings/{meeting['id']}",
97+
data=json.dumps({"settings": {"join_before_host": False}}),
98+
)

0 commit comments

Comments
 (0)