@zhin.js/adapter-qq 1.0.84 → 1.1.2
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 +940 -17
- package/PERMITS.md +25 -0
- package/README.md +135 -233
- package/adapters/qq/index.js +50 -0
- package/adapters/qq/index.ts +71 -0
- 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/agents/qq/skills/qq/tools/add_role/index.ts +21 -0
- package/agents/qq/skills/qq/tools/channel_info/index.js +17 -0
- package/agents/qq/skills/qq/tools/channel_info/index.ts +17 -0
- package/agents/qq/skills/qq/tools/create_role/index.js +20 -0
- package/agents/qq/skills/qq/tools/create_role/index.ts +20 -0
- package/agents/qq/skills/qq/tools/list_channels/index.js +18 -0
- package/agents/qq/skills/qq/tools/list_channels/index.ts +18 -0
- package/agents/qq/skills/qq/tools/list_guilds/index.js +14 -0
- package/agents/qq/skills/qq/tools/list_guilds/index.ts +14 -0
- package/agents/qq/skills/qq/tools/list_roles/index.js +18 -0
- package/agents/qq/skills/qq/tools/list_roles/index.ts +18 -0
- package/agents/qq/skills/qq/tools/member_detail/index.js +18 -0
- package/agents/qq/skills/qq/tools/member_detail/index.ts +18 -0
- package/agents/qq/skills/qq/tools/remove_role/index.js +21 -0
- package/agents/qq/skills/qq/tools/remove_role/index.ts +21 -0
- package/agents/qq/system.md +3 -0
- package/commands/qq/endpoint/add/[[id]]/index.js +3 -0
- package/commands/qq/endpoint/add/[[id]]/index.ts +3 -0
- package/commands/qq/endpoint/cancel/index.js +11 -0
- package/commands/qq/endpoint/cancel/index.ts +15 -0
- package/commands/qq/endpoint/definition.js +188 -0
- package/commands/qq/endpoint/definition.ts +263 -0
- package/commands/qq/endpoint/list/index.js +3 -0
- package/commands/qq/endpoint/list/index.ts +3 -0
- package/commands/qq/endpoint/remove/[id]/index.js +3 -0
- package/commands/qq/endpoint/remove/[id]/index.ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/endpoint.d.ts +56 -0
- package/lib/endpoint.js +456 -0
- package/lib/index.d.ts +8 -18
- package/lib/index.js +8 -338
- package/lib/outbound.d.ts +17 -0
- package/lib/outbound.js +234 -0
- package/lib/platform-permit.d.ts +15 -0
- package/lib/platform-permit.js +41 -0
- package/lib/protocol.d.ts +90 -0
- package/lib/protocol.js +161 -0
- package/lib/qq-bind-api.d.ts +36 -0
- package/lib/qq-bind-api.js +89 -0
- package/lib/qq-bind-flow.d.ts +19 -0
- package/lib/qq-bind-flow.js +85 -0
- package/lib/qq-bot-kind-prompt.d.ts +9 -0
- package/lib/qq-bot-kind-prompt.js +37 -0
- package/lib/qq-intents.d.ts +27 -0
- package/lib/qq-intents.js +51 -0
- package/lib/qq-runtime-state.d.ts +30 -0
- package/lib/qq-runtime-state.js +14 -0
- package/lib/side-event-dispatch.d.ts +10 -0
- package/lib/side-event-dispatch.js +114 -0
- package/lib/webhook.d.ts +18 -0
- package/lib/webhook.js +103 -0
- package/lib/ws.d.ts +55 -0
- package/lib/ws.js +234 -0
- package/middlewares/bot-kind-reply/index.js +47 -0
- package/middlewares/bot-kind-reply/index.ts +65 -0
- package/package.json +82 -29
- package/plugin.js +18 -0
- package/schema.json +158 -0
- package/src/client.ts +16 -0
- package/src/endpoint.ts +534 -0
- package/src/index.ts +66 -326
- package/src/outbound.ts +255 -0
- package/src/platform-permit.ts +56 -0
- package/src/protocol.ts +249 -0
- package/src/qq-bind-api.ts +145 -0
- package/src/qq-bind-flow.ts +127 -0
- package/src/qq-bot-kind-prompt.ts +47 -0
- package/src/qq-intents.ts +77 -0
- package/src/qq-runtime-state.ts +51 -0
- package/src/side-event-dispatch.ts +140 -0
- package/src/webhook.ts +132 -0
- package/src/ws.ts +331 -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 -17
- package/dist/index.js +0 -30
- package/lib/adapter.d.ts +0 -17
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -49
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -102
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -337
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -16
- 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/skills/qq/SKILL.md +0 -97
- package/src/adapter.ts +0 -58
- package/src/bot.ts +0 -347
- package/src/types.ts +0 -24
package/lib/endpoint.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ endpoints — lifecycle, outbound, admit, agent tool surface.
|
|
3
|
+
*/
|
|
4
|
+
import { Endpoint, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
5
|
+
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
6
|
+
import { formatOutbound } from './outbound.js';
|
|
7
|
+
import { formatInboundContent, parseCompoundMessageId, qqInboundConversation, qqOutboundKind, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
|
|
8
|
+
import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
|
|
9
|
+
import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
10
|
+
import { bindQqBotSideEvents, receiveQqSideEvent, } from './side-event-dispatch.js';
|
|
11
|
+
export class QqWebsocketEndpoint extends Endpoint {
|
|
12
|
+
#logger;
|
|
13
|
+
#options;
|
|
14
|
+
#createBot;
|
|
15
|
+
#bot = null;
|
|
16
|
+
#open = false;
|
|
17
|
+
#lifecycle;
|
|
18
|
+
#cleanup;
|
|
19
|
+
management = createQqEndpointManagement(() => this.#requireBot());
|
|
20
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super();
|
|
23
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
24
|
+
this.#lifecycle = createEndpointLifecycle({ name: options.config.id, reconnect: false });
|
|
25
|
+
this.#options = options;
|
|
26
|
+
this.#createBot = options.createBot ?? defaultCreateBot;
|
|
27
|
+
}
|
|
28
|
+
get client() {
|
|
29
|
+
return this.#requireBot();
|
|
30
|
+
}
|
|
31
|
+
/** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
|
|
32
|
+
get name() {
|
|
33
|
+
return this.#options.config.id;
|
|
34
|
+
}
|
|
35
|
+
async start() {
|
|
36
|
+
await this.#lifecycle.start(async (handle) => {
|
|
37
|
+
const bot = this.#createBot(this.#options.config);
|
|
38
|
+
this.#bot = bot;
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
let stopping;
|
|
41
|
+
const stopBot = async () => {
|
|
42
|
+
try {
|
|
43
|
+
bot.removeAllListeners();
|
|
44
|
+
}
|
|
45
|
+
catch { /* continue SDK cleanup */ }
|
|
46
|
+
try {
|
|
47
|
+
await bot.stop();
|
|
48
|
+
}
|
|
49
|
+
catch { /* best-effort SDK cleanup */ }
|
|
50
|
+
};
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
cancelled = true;
|
|
53
|
+
if (this.#bot === bot)
|
|
54
|
+
this.#bot = null;
|
|
55
|
+
return stopping ??= stopBot();
|
|
56
|
+
};
|
|
57
|
+
this.#cleanup = cleanup;
|
|
58
|
+
handle.onForceClose(() => { void cleanup(); });
|
|
59
|
+
try {
|
|
60
|
+
this.#bindBot(bot);
|
|
61
|
+
await bot.start();
|
|
62
|
+
if (cancelled) {
|
|
63
|
+
// SDK startup may create resources after stop; close this exact old client again.
|
|
64
|
+
await stopping;
|
|
65
|
+
await stopBot();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.#logger.info(`connected (websocket) | appid: ${this.#options.config.appid}`);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const wasCancelled = cancelled;
|
|
72
|
+
await cleanup();
|
|
73
|
+
if (wasCancelled) {
|
|
74
|
+
await stopBot();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
78
|
+
throw new Error(`QQ WebSocket 连接失败:请检查 appid/secret 是否配对、网关地址(gatewayUrl/accessTokenUrl)是否可达(原始错误:${raw})`, { cause: error });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
open() {
|
|
83
|
+
this.#open = true;
|
|
84
|
+
}
|
|
85
|
+
close() {
|
|
86
|
+
this.#open = false;
|
|
87
|
+
}
|
|
88
|
+
async stop() {
|
|
89
|
+
this.#open = false;
|
|
90
|
+
const cleanup = this.#cleanup;
|
|
91
|
+
this.#cleanup = undefined;
|
|
92
|
+
await this.#lifecycle.stop();
|
|
93
|
+
await cleanup?.();
|
|
94
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
95
|
+
}
|
|
96
|
+
async send({ conversation, payload }) {
|
|
97
|
+
const body = formatOutbound(payload);
|
|
98
|
+
const kind = qqOutboundKind(conversation);
|
|
99
|
+
const bot = this.#requireBot();
|
|
100
|
+
let result;
|
|
101
|
+
switch (kind) {
|
|
102
|
+
case 'private':
|
|
103
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
104
|
+
break;
|
|
105
|
+
case 'group':
|
|
106
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
107
|
+
break;
|
|
108
|
+
case 'channel':
|
|
109
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
110
|
+
break;
|
|
111
|
+
case 'direct':
|
|
112
|
+
if (!bot.sendDirectMessage)
|
|
113
|
+
throw new Error('QQ direct message not supported by transport');
|
|
114
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
118
|
+
this.#logger.info(`send ${kind}:${conversation.id}`
|
|
119
|
+
+ ` | id: ${messageId}`
|
|
120
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`);
|
|
121
|
+
return messageId;
|
|
122
|
+
}
|
|
123
|
+
async recallMessage(messageId) {
|
|
124
|
+
const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
|
|
125
|
+
const bot = this.#requireBot();
|
|
126
|
+
switch (kind) {
|
|
127
|
+
case 'private':
|
|
128
|
+
await bot.recallPrivateMessage?.(channelId, qqMsgId);
|
|
129
|
+
break;
|
|
130
|
+
case 'group':
|
|
131
|
+
await bot.recallGroupMessage?.(channelId, qqMsgId);
|
|
132
|
+
break;
|
|
133
|
+
case 'channel':
|
|
134
|
+
await bot.recallGuildMessage?.(channelId, qqMsgId);
|
|
135
|
+
break;
|
|
136
|
+
case 'direct':
|
|
137
|
+
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
this.#logger.debug(formatCompact({
|
|
141
|
+
op: 'qq_recall',
|
|
142
|
+
endpoint: this.#options.config.id,
|
|
143
|
+
kind,
|
|
144
|
+
messageId,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
/** Test / internal: admit a message when open. */
|
|
148
|
+
admit(msg) {
|
|
149
|
+
if (!this.#open)
|
|
150
|
+
return;
|
|
151
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
152
|
+
const content = formatInboundContent(msg);
|
|
153
|
+
this.#logger.info(`recv ${conversation.kind}:${conversation.id}`
|
|
154
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
155
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
156
|
+
+ ` | ${truncatePreview(content, 80)}`);
|
|
157
|
+
void this.emit('message.receive', {
|
|
158
|
+
conversation,
|
|
159
|
+
message: { conversation, id: msg.id },
|
|
160
|
+
content,
|
|
161
|
+
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
162
|
+
sender: {
|
|
163
|
+
id: msg.authorId,
|
|
164
|
+
name: senderDisplayName(msg) || undefined,
|
|
165
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
166
|
+
},
|
|
167
|
+
endpointId: this.#options.config.id,
|
|
168
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
169
|
+
metadata: Object.freeze({
|
|
170
|
+
channelKind: msg.channelKind,
|
|
171
|
+
userId: msg.authorId,
|
|
172
|
+
guildId: msg.guildId,
|
|
173
|
+
roles: msg.authorRoles,
|
|
174
|
+
}),
|
|
175
|
+
}).catch((err) => {
|
|
176
|
+
this.#logger.warn(formatCompact({
|
|
177
|
+
op: 'qq_gateway_receive_failed',
|
|
178
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
179
|
+
error: err instanceof Error ? err.message : String(err),
|
|
180
|
+
}));
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
#bindBot(bot) {
|
|
184
|
+
bindQqBotInboundEvents(bot, (raw) => {
|
|
185
|
+
if (this.#bot !== bot)
|
|
186
|
+
return;
|
|
187
|
+
this.#emitPlatformEvent('message', raw);
|
|
188
|
+
const msg = normalizeQqMessage(raw);
|
|
189
|
+
if (msg)
|
|
190
|
+
this.admit(msg);
|
|
191
|
+
});
|
|
192
|
+
bindQqBotSideEvents(bot, (eventName, raw) => {
|
|
193
|
+
if (this.#bot !== bot)
|
|
194
|
+
return;
|
|
195
|
+
this.#emitPlatformEvent(eventName, raw);
|
|
196
|
+
receiveQqSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, bot, eventName, raw, this.#logger);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
#emitPlatformEvent(name, event) {
|
|
200
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
201
|
+
this.#logger.warn(formatCompact({
|
|
202
|
+
op: 'qq_platform_event_failed',
|
|
203
|
+
event: name,
|
|
204
|
+
error: error instanceof Error ? error.message : String(error),
|
|
205
|
+
}));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
#requireBot() {
|
|
209
|
+
if (!this.#bot)
|
|
210
|
+
throw new Error('QQ bot not connected');
|
|
211
|
+
return this.#bot;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
|
|
215
|
+
export class QqHttpEndpoint extends Endpoint {
|
|
216
|
+
#logger;
|
|
217
|
+
#options;
|
|
218
|
+
#createBot;
|
|
219
|
+
#bot = null;
|
|
220
|
+
#open = false;
|
|
221
|
+
#lifecycle;
|
|
222
|
+
#cleanup;
|
|
223
|
+
management = createQqEndpointManagement(() => this.#requireBot());
|
|
224
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
225
|
+
constructor(options) {
|
|
226
|
+
super();
|
|
227
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
228
|
+
this.#lifecycle = createEndpointLifecycle({ name: options.config.id, reconnect: false });
|
|
229
|
+
this.#options = options;
|
|
230
|
+
this.#createBot = options.createBot ?? defaultCreateHttpBot;
|
|
231
|
+
}
|
|
232
|
+
get client() {
|
|
233
|
+
return this.#requireBot();
|
|
234
|
+
}
|
|
235
|
+
/** Live endpoint 名(Console/AdapterIndex 展示用)。 */
|
|
236
|
+
get name() {
|
|
237
|
+
return this.#options.config.id;
|
|
238
|
+
}
|
|
239
|
+
async start() {
|
|
240
|
+
await this.#lifecycle.start(async (handle) => {
|
|
241
|
+
const bot = this.#createBot(this.#options.config);
|
|
242
|
+
this.#bot = bot;
|
|
243
|
+
let cancelled = false;
|
|
244
|
+
let stopping;
|
|
245
|
+
const routes = [];
|
|
246
|
+
const stopBot = async () => {
|
|
247
|
+
try {
|
|
248
|
+
bot.removeAllListeners();
|
|
249
|
+
}
|
|
250
|
+
catch { /* continue SDK cleanup */ }
|
|
251
|
+
try {
|
|
252
|
+
await bot.stop();
|
|
253
|
+
}
|
|
254
|
+
catch { /* best-effort SDK cleanup */ }
|
|
255
|
+
};
|
|
256
|
+
const cleanup = () => {
|
|
257
|
+
cancelled = true;
|
|
258
|
+
if (this.#bot === bot)
|
|
259
|
+
this.#bot = null;
|
|
260
|
+
for (const release of routes.splice(0))
|
|
261
|
+
release();
|
|
262
|
+
return stopping ??= stopBot();
|
|
263
|
+
};
|
|
264
|
+
this.#cleanup = cleanup;
|
|
265
|
+
handle.onForceClose(() => { void cleanup(); });
|
|
266
|
+
try {
|
|
267
|
+
this.#bindBot(bot);
|
|
268
|
+
routes.push(...registerQqWebhookRoutes(this.#options.http, {
|
|
269
|
+
config: this.#options.config,
|
|
270
|
+
getBot: () => cancelled ? null : bot,
|
|
271
|
+
}));
|
|
272
|
+
await bot.start();
|
|
273
|
+
if (cancelled) {
|
|
274
|
+
// SDK startup may create resources after stop; close this exact old client again.
|
|
275
|
+
await stopping;
|
|
276
|
+
await stopBot();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
this.#logger.info(`connected (${this.#options.config.mode}) | path: ${this.#options.config.webhookPath}`);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const wasCancelled = cancelled;
|
|
283
|
+
await cleanup();
|
|
284
|
+
if (wasCancelled) {
|
|
285
|
+
await stopBot();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
open() {
|
|
293
|
+
this.#open = true;
|
|
294
|
+
}
|
|
295
|
+
close() {
|
|
296
|
+
this.#open = false;
|
|
297
|
+
}
|
|
298
|
+
async stop() {
|
|
299
|
+
this.#open = false;
|
|
300
|
+
const cleanup = this.#cleanup;
|
|
301
|
+
this.#cleanup = undefined;
|
|
302
|
+
await this.#lifecycle.stop();
|
|
303
|
+
await cleanup?.();
|
|
304
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
305
|
+
}
|
|
306
|
+
async send({ conversation, payload }) {
|
|
307
|
+
const body = formatOutbound(payload);
|
|
308
|
+
const kind = qqOutboundKind(conversation);
|
|
309
|
+
const bot = this.#requireBot();
|
|
310
|
+
let result;
|
|
311
|
+
switch (kind) {
|
|
312
|
+
case 'private':
|
|
313
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
314
|
+
break;
|
|
315
|
+
case 'group':
|
|
316
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
317
|
+
break;
|
|
318
|
+
case 'channel':
|
|
319
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
320
|
+
break;
|
|
321
|
+
case 'direct':
|
|
322
|
+
if (!bot.sendDirectMessage)
|
|
323
|
+
throw new Error('QQ direct message not supported by transport');
|
|
324
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
328
|
+
this.#logger.info(`send ${kind}:${conversation.id}`
|
|
329
|
+
+ ` | id: ${messageId}`
|
|
330
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`);
|
|
331
|
+
return messageId;
|
|
332
|
+
}
|
|
333
|
+
async recallMessage(messageId) {
|
|
334
|
+
const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
|
|
335
|
+
const bot = this.#requireBot();
|
|
336
|
+
switch (kind) {
|
|
337
|
+
case 'private':
|
|
338
|
+
await bot.recallPrivateMessage?.(channelId, qqMsgId);
|
|
339
|
+
break;
|
|
340
|
+
case 'group':
|
|
341
|
+
await bot.recallGroupMessage?.(channelId, qqMsgId);
|
|
342
|
+
break;
|
|
343
|
+
case 'channel':
|
|
344
|
+
await bot.recallGuildMessage?.(channelId, qqMsgId);
|
|
345
|
+
break;
|
|
346
|
+
case 'direct':
|
|
347
|
+
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
this.#logger.debug(formatCompact({
|
|
351
|
+
op: 'qq_recall',
|
|
352
|
+
endpoint: this.#options.config.id,
|
|
353
|
+
kind,
|
|
354
|
+
messageId,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
admit(msg) {
|
|
358
|
+
if (!this.#open)
|
|
359
|
+
return;
|
|
360
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
361
|
+
const content = formatInboundContent(msg);
|
|
362
|
+
this.#logger.info(`recv ${conversation.kind}:${conversation.id}`
|
|
363
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
364
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
365
|
+
+ ` | ${truncatePreview(content, 80)}`);
|
|
366
|
+
void this.emit('message.receive', {
|
|
367
|
+
conversation,
|
|
368
|
+
message: { conversation, id: msg.id },
|
|
369
|
+
content,
|
|
370
|
+
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
371
|
+
sender: {
|
|
372
|
+
id: msg.authorId,
|
|
373
|
+
name: senderDisplayName(msg) || undefined,
|
|
374
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
375
|
+
},
|
|
376
|
+
endpointId: this.#options.config.id,
|
|
377
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
378
|
+
metadata: Object.freeze({
|
|
379
|
+
channelKind: msg.channelKind,
|
|
380
|
+
userId: msg.authorId,
|
|
381
|
+
guildId: msg.guildId,
|
|
382
|
+
roles: msg.authorRoles,
|
|
383
|
+
}),
|
|
384
|
+
}).catch((err) => {
|
|
385
|
+
this.#logger.warn(formatCompact({
|
|
386
|
+
op: 'qq_gateway_receive_failed',
|
|
387
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
388
|
+
error: err instanceof Error ? err.message : String(err),
|
|
389
|
+
}));
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
#bindBot(bot) {
|
|
393
|
+
bindQqBotInboundEvents(bot, (raw) => {
|
|
394
|
+
if (this.#bot !== bot)
|
|
395
|
+
return;
|
|
396
|
+
this.#emitPlatformEvent('message', raw);
|
|
397
|
+
const msg = normalizeQqMessage(raw);
|
|
398
|
+
if (msg)
|
|
399
|
+
this.admit(msg);
|
|
400
|
+
});
|
|
401
|
+
bindQqBotSideEvents(bot, (eventName, raw) => {
|
|
402
|
+
if (this.#bot !== bot)
|
|
403
|
+
return;
|
|
404
|
+
this.#emitPlatformEvent(eventName, raw);
|
|
405
|
+
receiveQqSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, bot, eventName, raw, this.#logger);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
#emitPlatformEvent(name, event) {
|
|
409
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
410
|
+
this.#logger.warn(formatCompact({
|
|
411
|
+
op: 'qq_platform_event_failed',
|
|
412
|
+
event: name,
|
|
413
|
+
error: error instanceof Error ? error.message : String(error),
|
|
414
|
+
}));
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
#requireBot() {
|
|
418
|
+
if (!this.#bot)
|
|
419
|
+
throw new Error('QQ bot not connected');
|
|
420
|
+
return this.#bot;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function createQqEndpointManagement(requireClient) {
|
|
424
|
+
return Object.freeze({
|
|
425
|
+
async listChannels() {
|
|
426
|
+
const channels = [];
|
|
427
|
+
const client = requireClient();
|
|
428
|
+
const guilds = await client.getGuilds();
|
|
429
|
+
for (const guildValue of Array.isArray(guilds) ? guilds : []) {
|
|
430
|
+
const guild = asRecord(guildValue);
|
|
431
|
+
const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
|
|
432
|
+
if (!guildId)
|
|
433
|
+
continue;
|
|
434
|
+
const guildName = String(guild.name ?? guild.guild_name ?? guildId);
|
|
435
|
+
const rows = await client.getChannels(guildId);
|
|
436
|
+
for (const channelValue of Array.isArray(rows) ? rows : []) {
|
|
437
|
+
const channel = asRecord(channelValue);
|
|
438
|
+
const id = String(channel.id ?? channel.channel_id ?? channelValue ?? '');
|
|
439
|
+
if (!id)
|
|
440
|
+
continue;
|
|
441
|
+
channels.push({
|
|
442
|
+
id,
|
|
443
|
+
name: String(channel.name ?? channel.channel_name ?? id),
|
|
444
|
+
parent: { type: 'guild', id: guildId, name: guildName },
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return channels;
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function asRecord(value) {
|
|
453
|
+
return value !== null && typeof value === 'object'
|
|
454
|
+
? value
|
|
455
|
+
: {};
|
|
456
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
declare module "zhin.js" {
|
|
11
|
-
interface Adapters {
|
|
12
|
-
qq: QQAdapter;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export * from "./types.js";
|
|
16
|
-
export { QQBot } from "./bot.js";
|
|
17
|
-
export { QQAdapter } from "./adapter.js";
|
|
18
|
-
//# sourceMappingURL=index.d.ts.map
|
|
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
|
+
export { DEFAULT_QQ_BOT_KIND, QQ_INTENTS_BY_KIND, defaultQqEndpointIntentFields, parseQqBotKind, resolveQqIntents, type QqBotKind, type QqIntent, } from './qq-intents.js';
|
|
3
|
+
export { formatOutbound, resolveMediaFile, type QqOutboundElem, type QqOutboundMessage, } from './outbound.js';
|
|
4
|
+
export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqOfficialApi, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
|
|
5
|
+
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
|
|
6
|
+
export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
7
|
+
export { qqClient, type QqClientEventMap } from './client.js';
|
|
8
|
+
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, } from './platform-permit.js';
|