webex-bot 0.4.1__py2.py3-none-any.whl → 0.4.6__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webex_bot/__init__.py +1 -1
- webex_bot/commands/help.py +3 -2
- webex_bot/webex_bot.py +41 -20
- webex_bot/websockets/webex_websocket_client.py +45 -6
- {webex_bot-0.4.1.dist-info → webex_bot-0.4.6.dist-info}/METADATA +11 -3
- {webex_bot-0.4.1.dist-info → webex_bot-0.4.6.dist-info}/RECORD +9 -9
- {webex_bot-0.4.1.dist-info → webex_bot-0.4.6.dist-info}/WHEEL +1 -1
- {webex_bot-0.4.1.dist-info → webex_bot-0.4.6.dist-info}/LICENSE +0 -0
- {webex_bot-0.4.1.dist-info → webex_bot-0.4.6.dist-info}/top_level.txt +0 -0
webex_bot/__init__.py
CHANGED
webex_bot/commands/help.py
CHANGED
|
@@ -14,7 +14,7 @@ HELP_COMMAND_KEYWORD = "help"
|
|
|
14
14
|
|
|
15
15
|
class HelpCommand(Command):
|
|
16
16
|
|
|
17
|
-
def __init__(self, bot_name, bot_help_subtitle, bot_help_image):
|
|
17
|
+
def __init__(self, bot_name, bot_help_subtitle, bot_help_image, bot_help_image_size=ImageSize.SMALL):
|
|
18
18
|
self.commands = None
|
|
19
19
|
super().__init__(
|
|
20
20
|
command_keyword=HELP_COMMAND_KEYWORD,
|
|
@@ -25,6 +25,7 @@ class HelpCommand(Command):
|
|
|
25
25
|
self.bot_name = bot_name
|
|
26
26
|
self.bot_help_subtitle = bot_help_subtitle
|
|
27
27
|
self.bot_help_image = bot_help_image
|
|
28
|
+
self.bot_help_image_size = bot_help_image_size
|
|
28
29
|
|
|
29
30
|
def execute(self, message, attachment_actions, activity):
|
|
30
31
|
pass
|
|
@@ -42,7 +43,7 @@ class HelpCommand(Command):
|
|
|
42
43
|
|
|
43
44
|
image = Image(
|
|
44
45
|
url=self.bot_help_image,
|
|
45
|
-
size=
|
|
46
|
+
size=self.bot_help_image_size)
|
|
46
47
|
|
|
47
48
|
header_column = Column(items=[heading, subtitle], width=2)
|
|
48
49
|
header_image_column = Column(
|
webex_bot/webex_bot.py
CHANGED
|
@@ -16,10 +16,6 @@ from webex_bot.models.response import Response
|
|
|
16
16
|
from webex_bot.websockets.webex_websocket_client import WebexWebsocketClient, DEFAULT_DEVICE_URL
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
|
-
coloredlogs.install(level=os.getenv("LOG_LEVEL", "INFO"),
|
|
20
|
-
fmt='%(asctime)s [%(levelname)s] '
|
|
21
|
-
'[%(module)s.%(name)s.%(funcName)'
|
|
22
|
-
's]:%(lineno)s %(message)s')
|
|
23
19
|
|
|
24
20
|
|
|
25
21
|
class WebexBot(WebexWebsocketClient):
|
|
@@ -32,7 +28,10 @@ class WebexBot(WebexWebsocketClient):
|
|
|
32
28
|
device_url=DEFAULT_DEVICE_URL,
|
|
33
29
|
include_demo_commands=False,
|
|
34
30
|
bot_name="Webex Bot",
|
|
35
|
-
bot_help_subtitle="Here are my available commands. Click one to begin."
|
|
31
|
+
bot_help_subtitle="Here are my available commands. Click one to begin.",
|
|
32
|
+
threads=True,
|
|
33
|
+
help_command=None,
|
|
34
|
+
log_level="INFO"):
|
|
36
35
|
"""
|
|
37
36
|
Initialise WebexBot.
|
|
38
37
|
|
|
@@ -44,8 +43,16 @@ class WebexBot(WebexWebsocketClient):
|
|
|
44
43
|
@param include_demo_commands: If True, any demo commands will be included.
|
|
45
44
|
@param bot_name: Your custom name for the bot.
|
|
46
45
|
@param bot_help_subtitle: Text to show in the help card.
|
|
46
|
+
@param threads: If True, respond to msg by creating a thread.
|
|
47
|
+
@param help_command: If None, use internal HelpCommand, otherwise override.
|
|
48
|
+
@param log_level: Set loggin level.
|
|
49
|
+
|
|
47
50
|
"""
|
|
48
51
|
|
|
52
|
+
coloredlogs.install(level=os.getenv("LOG_LEVEL", log_level),
|
|
53
|
+
fmt='%(asctime)s [%(levelname)s] '
|
|
54
|
+
'[%(module)s.%(name)s.%(funcName)'
|
|
55
|
+
's]:%(lineno)s %(message)s')
|
|
49
56
|
log.info("Registering bot with Webex cloud")
|
|
50
57
|
WebexWebsocketClient.__init__(self,
|
|
51
58
|
teams_bot_token,
|
|
@@ -58,27 +65,30 @@ class WebexBot(WebexWebsocketClient):
|
|
|
58
65
|
# text and callback function
|
|
59
66
|
# By default supports 2 command, echo and help
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
if help_command is None:
|
|
69
|
+
self.help_command = HelpCommand(
|
|
70
|
+
bot_name=bot_name,
|
|
71
|
+
bot_help_subtitle=bot_help_subtitle,
|
|
72
|
+
bot_help_image=self.teams.people.me().avatar)
|
|
73
|
+
self.help_command.commands = self.commands
|
|
74
|
+
# Set default help message
|
|
75
|
+
self.help_message = "Hello! I understand the following commands: \n"
|
|
76
|
+
else:
|
|
77
|
+
self.help_command = help_command
|
|
65
78
|
self.commands = {
|
|
66
79
|
self.help_command
|
|
67
80
|
}
|
|
68
81
|
if include_demo_commands:
|
|
69
82
|
self.add_command(EchoCommand())
|
|
70
83
|
|
|
71
|
-
self.help_command.commands = self.commands
|
|
72
|
-
|
|
73
84
|
self.card_callback_commands = {}
|
|
74
85
|
self.approved_users = approved_users
|
|
75
86
|
self.approved_domains = approved_domains
|
|
76
87
|
self.approved_rooms = approved_rooms
|
|
77
|
-
# Set default help message
|
|
78
|
-
self.help_message = "Hello! I understand the following commands: \n"
|
|
79
88
|
self.approval_parameters_check()
|
|
80
89
|
self.bot_display_name = ""
|
|
81
90
|
self.get_me_info()
|
|
91
|
+
self.threads = threads
|
|
82
92
|
|
|
83
93
|
@backoff.on_exception(backoff.expo, requests.exceptions.ConnectionError)
|
|
84
94
|
def get_me_info(self):
|
|
@@ -307,7 +317,7 @@ class WebexBot(WebexWebsocketClient):
|
|
|
307
317
|
# If the Response lacks a roomId, set it to the incoming room
|
|
308
318
|
if not reply.roomId:
|
|
309
319
|
reply.roomId = room_id
|
|
310
|
-
if not reply.parentId and conv_target_id:
|
|
320
|
+
if not reply.parentId and conv_target_id and self.threads:
|
|
311
321
|
reply.parentId = conv_target_id
|
|
312
322
|
reply = reply.as_dict()
|
|
313
323
|
self.teams.messages.create(**reply)
|
|
@@ -351,14 +361,25 @@ class WebexBot(WebexWebsocketClient):
|
|
|
351
361
|
quote_info(f"{user_email} I've messaged you 1-1. Please reply to me there.")
|
|
352
362
|
if reply_one_to_one:
|
|
353
363
|
if not is_one_on_one_space:
|
|
354
|
-
self.
|
|
355
|
-
|
|
364
|
+
if self.threads:
|
|
365
|
+
self.teams.messages.create(roomId=room_id,
|
|
366
|
+
markdown=default_move_to_one_to_one_heads_up,
|
|
367
|
+
parentId=conv_target_id)
|
|
368
|
+
else:
|
|
369
|
+
self.teams.messages.create(roomId=room_id,
|
|
370
|
+
markdown=default_move_to_one_to_one_heads_up)
|
|
371
|
+
if self.threads:
|
|
372
|
+
self.teams.messages.create(toPersonEmail=user_email,
|
|
373
|
+
markdown=reply,
|
|
356
374
|
parentId=conv_target_id)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
375
|
+
else:
|
|
376
|
+
self.teams.messages.create(toPersonEmail=user_email,
|
|
377
|
+
markdown=reply)
|
|
360
378
|
else:
|
|
361
|
-
self.
|
|
379
|
+
if self.threads:
|
|
380
|
+
self.teams.messages.create(roomId=room_id, markdown=reply, parentId=conv_target_id)
|
|
381
|
+
else:
|
|
382
|
+
self.teams.messages.create(roomId=room_id, markdown=reply)
|
|
362
383
|
|
|
363
384
|
def run_pre_card_load_reply(self, command, message, teams_message, activity):
|
|
364
385
|
"""
|
|
@@ -28,6 +28,8 @@ DEVICE_DATA = {
|
|
|
28
28
|
ssl_context = ssl.create_default_context()
|
|
29
29
|
ssl_context.load_verify_locations(certifi.where())
|
|
30
30
|
|
|
31
|
+
MAX_BACKOFF_TIME = 240
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
class WebexWebsocketClient(object):
|
|
33
35
|
def __init__(self,
|
|
@@ -42,12 +44,14 @@ class WebexWebsocketClient(object):
|
|
|
42
44
|
self.on_message = on_message
|
|
43
45
|
self.on_card_action = on_card_action
|
|
44
46
|
self.websocket = None
|
|
47
|
+
self.share_id = None
|
|
45
48
|
|
|
46
49
|
def _process_incoming_websocket_message(self, msg):
|
|
47
50
|
"""
|
|
48
51
|
Handle websocket data.
|
|
49
52
|
:param msg: The raw websocket message
|
|
50
53
|
"""
|
|
54
|
+
logger.info(f"msg['data'] = {msg['data']}")
|
|
51
55
|
if msg['data']['eventType'] == 'conversation.activity':
|
|
52
56
|
activity = msg['data']['activity']
|
|
53
57
|
if activity['verb'] == 'post':
|
|
@@ -61,6 +65,31 @@ class WebexWebsocketClient(object):
|
|
|
61
65
|
self._ack_message(message_base_64_id)
|
|
62
66
|
# Now process it with the handler
|
|
63
67
|
self.on_message(teams_message=webex_message, activity=activity)
|
|
68
|
+
elif activity['verb'] == 'share':
|
|
69
|
+
logger.debug(f"activity={activity}")
|
|
70
|
+
self.share_id = activity['id']
|
|
71
|
+
return
|
|
72
|
+
elif activity['verb'] == 'update':
|
|
73
|
+
logger.debug(f"activity={activity}")
|
|
74
|
+
|
|
75
|
+
object = activity['object']
|
|
76
|
+
if object['objectType'] == 'content' and object['contentCategory'] == 'documents':
|
|
77
|
+
if 'files' in object.keys():
|
|
78
|
+
for item in object['files']['items']:
|
|
79
|
+
if not item['malwareQuarantineState'] == 'safe':
|
|
80
|
+
return
|
|
81
|
+
else:
|
|
82
|
+
return
|
|
83
|
+
else:
|
|
84
|
+
return
|
|
85
|
+
message_base_64_id = self._get_base64_message_id(activity)
|
|
86
|
+
webex_message = self.teams.messages.get(message_base_64_id)
|
|
87
|
+
logger.debug(f"webex_message from message_base_64_id: {webex_message}")
|
|
88
|
+
if self.on_message:
|
|
89
|
+
# ack message first
|
|
90
|
+
self._ack_message(message_base_64_id)
|
|
91
|
+
# Now process it with the handler
|
|
92
|
+
self.on_message(teams_message=webex_message, activity=activity)
|
|
64
93
|
elif activity['verb'] == 'cardAction':
|
|
65
94
|
logger.debug(f"activity={activity}")
|
|
66
95
|
|
|
@@ -83,10 +112,14 @@ class WebexWebsocketClient(object):
|
|
|
83
112
|
@return: base 64 message id
|
|
84
113
|
"""
|
|
85
114
|
activity_id = activity['id']
|
|
86
|
-
logger.debug(f"activity verb=
|
|
115
|
+
logger.debug(f"activity verb={activity['verb']}. message id={activity_id}")
|
|
87
116
|
conversation_url = activity['target']['url']
|
|
88
117
|
conv_target_id = activity['target']['id']
|
|
89
|
-
verb = "messages" if activity['verb']
|
|
118
|
+
verb = "messages" if activity['verb'] in ["post", "update"] else "attachment/actions"
|
|
119
|
+
if activity['verb'] == "update" and self.share_id is not None:
|
|
120
|
+
activity_id = self.share_id
|
|
121
|
+
self.share_id = None
|
|
122
|
+
logger.debug(f"activity_id={activity_id}")
|
|
90
123
|
conversation_message_url = conversation_url.replace(f"conversations/{conv_target_id}",
|
|
91
124
|
f"{verb}/{activity_id}")
|
|
92
125
|
headers = {"Authorization": f"Bearer {self.access_token}"}
|
|
@@ -134,6 +167,12 @@ class WebexWebsocketClient(object):
|
|
|
134
167
|
logger.debug(f"self.device_info: {self.device_info}")
|
|
135
168
|
return resp
|
|
136
169
|
|
|
170
|
+
def stop(self):
|
|
171
|
+
def terminate():
|
|
172
|
+
raise SystemExit()
|
|
173
|
+
|
|
174
|
+
asyncio.get_event_loop().create_task(terminate())
|
|
175
|
+
|
|
137
176
|
def run(self):
|
|
138
177
|
if self.device_info is None:
|
|
139
178
|
if self._get_device_info() is None:
|
|
@@ -151,10 +190,10 @@ class WebexWebsocketClient(object):
|
|
|
151
190
|
logger.warning(
|
|
152
191
|
f"An exception occurred while processing message. Ignoring. {messageProcessingException}")
|
|
153
192
|
|
|
154
|
-
@backoff.on_exception(backoff.expo, websockets.ConnectionClosedError)
|
|
155
|
-
@backoff.on_exception(backoff.expo, websockets.ConnectionClosedOK)
|
|
156
|
-
@backoff.on_exception(backoff.expo, websockets.ConnectionClosed)
|
|
157
|
-
@backoff.on_exception(backoff.expo, socket.gaierror)
|
|
193
|
+
@backoff.on_exception(backoff.expo, websockets.ConnectionClosedError, max_time=MAX_BACKOFF_TIME)
|
|
194
|
+
@backoff.on_exception(backoff.expo, websockets.ConnectionClosedOK, max_time=MAX_BACKOFF_TIME)
|
|
195
|
+
@backoff.on_exception(backoff.expo, websockets.ConnectionClosed, max_time=MAX_BACKOFF_TIME)
|
|
196
|
+
@backoff.on_exception(backoff.expo, socket.gaierror, max_time=MAX_BACKOFF_TIME)
|
|
158
197
|
async def _connect_and_listen():
|
|
159
198
|
ws_url = self.device_info['webSocketUrl']
|
|
160
199
|
logger.info(f"Opening websocket connection to {ws_url}")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: webex-bot
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Python package for a Webex Bot based on websockets.
|
|
5
5
|
Home-page: https://github.com/fbradyirl/webex_bot
|
|
6
6
|
Author: Finbarr Brady
|
|
7
|
-
Author-email: finbarr
|
|
7
|
+
Author-email: finbarr@somemail.com
|
|
8
8
|
License: MIT license
|
|
9
9
|
Keywords: webex_bot
|
|
10
10
|
Platform: UNKNOWN
|
|
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: webexteamssdk ==1.6.1
|
|
21
21
|
Requires-Dist: coloredlogs
|
|
22
|
-
Requires-Dist: websockets ==
|
|
22
|
+
Requires-Dist: websockets ==11.0.3
|
|
23
23
|
Requires-Dist: backoff
|
|
24
24
|
|
|
25
25
|
# Introduction
|
|
@@ -351,6 +351,14 @@ and off you go!
|
|
|
351
351
|
|
|
352
352
|
* Always ensure there is a thread ID in the Activity before accessing it
|
|
353
353
|
|
|
354
|
+
### 0.4.6 (2024-Apr-24)
|
|
355
|
+
|
|
356
|
+
* Add max backoff time (#55)
|
|
357
|
+
* Attached files. Help, threading and log level overrides. (#54)
|
|
358
|
+
* add stop() call to gracefully exit the bot (#42)
|
|
359
|
+
* feat(20231212): add help image size parameter (#46)
|
|
360
|
+
* update websockets to 11.0.3 (#43)
|
|
361
|
+
|
|
354
362
|
[1]: https://github.com/aaugustin/websockets
|
|
355
363
|
|
|
356
364
|
[2]: https://github.com/CiscoDevNet/webexteamssdk
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
webex_bot/__init__.py,sha256=
|
|
1
|
+
webex_bot/__init__.py,sha256=iAWNyB5H4ndIq6bXqGlXlf6PUKceoqT-5dwRKJMBN9A,95
|
|
2
2
|
webex_bot/exceptions.py,sha256=qs9yVitfJtvxwBMC8uCvTDOxUQ_oZjWFf1dU8Oaue14,740
|
|
3
3
|
webex_bot/formatting.py,sha256=jvPKym-z8CIJygpPVTVbt6vFXQo9_HQHpRDJB-nh-SI,382
|
|
4
|
-
webex_bot/webex_bot.py,sha256=
|
|
4
|
+
webex_bot/webex_bot.py,sha256=Vm_XKlTfPK1VnUdQZ3n36XXUJD5Z_iQ6iNPjrWKq49I,20449
|
|
5
5
|
webex_bot/cards/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
webex_bot/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
webex_bot/commands/echo.py,sha256=YmeRVh56pzsj0_lRHDvgyrv6YqQQdRiusG65dHTh9qU,3397
|
|
8
|
-
webex_bot/commands/help.py,sha256=
|
|
8
|
+
webex_bot/commands/help.py,sha256=s4buQxnmHijkqWiYs8RZ_H-yzi3EhAffSozPCGNndD0,3468
|
|
9
9
|
webex_bot/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
webex_bot/models/command.py,sha256=Rok661eZM2Mg465CZ4GgctWi1RD-BD5Bruq0FsJ8GDQ,5023
|
|
11
11
|
webex_bot/models/response.py,sha256=1U0EQv8O5R3-demaumxMExn278DG4AeQHhXP_g4xndk,2544
|
|
12
12
|
webex_bot/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
webex_bot/websockets/webex_websocket_client.py,sha256=
|
|
14
|
-
webex_bot-0.4.
|
|
15
|
-
webex_bot-0.4.
|
|
16
|
-
webex_bot-0.4.
|
|
17
|
-
webex_bot-0.4.
|
|
18
|
-
webex_bot-0.4.
|
|
13
|
+
webex_bot/websockets/webex_websocket_client.py,sha256=uBpvpRLmfhgtfDr0Yomg6FNei9CIsCEigNsAEu5c5iY,9427
|
|
14
|
+
webex_bot-0.4.6.dist-info/LICENSE,sha256=93eGb10xmgkBP2Fh_n0E9YDXe0c0oz-FsnAimXG0S4Y,1072
|
|
15
|
+
webex_bot-0.4.6.dist-info/METADATA,sha256=QP_kCZ9w2jsdrk5AqTWQxCDTH8E_uVzN_v3m4HKmWFc,11008
|
|
16
|
+
webex_bot-0.4.6.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
17
|
+
webex_bot-0.4.6.dist-info/top_level.txt,sha256=q1Y0RtYYinR7oXSwL93cK59c2KN_CbMVca8MLWeF63M,10
|
|
18
|
+
webex_bot-0.4.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|