Zoom

Use the Zoom integration to manage your Zoom users and meetings.

Messaging and Conferencing · Zoom

Details

IDZoom
ProviderZoom
CategoryMessaging and Conferencing
From Version5.0.0
Docker Imagedemisto/fastapi:0.125.0.10158186
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Use the Zoom integration manage your Zoom users and meetings
This integration was integrated and tested with version 2.0.0 of Zoom

Configure Zoom in Cortex

Parameter Description Required
Server URL (e.g., ‘https://api.zoom.us/v2/’)   True
Account ID (OAuth)   True
Client ID (OAuth)   True
Client Secret (OAuth)   True
Use system proxy settings   False
Trust any certificate (not secure)   False
Long running instance Enable in order to use zoom-ask and for mirroring. False
Listen Port Listener port number. Required when choosing long running instance with an external engine for using the Zoom Bot. False
Bot JID Zoom Bot app JID. False
Bot Client ID (OAuth) Zoom Bot app client ID. False
Bot Client Secret (OAuth) Zoom Bot app secret ID. False
Secret Token For mirroring, see Configuring Secret Token. False
Verification Token For verify the mirror in. False
Mirroring Enable Incident Mirroring. See how to configure the app. False
Certificate (Required for HTTPS) (For Cortex XSOAR 6.x) For use with HTTPS - the certificate that the service should use. (For Cortex XSOAR 8 and Cortex XSIAM) Custom certificates are supported only using engine. False
Private Key (Required for HTTPS) (For Cortex XSOAR 6.x) For use with HTTPS - the private key that the service should use. (For Cortex XSOAR 8 and Cortex XSIAM) When using an engine, configure a private API key False

Server configuration (XSOAR 6.x)

In the Server Configuration section, verify that the value for the instance.execute.external.INTEGRATION-INSTANCE-NAME key is set to true. If this key does not exist:

  1. Click + Add Server Configuration.
  2. Add instance.execute.external.INTEGRATION-INSTANCE-NAME and set the value to true.

XSOAR endpoint URL-

  • For Cortex XSOAR 6.x: <CORTEX-XSOAR-URL>/instance/execute/<INTEGRATION-INSTANCE-NAME>. For example, https://my.demisto.live/instance/execute/zoom. Note that the string instance does not refer to the name of your XSOAR instance, but rather is part of the URL.
  • For Cortex XSOAR 8.x / XSIAM: you need to run using external engine: https://<Engine URL>:<port>. For example, https://my-engine-url:7001.

Create Zoom ChatBOT app

  1. Log onto the Zoom App Marketplace: https://marketplace.zoom.us/.
  2. Click Develop > Build App.
  3. Choose General App.
  4. Click Create.

enter image description here

Configure App Settings

  1. Update your app’s name. By default, the build flow generates a generic name for your app. To change your app’s name, select the edit icon (pencil) and update the name. To save your changes, click anywhere outside of the app name field.
  2. Under the section “Select how the app is managed”, select “Admin-managed”, and click “Save”.
  3. Enter your Cortex XSOAR endpoint URL in the OAuth Redirect URL.
  4. Click Features > Surface > Team Chat.
    Toggle on the “Team Chat Subscription” section. Under “Bot Endpoint URL”, add:
    • For Cortex XSOAR 6.x: <CORTEX-XSOAR-URL>/instance/execute/<INTEGRATION-INSTANCE-NAME>. For example, https://my.demisto.live/instance/execute/zoom. Note that the string instance does not refer to the name of your Cortex XSOAR instance, but rather is part of the URL.
    • For Cortex XSOAR 8.x / XSIAM you need to run using external engine: https://<Engine Url>:<port>. For example, https://my-engine-url:7001.

enter image description here

  1. Click Scopes > + Add Scopes to add the following scope permissions.

    Scope Type Scope Name
    Team Chat /imchat:bot
    Team Chat /team_chat:read:user_message:admin
    Team Chat /team_chat:read:list_user_sessions:admin
    Team Chat /team_chat:read:list_user_messages:admin
    Team Chat /team_chat:write:user_channel:admin
    Team Chat /team_chat:write:members:admin
    Team Chat /team_chat:update:user_channel:admin
    User /user:read:user:admin

enter image description here

  1. Click Local Test > Add App Now to test your app and authorize your Cortex XSOAR app.

  2. If mirroring is enabled in the integration configuration or using ZoomAsk:

    • Endpoint URL Requirements:
      According to the Zoom requirements in order to receive webhooks, the Event notification endpoint URL that you specify for each event subscription must:
      • Be a publicly accessible https endpoint url that supports TLSv1.2+ with a valid certificate chain issued by a Certificate Authority (CA).
      • Employ the https:// scheme.
      • Be able to accept HTTP POST requests containing JSON payloads.
      • Be a fully qualified domain name (FQDN).
      • be able to respond with an HTTP Status Code.
    • Zoom Configurations:

      1. Copy the Secret Token from the “Features” > “Access” page under the “Token” section and add it to the instance configuration. enter image description here
      2. Configure Event Subscriptions.
      3. In the “Features” section, under “Access”, in the “General Features” section, enable “Event Subscription”.
      4. Click +Add New Event Subscription.
      5. Enter the following information:
        • Subscription name: Enter a name for this Event Subscription (e.g., “Send Message Sent”)
        • Add Events: Click +Add Events. Under Event Types, select Chat Message, and then select Chat Message Sent.
        • Event notification endpoint URL: Enter the Cortex XSOAR URL of your server (CORTEX-XSOAR-URL/instance/execute/INTEGRATION-INSTANCE-NAME) where you want to receive event notifications. This URL should handle incoming event data from Zoom. Make sure it’s publicly accessible.
        • Authentication Header Option -
          1. Default Header Provided by Zoom option - This option allows you to use a verification token provided by Zoom. Copy the Verification Token under the “Token” section and add it to the instance configuration.
            enter image description here

          2. Basic Authentication option - You can use Basic Authentication by providing your Zoom Client ID (OAuth) and Secret ID (OAuth) as configured in the instance configuration.
            enter image description here

        enter image description here

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

zoom-create-user


Create a new user in zoom account

Base Command

zoom-create-user

Input

Argument Name Description Required
first_name First name of the new user. Required
last_name Last name of the new user. Required
email The email of the new user. Required
user_type The type of the newly created user. Note: the old type “pro” in now called “Licensed”, and the type “Corporate” is not sopprted in Zoom v2 and above.. Possible values are: Basic, Licensed, pro, Corporate. Default is Basic. Optional

Context Output

Path Type Description
Zoom.User.id string The ID of the created user
Zoom.User.first_name string First name of the created user
Zoom.User.last_name string Last name for the created user
Zoom.User.email string Email of the created user
Zoom.User.created_at date Created date of the user. Note that this field does not appear in zoom v2 and above.
Zoom.User.type number The type of the user

Command example

!zoom-create-user email=example@example.com first_name=john last_name=smith user_type=Basic

Context Example

{
    "Zoom": {
        "User": {
            "email": "example@example.com",
            "first_name": "john",
            "id": "wSQafNLNSJWq_oBzmT7XOw",
            "last_name": "smith",
            "type": 1
        }
    }
}

Human Readable Output

User created successfully with ID: wSQafNLNSJWq_oBzmT7XOw

zoom-create-meeting


Create a new zoom meeting (scheduled ,instant, or recurring)

Base Command

zoom-create-meeting

Input

Argument Name Description Required
type The type of the meeting. Possible values are: Instant, Scheduled, Recurring meeting with fixed time. Default is Instant. Required
end_date_time For recurring meetings only. Select the final date on which the meeting will recur before it is canceled. For example: 2017-11-25T12:00:00Z. Optional
end_times For recurring meetings only.Select how many times the meeting should recur before it is canceled. max = 365. Default = 1.. Optional
monthly_day For recurring meetings with Monthly recurrence_type only. State the day in a month the meeting should recur. The value range is from 1 to 31. Default = 1.. Optional
monthly_week For recurring meetings with Monthly recurrence_type only.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. Allowed: -1 (for last week of the month) ┃1┃2┃3┃4. Optional
monthly_week_day For recurring meetings with Monthly recurrence_type only. State a specific day in a week when the monthly meeting should recur. Allowed: 1┃2┃3┃4┃5┃6┃7To use this field, you must also use the monthly_week field.. Optional
repeat_interval For recurring meeting with fixed time only. Define the interval at which the meeting should recur. For instance, if you would like to schedule a meeting that recurs every two months, you must set the value of this field as 2 and the value of the type parameter as Monthly. For a daily meeting, the maximum is 90 days. For a weekly meeting the maximum is of 12 weeks. For a monthly meeting, there is a maximum of 3 months. Optional
recurrence_type For recurring meetings only. Set the recurrence meeting types.. Possible values are: Daily, Weekly, Monthly. Optional
weekly_days For recurring meetings with a Weekly recurrence_type only. State a specific day in a week when the weekly meeting should recur. Allowed: 1┃2┃3┃4┃5┃6┃7 Default = 1.. Optional
auto-record-meeting The automatic recording settings. Note that the Cloud option is available for Zoom paid customers only. Possible values are: local, cloud, none. Default is none. Optional
encryption_type The type of end-to-end (E2EE) encryption, enhanced_encryption or e2ee. Possible values are: enhanced_encryption, e2ee. Default is enhanced_encryption. Optional
host_video start meetings with the host video on. Possible values are: true, false. Default is True. Optional
join_before_host_time If the value of the join_before_host field is true, this field sets the time that a participant can join before the meeting’s host. You can choose: 5 or 10 (minutes), or 0 for any time. Possible values are: 0, 5, 10. Optional
join_before_host Whether participants can join the meeting before its host. For scheduled or recurring meetings only. The default value is False. Possible values are: false, true. Optional
meeting_authentication If true, only authenticated users can join the meeting. Possible values are: false, true. Default is false. Optional
user email address or id of user for meeting. Required
topic The topic of the meeting. Required
waiting_room This allows the host to control when a participant joins the meeting. The default is False. Possible values are: false, true. Optional
start-time Meeting start time. When using a format like “yyyy-MM-ddTHH:mm:ssZ”, always use GMT time. When using a format like “yyyy-MM-ddTHH:mm:ss”, you should use local time and you will need to specify the time zone. Only used for scheduled meetings and recurring meetings with fixed time. Optional
timezone Timezone to format start_time. For example, “America/Los_Angeles”. For scheduled meetings only. . Optional

Context Output

Path Type Description
Zoom.Meeting.join_url string Join url for the meeting
Zoom.Meeting.id string Meeting id of the new meeting that is created
Zoom.Meeting.start_url string The URL to start the meeting
Zoom.Meeting.uuid string Unique meeting ID of the new meeting that is created
Zoom.Meeting.status string The status of the meeting
Zoom.Meeting.start_time Date The time that the meeting will start at
Zoom.Meeting.host email string The email of the host of this meeting
Zoom.Meeting.topic string The topic of the meeting
Zoom.Meeting.duration number The duratian of the meeting
Zoom.Meeting.created_at Date The time that this meeting was created
Zoom.Meeting.type number The type of the new meeting, Instant = 1, Scheduled =2,Recurring with fixed time = 8

Command example

!zoom-create-meeting topic=test type=scheduled user=example@example.com start-time=2023-01-29T12:12:12Z

Context Example

{
    "Zoom": {
        "Meeting": {
            "created_at": "2023-01-15T12:44:30Z",
            "duration": 60,
            "host_email": "example@example.com",
            "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
            "id": 88454393857,
            "join_url": "https://us06web.zoom.us/j/8845?pwd=WkI1WUdvbHhnMlJuaXU1WDNPdz09",
            "pre_schedule": false,
            "settings": {
                "allow_multiple_devices": false,
                "alternative_host_update_polls": false,
                "alternative_hosts": "",
                "alternative_hosts_email_notification": true,
                "approval_type": 2,
                "approved_or_denied_countries_or_regions": {
                    "enable": false
                },
                "audio": "both",
                "auto_recording": "none",
                "breakout_room": {
                    "enable": false
                },
                "close_registration": false,
                "cn_meeting": false,
                "device_testing": false,
                "email_notification": true,
                "enable_dedicated_group_chat": false,
                "encryption_type": "enhanced_encryption",
                "enforce_login": false,
                "enforce_login_domains": "",
                "focus_mode": false,
                "global_dial_in_countries": [
                    "US"
                ],
                "global_dial_in_numbers": [
                    {
                        "city": "Houston",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3462487799",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3602095623",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3863475053",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 5074734847",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 5642172000",
                        "type": "toll"
                    },
                    {
                        "city": "New York",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6465588656",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6469313860",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6694449171",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6892781000",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 7193594580",
                        "type": "toll"
                    },
                    {
                        "city": "Denver",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 7207072699",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 2532050468",
                        "type": "toll"
                    },
                    {
                        "city": "Tacoma",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 2532158782",
                        "type": "toll"
                    },
                    {
                        "city": "Washington DC",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3017158592",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3052241968",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3092053325",
                        "type": "toll"
                    },
                    {
                        "city": "US",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3126266799",
                        "type": "toll"
                    }
                ],
                "host_save_video_order": false,
                "host_video": true,
                "in_meeting": false,
                "jbh_time": 0,
                "join_before_host": false,
                "meeting_authentication": false,
                "mute_upon_entry": false,
                "participant_video": false,
                "private_meeting": false,
                "registrants_confirmation_email": true,
                "registrants_email_notification": true,
                "request_permission_to_unmute_participants": false,
                "show_share_button": false,
                "use_pmi": false,
                "waiting_room": false,
                "watermark": false
            },
            "start_time": "2023-01-29T12:12:12Z",
            "start_url": "https://us06web.zoom.us/s/883857?zIjAwMDAwMSIsInptX3NrbSI6InptX28ybSIsImFsZyI6IkhTMjU2In0.eyJhdWQiOiJjbGllbnRzbSIsInVpZCI6InVKaVpOLU83UnA2SnBfOTk1RnBaR2ciLCJpc3MiOiJ3ZWIiLCJzayI6IjczMjU5NTExMTgxNDYyODc0NjciLCJzdHkiOjEwMCwid2NkIjoidXMwNiIsImNsdCI6MCwibW51bSI6Ijg4NDU0MzkzODU3IiwiZXhwIjoxNjczNzkzODcwLCJpYXQiOjE2NzM3ODY2NzAsImFpZCI6ImFlS0QyQkZKUkFTdDFRVlVSV285Q0EiLCJjaWQiOiIifQ.5vRJBkMbmODUD_7H3bkS7OjR-MuuLUzNMJ_KeCzWc_U",
            "status": "waiting",
            "timezone": "Asia/Jerusalem",
            "topic": "test",
            "type": 2,
            "uuid": "4gbib+fjTFmz1wH1LoE7EQ=="
        }
    }
}

Human Readable Output

Meeting details

uuid id host_id host_email topic type status start_time duration timezone created_at start_url join_url
4gbib+fjTFmz1wH1LoE7EQ== 88454393857 uJiZN-O7Rp6Jp_995FpZGg example@example.com test 2 waiting 2023-01-29T12:12:12Z 60 Asia/Jerusalem 2023-01-15T12:44:30Z https://us06web.zoom.us/s/88454393857?zak=eyJ0eXAiOiJKV1QiLCJzdiI6IjAwMDAwMSIsInptX3NrbSI6InptX28ybSIsImFsZyI6IkhTMjU2In0.eyJhdWQiOiJjbGllbnRzbSIsInVpZCI6InVKaVpOLU83UnA2SnBfOTk1RnBaR2ciLCJpc3MiOiJ3ZWIiLCJzayI6IjczMjU5NTExMTgxNDYyODc0NjciLCJzdHkiOjEwMCwid2NkIjoidXMwNiIsImNsdCI6MCwibW51bSI6Ijg4NDU0MzkzODU3IiwiZXhwIjoxNjczNzkzODcwLCJpYXQiOjE2NzM3ODY2NzAsImFpZCI6ImFlS0QyQkZKUkFTdDFRVlVSV285Q0EiLCJjaWQiOiIifQ.5vRJBkMbmODUD_7H3bkS7OjR-MuuLUzNMJ_KeCzWc_U https://us06web.zoom.us/j/88454393857?pwd=WkI1WnVEWUdvbHhnMlJuaXU1WDNPdz09

zoom-fetch-recording


Get meeting record and save as file in the warroom

Base Command

zoom-fetch-recording

Input

Argument Name Description Required
meeting_id Meeting ID of the recorded meeting. Required
delete_after Whether to delete the recording from the cloud after downloading. Possible values are: false, true. Default is true. Optional

Context Output

Path Type Description
File.SHA256 Unknown Attachment’s SHA256
File.SHA1 Unknown Attachment’s SHA1
File.MD5 Unknown Attachment’s MD5
File.Name Unknown Attachment’s Name
File.Info Unknown Attachment’s Info
File.Size Unknown Attachment’s Size (In Bytes)
File.Extension Unknown Attachment’s Extension
File.Type Unknown Attachment’s Type
File.EntryID Unknown Attachment’s EntryID
File.SSDeep Unknown Attachment’s SSDeep hash

Command example

!zoom-fetch-recording meeting_id=83622325727 delete_after=false

Context Example

{
    "File": [
        {
            "EntryID": "446@37e93103-1bd6-4776-8021-0f7023b1bb79",
            "Extension": "MP4",
            "Info": "video/mp4",
            "MD5": "0de01f8f6d037e9ebecde2f91ad9b7a3",
            "Name": "recording_83622325727_d831fbd5-3938-44d0-b30b-962bf76e2916.MP4",
            "SHA1": "c81f9abb6dcf9be42dae99bc5e1501c43496d66e",
            "SHA256": "8b252b01c8e6af62b88de64abf80d360467da1598dcd6b50ac9a252ffafb2eb5",
            "SHA512": "b273e90a9cd0589eaba1c5e647fedab7b46e198193a54bec70a18def9ada57575833ea89b6eabf7f5852c3dc98b862780793d3e87e32a238ec2a7acffdba2495",
            "SSDeep": "1536:TDWnSNbM8oEWzjSn7anYBcoGdee32hfUunvSHx8:T6t8JWzjSWnYBcoG9325UunvSHq",
            "Size": 320333,
            "Type": "ISO Media, MP4 v2 [ISO 14496-14]"
        },
        {
            "EntryID": "447@37e93103-1bd6-4776-8021-0f7023b1bb79",
            "Extension": "M4A",
            "Info": "video/mp4",
            "MD5": "e826111564499ca8021d0ddfcfde064b",
            "Name": "recording_83622325727_19bf5f8a-e77c-4b75-b09e-13983521703c.M4A",
            "SHA1": "50d3386dc5b74935b8f8d541f319df34a49f90a0",
            "SHA256": "2719528cf61358ccbee861fb8e42d9d5fb37390baa22d0dc7dad1aa7e3935146",
            "SHA512": "74bfb40ade824e6e36329b3e2335b2028fac648d3ecef1a02e9be93d4a455e3acf4c1f9c844d1c2ebcc661d4c84d66c1da4f0ca0462eabc12bf2dff84006d460",
            "SSDeep": "24:fctSXvr4S+08n11/TlllZltk2B0tDilduxEJvCXyxrApd4dEcpXuFxnZQ4r:uYr41Xnf/T/vX/8OvqyrjiCuFhZQK",
            "Size": 243652,
            "Type": "ISO Media, MP4 v2 [ISO 14496-14]"
        }
    ]
}

Human Readable Output

The Audio file recording_83622325727_19bf5f8a-e77c-4b75-b09e-13983521703c.M4A was downloaded successfully

zoom-list-users


List the existing users

Base Command

zoom-list-users

Input

Argument Name Description Required
status Which status of users to list. Possible values are: active, inactive, pending. Default is active. Optional
page-size Number of users to return. Max 300. The default is 30. Optional
next_page_token The next page token is used to get the next page. IMPORTANT: You must pass the same page size that you passed in the first call. Optional
page-number Which page of results to return. The default = 1. Note: This argument is in a deprecate process by the API. As an alternative use “next_page_token” or “limit”.. Optional
limit The total amunt of results to show. Optional
user_id A user ID. this is for a singel user. Optional
role_id Filter the response by a specific role. For example: role_id=0 (Owner), role_id=2 (Member). Optional

Context Output

Path Type Description
Zoom.Metadata.Count number Total page count available
Zoom.Metadata.Number number Current page number
Zoom.Metadata.Size number Number of results in current page
Zoom.Metadata.Total number Total number of records
Zoom.User.id string ID of the user
Zoom.User.first_name string First name of user
Zoom.User.last_name string Last name of user
Zoom.User.email string Email of user
Zoom.User.type number Type of user
Zoom.User.created_at date Date when user was created
Zoom.User.dept string Department for user
Zoom.User.verified number Is the user verified
Zoom.User.last_login_time date Last login time of the user
Zoom.User.timezone string Default timezone for the user
Zoom.User.pmi string PMI of user
Zoom.User.group_ids string Groups user belongs to

Command example

!zoom-list-users status=pending limit=10

Context Example

{
    "Zoom": {
        "Metadata": {
            "Count": 6,
            "Number": 1,
            "Size": 10,
            "Total": 59
        },
        "User": [
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T12:55:33Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T13:09:28Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2022-12-22T10:19:47Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T13:12:46Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T12:04:08Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T12:56:08Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T12:58:16Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T13:15:04Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T11:58:04Z",
                "verified": 0
            },
            {
                "created_at": "2023-01-15T12:44:34Z",
                "email": "example@example.com",
                "id": "",
                "pmi": 0,
                "role_id": "0",
                "status": "pending",
                "type": 1,
                "user_created_at": "2023-01-02T13:09:52Z",
                "verified": 0
            }
        ]
    }
}

