feat: add QQ Bot platform adapter (Official API v2)

Add full QQ Bot integration via the Official QQ Bot API (v2):
- WebSocket gateway for inbound events (C2C, group, guild, DM)
- REST API for outbound text/markdown/media messages
- Voice transcription (Tencent ASR + configurable STT provider)
- Attachment processing (images, voice, files)
- User authorization (allowlist + allow-all + DM pairing)

Integration points:
- gateway: Platform.QQ enum, adapter factory, allowlist maps
- CLI: setup wizard, gateway config, status display, tools config
- tools: send_message cross-platform routing, toolsets
- cron: delivery platform support
- docs: QQ Bot setup guide
This commit is contained in:
Junjun Zhang 2026-04-13 21:56:38 +08:00 committed by Teknium
parent eb44abd6b1
commit 87bfc28e70
18 changed files with 2679 additions and 5 deletions

View file

@ -45,6 +45,9 @@ _EXTRA_ENV_KEYS = frozenset({
"WEIXIN_HOME_CHANNEL", "WEIXIN_HOME_CHANNEL_NAME", "WEIXIN_DM_POLICY", "WEIXIN_GROUP_POLICY",
"WEIXIN_ALLOWED_USERS", "WEIXIN_GROUP_ALLOWED_USERS", "WEIXIN_ALLOW_ALL_USERS",
"BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_PASSWORD",
"QQ_APP_ID", "QQ_CLIENT_SECRET",
"QQ_ALLOWED_USERS", "QQ_GROUP_ALLOWED_USERS", "QQ_ALLOW_ALL_USERS",
"QQ_HOME_CHANNEL", "QQ_HOME_CHANNEL_NAME", "QQ_SANDBOX",
"TERMINAL_ENV", "TERMINAL_SSH_KEY", "TERMINAL_SSH_PORT",
"WHATSAPP_MODE", "WHATSAPP_ENABLED",
"MATTERMOST_HOME_CHANNEL", "MATTERMOST_REPLY_MODE",
@ -1331,6 +1334,53 @@ OPTIONAL_ENV_VARS = {
"password": False,
"category": "messaging",
},
"BLUEBUBBLES_ALLOW_ALL_USERS": {
"description": "Allow all BlueBubbles users without allowlist",
"prompt": "Allow All BlueBubbles Users",
"category": "messaging",
},
"QQ_APP_ID": {
"description": "QQ Bot App ID from QQ Open Platform (q.qq.com)",
"prompt": "QQ App ID",
"url": "https://q.qq.com",
"category": "messaging",
},
"QQ_CLIENT_SECRET": {
"description": "QQ Bot Client Secret from QQ Open Platform",
"prompt": "QQ Client Secret",
"password": True,
"category": "messaging",
},
"QQ_ALLOWED_USERS": {
"description": "Comma-separated QQ user IDs allowed to use the bot",
"prompt": "QQ Allowed Users",
"category": "messaging",
},
"QQ_GROUP_ALLOWED_USERS": {
"description": "Comma-separated QQ group IDs allowed to interact with the bot",
"prompt": "QQ Group Allowed Users",
"category": "messaging",
},
"QQ_ALLOW_ALL_USERS": {
"description": "Allow all QQ users without an allowlist (true/false)",
"prompt": "Allow All QQ Users",
"category": "messaging",
},
"QQ_HOME_CHANNEL": {
"description": "Default QQ channel/group for cron delivery and notifications",
"prompt": "QQ Home Channel",
"category": "messaging",
},
"QQ_HOME_CHANNEL_NAME": {
"description": "Display name for the QQ home channel",
"prompt": "QQ Home Channel Name",
"category": "messaging",
},
"QQ_SANDBOX": {
"description": "Enable QQ sandbox mode for development testing (true/false)",
"prompt": "QQ Sandbox Mode",
"category": "messaging",
},
"GATEWAY_ALLOW_ALL_USERS": {
"description": "Allow all users to interact with messaging bots (true/false). Default: false.",
"prompt": "Allow all users (true/false)",

View file

@ -1913,6 +1913,30 @@ _PLATFORMS = [
"help": "Phone number or Apple ID to deliver cron results and notifications to."},
],
},
{
"key": "qq",
"label": "QQ Bot",
"emoji": "💬",
"token_var": "QQ_APP_ID",
"setup_instructions": [
"1. Go to https://open.qq.com/ and create an application",
"2. In the application dashboard, create a QQ Bot",
"3. Note your App ID and App Secret",
"4. Configure the WebSocket Gateway URL in QQ Open Platform settings",
"5. Set up message push URL if needed for event callbacks",
],
"vars": [
{"name": "QQ_APP_ID", "prompt": "App ID", "password": False,
"help": "Paste the App ID from QQ Open Platform."},
{"name": "QQ_CLIENT_SECRET", "prompt": "App Secret", "password": True,
"help": "Paste the App Secret from QQ Open Platform."},
{"name": "QQ_ALLOWED_USERS", "prompt": "Allowed QQ user IDs (comma-separated, or empty for DM pairing)", "password": False,
"is_allowlist": True,
"help": "Optional — pre-authorize specific users. Leave empty to use DM pairing instead."},
{"name": "QQ_HOME_CHANNEL", "prompt": "Home channel (QQ group ID for cron/notifications, or empty)", "password": False,
"help": "QQ group ID to deliver cron results and notifications to."},
],
},
]

