wxc_cc_bot module

A simple bot demonstrating some of the capabilities of the Webex Calling call control APIs

The “magic” happens in CallControlBot


wxc_cc_bot.catch_exception(f)[source]

Decorator to catch and log exceptions which led to termination of a thread

class wxc_cc_bot.CallControlBot(*, teams_bot_name: str, teams_bot_token: str, teams_bot_email: str, teams_bot_url: str, client_id: str, client_secret: str, client_scopes: str, client_redirect_url: str, debug=False)[source]

Bases: TeamsBot

The call control demo bot

Parameters:
  • teams_bot_name – Friendly name for the Bot (webhook name). Bot parameters are obtained from https://developer.webex.com/my-apps/wxcc-bot when creating the bot

  • teams_bot_token – Teams Auth Token for Bot Account

  • teams_bot_email – Teams Bot Email Address

  • teams_bot_url – WebHook URL for this Bot

The teams_bot_* parameters are used to initialize the webexteamsbot.TeamsBot base class.

Parameters:
  • client_id – client ID of the integration the Bot uses to obtain tokens to act on behalf of a user. Integration parameters are obtained from https://developer.webex.com/my-apps/wxcc-bot when creating the integration.

  • client_secret – client secret of the integration the Bot uses to obtain tokens to act on behalf of a user

  • client_scopes – scopes of the integration the Bot uses to obtain tokens to act on behalf of a user

  • client_redirect_url – redirect URL of the integration the Bot uses to obtain tokens to act on behalf of a user

  • debug – debug mode

During initialization some bot commands are registered using the webexteamsbot.add_command() method.

Command

Handler

Description

/auth

auth_callback()

Authenticate current user if needed or print authentication state.

/monitor

monitor_callback()

Enable or disable monitoring von telephony_call events for the current user. Events are echoed to the chat.

/dial

dial_callback()

Dial a destination for the current user. Uses wxc_sdk.telephony.calls.CallsApi.dial()

/answer

answer_callback()

Answer an incoming call for the current user. Uses wxc_sdk.telephony.calls.CallsApi.list_calls() and wxc_sdk.telephony.calls.CallsApi.answer()

/hangup

hangup_callback()

Hang up an active call. Uses wxc_sdk.telephony.calls.CallsApi.hangup()

/history

call_history_callback()

Show call history for current user. Uses wxc_sdk.telephony.calls.CallsApi.call_history()

/redis

redis_callback()

Interact with Redis data store. This command is only available if the Bot is using the Redis integration via a user_context.RedisTokenManager token manager.

The bot needs to persist state (access and refresh tokens) for each user so that users don’t need to reauthenticate every time the bot has been restarted. This per user state is stored in user_context.UserContext objects which are managed by a user_context.TokenManager. During initialization the bot creates either a user_context.YAMLTokenManager (persist state in local yaml file) or a user_context.RedisTokenManager (persist user state in Redis) for token management and handling of OAuth authentication flows. A user_context.RedisTokenManager is only used if the bot is running in an environment with a running Redis server. This is determined by checking the REDIS_HOST, REDIS_TLS_URL, and REDIS_URL environment variables.