Human Readable Output

Users

id email type pmi verified created_at status role_id
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0
  example@example.com 1 0 0 2023-01-15T12:44:34Z pending 0

Metadata

total_records
59

zoom-delete-user


Delete a user from Zoom

Base Command

zoom-delete-user

Input

Argument Name Description Required
user The user ID or email to delete. Required
action The action to take. Possible values are: disassociate, delete. Default is disassociate. Optional

Context Output

There is no context output for this command.

zoom-meeting-get


Get the information of an existing zoom meeting

Base Command

zoom-meeting-get

Input

Argument Name Description Required
meeting_id The id of the existing meeting. Required
occurrence_id Provide this field to view meeting details of a particular occurrence of the recurring meeting. Optional
show_previous_occurrences Set the value of this field to true if you would like to view meeting details of all previous occurrences of a recurring meeting. Possible values are: false, true. Default is True. Optional

Context Output

Path Type Description
Zoom.Meeting.join_url string Join url for the meeting
Zoom.Meeting.id string Meeting id of the new meeting that is created
Zoom.Meeting.start_url string The URL to start the meeting
Zoom.Meeting.uuid string Unique meeting ID of the new meeting that is created
Zoom.Meeting.status string The status of the meeting
Zoom.Meeting.start_time Date The time that the meeting will start at
Zoom.Meeting.host email string The email of the host of this meeting
Zoom.Meeting.topic string The topic of the meeting
Zoom.Meeting.duration number The duratian of the meeting
Zoom.Meeting.created_at Date The time that this meeting was created
Zoom.Meeting.type number The type of the new meeting, Instant = 1, Scheduled =2,Recurring with fixed time = 8

Command example

!zoom-meeting-get meeting_id=88949894296

Context Example

{
    "Zoom": {
        "Meeting": {
            "agenda": "",
            "assistant_id": "",
            "created_at": "2022-12-29T08:10:13Z",
            "duration": 60,
            "host_email": "example@example.com",
            "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
            "id": 88949894296,
            "join_url": "https://us06web.zoom.us/94296?pwd=b3GdrSDBBNU1FYTVRVmdadz09",
            "pre_schedule": false,
            "settings": {
                "allow_multiple_devices": false,
                "alternative_host_update_polls": false,
                "alternative_hosts": "",
                "alternative_hosts_email_notification": true,
                "approval_type": 2,
                "approved_or_denied_countries_or_regions": {
                    "enable": false
                },
                "audio": "both",
                "auto_recording": "none",
                "breakout_room": {
                    "enable": false
                },
                "close_registration": false,
                "cn_meeting": false,
                "device_testing": false,
                "email_notification": true,
                "enable_dedicated_group_chat": false,
                "encryption_type": "enhanced_encryption",
                "enforce_login": false,
                "enforce_login_domains": "",
                "focus_mode": false,
                "global_dial_in_countries": [
                    "US"
                ],
                "global_dial_in_numbers": [
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6469313860",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6694449171",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6892781000",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 7193594580",
                        "type": "toll"
                    },
                    {
                        "city": "Denver",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 7207072699",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 2532050468",
                        "type": "toll"
                    },
                    {
                        "city": "Tacoma",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 2532158782",
                        "type": "toll"
                    },
                    {
                        "city": "Washington DC",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3017158592",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3052241968",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3092053325",
                        "type": "toll"
                    },
                    {
                        "city": "US",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3126266799",
                        "type": "toll"
                    },
                    {
                        "city": "Houston",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3462487799",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3602095623",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 3863475053",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 5074734847",
                        "type": "toll"
                    },
                    {
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 5642172000",
                        "type": "toll"
                    },
                    {
                        "city": "New York",
                        "country": "US",
                        "country_name": "US",
                        "number": "+1 6465588656",
                        "type": "toll"
                    }
                ],
                "host_save_video_order": false,
                "host_video": true,
                "in_meeting": false,
                "jbh_time": 0,
                "join_before_host": false,
                "meeting_authentication": false,
                "mute_upon_entry": false,
                "participant_video": false,
                "private_meeting": false,
                "registrants_confirmation_email": true,
                "registrants_email_notification": true,
                "request_permission_to_unmute_participants": false,
                "show_share_button": false,
                "use_pmi": false,
                "waiting_room": false,
                "watermark": false
            },
            "start_time": "2022-12-29T12:12:12Z",
            "start_url": "https://us06web.zoom.us/s/88949?zak=eyJ0eXAiOiJKV1QiLCJzdiI6IjAwMDAwMSIsInptX3NrbSI6InVKaVpOLU83UnA2SnBfOTk1RnBaR2ciLCJpc3MiOiJ3ZWIiLCJzayI6IjczMjU5NTExMTgxNDYyODc0NjciLCJzdHkiOjEwMCwid2NkIjoidXMwNiIsImNsdCI6MCwibW51bSI6Ijg4OTQ5ODk0Mjk2IiwiZXhwIjoxNjczNzkzODc2LCJpYXQiOjE2NzM3ODY2NzYsImFpZCI6ImFlS0QyQkZKUkFTdDFRVlVSV285Q0EiLCJjaWQiOiIifQ.BTOeH_-MZRm7A5sACnDJrP_zKbzaDCWZ5orvtH4rVb0",
            "status": "waiting",
            "timezone": "Asia/Jerusalem",
            "topic": "test",
            "type": 2,
            "uuid": "anhEx2x6QWG7TREn71MmoA=="
        }
    }
}

Human Readable Output

Meeting details

uuid id host_id host_email topic type status start_time duration timezone agenda created_at start_url join_url
anhEx2x6QWG7TREn71MmoA== 88949894296 uJiZN-O7Rp6Jp_995FpZGg example@example.com test 2 waiting 2022-12-29T12:12:12Z 60 Asia/Jerusalem   2022-12-29T08:10:13Z https://us06web.zoom.us/s/88949894296?zak=eyJ0eXAiOiJKV1QiLCJzdiI6IjAwMDAwMSIsInptX3NrbSI6InptX28ybSIsImFsZyI6IkhTMjU2In0.eyJhdWQiOiJjbGllbnRzbSIsInVpZCI6InVKaVpOLU83UnA2SnBfOTk1RnBaR2ciLCJpc3MiOiJ3ZWIiLCJzayI6IjczMjU5NTExMTgxNDYyODc0NjciLCJzdHkiOjEwMCwid2NkIjoidXMwNiIsImNsdCI6MCwibW51bSI6Ijg4OTQ5ODk0Mjk2IiwiZXhwIjoxNjczNzkzODc2LCJpYXQiOjE2NzM3ODY2NzYsImFpZCI6ImFlS0QyQkZKUkFTdDFRVlVSV285Q0EiLCJjaWQiOiIifQ.BTOeH_-MZRm7A5sACnDJrP_zKbzaDCWZ5orvtH4rVb0 https://us06web.zoom.us/j/88949894296?pwd=b3dzT1pzWGdrSDBBNU1FYTVRVmdadz09

zoom-meeting-list


Show all the meetings of a given user. Note: only scheduled and unexpired meetings will appear.

Base Command

zoom-meeting-list

Input

Argument Name Description Required
user_id The user ID of the meetings owner. Required
page_size Number of users to return. Default = 30. Max = 300. Optional
page_number Which page of results to return. The default = 1. Note: This argument is in a deprecate process by the API. As an alternative use “next_page_token” or “limit”. Optional
next_page_token The next page token is used to paginate te the next page. IMPORTENT: You must pass the same page size that you pased at the first call.. Optional
limit The total amunt of results to show. Optional
type Filter the results by searching specific types. Possible values are: all, scheduled, live, upcoming, upcoming_meetings, previous_meetings. Default is “scheduled”. Optional

Context Output

Path Type Description
Zoom.Metadata.Total number Total number of records
Zoom.Meeting.page_size number The size of the page returned from the API
Zoom.Meeting.total_records number The total records in the API for this request.
Zoom.Meeting.join_url string Join url for the meeting
Zoom.Meeting.id string Meeting id of the new meeting that is created
Zoom.Meeting.start_url string The URL to start the meeting
Zoom.Meeting.uuid string Unique meeting ID of the new meeting that is created
Zoom.Meeting.status string The status of the meeting
Zoom.Meeting.start_time Date The time that the meeting will start at
Zoom.Meeting.host email string The email of the host of this meeting
Zoom.Meeting.topic string The topic of the meeting
Zoom.Meeting.duration number The duratian of the meeting
Zoom.Meeting.created_at Date The time that this meeting was created
Zoom.Meeting.type unknown The ty pe of this meeting

Command example

!zoom-meeting-list user_id=example@example.com type=scheduled limit=7

Context Example

{
    "Zoom": {
        "Meeting": {
            "meetings": [
                {
                    "created_at": "2022-12-04T10:41:27Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 83810397585,
                    "join_url": "https://us06web.zoom.us/j/83810397585?pwd=OGFiZjVvMWZzdDBoaXlLdz09",
                    "start_time": "2022-12-10T11:00:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "2",
                    "type": 8,
                    "uuid": "z93Dc6Kj3jx+zaYg=="
                },
                {
                    "created_at": "2022-12-06T07:59:02Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 84540166459,
                    "join_url": "https://us06web.zoom.us/j/84540166459?pwd=ZzZaaVpFYlBVYnFoUT09",
                    "start_time": "2022-12-12T08:00:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "My recurring Meeting -Dima",
                    "type": 8,
                    "uuid": "4kZQ+HlFOAz0VBPHoCYg=="
                },
                {
                    "created_at": "2022-12-27T21:05:41Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 89824497327,
                    "join_url": "https://us06web.zoom.us/j/89824497327?pwd=SWp3b3VDRVhmQT09",
                    "start_time": "2022-12-27T21:05:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "efe",
                    "type": 8,
                    "uuid": "RABXy2D4hA1rgpDgQ=="
                },
                {
                    "created_at": "2022-12-27T21:11:01Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 83245658341,
                    "join_url": "https://us06web.zoom.us/j/83245658341?pwd=SmNGMHI0RDNkZ2Yms3Zz09",
                    "start_time": "2022-12-27T21:11:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "efe",
                    "type": 8,
                    "uuid": "qOXvloSamJjjFaFgniTA=="
                },
                {
                    "created_at": "2022-12-27T21:25:54Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 88468901206,
                    "join_url": "https://us06web.zoom.us/j/88468901206?pwd=RXZYQlWCtpS3l1MHUxZz09",
                    "start_time": "2022-12-27T21:25:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "efe",
                    "type": 8,
                    "uuid": "yHzCvl4Sry+C9LnCwdnwQ=="
                },
                {
                    "created_at": "2022-12-28T06:39:48Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 87525048161,
                    "join_url": "https://us06web.zoom.us/j/87525048161?pwd=ZXlV3U0OUxxSlBkdz09",
                    "start_time": "2022-12-28T06:39:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "efe",
                    "type": 8,
                    "uuid": "Sblh/S1W+rTKUcojJjw=="
                },
                {
                    "created_at": "2022-12-28T06:42:25Z",
                    "duration": 60,
                    "host_id": "uJiZN-O7Rp6Jp_995FpZGg",
                    "id": 83877839723,
                    "join_url": "https://us06web.zoom.us/j/83877839723?pwd=WySkY4Zkc4Zz09",
                    "start_time": "2022-12-28T06:42:00Z",
                    "timezone": "Asia/Jerusalem",
                    "topic": "efe",
                    "type": 8,
                    "uuid": "iLXDe4HsR6uMb+x8GyybTA=="
                }
            ],
            "next_page_token": "N58N4bhqiBFapzndJocx6cc8NKr2",
            "page_size": 7,
            "total_records": 60
        },
        "Metadata": {
            "Size": 7,
            "Total": 60
        }
    }
}

Human Readable Output

Meeting list

uuid id host_id topic type start time duration timezone created_at join_url
z93Dc6KjSo20Wr3jx+zaYg== 83810397585 uJiZN-O7Rp6Jp_995FpZGg 2 8   60 Asia/Jerusalem 2022-12-04T10:41:27Z https://us06web.zoom.us/j/83810397585?pwd=OGFiZjRYNGhwWkVvMWZzdDBoaXlLdz09
4kZQ+Hl2RFOAz0VBPHoCYg== 84540166459 uJiZN-O7Rp6Jp_995FpZGg My recurring Meeting -Dima 8   60 Asia/Jerusalem 2022-12-06T07:59:02Z https://us06web.zoom.us/j/84540166459?pwd=ZzdmUEJ5QkZaaUZaaVpFYlBVYnFoUT09
RABXyk81T02D4hA1rgpDgQ== 89824497327 uJiZN-O7Rp6Jp_995FpZGg efe 8   60 Asia/Jerusalem 2022-12-27T21:05:41Z https://us06web.zoom.us/j/89824497327?pwd=SWpvK0I0L3pQcTNnWlF3b3VDRVhmQT09
qOXvlLOoSamJjjFaFgniTA== 83245658341 uJiZN-O7Rp6Jp_995FpZGg efe 8   60 Asia/Jerusalem 2022-12-27T21:11:01Z https://us06web.zoom.us/j/83245658341?pwd=SmNGMHI0R1hndnNlRXRDNkZ2Yms3Zz09
yHzCvl4USry+C9LnCwdnwQ== 88468901206 uJiZN-O7Rp6Jp_995FpZGg efe 8   60 Asia/Jerusalem 2022-12-27T21:25:54Z https://us06web.zoom.us/j/88468901206?pwd=RXZYQlhVbWJKZ1pLWCtpS3l1MHUxZz09
Sblh/I34S1W+rTKUcojJjw== 87525048161 uJiZN-O7Rp6Jp_995FpZGg efe 8   60 Asia/Jerusalem 2022-12-28T06:39:48Z https://us06web.zoom.us/j/87525048161?pwd=ZXlXdXl0QWlLOFVyV3U0OUxxSlBkdz09
iLXDe4HsR6uMb+x8GyybTA== 83877839723 uJiZN-O7Rp6Jp_995FpZGg efe 8   60 Asia/Jerusalem 2022-12-28T06:42:25Z https://us06web.zoom.us/j/83877839723?pwd=WU9xNmp5RW5KRDhsZ1RySkY4Zkc4Zz09

Metadata

total_records
60

Command example

Human Readable Output

Channels

Channel ID Channel JID Channel name Channel type
f7ce414a24634240ae0a7894823d8707 f7ce414a24634240ae0a7894823d8707@conference.xmpp.zoom.us Channel1 3
e82e99ac9b0c40b08449c76766cf398d e82e99ac9b0c40b08449c76766cf398d@conference.xmpp.zoom.us Channel2 3
e7e2c5e1ba854056906af1c9dc36148f e7e2c5e1ba854056906af1c9dc36148f@conference.xmpp.zoom.us channel3 3
e6388c9667c245c3844009ae995664d5 e6388c9667c245c3844009ae995664d5@conference.xmpp.zoom.us Channel4 3

zoom-list-user-channels


List the existing channels

Base Command

zoom-list-user-channels

Input

Argument Name Description Required
page-size Number of channels to return. Max 300. The default is 50. Optional
next_page_token The next page token is used to get the next page. IMPORTANT: You must pass the same page size that you passed in the first call. Optional
page-number Which page of results to return. The default = 1. Note: This argument is in a deprecate process by the API. As an alternative use “next_page_token” or “limit”. Optional
limit The total amunt of results to show. Optional
channel_id A channel ID. This is for a single channel. Optional
user_id A user ID. This is for a single user. Required

Context Output

Path Type Description
Zoom.Channel.id string The ID of the created channel.
Zoom.Channel.name string The name of the created channel.
Zoom.Channel.type string The created channel type.
Zoom.Channel.url string The created channel URL.

Command example

!zoom-list-user-channels user_id=uJiZN-O7Rp6Jp_995FpZGg channel_id=88f2f20f4abd415cba2429538be58025

Context Example

{
    "Zoom": {
        "Channel": {
            "channel_settings": {
                "add_member_permissions": 1,
                "allow_to_add_external_users": 0,
                "mention_all_permissions": 1,
                "new_members_can_see_previous_messages_files": true,
                "posting_permissions": 1
            },
            "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI4OGYyZjIwZjRhYmQ0MTVjYmEyNDI5NTM4YmU1ODAyNUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
            "id": "88f2f20f4abd415cba2429538be58025",
            "jid": "88f2f20f4abd415cba2429538be58025@conference.xmpp.zoom.us",
            "name": "Channel-Updated5",
            "type": 3
        },
        "UserChannelsNextToken": null
    }
}

Human Readable Output

Channels

Channel ID Channel name Channel type Channel url User id channel JID
88f2f20f4abd415cba2429538be58025 Channel-Updated5 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI4OGYyZjIwZjRhYmQ0MTVjYmEyNDI5NTM4YmU1ODAyNUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 88f2f20f4abd415cba2429538be58025@conference.xmpp.zoom.us

Command example

!zoom-list-user-channels user_id=uJiZN-O7Rp6Jp_995FpZGg

Context Example

