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.
Files changed (35) hide show
  1. {webex_bot-0.4.0 → webex_bot-0.4.6}/PKG-INFO +14 -2
  2. {webex_bot-0.4.0 → webex_bot-0.4.6}/README.md +12 -0
  3. {webex_bot-0.4.0 → webex_bot-0.4.6}/setup.cfg +1 -1
  4. {webex_bot-0.4.0 → webex_bot-0.4.6}/setup.py +3 -3
  5. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/__init__.py +1 -1
  6. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/help.py +3 -2
  7. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/webex_bot.py +44 -21
  8. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/websockets/webex_websocket_client.py +45 -6
  9. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/PKG-INFO +14 -2
  10. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/requires.txt +1 -1
  11. {webex_bot-0.4.0 → webex_bot-0.4.6}/CONTRIBUTING.rst +0 -0
  12. {webex_bot-0.4.0 → webex_bot-0.4.6}/LICENSE +0 -0
  13. {webex_bot-0.4.0 → webex_bot-0.4.6}/MANIFEST.in +0 -0
  14. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/Makefile +0 -0
  15. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/conf.py +0 -0
  16. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/contributing.rst +0 -0
  17. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/index.rst +0 -0
  18. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/installation.rst +0 -0
  19. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/make.bat +0 -0
  20. {webex_bot-0.4.0 → webex_bot-0.4.6}/docs/usage.rst +0 -0
  21. {webex_bot-0.4.0 → webex_bot-0.4.6}/tests/__init__.py +0 -0
  22. {webex_bot-0.4.0 → webex_bot-0.4.6}/tests/test_webex_bot.py +0 -0
  23. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/cards/__init__.py +0 -0
  24. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/__init__.py +0 -0
  25. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/commands/echo.py +0 -0
  26. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/exceptions.py +0 -0
  27. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/formatting.py +0 -0
  28. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/__init__.py +0 -0
  29. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/command.py +0 -0
  30. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/models/response.py +0 -0
  31. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot/websockets/__init__.py +0 -0
  32. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/SOURCES.txt +0 -0
  33. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/dependency_links.txt +0 -0
  34. {webex_bot-0.4.0 → webex_bot-0.4.6}/webex_bot.egg-info/not-zip-safe +0 -0
  35. {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.0
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.brady@gmail.com
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
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.4.0
2
+ current_version = 0.4.6
3
3
  commit = True
4
4
  tag = True
5
5
 
@@ -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.2', 'backoff']
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.brady@gmail.com',
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.0',
41
+ version='0.4.6',
42
42
  zip_safe=False,
43
43
  )
@@ -1,4 +1,4 @@
1
1
  """Top-level package for Webex Bot."""
2
2
 
3
3
  __author__ = """Finbarr Brady"""
4
- __version__ = '0.4.0'
4
+ __version__ = '0.4.6'
@@ -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=ImageSize.SMALL)
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
- self.help_command = HelpCommand(
62
- bot_name=bot_name,
63
- bot_help_subtitle=bot_help_subtitle,
64
- bot_help_image=self.teams.people.me().avatar)
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
- else:
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.teams.messages.create(roomId=room_id,
353
- markdown=default_move_to_one_to_one_heads_up,
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
- self.teams.messages.create(toPersonEmail=user_email,
356
- markdown=reply,
357
- parentId=conv_target_id)
375
+ else:
376
+ self.teams.messages.create(toPersonEmail=user_email,
377
+ markdown=reply)
358
378
  else:
359
- self.teams.messages.create(roomId=room_id, markdown=reply, parentId=conv_target_id)
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=post. message id={activity_id}")
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'] == "post" else "attachment/actions"
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.0
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.brady@gmail.com
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
@@ -1,4 +1,4 @@
1
1
  webexteamssdk==1.6.1
2
2
  coloredlogs
3
- websockets==10.2
3
+ websockets==11.0.3
4
4
  backoff
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