dsh-im-union 0.4.12 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dsh.plugin.json +1 -1
- package/lib/channels/meta.js +6 -1
- package/lib/channels/qq-group-admin.d.ts +35 -0
- package/lib/channels/qq-group-admin.js +43 -0
- package/lib/channels/qq.d.ts +102 -9
- package/lib/channels/qq.js +694 -62
- package/lib/engine/account-manager.d.ts +5 -0
- package/lib/engine/account-manager.js +14 -0
- package/lib/engine/commands.d.ts +3 -1
- package/lib/engine/commands.js +8 -1
- package/lib/engine/gateway.d.ts +10 -0
- package/lib/engine/gateway.js +121 -2
- package/package.json +1 -1
- package/src/channels/meta.ts +6 -1
- package/src/channels/qq-group-admin.ts +54 -0
- package/src/channels/qq.ts +726 -57
- package/src/engine/account-manager.ts +15 -0
- package/src/engine/commands.ts +9 -0
- package/src/engine/gateway.ts +131 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
本项目版本号遵循语义化版本;发布面与宿主兼容矩阵见 `scripts/hosts.mjs`(README 管理块同源)。
|
|
4
4
|
|
|
5
|
+
## 0.5.0(QQ 渠道能力补齐:出站图片 / Markdown / 入站图片文件 / 审批按钮 / 群管理)
|
|
6
|
+
|
|
7
|
+
对照 `_ref-im/dsh-qqbot`(同一套 QQ 官方 api-v2)逐面解锁 `src/channels/qq.ts`,遵守净室 / 零运行时依赖 / D7 纪律。
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **出站图片 `caps.fileOut` + `sendFile`**:上传换 file_info → msg_type=7 携带 **`media: { file_info }`**(体形按官方 `@tencent-connect/qqbot-nodejs` 1.0.5 `sendMediaMessage` 对账,非旧式 `image: file_info`);上传采用**双形态递降**:先官方 SDK 小文件形态(JSON + base64 `file_data`,`file_type=1`、`srv_send_msg=false`),被平台确定性拒绝再回退官方文档记载的 multipart/form-data 形态一次(网络不确定绝不重试,D7 免白占配额);群/单聊分 `/files` 端点,窗口内带 msg_id/seq;一举激活网关 SVG→PNG 附图链、产物回传与 `/export`。非图片 mime(zip/文件)优雅拒绝(官方 file_type=4 未开放);304080/40093002 确定性转译
|
|
12
|
+
- **Markdown 出站 msg_type=2**:正文含 `` 经宿主 `workspaceFiles` 受管读(workspaceRoot 限定,适配器不碰盘)取字节上传、回替 `qqbot-media://` url;非 QQ/无本地图/读失败/上传失败均原样保留
|
|
14
|
+
- **入站图片/文件 `caps.imageIn/fileIn`**:事件 `attachments[].url`(rkey 时效)即时下载填 `images[]/files[]`(含 mimeType);群聊图片 content_type 实测为 'file' → 兼用 URL 形态判定;无 arrayBuffer/非 2xx/空字节按占位不丢事件;入站派发改串行链保序(异步下载不打乱消息顺序)
|
|
15
|
+
- **审批/提问按钮 `askChoices`(keyboard)**:msg_type=2 + **`keyboard: { content: { rows } }`**(官方 InlineKeyboard 包装层,少一层平台不渲染),action.type=1 回传;收到 INTERACTION_CREATE 后**best-effort `PUT /interactions/{id} {code:0}` 上确认**再结算;D5 仅接受发起者点按;`config.keyboard=on` 才订阅 INTERACTION intent 并置 `nativeChoices=true`(默认关=零风险,未配置回退数字菜单)
|
|
16
|
+
- **语音入站免解码**:优先取事件原生 `asr_refer_text` 作正文,无则占位;不引入 silk 解码依赖
|
|
17
|
+
- **QQ 群管理**:新建 `src/channels/qq-group-admin.ts`(FEATURE_GATES 门控 + 端点/错误码人话映射),QqAdapter 暴露入群申请列表/审批、群禁言查询/设置、消息撤回;经 `/qq` 命令面可达,仅群聊、作用于本群、需 `config.groupAdminUsers` 授权名单(空=命令惰性拒绝)+ 机器人为群管理员
|
|
18
|
+
|
|
19
|
+
### Notes
|
|
20
|
+
|
|
21
|
+
- 撤回/编辑 `editStream`、原生流式 `nativeStream` 本轮不做(协议语义未逐面核实、与 dsh-qqbot 打平)
|
|
22
|
+
- 全量 753 单测绿(QQ +8);真机农场冒烟需 QQ 凭据待验
|
|
23
|
+
|
|
5
24
|
## 0.4.12(飞书审批卡片:权限 ask 走按钮确认)
|
|
6
25
|
|
|
7
26
|
### Added
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-im-union",
|
|
3
3
|
"name": "dsh-im-union",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Union IM bridge for DeepSeek Harness: multi-account bots across messaging platforms, each chat bound to its own session, with cross-host-version compatibility (0.1.2-rc.1 .. 0.2.1-rc.3).",
|
|
7
7
|
"zh": "DeepSeek Harness 的 IM 联合桥:多平台多账号机器人、每个聊天绑定独立会话,跨宿主版本兼容(0.1.2-rc.1 至 0.2.1-rc.3)。"
|
package/lib/channels/meta.js
CHANGED
|
@@ -70,7 +70,12 @@ export const CHANNEL_META = {
|
|
|
70
70
|
id: 'qq',
|
|
71
71
|
label: { zh: 'QQ', en: 'QQ' },
|
|
72
72
|
bind: 'qr-or-credentials',
|
|
73
|
-
fields: [
|
|
73
|
+
fields: [
|
|
74
|
+
cred('appId', 'AppID', 'AppID'),
|
|
75
|
+
cred('appSecret', 'AppSecret', 'AppSecret'),
|
|
76
|
+
plain('keyboard', '审批/提问按钮面 on/off(需机器人已开通交互事件与按钮模板)', 'Approval/question keyboard on/off (requires interaction events + button template)'),
|
|
77
|
+
plain('groupAdminUsers', '群管理(/qq)授权成员 openid(逗号分隔;空=禁用)', 'Group-admin (/qq) authorized member openids (comma separated; empty=disabled)'),
|
|
78
|
+
],
|
|
74
79
|
},
|
|
75
80
|
slack: {
|
|
76
81
|
id: 'slack',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ 普通群管理 —— 能力门控元数据与人话映射(P6/F6)。
|
|
3
|
+
*
|
|
4
|
+
* 依据 QQ 官方 api-v2「群聊管理」接口的开放度分档(2026 实测,与 dsh-qqbot 侧同源事实核对):
|
|
5
|
+
* 🔴 未开放(内邀中):成员列表 / 成员信息 —— 调用得 11253(仅白名单/未开放);
|
|
6
|
+
* 🟡 门槛=机器人为群管理员:入群申请列表/审批、群禁言查询/设置、群消息撤回(2 分钟窗口);
|
|
7
|
+
* 🟢 踢人(batch_remove_members):2026-10-20 起后台「权限」页可勾选,按错误码动态裁决、不硬关。
|
|
8
|
+
*
|
|
9
|
+
* 本文件只放「门控表 + 端点常量 + 错误码→人话」这类纯数据/纯函数,实际 REST 由 QqAdapter 复用
|
|
10
|
+
* 其既有 token/http 面发起(见 qq.ts 群管方法),避免另起一套凭证缓存。
|
|
11
|
+
*
|
|
12
|
+
* 端点(base=https://api.bot.qq.com,鉴权头 QQBot {access_token}):
|
|
13
|
+
* - 入群申请列表 GET /v2/groups/{gid}/join_request_list
|
|
14
|
+
* - 审批入群申请 POST /v2/groups/{gid}/approval_join_request/{member_openid} {op:'approve'|'decline', join_request_id?}
|
|
15
|
+
* - 群禁言查询 GET /v2/groups/{gid}/restrict_chat_setting
|
|
16
|
+
* - 设置群禁言 POST /v2/groups/{gid}/restrict_chat_setting {members:[{op:'add'|'del', member_openid, mute_expire_at}]}
|
|
17
|
+
* - 撤回群消息 DELETE /v2/groups/{gid}/messages/{message_id} (仅机器人自己发的,窗口 2 分钟)
|
|
18
|
+
*/
|
|
19
|
+
/** 群管能力门控:UI/命令据此裁决;官方开放后把对应项 open 改 true 即可点亮,未开门一律拒绝不猜发。 */
|
|
20
|
+
export interface QqGroupGate {
|
|
21
|
+
readonly open: boolean;
|
|
22
|
+
readonly human: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const QQ_GROUP_FEATURE_GATES: Record<string, QqGroupGate>;
|
|
25
|
+
/** 官方群管/权限相关错误码 → 面向用户的一句话(人话),映射不到回落通用文案。 */
|
|
26
|
+
export declare function qqGroupErrHuman(code: string | number): string;
|
|
27
|
+
/** 单个群管 REST 调用的返回形态:成功带 data,失败带 code + human(UI/命令直接展示 human)。 */
|
|
28
|
+
export type QqGroupResult<T> = {
|
|
29
|
+
ok: true;
|
|
30
|
+
data: T;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
code: string;
|
|
34
|
+
human: string;
|
|
35
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ 普通群管理 —— 能力门控元数据与人话映射(P6/F6)。
|
|
3
|
+
*
|
|
4
|
+
* 依据 QQ 官方 api-v2「群聊管理」接口的开放度分档(2026 实测,与 dsh-qqbot 侧同源事实核对):
|
|
5
|
+
* 🔴 未开放(内邀中):成员列表 / 成员信息 —— 调用得 11253(仅白名单/未开放);
|
|
6
|
+
* 🟡 门槛=机器人为群管理员:入群申请列表/审批、群禁言查询/设置、群消息撤回(2 分钟窗口);
|
|
7
|
+
* 🟢 踢人(batch_remove_members):2026-10-20 起后台「权限」页可勾选,按错误码动态裁决、不硬关。
|
|
8
|
+
*
|
|
9
|
+
* 本文件只放「门控表 + 端点常量 + 错误码→人话」这类纯数据/纯函数,实际 REST 由 QqAdapter 复用
|
|
10
|
+
* 其既有 token/http 面发起(见 qq.ts 群管方法),避免另起一套凭证缓存。
|
|
11
|
+
*
|
|
12
|
+
* 端点(base=https://api.bot.qq.com,鉴权头 QQBot {access_token}):
|
|
13
|
+
* - 入群申请列表 GET /v2/groups/{gid}/join_request_list
|
|
14
|
+
* - 审批入群申请 POST /v2/groups/{gid}/approval_join_request/{member_openid} {op:'approve'|'decline', join_request_id?}
|
|
15
|
+
* - 群禁言查询 GET /v2/groups/{gid}/restrict_chat_setting
|
|
16
|
+
* - 设置群禁言 POST /v2/groups/{gid}/restrict_chat_setting {members:[{op:'add'|'del', member_openid, mute_expire_at}]}
|
|
17
|
+
* - 撤回群消息 DELETE /v2/groups/{gid}/messages/{message_id} (仅机器人自己发的,窗口 2 分钟)
|
|
18
|
+
*/
|
|
19
|
+
export const QQ_GROUP_FEATURE_GATES = {
|
|
20
|
+
listJoinRequests: { open: true, human: '入群申请列表:机器人需为该群管理员' },
|
|
21
|
+
approveJoinRequest: { open: true, human: '入群申请审批:机器人需为该群管理员' },
|
|
22
|
+
getMuteState: { open: true, human: '群禁言状态查询:机器人需为该群管理员' },
|
|
23
|
+
setMute: { open: true, human: '设置禁言:机器人需为该群管理员(仅普通成员,最长 30 天,单次 ≤20 人)' },
|
|
24
|
+
recallMessage: { open: true, human: '撤回群消息:仅机器人自己发的、2 分钟窗口内' },
|
|
25
|
+
// 内邀中:不调用、命令侧直接告知未开放(开放后改 open:true 即自动可用)。
|
|
26
|
+
listMembers: { open: false, human: '获取群成员列表:官方尚未开放(内邀中),等待公测后自动可用' },
|
|
27
|
+
getMemberInfo: { open: false, human: '获取群成员信息(含昵称):官方标注「内邀接入中」,申请到白名单后自动可用' },
|
|
28
|
+
};
|
|
29
|
+
/** 官方群管/权限相关错误码 → 面向用户的一句话(人话),映射不到回落通用文案。 */
|
|
30
|
+
export function qqGroupErrHuman(code) {
|
|
31
|
+
const c = String(code);
|
|
32
|
+
const table = {
|
|
33
|
+
'11253': '该能力官方尚未开放/无接口权限(内邀中或需白名单),请联系平台运营或改为管理员后重试',
|
|
34
|
+
'11703': '机器人无该接口权限:需为该群管理员,或该能力尚未对应用开放',
|
|
35
|
+
'11201': '机器人不在该群,或群 openid/参数错误',
|
|
36
|
+
'40103004': '不能操作该成员:群主/管理员/机器人不可被禁言(只能禁普通成员)',
|
|
37
|
+
'40101': '频控:请求太快,稍等再试',
|
|
38
|
+
'401': '访问凭证无效,请刷新后重试',
|
|
39
|
+
'403': '机器人缺少群管理员身份或未授权',
|
|
40
|
+
'11001': '接口或参数不正确(多为路径/字段写错):检查 URL 与参数',
|
|
41
|
+
};
|
|
42
|
+
return table[c] ?? `调用失败(错误码 ${c}),请稍后重试`;
|
|
43
|
+
}
|
package/lib/channels/qq.d.ts
CHANGED
|
@@ -46,9 +46,12 @@
|
|
|
46
46
|
* 3. 编辑/撤回(PATCH/DELETE .../messages/{id}):群聊撤回路径未逐面核实 → editStream=false。
|
|
47
47
|
* 4. keyboard 按钮面(需后台模板/回调配置 + INTERACTION_CREATE 链路)→ nativeChoices=false,
|
|
48
48
|
* 审批/提问由公共层自动落「数字菜单」回退,本适配器无按钮实现。
|
|
49
|
-
* 5. 入站附件下载(attachments[].url 带 rkey
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* 5. 入站附件下载(P3/F3 已实现图片/文件档):attachments[].url 带 rkey 时效 → 事件到达即下载填
|
|
50
|
+
* images[]/files[];content_type 群聊图片实测为 'file' → 兼用 URL 形态判定;下载失败/过期/无 arrayBuffer
|
|
51
|
+
* 面 → 按占位文案不丢事件。语音(voice=false)不解码,取事件原生 asr_refer_text 转写(见 inboundText)。
|
|
52
|
+
* 6. 出站富媒体(P1/F1 已实现图片档):先上传换 file_info、msg_type=7 携带;file_type=4 文件官方
|
|
53
|
+
* 「暂不开放」→ 仅图片(png/jpeg/gif/webp)可发,非图片优雅拒绝;日配额 40093002、file_info 过期 304080
|
|
54
|
+
* 均确定性转译。群/单聊上传接口不互通 → 按 address.kind 选端点。
|
|
52
55
|
* 7. 频道(guild/子频道/私信)事件与 REST、语音 AUDIO_* 位、表情表态是否对群/C2C 开放 → 均不实现
|
|
53
56
|
* (threads=false、voice=false、statusReaction=false)。
|
|
54
57
|
* 8. webhook 回调模式(op12/op13)与多分片(shard>1):只走 WS 长连接单分片 [0,1]。
|
|
@@ -58,18 +61,23 @@
|
|
|
58
61
|
* fetchImpl/SocketImpl,不发真实网络请求。
|
|
59
62
|
*/
|
|
60
63
|
import type { ChatAddress } from '../engine/router.js';
|
|
61
|
-
import type { ChannelAdapter, ChannelCaps, ChannelDiagnosisCheck, MessageListener, Receipt, SemanticMessage } from './types.js';
|
|
64
|
+
import type { ChannelAdapter, ChannelCaps, ChannelDiagnosisCheck, MessageListener, OutboundFile, Receipt, SemanticMessage } from './types.js';
|
|
65
|
+
import type { QqGroupResult } from './qq-group-admin.js';
|
|
62
66
|
/** 群/C2C 事件族(1<<25):C2C_MESSAGE_CREATE + GROUP_AT_MESSAGE_CREATE。 */
|
|
63
67
|
export declare const QQ_INTENTS_GROUP_AND_C2C: number;
|
|
68
|
+
/** 交互事件族(INTERACTION_CREATE,1<<26):仅在 config 开 keyboard 时订阅(无权限会致 IDENTIFY 拒载)。 */
|
|
69
|
+
export declare const QQ_INTENTS_INTERACTION: number;
|
|
64
70
|
interface JsonHttpFace {
|
|
65
71
|
ok: boolean;
|
|
66
72
|
status: number;
|
|
67
73
|
json(): Promise<unknown>;
|
|
74
|
+
/** 入站附件下载用(P3/F3):全局 fetch 的 Response 自带;测试替身缺位则视为不可下载、优雅跳过。 */
|
|
75
|
+
arrayBuffer?(): Promise<ArrayBuffer>;
|
|
68
76
|
}
|
|
69
77
|
type JsonHttpCall = (url: string, init: {
|
|
70
78
|
method?: string;
|
|
71
79
|
headers?: Record<string, string>;
|
|
72
|
-
body?: string;
|
|
80
|
+
body?: string | FormData | Uint8Array;
|
|
73
81
|
signal?: AbortSignal;
|
|
74
82
|
}) => Promise<JsonHttpFace>;
|
|
75
83
|
interface SocketFace {
|
|
@@ -94,6 +102,8 @@ export interface QqAdapterOptions {
|
|
|
94
102
|
fetchImpl?: JsonHttpCall;
|
|
95
103
|
SocketImpl?: SocketDial;
|
|
96
104
|
logger?: (msg: string) => void;
|
|
105
|
+
/** 开启 keyboard 按钮面(审批/提问):订阅 INTERACTION intent + nativeChoices 置真;默认关(零风险)。 */
|
|
106
|
+
enableKeyboard?: boolean;
|
|
97
107
|
/** 下列三项为测试/调参面,默认取协议保守值。 */
|
|
98
108
|
passiveWindowMs?: number;
|
|
99
109
|
passiveMaxUses?: number;
|
|
@@ -138,6 +148,10 @@ export declare class QqAdapter implements ChannelAdapter {
|
|
|
138
148
|
private shuttingDown;
|
|
139
149
|
private readonly windows;
|
|
140
150
|
private readonly seen;
|
|
151
|
+
private readonly pendingChoice;
|
|
152
|
+
/** 入站派发串行链:保证按到达顺序派发(附件消息先下好字节再发,不越过后续消息)。 */
|
|
153
|
+
private dispatchTail;
|
|
154
|
+
private readonly enableKeyboard;
|
|
141
155
|
constructor(options: QqAdapterOptions);
|
|
142
156
|
onMessage(listener: MessageListener): () => void;
|
|
143
157
|
/** 换 access_token 并缓存;文档口径 expires_in≤7200s 且期内不刷新,故提前 120s 续期。 */
|
|
@@ -174,8 +188,6 @@ export declare class QqAdapter implements ChannelAdapter {
|
|
|
174
188
|
* 相同消息 id 重复推送(协议明载「相同 msg_id 可能重复推送」,按 d.id 去重)。
|
|
175
189
|
*/
|
|
176
190
|
normalize(type: string, frame: UnknownRecord): SemanticMessage | undefined;
|
|
177
|
-
/** 富文本/卡片/附件入站给占位文案而不丢事件(caps.imageIn/fileIn=false,不下载字节)。 */
|
|
178
|
-
private inboundText;
|
|
179
191
|
/** message_type=103 引用消息:被引用正文在 msg_elements[0].content(协议可据)。 */
|
|
180
192
|
private quoteOf;
|
|
181
193
|
private timeOf;
|
|
@@ -185,8 +197,9 @@ export declare class QqAdapter implements ChannelAdapter {
|
|
|
185
197
|
private liveWindow;
|
|
186
198
|
private endpoint;
|
|
187
199
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
200
|
+
* 文本出站:默认 msg_type=0(官方文档口径)。含 markdown 图片内嵌(`![` 或 `qqbot-media://`)时
|
|
201
|
+
* 走 msg_type=2(markdown 通道才渲染图);若 markdown 档被平台确定性拒绝(未开通/体不符)且非窗口类
|
|
202
|
+
* 拒绝,降级重发一次纯文本(协议安全:纯文本通道总是可用)。窗口内带 msg_id+msg_seq,每次 HTTP 尝试递增 seq 防撞去重。
|
|
190
203
|
*/
|
|
191
204
|
reply(address: ChatAddress, text: string, signal?: AbortSignal): Promise<Receipt>;
|
|
192
205
|
/**
|
|
@@ -195,14 +208,94 @@ export declare class QqAdapter implements ChannelAdapter {
|
|
|
195
208
|
* 失败一律吞:typing 属装饰面,不得影响主回复链。
|
|
196
209
|
*/
|
|
197
210
|
setTyping(address: ChatAddress): Promise<void>;
|
|
211
|
+
/** 富媒体上传端点:群/单聊不互通(文件头 6),路径与消息端点同构但尾缀 /files。 */
|
|
212
|
+
private uploadEndpoint;
|
|
213
|
+
/**
|
|
214
|
+
* 上传换 file_info(仅图片档 file_type=1)。**双形态递降**:先按官方 Node SDK(
|
|
215
|
+
* @tencent-connect/qqbot-nodejs 1.0.5 `MediaApi.uploadMedia`)的小文件形态——JSON +
|
|
216
|
+
* base64 `file_data`(已被在跑的真机器人证明可用);若平台确定性拒绝,再回退官方文档记载的
|
|
217
|
+
* multipart/form-data 形态一次。网络不确定(uncertain)绝不重试(D7:上传重试会白占配额)。
|
|
218
|
+
* 两种形态均带 `srv_send_msg=false`,否则平台会自己把图发出去与我们后续 msg_type=7 构成重发。
|
|
219
|
+
*/
|
|
220
|
+
private uploadFile;
|
|
221
|
+
/** 小文件上传(官方 SDK 形态):POST …/files 体 {file_type, file_name, file_data(base64), srv_send_msg:false}。 */
|
|
222
|
+
private uploadFileJson;
|
|
223
|
+
/** 上传备形态(官方文档记载):multipart/form-data,file_data 为二进制部分。 */
|
|
224
|
+
private uploadFileForm;
|
|
225
|
+
/**
|
|
226
|
+
* 出站图片:先上传换 file_info,再以 msg_type=7 携带 image 发送(群/单聊分端点)。窗口内带
|
|
227
|
+
* msg_id+msg_seq(同 reply 纪律,撞窗清空窗口)。QQ 官方机器人仅图片档开放,非图片 mime 优雅
|
|
228
|
+
* 拒绝(不静默);空字节拒发。file_info 过期(304080)/日配额(40093002)→确定性转译上抛。
|
|
229
|
+
*/
|
|
230
|
+
sendFile(address: ChatAddress, file: OutboundFile, signal?: AbortSignal): Promise<Receipt>;
|
|
231
|
+
/** 富文本/卡片/附件入站给占位文案而不丢事件;语音取事件原生 ASR 转写(P4/F5、免解码)。 */
|
|
232
|
+
private inboundText;
|
|
233
|
+
/**
|
|
234
|
+
* 入站派发(P3/F3):若附件可下载且本渠道开了 imageIn/fileIn → 先抢时效下载再派发;否则同步派发。
|
|
235
|
+
* 下载失败/过期不阻断(fillInboundAttachments 内部逐项吞错),事件绝不丢。
|
|
236
|
+
*/
|
|
237
|
+
private dispatchInbound;
|
|
238
|
+
/**
|
|
239
|
+
* 逐个下载附件字节填入 images[]/files[](rkey 时效,事件到达即拉)。图片优先按 content_type,
|
|
240
|
+
* 群聊图片 content_type 实测为 'file' → 兼用 URL 形态兵底;无 arrayBuffer 面/非 2xx/空字节 → 跳过不丢。
|
|
241
|
+
* 语音/视频不归入下载(voice=false,不入字节)。
|
|
242
|
+
*/
|
|
243
|
+
private fillInboundAttachments;
|
|
244
|
+
/**
|
|
245
|
+
* 审批/提问按钮(P5/F4):msg_type=2 + keyboard 行按钮(action.type=1 回传交互),等 INTERACTION_CREATE
|
|
246
|
+
* 回包结算。未启用 keyboard / 多选 / 选项为空 → 抛错(引擎回落文本菜单);发送失败同样上抛→回落。
|
|
247
|
+
* D5:仅接受发起者(ownerUserId)点按,非发起者忽略;超时/取消→空答(uncertain=false)。
|
|
248
|
+
*/
|
|
249
|
+
askChoices(address: ChatAddress, question: string, options: readonly string[], multi: boolean, signal?: AbortSignal, opts?: {
|
|
250
|
+
ownerUserId?: string;
|
|
251
|
+
}): Promise<{
|
|
252
|
+
answer: string;
|
|
253
|
+
uncertain: boolean;
|
|
254
|
+
}>;
|
|
255
|
+
/** INTERACTION_CREATE 消息按钮回传:解出 token+idx → 定位 pending → D5 发起者校验 → 结算。 */
|
|
256
|
+
private handleInteraction;
|
|
198
257
|
/** 渠道内不做准入判断:白名单归网关(红线 D6),恒交回 undefined。 */
|
|
199
258
|
authorize(): boolean | undefined;
|
|
259
|
+
/**
|
|
260
|
+
* markdown 内联本地图的媒体上传原语(P8/task_8):走 3 步 /v2/media-images——prepare→PUT presigned→complete,
|
|
261
|
+
* 返 `qqbot-media://` url 供写进 msg_type=2 正文。**适配器不读盘**:字节由调用方(gateway 受管工作区读)传入。
|
|
262
|
+
* 先查服务端同名(含内容指纹)复用不占配额;md5/sha1/sha256 走 node:crypto;网络/平台码→QqGroupResult 失败体。
|
|
263
|
+
*/
|
|
264
|
+
uploadMarkdownImage(file: OutboundFile, signal?: AbortSignal): Promise<QqGroupResult<{
|
|
265
|
+
url: string;
|
|
266
|
+
imageId: string;
|
|
267
|
+
}>>;
|
|
268
|
+
/** 能力门:官方未开(内邀中)直接回人话拒绝,不发请求(未逐面核协议不猜发)。 */
|
|
269
|
+
private gateGroup;
|
|
270
|
+
/** REST 抛错→统一群管返回体:平台码人话化,网络不确定标记为需复查(不自动重试)。 */
|
|
271
|
+
private toGroupErr;
|
|
272
|
+
/** 入群申请列表(🟡 群管理员)。 */
|
|
273
|
+
qqGroupListJoinRequests(gid: string, signal?: AbortSignal): Promise<QqGroupResult<UnknownRecord[]>>;
|
|
274
|
+
/** 审批入群申请(🟡 群管理员;op=approve|decline)。 */
|
|
275
|
+
qqGroupApproveJoin(gid: string, memberOpenid: string, op: 'approve' | 'decline', opts?: {
|
|
276
|
+
joinRequestId?: string;
|
|
277
|
+
signal?: AbortSignal;
|
|
278
|
+
}): Promise<QqGroupResult<true>>;
|
|
279
|
+
/** 查询群禁言状态(🟡 群管理员)。 */
|
|
280
|
+
qqGroupGetMuteState(gid: string, signal?: AbortSignal): Promise<QqGroupResult<UnknownRecord>>;
|
|
281
|
+
/**
|
|
282
|
+
* 设置群禁言(🟡 群管理员;仅普通成员,单次≤20 人)。muteSeconds<=0 → 解除禁言(del);
|
|
283
|
+
* 否则 add 逐条发(官方 add 批量会 50015013,del 支持一次多)。mute_expire_at 用 RFC3339。
|
|
284
|
+
*/
|
|
285
|
+
qqGroupSetMute(gid: string, memberOpenids: readonly string[], muteSeconds: number, signal?: AbortSignal): Promise<QqGroupResult<true>>;
|
|
286
|
+
/** 撤回消息(🟡 仅机器人自己发的、2 分钟窗口;scope=group|c2c)。DELETE 容忍空回包。 */
|
|
287
|
+
qqGroupRecall(peerId: string, msgId: string, scope?: 'group' | 'c2c', signal?: AbortSignal): Promise<QqGroupResult<true>>;
|
|
200
288
|
/**
|
|
201
289
|
* 诊断三态:token 面实探(只回显有效期,零 secret);gateway 会话位;
|
|
202
290
|
* roundtrip(真实收发闭环)恒 unverified;富媒体能力 unverified(平台侧开通/日配额)。
|
|
203
291
|
*/
|
|
204
292
|
diagnose(signal?: AbortSignal): Promise<ChannelDiagnosisCheck[]>;
|
|
205
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* 扫描正文 `` 将本地图片路径交给 resolveAndUpload(由调用方以受管工作区读+上传实现),
|
|
296
|
+
* 拿到 url 则替换,拿不到(不可解/越界/上传失败)保留原样。网络图/data:/qqbot-media: 不碰。
|
|
297
|
+
*/
|
|
298
|
+
export declare function rewriteMarkdownLocalImages(text: string, resolveAndUpload: (rawPath: string, alt: string) => Promise<string | undefined>): Promise<string>;
|
|
206
299
|
/** 按 UTF-8 字节预算安全截断(不切裂代理对),超限补省略号。 */
|
|
207
300
|
export declare function fitByteBudget(text: string, maxBytes?: number): string;
|
|
208
301
|
/** 工厂:secrets 取 meta(qq) 字段名 appId/appSecret;config 可覆盖 apiBaseUrl/tokenUrl。 */
|