{
    "Zoom": {
        "Channel": {
            "channels": [
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": false,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJlN2UyYzVlMWJhODU0MDU2OTA2YWYxYzlkYzM2MTQ4ZkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "e7e2c5e1ba854056906af1c9dc36148f",
                    "jid": "e7e2c5e1ba854056906af1c9dc36148f@conference.xmpp.zoom.us",
                    "name": "TestChannel1",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI3ODI4YTFiMjgzYmQ0MjYxOTkwOTRlNWVhMGFhOTM1M0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "7828a1b283bd426199094e5ea0aa9353",
                    "jid": "7828a1b283bd426199094e5ea0aa9353@conference.xmpp.zoom.us",
                    "name": "Channel",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIwOGE0OWVmY2I5YTA0N2MzYmU5NWMwNDc4NzI1NjJjZkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "08a49efcb9a047c3be95c047872562cf",
                    "jid": "08a49efcb9a047c3be95c047872562cf@conference.xmpp.zoom.us",
                    "name": "DemoChannel-Updated",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": false,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI5MmIwMjY4YWFmOGQ0MDM5OTQ4YjE3ZDc4M2IwODE5OEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "92b0268aaf8d4039948b17d783b08198",
                    "jid": "92b0268aaf8d4039948b17d783b08198@conference.xmpp.zoom.us",
                    "name": "TestGroupChannel1",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIzZDRjZjFiNDkzNDk0NGFkODM5YzEwOWJhYWM5MzEyNkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "3d4cf1b4934944ad839c109baac93126",
                    "jid": "3d4cf1b4934944ad839c109baac93126@conference.xmpp.zoom.us",
                    "name": "Admins1",
                    "type": 2
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJlODJlOTlhYzliMGM0MGIwODQ0OWM3Njc2NmNmMzk4ZEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "e82e99ac9b0c40b08449c76766cf398d",
                    "jid": "e82e99ac9b0c40b08449c76766cf398d@conference.xmpp.zoom.us",
                    "name": "Admins_to_delete_2",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIwZWJlNzEwMDNlYjI0ZjVmODU3N2U1YTEyNGYwNjQyOEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "0ebe71003eb24f5f8577e5a124f06428",
                    "jid": "0ebe71003eb24f5f8577e5a124f06428@conference.xmpp.zoom.us",
                    "name": "Admins_to_delete_2_updated",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 1,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIyZTk5YTdlMDdkZTA0ZjUyYmYyYzQ0OGNkMmIxNTFmY0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "2e99a7e07de04f52bf2c448cd2b151fc",
                    "jid": "2e99a7e07de04f52bf2c448cd2b151fc@conference.xmpp.zoom.us",
                    "name": "BotChannel",
                    "type": 5
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJiMThkMTUxNDBjMjU0NTQ5YTgwYWY2NTcyYjNiNTUyZUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "b18d15140c254549a80af6572b3b552e",
                    "jid": "b18d15140c254549a80af6572b3b552e@conference.xmpp.zoom.us",
                    "name": "ChannelName",
                    "type": 3
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIzNjQxYzNmZGMxZGM0Zjc4OGU4ZWIzNDA3YTQwYzQ3MkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "3641c3fdc1dc4f788e8eb3407a40c472",
                    "jid": "3641c3fdc1dc4f788e8eb3407a40c472@conference.xmpp.zoom.us",
                    "name": "Admins",
                    "type": 2
                },
                {
                    "channel_settings": {
                        "add_member_permissions": 1,
                        "allow_to_add_external_users": 0,
                        "mention_all_permissions": 1,
                        "new_members_can_see_previous_messages_files": true,
                        "posting_permissions": 1
                    },
                    "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJmN2NlNDE0YTI0NjM0MjQwYWUwYTc4OTQ4MjNkODcwN0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
                    "id": "f7ce414a24634240ae0a7894823d8707",
                    "jid": "f7ce414a24634240ae0a7894823d8707@conference.xmpp.zoom.us",
                    "name": "TestChannel3",
                    "type": 3
                }
            ],
            "next_page_token": "",
            "page_size": 50,
            "total_records": 11
        },
        "UserChannelsNextToken": ""
    }
}

Human Readable Output

Channels

Channel ID Channel name Channel type Channel url User id channel JID
e7e2c5e1ba854056906af1c9dc36148f TestChannel1 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJlN2UyYzVlMWJhODU0MDU2OTA2YWYxYzlkYzM2MTQ4ZkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg e7e2c5e1ba854056906af1c9dc36148f@conference.xmpp.zoom.us
7828a1b283bd426199094e5ea0aa9353 Channel 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI3ODI4YTFiMjgzYmQ0MjYxOTkwOTRlNWVhMGFhOTM1M0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 7828a1b283bd426199094e5ea0aa9353@conference.xmpp.zoom.us
08a49efcb9a047c3be95c047872562cf DemoChannel-Updated 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIwOGE0OWVmY2I5YTA0N2MzYmU5NWMwNDc4NzI1NjJjZkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 08a49efcb9a047c3be95c047872562cf@conference.xmpp.zoom.us
92b0268aaf8d4039948b17d783b08198 TestGroupChannel1 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiI5MmIwMjY4YWFmOGQ0MDM5OTQ4YjE3ZDc4M2IwODE5OEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 92b0268aaf8d4039948b17d783b08198@conference.xmpp.zoom.us
3d4cf1b4934944ad839c109baac93126 Admins1 2 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIzZDRjZjFiNDkzNDk0NGFkODM5YzEwOWJhYWM5MzEyNkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 3d4cf1b4934944ad839c109baac93126@conference.xmpp.zoom.us
e82e99ac9b0c40b08449c76766cf398d Admins_to_delete_2 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJlODJlOTlhYzliMGM0MGIwODQ0OWM3Njc2NmNmMzk4ZEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg e82e99ac9b0c40b08449c76766cf398d@conference.xmpp.zoom.us
0ebe71003eb24f5f8577e5a124f06428 Admins_to_delete_2_updated 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIwZWJlNzEwMDNlYjI0ZjVmODU3N2U1YTEyNGYwNjQyOEBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 0ebe71003eb24f5f8577e5a124f06428@conference.xmpp.zoom.us
2e99a7e07de04f52bf2c448cd2b151fc BotChannel 5 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIyZTk5YTdlMDdkZTA0ZjUyYmYyYzQ0OGNkMmIxNTFmY0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 2e99a7e07de04f52bf2c448cd2b151fc@conference.xmpp.zoom.us
b18d15140c254549a80af6572b3b552e ChannelName 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJiMThkMTUxNDBjMjU0NTQ5YTgwYWY2NTcyYjNiNTUyZUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg b18d15140c254549a80af6572b3b552e@conference.xmpp.zoom.us
3641c3fdc1dc4f788e8eb3407a40c472 Admins 2 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiIzNjQxYzNmZGMxZGM0Zjc4OGU4ZWIzNDA3YTQwYzQ3MkBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg 3641c3fdc1dc4f788e8eb3407a40c472@conference.xmpp.zoom.us
f7ce414a24634240ae0a7894823d8707 TestChannel3 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJmN2NlNDE0YTI0NjM0MjQwYWUwYTc4OTQ4MjNkODcwN0Bjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg f7ce414a24634240ae0a7894823d8707@conference.xmpp.zoom.us

zoom-create-channel


Creates a channel for a user

Base Command

zoom-create-channel

Input

Argument Name Description Required
user_id The user’s unique identifier. Required
member_emails The member(s) to include in the channel. A maximum of 20 members can be added to the channel at once with this API. Required
add_member_permissions Who can add new channel members: 1 - All channel members can add new members. 2 - Only channel owner and administrators can add new members. Note: This setting can only be modified by the channel owner. Default: 1.  
posting_permissions The channel members’ posting permissions: 1 — All chat channel members can post to the channel. 2 — Only the channel owner and administrators can post to the channel. * 3 — Only the channel owner, administrators and certain members can post to the channel. Default: 1. Optional
new_members_can_see_prev_msgs Whether new channel members can view messages and files previously posted in the channel. Possible values are: true, false. Default is True. Optional
channel_name The name of the channel. Maximum of 128 characters. Required
channel_type The type of the channel. The value can be one of the following: 1: Private channel. In this type of channel, members must be invited to join a channel. 2: Private channel with members that belong to one Zoom account. Members in this channel should be invited and the members should be from the same organization. 3: Public channel. Anyone can search for this channel and join the channel. 4: New chat. This is an instant channel which can be created by adding members to a new chat. Required

Context Output

Path Type Description
Zoom.Channel.id string the channel’s unique identifier.
Zoom.Channel.jid string The channel JID.
Zoom.Channel.name string The channel name.
Zoom.Channel.type string The type of the channel. The value can be one of the following: 1: Private channel. In this type of channel, members must be invited to join a channel. 2: Private channel with members that belong to one Zoom account. Members in this channel should be invited and the members should be from the same organization. 3: Public channel. Anyone can search for this channel and join the channel. 4: Group chat. This is an instant channel which can be created by adding members to a new chat.
     
Zoom.Channel.url string The URL of the Zoom chat channel.

Command example

!zoom-create-channel user_id=uJiZN-O7Rp6Jp_995FpZGg member_emails=example@example.com channel_name=ChannelName channel_type="Public channel"

Context Example

{
    "Zoom": {
        "Channel": {
            "channel_url": "https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJiMThkMTUxNDBjMjU0NTQ5YTgwYWY2NTcyYjNiNTUyZUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9",
            "id": "b18d15140c254549a80af6572b3b552e",
            "jid": "b18d15140c254549a80af6572b3b552e@conference.xmpp.zoom.us",
            "name": "ChannelName",
            "type": 3
        }
    }
}

Human Readable Output

Channel details

Channel ID Channel name Channel type Channel url User id
b18d15140c254549a80af6572b3b552e ChannelName 3 https://us06web.zoom.us/launch/chat/v2/eyJzaWQiOiJiMThkMTUxNDBjMjU0NTQ5YTgwYWY2NTcyYjNiNTUyZUBjb25mZXJlbmNlLnhtcHAuem9vbS51cyJ9 uJiZN-O7Rp6Jp_995FpZGg

zoom-delete-channel


Deletes a specific Zoom channel

Base Command

zoom-delete-channel

Input

Argument Name Description Required
channel_id Unique identifier of a channel. Required
user_id User unique identifier. Required

Context Output

There is no context output for this command.

Command example

!zoom-delete-channel user_id=uJiZN-O7Rp6Jp_995FpZGg channel_id=88f2f20f4abd415cba2429538be58025

Human Readable Output

Channel 88f2f20f4abd415cba2429538be58025 was deleted successfully

zoom-update-channel


Update an existing channel in a Zoom account.

Base Command

zoom-update-channel

Input

Argument Name Description Required
channel_id The channel ID. Required
user_id User unique identifier. Required
add_member_permissions Who can add new channel members: 1 - All channel members can add new members. 2 - Only channel owner and administrators can add new members. Note: This setting can only be modified by the channel owner. Optional
posting_permissions The channel members’ posting permissions: 1 — All chat channel members can post to the channel. 2 — Only the channel owner and administrators can post to the channel. * 3 — Only the channel owner, administrators and certain members can post to the channel. Optional
new_members_can_see_prev_msgs Whether new channel members can view messages and files previously posted in the channel. Possible values are: true, false. Default is True. Optional
channel_name A new name for the channel. Optional

Context Output

There is no context output for this command.

Command example

!zoom-update-channel user_id=uJiZN-O7Rp6Jp_995FpZGg channel_name=Channel-Updated5 channel_id=88f2f20f4abd415cba2429538be58025

Human Readable Output

Channel 88f2f20f4abd415cba2429538be58025 was updated successfully

zoom-invite-to-channel


Invites members that are in a user’s contact list to a channel. A channel can have one or multiple members. The maximum number of members that can be added at once with this API is 5.

Base Command

zoom-invite-to-channel

Input

Argument Name Description Required
channel_id The channel’s unique identifier. Required
user_id User unique identifier. Required
members The member’s email address. Required

Context Output

Path Type Description
Zoom.Channel.ids string A comma-separated list of user IDs of the members added to the channel. User IDs of those who are not from the same account will be omitted from the list.
     
Zoom.Channel.added_at string The date and time when the members are added to the channel.
Zoom.Channel.member_ids string A comma-separated list of member IDs of the members added to the channel.

Command example

!zoom-invite-to-channel user_id=uJiZN-O7Rp6Jp_995FpZGg members=example@example.com channel_id=88f2f20f4abd415cba2429538be58025

Context Example

{
    "Zoom": {
        "Channel": {
            "added_at": "2023-05-22T08:36:57Z",
            "ids": "pq0t1uvlrqmi_p_-cg2ugq",
            "member_ids": "1vqMC-BQdfok1tpAnSw8OT6FtcMiXBaUPNoRhTIzcvJjtbxgPXQRLmDm-10C2Ggg"
        }
    }
}

Human Readable Output

Channel details

Added at date and time Channel ID User id
2023-05-22T08:36:57Z 88f2f20f4abd415cba2429538be58025 pq0t1uvlrqmi_p_-cg2ugq

zoom-remove-from-channel


Removes a member from a chat channel. A channel can have one or more members.

Base Command

zoom-remove-from-channel

Input

Argument Name Description Required
channel_id The unique identifier of the channel from where you would like to remove a member. Required
user_id User unique identifier. Required
member_id The email address or user ID or member ID of the member to remove from the channel. Required

Context Output

There is no context output for this command.

Command example

!zoom-remove-from-channel user_id=uJiZN-O7Rp6Jp_995FpZGg member_id=example@example.com channel_id=88f2f20f4abd415cba2429538be58025

Human Readable Output

Member example@example.com was successfully removed from channel 88f2f20f4abd415cba2429538be58025

zoom-send-file


Sends a file on Zoom to either an individual user in your contact list or a channel of which you are a member.

Base Command

zoom-send-file

Input

Argument Name Description Required
to_channel The channel ID of the channel to which to send the chat file. Optional
user_id The user’s ID. Required
to_contact The user ID or member ID of the contact to send the chat file. Optional
entry_id Cortex XSOAR internal file ID. Required

Context Output

There is no context output for this command.

zoom-send-message


Sends chat messages on Zoom to either an individual user who is in your contact list or to a channel of which you are a member.

Base Command

zoom-send-message

Input

Argument Name Description Required
user_id Unique identifier of the user. Required
at_contact Email address of the mention contact. Optional
is_markdown if a markdown message provide in the message argument Optional
message The message to be sent. Maximum of 1024 characters. Required
entry_ids A list of the file IDs to send. This field only accepts a maximum of six file IDs. Optional
reply_main_message_id The reply message’s ID. This field only returns if the message is a reply message. Optional
to_channel The channel ID of the channel to send a message. Optional
to_contact The email address or user ID or member ID of the person to send a message. Optional

Context Output

Path Type Description
Zoom.ChatMessage.id number Unique identifier of the message.

Command example

!zoom-send-message user_id=uJiZN-O7Rp6Jp_995FpZGg message="message" to_contact=example@example.com

Context Example

{
    "Zoom": {
        "ChatMessage": {
            "id": "7df8cc5b-efc4-4e08-9083-9823b8871c82"
        }
    }
}

Human Readable Output

Message

Contact Message ID
example@example.com 7df8cc5b-efc4-4e08-9083-9823b8871c82

zoom-delete-message


Deletes a chat message previously sent to a contact or a channel.

Base Command

zoom-delete-message

Input

Argument Name Description Required
user_id Unique identifier of the user. Required
message_id Unique identifier of the message. Required
to_channel The channel ID where the message was sent. You must provide this parameter or the to_contact parameter. Optional
to_contact The member ID or user ID or email address of a chat contact to whom the message was sent. You must provide this parameter or the to_channel parameter. Optional

Context Output

There is no context output for this command.

Command example

!zoom-delete-message user_id=uJiZN-O7Rp6Jp_995FpZGg to_contact=example@example.com message_id=4a59df4a-9668-46bd-bff2-3e1f3462ecc3

Human Readable Output

Message 4a59df4a-9668-46bd-bff2-3e1f3462ecc3 was deleted successfully

zoom-update-message


Edits a chat message that you previously sent to either a contact or a channel in Zoom by providing the ID of the message as the value of the messageId parameter. As a query parameter, you must provide either the contact’s email address or the Channel ID of the channel where the message was sent.

Base Command

zoom-update-message

Input

Argument Name Description Required
user_id Unique identifier of the user. Required
message_id Unique identifier of the message. Required
message The edited message. Required
to_channel The channel ID of the channel where you sent the message. You must provide either to_contact or to_channel parameter in the API request. Channel ID can be retrieved from List User’s Channels API. Optional
to_contact The email address or user ID or member ID of the contact to whom the message was sent. You must provide either this parameter or the to_channel parameter in the API request. Optional
entry_ids A list of Cortex XSOAR file entry IDs to send. Optional

Context Output

There is no context output for this command.

Command example

!zoom-update-message user_id=uJiZN-O7Rp6Jp_995FpZGg message="message2" to_contact=example@example.com message_id=4a59df4a-9668-46bd-bff2-3e1f3462ecc3

Human Readable Output

Message 4a59df4a-9668-46bd-bff2-3e1f3462ecc3 was successfully updated

zoom-list-messages


Searches chat messages or shared files between a user and an individual contact or a chat channel.

Base Command

zoom-list-messages

Input

Argument Name Description Required
user_id Unique identifier of the user. Required
to_contact This field allows you to query by the email address or user ID or member ID of a chat contact with whom the user communicated. The API only returns messages sent and/or received between the user and the queried contact. Optional
to_channel This field queries by the channel ID of a channel in which the user had chat conversations. The API only returns messages sent and/or received by the user in the queried channel. Optional
date The query date from which to retrieve the chat messages. This value defaults to the current date. Format %Y-%m-%dT%H:%M:%S.. Optional
from Start date. Format %Y-%m-%dT%H:%M:%S. Optional
to End date. Format %Y-%m-%dT%H:%M:%S. Optional
include_deleted_and_edited_message Whether to include deleted and edited messages. Default false. Optional
search_type The type of search: message — Search messages. file — Search files.. Possible values are: message, file. Optional
search_key Key for search in messages. Optional
exclude_child_message Whether to exclude returning all child messages in a chat, leaving only the parent messages. Default: false. Optional
next_page_token The next page token paginates through a large set of results. A next page token is returned whenever the set of available results exceeds the current page size. This token’s expiration period is 15 minutes. Optional
page_size Number of records returned from a single API call. Default 50. Optional
page_number The page of results to return. Default 1. Optional
limit Total amount of results to show. Optional

Context Output

Path Type Description
Zoom.ChatMessage.date date The queried date value.
Zoom.ChatMessage.id string The channel’s unique identifier.
Zoom.Channel.jid string The channel JID.
Zoom.Channel.name string The channel name.
Zoom.Channel.type string The type of the channel. The value can be one of the following: 1: Private channel. In this type of channel, members must be invited to join a channel. 2: Private channel with members that belong to one Zoom account. Members in this channel should be invited and the members should be from the same organization. 3: Public channel. Anyone can search for this channel and join the channel. 4: Group chat. This is an instant channel which can be created by adding members to a new chat.
     
Zoom.ChatMessage.date date The queried date value.
Zoom.ChatMessage.from date-time The queried from value. (Returned only if the from query parameter is used)
Zoom.ChatMessage.messages array of object Information about received messages and files.
Zoom.ChatMessage.messages.bot_message object The chatbot’s message in JSON format.
Zoom.ChatMessage.messages.bot_message.date_time date-time The date and time when the message was sent.
Zoom.ChatMessage.messages.files array of object Information about chat files (if multiple files exist).
Zoom.ChatMessage.messages.files.download_url string The download URL of the chat file.
Zoom.ChatMessage.messages.files.file_id string The unique ID of the chat file.
Zoom.ChatMessage.messages.files.file_name string The name and file extension of the chat file.
Zoom.ChatMessage.messages.files.file_size integer The size of the chat file in bytes.
Zoom.ChatMessage.messages.rich_text array of object Rich text formatting information.
Zoom.ChatMessage.messages.rich_text.start_position integer The start position of the rich text in the message string.
Zoom.ChatMessage.messages.rich_text.end_position integer The end position of the rich text.
Zoom.ChatMessage.messages.rich_text.format_type enum The type of rich text format.
Zoom.ChatMessage.messages.rich_text.format_attr string The attribute values of a specific format_type.
Zoom.ChatMessage.messages.rich_text.download_url string The download URL of the first file.
Zoom.ChatMessage.messages.rich_text.file_id string The unique ID of the first file.
Zoom.ChatMessage.messages.rich_text.file_name string The name of the first file.
Zoom.ChatMessage.messages.rich_text.file_size integer The size of the first file in bytes.
Zoom.ChatMessage.messages.id string Unique identifier of the message.
Zoom.ChatMessage.messages.message string The message content.
Zoom.ChatMessage.messages.reply_main_message_id string The unique identifier of a reply message. (Returned only for reply messages)
Zoom.ChatMessage.messages.reply_main_message_timestamp integer The timestamp of when the reply message was sent. (Returned only for reply messages)
Zoom.ChatMessage.messages.sender string The email address of the message sender. Empty if the sender does not belong to the same account as the current user or is not a contact.
Zoom.ChatMessage.messages.sender_member_id string The member ID of the message sender.
Zoom.ChatMessage.messages.sender_display_name string The display name of the message sender.
Zoom.ChatMessage.messages.status enum Indicates the status of the message. Allowed values Deleted, Edited, Normal. (Returned only when include_deleted_and_edited_message query parameter is set to true)
Zoom.ChatMessage.messages.timestamp integer The timestamp of the message in microseconds.
Zoom.ChatMessage.at_items array of object Information about mentions.
Zoom.ChatMessage.at_items.at_contact string The user ID of the mentioned contact. Empty if the contact does not belong to the same account as the current user.
Zoom.ChatMessage.at_items.at_contact_member_id string The member ID of the mentioned contact.
Zoom.ChatMessage.at_items.at_type integer The type of mention. Allowed values 1 (Contact), 2 (Channel).
Zoom.ChatMessage.at_items.end_position integer The end position of the mention.
Zoom.ChatMessage.at_items.start_position integer The start position of the mention.
Zoom.ChatMessageNextToken.user_id string The user ID associated with the next page token.
Zoom.ChatMessageNextToken.to_channel string The channel ID associated with the next page token.
Zoom.ChatMessageNextToken.include_deleted_and_edited_message boolean Indicates whether deleted and edited messages are included in the next page token request.
Zoom.ChatMessageNextToken.search_type string The search type associated with the next page token.
Zoom.ChatMessageNextToken.to string The recipient associated with the next page token.
Zoom.ChatMessageNextToken.date string The date associated with the next page token.
Zoom.ChatMessageNextToken.page_size integer The page size associated with the next page token.
Zoom.ChatMessageNextToken.next_page_token string The next page token for pagination.
Zoom.ChatMessageNextToken.from string The sender associated with the next page token.
Zoom.ChatMessageNextToken.search_key string The search key associated with the next page token.
Zoom.ChatMessageNextToken.exclude_child_message boolean Indicates whether child messages are excluded in the next page token request.
Zoom.ChatMessageNextToken.to_contact string The contact ID associated with the next page token.

