From d2206c69cc628752c6cdbaa53f8f178c331c6f30 Mon Sep 17 00:00:00 2001 From: Teknium Date: Fri, 17 Apr 2026 15:29:15 -0700 Subject: [PATCH] fix(qqbot): add back-compat for env var rename; drop qrcode core dep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to WideLee's salvaged PR #11582. Back-compat for QQ_HOME_CHANNEL → QQBOT_HOME_CHANNEL rename: - gateway/config.py reads QQBOT_HOME_CHANNEL, falls back to QQ_HOME_CHANNEL with a one-shot deprecation warning so users on the old name aren't silently broken. - cron/scheduler.py: _HOME_TARGET_ENV_VARS['qqbot'] now maps to the new name; _get_home_target_chat_id falls back to the legacy name via a _LEGACY_HOME_TARGET_ENV_VARS table. - hermes_cli/status.py + hermes_cli/setup.py: honor both names when displaying or checking for missing home channels. - hermes_cli/config.py: keep legacy QQ_HOME_CHANNEL[_NAME] in _EXTRA_ENV_KEYS so .env sanitization still recognizes them. Scope cleanup: - Drop qrcode from core dependencies and requirements.txt (remains in messaging/dingtalk/feishu extras). _qqbot_render_qr already degrades gracefully when qrcode is missing, printing a 'pip install qrcode' tip and falling back to URL-only display. - Restore @staticmethod on QQAdapter._detect_message_type (it doesn't use self). Revert the test change that was only needed when it was converted to an instance method. - Reset uv.lock to origin/main; the PR's stale lock also included unrelated changes (atroposlib source URL, hermes-agent version bump, fastapi additions) that don't belong. Verified E2E: - Existing user (QQ_HOME_CHANNEL set): gateway + cron both pick up the legacy name; deprecation warning logs once. - Fresh user (QQBOT_HOME_CHANNEL set): gateway + cron use new name, no warning. - Both set: new name wins on both surfaces. Targeted tests: 296 passed, 4 skipped (qqbot + cron + hermes_cli). --- cron/scheduler.py | 17 +++++++++++-- gateway/config.py | 14 +++++++++- gateway/platforms/qqbot/adapter.py | 3 ++- hermes_cli/config.py | 1 + hermes_cli/setup.py | 4 ++- hermes_cli/status.py | 3 +++ pyproject.toml | 2 -- requirements.txt | 1 - tests/gateway/test_qqbot.py | 3 +-- uv.lock | 41 +++++++----------------------- 10 files changed, 47 insertions(+), 42 deletions(-) diff --git a/cron/scheduler.py b/cron/scheduler.py index 28c905713..db5991c6f 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -65,7 +65,15 @@ _HOME_TARGET_ENV_VARS = { "wecom": "WECOM_HOME_CHANNEL", "weixin": "WEIXIN_HOME_CHANNEL", "bluebubbles": "BLUEBUBBLES_HOME_CHANNEL", - "qqbot": "QQ_HOME_CHANNEL", + "qqbot": "QQBOT_HOME_CHANNEL", +} + +# Legacy env var names kept for back-compat. Each entry is the current +# primary env var → the previous name. _get_home_target_chat_id falls +# back to the legacy name if the primary is unset, so users who set the +# old name before the rename keep working until they migrate. +_LEGACY_HOME_TARGET_ENV_VARS = { + "QQBOT_HOME_CHANNEL": "QQ_HOME_CHANNEL", } from cron.jobs import get_due_jobs, mark_job_run, save_job_output, advance_next_run @@ -100,7 +108,12 @@ def _get_home_target_chat_id(platform_name: str) -> str: env_var = _HOME_TARGET_ENV_VARS.get(platform_name.lower()) if not env_var: return "" - return os.getenv(env_var, "") + value = os.getenv(env_var, "") + if not value: + legacy = _LEGACY_HOME_TARGET_ENV_VARS.get(env_var) + if legacy: + value = os.getenv(legacy, "") + return value def _resolve_single_delivery_target(job: dict, deliver_value: str) -> Optional[dict]: diff --git a/gateway/config.py b/gateway/config.py index d6a196e60..2d7407323 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -1230,11 +1230,23 @@ def _apply_env_overrides(config: GatewayConfig) -> None: if qq_group_allowed: extra["group_allow_from"] = qq_group_allowed qq_home = os.getenv("QQBOT_HOME_CHANNEL", "").strip() + qq_home_name_env = "QQBOT_HOME_CHANNEL_NAME" + if not qq_home: + # Back-compat: accept the pre-rename name and log a one-time warning. + legacy_home = os.getenv("QQ_HOME_CHANNEL", "").strip() + if legacy_home: + qq_home = legacy_home + qq_home_name_env = "QQ_HOME_CHANNEL_NAME" + import logging + logging.getLogger(__name__).warning( + "QQ_HOME_CHANNEL is deprecated; rename to QQBOT_HOME_CHANNEL " + "in your .env for consistency with the platform key." + ) if qq_home: config.platforms[Platform.QQBOT].home_channel = HomeChannel( platform=Platform.QQBOT, chat_id=qq_home, - name=os.getenv("QQBOT_HOME_CHANNEL_NAME", "Home"), + name=os.getenv("QQBOT_HOME_CHANNEL_NAME") or os.getenv(qq_home_name_env, "Home"), ) # Session settings diff --git a/gateway/platforms/qqbot/adapter.py b/gateway/platforms/qqbot/adapter.py index 5a0483453..ced744271 100644 --- a/gateway/platforms/qqbot/adapter.py +++ b/gateway/platforms/qqbot/adapter.py @@ -1072,7 +1072,8 @@ class QQAdapter(BasePlatformAdapter): # Attachment processing # ------------------------------------------------------------------ - def _detect_message_type(self, media_urls: list, media_types: list): + @staticmethod + def _detect_message_type(media_urls: list, media_types: list): """Determine MessageType from attachment content types.""" if not media_urls: return MessageType.TEXT diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 156e99f2d..1670156b2 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -45,6 +45,7 @@ _EXTRA_ENV_KEYS = frozenset({ "WEIXIN_ALLOWED_USERS", "WEIXIN_GROUP_ALLOWED_USERS", "WEIXIN_ALLOW_ALL_USERS", "BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_PASSWORD", "QQ_APP_ID", "QQ_CLIENT_SECRET", "QQBOT_HOME_CHANNEL", "QQBOT_HOME_CHANNEL_NAME", + "QQ_HOME_CHANNEL", "QQ_HOME_CHANNEL_NAME", # legacy aliases (pre-rename, still read for back-compat) "QQ_ALLOWED_USERS", "QQ_GROUP_ALLOWED_USERS", "QQ_ALLOW_ALL_USERS", "QQ_MARKDOWN_SUPPORT", "QQ_STT_API_KEY", "QQ_STT_BASE_URL", "QQ_STT_MODEL", "TERMINAL_ENV", "TERMINAL_SSH_KEY", "TERMINAL_SSH_PORT", diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 9c0ee0bff..6b4fc5d73 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -2215,7 +2215,9 @@ def setup_gateway(config: dict): missing_home.append("Slack") if get_env_value("BLUEBUBBLES_SERVER_URL") and not get_env_value("BLUEBUBBLES_HOME_CHANNEL"): missing_home.append("BlueBubbles") - if get_env_value("QQ_APP_ID") and not get_env_value("QQBOT_HOME_CHANNEL"): + if get_env_value("QQ_APP_ID") and not ( + get_env_value("QQBOT_HOME_CHANNEL") or get_env_value("QQ_HOME_CHANNEL") + ): missing_home.append("QQBot") if missing_home: diff --git a/hermes_cli/status.py b/hermes_cli/status.py index 8fafbc2f4..bc3290d56 100644 --- a/hermes_cli/status.py +++ b/hermes_cli/status.py @@ -327,6 +327,9 @@ def show_status(args): home_channel = "" if home_var: home_channel = os.getenv(home_var, "") + # Back-compat: QQBot home channel was renamed from QQ_HOME_CHANNEL to QQBOT_HOME_CHANNEL + if not home_channel and home_var == "QQBOT_HOME_CHANNEL": + home_channel = os.getenv("QQ_HOME_CHANNEL", "") status = "configured" if has_token else "not configured" if home_channel: diff --git a/pyproject.toml b/pyproject.toml index d97c10810..0cac0b6b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,8 +34,6 @@ dependencies = [ "edge-tts>=7.2.7,<8", # Skills Hub (GitHub App JWT auth — optional, only needed for bot identity) "PyJWT[crypto]>=2.12.0,<3", # CVE-2026-32597 - # QR code rendering for scan-to-configure flows - "qrcode>=7.4,<9", ] [project.optional-dependencies] diff --git a/requirements.txt b/requirements.txt index 74f42d6c8..96f48e77f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,4 +34,3 @@ croniter python-telegram-bot[webhooks]>=22.6 discord.py>=2.0 aiohttp>=3.9.0 -qrcode diff --git a/tests/gateway/test_qqbot.py b/tests/gateway/test_qqbot.py index 6446762ec..a5aeb6251 100644 --- a/tests/gateway/test_qqbot.py +++ b/tests/gateway/test_qqbot.py @@ -316,8 +316,7 @@ class TestResolveSTTConfig: class TestDetectMessageType: def _fn(self, media_urls, media_types): from gateway.platforms.qqbot import QQAdapter - adapter = QQAdapter(_make_config(app_id="a", client_secret="b")) - return adapter._detect_message_type(media_urls, media_types) + return QQAdapter._detect_message_type(media_urls, media_types) def test_no_media(self): from gateway.platforms.base import MessageType diff --git a/uv.lock b/uv.lock index fa6785aa5..45efc2d93 100644 --- a/uv.lock +++ b/uv.lock @@ -300,7 +300,7 @@ wheels = [ [[package]] name = "atroposlib" version = "0.4.0" -source = { git = "https://github.com/NousResearch/atropos.git?rev=c20c85256e5a45ad31edf8b7276e9c5ee1995a30#c20c85256e5a45ad31edf8b7276e9c5ee1995a30" } +source = { git = "https://github.com/NousResearch/atropos.git#c421582b6f7ce8a32f751aab3117d3824ac8f709" } dependencies = [ { name = "aiofiles" }, { name = "aiohttp" }, @@ -1699,7 +1699,7 @@ wheels = [ [[package]] name = "hermes-agent" -version = "0.9.0" +version = "0.8.0" source = { editable = "." } dependencies = [ { name = "anthropic" }, @@ -1717,7 +1717,6 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "python-dotenv" }, { name = "pyyaml" }, - { name = "qrcode" }, { name = "requests" }, { name = "rich" }, { name = "tenacity" }, @@ -1738,7 +1737,6 @@ all = [ { name = "dingtalk-stream" }, { name = "discord-py", extra = ["voice"] }, { name = "elevenlabs" }, - { name = "fastapi" }, { name = "faster-whisper" }, { name = "honcho-ai" }, { name = "lark-oapi" }, @@ -1758,7 +1756,6 @@ all = [ { name = "slack-bolt" }, { name = "slack-sdk" }, { name = "sounddevice" }, - { name = "uvicorn", extra = ["standard"] }, ] cli = [ { name = "simple-term-menu" }, @@ -1845,10 +1842,6 @@ voice = [ { name = "numpy" }, { name = "sounddevice" }, ] -web = [ - { name = "fastapi" }, - { name = "uvicorn", extra = ["standard"] }, -] yc-bench = [ { name = "yc-bench", marker = "python_full_version >= '3.12'" }, ] @@ -1862,7 +1855,7 @@ requires-dist = [ { name = "aiosqlite", marker = "extra == 'matrix'", specifier = ">=0.20" }, { name = "anthropic", specifier = ">=0.39.0,<1" }, { name = "asyncpg", marker = "extra == 'matrix'", specifier = ">=0.29" }, - { name = "atroposlib", marker = "extra == 'rl'", git = "https://github.com/NousResearch/atropos.git?rev=c20c85256e5a45ad31edf8b7276e9c5ee1995a30" }, + { name = "atroposlib", marker = "extra == 'rl'", git = "https://github.com/NousResearch/atropos.git" }, { name = "croniter", marker = "extra == 'cron'", specifier = ">=6.0.0,<7" }, { name = "daytona", marker = "extra == 'daytona'", specifier = ">=0.148.0,<1" }, { name = "debugpy", marker = "extra == 'dev'", specifier = ">=1.8.0,<2" }, @@ -1873,7 +1866,6 @@ requires-dist = [ { name = "exa-py", specifier = ">=2.9.0,<3" }, { name = "fal-client", specifier = ">=0.13.1,<1" }, { name = "fastapi", marker = "extra == 'rl'", specifier = ">=0.104.0,<1" }, - { name = "fastapi", marker = "extra == 'web'", specifier = ">=0.104.0,<1" }, { name = "faster-whisper", marker = "extra == 'voice'", specifier = ">=1.0.0,<2" }, { name = "fire", specifier = ">=0.7.1,<1" }, { name = "firecrawl-py", specifier = ">=4.16.0,<5" }, @@ -1902,7 +1894,6 @@ requires-dist = [ { name = "hermes-agent", extras = ["sms"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["tts-premium"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["voice"], marker = "extra == 'all'" }, - { name = "hermes-agent", extras = ["web"], marker = "extra == 'all'" }, { name = "honcho-ai", marker = "extra == 'honcho'", specifier = ">=2.0.1,<3" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1" }, { name = "jinja2", specifier = ">=3.1.5,<4" }, @@ -1927,7 +1918,6 @@ requires-dist = [ { name = "python-telegram-bot", extras = ["webhooks"], marker = "extra == 'messaging'", specifier = ">=22.6,<23" }, { name = "pywinpty", marker = "sys_platform == 'win32' and extra == 'pty'", specifier = ">=2.0.0,<3" }, { name = "pyyaml", specifier = ">=6.0.2,<7" }, - { name = "qrcode", specifier = ">=7.4,<9" }, { name = "requests", specifier = ">=2.33.0,<3" }, { name = "rich", specifier = ">=14.3.3,<15" }, { name = "simple-term-menu", marker = "extra == 'cli'", specifier = ">=1.0,<2" }, @@ -1937,13 +1927,12 @@ requires-dist = [ { name = "slack-sdk", marker = "extra == 'slack'", specifier = ">=3.27.0,<4" }, { name = "sounddevice", marker = "extra == 'voice'", specifier = ">=0.4.6,<1" }, { name = "tenacity", specifier = ">=9.1.4,<10" }, - { name = "tinker", marker = "extra == 'rl'", git = "https://github.com/thinking-machines-lab/tinker.git?rev=30517b667f18a3dfb7ef33fb56cf686d5820ba2b" }, + { name = "tinker", marker = "extra == 'rl'", git = "https://github.com/thinking-machines-lab/tinker.git" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'rl'", specifier = ">=0.24.0,<1" }, - { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = ">=0.24.0,<1" }, { name = "wandb", marker = "extra == 'rl'", specifier = ">=0.15.0,<1" }, - { name = "yc-bench", marker = "python_full_version >= '3.12' and extra == 'yc-bench'", git = "https://github.com/collinear-ai/yc-bench.git?rev=bfb0c88062450f46341bd9a5298903fc2e952a5c" }, + { name = "yc-bench", marker = "python_full_version >= '3.12' and extra == 'yc-bench'", git = "https://github.com/collinear-ai/yc-bench.git" }, ] -provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "web", "rl", "yc-bench", "all"] +provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "rl", "yc-bench", "all"] [[package]] name = "hf-transfer" @@ -4171,18 +4160,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] -[[package]] -name = "qrcode" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, -] - [[package]] name = "referencing" version = "0.37.0" @@ -4799,8 +4776,8 @@ wheels = [ [[package]] name = "tinker" -version = "0.18.0" -source = { git = "https://github.com/thinking-machines-lab/tinker.git?rev=30517b667f18a3dfb7ef33fb56cf686d5820ba2b#30517b667f18a3dfb7ef33fb56cf686d5820ba2b" } +version = "0.16.1" +source = { git = "https://github.com/thinking-machines-lab/tinker.git#07bd3c2dd3cd4398ac1c26f0ec0deccbf3c1f913" } dependencies = [ { name = "anyio" }, { name = "click" }, @@ -5513,7 +5490,7 @@ wheels = [ [[package]] name = "yc-bench" version = "0.1.0" -source = { git = "https://github.com/collinear-ai/yc-bench.git?rev=bfb0c88062450f46341bd9a5298903fc2e952a5c#bfb0c88062450f46341bd9a5298903fc2e952a5c" } +source = { git = "https://github.com/collinear-ai/yc-bench.git#0c53c98f01a431db2e391482bc46013045854ab2" } dependencies = [ { name = "litellm", marker = "python_full_version >= '3.12'" }, { name = "matplotlib", marker = "python_full_version >= '3.12'" },