webex-bot 0.4.1__tar.gz → 0.4.7__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.1 → webex_bot-0.4.7}/PKG-INFO +14 -2
- {webex_bot-0.4.1 → webex_bot-0.4.7}/README.md +12 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/setup.cfg +1 -1
- {webex_bot-0.4.1 → webex_bot-0.4.7}/setup.py +3 -3
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/__init__.py +1 -1
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/commands/help.py +3 -2
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/webex_bot.py +40 -21
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/websockets/webex_websocket_client.py +45 -6
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot.egg-info/PKG-INFO +14 -2
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot.egg-info/requires.txt +1 -1
- {webex_bot-0.4.1 → webex_bot-0.4.7}/CONTRIBUTING.rst +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/LICENSE +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/MANIFEST.in +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/Makefile +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/conf.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/contributing.rst +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/index.rst +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/installation.rst +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/make.bat +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/docs/usage.rst +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/tests/__init__.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/tests/test_webex_bot.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/cards/__init__.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/commands/__init__.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/commands/echo.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/exceptions.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/formatting.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/models/__init__.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/models/command.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/models/response.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot/websockets/__init__.py +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot.egg-info/SOURCES.txt +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot.egg-info/dependency_links.txt +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/webex_bot.egg-info/not-zip-safe +0 -0
- {webex_bot-0.4.1 → webex_bot-0.4.7}/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.7
|
|
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
|
|
@@ -347,6 +347,18 @@ and off you go!
|
|
|
347
347
|
|
|
348
348
|
* Always ensure there is a thread ID in the Activity before accessing it
|
|
349
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
|
+
|
|
358
|
+
### 0.4.7 (2024-Apr-24)
|
|
359
|
+
|
|
360
|
+
* Fix for help_command syntax issue
|
|
361
|
+
|
|
350
362
|
[1]: https://github.com/aaugustin/websockets
|
|
351
363
|
|
|
352
364
|
[2]: https://github.com/CiscoDevNet/webexteamssdk
|
|
@@ -327,6 +327,18 @@ and off you go!
|
|
|
327
327
|
|
|
328
328
|
* Always ensure there is a thread ID in the Activity before accessing it
|
|
329
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
|
+
|
|
338
|
+
### 0.4.7 (2024-Apr-24)
|
|
339
|
+
|
|
340
|
+
* Fix for help_command syntax issue
|
|
341
|
+
|
|
330
342
|
[1]: https://github.com/aaugustin/websockets
|
|
331
343
|
|
|
332
344
|
[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.7',
|
|
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,
|
|
@@ -53,32 +60,33 @@ class WebexBot(WebexWebsocketClient):
|
|
|
53
60
|
on_card_action=self.process_incoming_card_action,
|
|
54
61
|
device_url=device_url)
|
|
55
62
|
|
|
63
|
+
if help_command is None:
|
|
64
|
+
self.help_command = HelpCommand(
|
|
65
|
+
bot_name=bot_name,
|
|
66
|
+
bot_help_subtitle=bot_help_subtitle,
|
|
67
|
+
bot_help_image=self.teams.people.me().avatar)
|
|
68
|
+
else:
|
|
69
|
+
self.help_command = help_command
|
|
70
|
+
|
|
56
71
|
# A dictionary of commands this bot listens to
|
|
57
72
|
# Each key in the dictionary is a command, with associated help
|
|
58
73
|
# text and callback function
|
|
59
74
|
# By default supports 2 command, echo and help
|
|
60
|
-
|
|
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)
|
|
65
75
|
self.commands = {
|
|
66
76
|
self.help_command
|
|
67
77
|
}
|
|
78
|
+
|
|
68
79
|
if include_demo_commands:
|
|
69
80
|
self.add_command(EchoCommand())
|
|
70
81
|
|
|
71
|
-
self.help_command.commands = self.commands
|
|
72
|
-
|
|
73
82
|
self.card_callback_commands = {}
|
|
74
83
|
self.approved_users = approved_users
|
|
75
84
|
self.approved_domains = approved_domains
|
|
76
85
|
self.approved_rooms = approved_rooms
|
|
77
|
-
# Set default help message
|
|
78
|
-
self.help_message = "Hello! I understand the following commands: \n"
|
|
79
86
|
self.approval_parameters_check()
|
|
80
87
|
self.bot_display_name = ""
|
|
81
88
|
self.get_me_info()
|
|
89
|
+
self.threads = threads
|
|
82
90
|
|
|
83
91
|
@backoff.on_exception(backoff.expo, requests.exceptions.ConnectionError)
|
|
84
92
|
def get_me_info(self):
|
|
@@ -307,7 +315,7 @@ class WebexBot(WebexWebsocketClient):
|
|
|
307
315
|
# If the Response lacks a roomId, set it to the incoming room
|
|
308
316
|
if not reply.roomId:
|
|
309
317
|
reply.roomId = room_id
|
|
310
|
-
if not reply.parentId and conv_target_id:
|
|
318
|
+
if not reply.parentId and conv_target_id and self.threads:
|
|
311
319
|
reply.parentId = conv_target_id
|
|
312
320
|
reply = reply.as_dict()
|
|
313
321
|
self.teams.messages.create(**reply)
|
|
@@ -351,14 +359,25 @@ class WebexBot(WebexWebsocketClient):
|
|
|
351
359
|
quote_info(f"{user_email} I've messaged you 1-1. Please reply to me there.")
|
|
352
360
|
if reply_one_to_one:
|
|
353
361
|
if not is_one_on_one_space:
|
|
354
|
-
self.
|
|
355
|
-
|
|
362
|
+
if self.threads:
|
|
363
|
+
self.teams.messages.create(roomId=room_id,
|
|
364
|
+
markdown=default_move_to_one_to_one_heads_up,
|
|
365
|
+
parentId=conv_target_id)
|
|
366
|
+
else:
|
|
367
|
+
self.teams.messages.create(roomId=room_id,
|
|
368
|
+
markdown=default_move_to_one_to_one_heads_up)
|
|
369
|
+
if self.threads:
|
|
370
|
+
self.teams.messages.create(toPersonEmail=user_email,
|
|
371
|
+
markdown=reply,
|
|
356
372
|
parentId=conv_target_id)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
373
|
+
else:
|
|
374
|
+
self.teams.messages.create(toPersonEmail=user_email,
|
|
375
|
+
markdown=reply)
|
|
360
376
|
else:
|
|
361
|
-
self.
|
|
377
|
+
if self.threads:
|
|
378
|
+
self.teams.messages.create(roomId=room_id, markdown=reply, parentId=conv_target_id)
|
|
379
|
+
else:
|
|
380
|
+
self.teams.messages.create(roomId=room_id, markdown=reply)
|
|
362
381
|
|
|
363
382
|
def run_pre_card_load_reply(self, command, message, teams_message, activity):
|
|
364
383
|
"""
|
|
@@ -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.7
|
|
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
|
|
@@ -347,6 +347,18 @@ and off you go!
|
|
|
347
347
|
|
|
348
348
|
* Always ensure there is a thread ID in the Activity before accessing it
|
|
349
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
|
+
|
|
358
|
+
### 0.4.7 (2024-Apr-24)
|
|
359
|
+
|
|
360
|
+
* Fix for help_command syntax issue
|
|
361
|
+
|
|
350
362
|
[1]: https://github.com/aaugustin/websockets
|
|
351
363
|
|
|
352
364
|
[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
|