Command example

!zoom-list-messages user_id=uJiZN-O7Rp6Jp_995FpZGg to_contact=example@example.com date=today

Context Example

{
    "Zoom": {
        "ChatMessage": {
            "ChatMessage": [
                {
                    "date_time": "2023-05-22T08:24:14Z",
                    "id": "a62636c8-b6c1-4135-9352-88ac61eafc31",
                    "message": "message",
                    "send_member_id": "1vrdvnC1gHuxZtpVLR7Oz1_A1dcCrBf0PNoRhTIzcvJjtbxgPXQRLmDm-10C2Ggg",
                    "sender": "example@example.com",
                    "sender_display_name": "admin zoom",
                    "timestamp": 1684743854543
                },
                {
                    "date_time": "2023-05-22T08:20:22Z",
                    "id": "4a59df4a-9668-46bd-bff2-3e1f3462ecc3",
                    "message": "my message",
                    "send_member_id": "1vrdvnC1gHuxZtpVLR7Oz1_A1dcCrBf0PNoRhTIzcvJjtbxgPXQRLmDm-10C2Ggg",
                    "sender": "example@example.com",
                    "sender_display_name": "admin zoom",
                    "timestamp": 1684743622899
                }
            ],
            "ChatMessageNextToken": ""
        }
    }
}

Human Readable Output

Messages

Date Time From Message Id Message sender Message text Sender display name To User id
2023-05-22T08:24:14Z None a62636c8-b6c1-4135-9352-88ac61eafc31 example@example.com message admin zoom None uJiZN-O7Rp6Jp_995FpZGg
2023-05-22T08:20:22Z None 4a59df4a-9668-46bd-bff2-3e1f3462ecc3 example@example.com my message admin zoom None uJiZN-O7Rp6Jp_995FpZGg

zoom-delete-user-token


Revoke a user’s Zoom SSO session.
To list all available users use the zoom-list-users command.

Base Command

zoom-delete-user-token

Input

Argument Name Description Required
user_id The user ID or email to be revoked. Required

Command example

!zoom-delete-user-token user_id=useremail@myself.com

Context Output

There is no context output for this command.

Human Readable Output

Message

User SSO token for user useremail@myself.com is deleted

send-notification


Sends messages from your Marketplace Chatbot app on Zoom to either an individual user or to a channel.

Base Command

send-notification

Input

Argument Name Description Required
to The email address or user ID or member ID of the person to send a message. Required
channel_id The channel ID of the channel to send a message. Optional
message The message to be sent. Maximum of 1024 characters. Required
visible_to_user The UserID that allows a Chatbot to send a message to a group channel when it has only one designated person in that group channel to see the message. Optional

Context Output

There is no context output for this command.

Command example

!send-notification message=hi to=example@example.com

Context Output

There is no context output for this command.

Human Readable Output

Message

Message sent to Zoom successfully. Message ID is: 20230815153245201_BPK3S3S_aw1

mirror-investigation


Mirrors the investigation between Zoom and the Cortex XSOAR War Room.

Base Command

mirror-investigation

Input

Argument Name Description Required
type The mirroring type. Can be “all”, which mirrors everything, “chat”, which mirrors only chats (not commands), or “none”, which stops all mirroring. Possible values are: all, chat, none. Default is all. Optional
autoclose Whether the channel is auto-closed when an investigation is closed. Can be “true” or “false”. Default is “true”. Optional
direction The mirroring direction. Can be “FromDemisto”, “ToDemisto”, or “Both”. Default value is “Both”. Optional
channelName The name of the channel. The default is “incident-<incidentID>”. Optional

Context Output

There is no context output for this command.

Command Example

!mirror-investigation direction="FromDemisto" channelName="example"

Human Readable Output

Investigation mirrored successfully, channel:example

close-channel


Delete a mirrored Zoom channel.

Base Command

close-channel

Input

Argument Name Description Required
channel The name of the channel to delete. If not provided, the mirrored investigation channel is deleted (if the channel exists). Optional
channel_id The ID of the channel to delete. If not provided, the mirrored investigation channel is deleted (if the channel exists). Optional

Context Output

There is no context output for this command.

Command Example

!close-channel channel=new-zoom-channel

Human Readable Output

Channel successfully deleted.

Configuration parameters

  • url — Server URL (e.g. 'https://api.zoom.us/v2/') (required)
  • account_id — Account ID (OAuth) (required)
  • credentials — Client ID (OAuth) (required)
  • botJID — Bot JID
  • bot_credentials — Bot Client ID ((Team Chat App))
  • secret_token
  • verification_token
  • longRunning — Long running instance
  • longRunningPort — Listen Port
  • mirroring — Enable Incident Mirroring
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • certificate — Certificate (Required for HTTPS)
  • key — Private Key (Required for HTTPS)

Commands (23)

  • close-channel

    Delete a mirrored Zoom channel.

  • mirror-investigation

    Mirrors the investigation between Zoom and the Cortex XSOAR War Room.

  • send-notification

    Send a message using a chatbot app.

  • zoom-create-channel

    Creates a channel for a user. Use the existing command zoom-list-users to get the user ID by email (only if the user has an email address as part of the user parameters).

  • zoom-create-meeting

    Create a new Zoom meeting (scheduled, instant, or recurring).

  • zoom-create-user

    Create a new user in zoom account.

  • zoom-delete-channel

    Deletes a specific Zoom channel. Use the command zoom-list-account-public-channels in order to fetch the channel ID by the channel name.

  • zoom-delete-message

    Deletes a chat message previously sent to a contact or a channel. Use the command zoom-list-user-channels to get the channel ID by the channel name (for to_channel). See the existing command zoom-list-users to get the user ID by email (for to_contact). In order to update messages (or a single message using messageId), you need to provide to_contact (contact to send) or to_channel (channel to send to), but not both of them.

  • zoom-delete-user

    Delete a user from Zoom.

  • zoom-delete-user-token

    Revoke a user's Zoom SSO session. Use the zoom-list-users command to get the user ID or user email.

  • zoom-fetch-recording

    Get the meeting record and save it as a file in the War Room.

  • zoom-invite-to-channel

    Invites members that are in a user's contact list to a channel. A channel can have one or more members. The maximum number of members that can be added at once with this API is 5. Use the command zoom-list-account-public-channels in order to fetch the channel ID by the channel name.

  • zoom-list-account-public-channels

    List the existing channels You can only retrieve the channel information for the authorized user. In order to get a public channel by its name (for example Admins_to_delete_4) you need to run the command zoom-list-account-public-channels without the channel_id param and find which channel_id fits the channel name.

  • zoom-list-messages

    Searches chat messages or shared files between a user and an individual contact or a chat channel.

  • zoom-list-user-channels

    List the existing channels. In order get the user ID by user email - use the existing command zoom-list-users. In order to get the user channel by its name, run the command zoom-list-user-channels without the channel_id param and find which channel_id fits the channel name.

  • zoom-list-users

    List the existing users.

  • zoom-meeting-get

    Get the information of an existing Zoom meeting.

  • zoom-meeting-list

    Show all the meetings of a given user. Note: only scheduled and unexpired meetings will appear.

  • zoom-remove-from-channel

    Removes a member from a chat channel. A channel can have one or more members. Use the command zoom-list-account-public-channels in order to fetch the channel ID by the channel name. In order get the user ID by the user email - use the existing command zoom-list-users.

  • zoom-send-file

    Sends a file on Zoom to either an individual user in your contact list or a channel of which you are a member. Use existing command zoom-list-users to get the user ID by the user email (for to_contact). Use the command zoom-list-user-channels in order to fetch the channel ID by the channel name (for to_channel).

  • zoom-send-message

    Sends chat messages on Zoom to either an individual user who is in your contact list or to a channel of which you are a member. Use the command zoom-list-users to get the user id by the user email (for to_contact). Use the command zoom-list-user-channels in order to fetch the channel ID by the channel name (for to_channel). In order to send a message, you need to provide at least a message (message text) and to_contact (contact to send) or to_channel (channel to send to), but not both of them. In order to send a style message you need to provide is-markdown=true and the message can contain a markdown format. If you are using mention markdown in a message, provide the email address in the at_contact argument.

  • zoom-update-channel

    Update an existing channel in a Zoom account. Use the command zoom-list-account-public-channels in order to fetch the channel ID by the channel name. "Need to provide at least one of the following arguments: posting_permissions or channnel_name.".

  • zoom-update-message

    Edits a chat message that you previously sent to either a contact or a channel in Zoom by providing the ID of the message as the value of the messageId parameter. As a query parameter, you must provide either the contact's email address of the contact or the Channel ID of the channel where the message was sent. Use the command zoom-list-user-channels to get the channel ID by the channel name (for to_channel). Use the existing command zoom-list-users to get the user ID by email (for to_contact). In order to update messages (or a single message using messageId), you need to provide to_contact (contact to send) or to_channel (channel to send to), but not both of them.

import hashlib
import hmac
import shutil
from copy import copy
from datetime import datetime
from secrets import compare_digest
from tempfile import NamedTemporaryFile
from traceback import format_exc

import demistomock as demisto  # noqa: F401
import uvicorn
from CommonServerPython import *  # noqa: F401
from fastapi import Depends, FastAPI, Request, Response, status
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.security.api_key import APIKey, APIKeyHeader
from fastapi_utils.tasks import repeat_every
from uvicorn.logging import AccessFormatter
from ZoomApiModule import *

app = FastAPI()

basic_auth = HTTPBasic(auto_error=False)
token_auth = APIKeyHeader(auto_error=False, name="Authorization")

SYNC_CONTEXT = True
OBJECTS_TO_KEYS = {
    "messages": "entitlement",
}
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
PLAYGROUND_INVESTIGATION_TYPE = 9

# Note#1: type "Pro" is the old version, and "Licensed" is the new one, and i want to support both.
# Note#2: type "Corporate" is officially not supported any more, but i did not remove it just in case it still works.
USER_TYPE_MAPPING = {"Basic": 1, "Pro": 2, "Licensed": 2, "Corporate": 3}
MONTHLY_RECURRING_TYPE_MAPPING = {"Daily": 1, "Weekly": 2, "Monthly": 3}
INSTANT = "Instant"
SCHEDULED = "Scheduled"
RECURRING_WITH_TIME = "Recurring meeting with fixed time"

MEETING_TYPE_NUM_MAPPING = {"Instant": 1, "Scheduled": 2, "Recurring meeting with fixed time": 8}
FILE_TYPE_MAPPING = {"MP4": "Video", "M4A": "Audio"}
POSTING_PERMISSIONS_MAPPING = {
    "All members can post": 1,
    "Only the owner and admins can post": 2,
    "Only the owner, admins and certain members can post": 3,
}

MEMBER_PERMISSIONS_MAPPING = {"All channel members can add": 1, "Only channel owner and admins can add": 2}

CHANNEL_TYPE_MAPPING = {
    "Private channel": 1,
    "Private channel with members that belong to one account": 2,
    "Public channel": 3,
    "New chat": 4,
}
AT_TYPE = {"Mention a contact": 1, 'Mention "all" to notify everyone in the channel.': 2}

WRONG_TIME_FORMAT = "Wrong time format. Use this format: 'yyyy-MM-ddTHH:mm:ssZ' or 'yyyy-MM-ddTHH:mm:ss' "
LIMIT_AND_EXTRA_ARGUMENTS = """Too many arguments. If you choose a limit,
                                       don't enter a user_id or page_size or next_page_token or page_number."""
LIMIT_AND_EXTRA_ARGUMENTS_MEETING_LIST = """Too many arguments. If you choose a limit,
                                       don't enter a page_size or next_page_token or page_number."""
INSTANT_AND_TIME = "Too many arguments.Use start_time and timezone for scheduled meetings only."
JBH_TIME_AND_NO_JBH = """Collision arguments.
join_before_host_time argument can be used only if join_before_host is 'True'."""
WAITING_ROOM_AND_JBH = "Collision arguments. join_before_host argument can be used only if waiting_room is 'False'."
END_TIMES_AND_END_DATE_TIME = "Collision arguments. Choose only one of these two arguments, end_time or end_date_time."
NOT_RECURRING_WITH_RECURRING_ARGUMENTS = """One or more arguments that were filed
are used for a recurring meeting with a fixed time only."""
NOT_MONTHLY_AND_MONTHLY_ARGUMENTS = """One or more arguments that were
filed are for a recurring meeting with a fixed time and monthly recurrence_type only."""
MONTHLY_RECURRING_MISIING_ARGUMENTS = """Missing arguments. A recurring meeting with a fixed time and monthly recurrence_type
            must have the following arguments: monthly_week and monthly_week_day."""
NOT_WEEKLY_WITH_WEEKLY_ARGUMENTS = "Weekly_days is for weekly recurrence_type only."
EXTRA_PARAMS = """Too many fields were filled.
You should fill the Account ID, Client ID, and Client Secret fields (OAuth),
OR the API Key and API Secret fields (JWT - Deprecated)."""
RECURRING_MISSING_ARGUMENTS = """Missing arguments. A recurring meeting with a fixed
time is missing this argument: recurrence_type."""
MISSING_ARGUMENT = """Missing either a contact info or a channel id"""
USER_NOT_FOUND = """ This user email can't be found """
MARKDOWN_AND_EXTRA_ARGUMENTS = """Too many arguments. If you choose is_markdown,
                    don't provide one of the following arguments: start_position, end_position, format_type, at_type,
                    rt_start_position, rt_end_position or format_attr"""
MARKDOWN_EXTRA_FORMATS = """Too many style in text. you can provide only one style type"""
MARKDOWN_EXTRA_MENTIONS = """Too many mentions in text. you can provide only one mention in each message"""
WRONG_CHANNEL = """Couldn't find channel id base on provided channel_name. channel_name can use only for  mirrored channel.
Otherwise, please use the channel ID instead."""
MISSING_ARGUMENT_JID = """Missing argument: You must provide either a user ID or a channel ID.
If you're using a mirrored channel, you have the option to specify the channel name as well."""
TOO_MANY_JID = """Too many argument you must provide either a user JID or a channel id and not both """
BOT_PARAM_CHECK = """If you're using the Zoom chatbot, it's essential to provide all the necessary bot parameters,
including the botJID, bot_client_id, and bot_client_secret.
you can find these values in the Zoom Chatbot app configuration"""
OAUTH_PARAM_CHECK = """if you are using zoom APIs you must provide Oauth client_id and client_secret
you can find this values in the Zoom Oauth Server-To-Server app configuration"""
CLIENT: Zoom_Client
SECRET_TOKEN: str
MESSAGE_FOOTER = "\n**From Zoom**"
MIRRORING_ENABLED: bool = False
LONG_RUNNING: bool = False
MIRROR_TYPE = "mirrorEntry"
CACHED_INTEGRATION_CONTEXT: dict
CACHE_EXPIRY: float


"""CLIENT CLASS"""


class UserAgentFormatter(AccessFormatter):
    """This formatter extracts and includes the 'User-Agent' header information
    in the log messages."""

    def get_user_agent(self, scope: Dict) -> str:
        headers = scope.get("headers", [])
        user_agent_header = list(filter(lambda header: header[0].decode().lower() == "user-agent", headers))
        user_agent = ""
        if len(user_agent_header) == 1:
            user_agent = user_agent_header[0][1].decode()
        return user_agent

    def format_message(self, record):
        """Include the 'User-Agent' header information in the log message.
        Args:
            record: The log record to be formatted.
        Returns:
            str: The formatted log message."""
        record_copy = copy(record)
        scope = record_copy.__dict__["scope"]
        user_agent = self.get_user_agent(scope)
        record_copy.__dict__.update({"user_agent": user_agent})
        return super().formatMessage(record_copy)


