@zhin.js/adapter-qq 5.0.1 → 5.0.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 +60 -0
- package/README.md +52 -408
- package/adapters/qq.ts +45 -0
- package/agent/tools/add_role.ts +23 -0
- package/agent/tools/channel_info.ts +19 -0
- package/agent/tools/create_role.ts +22 -0
- package/agent/tools/list_channels.ts +20 -0
- package/agent/tools/list_guilds.ts +17 -0
- package/agent/tools/list_roles.ts +20 -0
- package/agent/tools/member_detail.ts +20 -0
- package/agent/tools/remove_role.ts +23 -0
- package/lib/endpoint.d.ts +64 -120
- package/lib/endpoint.js +279 -564
- package/lib/index.d.ts +6 -23
- package/lib/index.js +6 -375
- package/lib/platform-permit.d.ts +1 -2
- package/lib/platform-permit.js +4 -2
- package/lib/protocol.d.ts +96 -0
- package/lib/protocol.js +129 -0
- package/lib/qq-agent-deps.d.ts +21 -0
- package/lib/qq-agent-deps.js +30 -0
- package/lib/webhook.d.ts +18 -0
- package/lib/webhook.js +100 -0
- package/lib/ws.d.ts +24 -0
- package/lib/ws.js +118 -0
- package/package.json +47 -33
- package/plugin.ts +13 -0
- package/schema.json +51 -0
- package/src/endpoint.ts +313 -605
- package/src/index.ts +55 -368
- package/src/platform-permit.ts +1 -2
- package/src/protocol.ts +229 -0
- package/src/qq-agent-deps.ts +60 -0
- package/src/webhook.ts +127 -0
- package/src/ws.ts +172 -0
- package/client/Dashboard.tsx +0 -230
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -30
- package/lib/adapter.d.ts +0 -32
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -82
- package/lib/adapter.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/gateway-config.d.ts +0 -14
- package/lib/gateway-config.d.ts.map +0 -1
- package/lib/gateway-config.js +0 -32
- package/lib/gateway-config.js.map +0 -1
- package/lib/group-at-normalize.d.ts +0 -8
- package/lib/group-at-normalize.d.ts.map +0 -1
- package/lib/group-at-normalize.js +0 -68
- package/lib/group-at-normalize.js.map +0 -1
- package/lib/inbound-action.d.ts +0 -15
- package/lib/inbound-action.d.ts.map +0 -1
- package/lib/inbound-action.js +0 -57
- package/lib/inbound-action.js.map +0 -1
- package/lib/inbound-normalize.d.ts +0 -11
- package/lib/inbound-normalize.d.ts.map +0 -1
- package/lib/inbound-normalize.js +0 -47
- package/lib/inbound-normalize.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/mention-enrich.d.ts +0 -12
- package/lib/mention-enrich.d.ts.map +0 -1
- package/lib/mention-enrich.js +0 -52
- package/lib/mention-enrich.js.map +0 -1
- package/lib/outbound-keyboard.d.ts +0 -4
- package/lib/outbound-keyboard.d.ts.map +0 -1
- package/lib/outbound-keyboard.js +0 -96
- package/lib/outbound-keyboard.js.map +0 -1
- package/lib/outbound-markdown.d.ts +0 -24
- package/lib/outbound-markdown.d.ts.map +0 -1
- package/lib/outbound-markdown.js +0 -177
- package/lib/outbound-markdown.js.map +0 -1
- package/lib/outbound-media.d.ts +0 -11
- package/lib/outbound-media.d.ts.map +0 -1
- package/lib/outbound-media.js +0 -51
- package/lib/outbound-media.js.map +0 -1
- package/lib/outbound-mixed-media.d.ts +0 -21
- package/lib/outbound-mixed-media.d.ts.map +0 -1
- package/lib/outbound-mixed-media.js +0 -78
- package/lib/outbound-mixed-media.js.map +0 -1
- package/lib/platform-permit.d.ts.map +0 -1
- package/lib/platform-permit.js.map +0 -1
- package/lib/qq-bind-api.d.ts +0 -37
- package/lib/qq-bind-api.d.ts.map +0 -1
- package/lib/qq-bind-api.js +0 -90
- package/lib/qq-bind-api.js.map +0 -1
- package/lib/qq-bind-flow.d.ts +0 -20
- package/lib/qq-bind-flow.d.ts.map +0 -1
- package/lib/qq-bind-flow.js +0 -86
- package/lib/qq-bind-flow.js.map +0 -1
- package/lib/qq-bind-persist.d.ts +0 -11
- package/lib/qq-bind-persist.d.ts.map +0 -1
- package/lib/qq-bind-persist.js +0 -52
- package/lib/qq-bind-persist.js.map +0 -1
- package/lib/qq-endpoint-manager.d.ts +0 -16
- package/lib/qq-endpoint-manager.d.ts.map +0 -1
- package/lib/qq-endpoint-manager.js +0 -159
- package/lib/qq-endpoint-manager.js.map +0 -1
- package/lib/sdk-version.d.ts +0 -8
- package/lib/sdk-version.d.ts.map +0 -1
- package/lib/sdk-version.js +0 -19
- package/lib/sdk-version.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -45
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -101
- package/src/gateway-config.ts +0 -48
- package/src/group-at-normalize.ts +0 -76
- package/src/inbound-action.ts +0 -87
- package/src/inbound-normalize.ts +0 -57
- package/src/mention-enrich.ts +0 -71
- package/src/outbound-keyboard.ts +0 -118
- package/src/outbound-markdown.ts +0 -203
- package/src/outbound-media.ts +0 -58
- package/src/outbound-mixed-media.ts +0 -101
- package/src/qq-bind-api.ts +0 -145
- package/src/qq-bind-flow.ts +0 -127
- package/src/qq-bind-persist.ts +0 -71
- package/src/qq-endpoint-manager.ts +0 -199
- package/src/sdk-version.ts +0 -21
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -53
- /package/{skills/qq → agent}/PERMITS.md +0 -0
- /package/{skills/qq/SKILL.md → agent/skills/qq.md} +0 -0
package/lib/index.d.ts
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
web: PageManager;
|
|
8
|
-
router: Router;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
declare module "zhin.js" {
|
|
13
|
-
interface Adapters {
|
|
14
|
-
qq: QQAdapter;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export * from "./types.js";
|
|
18
|
-
export { QQEndpoint } from "./endpoint.js";
|
|
19
|
-
export { QQAdapter } from "./adapter.js";
|
|
20
|
-
export { startQqBindFlow } from "./qq-bind-flow.js";
|
|
21
|
-
export * from "./qq-bind-api.js";
|
|
22
|
-
export { QqEndpointManager, disposeQqEndpointProvision } from "./qq-endpoint-manager.js";
|
|
23
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type ParsedSendTarget, type QqAdapterConfig, type QqChannelKind, type QqInboundMessage, type QqWireSegment, type ResolvedQqConfig, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig, } from './protocol.js';
|
|
2
|
+
export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
|
|
3
|
+
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
|
|
4
|
+
export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
5
|
+
export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, type QqAgentDeps, type QqAgentEndpoint, } from './qq-agent-deps.js';
|
|
6
|
+
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, registerQqPlatformPermitChecker, } from './platform-permit.js';
|
package/lib/index.js
CHANGED
|
@@ -1,375 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { platformPermit, qqGuildPermitResolver, registerQqPlatformPermitChecker, } from "./platform-permit.js";
|
|
8
|
-
import { disposeQqEndpointProvision } from "./qq-endpoint-manager.js";
|
|
9
|
-
export * from "./types.js";
|
|
10
|
-
export { QQEndpoint } from "./endpoint.js";
|
|
11
|
-
export { QQAdapter } from "./adapter.js";
|
|
12
|
-
export { startQqBindFlow } from "./qq-bind-flow.js";
|
|
13
|
-
export * from "./qq-bind-api.js";
|
|
14
|
-
export { QqEndpointManager, disposeQqEndpointProvision } from "./qq-endpoint-manager.js";
|
|
15
|
-
const plugin = usePlugin();
|
|
16
|
-
const { provide, useContext, onDispose } = plugin;
|
|
17
|
-
useContext("router", (router) => {
|
|
18
|
-
provide({
|
|
19
|
-
name: "qq",
|
|
20
|
-
description: "QQ Official Endpoint Adapter",
|
|
21
|
-
mounted: async (p) => {
|
|
22
|
-
const adapter = new QQAdapter(p, router);
|
|
23
|
-
await adapter.start();
|
|
24
|
-
return adapter;
|
|
25
|
-
},
|
|
26
|
-
dispose: async (adapter) => {
|
|
27
|
-
await adapter.stop();
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
useContext('tool', 'qq', (toolService, qq) => {
|
|
32
|
-
const disposers = [];
|
|
33
|
-
disposers.push(registerQqPlatformPermitChecker());
|
|
34
|
-
const sceneTools = createSceneManagementTools(qq, 'qq', { permitResolver: qqGuildPermitResolver, registerChecker: false });
|
|
35
|
-
disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
|
|
36
|
-
// 获取频道列表
|
|
37
|
-
disposers.push(toolService.addTool({
|
|
38
|
-
name: 'qq_list_guilds',
|
|
39
|
-
description: '获取 QQ 频道列表',
|
|
40
|
-
parameters: {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties: {
|
|
43
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
44
|
-
},
|
|
45
|
-
required: ['endpoint_id'],
|
|
46
|
-
},
|
|
47
|
-
platforms: ['qq'],
|
|
48
|
-
tags: ['qq'],
|
|
49
|
-
execute: async (args) => {
|
|
50
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
51
|
-
if (!endpoint)
|
|
52
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
53
|
-
const guilds = await endpoint.getGuilds();
|
|
54
|
-
return { guilds, count: guilds.length };
|
|
55
|
-
},
|
|
56
|
-
}, plugin.name));
|
|
57
|
-
// 获取子频道列表
|
|
58
|
-
disposers.push(toolService.addTool({
|
|
59
|
-
name: 'qq_list_channels',
|
|
60
|
-
description: '获取 QQ 频道下的子频道列表',
|
|
61
|
-
parameters: {
|
|
62
|
-
type: 'object',
|
|
63
|
-
properties: {
|
|
64
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
65
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
66
|
-
},
|
|
67
|
-
required: ['endpoint_id', 'guild_id'],
|
|
68
|
-
},
|
|
69
|
-
platforms: ['qq'],
|
|
70
|
-
tags: ['qq'],
|
|
71
|
-
permissions: [platformPermit('manage_channels')],
|
|
72
|
-
execute: async (args) => {
|
|
73
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
74
|
-
if (!endpoint)
|
|
75
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
76
|
-
const channels = await endpoint.getChannels(args.guild_id);
|
|
77
|
-
return { channels, count: channels.length };
|
|
78
|
-
},
|
|
79
|
-
}, plugin.name));
|
|
80
|
-
// 获取角色列表
|
|
81
|
-
disposers.push(toolService.addTool({
|
|
82
|
-
name: 'qq_list_roles',
|
|
83
|
-
description: '获取 QQ 频道角色列表',
|
|
84
|
-
parameters: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
properties: {
|
|
87
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
88
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
89
|
-
},
|
|
90
|
-
required: ['endpoint_id', 'guild_id'],
|
|
91
|
-
},
|
|
92
|
-
platforms: ['qq'],
|
|
93
|
-
tags: ['qq'],
|
|
94
|
-
permissions: [platformPermit('manage_roles')],
|
|
95
|
-
execute: async (args) => {
|
|
96
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
97
|
-
if (!endpoint)
|
|
98
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
99
|
-
const roles = await endpoint.getGuildRoles(args.guild_id);
|
|
100
|
-
return { roles, count: roles.length };
|
|
101
|
-
},
|
|
102
|
-
}, plugin.name));
|
|
103
|
-
// 创建角色
|
|
104
|
-
disposers.push(toolService.addTool({
|
|
105
|
-
name: 'qq_create_role',
|
|
106
|
-
description: '创建 QQ 频道角色',
|
|
107
|
-
parameters: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
111
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
112
|
-
name: { type: 'string', description: '角色名称' },
|
|
113
|
-
color: { type: 'number', description: '颜色(RGB 十进制数值)' },
|
|
114
|
-
},
|
|
115
|
-
required: ['endpoint_id', 'guild_id', 'name'],
|
|
116
|
-
},
|
|
117
|
-
platforms: ['qq'],
|
|
118
|
-
tags: ['qq'],
|
|
119
|
-
permissions: [platformPermit('guild_owner')],
|
|
120
|
-
execute: async (args) => {
|
|
121
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
122
|
-
if (!endpoint)
|
|
123
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
124
|
-
const role = await endpoint.createGuildRole(args.guild_id, args.name, args.color);
|
|
125
|
-
return { success: !!role, role, message: role ? `角色 "${args.name}" 创建成功` : '创建失败' };
|
|
126
|
-
},
|
|
127
|
-
}, plugin.name));
|
|
128
|
-
// 添加角色
|
|
129
|
-
disposers.push(toolService.addTool({
|
|
130
|
-
name: 'qq_add_role',
|
|
131
|
-
description: '给成员添加 QQ 频道角色',
|
|
132
|
-
parameters: {
|
|
133
|
-
type: 'object',
|
|
134
|
-
properties: {
|
|
135
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
136
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
137
|
-
channel_id: { type: 'string', description: '子频道 ID' },
|
|
138
|
-
user_id: { type: 'string', description: '用户 ID' },
|
|
139
|
-
role_id: { type: 'string', description: '角色 ID' },
|
|
140
|
-
},
|
|
141
|
-
required: ['endpoint_id', 'guild_id', 'channel_id', 'user_id', 'role_id'],
|
|
142
|
-
},
|
|
143
|
-
platforms: ['qq'],
|
|
144
|
-
tags: ['qq'],
|
|
145
|
-
permissions: [platformPermit('manage_roles')],
|
|
146
|
-
execute: async (args) => {
|
|
147
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
148
|
-
if (!endpoint)
|
|
149
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
150
|
-
const success = await endpoint.addMemberRole(args.guild_id, args.channel_id, args.user_id, args.role_id);
|
|
151
|
-
return { success, message: success ? '已给成员添加角色' : '操作失败' };
|
|
152
|
-
},
|
|
153
|
-
}, plugin.name));
|
|
154
|
-
// 移除角色
|
|
155
|
-
disposers.push(toolService.addTool({
|
|
156
|
-
name: 'qq_remove_role',
|
|
157
|
-
description: '移除成员的 QQ 频道角色',
|
|
158
|
-
parameters: {
|
|
159
|
-
type: 'object',
|
|
160
|
-
properties: {
|
|
161
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
162
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
163
|
-
channel_id: { type: 'string', description: '子频道 ID' },
|
|
164
|
-
user_id: { type: 'string', description: '用户 ID' },
|
|
165
|
-
role_id: { type: 'string', description: '角色 ID' },
|
|
166
|
-
},
|
|
167
|
-
required: ['endpoint_id', 'guild_id', 'channel_id', 'user_id', 'role_id'],
|
|
168
|
-
},
|
|
169
|
-
platforms: ['qq'],
|
|
170
|
-
tags: ['qq'],
|
|
171
|
-
permissions: [platformPermit('manage_roles')],
|
|
172
|
-
execute: async (args) => {
|
|
173
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
174
|
-
if (!endpoint)
|
|
175
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
176
|
-
const success = await endpoint.removeMemberRole(args.guild_id, args.channel_id, args.user_id, args.role_id);
|
|
177
|
-
return { success, message: success ? '已移除成员的角色' : '操作失败' };
|
|
178
|
-
},
|
|
179
|
-
}, plugin.name));
|
|
180
|
-
// 子频道详情
|
|
181
|
-
disposers.push(toolService.addTool({
|
|
182
|
-
name: 'qq_channel_info',
|
|
183
|
-
description: '获取 QQ 频道中指定子频道的详细信息',
|
|
184
|
-
parameters: {
|
|
185
|
-
type: 'object',
|
|
186
|
-
properties: {
|
|
187
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
188
|
-
channel_id: { type: 'string', description: '子频道 ID' },
|
|
189
|
-
},
|
|
190
|
-
required: ['endpoint_id', 'channel_id'],
|
|
191
|
-
},
|
|
192
|
-
platforms: ['qq'],
|
|
193
|
-
tags: ['qq'],
|
|
194
|
-
permissions: [platformPermit('guild_admin')],
|
|
195
|
-
execute: async (args) => {
|
|
196
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
197
|
-
if (!endpoint)
|
|
198
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
199
|
-
const info = await endpoint.getChannelInfo(args.channel_id);
|
|
200
|
-
return info;
|
|
201
|
-
},
|
|
202
|
-
}, plugin.name));
|
|
203
|
-
// 单成员详情
|
|
204
|
-
disposers.push(toolService.addTool({
|
|
205
|
-
name: 'qq_member_detail',
|
|
206
|
-
description: '获取 QQ 频道中指定成员的详细信息',
|
|
207
|
-
parameters: {
|
|
208
|
-
type: 'object',
|
|
209
|
-
properties: {
|
|
210
|
-
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
211
|
-
guild_id: { type: 'string', description: '频道 ID' },
|
|
212
|
-
user_id: { type: 'string', description: '用户 ID' },
|
|
213
|
-
},
|
|
214
|
-
required: ['endpoint_id', 'guild_id', 'user_id'],
|
|
215
|
-
},
|
|
216
|
-
platforms: ['qq'],
|
|
217
|
-
tags: ['qq'],
|
|
218
|
-
permissions: [platformPermit('guild_admin')],
|
|
219
|
-
execute: async (args) => {
|
|
220
|
-
const endpoint = qq.endpoints.get(args.endpoint_id);
|
|
221
|
-
if (!endpoint)
|
|
222
|
-
throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
223
|
-
const member = await endpoint.getGuildMember(args.guild_id, args.user_id);
|
|
224
|
-
return member;
|
|
225
|
-
},
|
|
226
|
-
}, plugin.name));
|
|
227
|
-
return () => disposers.forEach(d => d());
|
|
228
|
-
});
|
|
229
|
-
// ── Web 控制台 ─────────────────────────────────────────────────────────
|
|
230
|
-
useContext("web", (pageManager) => {
|
|
231
|
-
pageManager.addEntry({
|
|
232
|
-
id: "qq",
|
|
233
|
-
development: path.resolve(import.meta.dirname, "../client/index.tsx"),
|
|
234
|
-
production: path.resolve(import.meta.dirname, "../dist/index.js"),
|
|
235
|
-
meta: { name: "QQ" },
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
useContext("router", "qq", (router, qq) => {
|
|
239
|
-
router.get("/api/qq/endpoints", async (ctx) => {
|
|
240
|
-
try {
|
|
241
|
-
const endpoints = Array.from(qq.endpoints.values());
|
|
242
|
-
const result = await Promise.all(endpoints.map(async (endpoint) => {
|
|
243
|
-
try {
|
|
244
|
-
let guildCount = 0;
|
|
245
|
-
try {
|
|
246
|
-
const guilds = await endpoint.getGuilds();
|
|
247
|
-
guildCount = guilds?.length || 0;
|
|
248
|
-
}
|
|
249
|
-
catch { }
|
|
250
|
-
return {
|
|
251
|
-
name: endpoint.$config.name,
|
|
252
|
-
connected: endpoint.$connected || false,
|
|
253
|
-
guildCount,
|
|
254
|
-
status: endpoint.$connected ? "online" : "offline",
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return { name: endpoint.$config.name, connected: false, guildCount: 0, status: "error" };
|
|
259
|
-
}
|
|
260
|
-
}));
|
|
261
|
-
ctx.body = { success: true, data: result };
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
ctx.status = 500;
|
|
265
|
-
ctx.body = { success: false, error: "获取 Endpoint 数据失败" };
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
// Endpoint 连接/断开
|
|
269
|
-
router.post("/api/qq/endpoints/:name/connect", async (ctx) => {
|
|
270
|
-
try {
|
|
271
|
-
const endpoint = qq.endpoints.get(ctx.params.name);
|
|
272
|
-
if (!endpoint) {
|
|
273
|
-
ctx.status = 404;
|
|
274
|
-
ctx.body = { success: false, error: "Endpoint 不存在" };
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (endpoint.$connected) {
|
|
278
|
-
ctx.body = { success: true, message: "已经在线" };
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
await endpoint.$connect();
|
|
282
|
-
ctx.body = { success: true, message: "连接成功" };
|
|
283
|
-
}
|
|
284
|
-
catch (e) {
|
|
285
|
-
ctx.status = 500;
|
|
286
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "连接失败" };
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
router.post("/api/qq/endpoints/:name/disconnect", async (ctx) => {
|
|
290
|
-
try {
|
|
291
|
-
const endpoint = qq.endpoints.get(ctx.params.name);
|
|
292
|
-
if (!endpoint) {
|
|
293
|
-
ctx.status = 404;
|
|
294
|
-
ctx.body = { success: false, error: "Endpoint 不存在" };
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (!endpoint.$connected) {
|
|
298
|
-
ctx.body = { success: true, message: "已经离线" };
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
await endpoint.$disconnect();
|
|
302
|
-
ctx.body = { success: true, message: "已断开" };
|
|
303
|
-
}
|
|
304
|
-
catch (e) {
|
|
305
|
-
ctx.status = 500;
|
|
306
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "断开失败" };
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
// 频道列表(QQ 官方 API 返回 guild_id/guild_name,归一化为控制台用的 id/name)
|
|
310
|
-
router.get("/api/qq/endpoints/:name/guilds", async (ctx) => {
|
|
311
|
-
try {
|
|
312
|
-
const endpoint = qq.endpoints.get(ctx.params.name);
|
|
313
|
-
if (!endpoint) {
|
|
314
|
-
ctx.status = 404;
|
|
315
|
-
ctx.body = { success: false, error: "Endpoint 不存在" };
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (!endpoint.$connected) {
|
|
319
|
-
ctx.status = 400;
|
|
320
|
-
ctx.body = { success: false, error: "Endpoint 未连接" };
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const raw = await endpoint.getGuilds();
|
|
324
|
-
const guilds = (raw || []).map((g) => ({
|
|
325
|
-
id: g.guild_id ?? g.id,
|
|
326
|
-
name: g.guild_name ?? g.name,
|
|
327
|
-
icon: g.icon,
|
|
328
|
-
description: g.description,
|
|
329
|
-
memberCount: g.member_count,
|
|
330
|
-
ownerId: g.owner_id,
|
|
331
|
-
owner: g.owner,
|
|
332
|
-
joinTime: g.join_time,
|
|
333
|
-
}));
|
|
334
|
-
ctx.body = { success: true, data: guilds };
|
|
335
|
-
}
|
|
336
|
-
catch (e) {
|
|
337
|
-
ctx.status = 500;
|
|
338
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "获取频道失败" };
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
// 子频道列表(QQ 官方 API 返回 channel_id/channel_name,归一化为 id/name)
|
|
342
|
-
router.get("/api/qq/endpoints/:name/guilds/:guildId/channels", async (ctx) => {
|
|
343
|
-
try {
|
|
344
|
-
const endpoint = qq.endpoints.get(ctx.params.name);
|
|
345
|
-
if (!endpoint) {
|
|
346
|
-
ctx.status = 404;
|
|
347
|
-
ctx.body = { success: false, error: "Endpoint 不存在" };
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if (!endpoint.$connected) {
|
|
351
|
-
ctx.status = 400;
|
|
352
|
-
ctx.body = { success: false, error: "Endpoint 未连接" };
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
const raw = await endpoint.getChannels(ctx.params.guildId);
|
|
356
|
-
const channels = (raw || []).map((ch) => ({
|
|
357
|
-
id: ch.channel_id ?? ch.id,
|
|
358
|
-
name: ch.channel_name ?? ch.name,
|
|
359
|
-
type: ch.type,
|
|
360
|
-
subType: ch.sub_type,
|
|
361
|
-
position: ch.position,
|
|
362
|
-
parentId: ch.parent_id,
|
|
363
|
-
}));
|
|
364
|
-
ctx.body = { success: true, data: channels };
|
|
365
|
-
}
|
|
366
|
-
catch (e) {
|
|
367
|
-
ctx.status = 500;
|
|
368
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "获取子频道失败" };
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
onDispose(() => {
|
|
373
|
-
disposeQqEndpointProvision();
|
|
374
|
-
});
|
|
375
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, } from './protocol.js';
|
|
2
|
+
export { QqHttpEndpoint, QqWebsocketEndpoint, } from './endpoint.js';
|
|
3
|
+
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, } from './webhook.js';
|
|
4
|
+
export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
5
|
+
export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, } from './qq-agent-deps.js';
|
|
6
|
+
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, registerQqPlatformPermitChecker, } from './platform-permit.js';
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type Message } from '@zhin.js/core';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function qqGuildPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeQqGuildSenderForPermit(input: {
|
|
@@ -14,4 +14,3 @@ export declare function normalizeQqGuildSenderForPermit(input: {
|
|
|
14
14
|
};
|
|
15
15
|
export declare function checkQqPlatformPermit(perm: string, message: Message<any>): boolean;
|
|
16
16
|
export declare function registerQqPlatformPermitChecker(): () => void;
|
|
17
|
-
//# sourceMappingURL=platform-permit.d.ts.map
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
|
|
3
|
+
*/
|
|
4
|
+
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
2
5
|
const ADAPTER = 'qq';
|
|
3
6
|
export function platformPermit(perm) {
|
|
4
7
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -44,4 +47,3 @@ export function checkQqPlatformPermit(perm, message) {
|
|
|
44
47
|
export function registerQqPlatformPermitChecker() {
|
|
45
48
|
return registerPlatformPermitChecker(ADAPTER, checkQqPlatformPermit);
|
|
46
49
|
}
|
|
47
|
-
//# sourceMappingURL=platform-permit.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
6
|
+
export interface QqAdapterConfig {
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
readonly appid?: string;
|
|
9
|
+
readonly secret?: string;
|
|
10
|
+
/** Default `websocket`. `webhook` / `middleware` use httpHostToken POST. */
|
|
11
|
+
readonly mode?: 'websocket' | 'webhook' | 'middleware';
|
|
12
|
+
readonly sandbox?: boolean;
|
|
13
|
+
readonly intents?: readonly string[];
|
|
14
|
+
readonly accessTokenUrl?: string;
|
|
15
|
+
readonly gatewayUrl?: string;
|
|
16
|
+
readonly webhookPath?: string;
|
|
17
|
+
readonly port?: number;
|
|
18
|
+
readonly path?: string;
|
|
19
|
+
/** Transitional: legacy root `endpoints[]` with `context: qq`. */
|
|
20
|
+
readonly endpoints?: ReadonlyArray<{
|
|
21
|
+
readonly context?: string;
|
|
22
|
+
readonly name?: string;
|
|
23
|
+
readonly appid?: string;
|
|
24
|
+
readonly secret?: string;
|
|
25
|
+
readonly mode?: 'websocket' | 'webhook' | 'middleware';
|
|
26
|
+
readonly sandbox?: boolean;
|
|
27
|
+
readonly intents?: readonly string[];
|
|
28
|
+
readonly accessTokenUrl?: string;
|
|
29
|
+
readonly gatewayUrl?: string;
|
|
30
|
+
readonly webhookPath?: string;
|
|
31
|
+
readonly port?: number;
|
|
32
|
+
readonly path?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface ResolvedQqWebsocketConfig {
|
|
36
|
+
readonly context: 'qq';
|
|
37
|
+
readonly mode: 'websocket';
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly appid: string;
|
|
40
|
+
readonly secret: string;
|
|
41
|
+
readonly sandbox: boolean;
|
|
42
|
+
readonly intents?: readonly string[];
|
|
43
|
+
readonly accessTokenUrl?: string;
|
|
44
|
+
readonly gatewayUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ResolvedQqHttpConfig {
|
|
47
|
+
readonly context: 'qq';
|
|
48
|
+
readonly mode: 'webhook' | 'middleware';
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly appid: string;
|
|
51
|
+
readonly secret: string;
|
|
52
|
+
readonly webhookPath: string;
|
|
53
|
+
readonly sandbox: boolean;
|
|
54
|
+
}
|
|
55
|
+
export type ResolvedQqConfig = ResolvedQqWebsocketConfig | ResolvedQqHttpConfig;
|
|
56
|
+
export type QqChannelKind = 'private' | 'group' | 'channel' | 'direct';
|
|
57
|
+
export interface QqInboundMessage {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly content: string;
|
|
60
|
+
readonly channelKind: QqChannelKind;
|
|
61
|
+
readonly channelId: string;
|
|
62
|
+
readonly authorId: string;
|
|
63
|
+
readonly authorName: string;
|
|
64
|
+
readonly authorRoles?: string[];
|
|
65
|
+
readonly timestamp: number;
|
|
66
|
+
readonly guildId?: string;
|
|
67
|
+
readonly rawMessage?: string;
|
|
68
|
+
/**
|
|
69
|
+
* 入站即 @ 机器人(AT_MESSAGE_CREATE 的 mentions[].bot,或群 GROUP_AT_MESSAGE_CREATE
|
|
70
|
+
* 事件本身仅 @ 时下发)。新 Runtime 纯文本 content 需经 metadata 传递。
|
|
71
|
+
*/
|
|
72
|
+
readonly mentioned?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface QqWireSegment {
|
|
75
|
+
readonly type: string;
|
|
76
|
+
readonly data?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
export interface ParsedSendTarget {
|
|
79
|
+
readonly kind: QqChannelKind;
|
|
80
|
+
readonly id: string;
|
|
81
|
+
}
|
|
82
|
+
export declare function resolveQqConfig(config?: QqAdapterConfig): ResolvedQqConfig;
|
|
83
|
+
/**
|
|
84
|
+
* Gateway reply target:`private:uid` / `group:gid` / `channel:cid` / `direct:guildId`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function formatInboundTarget(msg: QqInboundMessage): string;
|
|
87
|
+
export declare function parseSendTarget(target: string): ParsedSendTarget;
|
|
88
|
+
export declare function senderDisplayName(msg: QqInboundMessage): string;
|
|
89
|
+
export declare function formatInboundContent(msg: QqInboundMessage): string;
|
|
90
|
+
/**
|
|
91
|
+
* Wire-encode an already-rendered outbound payload into a plain text / QQ Sendable string.
|
|
92
|
+
* Segment canonicalization is intentionally not done here.
|
|
93
|
+
*/
|
|
94
|
+
export declare function formatOutboundText(payload: unknown): string;
|
|
95
|
+
/** 从 QQ API SendResult / 审核回包中解析出站消息 ID */
|
|
96
|
+
export declare function resolveOutboundMessageId(result: unknown): string;
|
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
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';
|
|
6
|
+
export function resolveQqConfig(config = {}) {
|
|
7
|
+
const entry = config.endpoints?.find((item) => item.context === 'qq' || !item.context);
|
|
8
|
+
const appid = pickCredential(config.appid, entry?.appid, process.env.QQ_APPID, process.env.QQ_BOT_APPID);
|
|
9
|
+
const secret = pickCredential(config.secret, entry?.secret, process.env.QQ_SECRET, process.env.QQ_BOT_SECRET);
|
|
10
|
+
if (!appid || !secret) {
|
|
11
|
+
throw new TypeError('QQ adapter requires appid + secret (plugins.<key>.appid/secret or endpoints with context: qq)');
|
|
12
|
+
}
|
|
13
|
+
const name = (typeof config.name === 'string' && config.name)
|
|
14
|
+
|| (typeof entry?.name === 'string' && entry.name)
|
|
15
|
+
|| process.env.QQ_BOT_NAME
|
|
16
|
+
|| 'qq-bot';
|
|
17
|
+
const mode = config.mode ?? entry?.mode ?? 'websocket';
|
|
18
|
+
if (mode === 'webhook' || mode === 'middleware') {
|
|
19
|
+
const webhookPathRaw = config.webhookPath ?? entry?.webhookPath ?? '/qq/webhook';
|
|
20
|
+
const webhookPath = webhookPathRaw.startsWith('/') ? webhookPathRaw : `/${webhookPathRaw}`;
|
|
21
|
+
return {
|
|
22
|
+
context: 'qq',
|
|
23
|
+
mode,
|
|
24
|
+
name,
|
|
25
|
+
appid,
|
|
26
|
+
secret,
|
|
27
|
+
webhookPath,
|
|
28
|
+
sandbox: config.sandbox === true || entry?.sandbox === true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
context: 'qq',
|
|
33
|
+
mode: 'websocket',
|
|
34
|
+
name,
|
|
35
|
+
appid,
|
|
36
|
+
secret,
|
|
37
|
+
sandbox: config.sandbox === true || entry?.sandbox === true,
|
|
38
|
+
intents: config.intents ?? entry?.intents,
|
|
39
|
+
accessTokenUrl: config.accessTokenUrl ?? entry?.accessTokenUrl,
|
|
40
|
+
gatewayUrl: config.gatewayUrl ?? entry?.gatewayUrl,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Gateway reply target:`private:uid` / `group:gid` / `channel:cid` / `direct:guildId`.
|
|
45
|
+
*/
|
|
46
|
+
export function formatInboundTarget(msg) {
|
|
47
|
+
return `${msg.channelKind}:${msg.channelId}`;
|
|
48
|
+
}
|
|
49
|
+
export function parseSendTarget(target) {
|
|
50
|
+
const sep = target.indexOf(':');
|
|
51
|
+
if (sep <= 0) {
|
|
52
|
+
return { kind: 'private', id: target };
|
|
53
|
+
}
|
|
54
|
+
const head = target.slice(0, sep);
|
|
55
|
+
const rest = target.slice(sep + 1);
|
|
56
|
+
if (head === 'private' || head === 'group' || head === 'channel' || head === 'direct') {
|
|
57
|
+
return { kind: head, id: rest };
|
|
58
|
+
}
|
|
59
|
+
return { kind: 'private', id: target };
|
|
60
|
+
}
|
|
61
|
+
export function senderDisplayName(msg) {
|
|
62
|
+
return msg.authorName || msg.authorId;
|
|
63
|
+
}
|
|
64
|
+
export function formatInboundContent(msg) {
|
|
65
|
+
const text = (msg.content || msg.rawMessage || '').trim();
|
|
66
|
+
return text || '(Empty message)';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wire-encode an already-rendered outbound payload into a plain text / QQ Sendable string.
|
|
70
|
+
* Segment canonicalization is intentionally not done here.
|
|
71
|
+
*/
|
|
72
|
+
export function formatOutboundText(payload) {
|
|
73
|
+
if (typeof payload === 'string')
|
|
74
|
+
return payload;
|
|
75
|
+
const segments = Array.isArray(payload)
|
|
76
|
+
? payload
|
|
77
|
+
: payload && typeof payload === 'object' && 'type' in payload
|
|
78
|
+
? [payload]
|
|
79
|
+
: [];
|
|
80
|
+
if (segments.length === 0) {
|
|
81
|
+
return payload == null
|
|
82
|
+
? ''
|
|
83
|
+
: typeof payload === 'object'
|
|
84
|
+
? JSON.stringify(payload)
|
|
85
|
+
: String(payload);
|
|
86
|
+
}
|
|
87
|
+
return segments
|
|
88
|
+
.map((item) => {
|
|
89
|
+
if (typeof item === 'string')
|
|
90
|
+
return item;
|
|
91
|
+
const data = item.data ?? {};
|
|
92
|
+
switch (item.type) {
|
|
93
|
+
case 'text':
|
|
94
|
+
return String(data.text ?? data.content ?? '');
|
|
95
|
+
case 'at': {
|
|
96
|
+
const id = String(data.user_id ?? data.qq ?? data.id ?? '');
|
|
97
|
+
return id ? `<@!${id}>` : '';
|
|
98
|
+
}
|
|
99
|
+
case 'image':
|
|
100
|
+
return String(data.url || data.file || '');
|
|
101
|
+
case 'reply':
|
|
102
|
+
return '';
|
|
103
|
+
default:
|
|
104
|
+
return data.text != null ? String(data.text) : '';
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.join('');
|
|
109
|
+
}
|
|
110
|
+
/** 从 QQ API SendResult / 审核回包中解析出站消息 ID */
|
|
111
|
+
export function resolveOutboundMessageId(result) {
|
|
112
|
+
if (!result || typeof result !== 'object') {
|
|
113
|
+
throw new Error('QQ 发送消息失败:响应为空');
|
|
114
|
+
}
|
|
115
|
+
const row = result;
|
|
116
|
+
const nested = row.data && typeof row.data === 'object'
|
|
117
|
+
? row.data
|
|
118
|
+
: undefined;
|
|
119
|
+
const audit = (row.message_audit ?? nested?.message_audit);
|
|
120
|
+
const id = row.id ?? row.message_id ?? audit?.audit_id;
|
|
121
|
+
if (id == null || id === '') {
|
|
122
|
+
const code = row.code;
|
|
123
|
+
const msg = row.message;
|
|
124
|
+
throw new Error(code != null
|
|
125
|
+
? `QQ 发送消息失败(${String(code)})${msg ? `: ${String(msg)}` : ''}`
|
|
126
|
+
: 'QQ 发送消息失败:响应缺少消息 ID');
|
|
127
|
+
}
|
|
128
|
+
return String(id);
|
|
129
|
+
}
|