@zhin.js/adapter-qq 1.1.0 → 1.1.3
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 +112 -0
- package/README.md +18 -19
- package/adapters/{qq.js → qq/index.js} +7 -7
- package/adapters/{qq.ts → qq/index.ts} +11 -11
- package/agents/qq/agent.json +20 -0
- package/agents/qq/boundaries.md +3 -0
- package/agents/qq/conventions.md +3 -0
- package/agents/qq/skills/qq/SKILL.md +22 -0
- package/agents/qq/skills/qq/tools/add_role/index.js +21 -0
- package/{agent/tools/add_role.ts → agents/qq/skills/qq/tools/add_role/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/channel_info/index.js +17 -0
- package/{agent/tools/channel_info.ts → agents/qq/skills/qq/tools/channel_info/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/create_role/index.js +20 -0
- package/{agent/tools/create_role.ts → agents/qq/skills/qq/tools/create_role/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/list_channels/index.js +18 -0
- package/{agent/tools/list_channels.ts → agents/qq/skills/qq/tools/list_channels/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/list_guilds/index.js +14 -0
- package/{agent/tools/list_guilds.ts → agents/qq/skills/qq/tools/list_guilds/index.ts} +1 -1
- package/agents/qq/skills/qq/tools/list_roles/index.js +18 -0
- package/{agent/tools/list_roles.ts → agents/qq/skills/qq/tools/list_roles/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/member_detail/index.js +18 -0
- package/{agent/tools/member_detail.ts → agents/qq/skills/qq/tools/member_detail/index.ts} +3 -3
- package/agents/qq/skills/qq/tools/remove_role/index.js +21 -0
- package/{agent/tools/remove_role.ts → agents/qq/skills/qq/tools/remove_role/index.ts} +3 -3
- package/agents/qq/system.md +3 -0
- package/commands/{endpoint/add/[[id]].js → qq/endpoint/add/[[id]]/index.js} +1 -1
- package/commands/qq/endpoint/add/[[id]]/index.ts +3 -0
- package/commands/{endpoint/cancel.js → qq/endpoint/cancel/index.js} +1 -2
- package/commands/{endpoint/cancel.ts → qq/endpoint/cancel/index.ts} +2 -2
- package/{lib/qq-endpoint-commands.js → commands/qq/endpoint/definition.js} +44 -36
- package/{src/qq-endpoint-commands.ts → commands/qq/endpoint/definition.ts} +57 -45
- package/commands/{endpoint/list.js → qq/endpoint/list/index.js} +1 -1
- package/commands/qq/endpoint/list/index.ts +3 -0
- package/commands/{endpoint/remove/[id].js → qq/endpoint/remove/[id]/index.js} +1 -1
- package/commands/qq/endpoint/remove/[id]/index.ts +3 -0
- package/lib/index.d.ts +1 -1
- package/lib/outbound.js +1 -1
- package/lib/protocol.d.ts +7 -28
- package/lib/protocol.js +19 -24
- package/lib/qq-bot-kind-prompt.js +1 -1
- package/middlewares/{bot-kind-reply.js → bot-kind-reply/index.js} +7 -6
- package/middlewares/{bot-kind-reply.ts → bot-kind-reply/index.ts} +11 -6
- package/package.json +34 -22
- package/schema.json +26 -10
- package/src/index.ts +1 -1
- package/src/outbound.ts +1 -1
- package/src/protocol.ts +25 -49
- package/src/qq-bot-kind-prompt.ts +1 -1
- package/src/ws.ts +1 -1
- package/agent/skills/qq.md +0 -97
- package/commands/endpoint/add/[[id]].ts +0 -3
- package/commands/endpoint/list.ts +0 -3
- package/commands/endpoint/remove/[id].ts +0 -3
- package/lib/qq-bind-persist.d.ts +0 -12
- package/lib/qq-bind-persist.js +0 -22
- package/lib/qq-endpoint-commands.d.ts +0 -45
- package/lib/qq-endpoint-config.d.ts +0 -17
- package/lib/qq-endpoint-config.js +0 -22
- package/src/qq-bind-persist.ts +0 -44
- package/src/qq-endpoint-config.ts +0 -44
- /package/{agent/PERMITS.md → PERMITS.md} +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { platformPermission } from '@zhin.js/permission';
|
|
5
|
+
export default defineAgentTool({
|
|
6
|
+
description: '移除成员的 QQ 频道角色',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
guild_id: z.string().describe('频道 ID'),
|
|
9
|
+
channel_id: z.string().describe('子频道 ID'),
|
|
10
|
+
user_id: z.string().describe('用户 ID'),
|
|
11
|
+
role_id: z.string().describe('角色 ID'),
|
|
12
|
+
}),
|
|
13
|
+
adapter: 'qq',
|
|
14
|
+
tags: ['qq'],
|
|
15
|
+
permissions: [platformPermission('qq', 'manage_roles')],
|
|
16
|
+
async execute({ guild_id, channel_id, user_id, role_id }, context) {
|
|
17
|
+
const client = context.$client;
|
|
18
|
+
const success = await client.removeMemberRole(guild_id, channel_id, user_id, role_id);
|
|
19
|
+
return { success, message: success ? '已移除成员的角色' : '操作失败' };
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defineAgentTool } from '@zhin.js/
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { platformPermission } from '@zhin.js/permission';
|
|
4
4
|
|
|
5
5
|
export default defineAgentTool<{ guild_id: string; channel_id: string; user_id: string; role_id: string }>({
|
|
6
6
|
description: '移除成员的 QQ 频道角色',
|
|
@@ -12,7 +12,7 @@ export default defineAgentTool<{ guild_id: string; channel_id: string; user_id:
|
|
|
12
12
|
}),
|
|
13
13
|
adapter: 'qq',
|
|
14
14
|
tags: ['qq'],
|
|
15
|
-
permissions: [
|
|
15
|
+
permissions: [platformPermission('qq', 'manage_roles')],
|
|
16
16
|
async execute({ guild_id, channel_id, user_id, role_id }: { guild_id: string; channel_id: string; user_id: string; role_id: string }, context) {
|
|
17
17
|
const client = context.$client;
|
|
18
18
|
const success = await client.removeMemberRole(guild_id, channel_id, user_id, role_id);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { defineCommand } from 'zhin.js/command';
|
|
3
|
-
import { isQqEndpointOperator, QQ_ENDPOINT_FORBIDDEN, runQqEndpointCancel, } from
|
|
4
|
-
import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
|
|
3
|
+
import { isQqEndpointOperator, QQ_ENDPOINT_FORBIDDEN, qqRuntimeStateToken, runQqEndpointCancel, } from '../definition.js';
|
|
5
4
|
export default defineCommand({
|
|
6
5
|
description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
|
|
7
6
|
execute({ config, input, use }) {
|
|
@@ -2,9 +2,9 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
2
2
|
import {
|
|
3
3
|
isQqEndpointOperator,
|
|
4
4
|
QQ_ENDPOINT_FORBIDDEN,
|
|
5
|
+
qqRuntimeStateToken,
|
|
5
6
|
runQqEndpointCancel,
|
|
6
|
-
} from '
|
|
7
|
-
import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
|
|
7
|
+
} from '../definition.js';
|
|
8
8
|
|
|
9
9
|
export default defineCommand({
|
|
10
10
|
description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
1
2
|
/**
|
|
2
|
-
* `qq
|
|
3
|
+
* `qq endpoint` 命令的业务逻辑(与命令定义文件分离,便于测试)。
|
|
3
4
|
* 通用部分(权限 / list / remove / 配置写回 / .env 持久化)已迁移到
|
|
4
5
|
* @zhin.js/adapter 的 createEndpointCommands 套件;本文件保留 QQ 特化的
|
|
5
6
|
* 扫码绑定 add 流程(经套件 bindFlow 钩子接入)与 cancel,并导出
|
|
6
|
-
* qqEndpointCommands 供 commands/endpoint/ 下的命令文件默认导出。
|
|
7
|
+
* qqEndpointCommands 供 commands/qq/endpoint/ 下的命令文件默认导出。
|
|
7
8
|
*/
|
|
8
|
-
import { createEndpointCommands, endpointCommandForbidden, extractEndpointCommandReply, formatEndpointList, isEndpointOperator, removeEndpointById, } from 'zhin.js/adapter';
|
|
9
|
+
import { buildEndpointEnvKey, createEndpointCommands, endpointCommandForbidden, endpointConfigurationStoreToken, extractEndpointCommandReply, formatEndpointList, isEndpointOperator, removeEndpointById, } from 'zhin.js/adapter';
|
|
9
10
|
import { defineCommand } from 'zhin.js/command';
|
|
10
|
-
import { QQ_BOT_KIND_PROMPT, qqCommandSessionKey } from
|
|
11
|
-
import { startQqBindFlow } from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
import { qqRuntimeStateToken, } from './qq-runtime-state.js';
|
|
11
|
+
import { QQ_BOT_KIND_PROMPT, qqCommandSessionKey } from "../../../lib/qq-bot-kind-prompt.js";
|
|
12
|
+
import { startQqBindFlow } from "../../../lib/qq-bind-flow.js";
|
|
13
|
+
import { defaultQqEndpointIntentFields } from "../../../lib/qq-intents.js";
|
|
14
|
+
import { qqRuntimeStateToken, } from "../../../lib/qq-runtime-state.js";
|
|
15
|
+
export { qqRuntimeStateToken };
|
|
16
16
|
/**
|
|
17
17
|
* endpoint 管理命令的操作者校验:实例配置声明了 master(顶层或任一端点项)时
|
|
18
18
|
* 仅 master 可执行 add/cancel/remove;未配置则放行(首个扫码绑定者即为 owner,
|
|
@@ -30,17 +30,17 @@ export function extractQqCommandReply(input) {
|
|
|
30
30
|
}
|
|
31
31
|
function busyFooter(state) {
|
|
32
32
|
if (state.bindFlow)
|
|
33
|
-
return '⚠️ 有进行中的扫码绑定,可用 qq
|
|
33
|
+
return '⚠️ 有进行中的扫码绑定,可用 qq endpoint cancel 取消';
|
|
34
34
|
if (state.pendingBotKind) {
|
|
35
|
-
return `⚠️ endpoint「${state.pendingBotKind.endpointId}」等待公域/私域选择,回复 public/private 或 qq
|
|
35
|
+
return `⚠️ endpoint「${state.pendingBotKind.endpointId}」等待公域/私域选择,回复 public/private 或 qq endpoint cancel`;
|
|
36
36
|
}
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
|
-
/** `qq
|
|
40
|
-
export function runQqEndpointList(state,
|
|
39
|
+
/** `qq endpoint list`:运行中的 endpoints(本 generation adapter create 注册)+ 配置里的 endpoints */
|
|
40
|
+
export async function runQqEndpointList(state, store) {
|
|
41
41
|
return formatEndpointList(qqEndpointListSpec, {
|
|
42
42
|
running: state.endpoints.values(),
|
|
43
|
-
configured:
|
|
43
|
+
configured: await store.list('qq'),
|
|
44
44
|
footer: busyFooter(state),
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -49,16 +49,24 @@ const qqEndpointListSpec = {
|
|
|
49
49
|
adapterDisplayName: 'QQ',
|
|
50
50
|
describeEntry: (entry) => `appid: ${String(entry.appid)}`,
|
|
51
51
|
};
|
|
52
|
-
function writeEndpointWithBotKind(pending, botKind,
|
|
53
|
-
const
|
|
52
|
+
async function writeEndpointWithBotKind(pending, botKind, store) {
|
|
53
|
+
const appidKey = buildEndpointEnvKey('qq', pending.endpointId, 'appid');
|
|
54
|
+
const secretKey = buildEndpointEnvKey('qq', pending.endpointId, 'secret');
|
|
54
55
|
const intentFields = defaultQqEndpointIntentFields(botKind);
|
|
55
|
-
const filePath =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const { filePath } = await store.add({
|
|
57
|
+
adapterKey: 'qq',
|
|
58
|
+
entry: {
|
|
59
|
+
id: pending.endpointId,
|
|
60
|
+
appid: `\${${appidKey}}`,
|
|
61
|
+
secret: `\${${secretKey}}`,
|
|
62
|
+
botKind: intentFields.botKind,
|
|
63
|
+
intents: [...intentFields.intents],
|
|
64
|
+
},
|
|
65
|
+
environment: {
|
|
66
|
+
[appidKey]: pending.appId,
|
|
67
|
+
[secretKey]: pending.appSecret,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
62
70
|
return (`✅ 已按 botKind=${intentFields.botKind} 将 endpoint「${pending.endpointId}」写入 .env 与 ${filePath}。\n` +
|
|
63
71
|
`intents: ${intentFields.intents.join(', ')}。\n` +
|
|
64
72
|
'⚠️ 需重启 zhin 后新 endpoint 才会生效。');
|
|
@@ -67,18 +75,18 @@ function writeEndpointWithBotKind(pending, botKind, projectRoot) {
|
|
|
67
75
|
* 完成 pending 的公域/私域选择:一次性写 .env + yaml。
|
|
68
76
|
* @returns 成功文案;调用方负责清 pending。
|
|
69
77
|
*/
|
|
70
|
-
export function completeQqPendingBotKind(pending, botKind,
|
|
71
|
-
return writeEndpointWithBotKind(pending, botKind,
|
|
78
|
+
export async function completeQqPendingBotKind(pending, botKind, store) {
|
|
79
|
+
return await writeEndpointWithBotKind(pending, botKind, store);
|
|
72
80
|
}
|
|
73
81
|
/**
|
|
74
|
-
* `qq
|
|
82
|
+
* `qq endpoint add [id]`:启动扫码绑定流程。
|
|
75
83
|
* 返回的 Promise 在二维码链接就绪(或前置失败)时 resolve 为首条回复;
|
|
76
84
|
* 后续状态(已扫码 / 成功 / 失败 / 过期刷新)通过 reply 推回当前会话。
|
|
77
85
|
* 扫码成功后凭据暂存内存并询问公域/私域;确认后一次性写 .env + yaml。
|
|
78
86
|
*/
|
|
79
|
-
export function runQqEndpointAdd(state, id, reply,
|
|
87
|
+
export function runQqEndpointAdd(state, id, reply, store, input) {
|
|
80
88
|
if (state.bindFlow || state.pendingBotKind) {
|
|
81
|
-
return Promise.resolve('已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先发送 qq
|
|
89
|
+
return Promise.resolve('已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先发送 qq endpoint cancel 取消后再试');
|
|
82
90
|
}
|
|
83
91
|
const endpointId = id?.trim() || undefined;
|
|
84
92
|
const sessionKey = qqCommandSessionKey(input);
|
|
@@ -115,12 +123,12 @@ export function runQqEndpointAdd(state, id, reply, projectRoot, input) {
|
|
|
115
123
|
const finalName = endpointId ?? appId;
|
|
116
124
|
// 无会话上下文(Host 等)时无法追问,直接按公域一次性写入
|
|
117
125
|
if (!sessionKey) {
|
|
118
|
-
settle(writeEndpointWithBotKind({
|
|
126
|
+
settle(await writeEndpointWithBotKind({
|
|
119
127
|
endpointId: finalName,
|
|
120
128
|
appId,
|
|
121
129
|
appSecret,
|
|
122
130
|
sessionKey: '',
|
|
123
|
-
}, 'public',
|
|
131
|
+
}, 'public', store) +
|
|
124
132
|
'\n(当前会话无法交互选择公/私域;私域请手动改配置。)');
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
@@ -145,7 +153,7 @@ export function runQqEndpointAdd(state, id, reply, projectRoot, input) {
|
|
|
145
153
|
state.bindFlow = { id: endpointId, stop };
|
|
146
154
|
});
|
|
147
155
|
}
|
|
148
|
-
/** `qq
|
|
156
|
+
/** `qq endpoint cancel`:中止进行中的绑定或待选 botKind */
|
|
149
157
|
export function runQqEndpointCancel(state) {
|
|
150
158
|
if (state.bindFlow) {
|
|
151
159
|
state.bindFlow.stop();
|
|
@@ -159,13 +167,13 @@ export function runQqEndpointCancel(state) {
|
|
|
159
167
|
}
|
|
160
168
|
return '当前没有进行中的 QQ 绑定流程';
|
|
161
169
|
}
|
|
162
|
-
/** `qq
|
|
163
|
-
export function runQqEndpointRemove(_state, id,
|
|
164
|
-
return removeEndpointById(qqEndpointListSpec, id,
|
|
170
|
+
/** `qq endpoint remove <id>`:从 zhin.config.yml 移除对应 endpoints 项 */
|
|
171
|
+
export async function runQqEndpointRemove(_state, id, store) {
|
|
172
|
+
return await removeEndpointById(qqEndpointListSpec, id, store);
|
|
165
173
|
}
|
|
166
174
|
/**
|
|
167
175
|
* 通用套件生成的 QQ endpoint 命令(add 经 bindFlow 钩子走扫码绑定)。
|
|
168
|
-
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
|
|
176
|
+
* commands/qq/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
|
|
169
177
|
*/
|
|
170
178
|
export const qqEndpointCommands = createEndpointCommands({
|
|
171
179
|
...qqEndpointListSpec,
|
|
@@ -176,5 +184,5 @@ export const qqEndpointCommands = createEndpointCommands({
|
|
|
176
184
|
running: (use) => use(qqRuntimeStateToken).endpoints.values(),
|
|
177
185
|
listFooter: (use) => busyFooter(use(qqRuntimeStateToken)),
|
|
178
186
|
addDescription: '手机 QQ 扫码绑定机器人:确认公域/私域后一次性写入 .env 与 zhin.config.yml(重启生效)',
|
|
179
|
-
bindFlow: ({ id, reply, input, use }) => runQqEndpointAdd(use(qqRuntimeStateToken), id, reply,
|
|
187
|
+
bindFlow: ({ id, reply, input, use }) => runQqEndpointAdd(use(qqRuntimeStateToken), id, reply, use(endpointConfigurationStoreToken), input),
|
|
180
188
|
}, defineCommand);
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `qq
|
|
2
|
+
* `qq endpoint` 命令的业务逻辑(与命令定义文件分离,便于测试)。
|
|
3
3
|
* 通用部分(权限 / list / remove / 配置写回 / .env 持久化)已迁移到
|
|
4
4
|
* @zhin.js/adapter 的 createEndpointCommands 套件;本文件保留 QQ 特化的
|
|
5
5
|
* 扫码绑定 add 流程(经套件 bindFlow 钩子接入)与 cancel,并导出
|
|
6
|
-
* qqEndpointCommands 供 commands/endpoint/ 下的命令文件默认导出。
|
|
6
|
+
* qqEndpointCommands 供 commands/qq/endpoint/ 下的命令文件默认导出。
|
|
7
7
|
*/
|
|
8
8
|
import {
|
|
9
|
+
buildEndpointEnvKey,
|
|
9
10
|
createEndpointCommands,
|
|
10
11
|
endpointCommandForbidden,
|
|
12
|
+
endpointConfigurationStoreToken,
|
|
11
13
|
extractEndpointCommandReply,
|
|
12
14
|
formatEndpointList,
|
|
13
15
|
isEndpointOperator,
|
|
14
16
|
removeEndpointById,
|
|
15
17
|
type ConfiguredEndpointEntry,
|
|
16
18
|
type EndpointCommandReply,
|
|
19
|
+
type EndpointConfigurationStore,
|
|
17
20
|
} from 'zhin.js/adapter';
|
|
18
21
|
import { defineCommand } from 'zhin.js/command';
|
|
19
|
-
import { QQ_BOT_KIND_PROMPT, qqCommandSessionKey } from '
|
|
20
|
-
import { startQqBindFlow } from '
|
|
21
|
-
import {
|
|
22
|
-
import { addQqEndpointToConfig, listQqEndpointEntries } from './qq-endpoint-config.js';
|
|
23
|
-
import { defaultQqEndpointIntentFields, type QqBotKind } from './qq-intents.js';
|
|
22
|
+
import { QQ_BOT_KIND_PROMPT, qqCommandSessionKey } from '../../../src/qq-bot-kind-prompt.js';
|
|
23
|
+
import { startQqBindFlow } from '../../../src/qq-bind-flow.js';
|
|
24
|
+
import { defaultQqEndpointIntentFields, type QqBotKind } from '../../../src/qq-intents.js';
|
|
24
25
|
import {
|
|
25
26
|
qqRuntimeStateToken,
|
|
26
27
|
type QqPendingBotKind,
|
|
27
28
|
type QqRuntimeState,
|
|
28
|
-
} from '
|
|
29
|
+
} from '../../../src/qq-runtime-state.js';
|
|
30
|
+
|
|
31
|
+
export { qqRuntimeStateToken };
|
|
29
32
|
|
|
30
33
|
export type QqCommandReply = EndpointCommandReply;
|
|
31
34
|
|
|
@@ -48,18 +51,21 @@ export function extractQqCommandReply(input: unknown): QqCommandReply {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
function busyFooter(state: QqRuntimeState): string | undefined {
|
|
51
|
-
if (state.bindFlow) return '⚠️ 有进行中的扫码绑定,可用 qq
|
|
54
|
+
if (state.bindFlow) return '⚠️ 有进行中的扫码绑定,可用 qq endpoint cancel 取消';
|
|
52
55
|
if (state.pendingBotKind) {
|
|
53
|
-
return `⚠️ endpoint「${state.pendingBotKind.endpointId}」等待公域/私域选择,回复 public/private 或 qq
|
|
56
|
+
return `⚠️ endpoint「${state.pendingBotKind.endpointId}」等待公域/私域选择,回复 public/private 或 qq endpoint cancel`;
|
|
54
57
|
}
|
|
55
58
|
return undefined;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
/** `qq
|
|
59
|
-
export function runQqEndpointList(
|
|
61
|
+
/** `qq endpoint list`:运行中的 endpoints(本 generation adapter create 注册)+ 配置里的 endpoints */
|
|
62
|
+
export async function runQqEndpointList(
|
|
63
|
+
state: QqRuntimeState,
|
|
64
|
+
store: EndpointConfigurationStore,
|
|
65
|
+
): Promise<string> {
|
|
60
66
|
return formatEndpointList(qqEndpointListSpec, {
|
|
61
67
|
running: state.endpoints.values(),
|
|
62
|
-
configured:
|
|
68
|
+
configured: await store.list('qq'),
|
|
63
69
|
footer: busyFooter(state),
|
|
64
70
|
});
|
|
65
71
|
}
|
|
@@ -70,28 +76,28 @@ const qqEndpointListSpec = {
|
|
|
70
76
|
describeEntry: (entry: ConfiguredEndpointEntry) => `appid: ${String(entry.appid)}`,
|
|
71
77
|
} as const;
|
|
72
78
|
|
|
73
|
-
function writeEndpointWithBotKind(
|
|
79
|
+
async function writeEndpointWithBotKind(
|
|
74
80
|
pending: QqPendingBotKind,
|
|
75
81
|
botKind: QqBotKind,
|
|
76
|
-
|
|
77
|
-
): string {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
pending.appId,
|
|
81
|
-
pending.appSecret,
|
|
82
|
-
projectRoot,
|
|
83
|
-
);
|
|
82
|
+
store: EndpointConfigurationStore,
|
|
83
|
+
): Promise<string> {
|
|
84
|
+
const appidKey = buildEndpointEnvKey('qq', pending.endpointId, 'appid');
|
|
85
|
+
const secretKey = buildEndpointEnvKey('qq', pending.endpointId, 'secret');
|
|
84
86
|
const intentFields = defaultQqEndpointIntentFields(botKind);
|
|
85
|
-
const filePath =
|
|
86
|
-
|
|
87
|
+
const { filePath } = await store.add({
|
|
88
|
+
adapterKey: 'qq',
|
|
89
|
+
entry: {
|
|
87
90
|
id: pending.endpointId,
|
|
88
|
-
appid:
|
|
89
|
-
secret:
|
|
91
|
+
appid: `\${${appidKey}}`,
|
|
92
|
+
secret: `\${${secretKey}}`,
|
|
90
93
|
botKind: intentFields.botKind,
|
|
91
94
|
intents: [...intentFields.intents],
|
|
92
95
|
},
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
environment: {
|
|
97
|
+
[appidKey]: pending.appId,
|
|
98
|
+
[secretKey]: pending.appSecret,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
95
101
|
return (
|
|
96
102
|
`✅ 已按 botKind=${intentFields.botKind} 将 endpoint「${pending.endpointId}」写入 .env 与 ${filePath}。\n` +
|
|
97
103
|
`intents: ${intentFields.intents.join(', ')}。\n` +
|
|
@@ -103,16 +109,16 @@ function writeEndpointWithBotKind(
|
|
|
103
109
|
* 完成 pending 的公域/私域选择:一次性写 .env + yaml。
|
|
104
110
|
* @returns 成功文案;调用方负责清 pending。
|
|
105
111
|
*/
|
|
106
|
-
export function completeQqPendingBotKind(
|
|
112
|
+
export async function completeQqPendingBotKind(
|
|
107
113
|
pending: QqPendingBotKind,
|
|
108
114
|
botKind: QqBotKind,
|
|
109
|
-
|
|
110
|
-
): string {
|
|
111
|
-
return writeEndpointWithBotKind(pending, botKind,
|
|
115
|
+
store: EndpointConfigurationStore,
|
|
116
|
+
): Promise<string> {
|
|
117
|
+
return await writeEndpointWithBotKind(pending, botKind, store);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
/**
|
|
115
|
-
* `qq
|
|
121
|
+
* `qq endpoint add [id]`:启动扫码绑定流程。
|
|
116
122
|
* 返回的 Promise 在二维码链接就绪(或前置失败)时 resolve 为首条回复;
|
|
117
123
|
* 后续状态(已扫码 / 成功 / 失败 / 过期刷新)通过 reply 推回当前会话。
|
|
118
124
|
* 扫码成功后凭据暂存内存并询问公域/私域;确认后一次性写 .env + yaml。
|
|
@@ -121,12 +127,12 @@ export function runQqEndpointAdd(
|
|
|
121
127
|
state: QqRuntimeState,
|
|
122
128
|
id: string | undefined,
|
|
123
129
|
reply: QqCommandReply,
|
|
124
|
-
|
|
130
|
+
store: EndpointConfigurationStore,
|
|
125
131
|
input?: unknown,
|
|
126
132
|
): Promise<string> {
|
|
127
133
|
if (state.bindFlow || state.pendingBotKind) {
|
|
128
134
|
return Promise.resolve(
|
|
129
|
-
'已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先发送 qq
|
|
135
|
+
'已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先发送 qq endpoint cancel 取消后再试',
|
|
130
136
|
);
|
|
131
137
|
}
|
|
132
138
|
const endpointId = id?.trim() || undefined;
|
|
@@ -168,7 +174,7 @@ export function runQqEndpointAdd(
|
|
|
168
174
|
// 无会话上下文(Host 等)时无法追问,直接按公域一次性写入
|
|
169
175
|
if (!sessionKey) {
|
|
170
176
|
settle(
|
|
171
|
-
writeEndpointWithBotKind(
|
|
177
|
+
await writeEndpointWithBotKind(
|
|
172
178
|
{
|
|
173
179
|
endpointId: finalName,
|
|
174
180
|
appId,
|
|
@@ -176,7 +182,7 @@ export function runQqEndpointAdd(
|
|
|
176
182
|
sessionKey: '',
|
|
177
183
|
},
|
|
178
184
|
'public',
|
|
179
|
-
|
|
185
|
+
store,
|
|
180
186
|
) +
|
|
181
187
|
'\n(当前会话无法交互选择公/私域;私域请手动改配置。)',
|
|
182
188
|
);
|
|
@@ -208,7 +214,7 @@ export function runQqEndpointAdd(
|
|
|
208
214
|
});
|
|
209
215
|
}
|
|
210
216
|
|
|
211
|
-
/** `qq
|
|
217
|
+
/** `qq endpoint cancel`:中止进行中的绑定或待选 botKind */
|
|
212
218
|
export function runQqEndpointCancel(state: QqRuntimeState): string {
|
|
213
219
|
if (state.bindFlow) {
|
|
214
220
|
state.bindFlow.stop();
|
|
@@ -223,18 +229,18 @@ export function runQqEndpointCancel(state: QqRuntimeState): string {
|
|
|
223
229
|
return '当前没有进行中的 QQ 绑定流程';
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
/** `qq
|
|
227
|
-
export function runQqEndpointRemove(
|
|
232
|
+
/** `qq endpoint remove <id>`:从 zhin.config.yml 移除对应 endpoints 项 */
|
|
233
|
+
export async function runQqEndpointRemove(
|
|
228
234
|
_state: QqRuntimeState,
|
|
229
235
|
id: string,
|
|
230
|
-
|
|
231
|
-
): string {
|
|
232
|
-
return removeEndpointById(qqEndpointListSpec, id,
|
|
236
|
+
store: EndpointConfigurationStore,
|
|
237
|
+
): Promise<string> {
|
|
238
|
+
return await removeEndpointById(qqEndpointListSpec, id, store);
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
/**
|
|
236
242
|
* 通用套件生成的 QQ endpoint 命令(add 经 bindFlow 钩子走扫码绑定)。
|
|
237
|
-
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
|
|
243
|
+
* commands/qq/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
|
|
238
244
|
*/
|
|
239
245
|
export const qqEndpointCommands = createEndpointCommands({
|
|
240
246
|
...qqEndpointListSpec,
|
|
@@ -247,5 +253,11 @@ export const qqEndpointCommands = createEndpointCommands({
|
|
|
247
253
|
addDescription:
|
|
248
254
|
'手机 QQ 扫码绑定机器人:确认公域/私域后一次性写入 .env 与 zhin.config.yml(重启生效)',
|
|
249
255
|
bindFlow: ({ id, reply, input, use }) =>
|
|
250
|
-
runQqEndpointAdd(
|
|
256
|
+
runQqEndpointAdd(
|
|
257
|
+
use(qqRuntimeStateToken),
|
|
258
|
+
id,
|
|
259
|
+
reply,
|
|
260
|
+
use(endpointConfigurationStoreToken),
|
|
261
|
+
input,
|
|
262
|
+
),
|
|
251
263
|
}, defineCommand);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type
|
|
1
|
+
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type QqEndpointConfig, type QqChannelKind, type QqInboundMessage, type QqWireSegment, type ResolvedQqConfig, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig, } from './protocol.js';
|
|
2
2
|
export { DEFAULT_QQ_BOT_KIND, QQ_INTENTS_BY_KIND, defaultQqEndpointIntentFields, parseQqBotKind, resolveQqIntents, type QqBotKind, type QqIntent, } from './qq-intents.js';
|
|
3
3
|
export { formatOutbound, resolveMediaFile, type QqOutboundElem, type QqOutboundMessage, } from './outbound.js';
|
|
4
4
|
export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqOfficialApi, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
|
package/lib/outbound.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 不可映射的段一律降级为文本并 warn(保持既有 fallback 行为)。
|
|
14
14
|
*/
|
|
15
15
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
16
|
-
import { isMediaRef } from '@zhin.js/
|
|
16
|
+
import { isMediaRef } from '@zhin.js/im-contract';
|
|
17
17
|
const logger = getLogger('qq');
|
|
18
18
|
/**
|
|
19
19
|
* 解析 image/audio/video/file 段的投递源(canonical MediaRef 唯一来源):
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQ Official Bot protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
|
|
3
|
-
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
|
-
*/
|
|
1
|
+
/** QQ Official Bot protocol helpers. Canonicalization is owned by Core. */
|
|
5
2
|
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
|
-
/**
|
|
7
|
-
export interface
|
|
8
|
-
readonly id
|
|
9
|
-
readonly appid
|
|
10
|
-
readonly secret
|
|
3
|
+
/** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
|
|
4
|
+
export interface QqEndpointConfig {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly appid: string;
|
|
7
|
+
readonly secret: string;
|
|
11
8
|
/** Default `websocket`. `webhook` / `middleware` use httpHostToken POST. */
|
|
12
9
|
readonly mode?: 'websocket' | 'webhook' | 'middleware';
|
|
13
10
|
readonly sandbox?: boolean;
|
|
@@ -18,24 +15,6 @@ export interface QqAdapterConfig {
|
|
|
18
15
|
readonly accessTokenUrl?: string;
|
|
19
16
|
readonly gatewayUrl?: string;
|
|
20
17
|
readonly webhookPath?: string;
|
|
21
|
-
readonly port?: number;
|
|
22
|
-
readonly path?: string;
|
|
23
|
-
/** Transitional: legacy root `endpoints[]` with `context: qq`. */
|
|
24
|
-
readonly endpoints?: ReadonlyArray<{
|
|
25
|
-
readonly context?: string;
|
|
26
|
-
readonly id?: string;
|
|
27
|
-
readonly appid?: string;
|
|
28
|
-
readonly secret?: string;
|
|
29
|
-
readonly mode?: 'websocket' | 'webhook' | 'middleware';
|
|
30
|
-
readonly sandbox?: boolean;
|
|
31
|
-
readonly intents?: readonly string[];
|
|
32
|
-
readonly botKind?: string;
|
|
33
|
-
readonly accessTokenUrl?: string;
|
|
34
|
-
readonly gatewayUrl?: string;
|
|
35
|
-
readonly webhookPath?: string;
|
|
36
|
-
readonly port?: number;
|
|
37
|
-
readonly path?: string;
|
|
38
|
-
}>;
|
|
39
18
|
}
|
|
40
19
|
export interface ResolvedQqWebsocketConfig {
|
|
41
20
|
readonly context: 'qq';
|
|
@@ -84,7 +63,7 @@ export interface QqWireSegment {
|
|
|
84
63
|
readonly type: string;
|
|
85
64
|
readonly data?: Record<string, unknown>;
|
|
86
65
|
}
|
|
87
|
-
export declare function resolveQqConfig(config
|
|
66
|
+
export declare function resolveQqConfig(config: QqEndpointConfig): ResolvedQqConfig;
|
|
88
67
|
/**
|
|
89
68
|
* 入站归一化 → ConversationRef:`direct`(频道私信)映射为 guild 容器内的
|
|
90
69
|
* private 会话;频道消息的所属 guild 进 `parent`。
|
package/lib/protocol.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQ Official Bot protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
|
|
3
|
-
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
|
-
*/
|
|
5
|
-
import { pickCredential } from 'zhin.js/adapter';
|
|
1
|
+
/** QQ Official Bot protocol helpers. Canonicalization is owned by Core. */
|
|
6
2
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
3
|
import { resolveQqIntents } from './qq-intents.js';
|
|
8
4
|
const logger = getLogger('qq');
|
|
9
|
-
export function resolveQqConfig(config
|
|
10
|
-
const
|
|
11
|
-
const appid =
|
|
12
|
-
const secret =
|
|
13
|
-
|
|
14
|
-
throw new TypeError('QQ adapter requires appid + secret (plugins.<key>.appid/secret or endpoints with context: qq)');
|
|
15
|
-
}
|
|
16
|
-
const id = (typeof config.id === 'string' && config.id)
|
|
17
|
-
|| (typeof entry?.id === 'string' && entry.id)
|
|
18
|
-
|| process.env.QQ_BOT_NAME
|
|
19
|
-
|| 'qq-bot';
|
|
20
|
-
const mode = config.mode ?? entry?.mode ?? 'websocket';
|
|
5
|
+
export function resolveQqConfig(config) {
|
|
6
|
+
const id = requiredEndpointField(config.id, 'id');
|
|
7
|
+
const appid = requiredEndpointField(config.appid, 'appid');
|
|
8
|
+
const secret = requiredEndpointField(config.secret, 'secret');
|
|
9
|
+
const mode = config.mode ?? 'websocket';
|
|
21
10
|
if (mode === 'webhook' || mode === 'middleware') {
|
|
22
|
-
const webhookPathRaw = config.webhookPath ??
|
|
11
|
+
const webhookPathRaw = config.webhookPath ?? '/qq/webhook';
|
|
23
12
|
const webhookPath = webhookPathRaw.startsWith('/') ? webhookPathRaw : `/${webhookPathRaw}`;
|
|
24
13
|
return {
|
|
25
14
|
context: 'qq',
|
|
@@ -28,7 +17,7 @@ export function resolveQqConfig(config = {}) {
|
|
|
28
17
|
appid,
|
|
29
18
|
secret,
|
|
30
19
|
webhookPath,
|
|
31
|
-
sandbox: config.sandbox === true
|
|
20
|
+
sandbox: config.sandbox === true,
|
|
32
21
|
};
|
|
33
22
|
}
|
|
34
23
|
return {
|
|
@@ -37,15 +26,21 @@ export function resolveQqConfig(config = {}) {
|
|
|
37
26
|
id,
|
|
38
27
|
appid,
|
|
39
28
|
secret,
|
|
40
|
-
sandbox: config.sandbox === true
|
|
29
|
+
sandbox: config.sandbox === true,
|
|
41
30
|
intents: resolveQqIntents({
|
|
42
|
-
intents: config.intents
|
|
43
|
-
botKind: config.botKind
|
|
31
|
+
intents: config.intents,
|
|
32
|
+
botKind: config.botKind,
|
|
44
33
|
}),
|
|
45
|
-
accessTokenUrl: config.accessTokenUrl
|
|
46
|
-
gatewayUrl: config.gatewayUrl
|
|
34
|
+
accessTokenUrl: config.accessTokenUrl,
|
|
35
|
+
gatewayUrl: config.gatewayUrl,
|
|
47
36
|
};
|
|
48
37
|
}
|
|
38
|
+
function requiredEndpointField(value, field) {
|
|
39
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
40
|
+
throw new TypeError(`QQ endpoint requires a non-empty ${field}`);
|
|
41
|
+
}
|
|
42
|
+
return value.trim();
|
|
43
|
+
}
|
|
49
44
|
/**
|
|
50
45
|
* 入站归一化 → ConversationRef:`direct`(频道私信)映射为 guild 容器内的
|
|
51
46
|
* private 会话;频道消息的所属 guild 进 `parent`。
|