55
66A bot must be created and pointed to this server in the My Apps section of
77https://developer.webex.com. The bot's Access Token should be added as a
8- ' WEBEX_TEAMS_ACCESS_TOKEN' environment variable on the web server hosting this
8+ " WEBEX_TEAMS_ACCESS_TOKEN" environment variable on the web server hosting this
99script.
1010
1111This script must expose a public IP address in order to receive notifications
2929to a user submitting a form, the details of that response will be posted in
3030the space.
3131
32- This script should supports Python versions 2 and 3, but it has only been
33- tested with version 3.
32+ This script should support Python versions 3 only.
3433
3534Copyright (c) 2016-2020 Cisco and/or its affiliates.
3635
5453"""
5554
5655
57- # Use future for Python v2 and v3 compatibility
58- from __future__ import (
59- absolute_import ,
60- division ,
61- print_function ,
62- unicode_literals ,
63- )
64- from builtins import *
56+ import os
57+ import sys
58+ from urllib .parse import urljoin
6559
60+ from flask import Flask , request
6661
62+ from webexteamssdk import WebexTeamsAPI , Webhook
63+
64+
65+ # Script metadata
6766__author__ = "JP Shipherd"
6867__author_email__ = "jshipher@cisco.com"
6968__contributors__ = ["Chris Lunsford <chrlunsf@cisco.com>" ]
7069__copyright__ = "Copyright (c) 2016-2020 Cisco and/or its affiliates."
7170__license__ = "MIT"
7271
73- from flask import Flask , request
74- from signal import signal , SIGINT
75- import requests
76- import sys
77-
78- from webexteamssdk import WebexTeamsAPI , Webhook
79-
80- # Find and import urljoin
81- if sys .version_info [0 ] < 3 :
82- from urlparse import urljoin
83- else :
84- from urllib .parse import urljoin
8572
8673# Constants
8774WEBHOOK_NAME = "botWithCardExampleWebhook"
9481# Adaptive Card Design Schema for a sample form.
9582# To learn more about designing and working with buttons and cards,
9683# checkout https://developer.webex.com/docs/api/guides/cards
97- card_content = {
84+ CARD_CONTENT = {
9885 "$schema" : "http://adaptivecards.io/schemas/adaptive-card.json" ,
9986 "type" : "AdaptiveCard" ,
100- "version" : "1.0 " ,
87+ "version" : "1.1 " ,
10188 "body" : [
10289 {
10390 "type" : "TextBlock" ,
10794 },
10895 {
10996 "type" : "TextBlock" ,
110- "text" : "This **Input.Text** element collects some free from text. \
111- Designers can use attributes like `isMutiline`, `maxLength` and `placeholder` \
112- to shape the way that users enter text in a form." ,
97+ "text" : "This **Input.Text** element collects some free from "
98+ "text. Designers can use attributes like `isMutiline`, "
99+ "`maxLength` and `placeholder to shape the way that users "
100+ "enter text in a form." ,
113101 "wrap" : True
114102 },
115103 {
121109 },
122110 {
123111 "type" : "TextBlock" ,
124- "text" : "This **Input.Number** element collects a number. \
125- Designers can use the `max`, `min` and `placeholder` attributes \
126- to control the input options." ,
112+ "text" : "This **Input.Number** element collects a number. "
113+ " Designers can use the `max`, `min` and `placeholder` "
114+ "attributes to control the input options." ,
127115 "wrap" : True
128116 },
129117 {
135123 },
136124 {
137125 "type" : "TextBlock" ,
138- "text" : "The **Input.ChoiceSet** element provides a variety of ways that users \
139- can choose from a set of options. This is the default view, but designers can \
140- use the `style` and `isMutiSelect` attributes to change the way it works. \
141- The choices are defined in an array attribute called `choices`." ,
126+ "text" : "The **Input.ChoiceSet** element provides a variety of "
127+ "ways that users can choose from a set of options. This "
128+ "is the default view, but designers can use the `style` "
129+ "and `isMutiSelect` attributes to change the way it "
130+ "works. The choices are defined in an array attribute "
131+ "called `choices`." ,
142132 "wrap" : True
143133 },
144134 {
179169 ]
180170}
181171
182- # Read required environment variables
183- import os
184- port = 0
185- webhook_url = ""
186- try :
187- webhook_url = os .environ ['WEBHOOK_URL' ]
188- port = int (os .environ ['PORT' ])
189- os .environ ['WEBEX_TEAMS_ACCESS_TOKEN' ]
190- except KeyError :
191- print ('''
192- Missing required environment variable. You must set:
193- * WEBEX_TEAMS_ACCESS_TOKEN -- Access token for a Webex bot\n
194- * WEBHOOK_URL -- URL for Webex Webhooks (ie: https://2fXX9c.ngrok.io)
195- * PORT - Port for Webhook URL (ie: the port param passed to ngrok)
196- '''
197- )
198- sys .exit
172+
173+ # Module variables
174+ webhook_url = os .environ .get ("WEBHOOK_URL" , "" )
175+ port = int (os .environ .get ("PORT" , 0 ))
176+ access_token = os .environ .get ("WEBEX_TEAMS_ACCESS_TOKEN" , "" )
177+ if not all ((webhook_url , port , access_token )):
178+ print (
179+ """Missing required environment variable. You must set:
180+ * WEBHOOK_URL -- URL for Webex Webhooks (ie: https://2fXX9c.ngrok.io)
181+ * PORT - Port for Webhook URL (ie: the port param passed to ngrok)
182+ * WEBEX_TEAMS_ACCESS_TOKEN -- Access token for a Webex bot
183+ """
184+ )
185+ sys .exit ()
199186
200187# Initialize the environment
201188# Create the web application instance
202189flask_app = Flask (__name__ )
203190# Create the Webex Teams API connection object
204191api = WebexTeamsAPI ()
192+ # Get the details for the account who's access token we are using
193+ me = api .people .me ()
205194
206195
207196# Helper functions
208- def delete_webhooks_with_name (api ):
209- """List all webhooks and delete ours ."""
197+ def delete_webhooks_with_name ():
198+ """List all webhooks and delete webhooks created by this script ."""
210199 for webhook in api .webhooks .list ():
211200 if webhook .name == WEBHOOK_NAME :
212201 print ("Deleting Webhook:" , webhook .name , webhook .targetUrl )
213202 api .webhooks .delete (webhook .id )
214203
215- def create_webhooks (api , webhook_url ):
204+
205+ def create_webhooks (webhook_url ):
216206 """Create the Webex Teams webhooks we need for our bot."""
217207 print ("Creating Message Created Webhook..." )
218208 webhook = api .webhooks .create (
@@ -234,97 +224,114 @@ def create_webhooks(api, webhook_url):
234224 print (webhook )
235225 print ("Webhook successfully created." )
236226
237- def respond_to_button_press (api , webhook ):
227+
228+ def respond_to_button_press (webhook ):
238229 """Respond to a button press on the card we posted"""
239230
240231 # Some server side debugging
241232 room = api .rooms .get (webhook .data .roomId )
242233 attachment_action = api .attachment_actions .get (webhook .data .id )
243234 person = api .people .get (attachment_action .personId )
244235 message_id = attachment_action .messageId
245- print ("NEW BUTTON PRESS IN ROOM '{}'" .format (room .title ))
246- print ("FROM '{}'" .format (person .displayName ))
236+ print (
237+ f"""
238+ NEW BUTTON PRESS IN ROOM '{ room .title } '
239+ FROM '{ person .displayName } '
240+ """
241+ )
247242
248243 api .messages .create (
249244 room .id ,
250245 parentId = message_id ,
251- markdown = f'This is the data sent from the button press. A more robust app would do something cool with this:\n ```\n { attachment_action .to_json (indent = 2 )} \n ```'
246+ markdown = f"This is the data sent from the button press. A more "
247+ f"robust app would do something cool with this:\n "
248+ f"```\n { attachment_action .to_json (indent = 2 )} \n ```"
252249 )
253250
254- def respond_to_message (api , webhook ):
251+
252+ def respond_to_message (webhook ):
255253 """Respond to a message to our bot"""
256254
257255 # Some server side debugging
258256 room = api .rooms .get (webhook .data .roomId )
259257 message = api .messages .get (webhook .data .id )
260258 person = api .people .get (message .personId )
261- print ("NEW MESSAGE IN ROOM '{}'" .format (room .title ))
262- print ("FROM '{}'" .format (person .displayName ))
263- print ("MESSAGE '{}'\n " .format (message .text ))
259+ print (
260+ f"""
261+ NEW MESSAGE IN ROOM '{ room .title } '
262+ FROM '{ person .displayName } '
263+ MESSAGE '{ message .text } '
264+ """
265+ )
264266
265267 # This is a VERY IMPORTANT loop prevention control step.
266268 # If you respond to all messages... You will respond to the messages
267269 # that the bot posts and thereby create a loop condition.
268- me = api .people .me ()
269270 if message .personId == me .id :
270271 # Message was sent by me (bot); do not respond.
271- return 'OK'
272+ return "OK"
272273
273274 else :
274275 # Message was sent by someone else; parse message and respond.
275- api .messages .create (room .id , text = "All I do is post a sample card. Here it is:" )
276+ api .messages .create (
277+ room .id ,
278+ text = "All I do is post a sample card. Here it is:" ,
279+ )
276280 api .messages .create (
277281 room .id ,
278282 text = "If you see this your client cannot render cards" ,
279283 attachments = [{
280284 "contentType" : "application/vnd.microsoft.card.adaptive" ,
281- "content" : card_content
282- }]
285+ "content" : CARD_CONTENT
286+ }],
283287 )
284- return 'OK'
288+ return "OK"
285289
286- # Signal handler to clean up webhooks when we shutdown
287- def signal_handler (sig , frame ):
288- """Cleanup webhooks on shutdown"""
289- print ('You pressed Ctrl+C! Cleaning up webhooks...' )
290- delete_webhooks_with_name (api )
291- sys .exit (0 )
292290
293291# Core bot functionality
294292# Webex will post to this server when a message is created for the bot
295293# or when a user clicks on an Action.Submit button in a card posted by this bot
296294# Your Webex Teams webhook should point to http://<serverip>:<port>/events
297- @flask_app .route (' /events' , methods = ["POST" ])
295+ @flask_app .route (" /events" , methods = ["POST" ])
298296def webex_teams_webhook_events ():
299297 """Respond to inbound webhook JSON HTTP POST from Webex Teams."""
300298 # Create a Webhook object from the JSON data
301299 webhook_obj = Webhook (request .json )
302300
303301 # Handle a new message event
304- if webhook_obj .resource == MESSAGE_WEBHOOK_RESOURCE and \
305- webhook_obj .event == MESSAGE_WEBHOOK_EVENT :
306- respond_to_message (api , webhook_obj )
302+ if ( webhook_obj .resource == MESSAGE_WEBHOOK_RESOURCE
303+ and webhook_obj .event == MESSAGE_WEBHOOK_EVENT ) :
304+ respond_to_message (webhook_obj )
307305
308306 # Handle an Action.Submit button press event
309- elif webhook_obj .resource == CARDS_WEBHOOK_RESOURCE and \
310- webhook_obj .event == CARDS_WEBHOOK_EVENT :
311- respond_to_button_press (api , webhook_obj )
307+ elif ( webhook_obj .resource == CARDS_WEBHOOK_RESOURCE
308+ and webhook_obj .event == CARDS_WEBHOOK_EVENT ) :
309+ respond_to_button_press (webhook_obj )
312310
313311 # Ignore anything else (which should never happen
314312 else :
315- print ("IGNORING UNEXPECTED WEBHOOK:" )
316- print (webhook_obj )
313+ print (f"IGNORING UNEXPECTED WEBHOOK:\n { webhook_obj } " )
317314
318- return 'OK'
315+ return "OK"
319316
320317
321318def main ():
322- # Tell Python to run the handler() function when SIGINT is recieved
323- signal (SIGINT , signal_handler )
324- delete_webhooks_with_name (api )
325- create_webhooks (api , webhook_url )
326- # Start the Flask web server
327- flask_app .run (host = '0.0.0.0' , port = port )
328-
329- if __name__ == '__main__' :
319+ # Delete preexisting webhooks created by this script
320+ delete_webhooks_with_name ()
321+
322+ create_webhooks (webhook_url )
323+
324+ try :
325+ # Start the Flask web server
326+ flask_app .run (host = "0.0.0.0" , port = port )
327+
328+ except KeyboardInterrupt :
329+ print ("You pressed Ctrl+C! Shutting down." )
330+
331+ finally :
332+ print ("Cleaning up webhooks..." )
333+ delete_webhooks_with_name ()
334+
335+
336+ if __name__ == "__main__" :
330337 main ()
0 commit comments