From 4654f75627aeb50600786e7ce20f8431cf02ffa8 Mon Sep 17 00:00:00 2001 From: Teknium Date: Mon, 13 Apr 2026 22:46:50 -0700 Subject: [PATCH] fix: QQBot missing integration points, timestamp parsing, test fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Platform.QQBOT to _UPDATE_ALLOWED_PLATFORMS (enables /update command) - Add 'qqbot' to webhook cross-platform delivery routing - Add 'qqbot' to hermes dump platform detection - Fix test_name_property casing: 'QQBot' not 'QQBOT' - Add _parse_qq_timestamp() for ISO 8601 + integer ms compatibility (QQ API changed timestamp format — from PR #2411 finding) - Wire timestamp parsing into all 4 message handlers --- gateway/platforms/qqbot.py | 26 ++++++++++++++++++++++---- gateway/platforms/webhook.py | 1 + gateway/run.py | 2 +- hermes_cli/dump.py | 1 + tests/gateway/test_qqbot.py | 2 +- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/gateway/platforms/qqbot.py b/gateway/platforms/qqbot.py index 647388a313a..631bd39e133 100644 --- a/gateway/platforms/qqbot.py +++ b/gateway/platforms/qqbot.py @@ -756,7 +756,7 @@ class QQAdapter(BasePlatformAdapter): message_id=msg_id, media_urls=image_urls, media_types=image_media_types, - timestamp=datetime.now(tz=timezone.utc), + timestamp=self._parse_qq_timestamp(timestamp), ) await self.handle_message(event) @@ -801,7 +801,7 @@ class QQAdapter(BasePlatformAdapter): message_id=msg_id, media_urls=image_urls, media_types=image_media_types, - timestamp=datetime.now(tz=timezone.utc), + timestamp=self._parse_qq_timestamp(timestamp), ) await self.handle_message(event) @@ -846,7 +846,7 @@ class QQAdapter(BasePlatformAdapter): message_id=msg_id, media_urls=image_urls, media_types=image_media_types, - timestamp=datetime.now(tz=timezone.utc), + timestamp=self._parse_qq_timestamp(timestamp), ) await self.handle_message(event) @@ -887,7 +887,7 @@ class QQAdapter(BasePlatformAdapter): message_id=msg_id, media_urls=image_urls, media_types=image_media_types, - timestamp=datetime.now(tz=timezone.utc), + timestamp=self._parse_qq_timestamp(timestamp), ) await self.handle_message(event) @@ -1902,6 +1902,24 @@ class QQAdapter(BasePlatformAdapter): return True return False + def _parse_qq_timestamp(self, raw: str) -> datetime: + """Parse QQ API timestamp (ISO 8601 string or integer ms). + + The QQ API changed from integer milliseconds to ISO 8601 strings. + This handles both formats gracefully. + """ + if not raw: + return datetime.now(tz=timezone.utc) + try: + return datetime.fromisoformat(raw) + except (ValueError, TypeError): + pass + try: + return datetime.fromtimestamp(int(raw) / 1000, tz=timezone.utc) + except (ValueError, TypeError): + pass + return datetime.now(tz=timezone.utc) + def _is_duplicate(self, msg_id: str) -> bool: now = time.time() if len(self._seen_messages) > DEDUP_MAX_SIZE: diff --git a/gateway/platforms/webhook.py b/gateway/platforms/webhook.py index eac7ed80e45..c37445b17e8 100644 --- a/gateway/platforms/webhook.py +++ b/gateway/platforms/webhook.py @@ -203,6 +203,7 @@ class WebhookAdapter(BasePlatformAdapter): "wecom_callback", "weixin", "bluebubbles", + "qqbot", ): return await self._deliver_cross_platform( deliver_type, content, delivery diff --git a/gateway/run.py b/gateway/run.py index a43be2b351f..c8c25256b8f 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -6480,7 +6480,7 @@ class GatewayRunner: Platform.TELEGRAM, Platform.DISCORD, Platform.SLACK, Platform.WHATSAPP, Platform.SIGNAL, Platform.MATTERMOST, Platform.MATRIX, Platform.HOMEASSISTANT, Platform.EMAIL, Platform.SMS, Platform.DINGTALK, - Platform.FEISHU, Platform.WECOM, Platform.WECOM_CALLBACK, Platform.WEIXIN, Platform.BLUEBUBBLES, Platform.LOCAL, + Platform.FEISHU, Platform.WECOM, Platform.WECOM_CALLBACK, Platform.WEIXIN, Platform.BLUEBUBBLES, Platform.QQBOT, Platform.LOCAL, }) async def _handle_debug_command(self, event: MessageEvent) -> str: diff --git a/hermes_cli/dump.py b/hermes_cli/dump.py index 491bf6e2c37..a5207908578 100644 --- a/hermes_cli/dump.py +++ b/hermes_cli/dump.py @@ -131,6 +131,7 @@ def _configured_platforms() -> list[str]: "wecom": "WECOM_BOT_ID", "wecom_callback": "WECOM_CALLBACK_CORP_ID", "weixin": "WEIXIN_ACCOUNT_ID", + "qqbot": "QQ_APP_ID", } return [name for name, env in checks.items() if os.getenv(env)] diff --git a/tests/gateway/test_qqbot.py b/tests/gateway/test_qqbot.py index e92e707c8cd..d3ca5320dd1 100644 --- a/tests/gateway/test_qqbot.py +++ b/tests/gateway/test_qqbot.py @@ -93,7 +93,7 @@ class TestQQAdapterInit: def test_name_property(self): adapter = self._make(app_id="a", client_secret="b") - assert adapter.name == "QQBOT" + assert adapter.name == "QQBot" # ---------------------------------------------------------------------------