class Client(Zoom_Client):
    """A client class that implements logic to authenticate with Zoom application."""

    def zoom_create_user(self, user_type_num: int, email: str, first_name: str, last_name: str):
        return self.error_handled_http_request(
            method="POST",
            url_suffix="users",
            headers={"authorization": f"Bearer {self.access_token}"},
            json_data={
                "action": "create",
                "user_info": {"email": email, "type": user_type_num, "first_name": first_name, "last_name": last_name},
            },
        )

    def zoom_list_users(
        self,
        page_size: int,
        status: str = "active",
        next_page_token: str = None,
        role_id: str = None,
        url_suffix: str = None,
        page_number: int = None,
    ):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=url_suffix,
            headers={"authorization": f"Bearer {self.access_token}"},
            params={
                "status": status,
                "page_size": page_size,
                "page_number": page_number,
                "next_page_token": next_page_token,
                "role_id": role_id,
            },
        )

    def zoom_delete_user(self, user: str, action: str):
        return self.error_handled_http_request(
            method="DELETE",
            url_suffix="users/" + user,
            headers={"authorization": f"Bearer {self.access_token}"},
            json_data={"action": action},
            resp_type="response",
            return_empty_response=True,
        )

    def zoom_create_meeting(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="POST", url_suffix=url_suffix, headers={"authorization": f"Bearer {self.access_token}"}, json_data=json_data
        )

    def zoom_meeting_get(self, meeting_id: str, occurrence_id: str | None = None, show_previous_occurrences: bool | str = False):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=f"/meetings/{meeting_id}",
            headers={"authorization": f"Bearer {self.access_token}"},
            params={"occurrence_id": occurrence_id, "show_previous_occurrences": show_previous_occurrences},
        )

    def zoom_meeting_list(
        self,
        user_id: str,
        next_page_token: str | None = None,
        page_size: int | str = 30,
        type: str | int | None = None,
        page_number: int = None,
    ):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=f"users/{user_id}/meetings",
            headers={"authorization": f"Bearer {self.access_token}"},
            params={"type": type, "next_page_token": next_page_token, "page_size": page_size, "page_number": page_number},
        )

    def zoom_fetch_recording(
        self, method: str, url_suffix: str = "", full_url: str = "", stream: bool = False, resp_type: str = "json"
    ):
        return self.error_handled_http_request(
            method=method,
            full_url=full_url,
            url_suffix=url_suffix,
            resp_type=resp_type,
            stream=stream,
            headers={"authorization": f"Bearer {self.access_token}"},
        )

    def zoom_list_channels(self, page_size: int, next_page_token: str = None, url_suffix: str = "", page_number: int = 1):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=f"chat/{url_suffix}",
            headers={"authorization": f"Bearer {self.access_token}"},
            params={"page_size": page_size, "page_number": page_number, "next_page_token": next_page_token},
        )

    def zoom_create_channel(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="POST", url_suffix=url_suffix, headers={"authorization": f"Bearer {self.access_token}"}, json_data=json_data
        )

    def zoom_update_channel(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="PATCH",
            url_suffix=url_suffix,
            headers={"authorization": f"Bearer {self.access_token}"},
            json_data=json_data,
            resp_type="response",
            return_empty_response=True,
        )

    def zoom_list_user_channels(
        self, user_id: str, page_size: int, next_page_token: str = None, url_suffix: str = "", page_number: int = None
    ):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=f"chat/{url_suffix}",
            headers={"authorization": f"Bearer {self.access_token}"},
            params={"user_id": user_id, "page_size": page_size, "page_number": page_number, "next_page_token": next_page_token},
        )

    def zoom_delete_channel(self, url_suffix: str):
        return self.error_handled_http_request(
            method="DELETE",
            url_suffix=url_suffix,
            headers={"authorization": f"Bearer {self.access_token}"},
            resp_type="response",
            return_empty_response=True,
        )

    def zoom_invite_to_channel(self, members_json: str, url: str = None):
        return self.error_handled_http_request(
            method="POST", url_suffix=url, headers={"authorization": f"Bearer {self.access_token}"}, json_data=members_json
        )

    def zoom_remove_from_channel(self, url_suffix: str = None):
        return self.error_handled_http_request(
            method="DELETE",
            url_suffix=url_suffix,
            headers={"authorization": f"Bearer {self.access_token}"},
            resp_type="response",
            return_empty_response=True,
        )

    def zoom_send_file(self, url_suffix: str, file_info: dict, json_data: dict):
        files = {"file": (file_info["name"], open(file_info["path"], "rb"))}

        response = self.error_handled_http_request(
            method="POST",
            full_url=url_suffix,
            headers={"Authorization": f"Bearer {self.access_token}"},
            files=files,
            data=json_data,
        )
        return response

    def zoom_upload_file(self, url_suffix: str, file_info: dict):
        files = {"file": (file_info["name"], open(file_info["path"], "rb"))}
        return self._http_request(
            "POST", headers={"Authorization": f"Bearer {self.access_token}"}, files=files, full_url=url_suffix
        )

    def zoom_send_message(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="POST", url_suffix=url_suffix, json_data=json_data, headers={"authorization": f"Bearer {self.access_token}"}
        )

    def zoom_delete_message(self, url_suffix: str = None):
        return self.error_handled_http_request(
            method="DELETE",
            url_suffix=url_suffix,
            headers={"authorization": f"Bearer {self.access_token}"},
            resp_type="response",
            return_empty_response=True,
        )

    def zoom_update_message(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="PUT",
            url_suffix=url_suffix,
            json_data=json_data,
            headers={"authorization": f"Bearer {self.access_token}"},
            return_empty_response=True,
        )

    def zoom_list_user_messages(
        self,
        user_id: str,
        date_arg: datetime,
        from_arg: datetime,
        to_arg: datetime,
        page_size: int,
        next_page_token: str = None,
        url_suffix: str = None,
        page_number: int = None,
        to_contact: str = None,
        to_channel: str = None,
        include_deleted_and_edited_message: bool = False,
        search_type: str = None,
        search_key: str = None,
        exclude_child_message: bool = False,
    ):
        return self.error_handled_http_request(
            method="GET",
            url_suffix=f"chat/{url_suffix}",
            headers={"authorization": f"Bearer {self.access_token}"},
            params={
                "user_id": user_id,
                "page_size": page_size,
                "page_number": page_number,
                "next_page_token": next_page_token,
                "to_contact": to_contact,
                "to_channel": to_channel,
                "date": date_arg,
                "from": from_arg,
                "to": to_arg,
                "include_deleted_and_edited_message": include_deleted_and_edited_message,
                "search_key": search_key,
                "search_type": search_type,
                "exclude_child_message": exclude_child_message,
            },
        )

    def zoom_send_notification(self, url_suffix: str, json_data: dict):
        return self.error_handled_http_request(
            method="POST",
            url_suffix=url_suffix,
            json_data=json_data,
            headers={"authorization": f"Bearer {self.bot_access_token}"},
        )

    def zoom_get_admin_user_id_from_token(self):
        return self.error_handled_http_request(
            method="get", url_suffix="users/me", headers={"authorization": f"Bearer {self.access_token}"}
        )

    def zoom_delete_user_token(self, url_suffix: str):
        return self.error_handled_http_request(
            method="DELETE", url_suffix=url_suffix, resp_type="response", headers={"authorization": f"Bearer {self.access_token}"}
        )


"""HELPER FUNCTIONS"""


def next_expiry_time() -> float:
    """
    Returns:
        A float representation of a new expiry time with an offset of 5 seconds
    """
    return (datetime.now(timezone.utc) + timedelta(seconds=5)).timestamp()


async def check_and_handle_entitlement(text: str, message_id: str, user_name: str, user_email: str) -> str:
    """
    Handles an entitlement message (a reply to a question)
    Args:
    Returns:
        If the message contains entitlement, return a reply.
    """
    integration_context = fetch_context(force_refresh=True)
    messages = integration_context.get("messages", [])
    reply = ""
    if not messages or not message_id:
        return reply
    messages = json.loads(messages)
    message_filter = list(filter(lambda q: q.get("message_id") == message_id, messages))
    if message_filter:
        message = message_filter[0]
        entitlement = message.get("entitlement")
        reply = message.get("reply", f"Thank you {user_name} for your response {text}.")
        guid, incident_id, task_id = extract_entitlement(entitlement)
        demisto.handleEntitlementForUser(incident_id, guid, user_email, text, task_id)
        message["remove"] = True
        set_to_integration_context_with_retries({"messages": messages}, OBJECTS_TO_KEYS, SYNC_CONTEXT)
    return reply


def save_entitlement(entitlement, message_id, reply, expiry, default_response, to_jid):
    """
    Saves an entitlement

    Args:
        entitlement: The entitlement
        message_id: The message_id
        reply: The reply to send to the user.
        expiry: The question expiration date.
        default_response: The response to send if the question times out.
        to_jid: the user jid the message was sent to
    """
    integration_context = get_integration_context(SYNC_CONTEXT)
    messages = integration_context.get("messages", [])
    if messages:
        messages = json.loads(integration_context["messages"])
    messages.append(
        {
            "message_id": message_id,
            "entitlement": entitlement,
            "reply": reply,
            "expiry": expiry,
            "sent": datetime.strftime(datetime.utcnow(), DATE_FORMAT),
            "default_response": default_response,
            "to_jid": to_jid,
        }
    )

    set_to_integration_context_with_retries({"messages": messages}, OBJECTS_TO_KEYS, SYNC_CONTEXT)


def extract_entitlement(entitlement: str) -> tuple[str, str, str]:
    """
    Extracts entitlement components from an entitlement string
    Args:
        entitlement: The entitlement itself
        text: The actual reply text

    Returns:
        Entitlement components
    """
    parts = entitlement.split("@")
    if len(parts) < 2:
        raise DemistoException("Entitlement cannot be parsed")
    guid = parts[0]
    id_and_task = parts[1].split("|")
    incident_id = id_and_task[0]
    task_id = ""

    if len(id_and_task) > 1:
        task_id = id_and_task[1]

    return guid, incident_id, task_id


@app.on_event("startup")
@repeat_every(seconds=60, wait_first=True)
async def check_for_unanswered_messages():
    # demisto.debug('check for unanswered messages')
    integration_context = fetch_context()
    messages = integration_context.get("messages")
    if messages:
        messages = json.loads(messages)
        now = datetime.utcnow()
        updated_messages = []

        for message in messages:
            if message.get("expiry"):
                # Check if the question expired - if it did, answer it with the default response
                # and remove it
                expiry = datetime.strptime(message["expiry"], DATE_FORMAT)
                if expiry < now:
                    demisto.debug(f"message expired: {message}")
                    _ = await answer_question(message.get("default_response"), message, email="")
                    updated_messages.append(message)
                    continue
            updated_messages.append(message)
        if updated_messages:
            set_to_integration_context_with_retries({"messages": messages}, OBJECTS_TO_KEYS, SYNC_CONTEXT)


def run_long_running(port: int, is_test: bool = False):
    while True:
        certificate = demisto.params().get("certificate", "")
        private_key = demisto.params().get("key", "")

        certificate_path = ""
        private_key_path = ""
        try:
            ssl_args = {}

            if certificate and private_key:
                certificate_file = NamedTemporaryFile(delete=False)
                certificate_path = certificate_file.name
                certificate_file.write(bytes(certificate, "utf-8"))
                certificate_file.close()
                ssl_args["ssl_certfile"] = certificate_path

                private_key_file = NamedTemporaryFile(delete=False)
                private_key_path = private_key_file.name
                private_key_file.write(bytes(private_key, "utf-8"))
                private_key_file.close()
                ssl_args["ssl_keyfile"] = private_key_path

                demisto.debug("Starting HTTPS Server")
            else:
                demisto.debug("Starting HTTP Server")

            integration_logger = IntegrationLogger()
            integration_logger.buffering = False
            log_config = dict(uvicorn.config.LOGGING_CONFIG)
            log_config["handlers"]["default"]["stream"] = integration_logger
            log_config["handlers"]["access"]["stream"] = integration_logger
            log_config["formatters"]["access"] = {
                "()": UserAgentFormatter,
                "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s "%(user_agent)s"',
            }
            uvicorn.run(app, host="0.0.0.0", port=port, log_config=log_config, **ssl_args)  # type: ignore[arg-type]
        except Exception as e:
            demisto.error(f"An error occurred in the long running loop: {e!s} - {format_exc()}")
            demisto.updateModuleHealth(f"An error occurred: {e!s}")
        finally:
            if certificate_path:
                os.unlink(certificate_path)
            if private_key_path:
                os.unlink(private_key_path)
            time.sleep(5)


def run_log_running(port: int, is_test: bool = False):
    while True:
        try:
            demisto.debug("Starting Server")
            integration_logger = IntegrationLogger()
            integration_logger.buffering = False
            log_config = dict(uvicorn.config.LOGGING_CONFIG)
            log_config["handlers"]["default"]["stream"] = integration_logger
            log_config["handlers"]["access"]["stream"] = integration_logger
            log_config["formatters"]["access"] = {
                "()": UserAgentFormatter,
                "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s "%(user_agent)s"',
            }
            uvicorn.run(app, host="0.0.0.0", port=port, log_config=log_config)
            if is_test:
                time.sleep(5)
                return "ok"
        except Exception as e:
            demisto.error(f"An error occurred in the long running loop: {e!s} - {format_exc()}")
            demisto.updateModuleHealth(f"An error occurred: {e!s}")
        finally:
            time.sleep(5)


async def zoom_send_notification_async(client, url_suffix, json_data_all):
    client.zoom_send_notification(url_suffix, json_data_all)


async def process_entitlement_reply(
    entitlement_reply: str,
    account_id: str,
    robot_jid: str,
    to_jid: str | None = None,
    user_name: str | None = None,
    action_text: str | None = None,
):
    """
    Triggered when an entitlement reply is found, this function will update the original message with the reply message.
    :param entitlement_reply: str: The text to update the asking question with.
    :param user_name: str: name of the user who answered the entitlement
    :param action_text: str: The text attached to the button, used for string replacement.
    :param toJid: str: The Jid of where the question exists.
    :param accountId: str: Zoom account ID
    :param robotJid: str: Zoom BOT JID
    :return: None
    """
    if "{user}" in entitlement_reply:
        entitlement_reply = entitlement_reply.replace("{user}", str(user_name))
    if "{response}" in entitlement_reply and action_text:
        entitlement_reply = entitlement_reply.replace("{response}", str(action_text))

    url_suffix = "/im/chat/messages"
    content_json = {
        "content": {"body": [{"type": "message", "text": entitlement_reply}]},
        "to_jid": to_jid,
        "robot_jid": robot_jid,
        "account_id": account_id,
    }
    await zoom_send_notification_async(CLIENT, url_suffix, content_json)


async def answer_question(text: str, question: dict, email: str = ""):
    entitlement = question.get("entitlement", "")
    to_jid = question.get("to_jid")
    guid, incident_id, task_id = extract_entitlement(entitlement)
    try:
        demisto.handleEntitlementForUser(incident_id, guid, email, text, task_id)
        account_id = CLIENT.account_id
        bot_jid = CLIENT.bot_jid
        if account_id and bot_jid:
            _ = await process_entitlement_reply(text, account_id, bot_jid, to_jid)
    except Exception as e:
        demisto.error(f"Failed handling entitlement {entitlement}: {e!s}")
    question["remove"] = True
    return incident_id


async def handle_text_received_from_zoom(investigation_id: str, text: str, operator_email: str, operator_name: str):
    """
    Handles text received from Zoom

    Args:
        investigation_id: The mirrored investigation ID
        text: The received text
        operator_email: The sender email
        operator_name: The sender name
    """
    if text:
        demisto.addEntry(id=investigation_id, entry=text, username=operator_name, email=operator_email, footer=MESSAGE_FOOTER)


async def handle_listen_error(error: str):
    """
    Logs an error and updates the module health accordingly.

    Args:
        error: The error string.
    """
    demisto.error(error)
    demisto.updateModuleHealth(error)


async def handle_mirroring(payload):
    """
    handle messages from the Zoom webhook that have been identified as possible mirrored messages
    If we find one, we will update the mirror object and send
    the message to the corresponding investigation's war room as an entry.
    :param payload: str: The request payload from zoom
    :return: None
    """
    channel_id = payload.get("object", {}).get("channel_id")
    if not channel_id:
        return
    integration_context = fetch_context()
    if not integration_context or "mirrors" not in integration_context:
        return
    mirrors = json.loads(integration_context["mirrors"])
    mirror_filter = list(filter(lambda m: m["channel_id"] == channel_id, mirrors))
    if not mirror_filter:
        return
    for mirror in mirror_filter:
        if mirror["mirror_direction"] == "FromDemisto" or mirror["mirror_type"] == "none":
            return
        if not mirror["mirrored"]:
            # In case the investigation is not mirrored yet
            mirror = mirrors.pop(mirrors.index(mirror))
            if mirror["mirror_direction"] and mirror["mirror_type"]:
                investigation_id = mirror["investigation_id"]
                mirror_type = mirror["mirror_type"]
                auto_close = mirror["auto_close"]
                direction = mirror["mirror_direction"]
                demisto.mirrorInvestigation(investigation_id, f"{mirror_type}:{direction}", auto_close)
                mirror["mirrored"] = True
                mirrors.append(mirror)
                set_to_integration_context_with_retries({"mirrors": mirrors}, OBJECTS_TO_KEYS, SYNC_CONTEXT)
        message = payload["object"]["message"]
        demisto.info(f"payload:{payload}")
        operator_email = payload["operator"]
        operator_name = zoom_get_user_name_by_email(CLIENT, operator_email)
        investigation_id = mirror["investigation_id"]
        await handle_text_received_from_zoom(investigation_id, message, operator_email, operator_name)


async def event_url_validation(payload):
    """Verify the authenticity of a token
    Args:
        payload : request from Zoom
    Returns:
        json_res: A dictionary containing the 'plainToken' and its corresponding 'encryptedToken' (HMAC-SHA256 signature).
    """
    plaintoken = payload.get("plainToken")
    hash_object = hmac.new(SECRET_TOKEN.encode("utf-8"), msg=plaintoken.encode("utf-8"), digestmod=hashlib.sha256)
    expected_signature = hash_object.hexdigest()
    json_res = {"plainToken": plaintoken, "encryptedToken": expected_signature}
    return json_res


@app.post("/")
async def handle_zoom_response(
    request: Request, credentials: HTTPBasicCredentials = Depends(basic_auth), token: APIKey = Depends(token_auth)
):
    """handle any response that came from Zoom app
    Args:
        request : zoom request
    Returns:
        JSONResponse:response to zoom
    """
    request = await request.json()
    demisto.debug(request)
    credentials_param = demisto.params().get("credentials")
    auth_failed = False
    v_token = demisto.params().get("verification_token", {}).get("password")
    if not str(token).startswith("Basic") and v_token:
        if token != v_token:
            auth_failed = True

    elif credentials and credentials_param and (username := credentials_param.get("identifier")):
        password = credentials_param.get("password", "")
        if not compare_digest(credentials.username, username) or not compare_digest(credentials.password, password):
            auth_failed = True
    if auth_failed:
        demisto.debug("Authorization failed")
        return Response(status_code=status.HTTP_401_UNAUTHORIZED, content="Authorization failed.")

    event_type = request["event"]
    payload = request["payload"]
    try:
        if event_type == "endpoint.url_validation" and SECRET_TOKEN:
            res = await event_url_validation(payload)
            return JSONResponse(content=res)

        elif event_type == "interactive_message_actions":
            if "actionItem" in payload:
                action = payload["actionItem"]["value"]
            elif "selectedItems" in payload:
                action = payload["selectedItems"][0]["value"]
            else:
                return Response(status_code=status.HTTP_400_BAD_REQUEST)
            message_id = payload["messageId"]
            account_id = payload["accountId"]
            robot_jid = payload["robotJid"]
            to_jid = payload["toJid"]
            user_name = payload["userName"]
            user_id = payload["userId"]
            user_email = zoom_get_user_email_by_id(CLIENT, user_id)
            entitlement_reply = await check_and_handle_entitlement(action, message_id, user_name, user_email)
            if entitlement_reply:
                await process_entitlement_reply(entitlement_reply, account_id, robot_jid, to_jid, user_name, action)
                demisto.updateModuleHealth("")
            demisto.debug(f"Action {action} was clicked on message id {message_id}")
            return Response(status_code=status.HTTP_200_OK)
        elif event_type == "chat_message.sent" and MIRRORING_ENABLED:
            await handle_mirroring(payload)
            return Response(status_code=status.HTTP_200_OK)
        else:
            return Response(status_code=status.HTTP_400_BAD_REQUEST)
    except Exception as e:
        await handle_listen_error(f"An error occurred while handling a response from Zoom: {e}")


def test_module(client: Client):
    """Tests connectivity with the client.
    Takes as an argument all client arguments to create a new client
    """
    try:
        if MIRRORING_ENABLED and (
            not LONG_RUNNING or not SECRET_TOKEN or not client.bot_client_id or not client.bot_client_secret or not client.bot_jid
        ):
            raise DemistoException("""Mirroring is enabled, however long running is disabled
or the necessary bot authentication parameters are missing.
For mirrors to work correctly, long running must be enabled and you must provide all
the zoom-bot following parameters:
secret token,
Bot JID,
bot client id and secret id""")
        client.zoom_list_users(page_size=1, url_suffix="users")
        if client.bot_access_token:
            json_data = {
                "robot_jid": client.bot_jid,
                "to_jid": "foo@conference.xmpp.zoom.us",
                "account_id": client.account_id,
                "content": {"head": {"text": "hi"}},
            }
            client.zoom_send_notification(url_suffix="/im/chat/messages", json_data=json_data)
    except DemistoException as e:
        error_message = e.message
        if "Invalid access token" in error_message:
            error_message = INVALID_CREDENTIALS
        elif "The Token's Signature resulted invalid" in error_message:
            error_message = INVALID_API_SECRET
        elif "Invalid client_id or client_secret" in error_message:
            error_message = INVALID_ID_OR_SECRET
        elif "No channel or user can be found with the given to_jid." in error_message:
            return "ok"
        elif "Invalid authorization token." in error_message:
            error_message = INVALID_TOKEN
        elif "No Chatbot can be found with the given robot_jid value." in error_message:
            error_message = INVALID_BOT_ID
        elif "Invalid robot_jid value specified in the Request Body." in error_message:
            error_message = INVALID_BOT_ID
        else:
            error_message = f"Problem reaching Zoom API, check your credentials. Error message: {error_message}"
        return error_message
    return "ok"


def remove_None_values_from_dict(dict_to_reduce: Dict[str, Any]):
    """
    Removes None values (but not False values) from a given dict and from the nested dicts in it.
    """
    reduced_dict = {}
    for key, value in dict_to_reduce.items():
        if value is not None:
            if isinstance(value, dict):
                reduced_nested_dict = remove_None_values_from_dict(value)
                if reduced_nested_dict:
                    reduced_dict[key] = reduced_nested_dict
            else:
                reduced_dict[key] = value

    return reduced_dict


