#!/usr/bin/env python3 import configparser import json from pprint import pprint from zoomus import ZoomClient import psycopg2 import psycopg2.extras import time import dateutil.parser import sys import argparse config = configparser.ConfigParser() config.read('zoom.ini') client = ZoomClient(config['api']['key'], config['api']['secret']) db_conn = psycopg2.connect(dbname=config['db']['name'], user=config['db']['user'], password=config['db']['passwd'], host="127.0.0.1") db = db_conn.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor) def die(msg): print(msg, file=sys.stderr) sys.exit(1) def parse_time(iso_time): return dateutil.parser.isoparse(iso_time) def get_meeting(host_id, meeting_id): resp = client.meeting.get(host_id=host_id, id=meeting_id) resp.raise_for_status() details = json.loads(resp.content) if debug: pprint(details) return details def insert_meeting(uid, meet): meeting_id = meet["id"] db.execute(""" INSERT INTO zoom_meetings (meeting_id, uuid, host_uid, topic, type) VALUES (%s, %s, %s, %s, %s) RETURNING mid """, ( meet['id'], meet['uuid'], uid, meet['topic'], meet['type'], )) meeting_row = db.fetchone() mid = meeting_row.mid print(f"Meeting {meeting_id}: Creating") if 'occurrences' in meet: # Recurrent meetings have a list of occurrences for occ in meet["occurrences"]: occ_id = occ['occurrence_id'] if occ.get('status', "") == 'deleted': print(f"Meeting {meeting_id}.{occ_id}: Marked as deleted") continue print(f"Meeting {meeting_id}.{occ_id}: Scheduling") db.execute(""" INSERT INTO zoom_schedule (mid, occurrence_id, start_time, duration) VALUES (%s, %s, %s, %s) """, ( mid, occ_id, parse_time(occ['start_time']), occ['duration'], )) elif 'start_time' in meet: # Other meetings usually have a starting time print(f"Meeting {meeting_id}: Scheduling") db.execute(""" INSERT INTO zoom_schedule (mid, occurrence_id, start_time, duration) VALUES (%s, %s, %s, %s) """, ( mid, 0, parse_time(meet['start_time']), meet['duration'], )) return mid def add_meeting(uid, meet): mtype = meet['type'] if mtype == 8: # Recurring meetings: need to ask for the list of occurrences time.sleep(0.2) meet = get_meeting(meet['host_id'], meet['id']) insert_meeting(uid, meet) def get_meetings(uid, user_id): page_id = 1 num_pages = 999 expected_rec = 0 total_rec = 0 while page_id <= num_pages: print(f"Fetching meetings for user {user_id}: page {page_id} of {num_pages}") resp = client.meeting.list(user_id = user_id, type = 'scheduled', page_number = page_id) resp.raise_for_status() meeting_list = json.loads(resp.content) if debug: pprint(meeting_list) num_pages = meeting_list['page_count'] expected_rec = meeting_list['total_records'] for meet in meeting_list['meetings']: add_meeting(uid, meet) total_rec += 1 page_id += 1 time.sleep(0.2) assert total_rec == expected_rec, "Unexpected number of records, probably because of race condition" def fetch_all(): db.execute('DELETE FROM zoom_meetings') db.execute("SELECT * FROM zoom_users") users = db.fetchall() for u in users: get_meetings(u.uid, u.user_id) db_conn.commit() def fetch_user(user_email): db.execute("SELECT * FROM zoom_users WHERE email=%s", (user_email,)) u = db.fetchone() if not u: die("No such user.") db.execute('DELETE FROM zoom_meetings WHERE host_uid=%s', (u.uid,)) get_meetings(u.uid, u.user_id) db_conn.commit() def fetch_single(user_email, meeting_id): db.execute("SELECT * FROM zoom_meetings WHERE meeting_id=%s", (meeting_id,)) meeting_row = db.fetchone() if meeting_row: db.execute("SELECT * FROM zoom_users WHERE uid=%s", (meeting_row.host_uid,)) user_row = db.fetchone() assert user_row print(f"Meeting owned by {user_row.email}") if user_email is not None and user_email != user_row.email: die("Mismatched meeting host!") print(f"Deleting previous records on meeting {meeting_id}") db.execute("DELETE FROM zoom_schedule WHERE mid=%s", (meeting_row.mid,)) db.execute("DELETE FROM zoom_meetings WHERE mid=%s", (meeting_row.mid,)) else: if not user_email: die("Meeting is not known yet, you need to specify --user") db.execute("SELECT * FROM zoom_users WHERE email=%s", (user_email,)) user_row = db.fetchone() if user_row is None: die(f"E-mail {user_email} not found") meet = get_meeting(user_row.user_id, meeting_id) insert_meeting(user_row.uid, meet) db_conn.commit() argp = argparse.ArgumentParser(description="Fetch meeting data from Zoom API") argp.add_argument('--all', action='store_const', const=True, default=False, help="re-fetch all meetings") argp.add_argument('--user', metavar='EMAIL', help='fetch meetings hosted by a given user') argp.add_argument('--meeting', type=int, metavar='ID', help='fetch a single meeting (requires --user unless the meeting is already known)') argp.add_argument('--debug', action='store_const', const=True, default=False, help="enable debugging dumps") args = argp.parse_args() debug = args.debug if args.all: fetch_all() elif args.meeting is not None: fetch_single(args.user, args.meeting) elif args.user is not None: fetch_user(args.user) else: print("Nothing to do.")