* fix(qqofficial): support full-group messages and native bot mentions * test(qqofficial): cover full-group form reply targets --------- Co-authored-by: Chin Rock <[email protected]> Co-authored-by: multica-agent <[email protected]>
8.7 KiB
QQOfficial EBA Adapter
Adapter directory: src/langbot/pkg/platform/adapters/qqofficial/
Manifest name: qqofficial-omni
Status: partial migration. The EBA adapter structure, manifest, converters, cache-backed safe APIs, platform API map, unit tests, and direct live probe scaffold are in place. A real QQ Official WebSocket bot on dev.rockchin.top received an inbound user message and drove LangBot into the normal pipeline path; the response path was blocked by the test environment model service returning model_not_found for deepseek-v3.
Config
| Field | Required | Notes |
|---|---|---|
appid |
yes | QQ Official app ID. |
secret |
yes | QQ Official app secret. |
token |
yes | QQ Official callback token. |
enable-webhook |
yes | Uses LangBot unified webhook when true; otherwise uses the QQ WebSocket gateway. |
enable-stream-reply |
yes | Enables C2C streaming replies when supported by the QQ Official endpoint. |
webhook_url |
no | Generated by LangBot and copied into the QQ Official callback settings in webhook mode. |
Events
| Event | Evidence | Notes |
|---|---|---|
message.received |
adapter-live, unit | C2C_MESSAGE_CREATE, DIRECT_MESSAGE_CREATE, GROUP_AT_MESSAGE_CREATE, GROUP_MESSAGE_CREATE, and AT_MESSAGE_CREATE map to common MessageReceivedEvent. Full-group mode has protocol regression coverage; the earlier live probe below covered the original message path. |
message.reaction |
unit | MESSAGE_REACTION_ADD and MESSAGE_REACTION_REMOVE map to common MessageReactionEvent. Live gateway evidence is pending. |
group.member_joined |
unit | GUILD_MEMBER_ADD and GROUP_MEMBER_ADD map to common MemberJoinedEvent when the gateway payload carries a group/guild/channel ID and member openid. |
group.member_left |
unit | GUILD_MEMBER_REMOVE and GROUP_MEMBER_REMOVE map to common MemberLeftEvent. Live gateway evidence is pending. |
bot.invited_to_group |
unit | GUILD_CREATE and GROUP_ADD_ROBOT map to common BotInvitedToGroupEvent. |
bot.removed_from_group |
unit | GUILD_DELETE and GROUP_DEL_ROBOT map to common BotRemovedFromGroupEvent. |
platform.specific |
unit | Unmapped gateway events are emitted as structured PlatformSpecificEvent; live evidence is pending. |
Full-group messages
After a group owner enables QQ's "receive all messages" permission, QQ delivers
GROUP_MESSAGE_CREATE, including messages that mention the bot. Both the EBA
and legacy adapters accept this event over WebSocket and unified Webhook, retain
the sender's member_openid (with the older openid alias as fallback), and use
the original message ID for passive group replies.
Ordinary messages and images do not receive a bot At component. A full-group
message receives that component only when QQ supplies a boolean mentions[].is_you
equal to true and a matching native <@ID> or <@!ID> token in its text. Only
the bot's own token is removed; mentions of other members remain text. Nicknames,
bot: true alone, and a text that looks like a mention do not authorize a reply.
The EBA component targets the adapter's configured bot account so existing
is_at_bot route filters continue to match.
Default EBA group routes requiring is_at_bot still exclude unmentioned group
messages. Custom event routes or plugin subscriptions may opt into ordinary
messages. This change does not alter QQ permissions, existing bot configuration,
event bindings, or plugin subscriptions.
tests/unit_tests/platform/test_qqofficial_full_group_messages.py exercises the
real adapters and shared QQ parser with external gateway/download/send transports
substituted. It covers text, images, self/other/unverified mentions, sender aliases,
legacy @-only mode, private messages, and passive reply targets. This is protocol
regression coverage, not a claim of a fresh live QQ probe of the whole upstream build.
Common APIs
| API | Evidence | Notes |
|---|---|---|
send_message |
unit, blocked | Sends private C2C, group, and text-only channel messages through the existing QQ Official client. Live outbound UI verification is pending because the test pipeline failed before producing a bot response. |
reply_message |
unit, blocked | Replies using the source QQOfficialEvent message ID when available. Live reply was blocked by the test environment model service returning model_not_found. |
get_message |
unit | Returns cached inbound MessageReceivedEvent. |
get_user_info |
unit | Returns cached inbound sender. |
get_friend_list |
unit | Returns cached private senders. |
get_group_info |
unit | Returns cached group/channel metadata from inbound events. |
get_group_member_info |
unit | Returns cached group sender as a common member. |
get_group_member_list |
unit | Returns cached group members observed by the adapter. |
call_platform_api |
unit, blocked | Safe diagnostic actions are implemented; live calls are pending credentials. |
Platform APIs
| Action | Evidence | Notes |
|---|---|---|
check_access_token |
unit, blocked | Calls the existing client token check. |
refresh_access_token |
unit, blocked | Forces token refresh. |
get_gateway_url |
unit, blocked | Fetches the WebSocket gateway URL. |
get_mode |
unit | Returns webhook and stream-reply mode. |
Components
| Receive Component | Evidence | Notes |
|---|---|---|
Source |
unit | Uses QQ message/event IDs and timestamp. |
Plain |
unit | Preserves text content. |
At |
unit | Group and channel mention events insert an adapter bot mention marker. |
Image |
unit | QQ image attachment URL is converted to common Image; falls back to URL if download fails. |
Unknown |
unit | Unsupported/empty native payloads become Unknown. |
Voice, File, Quote, Face, Forward, mixed chain |
blocked | Current native parser only exposes text and image attachments; live endpoint behavior still needs verification. |
| Send Component | Evidence | Notes |
|---|---|---|
Plain |
unit, blocked | Sends through private, group, or channel text APIs. |
At, AtAll |
unit, blocked | Converted to readable mention text. |
Image |
unit, blocked | Sends through the QQ Official rich media upload/send path for C2C and group targets. |
Voice |
unit, blocked | Sends through the QQ Official rich media upload/send path for C2C and group targets. |
File |
unit, blocked | Sends through the QQ Official rich media upload/send path for C2C and group targets. |
Quote, Forward, mixed chain |
unit, blocked | Flattened to ordered send payloads where possible. |
Face |
not-supported | No common QQ Official face mapping is implemented. |
Verification Record
Test date: 2026-06-02
Endpoint/simulator: dev.rockchin.top with a real QQ Official WebSocket bot (qqofficial-omni, bot UUID 80a5560b-52b1-40e7-b7d6-4a2341eb4780) and LangBot running from /home/wgc/LangBotxg/LangBotEbaTest.
Observed evidence:
- The QQ Official WebSocket bot was enabled with
enable-webhook=false. - A real user message reached LangBot and entered the standard pipeline path.
- The response path stopped at the model layer with
model_not_foundfordeepseek-v3; this is a model/provider configuration issue, not an adapter conversion failure. qq-webhook.langbot.devwas temporarily routed through Caddy to127.0.0.1:5301for webhook checks, but the observed EBA bot used WebSocket mode.
Standalone runtime command:
cd langbot-plugin-sdk
uv run python -m langbot_plugin.cli.__init__ rt --debug-only --ws-control-port 5400 --ws-debug-port 5401 --skip-deps-check
Probe plugin: data/plugins/LangBot__EBAEventProbe when live credentials are available.
Adapter live probe:
uv run python -m py_compile tests/e2e/live_qqofficial_eba_probe.py
QQOFFICIAL_APPID=... QQOFFICIAL_SECRET=... QQOFFICIAL_TOKEN=... uv run python tests/e2e/live_qqofficial_eba_probe.py
Webhook-mode probe:
QQOFFICIAL_APPID=... QQOFFICIAL_SECRET=... QQOFFICIAL_TOKEN=... uv run python tests/e2e/live_qqofficial_eba_probe.py --webhook --host 0.0.0.0 --port 5312
Evidence JSONL path: data/temp/qqofficial_eba_probe.jsonl for direct adapter live probe; plugin E2E evidence should use data/temp/qqofficial_eba_plugin_probe.jsonl.
Destructive operations: none implemented.
Blocked items:
plugin-e2e-ui: standalone probe plugin JSONL evidence is still pending; the observed live run reached LangBot core/pipeline but was not recorded by the EBA probe plugin.plugin-e2e-outbound: waiting for visible QQ client verification of pluginsend_message/reply_messageoutput after a working model/provider is configured.- Inbound non-text media and platform lifecycle events require endpoint evidence before they can be marked complete.