5
5
6
6
A bot must be created and pointed to this server in the My Apps section of
7
7
https://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
9
9
script.
10
10
11
11
This script must expose a public IP address in order to receive notifications
29
29
to a user submitting a form, the details of that response will be posted in
30
30
the space.
31
31
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.
34
33
35
34
Copyright (c) 2016-2020 Cisco and/or its affiliates.
36
35
54
53
"""
55
54
56
55
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
65
59
60
+ from flask import Flask , request
66
61
62
+ from webexteamssdk import WebexTeamsAPI , Webhook
63
+
64
+
65
+ # Script metadata
67
66
__author__ = "JP Shipherd"
68
67
__author_email__ = "jshipher@cisco.com"
69
68
__contributors__ = ["Chris Lunsford <chrlunsf@cisco.com>" ]
70
69
__copyright__ = "Copyright (c) 2016-2020 Cisco and/or its affiliates."
71
70
__license__ = "MIT"
72
71
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
85
72
86
73
# Constants
87
74
WEBHOOK_NAME = "botWithCardExampleWebhook"
94
81
# Adaptive Card Design Schema for a sample form.
95
82
# To learn more about designing and working with buttons and cards,
96
83
# checkout https://developer.webex.com/docs/api/guides/cards
97
- card_content = {
84
+ CARD_CONTENT = {
98
85
"$schema" : "http://adaptivecards.io/schemas/adaptive-card.json" ,
99
86
"type" : "AdaptiveCard" ,
100
- "version" : "1.0 " ,
87
+ "version" : "1.1 " ,
101
88
"body" : [
102
89
{
103
90
"type" : "TextBlock" ,
107
94
},
108
95
{
109
96
"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." ,
113
101
"wrap" : True
114
102
},
115
103
{
121
109
},
122
110
{
123
111
"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." ,
127
115
"wrap" : True
128
116
},
129
117
{
135
123
},
136
124
{
137
125
"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`." ,
142
132
"wrap" : True
143
133
},
144
134
{
179
169
]
180
170
}
181
171
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 ()
199
186
200
187
# Initialize the environment
201
188
# Create the web application instance
202
189
flask_app = Flask (__name__ )
203
190
# Create the Webex Teams API connection object
204
191
api = WebexTeamsAPI ()
192
+ # Get the details for the account who's access token we are using
193
+ me = api .people .me ()
205
194
206
195
207
196
# 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 ."""
210
199
for webhook in api .webhooks .list ():
211
200
if webhook .name == WEBHOOK_NAME :
212
201
print ("Deleting Webhook:" , webhook .name , webhook .targetUrl )
213
202
api .webhooks .delete (webhook .id )
214
203
215
- def create_webhooks (api , webhook_url ):
204
+
205
+ def create_webhooks (webhook_url ):
216
206
"""Create the Webex Teams webhooks we need for our bot."""
217
207
print ("Creating Message Created Webhook..." )
218
208
webhook = api .webhooks .create (
@@ -234,97 +224,114 @@ def create_webhooks(api, webhook_url):
234
224
print (webhook )
235
225
print ("Webhook successfully created." )
236
226
237
- def respond_to_button_press (api , webhook ):
227
+
228
+ def respond_to_button_press (webhook ):
238
229
"""Respond to a button press on the card we posted"""
239
230
240
231
# Some server side debugging
241
232
room = api .rooms .get (webhook .data .roomId )
242
233
attachment_action = api .attachment_actions .get (webhook .data .id )
243
234
person = api .people .get (attachment_action .personId )
244
235
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
+ )
247
242
248
243
api .messages .create (
249
244
room .id ,
250
245
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 ```"
252
249
)
253
250
254
- def respond_to_message (api , webhook ):
251
+
252
+ def respond_to_message (webhook ):
255
253
"""Respond to a message to our bot"""
256
254
257
255
# Some server side debugging
258
256
room = api .rooms .get (webhook .data .roomId )
259
257
message = api .messages .get (webhook .data .id )
260
258
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
+ )
264
266
265
267
# This is a VERY IMPORTANT loop prevention control step.
266
268
# If you respond to all messages... You will respond to the messages
267
269
# that the bot posts and thereby create a loop condition.
268
- me = api .people .me ()
269
270
if message .personId == me .id :
270
271
# Message was sent by me (bot); do not respond.
271
- return 'OK'
272
+ return "OK"
272
273
273
274
else :
274
275
# 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
+ )
276
280
api .messages .create (
277
281
room .id ,
278
282
text = "If you see this your client cannot render cards" ,
279
283
attachments = [{
280
284
"contentType" : "application/vnd.microsoft.card.adaptive" ,
281
- "content" : card_content
282
- }]
285
+ "content" : CARD_CONTENT
286
+ }],
283
287
)
284
- return 'OK'
288
+ return "OK"
285
289
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 )
292
290
293
291
# Core bot functionality
294
292
# Webex will post to this server when a message is created for the bot
295
293
# or when a user clicks on an Action.Submit button in a card posted by this bot
296
294
# 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" ])
298
296
def webex_teams_webhook_events ():
299
297
"""Respond to inbound webhook JSON HTTP POST from Webex Teams."""
300
298
# Create a Webhook object from the JSON data
301
299
webhook_obj = Webhook (request .json )
302
300
303
301
# 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 )
307
305
308
306
# 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 )
312
310
313
311
# Ignore anything else (which should never happen
314
312
else :
315
- print ("IGNORING UNEXPECTED WEBHOOK:" )
316
- print (webhook_obj )
313
+ print (f"IGNORING UNEXPECTED WEBHOOK:\n { webhook_obj } " )
317
314
318
- return 'OK'
315
+ return "OK"
319
316
320
317
321
318
def 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__" :
330
337
main ()
0 commit comments