Files
LangBot/dev-docs/event-based-agents/adapters/qqofficial.md
T
0c99832298 fix(qqofficial): receive full-group messages and preserve bot mentions (#2769)
* 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]>
2026-10-28 09:57:00 +08:00

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_found for deepseek-v3; this is a model/provider configuration issue, not an adapter conversion failure.
  • qq-webhook.langbot.dev was temporarily routed through Caddy to 127.0.0.1:5301 for 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 plugin send_message/reply_message output after a working model/provider is configured.
  • Inbound non-text media and platform lifecycle events require endpoint evidence before they can be marked complete.