def check_start_time_format(start_time):
    """checking if the time format is a full time format"""
    expected_format = "%Y-%m-%dT%H:%M:%S"
    if start_time.endswith("Z"):
        expected_format += "%z"
    try:
        datetime.strptime(start_time, expected_format)
    except ValueError as e:
        raise DemistoException(WRONG_TIME_FORMAT) from e


def manual_list_user_pagination(client: Client, next_page_token: str | None, limit: int, status: str, role_id: str | None):
    res = []
    page_size = min(limit, MAX_RECORDS_PER_PAGE)
    while limit > 0 and next_page_token != "":
        response = client.zoom_list_users(
            page_size=page_size, status=status, next_page_token=next_page_token, role_id=role_id, url_suffix="users"
        )
        next_page_token = response.get("next_page_token")

        res.append(response)
        limit -= MAX_RECORDS_PER_PAGE
    return res


def manual_list_channel_pagination(client: Client, next_page_token: str | None, limit: int, url_suffix: str):
    res = {}
    page_size = min(limit, MAX_RECORDS_PER_PAGE)
    while limit > 0:
        response = client.zoom_list_channels(page_size=page_size, next_page_token=next_page_token, url_suffix=url_suffix)
        next_page_token = response.get("next_page_token")

        res.update(response)
        limit -= MAX_RECORDS_PER_PAGE
    return res


def manual_list_user_channel_pagination(client: Client, user_id: str, next_page_token: str | None, limit: int, url_suffix: str):
    res = {}
    page_size = min(limit, MAX_RECORDS_PER_PAGE)
    while limit > 0:
        response = client.zoom_list_user_channels(
            user_id=user_id, page_size=page_size, next_page_token=next_page_token, url_suffix=url_suffix
        )
        next_page_token = response.get("next_page_token")

        res.update(response)
        limit -= MAX_RECORDS_PER_PAGE
    return res


def manual_meeting_list_pagination(client: Client, user_id: str, next_page_token: str | None, limit: int, type: str | int | None):
    res = []
    page_size = min(limit, MAX_RECORDS_PER_PAGE)
    while limit > 0 and next_page_token != "":
        response = client.zoom_meeting_list(user_id=user_id, next_page_token=next_page_token, page_size=page_size, type=type)
        next_page_token = response.get("next_page_token")
        res.append(response)
        # subtract what i already got
        limit -= MAX_RECORDS_PER_PAGE
    return res


def remove_extra_info_list_users(limit, raw_data):
    """_summary_
    Due to the fact that page_size must be const,
    Extra information may be provided

    Args:
        limit (int): the number of records the user asked for
        raw_data (dict):the entire response from the pagination function
    """
    all_info = []
    for page in raw_data:
        users_info = page.get("users", [])
        for user in users_info:
            all_info.append(user)
            if len(all_info) >= limit:
                return all_info
    return all_info


def remove_extra_info_list(name, limit, raw_data):
    """_summary_
    Due to the fact that page_size must be const,
    Extra information may be provided
    Args:
        limit (int): the number of records the user asked for
        raw_data (dict):the entire response from the pagination function
    """
    all_info = []
    channel_info = raw_data.get(name, [raw_data])
    for channel in channel_info:
        all_info.append(channel)
        if len(all_info) >= limit:
            return all_info
    return all_info


def remove_extra_info_meeting_list(limit, raw_data):
    """
    Due to the fact that page_size must be const,
    Extra information may be provided to me, such as:
    In the case of limit = 301, manual_meeting_list_pagination will return 600 meetings (MAX_RECORDS * 2),
    The last 299 must be removed.

    Args:
        limit (int): the number of records the user asked for
        raw_data (dict):the entire response from the pagination function
    """
    all_info = []
    for page in raw_data:
        meetings = page.get("meetings")
        for meeting in meetings:
            all_info.append(meeting)
            if len(all_info) >= limit:
                return all_info
    return all_info


"""FORMATTING FUNCTIONS"""


def zoom_list_users_command(client, **args) -> CommandResults:
    # PREPROCESSING
    client = client
    page_size = arg_to_number(args.get("page_size", 30))
    user_id = args.get("user_id")
    status = args.get("status", "active")
    next_page_token = args.get("next_page_token")
    role_id = args.get("role_id")
    limit = arg_to_number(args.get("limit"))
    page_number = arg_to_number(args.get("page_number", 1))

    url_suffix = f"users/{user_id}" if user_id else "users"

    if limit:
        if "page_size" in args or "page_number" in args or next_page_token or user_id:
            # arguments collision
            raise DemistoException(LIMIT_AND_EXTRA_ARGUMENTS)
        else:
            # multiple requests are needed
            raw_data = manual_list_user_pagination(
                client=client, next_page_token=next_page_token, limit=limit, status=status, role_id=role_id
            )

            minimal_needed_info = remove_extra_info_list_users(limit, raw_data)

            md = tableToMarkdown(
                "Users", minimal_needed_info, ["id", "email", "type", "pmi", "verified", "created_at", "status", "role_id"]
            )
            md += "\n" + tableToMarkdown("Metadata", [raw_data][0][0], ["total_records"])
            raw_data = raw_data[0]
    else:
        # only one request is needed
        raw_data = client.zoom_list_users(
            page_size=page_size,
            status=status,  # type: ignore[arg-type]
            next_page_token=next_page_token,
            role_id=role_id,
            url_suffix=url_suffix,
            page_number=page_number,
        )
        # parsing the data according to the different given arguments
        if user_id:
            md = tableToMarkdown(
                "User", [raw_data], ["id", "email", "type", "pmi", "verified", "created_at", "status", "role_id"]
            )
        else:
            md = tableToMarkdown(
                "Users", raw_data.get("users"), ["id", "email", "type", "pmi", "verified", "created_at", "status", "role_id"]
            )
            md += "\n" + tableToMarkdown(
                "Metadata", [raw_data], ["page_count", "page_number", "page_size", "total_records", "next_page_token"]
            )
    return CommandResults(
        outputs_prefix="Zoom",
        readable_output=md,
        outputs={
            "User": raw_data.get("users"),
            "Metadata": {
                "Count": raw_data.get("page_count"),
                "Number": raw_data.get("page_number"),
                "Size": raw_data.get("page_size"),
                "Total": raw_data.get("total_records"),
            },
        },
        raw_response=raw_data,
    )


def zoom_create_user_command(client, **args) -> CommandResults:
    client = client
    user_type = args.get("user_type", "")
    email = args.get("email")
    first_name = args.get("first_name")
    last_name = args.get("last_name")
    user_type_num = USER_TYPE_MAPPING.get(user_type)
    raw_data = client.zoom_create_user(user_type_num, email, first_name, last_name)
    return CommandResults(
        outputs_prefix="Zoom.User",
        readable_output=f"User created successfully with ID: {raw_data.get('id')}",
        outputs=raw_data,
        raw_response=raw_data,
    )


def zoom_delete_user_command(client, **args) -> CommandResults:
    client = client
    user = args.get("user")
    action = args.get("action")
    client.zoom_delete_user(user, action)
    return CommandResults(
        readable_output=f"User {user} was deleted successfully",
    )


def zoom_create_meeting_command(client, **args) -> CommandResults:
    client = client
    user_id = args.get("user")
    topic = args.get("topic", "")
    host_video = argToBoolean(args.get("host_video", True))
    join_before_host_time = args.get("join_before_host_time")
    start_time = args.get("start_time")
    timezone = args.get("timezone", "")
    type = args.get("type", "Instant")
    auto_record_meeting = args.get("auto_record_meeting")
    encryption_type = args.get("encryption_type")
    join_before_host = argToBoolean(args.get("join_before_host", False))
    meeting_authentication = argToBoolean(args.get("meeting_authentication", False))
    waiting_room = argToBoolean(args.get("waiting_room", False))
    end_date_time = args.get("end_date_time")
    end_times = arg_to_number(args.get("end_times", 1))
    monthly_day = arg_to_number(args.get("monthly_day", 1))
    monthly_week = arg_to_number(args.get("monthly_week"))
    monthly_week_day = arg_to_number(args.get("monthly_week_day"))
    repeat_interval = arg_to_number(args.get("repeat_interval"))
    recurrence_type = args.get("recurrence_type", "")
    weekly_days = arg_to_number(args.get("weekly_days", 1))

    num_type: int | None = MEETING_TYPE_NUM_MAPPING.get(type)

    # argument checking
    if type == INSTANT and (timezone or start_time):
        raise DemistoException(INSTANT_AND_TIME)

    if join_before_host_time and not join_before_host:
        raise DemistoException(JBH_TIME_AND_NO_JBH)

    if waiting_room and join_before_host:
        raise DemistoException(WAITING_ROOM_AND_JBH)

    if args.get("end_times") and end_date_time:
        raise DemistoException(END_TIMES_AND_END_DATE_TIME)

    if type != RECURRING_WITH_TIME and any(
        (
            end_date_time,
            args.get("end_times"),
            args.get("monthly_day"),
            monthly_week,
            monthly_week_day,
            repeat_interval,
            args.get("weekly_days"),
        )
    ):
        raise DemistoException(NOT_RECURRING_WITH_RECURRING_ARGUMENTS)

    if (
        type == RECURRING_WITH_TIME
        and recurrence_type != "Monthly"
        and any((args.get("monthly_day"), monthly_week, monthly_week_day))
    ):
        raise DemistoException(NOT_MONTHLY_AND_MONTHLY_ARGUMENTS)

    if (
        type == RECURRING_WITH_TIME
        and recurrence_type == "Monthly"
        and not (monthly_week and monthly_week_day)
        and not args.get("monthly_day")
    ):
        raise DemistoException(MONTHLY_RECURRING_MISIING_ARGUMENTS)

    if type == RECURRING_WITH_TIME and recurrence_type != "Weekly" and args.get("weekly_days"):
        raise DemistoException(NOT_WEEKLY_WITH_WEEKLY_ARGUMENTS)

    if type == RECURRING_WITH_TIME and not recurrence_type:
        raise DemistoException(RECURRING_MISSING_ARGUMENTS)

    # converting separately after the argument checking, because 0 as an int is equaled to false
    join_before_host_time = arg_to_number(join_before_host_time)

    if start_time:
        check_start_time_format(start_time)

    json_all_data: Dict[str, Union[Any, None, int]] = {}

    # special section for recurring meeting with fixed time
    if type == RECURRING_WITH_TIME:
        recurrence_type_num = MONTHLY_RECURRING_TYPE_MAPPING.get(recurrence_type)
        json_all_data.update(
            {
                "recurrence": {
                    "end_date_time": end_date_time,
                    "end_times": end_times,
                    "monthly_day": monthly_day,
                    "monthly_week": monthly_week,
                    "monthly_week_day": monthly_week_day,
                    "repeat_interval": repeat_interval,
                    "type": recurrence_type_num,
                    "weekly_days": weekly_days,
                }
            }
        )
    json_all_data.update(
        {
            "settings": {
                "auto_recording": auto_record_meeting,
                "encryption_type": encryption_type,
                "host_video": host_video,
                "jbh_time": join_before_host_time,
                "join_before_host": join_before_host,
                "meeting_authentication": meeting_authentication,
                "waiting_room": waiting_room,
            },
            "start_time": start_time,
            "timezone": timezone,
            "type": num_type,
            "topic": topic,
        }
    )
    # remove all keys with val of None
    json_data = remove_None_values_from_dict(json_all_data)
    url_suffix = f"users/{user_id}/meetings"
    # call the API
    raw_data = client.zoom_create_meeting(url_suffix=url_suffix, json_data=json_data)
    # parsing the response
    if type == "Recurring meeting with fixed time":
        raw_data.update({"start_time": raw_data.get("occurrences")[0].get("start_time")})
        raw_data.update({"duration": raw_data.get("occurrences")[0].get("duration")})

    md = tableToMarkdown(
        "Meeting details",
        [raw_data],
        [
            "uuid",
            "id",
            "host_id",
            "host_email",
            "topic",
            "type",
            "status",
            "start_time",
            "duration",
            "timezone",
            "created_at",
            "start_url",
            "join_url",
        ],
    )

    # removing passwords from the response#
    safe_raw_data = raw_data
    for sensitive_info in ["password", "pstn_password", "encrypted_password", "h323_password"]:
        safe_raw_data.pop(sensitive_info, None)
    return CommandResults(outputs_prefix="Zoom.Meeting", readable_output=md, outputs=safe_raw_data, raw_response=raw_data)


def zoom_fetch_recording_command(client: Client, **args):
    # preprocessing
    results = []
    meeting_id = args.get("meeting_id")
    delete_after = argToBoolean(args.get("delete_after"))
    client = client

    data = client.zoom_fetch_recording(method="GET", url_suffix=f"meetings/{meeting_id}/recordings")
    recording_files = data.get("recording_files")
    # Getting the audio and video files which are contained in every recording.
    for file in recording_files:
        download_url = file.get("download_url")
        try:
            # download the file
            demisto.debug(f"Trying to download the files of meeting {meeting_id}")
            record = client.zoom_fetch_recording(method="GET", full_url=download_url, resp_type="response", stream=True)
            file_type = file.get("file_type")
            file_type_as_literal = FILE_TYPE_MAPPING.get(file_type)
            # save the file
            filename = f'recording_{meeting_id}_{file.get("id")}.{file_type}'
            with open(filename, "wb") as f:
                # Saving the content of the file locally.
                record.raw.decode_content = True
                shutil.copyfileobj(record.raw, f)

            results.append(file_result_existing_file(filename))
            results.append(
                CommandResults(readable_output=f"The {file_type_as_literal} file {filename} was downloaded successfully")
            )

            if delete_after:
                try:
                    # delete the file from the cloud
                    demisto.debug(f"Trying to delete the file {filename}")
                    client.zoom_fetch_recording(
                        method="DELETE", url_suffix=f'meetings/{meeting_id}/recordings/{file["id"]}', resp_type="response"
                    )
                    results.append(
                        CommandResults(
                            readable_output=f"The {file_type_as_literal} file {filename} was successfully removed from the cloud."
                        )
                    )
                except DemistoException as exp:
                    results.append(CommandResults(readable_output=f"Failed to delete file {filename}. {exp}"))

        except DemistoException as exp:
            raise DemistoException(f"Unable to download recording for meeting {meeting_id}: {exp}")

    return results


def zoom_meeting_get_command(client, **args) -> CommandResults:
    client = client
    meeting_id = args.get("meeting_id")
    occurrence_id = args.get("occurrence_id")
    show_previous_occurrences = argToBoolean(args.get("show_previous_occurrences"))

    raw_data = client.zoom_meeting_get(meeting_id, occurrence_id, show_previous_occurrences)
    # parsing the response
    md = tableToMarkdown(
        "Meeting details",
        raw_data,
        [
            "uuid",
            "id",
            "host_id",
            "host_email",
            "topic",
            "type",
            "status",
            "start_time",
            "duration",
            "timezone",
            "agenda",
            "created_at",
            "start_url",
            "join_url",
        ],
    )
    # removing passwords from the response#
    safe_raw_data = raw_data
    for sensitive_info in ["password", "pstn_password", "encrypted_password", "h323_password"]:
        safe_raw_data.pop(sensitive_info, None)
    return CommandResults(
        outputs_prefix="Zoom.Meeting", readable_output=md, outputs_key_field="id", outputs=safe_raw_data, raw_response=raw_data
    )


def zoom_meeting_list_command(client, **args) -> CommandResults:
    client = client
    user_id = args.get("user_id", "")
    next_page_token = args.get("next_page_token")
    page_size = arg_to_number(args.get("page_size", 30))
    limit = arg_to_number(args.get("limit"))
    type = args.get("type")
    page_number = arg_to_number(args.get("page_number", 1))

    if limit:
        if "page_size" in args or next_page_token or "page_number" in args:
            # arguments collision
            raise DemistoException(LIMIT_AND_EXTRA_ARGUMENTS_MEETING_LIST)
        else:
            # multiple request are needed
            raw_data = manual_meeting_list_pagination(
                client=client, user_id=user_id, next_page_token=next_page_token, limit=limit, type=type
            )

            minimal_needed_info = remove_extra_info_meeting_list(limit=limit, raw_data=raw_data)

            md = tableToMarkdown(
                "Meeting list",
                minimal_needed_info,
                ["uuid", "id", "host_id", "topic", "type", "start time", "duration", "timezone", "created_at", "join_url"],
            )
            md += "\n" + tableToMarkdown("Metadata", [raw_data][0][0], ["total_records"])
            raw_data = raw_data[0]

    else:
        # one request in needed
        raw_data = client.zoom_meeting_list(
            user_id=user_id, next_page_token=next_page_token, page_size=page_size, type=type, page_number=page_number
        )
        # parsing the data
        md = tableToMarkdown(
            "Meeting list",
            raw_data.get("meetings"),
            ["uuid", "id", "host_id", "topic", "type", "start_time", "duration", "timezone", "created_at", "join_url"],
        )
        md += "\n" + tableToMarkdown("Metadata", [raw_data], ["next_page_token", "page_size", "page_number", "total_records"])

    return CommandResults(
        outputs_prefix="Zoom",
        readable_output=md,
        # keeping the syntax of the output of the previous version
        outputs={"Meeting": raw_data, "Metadata": {"Size": raw_data.get("page_size"), "Total": raw_data.get("total_records")}},
        raw_response=raw_data,
    )


def check_authentication_type_parameters(
    api_key: str,
    api_secret: str,
    # checking if the user entered extra parameters
    # at the configuration level
    account_id: str,
    client_id: str,
    client_secret: str,
):
    if any((api_key, api_secret)) and any((account_id, client_id, client_secret)):
        raise DemistoException(EXTRA_PARAMS)


def check_authentication_bot_parameters(bot_Jid: str, client_id: str, client_secret: str):
    """check authentication parameters that both client_id, secret_id, bot_Jid are provided or none of them"""
    if (bot_Jid and client_id and client_secret) or (not bot_Jid and not client_id and not client_secret):
        return
    else:
        raise DemistoException(BOT_PARAM_CHECK)


def check_authentication_parameters(client_id: str, client_secret: str):
    """check authentication parameters that both client_id and secret are provided"""
    if (client_id and client_secret) or (not client_id and not client_secret):
        return
    else:
        raise DemistoException(OAUTH_PARAM_CHECK)


def zoom_list_account_public_channels_command(client, **args) -> CommandResults:
    """
    Lists public channels associated with a Zoom account.
    """
    # PREPROCESSING
    client = client
    page_size = arg_to_number(args.get("page_size", 50))
    channel_id = args.get("channel_id")
    next_page_token = args.get("next_page_token")
    limit = arg_to_number(args.get("limit"))
    page_number = arg_to_number(args.get("page_number", 1))

    url_suffix = f"channels/{channel_id}" if channel_id else "channels"

    if limit:
        if "page_size" in args or "page_number" in args or next_page_token or channel_id:
            # arguments collision
            raise DemistoException(LIMIT_AND_EXTRA_ARGUMENTS)
        else:
            # multiple requests are needed
            raw_data = manual_list_channel_pagination(
                client=client, next_page_token=next_page_token, limit=limit, url_suffix=url_suffix
            )

            data = remove_extra_info_list("channels", limit, raw_data)
            token = raw_data.get("next_page_token", None)
    else:
        # only one request is needed
        raw_data = client.zoom_list_channels(
            page_size=page_size, next_page_token=next_page_token, url_suffix=url_suffix, page_number=page_number
        )
        # parsing the data according to the different given arguments
        data = [raw_data] if channel_id else raw_data.get("channels")
        token = raw_data.get("next_page_token", None)
    outputs = []
    for i in data:
        outputs.append(
            {
                "Channel JID": i.get("jid"),
                "Channel ID": i.get("id"),
                "Channel name": i.get("name"),
                "Channel type": i.get("type"),
                "Channel url": i.get("channel_url"),
            }
        )

    md = tableToMarkdown("Channels", outputs, removeNull=True)
    md += "\n" + f"Channels Next Token: {token}"

    return CommandResults(
        outputs_prefix="Zoom.Channel", readable_output=md, outputs={**raw_data, "ChannelsNextToken": token}, raw_response=raw_data
    )


