From b23551c3728f1a15c93c08da7ffa9e4531b95702 Mon Sep 17 00:00:00 2001 From: Yingbei Date: Tue, 4 Mar 2025 23:45:51 +0800 Subject: [PATCH 1/5] enhance: add support to get past meeting instances and prompts for PMI instances --- zoom/tool.gpt | 20 +++++++++++++++----- zoom/tools/meetings.py | 29 ++++++++++++++++++++++++++++- zoom/tools/recordings.py | 4 ++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/zoom/tool.gpt b/zoom/tool.gpt index 6dd73ee7..6338436b 100644 --- a/zoom/tool.gpt +++ b/zoom/tool.gpt @@ -2,11 +2,11 @@ Name: Zoom Description: Tools for interacting with Zoom Metadata: bundle: true -Share Tools: Get User, Create Meeting, Update Meeting, List Meeting Templates, Get Meeting, Delete Meeting, List Meetings, List Upcoming Meetings, Get Meeting Invitation, Get Meeting Recordings, List User Recordings, Get Meeting Summary, Get Past Meeting Details, List Available Timezones +Share Tools: Get User, Create Meeting, Update Meeting, List Meeting Templates, Get Meeting, Delete Meeting, List Meetings, List Upcoming Meetings, List Past Meeting Instances, Get Past Meeting Details, Get Meeting Invitation, Get Meeting Recordings, List User Recordings, Get Meeting Summary, List Available Timezones --- Name: Get User -Description: Get the information of the current zoom user. In the response, `type` will indicate user's plan type. 1 - Basic. 2 - Licensed. 4 - Unassigned without Meetings Basic. 99 - None, which can only be set with ssoCreate. +Description: Get the information of the current zoom user. In the response, `type` will indicate user's plan type. 1 - Basic. 2 - Licensed. 4 - Unassigned without Meetings Basic. 99 - None, which can only be set with ssoCreate. This also returns the user's Personal Meeting ID(PMI). Credential: ./credential Share Context: Zoom Context @@ -91,9 +91,10 @@ Param: meeting_id: The ID of the meeting to delete --- Name: List Meetings -Description: List all meetings hosted by the current Zoom user. +Description: List all scheduled meetings hosted by the current Zoom user. This does not return information about instant meetings. Credential: ./credential Share Context: Zoom Context +Param: type: Optional. The type of the meetings to list. Default to scheduled, which will return all valid previous (unexpired) meetings, live meetings, and upcoming scheduled meetings. Must be one of: scheduled, live, upcoming, upcoming_meetings, previous_meetings. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py ListMeetings @@ -108,7 +109,7 @@ Param: meeting_id: The ID of the meeting to get the invitation for --- Name: List Upcoming Meetings -Description: List all upcoming meetings hosted by the current Zoom user. +Description: List upcoming meetings(within the next 24 hours) that the current Zoom user scheduled or invited to join. Credential: ./credential Share Context: Zoom Context @@ -119,7 +120,7 @@ Name: Get Meeting Recordings Description: Get the cloud recordings of a Zoom meeting. Cloud rerordings are only available for licensed users. Credential: ./credential Share Context: Zoom Context -Param: meeting_id: The ID of the meeting to get the recordings for +Param: meeting_id_or_uuid: The ID or UUID of the meeting to get the recordings for. If providing the meeting ID instead of UUID, the response will be for the latest meeting instance. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py GetMeetingRecordings @@ -140,6 +141,15 @@ Param: meeting_id: The ID of the meeting to get the details for #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py GetPastMeetingDetails +--- +Name: List Past Meeting Instances +Description: List all instances of a past Zoom meeting. +Credential: ./credential +Share Context: Zoom Context +Param: meeting_id: The ID of the meeting to get the instances for + +#!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py ListPastMeetingInstances + --- Name: Get Meeting Summary Description: Retrieve the Zoom AI-generated summary of a Zoom meeting. This feature is available exclusively to licensed users. To access it, the host must ensure that the `Meeting Summary with AI Companion` feature is enabled in their account settings. The meeting summary's accessibility depends on the host's sharing settings, by default only the host has the access. diff --git a/zoom/tools/meetings.py b/zoom/tools/meetings.py index 81653e4f..adb49d37 100644 --- a/zoom/tools/meetings.py +++ b/zoom/tools/meetings.py @@ -271,7 +271,21 @@ def list_meetings(): headers = { "Authorization": f"Bearer {ACCESS_TOKEN}", } - response = requests.get(url, headers=headers) + params = {} + type = os.getenv("TYPE", "") + type_enums = [ + "scheduled", + "live", + "upcoming", + "upcoming_meetings", + "previous_meetings", + ] + if type != "": + if type not in type_enums: + raise ValueError(f"Invalid type: {type}. Must be one of: {type_enums}") + params["type"] = type + + response = requests.get(url, headers=headers, params=params) if response.status_code != 200: return {"message": f"Error listing meetings: {response.text}"} @@ -471,3 +485,16 @@ def get_past_meeting_details(): if response.status_code != 200: return {"message": f"Error getting past meeting details: {response.text}"} return response.json() + + +@tool_registry.decorator("ListPastMeetingInstances") +def list_past_meeting_instances(): + meeting_id = os.environ["MEETING_ID"] + url = f"{ZOOM_API_URL}/past_meetings/{meeting_id}/instances" + headers = { + "Authorization": f"Bearer {ACCESS_TOKEN}", + } + response = requests.get(url, headers=headers) + if response.status_code != 200: + return {"message": f"Error listing past meeting instances: {response.text}"} + return response.json() diff --git a/zoom/tools/recordings.py b/zoom/tools/recordings.py index 9e45167d..8bca2bb6 100644 --- a/zoom/tools/recordings.py +++ b/zoom/tools/recordings.py @@ -5,8 +5,8 @@ @tool_registry.decorator("GetMeetingRecordings") def get_meeting_recordings(): - meeting_id = os.environ["MEETING_ID"] - url = f"{ZOOM_API_URL}/meetings/{meeting_id}/recordings" + meeting_id_or_uuid = os.environ["MEETING_ID_OR_UUID"] + url = f"{ZOOM_API_URL}/meetings/{meeting_id_or_uuid}/recordings" headers = { "Authorization": f"Bearer {ACCESS_TOKEN}", } From b6bea6bd4caa9aab4a6e207e9e24920cef9274c5 Mon Sep 17 00:00:00 2001 From: Yingbei Date: Wed, 5 Mar 2025 19:37:18 +0800 Subject: [PATCH 2/5] enhance: prompt agent to be better aware of date & time, and the case of instant meetings in personal meeting rooms --- zoom/tool.gpt | 24 +++++++----- zoom/tools/meetings.py | 89 ++++++++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/zoom/tool.gpt b/zoom/tool.gpt index 6338436b..43081648 100644 --- a/zoom/tool.gpt +++ b/zoom/tool.gpt @@ -33,7 +33,7 @@ Param: timezone: Optional. The timezone of the meeting, if not provided, the mee Param: topic: Optional. Defaults to empty. The topic of the meeting, up to 2000 characters Param: meeting_type: Optional. Defaults to 2(scheduled meeting). The type of the meeting: 1 for instant meeting, 2 for scheduled meeting, 3 for recurring meeting with no fixed time, 8 for recurring meeting with fixed time, 10 for screen share only meeting Param: recurrence: Optional. Use this only for a meeting with meeting_type 8, a recurring meeting with a fixed time. Defaults to empty. If provided, it must be a valid JSON string that can be loaded with json.loads(). Attributes of the recurrence object are -- end_date_time (string, date-time): Select the final date when the meeting will recur before it is canceled. Should be in UTC time, such as 2017-11-25T12:00:00Z. Cannot be used with end_times. | end_times (integer, max: 60, default: 1): Select how many times the meeting should recur before it is canceled. If end_times is set to 0, it means there is no end time. The maximum number of recurring is 60. Cannot be used with end_date_time. | monthly_day (integer, default: 1): Use this field only if you're scheduling a recurring meeting of type 3 to state the day in a month when the meeting should recur. The value range is from 1 to 31. For the meeting to recur on 23rd of each month, provide 23 as this field's value and 1 as the repeat_interval field's value. To have the meeting recur every three months on the 23rd, change the repeat_interval field value to 3. | monthly_week (integer, enum: -1, 1, 2, 3, 4): Use this field only if you're scheduling a recurring meeting of type 3 to state the week of the month when the meeting should recur. If you use this field, you must also use the monthly_week_day field to state the day of the week when the meeting should recur. -1 = Last week of the month, 1 = First week, 2 = Second week, 3 = Third week, 4 = Fourth week. | monthly_week_day (integer, enum: 1, 2, 3, 4, 5, 6, 7): Use this field only if you're scheduling a recurring meeting of type 3 to state a specific day in a week when the monthly meeting should recur. To use this field, you must also use the monthly_week field. 1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday. | repeat_interval (integer): Define the interval when the meeting should recur. For instance, to schedule a meeting that recurs every two months, set this field's value as 2 and the value of the type parameter as 3. For a daily meeting, the maximum number of recurrences is 99 days. For a weekly meeting, the maximum is 50 weeks. For a monthly meeting, the maximum is 10 months. | type (integer, enum: 1, 2, 3, required): Recurrence meeting types. 1 = Daily, 2 = Weekly, 3 = Monthly. | weekly_days (string, enum: 1, 2, 3, 4, 5, 6, 7, default: 1): Required if you're scheduling a recurring meeting of type 2 to state the days of the week when the meeting should repeat. This field's value could be a number between 1 to 7 in string format. For instance, if the meeting should recur on Sunday, provide "1" as this field's value. To set the meeting to occur on multiple days of a week, provide comma-separated values. For instance, for Sundays and Tuesdays, provide "1,3". 1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday. - +Param: auto_start_meeting_summary: Optional. Default to false. Set to true to automatically start Zoom's AI meeting summary. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py CreateMeeting @@ -50,8 +50,8 @@ Param: duration: Optional. The duration of the meeting in minutes Param: password: Optional. The password for the meeting, if not provided, a random password will be generated Param: pre_schedule: Optional. Whether to create a prescheduled meeting. This only supports the meeting type value of 2 (scheduled meetings) and 3 (recurring meetings with no fixed time). Param: audio_recording: Optional. none, local or cloud. The audio recording setting for the meeting -Param: contact_email: Optional. The email of the contact for the meeting, should be user's email -Param: contact_name: Optional. The name of the contact for the meeting, should be user's name +Param: contact_email: Optional. The email of the contact for the meeting, should be user's email +Param: contact_name: Optional. The name of the contact for the meeting, should be user's name Param: private_meeting: Optional. Whether the meeting is private Param: start_time: Optional. The meeting's start time. This field is only used for scheduled or recurring meetings with a fixed time. This supports local time and GMT formats.To set a meeting's start time in GMT, use the yyyy-MM-ddTHH:mm:ssZ date-time format. For example, 2020-03-31T12:02:00Z. To set a meeting's start time using a specific timezone, use the yyyy-MM-ddTHH:mm:ss date-time format and specify the timezone ID in the timezone field. If you do not specify a timezone, the timezone value defaults to your Zoom account's timezone. You can also use UTC for the timezone value. Note: If no start_time is set for a scheduled meeting, the start_time is set at the current time and the meeting type changes to an instant meeting, which expires after 30 days. Param: meeting_template_id: Optional. The ID of the meeting template to use. @@ -59,6 +59,7 @@ Param: timezone: Optional. The timezone of the meeting, if not provided, the mee Param: topic: Optional. The topic of the meeting, up to 2000 characters Param: meeting_type: Optional. The type of the meeting. 1 for instant meeting, 2 for scheduled meeting, 3 for recurring meeting with no fixed time, 8 for recurring meeting with fixed time, 10 for screen share only meeting Param: recurrence: Optional. Use this only for a meeting with meeting_type 8, a recurring meeting with a fixed time. If provided, it must be a valid JSON string that can be loaded with json.loads(), and should include full recurrence information. Attributes of the recurrence object are -- end_date_time (string, date-time): Select the final date when the meeting will recur before it is canceled. Should be in UTC time, such as 2017-11-25T12:00:00Z. Cannot be used with end_times. | end_times (integer, max: 60, default: 1): Select how many times the meeting should recur before it is canceled. If end_times is set to 0, it means there is no end time. The maximum number of recurring is 60. Cannot be used with end_date_time. | monthly_day (integer, default: 1): Use this field only if you're scheduling a recurring meeting of type 3 to state the day in a month when the meeting should recur. The value range is from 1 to 31. For the meeting to recur on 23rd of each month, provide 23 as this field's value and 1 as the repeat_interval field's value. To have the meeting recur every three months on the 23rd, change the repeat_interval field value to 3. | monthly_week (integer, enum: -1, 1, 2, 3, 4): Use this field only if you're scheduling a recurring meeting of type 3 to state the week of the month when the meeting should recur. If you use this field, you must also use the monthly_week_day field to state the day of the week when the meeting should recur. -1 = Last week of the month, 1 = First week, 2 = Second week, 3 = Third week, 4 = Fourth week. | monthly_week_day (integer, enum: 1, 2, 3, 4, 5, 6, 7): Use this field only if you're scheduling a recurring meeting of type 3 to state a specific day in a week when the monthly meeting should recur. To use this field, you must also use the monthly_week field. 1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday. | repeat_interval (integer): Define the interval when the meeting should recur. For instance, to schedule a meeting that recurs every two months, set this field's value as 2 and the value of the type parameter as 3. For a daily meeting, the maximum number of recurrences is 99 days. For a weekly meeting, the maximum is 50 weeks. For a monthly meeting, the maximum is 10 months. | type (integer, enum: 1, 2, 3, required): Recurrence meeting types. 1 = Daily, 2 = Weekly, 3 = Monthly. | weekly_days (string, enum: 1, 2, 3, 4, 5, 6, 7, default: 1): Required if you're scheduling a recurring meeting of type 2 to state the days of the week when the meeting should repeat. This field's value could be a number between 1 to 7 in string format. For instance, if the meeting should recur on Sunday, provide "1" as this field's value. To set the meeting to occur on multiple days of a week, provide comma-separated values. For instance, for Sundays and Tuesdays, provide "1,3". 1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday. +Param: auto_start_meeting_summary: Optional. Default to false. Set to true to automatically start Zoom's AI meeting summary. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py UpdateMeeting @@ -134,16 +135,16 @@ Share Context: Zoom Context --- Name: Get Past Meeting Details -Description: Get the details of a past Zoom meeting. +Description: Get the details of a Zoom meeting instance. It is possible that the meeting has not occurred yet. Credential: ./credential Share Context: Zoom Context -Param: meeting_id: The ID of the meeting to get the details for +Param: meeting_id_or_uuid: The ID or UUID of the meeting to get the details for. If providing the meeting ID instead of UUID, the response will be for the latest meeting instance. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py GetPastMeetingDetails --- Name: List Past Meeting Instances -Description: List all instances of a past Zoom meeting. +Description: List all instances of a Zoom meeting. Credential: ./credential Share Context: Zoom Context Param: meeting_id: The ID of the meeting to get the instances for @@ -155,7 +156,6 @@ Name: Get Meeting Summary Description: Retrieve the Zoom AI-generated summary of a Zoom meeting. This feature is available exclusively to licensed users. To access it, the host must ensure that the `Meeting Summary with AI Companion` feature is enabled in their account settings. The meeting summary's accessibility depends on the host's sharing settings, by default only the host has the access. Credential: ./credential Share Context: Zoom Context -Share Tools: Get Past Meeting Details Param: meeting_uuid: The UUID of the meeting to get the summary for, NOT the meeting ID. Must use the UUID obtained from `Get Past Meeting Details` tool. #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py GetMeetingSummary @@ -175,10 +175,14 @@ Share Context: ../time #!sys.echo -# Instructions for using Zoom tools - -You can use this tool to interact with Zoom with Zoom APIs. +# KEY Instructions for using Zoom tools +- Ensure that when displaying meeting dates and times, both the original meeting timezone and the user's timezone are shown if they differ. +- When the user provides dates and times, assume they are in the user's preferred timezone unless explicitly stated otherwise. +- When the user initiates a meeting creation, politely confirm all optional parameters with them. +- You can use the Get User Tool to get the user's personal meeting ID(PMI). +- The List Meetings Tool only returns scheduled meetings hosted by the current user. However, users may create meeting events and use instant meetings in their personal meeting room instead. If a user requests to list meetings or find recordings/summaries but finds no results, kindly remind them that you can also check instances of instant meetings held in their personal meeting room. +- Note that if an instant meeting was not hosted in the user's personal meeting room, you would need the user to provide the meeting ID to get the instances/recordings/summaries of the meeting. # End of instructions for using Zoom tools --- diff --git a/zoom/tools/meetings.py b/zoom/tools/meetings.py index adb49d37..965aef8f 100644 --- a/zoom/tools/meetings.py +++ b/zoom/tools/meetings.py @@ -128,6 +128,9 @@ def create_meeting(): meeting_type = int(os.getenv("MEETING_TYPE", 2)) recurrence = os.getenv("RECURRENCE", "") + auto_start_meeting_summary = str_to_bool( + os.getenv("AUTO_START_MEETING_SUMMARY", "false") + ) if meeting_type not in _meeting_types: raise ValueError( @@ -188,7 +191,7 @@ def create_meeting(): }, "participant_focused_meeting": False, "push_change_to_calendar": False, - "auto_start_meeting_summary": False, + "auto_start_meeting_summary": auto_start_meeting_summary, "auto_start_ai_companion_questions": False, "device_testing": False, }, @@ -287,7 +290,9 @@ def list_meetings(): response = requests.get(url, headers=headers, params=params) if response.status_code != 200: - return {"message": f"Error listing meetings: {response.text}"} + return { + "message": f"{response.status_code} Error listing meetings: {response.text}" + } res_json = response.json() for meeting in res_json["meetings"]: @@ -348,21 +353,6 @@ def update_meeting(): url = f"{ZOOM_API_URL}/meetings/{meeting_id}" payload = {} - settings = {} - if "MEETING_INVITEES" in os.environ: - meeting_invitees = os.environ[ - "MEETING_INVITEES" - ] # a list of emails separated by commas - if meeting_invitees != "" and not _validate_invitees( - meeting_invitees.split(",") - ): - raise ValueError( - f"Invalid invitees: {meeting_invitees}. Must be a list of valid email addresses separated by commas." - ) - meeting_invitees_list = [ - {"email": invitee} for invitee in meeting_invitees.split(",") - ] - settings["meeting_invitees"] = meeting_invitees_list if "AGENDA" in os.environ: agenda = os.environ["AGENDA"] @@ -392,18 +382,7 @@ def update_meeting(): if "PRE_SCHEDULE" in os.environ: pre_schedule = str_to_bool(os.environ["PRE_SCHEDULE"]) payload["pre_schedule"] = pre_schedule - if "AUDIO_RECORDING" in os.environ: - audio_recording = os.environ["AUDIO_RECORDING"] - settings["auto_recording"] = audio_recording - if "CONTACT_EMAIL" in os.environ: - contact_email = os.environ["CONTACT_EMAIL"] - settings["contact_email"] = contact_email - if "CONTACT_NAME" in os.environ: - contact_name = os.environ["CONTACT_NAME"] - settings["contact_name"] = contact_name - if "PRIVATE_MEETING" in os.environ: - private_meeting = str_to_bool(os.environ["PRIVATE_MEETING"]) - settings["private_meeting"] = private_meeting + if "START_TIME" in os.environ: start_time = os.environ["START_TIME"] if start_time != "" and not _validate_meeting_start_time(start_time): @@ -430,6 +409,42 @@ def update_meeting(): ) payload["type"] = meeting_type + # args of settings + settings = {} + if "AUDIO_RECORDING" in os.environ: + audio_recording = os.environ["AUDIO_RECORDING"] + settings["auto_recording"] = audio_recording + if "CONTACT_EMAIL" in os.environ: + contact_email = os.environ["CONTACT_EMAIL"] + settings["contact_email"] = contact_email + if "CONTACT_NAME" in os.environ: + contact_name = os.environ["CONTACT_NAME"] + settings["contact_name"] = contact_name + if "PRIVATE_MEETING" in os.environ: + private_meeting = str_to_bool(os.environ["PRIVATE_MEETING"]) + settings["private_meeting"] = private_meeting + + if "MEETING_INVITEES" in os.environ: + meeting_invitees = os.environ[ + "MEETING_INVITEES" + ] # a list of emails separated by commas + if meeting_invitees != "" and not _validate_invitees( + meeting_invitees.split(",") + ): + raise ValueError( + f"Invalid invitees: {meeting_invitees}. Must be a list of valid email addresses separated by commas." + ) + meeting_invitees_list = [ + {"email": invitee} for invitee in meeting_invitees.split(",") + ] + settings["meeting_invitees"] = meeting_invitees_list + + if "AUTO_START_MEETING_SUMMARY" in os.environ: + auto_start_meeting_summary = str_to_bool( + os.environ["AUTO_START_MEETING_SUMMARY"] + ) + settings["auto_start_meeting_summary"] = auto_start_meeting_summary + if settings: payload["settings"] = settings @@ -470,20 +485,24 @@ def get_meeting_summary(): } response = requests.get(url, headers=headers) if response.status_code != 200: - return {"message": f"Error getting meeting summary: {response.text}"} + return { + "message": f"{response.status_code} Error getting meeting summary: {response.text}" + } return response.json() @tool_registry.decorator("GetPastMeetingDetails") def get_past_meeting_details(): - meeting_id = os.environ["MEETING_ID"] - url = f"{ZOOM_API_URL}/past_meetings/{meeting_id}" + meeting_id_or_uuid = os.environ["MEETING_ID_OR_UUID"] + url = f"{ZOOM_API_URL}/past_meetings/{meeting_id_or_uuid}" headers = { "Authorization": f"Bearer {ACCESS_TOKEN}", } response = requests.get(url, headers=headers) if response.status_code != 200: - return {"message": f"Error getting past meeting details: {response.text}"} + return { + "message": f"{response.status_code} Error getting past meeting details: {response.text}" + } return response.json() @@ -496,5 +515,7 @@ def list_past_meeting_instances(): } response = requests.get(url, headers=headers) if response.status_code != 200: - return {"message": f"Error listing past meeting instances: {response.text}"} + return { + "message": f"{response.status_code} Error listing past meeting instances: {response.text}" + } return response.json() From 5b3cdd2272f2fb0b429610388d9fceea914b0849 Mon Sep 17 00:00:00 2001 From: Yingbei Date: Wed, 5 Mar 2025 20:20:54 +0800 Subject: [PATCH 3/5] fix: add logger to print some debug log to stderr --- zoom/tools/helper.py | 33 +++++++++++++++++++++++++++++++++ zoom/tools/meetings.py | 24 +++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/zoom/tools/helper.py b/zoom/tools/helper.py index 9fb05e67..b5518846 100644 --- a/zoom/tools/helper.py +++ b/zoom/tools/helper.py @@ -1,4 +1,6 @@ import os +import logging +import sys ACCESS_TOKEN = os.getenv("ZOOM_OAUTH_TOKEN") @@ -8,6 +10,37 @@ ZOOM_API_URL = "https://api.zoom.us/v2" +def setup_logger(name): + """Setup a logger that writes to sys.stderr. This will eventually show up in GPTScript's debugging logs. + + Args: + name (str): The name of the logger. + + Returns: + logging.Logger: The logger. + """ + # Create a logger + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) # Set the logging level + + # Create a stream handler that writes to sys.stderr + stderr_handler = logging.StreamHandler(sys.stderr) + + # Create a log formatter + formatter = logging.Formatter( + "[WordPress Tool Debugging Log]: %(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + stderr_handler.setFormatter(formatter) + + # Add the handler to the logger + logger.addHandler(stderr_handler) + + return logger + + +logger = setup_logger(__name__) + + def str_to_bool(value): """Convert a string to a boolean.""" return str(value).lower() in ("true", "1", "yes") diff --git a/zoom/tools/meetings.py b/zoom/tools/meetings.py index 965aef8f..0a28c7bf 100644 --- a/zoom/tools/meetings.py +++ b/zoom/tools/meetings.py @@ -1,4 +1,10 @@ -from tools.helper import ZOOM_API_URL, ACCESS_TOKEN, str_to_bool, tool_registry +from tools.helper import ( + ZOOM_API_URL, + ACCESS_TOKEN, + str_to_bool, + tool_registry, + setup_logger, +) from tools.users import get_user_type import requests import os @@ -9,6 +15,8 @@ from zoneinfo import ZoneInfo import json +logger = setup_logger(__name__) + def _convert_utc_to_local_time(utc_time_str: str, timezone: str) -> str: try: @@ -27,6 +35,7 @@ def _convert_utc_to_local_time(utc_time_str: str, timezone: str) -> str: ) # Customize format as necessary return output_gmt_format except Exception as e: + logger.error(f"Error converting time: {e}") raise ValueError(f"Error converting time: {e}") @@ -50,7 +59,10 @@ def _validate_meeting_start_time(input_time: str) -> bool: local_format = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$" # Validate against both formats - return bool(re.match(gmt_format, input_time) or re.match(local_format, input_time)) + res = bool(re.match(gmt_format, input_time) or re.match(local_format, input_time)) + if res == False: # invalid time format + logger.error(f"Invalid input time format: {input_time}") + return res def _validate_invitees(invitees: list) -> bool: @@ -101,6 +113,9 @@ def create_meeting(): "MEETING_INVITEES", "" ) # a list of emails separated by commas if meeting_invitees != "" and not _validate_invitees(meeting_invitees.split(",")): + logger.error( + f"Invalid invitees: {meeting_invitees}. Must be a list of valid email addresses separated by commas." + ) raise ValueError( f"Invalid invitees: {meeting_invitees}. Must be a list of valid email addresses separated by commas." ) @@ -207,8 +222,11 @@ def create_meeting(): recurrence_object = json.loads(recurrence) payload["recurrence"] = recurrence_object except Exception as e: + logger.error( + f"Exception {e}: Invalid recurrence: {recurrence}. Must be a valid JSON object." + ) raise ValueError( - f"Invalid recurrence: {recurrence}. Must be a valid JSON object." + f"Exception {e}: Invalid recurrence: {recurrence}. Must be a valid JSON object." ) # features for licensed users if user_type == 2: From 73ca027c2d915f245f8afaae7e475a114eba0b36 Mon Sep 17 00:00:00 2001 From: Yingbei Tong Date: Thu, 6 Mar 2025 11:28:40 +0800 Subject: [PATCH 4/5] fix: Update zoom/tools/helper.py Co-authored-by: Grant Linville --- zoom/tools/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zoom/tools/helper.py b/zoom/tools/helper.py index b5518846..e6ebe8e3 100644 --- a/zoom/tools/helper.py +++ b/zoom/tools/helper.py @@ -28,7 +28,7 @@ def setup_logger(name): # Create a log formatter formatter = logging.Formatter( - "[WordPress Tool Debugging Log]: %(asctime)s - %(name)s - %(levelname)s - %(message)s" + "[Zoom Tool Debugging Log]: %(asctime)s - %(name)s - %(levelname)s - %(message)s" ) stderr_handler.setFormatter(formatter) From 2937247fb6ab4f9c94b2b87137815bc0a9102940 Mon Sep 17 00:00:00 2001 From: Yingbei Date: Thu, 6 Mar 2025 11:36:37 +0800 Subject: [PATCH 5/5] fix: typo fix as Grant pointed out --- zoom/tools/meetings.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/zoom/tools/meetings.py b/zoom/tools/meetings.py index 0a28c7bf..36dc6d32 100644 --- a/zoom/tools/meetings.py +++ b/zoom/tools/meetings.py @@ -373,14 +373,11 @@ def update_meeting(): payload = {} if "AGENDA" in os.environ: - agenda = os.environ["AGENDA"] - payload["agenda"] = agenda + payload["agenda"] = os.environ["AGENDA"] if "DEFAULT_PASSWORD" in os.environ: - default_password = str_to_bool(os.environ["DEFAULT_PASSWORD"]) - payload["default_password"] = default_password + payload["default_password"] = str_to_bool(os.environ["DEFAULT_PASSWORD"]) if "DURATION" in os.environ: - duration = int(os.environ["DURATION"]) - payload["duration"] = duration + payload["duration"] = int(os.environ["DURATION"]) if "PASSWORD" in os.environ: password = os.environ["PASSWORD"] if password == "": @@ -398,8 +395,7 @@ def update_meeting(): payload["recurrence"] = recurrence_object if "PRE_SCHEDULE" in os.environ: - pre_schedule = str_to_bool(os.environ["PRE_SCHEDULE"]) - payload["pre_schedule"] = pre_schedule + payload["pre_schedule"] = str_to_bool(os.environ["PRE_SCHEDULE"]) if "START_TIME" in os.environ: start_time = os.environ["START_TIME"] @@ -410,14 +406,11 @@ def update_meeting(): payload["start_time"] = start_time if "MEETING_TEMPLATE_ID" in os.environ: - meeting_template_id = os.environ["MEETING_TEMPLATE_ID"] - payload["template_id"] = meeting_template_id + payload["template_id"] = os.environ["MEETING_TEMPLATE_ID"] if "TIMEZONE" in os.environ: - timezone = os.environ["TIMEZONE"] - payload["timezone"] = timezone + payload["timezone"] = os.environ["TIMEZONE"] if "TOPIC" in os.environ: - topic = os.environ["TOPIC"] - payload["topic"] = topic + payload["topic"] = os.environ["TOPIC"] if "MEETING_TYPE" in os.environ: meeting_type = int(os.environ["MEETING_TYPE"]) @@ -430,17 +423,13 @@ def update_meeting(): # args of settings settings = {} if "AUDIO_RECORDING" in os.environ: - audio_recording = os.environ["AUDIO_RECORDING"] - settings["auto_recording"] = audio_recording + settings["audio_recording"] = os.environ["AUDIO_RECORDING"] if "CONTACT_EMAIL" in os.environ: - contact_email = os.environ["CONTACT_EMAIL"] - settings["contact_email"] = contact_email + settings["contact_email"] = os.environ["CONTACT_EMAIL"] if "CONTACT_NAME" in os.environ: - contact_name = os.environ["CONTACT_NAME"] - settings["contact_name"] = contact_name + settings["contact_name"] = os.environ["CONTACT_NAME"] if "PRIVATE_MEETING" in os.environ: - private_meeting = str_to_bool(os.environ["PRIVATE_MEETING"]) - settings["private_meeting"] = private_meeting + settings["private_meeting"] = str_to_bool(os.environ["PRIVATE_MEETING"]) if "MEETING_INVITEES" in os.environ: meeting_invitees = os.environ[ @@ -458,10 +447,9 @@ def update_meeting(): settings["meeting_invitees"] = meeting_invitees_list if "AUTO_START_MEETING_SUMMARY" in os.environ: - auto_start_meeting_summary = str_to_bool( + settings["auto_start_meeting_summary"] = str_to_bool( os.environ["AUTO_START_MEETING_SUMMARY"] ) - settings["auto_start_meeting_summary"] = auto_start_meeting_summary if settings: payload["settings"] = settings