mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-01 01:10:21 +08:00
feat(qqofficial): allow QQ Official Webhook adapters to proactively send group messages without requiring a cached msg_id (#8841)
* feat(qqofficial): support webhook QR setup * docs(qqofficial): simplify webhook QR setup step
This commit is contained in:
@@ -413,7 +413,7 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
sent_message_id = self._extract_message_id(ret)
|
||||
if sent_message_id:
|
||||
self.remember_session_message_id(session.session_id, sent_message_id)
|
||||
await super().send_by_session(session, message_chain)
|
||||
await Platform.send_by_session(self, session, message_chain)
|
||||
|
||||
def remember_session_message_id(self, session_id: str, message_id: str) -> None:
|
||||
if not session_id or not message_id:
|
||||
|
||||
@@ -123,7 +123,7 @@ class QQOfficialWebhookPlatformAdapter(Platform):
|
||||
self.webhook_helper = None
|
||||
self._session_last_message_id: dict[str, str] = {}
|
||||
self._session_scene: dict[str, str] = {}
|
||||
self._allow_group_proactive_send = False
|
||||
self._allow_group_proactive_send = True
|
||||
|
||||
async def send_by_session(
|
||||
self,
|
||||
|
||||
@@ -99,7 +99,7 @@ class PlatformService:
|
||||
)
|
||||
if platform_type == "dingtalk":
|
||||
return await self._handle_dingtalk_registration(action, payload)
|
||||
if platform_type == "qq_official":
|
||||
if platform_type in {"qq_official", "qq_official_webhook"}:
|
||||
return await self._handle_qqofficial_registration(
|
||||
action,
|
||||
payload,
|
||||
|
||||
@@ -1045,7 +1045,9 @@ export default {
|
||||
return this.selectedPlatformConfig?.type === "dingtalk";
|
||||
},
|
||||
isQqOfficialPlatform() {
|
||||
return this.selectedPlatformConfig?.type === "qq_official";
|
||||
return ["qq_official", "qq_official_webhook"].includes(
|
||||
this.selectedPlatformConfig?.type,
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -77,6 +77,13 @@ const REGISTRATION_ACTIONS = {
|
||||
successKey: 'registrationAction.qqOfficial.created',
|
||||
statusKeyPrefix: 'registrationAction.qqOfficial.status',
|
||||
},
|
||||
qq_official_webhook: {
|
||||
icon: 'mdi-qrcode',
|
||||
titleKey: 'registrationAction.qqOfficial.title',
|
||||
scanTitleKey: 'registrationAction.qqOfficial.scanTitle',
|
||||
successKey: 'registrationAction.qqOfficial.created',
|
||||
statusKeyPrefix: 'registrationAction.qqOfficial.status',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
> [!WARNING]
|
||||
> 1. QQ Official Bot currently requires an IP whitelist.
|
||||
> 2. It supports group chat, private chat, channel chat, and channel private chat.
|
||||
> 3. You need a server with a public IP and a domain.
|
||||
> 2. Webhook mode requires a server with a public IP, a domain, and HTTPS access.
|
||||
> 3. It supports group chat, private chat, channel chat, and channel private chat.
|
||||
|
||||
## Supported Basic Message Types
|
||||
|
||||
@@ -19,7 +19,43 @@
|
||||
|
||||
Proactive message push: Supported.
|
||||
|
||||
## Apply for a Bot
|
||||
## Create a QQ Bot in AstrBot with One-click QR Setup (Recommended)
|
||||
|
||||
### Setup Flow
|
||||
|
||||
1. In AstrBot WebUI, click `Bots` in the left sidebar, then click `+ Create Bot`.
|
||||
2. Select `QQ Official Bot (Webhook)`.
|
||||
3. Under `Choose setup method`, select `One-click QR setup`, click start, then scan the QR code with mobile QQ.
|
||||
4. After confirming the QR binding, click `Save`.
|
||||
5. Configure DNS and a reverse proxy for your server so HTTPS requests are forwarded to AstrBot's `6185` port.
|
||||
6. Go back to the QQ Open Platform bot management page, open `Development -> Callback Configuration`, and enter the Webhook callback URL generated by AstrBot.
|
||||
7. Select the callback events you need. To receive full group messages, make sure the group event `GROUP_MESSAGE_CREATE` is selected.
|
||||
8. Save the callback configuration, then restart AstrBot.
|
||||
|
||||
> [!TIP]
|
||||
> With `Unified Webhook Mode`, AstrBot generates a unique Webhook callback URL automatically. You can find it in the logs or on the bot card in WebUI.
|
||||
|
||||

|
||||
|
||||
### Use in Group Chats
|
||||
|
||||
#### Add to a Group Chat
|
||||
|
||||
Open the created QQ bot profile page (mobile QQ -> Contacts -> Bots tab). You can find `Add to group chat` near the bottom. Currently, the bot can only be added to groups where you are the group owner.
|
||||
|
||||
#### Set Message Access Scope and Proactive Speaking
|
||||
|
||||
In mobile QQ group settings, open the bot settings page. We recommend setting `Messages the bot can access` to `All group messages`, and enabling `Allow the bot to proactively speak in the group`.
|
||||
|
||||
With this configuration, the bot can receive full group messages and proactively push messages to the group, such as scheduled task notifications and plugin notifications.
|
||||
|
||||
Webhook mode also requires selecting the group event `GROUP_MESSAGE_CREATE` in QQ Open Platform callback configuration. Otherwise, AstrBot cannot receive full group message events.
|
||||
|
||||

|
||||
|
||||
## Manually Apply for a QQ Bot (Not Recommended)
|
||||
|
||||
### Apply for a Bot
|
||||
|
||||
Open [QQ Official Bot](https://q.qq.com) and sign in.
|
||||
|
||||
@@ -29,7 +65,7 @@ Open the created bot to enter its management page:
|
||||
|
||||

|
||||
|
||||
## Allow Bot in Channel / Group / Private Chat
|
||||
### Allow Bot in Channel / Group / Private Chat
|
||||
|
||||
Open `Sandbox Configuration` to set a sandbox channel / QQ group / QQ private chat (up to 20 members).
|
||||
|
||||
@@ -37,49 +73,62 @@ Then configure QQ groups, private chat QQ accounts, and QQ channels as needed.
|
||||
|
||||

|
||||
|
||||
## Recommended Group Chat Settings
|
||||
|
||||
In mobile QQ group settings, open the bot settings page. We recommend setting `Messages the bot can access` to `All group messages`, and enabling `Allow the bot to proactively speak in the group`.
|
||||
|
||||
With this configuration, the bot can receive full group messages and proactively push messages to the group, such as scheduled task notifications and plugin notifications.
|
||||
|
||||

|
||||
|
||||
## Get `appid` and `secret`
|
||||
### Get `appid` and `secret`
|
||||
|
||||
After adding the bot where you need it, open `Development -> Development Settings`, then copy `appid` and `secret`.
|
||||
|
||||
## Add IP Whitelist
|
||||
If you use AstrBot WebUI's `One-click QR setup`, you can skip this step. AstrBot fills in `appid` and `secret` automatically after QR binding succeeds.
|
||||
|
||||
### Add IP Whitelist
|
||||
|
||||
Open `Development -> Development Settings`, find IP whitelist, and add your server IP.
|
||||
|
||||

|
||||
|
||||
## Configure in AstrBot
|
||||
> [!TIP]
|
||||
> If you do not know your server IP, run `curl ifconfig.me` or check [ip138.com](https://ip138.com/).
|
||||
>
|
||||
> In NAT environments without a public IP, the observed IP may change depending on your carrier. Use proxy/tunnel if needed.
|
||||
|
||||
### Configure in AstrBot
|
||||
|
||||
1. Open AstrBot Dashboard.
|
||||
2. Click `Bots` in the left sidebar.
|
||||
3. Click `+ Create Bot`.
|
||||
4. Select `qq_official_webhook`.
|
||||
4. Select `QQ Official Bot (Webhook)`.
|
||||
|
||||
Fill in:
|
||||
Recommended: use `One-click QR setup`.
|
||||
|
||||
1. Under `Choose setup method`, select `One-click QR setup`.
|
||||
2. Click start, then scan and confirm the QR code with mobile QQ.
|
||||
3. Wait until the page shows binding success. AstrBot fills in `appid` and `secret` automatically.
|
||||
4. Keep `Unified Webhook Mode` enabled, adjust `ID` and other options as needed, then click `Save`.
|
||||
|
||||
If QR setup is unavailable, choose `Manual setup` and fill in:
|
||||
|
||||
- ID (`id`): any unique identifier.
|
||||
- Enable (`enable`): checked.
|
||||
- `appid`: from QQ Official Bot platform.
|
||||
- `secret`: from QQ Official Bot platform.
|
||||
- Unified Webhook Mode (`unified_webhook_mode`): keep enabled.
|
||||
|
||||
Click `Save`.
|
||||
|
||||
## Configure Callback URL
|
||||
### Configure Reverse Proxy
|
||||
|
||||
In `Development -> Callback Configuration`, configure callback URL.
|
||||
After saving, configure DNS and reverse proxy for your server. Forward requests to AstrBot's `6185` port. If `Unified Webhook Mode` is disabled, forward requests to the port configured in the previous step instead.
|
||||
|
||||
Set request URL to `<your-domain>/astrbot-qo-webhook/callback`.
|
||||
The Webhook callback URL must be reachable from QQ Open Platform over the public internet and must use HTTPS.
|
||||
|
||||
Your domain should reverse-proxy traffic to AstrBot port `6196` using `Caddy`, `Nginx`, or `Apache`.
|
||||
### Configure Callback URL and Events
|
||||
|
||||
Then add callback events and select all four event categories (private, group, channel, etc.).
|
||||
Open `Development -> Callback Configuration`.
|
||||
|
||||
After you save the bot in AstrBot, AstrBot generates a unique Webhook callback URL. You can find it in the logs or on the bot card in WebUI.
|
||||
|
||||
Use that URL as the request URL.
|
||||
|
||||
Then add callback events. To receive full group messages, select the group event `GROUP_MESSAGE_CREATE`; also select private chat events, channel events, and other events as needed.
|
||||
|
||||

|
||||
|
||||
@@ -87,10 +136,6 @@ After entering values, move focus out of the input box to trigger validation. If
|
||||
|
||||
Then restart AstrBot.
|
||||
|
||||
## Done
|
||||
|
||||
AstrBot should now be connected. If messages do not respond immediately, wait 1-2 minutes, restart AstrBot, and test again.
|
||||
|
||||
## Appendix: Reverse Proxy Setup
|
||||
|
||||
If you are new to reverse proxy, Caddy is recommended:
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
|
||||
# 通过 QQ官方机器人 接入 QQ (Webhook)
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> 1. 截至目前,QQ 官方机器人需要设置 IP 白名单。
|
||||
> 2. 支持群聊、私聊、频道聊天、频道私聊。
|
||||
>
|
||||
> **需要**一台带有公网 IP 的服务器和域名(如果没备案,需要服务器在海外或者中国港澳台地区)
|
||||
> 2. Webhook 模式需要一台带公网 IP 的服务器、域名和 HTTPS 访问能力。
|
||||
> 3. 支持群聊、私聊、频道聊天、频道私聊。
|
||||
|
||||
## 支持的基本消息类型
|
||||
|
||||
@@ -22,7 +20,43 @@
|
||||
|
||||
主动消息推送:支持。
|
||||
|
||||
## 申请一个机器人
|
||||
## 在 AstrBot 中扫码一键创建 QQ 机器人(推荐)
|
||||
|
||||
### 配置流程
|
||||
|
||||
1. 进入 AstrBot 的 WebUI,点击左边栏 `机器人`,然后点击 `+ 创建机器人`。
|
||||
2. 选择 `QQ 官方机器人(Webhook)`。
|
||||
3. 在 `选择创建方式` 中选择 `扫码一键创建`,点击开始创建后,用手机 QQ 扫描页面中的二维码。
|
||||
4. 扫码确认后,点击 `保存`。
|
||||
5. 根据服务器环境配置域名 DNS 解析和反向代理,将 HTTPS 请求转发到 AstrBot 所在服务器的 `6185` 端口。
|
||||
6. 回到 QQ 开放平台的机器人管理页,在 `开发 -> 回调配置` 中填写 AstrBot 生成的 Webhook 回调地址。
|
||||
7. 在回调事件中勾选需要接收的事件。如果需要接收群聊全量消息,请确保勾选群事件 `GROUP_MESSAGE_CREATE`。
|
||||
8. 保存回调配置后,重启 AstrBot。
|
||||
|
||||
> [!TIP]
|
||||
> 使用 `统一 Webhook 模式` 时,AstrBot 会自动生成唯一的 Webhook 回调链接。你可以在日志中,或者 WebUI 的机器人卡片上找到该链接。
|
||||
|
||||

|
||||
|
||||
### 在群聊中使用
|
||||
|
||||
#### 添加到群聊
|
||||
|
||||
进入创建的 QQ 机器人的资料页(手机QQ -> 联系人 -> 机器人页签),在下方可以找到 “添加到群聊”。目前只能添加到自己为群主的群聊。
|
||||
|
||||
#### 设置机器人可获取的群聊消息范围和主动发言
|
||||
|
||||
在手机 QQ 的群聊设置中打开机器人设置,推荐将 `机器人可获取的群聊消息范围` 设置为 `获取群内全部消息`,并开启 `机器人主动在群聊内发言`。
|
||||
|
||||
这样机器人可以接收群聊全量消息,也可以在群聊中主动推送消息,例如定时任务推送、插件主动通知等。
|
||||
|
||||
Webhook 模式还需要在 QQ 开放平台的回调配置中勾选群事件 `GROUP_MESSAGE_CREATE`,否则 AstrBot 无法收到群聊全量消息事件。
|
||||
|
||||

|
||||
|
||||
## 手动申请 QQ 机器人(不推荐)
|
||||
|
||||
### 申请一个机器人
|
||||
|
||||
首先,打开 [QQ官方机器人](https://q.qq.com) 并登录。
|
||||
|
||||
@@ -32,7 +66,7 @@
|
||||
|
||||

|
||||
|
||||
## 允许机器人加入频道/群/私聊
|
||||
### 允许机器人加入频道/群/私聊
|
||||
|
||||
点击`沙箱配置`,这允许你立即设置一个沙箱频道/QQ群/QQ私聊,用于拉入机器人(需要小于等于20个人)。
|
||||
|
||||
@@ -40,34 +74,40 @@
|
||||
|
||||

|
||||
|
||||
## 推荐群聊配置
|
||||
|
||||
在手机 QQ 的群聊设置中打开机器人设置,推荐将 `机器人可获取的群聊消息范围` 设置为 `获取群内全部消息`,并开启 `机器人主动在群聊内发言`。
|
||||
|
||||
这样机器人可以接收群聊全量消息,也可以在群聊中主动推送消息,例如定时任务推送、插件主动通知等。
|
||||
|
||||

|
||||
|
||||
## 获取 appid、secret
|
||||
### 获取 appid、secret
|
||||
|
||||
添加机器人到你想用的地方后。
|
||||
|
||||
点击 `开发->开发设置`,找到 appid、secret。复制并保存它们。
|
||||
|
||||
## 添加 IP 白名单
|
||||
如果你使用 AstrBot WebUI 的 `扫码一键创建`,这一步可以跳过。扫码绑定成功后,AstrBot 会自动填入 `appid` 和 `secret`。
|
||||
|
||||
### 添加 IP 白名单
|
||||
|
||||
点击 `开发->开发设置`,找到 IP 白名单。添加你的服务器 IP 地址。
|
||||
|
||||

|
||||
|
||||
## 在 AstrBot 配置
|
||||
> [!TIP]
|
||||
> 如果你不知道你的服务器 IP 地址,可以在终端中输入 `curl ifconfig.me` 来获取。或者登录 [ip138.com](https://ip138.com/) 查看。
|
||||
>
|
||||
> 如果你在没有公网 IP 的环境下,你看到的 IP 是运营商 NAT 的 IP,这个 IP 根据你的运营商的情况可能会随时变化。如有必要,可以配置代理。
|
||||
|
||||
### 在 AstrBot 配置
|
||||
|
||||
1. 进入 AstrBot 的管理面板
|
||||
2. 点击左边栏 `机器人`
|
||||
3. 然后在右边的界面中,点击 `+ 创建机器人`
|
||||
4. 选择 `qq_official_webhook`
|
||||
4. 选择 `QQ 官方机器人(Webhook)`
|
||||
|
||||
弹出的配置项填写:
|
||||
推荐使用 `扫码一键创建`:
|
||||
|
||||
1. 在 `选择创建方式` 中选择 `扫码一键创建`。
|
||||
2. 点击开始创建,用手机 QQ 扫描二维码并确认。
|
||||
3. 等待页面显示绑定成功。AstrBot 会自动填入 `appid` 和 `secret`。
|
||||
4. 保持 `统一 Webhook 模式` 开启,根据需要调整 `ID` 等配置,然后点击 `保存`。
|
||||
|
||||
如果扫码不可用,也可以选择 `手动创建`。弹出的配置项填写:
|
||||
|
||||
- ID(id):随意填写,用于区分不同的消息平台实例。
|
||||
- 启用(enable): 勾选。
|
||||
@@ -77,24 +117,21 @@
|
||||
|
||||
点击 `保存`。
|
||||
|
||||
## 反向代理
|
||||
### 配置反向代理
|
||||
|
||||
保存之后,请根据你的服务器环境,配置域名 DNS 解析和反向代理,将请求转发到 AstrBot 所在服务器的 `6185` 端口 (如果没有开启统一 Webhook 模式,将请求转发到上一步配置指定的端口)。
|
||||
保存之后,请根据你的服务器环境,配置域名 DNS 解析和反向代理,将请求转发到 AstrBot 所在服务器的 `6185` 端口(如果没有开启统一 Webhook 模式,将请求转发到上一步配置指定的端口)。
|
||||
|
||||
## 设置回调地址
|
||||
Webhook 回调地址必须可以被 QQ 开放平台公网访问,并且需要使用 HTTPS。
|
||||
|
||||
在 `开发->回调配置` 处,配置回调地址。
|
||||
### 设置回调地址和事件
|
||||
|
||||
在 `开发 -> 回调配置` 处,配置回调地址。
|
||||
|
||||
上一步点击保存之后,AstrBot 将会自动为你生成唯一的 Webhook 回调链接,你可以在日志中或者 WebUI 的机器人页的卡片上找到。
|
||||
|
||||

|
||||
|
||||
将请求地址填写为该地址。
|
||||
|
||||
> [!TIP]
|
||||
> v4.8.0 之前没有 `统一 Webhook 模式`,则请求地址填写 `<你的域名>/astrbot-qo-webhook/callback`。
|
||||
|
||||
填写好之后,添加事件,四个事件类型都全选:单聊事件、群事件、频道事件等,如下图。
|
||||
填写好之后,添加事件。需要接收群聊全量消息时,请勾选群事件 `GROUP_MESSAGE_CREATE`;同时按需勾选单聊事件、频道事件等。
|
||||
|
||||

|
||||
|
||||
@@ -102,10 +139,6 @@
|
||||
|
||||
接着重启 AstrBot。
|
||||
|
||||
## 🎉 大功告成
|
||||
|
||||
此时,你的 AstrBot 应该已经连接成功。如果发送消息没有反应,请等待一两分钟后重启 AstrBot 再进行确认(测试时发现回调地址不会立即生效)。
|
||||
|
||||
## 附录:如何配置反向代理
|
||||
|
||||
如果你还没有相关经验,这里推荐使用 Caddy 作为反向代理的工具,请参考:
|
||||
|
||||
@@ -255,7 +255,7 @@ async def test_ws_group_send_by_session_with_cached_msg_id_still_omits_msg_id():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_webhook_group_send_by_session_without_cached_msg_id_skips_send():
|
||||
async def test_webhook_group_send_by_session_without_cached_msg_id_omits_msg_id():
|
||||
adapter = QQOfficialWebhookPlatformAdapter(
|
||||
{
|
||||
"id": "qq-official-webhook-test",
|
||||
@@ -276,7 +276,13 @@ async def test_webhook_group_send_by_session_without_cached_msg_id_skips_send():
|
||||
MessageChain(chain=[Plain("webhook proactive hello")]),
|
||||
)
|
||||
|
||||
adapter.client.api.post_group_message.assert_not_awaited()
|
||||
adapter.client.api.post_group_message.assert_awaited_once()
|
||||
kwargs = adapter.client.api.post_group_message.await_args.kwargs
|
||||
assert kwargs["group_openid"] == "group-1"
|
||||
assert kwargs["content"] == "webhook proactive hello"
|
||||
assert "msg_id" not in kwargs
|
||||
assert "msg_seq" in kwargs
|
||||
assert adapter._session_last_message_id["group-1"] == "sent-1"
|
||||
|
||||
|
||||
def test_qqofficial_ws_is_not_excluded_from_segmented_reply():
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import base64
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from Crypto.Cipher import AES
|
||||
@@ -11,6 +12,8 @@ from astrbot.core.platform.sources.qqofficial.login_registration import (
|
||||
generate_qqofficial_bind_key,
|
||||
qqofficial_login_result,
|
||||
)
|
||||
from astrbot.dashboard.services import platform_service
|
||||
from astrbot.dashboard.services.platform_service import PlatformService
|
||||
|
||||
|
||||
def test_generate_qqofficial_bind_key_returns_base64_aes_key():
|
||||
@@ -69,3 +72,40 @@ def test_decrypt_qqofficial_secret_rejects_invalid_payload():
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
decrypt_qqofficial_secret("invalid", bind_key)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qqofficial_webhook_registration_reuses_qr_binding(monkeypatch):
|
||||
async def fake_request_qqofficial_login_qr(platform_config: dict):
|
||||
assert platform_config["type"] == "qq_official_webhook"
|
||||
return SimpleNamespace(
|
||||
task_id="task-1",
|
||||
bind_key="bind-key",
|
||||
qrcode="qr-content",
|
||||
interval=3,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
platform_service,
|
||||
"request_qqofficial_login_qr",
|
||||
fake_request_qqofficial_login_qr,
|
||||
)
|
||||
service = PlatformService.__new__(PlatformService)
|
||||
|
||||
result = await service.handle_platform_registration(
|
||||
"qq_official_webhook",
|
||||
{
|
||||
"action": "start",
|
||||
"platform_config": {"type": "qq_official_webhook"},
|
||||
},
|
||||
)
|
||||
|
||||
assert result == {
|
||||
"status": "pending",
|
||||
"registration_code": "task-1",
|
||||
"task_id": "task-1",
|
||||
"bind_key": "bind-key",
|
||||
"qrcode": "qr-content",
|
||||
"qrcode_img_content": "qr-content",
|
||||
"interval": 3,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user