def zoom_list_user_channels_command(client, **args) -> CommandResults:
    """
    Lists channels associated with a specific Zoom user.
    """
    # PREPROCESSING
    client = client
    page_size = arg_to_number(args.get("page_size", 50))
    channel_id = args.get("channel_id")
    user_id = args.get("user_id", "")
    next_page_token = args.get("next_page_token")
    limit = arg_to_number(args.get("limit"))
    page_number = arg_to_number(args.get("page_number", 1))

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    data = []
    url_suffix = f"users/{user_id}/channels/{channel_id}" if channel_id else f"users/{user_id}/channels"
    if limit:
        if "page_size" in args or "page_number" in args or next_page_token:
            # arguments collision
            raise DemistoException(LIMIT_AND_EXTRA_ARGUMENTS)
        else:
            # multiple requests are needed
            raw_data = manual_list_user_channel_pagination(
                client=client, user_id=user_id, next_page_token=next_page_token, limit=limit, url_suffix=url_suffix
            )
            data = remove_extra_info_list("channels", limit, raw_data)
            token = raw_data.get("next_page_token", None)
    else:
        # only one request is needed
        raw_data = client.zoom_list_user_channels(
            user_id=user_id, page_size=page_size, next_page_token=next_page_token, url_suffix=url_suffix, page_number=page_number
        )
        # parsing the data according to the different given arguments
        data = [raw_data] if channel_id else raw_data.get("channels")
        token = raw_data.get("next_page_token", None)
    outputs = []
    for i in data:
        outputs.append(
            {
                "User id": user_id,
                "Channel ID": i.get("id"),
                "channel JID": i.get("jid"),
                "Channel name": i.get("name"),
                "Channel type": i.get("type"),
                "Channel url": i.get("channel_url"),
            }
        )
    md = tableToMarkdown("Channels", outputs)

    return CommandResults(
        outputs_prefix="Zoom.Channel",
        outputs_key_field="id",
        readable_output=md,
        outputs={**raw_data, "UserChannelsNextToken": token},
        raw_response=raw_data,
    )


def zoom_create_channel_command(client, **args) -> CommandResults:
    """
    Create a new zoom channel
    """
    client = client
    user_id = args.get("user_id")
    member_emails = argToList(args.get("member_emails"))
    add_member_permissions = args.get("add_member_permissions", None)
    add_member_permissions_num = MEMBER_PERMISSIONS_MAPPING.get(add_member_permissions)
    posting_permissions = args.get("posting_permissions", None)
    posting_permissions_num = POSTING_PERMISSIONS_MAPPING.get(posting_permissions)
    new_members_can_see_prev_msgs = args.get("new_members_can_see_prev_msgs", True)
    channel_name = args.get("channel_name")
    channel_type = args.get("channel_type", None)
    channel_type_num = CHANNEL_TYPE_MAPPING.get(channel_type)
    json_all_data = {}
    email_json = [{"email": email} for email in member_emails]

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)
    # special section for recurring meeting with fixed time
    json_all_data.update(
        {
            "channel_settings": {
                "add_member_permissions": add_member_permissions_num,
                "new_members_can_see_previous_messages_files": new_members_can_see_prev_msgs,
                "posting_permissions": posting_permissions_num,
            },
            "name": channel_name,
            "type": channel_type_num,
            "members": email_json,
        }
    )

    json_data = remove_None_values_from_dict(json_all_data)
    url_suffix = f"/chat/users/{user_id}/channels"
    raw_data = client.zoom_create_channel(url_suffix, json_data)
    # md = tableToMarkdown('Channel details', [raw_data], ['id', 'name', 'type', 'channel_url'])

    outputs = []
    outputs.append(
        {
            "User id": user_id,
            "Channel ID": raw_data.get("id"),
            "Channel name": raw_data.get("name"),
            "Channel type": raw_data.get("type"),
            "Channel url": raw_data.get("channel_url"),
        }
    )

    human_readable = tableToMarkdown("Channel details", outputs, removeNull=True)

    return CommandResults(
        outputs_prefix="Zoom.Channel",
        outputs_key_field="id",
        readable_output=human_readable,
        outputs=raw_data,
        raw_response=raw_data,
    )


def zoom_delete_channel_command(client, **args) -> CommandResults:
    """
    Delete a Zoom channel
    """
    client = client
    channel_id = args.get("channel_id")
    user_id = args.get("user_id")
    url_suffix = f"/chat/users/{user_id}/channels/{channel_id}"

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    client.zoom_delete_channel(url_suffix)
    return CommandResults(
        readable_output=f"Channel {channel_id} was deleted successfully",
    )


def zoom_update_channel_command(client, **args) -> CommandResults:
    """
    Update a Zoom channel
    """
    client = client
    add_member_permissions = args.get("add_member_permissions", None)
    add_member_permissions_num = MEMBER_PERMISSIONS_MAPPING.get(add_member_permissions)
    posting_permissions = args.get("posting_permissions", None)
    posting_permissions_num = POSTING_PERMISSIONS_MAPPING.get(posting_permissions)
    new_members_can_see_prev_msgs = args.get("new_members_can_see_prev_msgs", True)
    channel_name = args.get("channel_name")
    channel_id = args.get("channel_id")
    user_id = args.get("user_id")
    json_all_data = {}

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)
    # special section for recurring meeting with fixed time
    json_all_data.update(
        {
            "name": channel_name,
            "channel_settings": {
                "add_member_permissions": add_member_permissions_num,
                "new_members_can_see_previous_messages_files": new_members_can_see_prev_msgs,
                "posting_permissions": posting_permissions_num,
            },
        }
    )

    json_data = remove_None_values_from_dict(json_all_data)
    url_suffix = f"/chat/users/{user_id}/channels/{channel_id}"
    client.zoom_update_channel(url_suffix, json_data)

    return CommandResults(
        readable_output=f"Channel {channel_id} was updated successfully",
    )


def zoom_invite_to_channel_command(client, **args) -> CommandResults:
    """
    invite users to Zoom channel
    """
    channel_id = args.get("channel_id")
    user_id = args.get("user_id")
    members = argToList(args.get("members"))
    url_suffix = f"/chat/users/{user_id}/channels/{channel_id}/members"
    json_members = [{"email": email} for email in members]

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    members_json = {"members": json_members}

    raw_data = client.zoom_invite_to_channel(members_json, url_suffix)

    outputs = [{"User id": raw_data.get("ids"), "Channel ID": channel_id, "Added at date and time": raw_data.get("added_at")}]

    human_readable = tableToMarkdown("Channel details", outputs, removeNull=True)

    return CommandResults(
        outputs_prefix="Zoom.Channel",
        outputs_key_field="id",
        readable_output=human_readable,
        outputs=raw_data,
        raw_response=raw_data,
    )


def zoom_remove_from_channel_command(client, **args) -> CommandResults:
    """
    Remove a user from Zoom channel
    """
    client = client
    channel_id = args.get("channel_id")
    member_id = args.get("member_id")
    user_id = args.get("user_id")

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    url_suffix = f"/chat/users/{user_id}/channels/{channel_id}/members/{member_id}"
    client.zoom_remove_from_channel(url_suffix)
    return CommandResults(
        readable_output=f"Member {member_id} was successfully removed from channel {channel_id}",
    )


def zoom_send_file_command(client, **args) -> CommandResults:
    """
    Send file in Zoom
    """
    client = client
    user_id = args.get("user_id")
    to_channel = args.get("to_channel")
    to_contact = args.get("to_contact")
    entry_id = args.get("entry_id")

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    if "to_contact" not in args and "to_channel" not in args:
        raise DemistoException(MISSING_ARGUMENT)

    file_info = demisto.getFilePath(entry_id)
    upload_url = f"https://file.zoom.us/v2/chat/users/{user_id}/messages/files"
    json_data = remove_None_values_from_dict({"to_contact": to_contact, "to_channel": to_channel})
    upload_response = client.zoom_send_file(upload_url, file_info, json_data)

    message_id = upload_response.get("id")
    return CommandResults(readable_output=f"Message with id {message_id} was successfully sent")


def process_links(formatted_message, formats):
    """
    Processes markdown links in the formatted message.
    for example- [my link](https://****.com)
    Args:
        formatted_message (str): The formatted message to process.

    Returns:
        str: The formatted message with links processed.
    """
    add_link_regex = r"\[([^[\]]*?)\]\((http[s]?://.*?)\)"
    matches = re.findall(add_link_regex, formatted_message)

    for match in matches:
        link_text, link_url = match
        add_link_range = {"text": link_text, "format_type": "AddLink", "format_attr": link_url}
        formats.append(add_link_range)
        # Replace Markdown syntax with plain text
        formatted_message = formatted_message.replace("[" + link_text + "](" + link_url + ")", link_text)

    return formatted_message, formats


def process_mentions(formatted_message, formats, at_contact=None):
    """
    Processes mentions in the markdown message you can provide just one mention on each message.
    for example-'@all'

    Args:
        formatted_message (str): The markdown message to process.
        at_contact (str): The contact to mention.

    Returns:
        str: The formatted message without the mention markdown.
    """
    mention_regex = r"(@\w+)"

    matches = re.findall(mention_regex, formatted_message)[:1]
    for match in matches:
        if match == "@all":
            mention_range = {
                "text": match,
                "at_type": 2,
                "at_contact": at_contact,
            }
            formats.append(mention_range)
        elif at_contact:
            mention_range = {
                "text": match,
                "at_contact": at_contact,
                "at_type": 1,
            }
            formats.append(mention_range)
    return formatted_message, formats


def process_background_colors(formatted_message, formats):
    """
    Processes background colors in the markdown message.
    for example- [#<rgb>bg](message that will be with background)

    Args:
        formatted_message (str): The markdown message to process.

    Returns:
        str: The formatted message without background color markdown.
    """
    bg_color_regex = r"\[#([A-Fa-f0-9]{6})bg\]\((.*?)\)"

    matches = re.findall(bg_color_regex, formatted_message)
    for match in matches:
        bg_color, text = match
        bg_color_range = {
            "text": text,
            "format_type": "BackgroundColor",
            "format_attr": bg_color,
        }
        formats.append(bg_color_range)

        # Replace Markdown syntax with plain text
        formatted_message = formatted_message.replace("[#" + bg_color + "bg](" + text + ")", text)
    return formatted_message, formats


def process_font_sizes(formatted_message, formats):
    """
    Processes font sizes in the markdown message.
    use s|m|l for text size
    for example- [s](message)

    Args:
        formatted_message (str): The markdown message to process.

    Returns:
        str: The formatted message without fontsize markdown.
    """
    font_size_regex = "\\[(s|m|l)\\]\\((.*?)\\)"
    matches = re.findall(font_size_regex, formatted_message)
    for match in matches:
        size, text = match
        font_size_range = {"text": text, "format_type": "FontSize", "format_attr": size}
        formats.append(font_size_range)

        # Replace Markdown syntax with plain text
        formatted_message = formatted_message.replace("[" + size + "](" + text + ")", text)
    return formatted_message, formats


def process_font_colors(formatted_message, formats):
    """
    Processes font colors in the formatted message.

    Args:
        formatted_message (str): The formatted message to process.

    Returns:
        str: The formatted message with font colors processed.
    """
    font_color_regex = "\\[#([A-Fa-f0-9]{6})\\]\\((.*?)\\)"
    matches = re.findall(font_color_regex, formatted_message)
    for match in matches:
        color, text = match
        font_color_range = {
            "text": text,
            "format_type": "FontColor",
            "format_attr": color,
        }
        formats.append(font_color_range)

        # Replace Markdown syntax with plain text
        formatted_message = formatted_message.replace("[#" + color + "](" + text + ")", text)
    return formatted_message, formats


def process_left_indent_and_paragraphs(formatted_message, formats):
    """
    Processes left indents and paragraphs in the markdown message.
    for left indents use >,>>,>>>
    for paragraph usr #,##,### (up to 3)
    this type of markdowns effect the whole line.

    Args:
        formatted_message (str): The formatted message to process.

    Returns:
        tuple: A tuple containing the formatted message, rich text formatting data, and @mention items.
    """

    left_indent_regex = r"(>{1,4})\s(.*?)$"
    paragraph_regex = r"(#{1,4})\s(.*?)$"
    lines = formatted_message.split("\n")
    for i, line in enumerate(lines):
        match = re.findall(left_indent_regex, line)
        for matches in match:
            indent_level, indent_text = matches
            left_indent_range = {
                "text": indent_text,
                "format_type": "LeftIndent",
                "format_attr": len(indent_level) * 20,
            }
            formats.append(left_indent_range)
            # Replace Markdown syntax with plain text
            lines[i] = indent_text

        pp_matches = re.findall(paragraph_regex, line)
        for p_matches in pp_matches:
            heading_level, heading_text = p_matches
            paragraph_range = {
                "text": heading_text,
                "format_type": "paragraph",
                "format_attr": f"h{len(heading_level)}",
            }
            formats.append(paragraph_range)
            lines[i] = heading_text
    formatted_message = "\n".join(lines)
    return formatted_message, formats


def parse_markdown_message(markdown_message: str, at_contact: str = None):
    """
    Parses a Markdown message and extracts formatting information.

    Args:
        markdown_message (str): The Markdown message to parse.
        at_contact (str, optional): The contact to mention. Defaults to None.

    Returns:
        tuple: A tuple containing the formatted message, rich text formatting data, and @mention items.
    """

    formats: List[dict] = []
    at_items = []
    rich_text = []
    formatted_message, formats = process_links(markdown_message, formats)
    formatted_message, formats = process_mentions(formatted_message, formats, at_contact)
    formatted_message, formats = process_background_colors(formatted_message, formats)
    formatted_message, formats = process_font_sizes(formatted_message, formats)
    formatted_message, formats = process_font_colors(formatted_message, formats)
    formatted_message, formats = process_left_indent_and_paragraphs(formatted_message, formats)

    # extract the start,end positions
    for i in range(len(formats)):
        t = formats[i]["text"]
        start_position = formatted_message.find(t)
        end_position = start_position + len(t) - 1
        formats[i]["start_position"] = start_position
        formats[i]["end_position"] = end_position
        if formats[i].get("at_type"):
            at_items.append(formats[i])
        else:
            rich_text.append(formats[i])

    return formatted_message, {"message": formatted_message, "rich_text": rich_text, "at_items": at_items}


def zoom_send_message_command(client, **args) -> CommandResults:
    """
    Send  Zoom chat message
    """
    client = client
    at_contact = args.get("at_contact")
    user_id = args.get("user_id")
    message = args.get("message", "")
    entry_ids = argToList(args.get("entry_ids", []))
    reply_main_message_id = args.get("reply_main_message_id")
    to_channel = args.get("to_channel")
    to_contact = args.get("to_contact")
    is_markdown = args.get("is_markdown", False)

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    url_suffix = f"/chat/users/{user_id}/messages"
    upload_file_url = f"https://file.zoom.us/v2/chat/users/{user_id}/files"
    zoom_file_id: List = []
    for id in entry_ids:
        file_info = demisto.getFilePath(id)
        res = client.zoom_upload_file(upload_file_url, file_info)
        zoom_file_id.append(res.get("id"))

    # check if the text contain markdown to parse and also provide text style arguments
    if is_markdown:
        # check if text have more then 1 mention
        if message.count("@") > 1 and at_contact:
            raise DemistoException(MARKDOWN_EXTRA_MENTIONS)

        message, json_data_all = parse_markdown_message(message, at_contact)
        json_data_all.update(
            {
                "file_ids": zoom_file_id,
                "reply_main_message_id": reply_main_message_id,
                "to_channel": to_channel,
                "to_contact": to_contact,
            }
        )
    else:
        json_data_all = {
            "message": message,
            "file_ids": zoom_file_id,
            "reply_main_message_id": reply_main_message_id,
            "to_channel": to_channel,
            "to_contact": to_contact,
        }
    json_data = remove_None_values_from_dict(json_data_all)

    raw_data = client.zoom_send_message(url_suffix, json_data)
    data = {"Mentioned user": at_contact, "Channel ID ": to_channel, "Message ID": raw_data.get("id"), "Contact": to_contact}

    md = tableToMarkdown("Message", data, removeNull=True)
    return CommandResults(
        outputs_prefix="Zoom.ChatMessage", readable_output=md, outputs_key_field="id", outputs=raw_data, raw_response=raw_data
    )


def zoom_delete_message_command(client, **args) -> CommandResults:
    """
    Delete Zoom chat message
    """
    client = client
    message_id = args.get("message_id")
    to_contact = args.get(
        "to_contact",
    )
    user_id = args.get("user_id")
    to_channel = args.get("to_channel")

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    if to_channel:
        url_suffix = f"/chat/users/{user_id}/messages/{message_id}?to_channel={to_channel}"
    elif to_contact:
        url_suffix = f"/chat/users/{user_id}/messages/{message_id}?to_contact={to_contact}"
    else:
        raise DemistoException(MISSING_ARGUMENT)

    client.zoom_delete_message(url_suffix)
    return CommandResults(
        readable_output=f"Message {message_id} was deleted successfully",
    )


def zoom_update_message_command(client, **args) -> CommandResults:
    """
    Update Zoom chat message
    """
    client = client
    message_id = args.get("message_id")
    to_contact = args.get("to_contact")
    user_id = args.get("user_id")
    to_channel = args.get("to_channel")
    message = args.get("message")
    entry_ids = argToList(args.get("entry_ids", []))

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)

    upload_file_url = f"https://file.zoom.us/v2/chat/users/{user_id}/files"
    zoom_file_id: List = []
    demisto.debug(f"file id args {entry_ids}")
    for id in entry_ids:
        file_info = demisto.getFilePath(id)
        res = client.zoom_upload_file(upload_file_url, file_info)
        zoom_file_id.append(res.get("id"))
    demisto.debug("upload the file without error")

    url_suffix = f"/chat/users/{user_id}/messages/{message_id}"
    json_data = remove_None_values_from_dict(
        {"message": message, "file_ids": zoom_file_id, "to_channel": to_channel, "to_contact": to_contact}
    )

    client.zoom_update_message(url_suffix, json_data)
    return CommandResults(
        readable_output=f"Message {message_id} was successfully updated",
    )


def zoom_delete_user_token_command(client, **args) -> CommandResults:
    """
    Revoke a user's Zoom SSO session
    """
    client = client
    user_id = args.get("user_id")
    url_suffix = f"/users/{user_id}/token"
    client.zoom_delete_user_token(url_suffix)
    return CommandResults(
        readable_output=f"User SSO token for user {user_id} is deleted",
    )


def zoom_get_user_id_by_email(client, email):
    """
    Retrieves the user ID associated with the given email address.

    :param client: The Zoom client object.,
    email: The email address of the user.

    :return: The user ID associated with the email address.
    :rtype: str
    """
    user_url_suffix = f"users/{email}"
    user_id = client.zoom_list_users(page_size=50, url_suffix=user_url_suffix).get("id")
    if not user_id:
        raise DemistoException(USER_NOT_FOUND)
    return user_id


def zoom_get_user_name_by_email(client, user_email):
    """
    Retrieves the user name associated with the given email address.

    :param client: The Zoom client object.
    user_email: The email address of the user.

    :return: The user name associated with the email address.
    :rtype: str
    """
    user_url_suffix = f"users/{user_email}"
    user_name = client.zoom_list_users(page_size=1, url_suffix=user_url_suffix)
    demisto.info(f"user_name: {user_name}")
    if not user_name:
        raise DemistoException(USER_NOT_FOUND)
    user_name = user_name.get("display_name")
    return user_name


def zoom_get_user_email_by_id(client, user_id):
    """
    Retrieves the user email address associated with the given user ID.

    :param client: The Zoom client object.
    user_id: The user ID of the user.

    :return: The email address associated with the user ID.
    :rtype: str
    """
    user_url_suffix = f"users/{user_id}"
    user_email = client.zoom_list_users(page_size=1, url_suffix=user_url_suffix).get("email")
    if not user_email:
        raise DemistoException(USER_NOT_FOUND)
    return user_email