View file

@ -35,6 +35,7 @@ PLATFORMS: OrderedDict[str, PlatformInfo] = OrderedDict([
("wecom", PlatformInfo(label="💬 WeCom", default_toolset="hermes-wecom")),
("wecom_callback", PlatformInfo(label="💬 WeCom Callback", default_toolset="hermes-wecom-callback")),
("weixin", PlatformInfo(label="💬 Weixin", default_toolset="hermes-weixin")),
("qq", PlatformInfo(label="💬 QQ", default_toolset="hermes-qq")),
("webhook", PlatformInfo(label="🔗 Webhook", default_toolset="hermes-webhook")),
("api_server", PlatformInfo(label="🌐 API Server", default_toolset="hermes-api-server")),
])

View file

@ -2034,6 +2034,15 @@ def _setup_bluebubbles():
print_info(" Install: https://docs.bluebubbles.app/helper-bundle/installation")
def _setup_qq():
"""Configure QQ Bot (Official API v2) via standard platform setup."""
from hermes_cli.gateway import _PLATFORMS
qq_platform = next((p for p in _PLATFORMS if p["key"] == "qq"), None)
if qq_platform:
from hermes_cli.gateway import _setup_standard_platform
_setup_standard_platform(qq_platform)
def _setup_webhooks():
"""Configure webhook integration."""
print_header("Webhooks")
@ -2097,6 +2106,7 @@ _GATEWAY_PLATFORMS = [
("WeCom Callback (Self-Built App)", "WECOM_CALLBACK_CORP_ID", _setup_wecom_callback),
("Weixin (WeChat)", "WEIXIN_ACCOUNT_ID", _setup_weixin),
("BlueBubbles (iMessage)", "BLUEBUBBLES_SERVER_URL", _setup_bluebubbles),
("QQ Bot", "QQ_APP_ID", _setup_qq),
("Webhooks (GitHub, GitLab, etc.)", "WEBHOOK_ENABLED", _setup_webhooks),
]

View file

@ -305,6 +305,7 @@ def show_status(args):
"WeCom Callback": ("WECOM_CALLBACK_CORP_ID", None),
"Weixin": ("WEIXIN_ACCOUNT_ID", "WEIXIN_HOME_CHANNEL"),
"BlueBubbles": ("BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_HOME_CHANNEL"),
"QQ": ("QQ_APP_ID", "QQ_HOME_CHANNEL"),
}
for name, (token_var, home_var) in platforms.items():

View file

@ -426,6 +426,8 @@ def _get_enabled_platforms() -> List[str]:
enabled.append("slack")
if get_env_value("WHATSAPP_ENABLED"):
enabled.append("whatsapp")
if get_env_value("QQ_APP_ID"):
enabled.append("qq")
return enabled