webex-bot 0.4.0__tar.gz → 0.4.6__tar.gz
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-0.4.0 → webex_bot-0.4.6}/PKG-INFO +14 -2
- {webex_bot-0.4.0 → webex_bot-0.4.6}/README.md +12 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/setup.cfg +1 -1
- {webex_bot-0.4.0 → webex_bot-0.4.6}/setup.py +3 -3
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/__init__.py +1 -1
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/help.py +3 -2
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/webex_bot.py +44 -21
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/websockets/webex_websocket_client.py +45 -6
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/PKG-INFO +14 -2
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/requires.txt +1 -1
- {webex_bot-0.4.0 → webex_bot-0.4.6}/CONTRIBUTING.rst +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/LICENSE +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/MANIFEST.in +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/Makefile +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/conf.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/contributing.rst +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/index.rst +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/installation.rst +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/make.bat +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/usage.rst +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/tests/__init__.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/tests/test_webex_bot.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/cards/__init__.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/__init__.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/echo.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/exceptions.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/formatting.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/__init__.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/command.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/response.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/websockets/__init__.py +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/SOURCES.txt +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/dependency_links.txt +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/not-zip-safe +0 -0
- {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/top_level.txt +0 -0
|
@@ -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
|
|
@@ -343,6 +343,18 @@ and off you go!
|
|
|
343
343
|
* Bot will reply in response to the original message via the thread ID. This is not always possible in the case of a
|
|
344
344
|
card action response due to some server side issue.
|
|
345
345
|
|
|
346
|
+
### 0.4.1 (2023-Sept-07)
|
|
347
|
+
|
|
348
|
+
* Always ensure there is a thread ID in the Activity before accessing it
|
|
349
|
+
|
|
350
|
+
### 0.4.6 (2024-Apr-24)
|
|
351
|
+
|
|
352
|
+
* Add max backoff time (#55)
|
|
353
|
+
* Attached files. Help, threading and log level overrides. (#54)
|
|
354
|
+
* add stop() call to gracefully exit the bot (#42)
|
|
355
|
+
* feat(20231212): add help image size parameter (#46)
|
|
356
|
+
* update websockets to 11.0.3 (#43)
|
|
357
|
+
|
|
346
358
|
[1]: https://github.com/aaugustin/websockets
|
|
347
359
|
|
|
348
360
|
[2]: https://github.com/CiscoDevNet/webexteamssdk
|
|
@@ -323,6 +323,18 @@ and off you go!
|
|
|
323
323
|
* Bot will reply in response to the original message via the thread ID. This is not always possible in the case of a
|
|
324
324
|
card action response due to some server side issue.
|
|
325
325
|
|
|
326
|
+
### 0.4.1 (2023-Sept-07)
|
|
327
|
+
|
|
328
|
+
* Always ensure there is a thread ID in the Activity before accessing it
|
|
329
|
+
|
|
330
|
+
### 0.4.6 (2024-Apr-24)
|
|
331
|
+
|
|
332
|
+
* Add max backoff time (#55)
|
|
333
|
+
* Attached files. Help, threading and log level overrides. (#54)
|
|
334
|
+
* add stop() call to gracefully exit the bot (#42)
|
|
335
|
+
* feat(20231212): add help image size parameter (#46)
|
|
336
|
+
* update websockets to 11.0.3 (#43)
|
|
337
|
+
|
|
326
338
|
[1]: https://github.com/aaugustin/websockets
|
|
327
339
|
|
|
328
340
|
[2]: https://github.com/CiscoDevNet/webexteamssdk
|
|
@@ -7,7 +7,7 @@ from setuptools import setup, find_packages
|
|
|
7
7
|
with open('README.md') as readme_file:
|
|
8
8
|
readme = readme_file.read()
|
|
9
9
|
|
|
10
|
-
requirements = ['webexteamssdk==1.6.1', 'coloredlogs', 'websockets==
|
|
10
|
+
requirements = ['webexteamssdk==1.6.1', 'coloredlogs', 'websockets==11.0.3', 'backoff']
|
|
11
11
|
|
|
12
12
|
setup_requirements = ['pytest-runner', ]
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ test_requirements = ['pytest>=3', ]
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
author="Finbarr Brady",
|
|
18
|
-
author_email='finbarr
|
|
18
|
+
author_email='finbarr@somemail.com',
|
|
19
19
|
python_requires='>=3.8',
|
|
20
20
|
classifiers=[
|
|
21
21
|
'Development Status :: 2 - Pre-Alpha',
|
|
@@ -38,6 +38,6 @@ setup(
|
|
|
38
38
|
test_suite='tests',
|
|
39
39
|
tests_require=test_requirements,
|
|
40
40
|
url='https://github.com/fbradyirl/webex_bot',
|
|
41
|
-
version='0.4.
|
|
41
|
+
version='0.4.6',
|
|
42
42
|
zip_safe=False,
|
|
43
43
|
)
|
|
@@ -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(
|
|
@@ -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):
|
|
@@ -263,8 +273,10 @@ class WebexBot(WebexWebsocketClient):
|
|
|
263
273
|
f"response to cardAction inside a thread. "
|
|
264
274
|
f"Must reply outside of the thread in this case.: {activity}")
|
|
265
275
|
thread_parent_id = None
|
|
266
|
-
|
|
276
|
+
elif 'id' in activity:
|
|
267
277
|
thread_parent_id = activity['id']
|
|
278
|
+
else:
|
|
279
|
+
log.info("There is no activity id (thread ID) for this request.")
|
|
268
280
|
|
|
269
281
|
if command.delete_previous_message and hasattr(teams_message, 'messageId'):
|
|
270
282
|
previous_message_id = teams_message.messageId
|
|
@@ -305,7 +317,7 @@ class WebexBot(WebexWebsocketClient):
|
|
|
305
317
|
# If the Response lacks a roomId, set it to the incoming room
|
|
306
318
|
if not reply.roomId:
|
|
307
319
|
reply.roomId = room_id
|
|
308
|
-
if not reply.parentId and conv_target_id:
|
|
320
|
+
if not reply.parentId and conv_target_id and self.threads:
|
|
309
321
|
reply.parentId = conv_target_id
|
|
310
322
|
reply = reply.as_dict()
|
|
311
323
|
self.teams.messages.create(**reply)
|
|
@@ -349,14 +361,25 @@ class WebexBot(WebexWebsocketClient):
|
|
|
349
361
|
quote_info(f"{user_email} I've messaged you 1-1. Please reply to me there.")
|
|
350
362
|
if reply_one_to_one:
|
|
351
363
|
if not is_one_on_one_space:
|
|
352
|
-
self.
|
|
353
|
-
|
|
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,
|
|
354
374
|
parentId=conv_target_id)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
375
|
+
else:
|
|
376
|
+
self.teams.messages.create(toPersonEmail=user_email,
|
|
377
|
+
markdown=reply)
|
|
358
378
|
else:
|
|
359
|
-
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)
|
|
360
383
|
|
|
361
384
|
def run_pre_card_load_reply(self, command, message, teams_message, activity):
|
|
362
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
|
|
@@ -343,6 +343,18 @@ and off you go!
|
|
|
343
343
|
* Bot will reply in response to the original message via the thread ID. This is not always possible in the case of a
|
|
344
344
|
card action response due to some server side issue.
|
|
345
345
|
|
|
346
|
+
### 0.4.1 (2023-Sept-07)
|
|
347
|
+
|
|
348
|
+
* Always ensure there is a thread ID in the Activity before accessing it
|
|
349
|
+
|
|
350
|
+
### 0.4.6 (2024-Apr-24)
|
|
351
|
+
|
|
352
|
+
* Add max backoff time (#55)
|
|
353
|
+
* Attached files. Help, threading and log level overrides. (#54)
|
|
354
|
+
* add stop() call to gracefully exit the bot (#42)
|
|
355
|
+
* feat(20231212): add help image size parameter (#46)
|
|
356
|
+
* update websockets to 11.0.3 (#43)
|
|
357
|
+
|
|
346
358
|
[1]: https://github.com/aaugustin/websockets
|
|
347
359
|
|
|
348
360
|
[2]: https://github.com/CiscoDevNet/webexteamssdk
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|