def zoom_list_messages_command(client, **args) -> CommandResults:
    """
    Lists messages from Zoom chat.

    :raises DemistoException: If a required argument is missing.
    """

    client = client
    user_id = args.get("user_id")
    to_contact = args.get("to_contact")
    to_channel = args.get("to_channel")
    date_arg = arg_to_datetime(args.get("date"))
    from_arg = arg_to_datetime(args.get("from"))
    to_arg = arg_to_datetime(args.get("to"))
    date_arg = date_arg.strftime("%Y-%m-%dT%H:%M:%SZ") if date_arg else None
    from_arg = from_arg.strftime("%Y-%m-%dT%H:%M:%SZ") if from_arg else None
    to_arg = to_arg.strftime("%Y-%m-%dT%H:%M:%SZ") if to_arg else None
    include_deleted_and_edited_message = args.get("include_deleted_and_edited_message")
    search_type = args.get("search_type")
    search_key = args.get("search_key")
    exclude_child_message = args.get("exclude_child_message", False)
    limit = arg_to_number(args.get("limit", 50))
    page_size = arg_to_number(args.get("page_size"))

    if limit and page_size and limit != 50:
        raise DemistoException(LIMIT_AND_EXTRA_ARGUMENTS)
    else:
        limit = page_size if page_size else limit

    if not to_contact and not to_channel:
        raise DemistoException(MISSING_ARGUMENT)

    if user_id and re.match(emailRegex, user_id):
        user_id = zoom_get_user_id_by_email(client, user_id)
    json_data = {
        "user_id": user_id,
        "to_contact": to_contact,
        "to_channel": to_channel,
        "date": date_arg,
        "from": from_arg,
        "to": to_arg,
        "include_deleted_and_edited_message": include_deleted_and_edited_message,
        "search_type": search_type,
        "search_key": search_key,
        "exclude_child_message": exclude_child_message,
        "page_size": limit,
    }
    # remove all keys with val of None
    request_data = remove_None_values_from_dict(json_data)
    url_suffix = f"users/{user_id}/messages"
    all_messages: List = []
    next_page_token = args.get("next_page_token", None)
    while True:
        try:
            raw_data = client.zoom_list_user_messages(
                url_suffix=url_suffix,
                user_id=user_id,
                to_contact=to_contact,
                to_channel=to_channel,
                date_arg=date_arg,
                from_arg=from_arg,
                to_arg=to_arg,
                include_deleted_and_edited_message=include_deleted_and_edited_message,
                search_type=search_type,
                search_key=search_key,
                exclude_child_message=exclude_child_message,
                next_page_token=next_page_token,
                page_size=limit,
            )
            data = raw_data.get("messages", [])
            if limit and len(all_messages) + len(data) > limit:
                remaining_limit = limit - len(all_messages)
                data = data[:remaining_limit]

            all_messages.extend(data)

            if limit and len(all_messages) >= limit:
                all_messages = all_messages[:limit]
                break
            next_page_token = raw_data.get("next_page_token", None)
            if next_page_token and next_page_token != "":
                next_page_token = raw_data["next_page_token"]
            else:
                break
        except DemistoException as e:
            error_message = e.message
            if "The next page token is invalid or expired." in error_message and next_page_token:
                raise DemistoException(f"Please ensure that the correct argument values are used when attempting to use \
the next_page_toke.\n Note that when using next_page_token it is mandatory to specify date time and not relative time.\n \
To find the appropriate values, refer to the ChatMessageNextToken located in the context. \n {error_message}")

    outputs = []
    for i in all_messages:
        outputs.append(
            {
                "User id": user_id,
                "Message Id": i.get("id"),
                "Message text": i.get("message"),
                "Message sender": i.get("sender"),
                "Sender display name": i.get("sender_display_name"),
                "Date Time": i.get("date_time"),
                "From": str(from_arg),
                "To": str(to_arg),
            }
        )

    md = tableToMarkdown("Messages", outputs)
    md += "\n" + "Messages next token:" + raw_data.get("next_page_token", "")

    if raw_data.get("next_page_token"):
        request_data.update({"next_page_token": raw_data.get("next_page_token")})
    else:
        request_data = None

    return CommandResults(
        outputs_prefix="Zoom",
        readable_output=md,
        outputs={"ChatMessage": {"messages": all_messages}, "ChatMessageNextToken": request_data},
        raw_response=raw_data,
    )


def get_channel_jid_from_context(channel_name: str = None, investigation_id=None):
    """
    Retrieves a Zoom channel JID based on the provided criteria.

    :param channel_name: The name of the channel to get the JID for.
    :param investigation_id: The Demisto investigation ID to search for a mirrored channel.

    :return: The requested channel JID or None if not found.
    """
    if not channel_name and not investigation_id:
        return None
    integration_context = fetch_context()
    mirrors = json.loads(integration_context.get("mirrors", "[]"))

    # Filter mirrors based on the provided criteria.
    if investigation_id:
        mirrored_channel_filter = next((m for m in mirrors if m["investigation_id"] == investigation_id), None)
    else:
        mirrored_channel_filter = next((m for m in mirrors if m["channel_name"] == channel_name), None)
    if mirrored_channel_filter:
        return mirrored_channel_filter.get("channel_jid")
    return None


def send_notification(client, **args):
    client = client
    bot_jid = client.bot_jid
    account_id = client.account_id
    to = args.get("to")
    channel_id = args.get("channel_id")
    visible_to_user = args.get("visible_to_user")
    zoom_ask = argToBoolean(args.get("zoom_ask", False))
    entitlement = None

    message_type = args.get("messageType", "")  # From server
    original_message = args.get("originalMessage", "")  # From server
    entry_object = args.get("entryObject")  # From server
    channel = args.get("channel")  # From server
    investigation_id = None
    if entry_object:
        investigation_id = entry_object.get("investigationId")  # From server, available from demisto v6.1 and above

    if message_type and message_type != MIRROR_TYPE:
        return f"Message type is not in permitted options. Received: {message_type}"

    if message_type == MIRROR_TYPE and original_message.find(MESSAGE_FOOTER) != -1:
        # return so there will not be a loop of messages
        return "Message already mirrored"

    if to and "@xmpp.zoom.us" not in to:
        if re.match(emailRegex, to):
            to = zoom_get_user_id_by_email(client, to).lower() + "@xmpp.zoom.us"
        else:
            to = to.lower() + "@xmpp.zoom.us"

    if channel_id and "@conference.xmpp.zoom.us" not in channel_id:
        channel_id = channel_id.lower() + "@conference.xmpp.zoom.us"

    if zoom_ask:
        parsed_message = json.loads(args.get("message", ""))
        entitlement = parsed_message.get("entitlement")
        message = parsed_message.get("blocks")
        reply = parsed_message.get("reply")
        expiry = parsed_message.get("expiry")
        default_response = parsed_message.get("default_response")
    else:
        message = {"head": {"type": "message", "text": args.get("message", "")}}
        reply = None
        expiry = None
        default_response = None
    if channel:  # if channel name provided
        channel_id = get_channel_jid_from_context(channel, investigation_id)
        if not channel_id:
            raise DemistoException(WRONG_CHANNEL)
    if to and channel_id:
        raise DemistoException(TOO_MANY_JID)
    if not to and not channel_id:
        raise DemistoException(MISSING_ARGUMENT_JID)

    url_suffix = "/im/chat/messages"
    json_data_all = {
        "robot_jid": bot_jid,
        "to_jid": to if to else channel_id,
        "account_id": account_id,
        "visible_to_user": visible_to_user,
        "content": message,
    }
    json_data = remove_None_values_from_dict(json_data_all)

    raw_data = client.zoom_send_notification(url_suffix, json_data)
    if raw_data:
        message_id = raw_data.get("message_id")
        if entitlement:
            save_entitlement(entitlement, message_id, reply, expiry, default_response, to if to else channel_id)
    return CommandResults(readable_output=f'Message sent to Zoom successfully. Message ID is: {raw_data.get("message_id")}')


def get_admin_user_id_from_token(client):
    """get the user admin id from the token"""
    admin_user_result = client.zoom_get_admin_user_id_from_token()
    return admin_user_result.get("id")


def fetch_context(force_refresh: bool = False) -> dict:
    """
    Fetches the integration instance context from the server if the CACHE_EXPIRY is smaller than the current epoch time
    In the event that the cache is not expired, we return a cached copy of the context which has been stored in memory.
    We can force the retrieval of the updated context by setting the force_refresh flag to True.
    :param force_refresh: bool: Indicates if the context should be refreshed regardless of the expiry time.
    :return: dict: Either a cached copy of the integration context, or the context itself.
    """
    global CACHED_INTEGRATION_CONTEXT, CACHE_EXPIRY
    now = int(datetime.now(timezone.utc).timestamp())
    if (now >= CACHE_EXPIRY) or force_refresh:
        demisto.debug(
            f"Cached context has expired or forced refresh. forced refresh value is {force_refresh}. Fetching new context"
        )
        CACHE_EXPIRY = next_expiry_time()
        CACHED_INTEGRATION_CONTEXT = get_integration_context(SYNC_CONTEXT)

    return CACHED_INTEGRATION_CONTEXT


def mirror_investigation(client, **args) -> CommandResults:
    if not MIRRORING_ENABLED:
        demisto.error(" couldn't mirror investigation, Mirroring is disabled")
    if MIRRORING_ENABLED and not LONG_RUNNING:
        demisto.error(
            "Mirroring is enabled, however long running is disabled. For mirrors to work correctly,"
            " long running must be enabled."
        )
    client = client
    type = args.get("type", "all")
    direction = args.get("direction", "Both")
    channel_name = args.get("channelName")
    autoclose = argToBoolean(args.get("autoclose", True))
    send_first_message = False
    # kick_admin = argToBoolean(args.get('kickAdmin', False))
    # members = argToList(args.get('members'))

    investigation = demisto.investigation()
    investigation_id = str(investigation.get("id"))
    if investigation.get("type") == PLAYGROUND_INVESTIGATION_TYPE:
        return_error("Sorry, but this action cannot be performed in the playground ")

    integration_context = get_integration_context(SYNC_CONTEXT)
    if not integration_context or not integration_context.get("mirrors", []):
        mirrors: list = []
        current_mirror = []
    else:
        mirrors = json.loads(integration_context["mirrors"])
        current_mirror = list(filter(lambda m: m["investigation_id"] == investigation_id, mirrors))

    # get admin user id from token
    admin_user_id = get_admin_user_id_from_token(client)

    channel_filter: list = []
    if channel_name:
        # check if channel already exists
        channel_filter = list(filter(lambda m: m["channel_name"] == channel_name, mirrors))

    if not current_mirror:
        channel_name = channel_name or f"incident-{investigation_id}"

        if not channel_filter:
            # create new channel
            result = zoom_create_channel_command(
                client=client,
                user_id=admin_user_id,
                channel_type="Public channel",
                channel_name=channel_name,
                member_emails=admin_user_id,
            )
            if result and isinstance(result.outputs, dict) and len(result.outputs) > 0:
                channel_jid = result.outputs.get("jid")
                channel_id = result.outputs.get("id")
            else:
                raise DemistoException("error in create new channel")
            send_first_message = True
        else:
            mirrored_channel = channel_filter[0]
            channel_jid = mirrored_channel["channel_jid"]
            channel_id = mirrored_channel["channel_id"]
            channel_name = mirrored_channel["channel_name"]

        mirror = {
            "channel_jid": channel_jid,
            "channel_id": channel_id,
            "channel_name": channel_name,
            "investigation_id": investigation.get("id"),
            "mirror_type": type,
            "mirror_direction": direction,
            "auto_close": bool(autoclose),
            "mirrored": True,
        }
    else:
        mirror = mirrors.pop(mirrors.index(current_mirror[0]))
        channel_jid = mirror["channel_jid"]
        channel_id = mirror["channel_id"]
        if type:
            mirror["mirror_type"] = type
        if autoclose:
            mirror["auto_close"] = autoclose
        if direction:
            mirror["mirror_direction"] = direction
        if channel_name and not channel_filter:
            # update channel name
            result = zoom_update_channel_command(
                client=client, user_id=admin_user_id, channel_name=channel_name, channel_id=channel_id
            )
            mirror["channel_name"] = channel_name
        channel_name = mirror["channel_name"]
        mirror["mirrored"] = True
    demisto.mirrorInvestigation(investigation_id, f"{type}:{direction}", autoclose)

    mirrors.append(mirror)
    set_to_integration_context_with_retries({"mirrors": mirrors}, OBJECTS_TO_KEYS, SYNC_CONTEXT)

    if send_first_message:
        server_links = demisto.demistoUrls()
        server_link = server_links.get("server")
        message = (
            f"This channel was created to mirror incident {investigation_id}."
            f" \n View it on: {server_link}#/WarRoom/{investigation_id}"
        )
        url_suffix = "/im/chat/messages"
        json_data_all = {
            "content": {"body": [{"type": "message", "text": message}]},
            "to_jid": channel_jid,
            "robot_jid": client.bot_jid,
            "account_id": client.account_id,
        }
        client.zoom_send_notification(url_suffix, json_data_all)
    # if kick_admin:
    #     demisto.debug("kick-admin:")
    #     url_suffix = f'/chat/users/{admin_user_id}/channels/{channel_id}/members/{admin_user_id}'
    #     res = client.zoom_remove_from_channel(url_suffix)
    #     demisto.debug(f"res: {res}")
    return CommandResults(
        readable_output=f"Investigation mirrored successfully,\n channel name:{channel_name} \n channel JID: {channel_jid}"
    )


def find_mirror_by_investigation() -> dict:
    """
    Finds a mirrored channel by the mirrored investigation

    Returns:
        The mirror object
    """
    mirror: dict = {}
    investigation = demisto.investigation()
    if investigation:
        integration_context = get_integration_context(SYNC_CONTEXT)
        if "mirrors" in integration_context:
            mirrors = json.loads(integration_context["mirrors"])
            investigation_filter = list(filter(lambda m: investigation.get("id") == m["investigation_id"], mirrors))
            if investigation_filter:
                mirror = investigation_filter[0]

    return mirror


def close_channel(client, **args):
    """
    if AutoClose is true in mirroring
    delete the mirrored zoom channel from zoom and from the context
    """
    client = client
    admin_user_id = get_admin_user_id_from_token(client)
    mirror = find_mirror_by_investigation()
    channel_id = None
    integration_context = get_integration_context(SYNC_CONTEXT)
    if mirror:
        mirrors = json.loads(integration_context["mirrors"])
        channel_id = mirror.get("channel_id", "")
        channel_mirrors = list(filter(lambda m: channel_id == m["channel_id"], mirrors))
        for mirror in channel_mirrors:
            mirror["remove"] = True
            demisto.mirrorInvestigation(
                mirror["investigation_id"], f'none:{mirror["mirror_direction"]}', bool(mirror["auto_close"])
            )
        set_to_integration_context_with_retries({"mirrors": mirrors}, OBJECTS_TO_KEYS, SYNC_CONTEXT)
    if channel_id:
        zoom_delete_channel_command(client=client, channel_id=channel_id, user_id=admin_user_id)
        return "Channel successfully deleted."
    return return_error("Channel {channel_id} not found")


def main():  # pragma: no cover
    params = demisto.params()
    args = demisto.args()
    base_url = params.get("url")
    account_id = params.get("account_id")
    client_id = params.get("credentials", {}).get("identifier")
    client_secret = params.get("credentials", {}).get("password")
    bot_client_id = params.get("bot_credentials", {}).get("identifier")
    bot_client_secret = params.get("bot_credentials", {}).get("password")
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    bot_jid = params.get("botJID", None)
    secret_token = params.get("secret_token", {}).get("password")
    global SECRET_TOKEN, LONG_RUNNING, MIRRORING_ENABLED, CACHE_EXPIRY, CACHED_INTEGRATION_CONTEXT
    SECRET_TOKEN = secret_token
    LONG_RUNNING = params.get("longRunning", False)
    MIRRORING_ENABLED = params.get("mirroring", False)

    # Pull initial Cached context and set the Expiry
    CACHE_EXPIRY = next_expiry_time()
    CACHED_INTEGRATION_CONTEXT = get_integration_context(SYNC_CONTEXT)

    if MIRRORING_ENABLED and (not LONG_RUNNING or not SECRET_TOKEN or not bot_client_id or not bot_client_secret or not bot_jid):
        raise DemistoException("""Mirroring is enabled, however long running is disabled
or the necessary bot authentication parameters are missing.
For mirrors to work correctly, long running must be enabled and you must provide all
the zoom-bot following parameters:
secret token,
Bot JID,
bot client id and secret id""")
    if LONG_RUNNING:
        try:
            port = int(params.get("longRunningPort"))
        except ValueError as e:
            raise ValueError(f"Invalid listen port - {e}")
    else:
        port = 0
        demisto.debug(f"Not a longrunning, setting {port=}")

    command = demisto.command()
    # this is to avoid BC. because some of the arguments given as <a-b>, i.e "user-list"
    args = {key.replace("-", "_"): val for key, val in args.items()}

    try:
        check_authentication_bot_parameters(bot_jid, bot_client_id, bot_client_secret)
        check_authentication_parameters(client_id, client_secret)
        global CLIENT
        client = Client(
            base_url=base_url,
            verify=verify_certificate,
            proxy=proxy,
            bot_jid=bot_jid,
            account_id=account_id,
            client_id=client_id,
            client_secret=client_secret,
            bot_client_id=bot_client_id,
            bot_client_secret=bot_client_secret,
        )
        CLIENT = client
        results = CommandResults()

        if command == "test-module":
            return_results(test_module(client=client))

        demisto.debug(f"Command being called is {command}")

        """CRUD commands"""
        if command == "long-running-execution":
            run_long_running(port)
        elif command == "mirror-investigation":
            results = mirror_investigation(client, **args)
        elif command == "close-channel":
            results = close_channel(client, **args)
        elif command == "zoom-create-user":
            results = zoom_create_user_command(client, **args)
        elif command == "zoom-create-meeting":
            results = zoom_create_meeting_command(client, **args)
        elif command == "zoom-meeting-get":
            results = zoom_meeting_get_command(client, **args)
        elif command == "zoom-meeting-list":
            results = zoom_meeting_list_command(client, **args)
        elif command == "zoom-delete-user":
            results = zoom_delete_user_command(client, **args)
        elif command == "zoom-fetch-recording":
            results = zoom_fetch_recording_command(client, **args)
        elif command == "zoom-list-users":
            results = zoom_list_users_command(client, **args)
        elif command == "zoom-list-account-public-channels":
            results = zoom_list_account_public_channels_command(client, **args)
        elif command == "zoom-list-user-channels":
            results = zoom_list_user_channels_command(client, **args)
        elif command == "zoom-create-channel":
            results = zoom_create_channel_command(client, **args)
        elif command == "zoom-delete-channel":
            results = zoom_delete_channel_command(client, **args)
        elif command == "zoom-update-channel":
            results = zoom_update_channel_command(client, **args)
        elif command == "zoom-invite-to-channel":
            results = zoom_invite_to_channel_command(client, **args)
        elif command == "zoom-remove-from-channel":
            results = zoom_remove_from_channel_command(client, **args)
        elif command == "zoom-send-file":
            results = zoom_send_file_command(client, **args)
        elif command == "zoom-list-messages":
            results = zoom_list_messages_command(client, **args)
        elif command == "zoom-send-message":
            results = zoom_send_message_command(client, **args)
        elif command == "zoom-delete-message":
            results = zoom_delete_message_command(client, **args)
        elif command == "zoom-update-message":
            results = zoom_update_message_command(client, **args)
        elif command == "zoom-delete-user-token":
            results = zoom_delete_user_token_command(client, **args)
        elif command == "send-notification":
            results = send_notification(client, **args)

        else:
            return_error("Unrecognized command: " + demisto.command())
        return_results(results)

    except DemistoException as e:
        # For any other integration command exception, return an error
        demisto.error(format_exc())
        return_error(f"Failed to execute {command} command. Error: {e!s}.")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()