The /redirect endpoint under the bot base url (for example http://localhost:6001/redirect) is registered with the flask.Flask base class of CallControlBot. This endpoint is used as redirect URL for the OAuth authorization flows to obtain access tokens for the bot users. The registration is done by calling user_context.TokenManager.register_redirect().

Users can interact with the Bot in 1:1 spaces using above commands, In the registered command handlers if an API call is needed to serve the user request then the 1st check is whether a user context exists for the user the message was received from:

user_context = self._token_manager.get_user_context_and_refresh(user_id=message.personId)
if user_context is None or not user_context.tokens.access_token:
    return f'User {message.personEmail} not authenticated. Use /auth command first'

These user contexts are created as the result of a successful user authorization flow which is initiated in the handler of the /auth command: auth_callback() like this:

# to initiate authentication we need a new flow id, then have to create an authorization URL, and finally
# share the authorization URL with the user so that the user can initiate the authorization flow using
# that link

# register auth flow and get flow id
flow_id = self._token_manager.start_flow(user_id=user_id)

# get auth URL and share URL with user
auth_url = self._integration.auth_url(state=flow_id)
self.teams.messages.create(toPersonEmail=user_email,
                           markdown=f'Click this [link]({auth_url}) to authenticate ({flow_id})')

A flow is started, an authorization URL is built and this URL is then presented to the user in a 1:1 message. At the end of the flow an authorization code is passed back via an HTTP GEt on teh redirect URL of the integration. This GET is served by either user_context.RedisTokenManager.process_redirect() or user_context.YAMLTokenManager.process_redirect() depending on whether the backend to persist state is a local YAML file or redis. In botch cases first a check is executed whether for the state valued passed in the IRL a flow exists. If that’s the case then a new set of tokens is executed. The final check then gets the user details for the authenticated user and then checks whether the tokens belong to the user who initiated the OAuth flow. If all goes well then the tokens (access and refresh token) are stored in the user context for the user.

call_event_url(user_id: str) str[source]

User specific call event URL: /callevent/{user_id}

This URL is used as target URL when creating a webhook for call events of a given user in monitor_callback()

Parameters:

user_id (str) – user ID to create a telephony_call webhook events URL for.

Returns:

generated URL

Return type:

str

call_event(user_id: str)[source]

This is the view function that is called by Flask when a POST on the call event URL needs to be handled. This endpoint is used as target URL when creating a webhook for call events of a given user.

The demo bot simply tries to deserialize the telephony_call event and then send a message to the user containing the JSON representation of the telephone_call event.

Parameters:

user_id (str) – user id, passed as parameter in the request URL

auth_callback(message: Message) str[source]

handler for /auth command.

The handler supports:
  • /auth: check if the user has been authenticated. Get authorization URL if needed, else display existing access token validity

  • /auth clear: clear existing user context for current user

  • /auth force: force new authentication of current user even if the bot already has tokens for the current user.

  • /auth maintenance: force user_context.RedisTokenManager.flow_maintenance(). This cleans up all dangling OAuth authorization flows: flows which have been initiated but never completed.

    This command only makes sense if the bot is using a user_context.RedisTokenManager.

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

monitor_callback(message: Message)[source]

handler for /monitor command

The handler supports:
  • /montor on: turn telephony_calls event monitoring on

  • /montor off: turn telephony_calls event monitoring off

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

To turn monitoring on a webhook for telephony_calls events is created for the current user. The URL for the webhook (destination for the POST from Webex) is user specific and created using call_event_url().

dial_callback(message: Message)[source]

handler for /dial command

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

answer_callback(message: Message)[source]

handler for /answer command

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

hangup_callback(message: Message)[source]

handler for /hangup command

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

redis_callback(message: Message)[source]

handler for /redis command

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

call_history_callback(message: Message) str[source]

handler for /history command

Parameters:

message (webexteamssdk.Message) – The message from the user in the 1:1 space with the bot

Returns:

response to user

Return type:

str

wxc_cc_bot.create_app() CallControlBot[source]

Create a CallControlBot instance.

Returns:

the bot instance; subclass of flask.Flask

Return type:

CallControlBot

All parameters to create the bot are read from environment variables:

Environment variable

Description

WXC_CC_BOT_EMAIL

bot email

WXC_CC_BOT_ACCESS_TOKEN

bot access token

WXC_CC_BOT_NAME

bot name

WXC_CC_INTEGRATION_CLIENT_ID

client id for integration that is used to obtain tokens to act on behalf of the user that is interacting with the bot.

WXC_CC_INTEGRATION_CLIENT_SECRET

client secret for integration that is used to obtain tokens to act on behalf of the user that is interacting with the bot.

WXC_CC_INTEGRATION_CLIENT_SCOPES

scopes for integration that is used to obtain tokens to act on behalf of the user that is interacting with the bot. Spoce separated list of scopes.

The scripts reads .env from the current directory. This file can be used to set all these variables. A template (.env (sample)) exists in the project directory:

# rename this to .env and set values

# client ID for integration to be used.
# required scopes: spark:calls_write spark:kms spark:calls_read spark-admin:telephony_config_read spark:people_read
WXC_CC_INTEGRATION_CLIENT_ID=

# client secret of integration
WXC_CC_INTEGRATION_CLIENT_SECRET=

# scopes to request. Use these scopes when creating the integration at developer.webex.com
WXC_CC_INTEGRATION_CLIENT_SCOPES="spark:calls_write spark:kms spark:calls_read spark-admin:telephony_config_read spark:people_read"

# bot email address
WXC_CC_BOT_EMAIL=

# bot access token
WXC_CC_BOT_ACCESS_TOKEN=

# bot name
WXC_CC_BOT_NAME=

create_app() is used in the Procfile when deploying to Heroku:

web: gunicorn 'wxc_cc_bot:create_app()' --workers=1 --log-file -