webex-message-handler 0.3.1__tar.gz → 0.4.1__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_message_handler-0.3.1 → webex_message_handler-0.4.1}/PKG-INFO +12 -1
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/README.md +11 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/pyproject.toml +1 -1
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/handler.py +50 -21
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/test_handler.py +2 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/.gitignore +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/API.md +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/LICENSE +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/examples/basic_bot.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/__init__.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/device_manager.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/errors.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/kms_client.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/logger.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/mercury_socket.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/message_decryptor.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/types.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/__init__.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/conftest.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/test_device_manager.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/test_integration.py +0 -0
- {webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/tests/test_message_decryptor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webex-message-handler
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Lightweight Webex Mercury WebSocket + KMS decryption for receiving bot messages without the full Webex SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/3rg0n/webex-message-handler
|
|
6
6
|
Project-URL: Repository, https://github.com/3rg0n/webex-message-handler
|
|
@@ -99,6 +99,16 @@ asyncio.run(main())
|
|
|
99
99
|
|
|
100
100
|
See `examples/basic_bot.py` for a complete working example.
|
|
101
101
|
|
|
102
|
+
## Important: Implementing Loop Detection
|
|
103
|
+
|
|
104
|
+
This library only handles the **receive side** of messaging — it decrypts incoming messages from the Mercury WebSocket. It has no visibility into messages your bot **sends** via the REST API. This means it cannot detect message loops on its own.
|
|
105
|
+
|
|
106
|
+
If your bot replies to incoming messages, you **must** implement loop detection in your wrapper code. Without it, a bug or misconfiguration could cause your bot to endlessly reply to its own messages. Webex enforces a server-side rate limit (approximately 11 consecutive messages before throttling), but that still results in spam before the cutoff.
|
|
107
|
+
|
|
108
|
+
**Recommended approach:** Track your bot's outgoing message rate. If it exceeds a threshold (e.g., 5 messages in 3 seconds to the same room), pause sending and log a warning.
|
|
109
|
+
|
|
110
|
+
The `ignore_self_messages` option (default: `True`) provides a first line of defense by filtering out messages sent by this bot's own identity. If the library cannot verify the bot's identity during `connect()` (e.g., `/people/me` API failure), connection will fail rather than silently running without protection. Set `ignore_self_messages=False` to opt out, but only if you have your own loop prevention in place.
|
|
111
|
+
|
|
102
112
|
## Proxy Support (Enterprise)
|
|
103
113
|
|
|
104
114
|
For corporate environments behind a proxy, pass a configured connector:
|
|
@@ -162,6 +172,7 @@ WebexMessageHandler(config: WebexMessageHandlerConfig)
|
|
|
162
172
|
|--------|------|---------|-------------|
|
|
163
173
|
| `token` | `str` | required | Webex bot access token |
|
|
164
174
|
| `logger` | `Logger` | noop | Custom logger (`console_logger` provided) |
|
|
175
|
+
| `ignore_self_messages` | `bool` | `True` | Filter out messages sent by this bot |
|
|
165
176
|
| `connector` | `aiohttp.BaseConnector` | `None` | HTTP/HTTPS connector for proxy support |
|
|
166
177
|
| `ping_interval` | `float` | `15.0` | Mercury ping interval (seconds) |
|
|
167
178
|
| `pong_timeout` | `float` | `14.0` | Pong response timeout (seconds) |
|
|
@@ -68,6 +68,16 @@ asyncio.run(main())
|
|
|
68
68
|
|
|
69
69
|
See `examples/basic_bot.py` for a complete working example.
|
|
70
70
|
|
|
71
|
+
## Important: Implementing Loop Detection
|
|
72
|
+
|
|
73
|
+
This library only handles the **receive side** of messaging — it decrypts incoming messages from the Mercury WebSocket. It has no visibility into messages your bot **sends** via the REST API. This means it cannot detect message loops on its own.
|
|
74
|
+
|
|
75
|
+
If your bot replies to incoming messages, you **must** implement loop detection in your wrapper code. Without it, a bug or misconfiguration could cause your bot to endlessly reply to its own messages. Webex enforces a server-side rate limit (approximately 11 consecutive messages before throttling), but that still results in spam before the cutoff.
|
|
76
|
+
|
|
77
|
+
**Recommended approach:** Track your bot's outgoing message rate. If it exceeds a threshold (e.g., 5 messages in 3 seconds to the same room), pause sending and log a warning.
|
|
78
|
+
|
|
79
|
+
The `ignore_self_messages` option (default: `True`) provides a first line of defense by filtering out messages sent by this bot's own identity. If the library cannot verify the bot's identity during `connect()` (e.g., `/people/me` API failure), connection will fail rather than silently running without protection. Set `ignore_self_messages=False` to opt out, but only if you have your own loop prevention in place.
|
|
80
|
+
|
|
71
81
|
## Proxy Support (Enterprise)
|
|
72
82
|
|
|
73
83
|
For corporate environments behind a proxy, pass a configured connector:
|
|
@@ -131,6 +141,7 @@ WebexMessageHandler(config: WebexMessageHandlerConfig)
|
|
|
131
141
|
|--------|------|---------|-------------|
|
|
132
142
|
| `token` | `str` | required | Webex bot access token |
|
|
133
143
|
| `logger` | `Logger` | noop | Custom logger (`console_logger` provided) |
|
|
144
|
+
| `ignore_self_messages` | `bool` | `True` | Filter out messages sent by this bot |
|
|
134
145
|
| `connector` | `aiohttp.BaseConnector` | `None` | HTTP/HTTPS connector for proxy support |
|
|
135
146
|
| `ping_interval` | `float` | `15.0` | Mercury ping interval (seconds) |
|
|
136
147
|
| `pong_timeout` | `float` | `14.0` | Pong response timeout (seconds) |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "webex-message-handler"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Lightweight Webex Mercury WebSocket + KMS decryption for receiving bot messages without the full Webex SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/handler.py
RENAMED
|
@@ -31,10 +31,35 @@ from .types import (
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
pass
|
|
33
33
|
|
|
34
|
+
import base64
|
|
35
|
+
|
|
34
36
|
# Type alias for event callbacks
|
|
35
37
|
EventCallback = Callable[..., Any]
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
def extract_person_uuid(person_id: str) -> str:
|
|
41
|
+
"""Extract the raw UUID from a Webex person ID.
|
|
42
|
+
|
|
43
|
+
The Webex REST API returns base64-encoded IDs like:
|
|
44
|
+
"Y2lzY29zcGFyazovL3VzL1BFT1BMRS9mYjUx..." → "ciscospark://us/PEOPLE/fb51254f-..."
|
|
45
|
+
|
|
46
|
+
Mercury wire format uses raw UUIDs:
|
|
47
|
+
"fb51254f-3b37-4e50-aa04-45744c2effc7"
|
|
48
|
+
|
|
49
|
+
This function normalizes both formats to the raw UUID for comparison.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
decoded = base64.b64decode(person_id).decode("utf-8")
|
|
53
|
+
if decoded.startswith("ciscospark://"):
|
|
54
|
+
uuid = decoded.rsplit("/", 1)[-1]
|
|
55
|
+
if uuid:
|
|
56
|
+
return uuid
|
|
57
|
+
except Exception:
|
|
58
|
+
# Not base64 — treat as raw UUID
|
|
59
|
+
pass
|
|
60
|
+
return person_id
|
|
61
|
+
|
|
62
|
+
|
|
38
63
|
class WebexMessageHandler:
|
|
39
64
|
"""Receives and decrypts Webex messages over Mercury WebSocket.
|
|
40
65
|
|
|
@@ -321,27 +346,31 @@ class WebexMessageHandler:
|
|
|
321
346
|
)
|
|
322
347
|
|
|
323
348
|
async def _fetch_bot_person_id(self) -> None:
|
|
324
|
-
"""Fetch the bot's person ID for self-message filtering.
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
349
|
+
"""Fetch the bot's person ID for self-message filtering.
|
|
350
|
+
|
|
351
|
+
Raises on failure — connect() will not proceed without a valid bot ID
|
|
352
|
+
when ignore_self_messages is enabled.
|
|
353
|
+
"""
|
|
354
|
+
self._logger.debug("Fetching bot person info for self-message filtering")
|
|
355
|
+
response = await self._http_do(
|
|
356
|
+
FetchRequest(
|
|
357
|
+
url="https://webexapis.com/v1/people/me",
|
|
358
|
+
method="GET",
|
|
359
|
+
headers={
|
|
360
|
+
"Authorization": f"Bearer {self._token}",
|
|
361
|
+
"Content-Type": "application/json",
|
|
362
|
+
},
|
|
336
363
|
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
364
|
+
)
|
|
365
|
+
if not response.ok:
|
|
366
|
+
raise RuntimeError(
|
|
367
|
+
f"Failed to fetch bot identity for self-message filtering: HTTP {response.status}. "
|
|
368
|
+
"Set ignore_self_messages=False to skip this check (not recommended — may cause message loops)."
|
|
369
|
+
)
|
|
370
|
+
data = await response.json()
|
|
371
|
+
raw_id = data.get("id", "")
|
|
372
|
+
self._bot_person_id = extract_person_uuid(raw_id)
|
|
373
|
+
self._logger.info(f"Bot person ID cached for self-message filtering: {self._bot_person_id}")
|
|
345
374
|
|
|
346
375
|
def _setup_mercury_listeners(self) -> None:
|
|
347
376
|
# Forward KMS messages from Mercury to the KMS client
|
|
@@ -409,7 +438,7 @@ class WebexMessageHandler:
|
|
|
409
438
|
raw=decrypted,
|
|
410
439
|
)
|
|
411
440
|
# Filter self-messages if enabled
|
|
412
|
-
if self._ignore_self_messages and self._bot_person_id and message.person_id == self._bot_person_id:
|
|
441
|
+
if self._ignore_self_messages and self._bot_person_id and extract_person_uuid(message.person_id) == self._bot_person_id:
|
|
413
442
|
self._logger.debug(f"Ignoring self-message from bot ({self._bot_person_id})")
|
|
414
443
|
return
|
|
415
444
|
|
|
@@ -130,6 +130,7 @@ class TestConnect:
|
|
|
130
130
|
mock_kms_cls.return_value = mock_kms
|
|
131
131
|
|
|
132
132
|
handler = _make_handler()
|
|
133
|
+
handler._fetch_bot_person_id = AsyncMock()
|
|
133
134
|
connected_events = []
|
|
134
135
|
handler.on("connected", lambda: connected_events.append(True))
|
|
135
136
|
|
|
@@ -183,6 +184,7 @@ class TestDisconnect:
|
|
|
183
184
|
mock_kms_cls.return_value = mock_kms
|
|
184
185
|
|
|
185
186
|
handler = _make_handler()
|
|
187
|
+
handler._fetch_bot_person_id = AsyncMock()
|
|
186
188
|
await handler.connect()
|
|
187
189
|
await handler.disconnect()
|
|
188
190
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/errors.py
RENAMED
|
File without changes
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/kms_client.py
RENAMED
|
File without changes
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/logger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{webex_message_handler-0.3.1 → webex_message_handler-0.4.1}/src/webex_message_handler/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|