@zhin.js/adapter-qq 1.1.18 → 1.1.19
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 +39 -0
- package/README.md +21 -18
- package/adapters/qq/index.js +4 -38
- package/adapters/qq/index.ts +3 -46
- package/agents/qq/conversation.js +1 -1
- package/agents/qq/conversation.ts +1 -1
- package/agents/qq/skills/qq/tools/add_role/index.js +2 -1
- package/agents/qq/skills/qq/tools/add_role/index.ts +2 -1
- package/agents/qq/skills/qq/tools/channel_info/index.js +1 -1
- package/agents/qq/skills/qq/tools/channel_info/index.ts +1 -1
- package/agents/qq/skills/qq/tools/create_role/index.js +1 -1
- package/agents/qq/skills/qq/tools/create_role/index.ts +1 -1
- package/agents/qq/skills/qq/tools/guild_kick_member/index.js +1 -1
- package/agents/qq/skills/qq/tools/guild_kick_member/index.ts +1 -1
- package/agents/qq/skills/qq/tools/guild_mute_member/index.js +1 -1
- package/agents/qq/skills/qq/tools/guild_mute_member/index.ts +1 -1
- package/agents/qq/skills/qq/tools/list_channels/index.js +1 -1
- package/agents/qq/skills/qq/tools/list_channels/index.ts +1 -1
- package/agents/qq/skills/qq/tools/list_roles/index.js +1 -1
- package/agents/qq/skills/qq/tools/list_roles/index.ts +1 -1
- package/agents/qq/skills/qq/tools/member_detail/index.js +1 -1
- package/agents/qq/skills/qq/tools/member_detail/index.ts +1 -1
- package/agents/qq/skills/qq/tools/remove_role/index.js +2 -1
- package/agents/qq/skills/qq/tools/remove_role/index.ts +2 -1
- package/agents/qq/skills/qq-conversation/tools/conversation_info/index.js +1 -1
- package/agents/qq/skills/qq-conversation/tools/conversation_info/index.ts +1 -1
- package/agents/qq/skills/qq-directory/tools/list_guilds/index.js +1 -1
- package/agents/qq/skills/qq-directory/tools/list_guilds/index.ts +1 -1
- package/agents/qq/skills/qq-group-admin/tools/group_kick/index.js +2 -2
- package/agents/qq/skills/qq-group-admin/tools/group_kick/index.ts +2 -2
- package/agents/qq/skills/qq-group-admin/tools/handle_join_request/index.js +1 -1
- package/agents/qq/skills/qq-group-admin/tools/handle_join_request/index.ts +1 -1
- package/agents/qq/skills/qq-group-admin/tools/set_mute/index.js +2 -2
- package/agents/qq/skills/qq-group-admin/tools/set_mute/index.ts +2 -2
- package/agents/qq/skills/qq-group-directory/tools/group_info/index.js +1 -1
- package/agents/qq/skills/qq-group-directory/tools/group_info/index.ts +1 -1
- package/agents/qq/skills/qq-group-directory/tools/group_member_info/index.js +1 -1
- package/agents/qq/skills/qq-group-directory/tools/group_member_info/index.ts +1 -1
- package/agents/qq/skills/qq-group-directory/tools/group_members/index.js +1 -1
- package/agents/qq/skills/qq-group-directory/tools/group_members/index.ts +1 -1
- package/agents/qq/skills/qq-group-state/tools/group_blacklist/index.js +1 -1
- package/agents/qq/skills/qq-group-state/tools/group_blacklist/index.ts +1 -1
- package/agents/qq/skills/qq-group-state/tools/list_join_requests/index.js +1 -1
- package/agents/qq/skills/qq-group-state/tools/list_join_requests/index.ts +1 -1
- package/agents/qq/skills/qq-group-state/tools/list_muted/index.js +1 -1
- package/agents/qq/skills/qq-group-state/tools/list_muted/index.ts +1 -1
- package/commands/qq/endpoint/definition.js +41 -36
- package/commands/qq/endpoint/definition.ts +41 -67
- package/lib/adapter.d.ts +13 -0
- package/lib/adapter.js +27 -0
- package/lib/client.d.ts +7 -9
- package/lib/client.js +1 -2
- package/lib/console.d.ts +1 -6
- package/lib/console.js +29 -20
- package/lib/endpoint.d.ts +8 -27
- package/lib/endpoint.js +218 -165
- package/lib/group-admin.d.ts +14 -9
- package/lib/group-admin.js +21 -20
- package/lib/i18n.js +4 -4
- package/lib/image-size.d.ts +6 -1
- package/lib/image-size.js +2 -3
- package/lib/index.d.ts +5 -4
- package/lib/index.js +3 -3
- package/lib/interaction.js +1 -1
- package/lib/outbound.d.ts +4 -1
- package/lib/outbound.js +3 -2
- package/lib/protocol.d.ts +2 -0
- package/lib/qq-console-spec.js +12 -1
- package/lib/qq-console.d.ts +3 -4
- package/lib/qq-console.js +16 -6
- package/lib/qq-endpoint-write.d.ts +6 -10
- package/lib/qq-endpoint-write.js +5 -32
- package/lib/qq-runtime-state.d.ts +11 -10
- package/lib/qq-runtime-state.js +4 -5
- package/lib/qq-send.d.ts +7 -14
- package/lib/qq-send.js +20 -6
- package/lib/webhook.d.ts +3 -6
- package/lib/webhook.js +6 -49
- package/lib/ws.d.ts +10 -33
- package/lib/ws.js +21 -70
- package/middlewares/bot-kind-reply/index.js +1 -2
- package/middlewares/bot-kind-reply/index.ts +0 -2
- package/package.json +19 -19
- package/plugin.js +5 -5
- package/src/adapter.ts +23 -0
- package/src/client.ts +4 -12
- package/src/console.ts +27 -20
- package/src/endpoint.ts +218 -160
- package/src/group-admin.ts +28 -26
- package/src/i18n.ts +4 -4
- package/src/image-size.ts +7 -3
- package/src/index.ts +5 -5
- package/src/interaction.ts +1 -1
- package/src/outbound.ts +3 -2
- package/src/protocol.ts +2 -0
- package/src/qq-console-spec.ts +12 -1
- package/src/qq-console.ts +14 -8
- package/src/qq-endpoint-write.ts +10 -38
- package/src/qq-runtime-state.ts +11 -16
- package/src/qq-send.ts +28 -12
- package/src/webhook.ts +7 -57
- package/src/ws.ts +27 -105
package/lib/webhook.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* QQ HTTP webhook / middleware inbound via httpHostToken POST.
|
|
3
3
|
*/
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { ReceiverMode } from 'qq-official-bot';
|
|
6
6
|
import { getLogger } from '@zhin.js/logger';
|
|
7
7
|
const logger = getLogger('qq');
|
|
8
8
|
export function registerQqWebhookRoutes(http, handler) {
|
|
@@ -28,6 +28,7 @@ export async function handleQqWebhookRequest(request, response, handler) {
|
|
|
28
28
|
req: request,
|
|
29
29
|
res: response,
|
|
30
30
|
request: { body: undefined },
|
|
31
|
+
response: {},
|
|
31
32
|
}, async () => undefined);
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
@@ -52,52 +53,8 @@ export async function readRequestBodyText(request) {
|
|
|
52
53
|
}
|
|
53
54
|
return Buffer.concat(chunks).toString('utf8');
|
|
54
55
|
}
|
|
55
|
-
export function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
mode: ReceiverMode.MIDDLEWARE,
|
|
60
|
-
platform: 'koa',
|
|
61
|
-
sandbox: config.sandbox,
|
|
62
|
-
dataDir: path.join(process.cwd(), 'data', 'qq'),
|
|
63
|
-
// 关闭内置 log4js,与 WebSocket 模式一致走 zhin adapter logger
|
|
64
|
-
logLevel: 'off',
|
|
65
|
-
});
|
|
66
|
-
return {
|
|
67
|
-
api: bot,
|
|
68
|
-
start: () => bot.start().then(() => undefined),
|
|
69
|
-
stop: () => bot.stop().then(() => undefined),
|
|
70
|
-
on: (event, listener) => {
|
|
71
|
-
bot.on(event, listener);
|
|
72
|
-
},
|
|
73
|
-
removeAllListeners: () => {
|
|
74
|
-
bot.removeAllListeners(undefined);
|
|
75
|
-
},
|
|
76
|
-
sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
|
|
77
|
-
sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
|
|
78
|
-
sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
|
|
79
|
-
sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
|
|
80
|
-
recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
|
|
81
|
-
recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
|
|
82
|
-
recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
|
|
83
|
-
recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
|
|
84
|
-
getGuilds: () => bot.guildService.getList(),
|
|
85
|
-
getChannels: (guildId) => bot.channelService.getList(guildId),
|
|
86
|
-
getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
|
|
87
|
-
getGuildMember: (guildId, userId) => bot.memberService.getGuildMemberInfo(guildId, userId),
|
|
88
|
-
getGuildRoles: (guildId) => bot.guildService.getRoles(guildId),
|
|
89
|
-
createGuildRole: (guildId, name, color) => bot.guildService.createRole(guildId, { name, color: color || 0, hoist: 0 }),
|
|
90
|
-
addMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
91
|
-
await bot.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
92
|
-
return true;
|
|
93
|
-
},
|
|
94
|
-
removeMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
95
|
-
await bot.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
96
|
-
return true;
|
|
97
|
-
},
|
|
98
|
-
approveGroupJoinRequest: (groupId, userId, options) => bot
|
|
99
|
-
.approveGroupJoinRequest?.(groupId, userId, options)
|
|
100
|
-
?? Promise.reject(new Error('approveGroupJoinRequest is not available')),
|
|
101
|
-
middleware: (ctx, next) => bot.middleware(ctx, next),
|
|
102
|
-
};
|
|
56
|
+
export function qqHttpBotOptions(config) {
|
|
57
|
+
return { handleProcessErrors: false, appid: config.appid, secret: config.secret,
|
|
58
|
+
mode: ReceiverMode.MIDDLEWARE, platform: 'koa', sandbox: config.sandbox,
|
|
59
|
+
dataDir: path.join(process.cwd(), 'data', 'qq'), logLevel: 'off' };
|
|
103
60
|
}
|
package/lib/ws.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { Bot,
|
|
1
|
+
import { Bot, ReceiverMode } from 'qq-official-bot';
|
|
2
2
|
import type { EndpointTransportState } from 'zhin.js/adapter';
|
|
3
|
-
import type { QqOutboundMessage } from './outbound.js';
|
|
4
|
-
import type { QqReplySource } from './passive-reply.js';
|
|
5
3
|
/** Per-send options. `msgSeq` numbers replies to one passive source (qq-official-bot ≥ 1.3.3). */
|
|
6
4
|
export interface QqSendOptions {
|
|
7
5
|
readonly msgSeq?: number;
|
|
@@ -9,35 +7,7 @@ export interface QqSendOptions {
|
|
|
9
7
|
import type { QqInboundMessage, ResolvedQqWebsocketConfig } from './protocol.js';
|
|
10
8
|
/** SDK business surface exposed to plugins without bypassing Endpoint lifecycle ownership. */
|
|
11
9
|
export type QqOfficialApi = Pick<Bot, 'messageService' | 'fileProcessor' | 'guildService' | 'channelService' | 'memberService' | 'permissionService' | 'reactionService' | 'scheduleService' | 'threadService' | 'audioService' | 'botService' | 'groupService' | 'menuPanelService' | 'group' | 'user' | 'channel' | 'direct' | 'guild'>;
|
|
12
|
-
|
|
13
|
-
export interface QqBotTransport {
|
|
14
|
-
readonly transportState?: EndpointTransportState;
|
|
15
|
-
/** Full qq-official-bot business API; lifecycle remains owned by the Endpoint. */
|
|
16
|
-
readonly api: QqOfficialApi;
|
|
17
|
-
start(): Promise<void>;
|
|
18
|
-
stop(): Promise<void>;
|
|
19
|
-
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
20
|
-
removeAllListeners(): void;
|
|
21
|
-
/** `source` makes it a passive reply; without it the platform treats the message as active. */
|
|
22
|
-
sendPrivateMessage(userId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
|
|
23
|
-
sendGroupMessage(groupId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
|
|
24
|
-
sendGuildMessage(channelId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
|
|
25
|
-
sendDirectMessage?(guildId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
|
|
26
|
-
recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
|
|
27
|
-
recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
|
|
28
|
-
recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
|
|
29
|
-
recallDirectMessage?(guildId: string, messageId: string): Promise<unknown>;
|
|
30
|
-
getGuilds(): Promise<unknown[]>;
|
|
31
|
-
getChannels(guildId: string): Promise<unknown[]>;
|
|
32
|
-
getChannelInfo(channelId: string): Promise<unknown>;
|
|
33
|
-
getGuildMember(guildId: string, userId: string): Promise<unknown>;
|
|
34
|
-
getGuildRoles(guildId: string): Promise<unknown[]>;
|
|
35
|
-
createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
|
|
36
|
-
addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
|
|
37
|
-
removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
|
|
38
|
-
approveGroupJoinRequest?(groupId: string, userId: string, options: ApproveJoinRequestOptions): Promise<unknown>;
|
|
39
|
-
}
|
|
40
|
-
export type CreateQqBot = (config: ResolvedQqWebsocketConfig) => QqBotTransport;
|
|
10
|
+
export type QqBotTransport = Bot;
|
|
41
11
|
interface QqOfficialBotLifecycle {
|
|
42
12
|
stop(): Promise<unknown>;
|
|
43
13
|
readonly sessionManager: {
|
|
@@ -59,5 +29,12 @@ interface QqOfficialBotLifecycle {
|
|
|
59
29
|
export declare function stopQqOfficialBot(bot: QqOfficialBotLifecycle): Promise<void>;
|
|
60
30
|
export declare function bindQqBotInboundEvents(bot: QqBotTransport, admitRaw: (raw: unknown) => void): void;
|
|
61
31
|
export declare function normalizeQqMessage(raw: unknown): QqInboundMessage | null;
|
|
62
|
-
export declare function
|
|
32
|
+
export declare function qqWebsocketBotOptions(config: ResolvedQqWebsocketConfig): ConstructorParameters<typeof Bot<ReceiverMode.WEBSOCKET>>[0];
|
|
33
|
+
/** SDK 1.3.3 has no public receiver configuration/state accessor. This seam only reads its verified native fields. */
|
|
34
|
+
export declare function configureQqWebsocketReceiver(bot: Bot<ReceiverMode.WEBSOCKET>, config: ResolvedQqWebsocketConfig): {
|
|
35
|
+
state(): EndpointTransportState;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
};
|
|
38
|
+
/** Session.stop leaves Auth refresh alive for reconnect; actual instance retirement destroys it. */
|
|
39
|
+
export declare function disposeQqAuthorization(bot: Bot): void;
|
|
63
40
|
export {};
|
package/lib/ws.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createQqStreamAgent } from './stream-proxy.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ReceiverMode, } from 'qq-official-bot';
|
|
7
7
|
/**
|
|
8
8
|
* qq-official-bot 1.2 used a separate connection manager; 1.3 moved WebSocket
|
|
9
9
|
* ownership into the receiver, but Session.stop() still leaves the auth refresh
|
|
@@ -72,6 +72,8 @@ export function normalizeQqMessage(raw) {
|
|
|
72
72
|
&& msg.mentions.some((m) => m?.is_you === true || (m?.bot === true && channelKind !== 'group'));
|
|
73
73
|
return {
|
|
74
74
|
id: String(msg.message_id),
|
|
75
|
+
event: raw,
|
|
76
|
+
...(typeof msg.timestamp === 'number' && Number.isFinite(msg.timestamp) ? { platformSentAt: msg.timestamp * 1_000 } : {}),
|
|
75
77
|
content,
|
|
76
78
|
channelKind,
|
|
77
79
|
channelId,
|
|
@@ -183,87 +185,36 @@ function extractTextContent(message) {
|
|
|
183
185
|
})
|
|
184
186
|
.join('');
|
|
185
187
|
}
|
|
186
|
-
export function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
secret: config.secret,
|
|
191
|
-
mode: ReceiverMode.WEBSOCKET,
|
|
192
|
-
sandbox: config.sandbox,
|
|
193
|
-
dataDir: path.join(process.cwd(), 'data', 'qq'),
|
|
194
|
-
// 关闭 qq-official-bot 内置 log4js,入/出站由 getAdapterLogger('qq', …) 统一格式
|
|
195
|
-
logLevel: 'off',
|
|
188
|
+
export function qqWebsocketBotOptions(config) {
|
|
189
|
+
return { handleProcessErrors: false, appid: config.appid, secret: config.secret,
|
|
190
|
+
mode: ReceiverMode.WEBSOCKET, sandbox: config.sandbox,
|
|
191
|
+
dataDir: path.join(process.cwd(), 'data', 'qq'), logLevel: 'off',
|
|
196
192
|
...(config.intents ? { intents: config.intents } : {}),
|
|
197
193
|
...(config.accessTokenUrl ? { accessTokenUrl: config.accessTokenUrl } : {}),
|
|
198
|
-
...(config.gatewayUrl ? { gatewayUrl: config.gatewayUrl } : {}),
|
|
199
|
-
|
|
194
|
+
...(config.gatewayUrl ? { gatewayUrl: config.gatewayUrl } : {}), };
|
|
195
|
+
}
|
|
196
|
+
/** SDK 1.3.3 has no public receiver configuration/state accessor. This seam only reads its verified native fields. */
|
|
197
|
+
export function configureQqWebsocketReceiver(bot, config) {
|
|
200
198
|
const receiver = bot.receiver;
|
|
201
199
|
receiver.config.autoReconnect = false;
|
|
202
200
|
const agent = config.streamProxy ? createQqStreamAgent(config.streamProxy) : undefined;
|
|
203
201
|
receiver.config.agent = agent;
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
const receiverError = () => { };
|
|
207
|
-
receiver.on('error', receiverError);
|
|
202
|
+
const observeError = () => undefined;
|
|
203
|
+
bot.receiver.on('error', observeError);
|
|
208
204
|
return {
|
|
209
|
-
|
|
210
|
-
const receiver = bot.receiver;
|
|
205
|
+
state() {
|
|
211
206
|
const state = receiver.handler?.ws?.readyState;
|
|
212
207
|
if (state === 1 && receiver.isReady && receiver.isClosed === false)
|
|
213
208
|
return 'open';
|
|
214
209
|
if (state === 0)
|
|
215
210
|
return 'connecting';
|
|
216
|
-
|
|
217
|
-
return 'reconnecting';
|
|
218
|
-
return 'closed';
|
|
219
|
-
},
|
|
220
|
-
api: bot,
|
|
221
|
-
start: () => bot.start().then(() => undefined),
|
|
222
|
-
stop: async () => {
|
|
223
|
-
try {
|
|
224
|
-
await stopQqOfficialBot(bot);
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
agent?.destroy();
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
on: (event, listener) => {
|
|
231
|
-
if (event === 'transport.close') {
|
|
232
|
-
receiver.on('close', listener);
|
|
233
|
-
receiverListeners.push({ event: 'close', listener });
|
|
234
|
-
}
|
|
235
|
-
else
|
|
236
|
-
bot.on(event, listener);
|
|
211
|
+
return receiver.isStarted ? 'reconnecting' : 'closed';
|
|
237
212
|
},
|
|
238
|
-
|
|
239
|
-
for (const entry of receiverListeners.splice(0))
|
|
240
|
-
receiver.removeListener(entry.event, entry.listener);
|
|
241
|
-
bot.removeAllListeners(undefined);
|
|
242
|
-
},
|
|
243
|
-
sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
|
|
244
|
-
sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
|
|
245
|
-
sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
|
|
246
|
-
sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
|
|
247
|
-
recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
|
|
248
|
-
recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
|
|
249
|
-
recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
|
|
250
|
-
recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
|
|
251
|
-
getGuilds: () => bot.guildService.getList(),
|
|
252
|
-
getChannels: (guildId) => bot.channelService.getList(guildId),
|
|
253
|
-
getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
|
|
254
|
-
getGuildMember: (guildId, userId) => bot.memberService.getGuildMemberInfo(guildId, userId),
|
|
255
|
-
getGuildRoles: (guildId) => bot.guildService.getRoles(guildId),
|
|
256
|
-
createGuildRole: (guildId, name, color) => bot.guildService.createRole(guildId, { name, color: color || 0, hoist: 0 }),
|
|
257
|
-
addMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
258
|
-
await bot.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
259
|
-
return true;
|
|
260
|
-
},
|
|
261
|
-
removeMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
262
|
-
await bot.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
263
|
-
return true;
|
|
264
|
-
},
|
|
265
|
-
approveGroupJoinRequest: (groupId, userId, options) => bot
|
|
266
|
-
.approveGroupJoinRequest?.(groupId, userId, options)
|
|
267
|
-
?? Promise.reject(new Error('approveGroupJoinRequest is not available')),
|
|
213
|
+
dispose() { bot.receiver.off('error', observeError); agent?.destroy(); },
|
|
268
214
|
};
|
|
269
215
|
}
|
|
216
|
+
/** Session.stop leaves Auth refresh alive for reconnect; actual instance retirement destroys it. */
|
|
217
|
+
export function disposeQqAuthorization(bot) {
|
|
218
|
+
const session = bot.sessionManager;
|
|
219
|
+
session.authManager.destroy();
|
|
220
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { messages as t } from "../../lib/i18n.js";
|
|
3
3
|
import { defineMiddleware } from 'zhin.js/middleware';
|
|
4
|
-
import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
|
|
5
4
|
import { completeQqPendingBotKind, } from '../../commands/qq/endpoint/definition.js';
|
|
6
5
|
import { parseQqBotKindAnswer, qqCommandSessionKey, qqBotKindPrompt, } from "../../lib/qq-bot-kind-prompt.js";
|
|
7
6
|
import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
|
|
@@ -37,7 +36,7 @@ export default defineMiddleware({
|
|
|
37
36
|
return;
|
|
38
37
|
}
|
|
39
38
|
try {
|
|
40
|
-
const text = await completeQqPendingBotKind(pending, botKind
|
|
39
|
+
const text = await completeQqPendingBotKind(pending, botKind);
|
|
41
40
|
state.pendingBotKind = null;
|
|
42
41
|
await context.input.$reply?.(text);
|
|
43
42
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { messages as t } from '../../src/i18n.js';
|
|
2
2
|
import { defineMiddleware } from 'zhin.js/middleware';
|
|
3
3
|
import type { CommandMessage } from 'zhin.js/command';
|
|
4
|
-
import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
|
|
5
4
|
import {
|
|
6
5
|
completeQqPendingBotKind,
|
|
7
6
|
} from '../../commands/qq/endpoint/definition.js';
|
|
@@ -53,7 +52,6 @@ export default defineMiddleware<CommandMessage>({
|
|
|
53
52
|
const text = await completeQqPendingBotKind(
|
|
54
53
|
pending,
|
|
55
54
|
botKind,
|
|
56
|
-
context.use(endpointConfigurationStoreToken),
|
|
57
55
|
);
|
|
58
56
|
state.pendingBotKind = null;
|
|
59
57
|
await context.input.$reply?.(text);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-qq",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
4
4
|
"description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,27 +34,27 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"qq-official-bot": "^1.3.3",
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/agent-feature": "1.1.
|
|
39
|
-
"@zhin.js/client": "1.1.
|
|
37
|
+
"@zhin.js/adapter": "1.1.26",
|
|
38
|
+
"@zhin.js/agent-feature": "1.1.11",
|
|
39
|
+
"@zhin.js/client": "1.1.22",
|
|
40
40
|
"@zhin.js/console-contract": "1.1.3",
|
|
41
|
-
"@zhin.js/core": "1.1.
|
|
42
|
-
"@zhin.js/feature-kit": "1.1.
|
|
43
|
-
"@zhin.js/host-http": "1.1.
|
|
41
|
+
"@zhin.js/core": "1.1.54",
|
|
42
|
+
"@zhin.js/feature-kit": "1.1.10",
|
|
43
|
+
"@zhin.js/host-http": "1.1.12",
|
|
44
44
|
"@zhin.js/im-contract": "1.1.9",
|
|
45
45
|
"@zhin.js/logger": "1.1.2",
|
|
46
|
-
"@zhin.js/page": "1.1.
|
|
47
|
-
"@zhin.js/permission": "1.1.
|
|
48
|
-
"@zhin.js/skill": "1.1.
|
|
49
|
-
"@zhin.js/tool": "1.1.
|
|
46
|
+
"@zhin.js/page": "1.1.11",
|
|
47
|
+
"@zhin.js/permission": "1.1.10",
|
|
48
|
+
"@zhin.js/skill": "1.1.11",
|
|
49
|
+
"@zhin.js/tool": "1.1.13"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"zod": "^4.0.0",
|
|
53
|
-
"@zhin.js/adapter": "^1.1.
|
|
54
|
-
"@zhin.js/command": "^1.1.
|
|
55
|
-
"@zhin.js/core": "^1.1.
|
|
56
|
-
"@zhin.js/middleware": "^1.1.
|
|
57
|
-
"zhin.js": "^1.1.
|
|
53
|
+
"@zhin.js/adapter": "^1.1.26",
|
|
54
|
+
"@zhin.js/command": "^1.1.12",
|
|
55
|
+
"@zhin.js/core": "^1.1.54",
|
|
56
|
+
"@zhin.js/middleware": "^1.1.11",
|
|
57
|
+
"zhin.js": "^1.1.19"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"@zhin.js/command": {
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"typescript": "^6.0.3",
|
|
76
76
|
"vitest": "^4.1.10",
|
|
77
77
|
"zod": "^4.4.3",
|
|
78
|
-
"@zhin.js/host-http": "1.1.
|
|
79
|
-
"zhin.js": "1.1.
|
|
78
|
+
"@zhin.js/host-http": "1.1.12",
|
|
79
|
+
"zhin.js": "1.1.19"
|
|
80
80
|
},
|
|
81
81
|
"files": [
|
|
82
82
|
"adapters",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"features": [
|
|
110
110
|
{
|
|
111
111
|
"package": "@zhin.js/adapter",
|
|
112
|
-
"api": "^
|
|
112
|
+
"api": "^2.0.0"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"package": "@zhin.js/command",
|
package/plugin.js
CHANGED
|
@@ -4,6 +4,7 @@ import { permissionHostToken } from '@zhin.js/permission';
|
|
|
4
4
|
import { checkQqPlatformPermit } from "./lib/platform-permit.js";
|
|
5
5
|
import { createQqRuntimeState, qqRuntimeStateToken } from "./lib/qq-runtime-state.js";
|
|
6
6
|
import { createQqConsoleRpc } from "./lib/console.js";
|
|
7
|
+
import { cancelQqConsoleBind } from "./lib/qq-console.js";
|
|
7
8
|
export default definePlugin({
|
|
8
9
|
name: 'qq',
|
|
9
10
|
metadata: {
|
|
@@ -12,10 +13,9 @@ export default definePlugin({
|
|
|
12
13
|
setup(context) {
|
|
13
14
|
const state = createQqRuntimeState();
|
|
14
15
|
context.resources.provide(qqRuntimeStateToken, state);
|
|
15
|
-
context.resources.provide(pluginConsoleRpcToken, createQqConsoleRpc(state,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
context.resources.provide(pluginConsoleRpcToken, createQqConsoleRpc(state, () => context.config.get()));
|
|
17
|
+
const releasePermission = context.resources.has(permissionHostToken)
|
|
18
|
+
? context.resources.use(permissionHostToken).registerPlatform('qq', checkQqPlatformPermit) : undefined;
|
|
19
|
+
return () => { cancelQqConsoleBind(state); releasePermission?.(); };
|
|
20
20
|
},
|
|
21
21
|
});
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Adapter, createAdapterOutageNotifier, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
|
|
2
|
+
import { outboundHostToken } from 'zhin.js';
|
|
3
|
+
import { httpHostToken } from '@zhin.js/host-http';
|
|
4
|
+
import { QqHttpEndpoint, QqWebsocketEndpoint } from './endpoint.js';
|
|
5
|
+
import { resolveQqConfig, type QqEndpointConfig } from './protocol.js';
|
|
6
|
+
import type { QqClientEventMap } from './client.js';
|
|
7
|
+
|
|
8
|
+
export class QqAdapter extends Adapter<QqHttpEndpoint | QqWebsocketEndpoint, QqEndpointConfig, QqClientEventMap> {
|
|
9
|
+
constructor(private readonly context: AdapterContext<QqEndpointConfig>) { super(); }
|
|
10
|
+
override get configurationIdentityFields(): readonly string[] { return ['id', 'appid']; }
|
|
11
|
+
override get configurationIdentityProbeFields(): readonly string[] { return ['accessTokenUrl', 'gatewayUrl']; }
|
|
12
|
+
override get configurationUpdateFields(): readonly string[] { return ['secret', 'mode', 'sandbox', 'intents', 'botKind',
|
|
13
|
+
'accessTokenUrl', 'gatewayUrl', 'streamProxy', 'streamProxy.port', 'streamProxy.serverName', 'webhookPath']; }
|
|
14
|
+
protected override validateConfiguration(input: QqEndpointConfig): void { resolveQqConfig(input); }
|
|
15
|
+
protected createEndpoint(input: QqEndpointConfig, execution: EndpointCreationContext): QqHttpEndpoint | QqWebsocketEndpoint {
|
|
16
|
+
const config = resolveQqConfig(input);
|
|
17
|
+
const outbound = this.context.has?.(outboundHostToken) ? this.context.use(outboundHostToken) : undefined;
|
|
18
|
+
const notifier = createAdapterOutageNotifier(outbound, config.notify);
|
|
19
|
+
return config.mode === 'websocket'
|
|
20
|
+
? new QqWebsocketEndpoint({ id: execution.canonicalId, config, notifier })
|
|
21
|
+
: new QqHttpEndpoint({ id: execution.canonicalId, config, notifier, http: this.context.use(httpHostToken) });
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import type { QqBotTransport } from './ws.js';
|
|
1
|
+
import type { EventMap } from 'qq-official-bot';
|
|
2
|
+
import type { QqAdapter } from './adapter.js';
|
|
4
3
|
|
|
5
|
-
export type QqClientEventMap =
|
|
4
|
+
export type QqClientEventMap = { readonly [K in keyof EventMap]: EventMap[K] extends (event: infer E, ...args: never[]) => unknown ? E : never };
|
|
6
5
|
|
|
7
6
|
declare module '@zhin.js/feature-kit' {
|
|
8
|
-
interface
|
|
9
|
-
readonly qq: {
|
|
10
|
-
readonly client: QqBotTransport;
|
|
11
|
-
readonly events: QqClientEventMap;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
7
|
+
interface AdapterRegistry { readonly qq: QqAdapter; }
|
|
14
8
|
}
|
|
15
|
-
|
|
16
|
-
export const qqClient = defineEndpointClient<QqBotTransport, QqClientEventMap>('qq');
|
package/src/console.ts
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* (以及公域 / 私域选择、取消)搬到 Console 页面。
|
|
4
4
|
*/
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
prepareConsoleAdapterMutation,
|
|
7
|
+
ownedConsoleAdapter,
|
|
7
8
|
createAdapterConsoleRpc,
|
|
8
|
-
type AdapterConsoleResources,
|
|
9
9
|
type AdapterConsoleSpec,
|
|
10
|
-
type EndpointConfigurationStore,
|
|
11
10
|
} from 'zhin.js/adapter';
|
|
12
11
|
import { PluginConsoleRpcError, type PluginConsoleRpc } from 'zhin.js';
|
|
13
12
|
import { qqConsoleSpec } from './qq-console-spec.js';
|
|
@@ -20,16 +19,10 @@ import type { QqRuntimeState } from './qq-runtime-state.js';
|
|
|
20
19
|
|
|
21
20
|
export function createQqConsoleRpc(
|
|
22
21
|
state: QqRuntimeState,
|
|
23
|
-
resources: AdapterConsoleResources,
|
|
24
22
|
getConfig: () => unknown,
|
|
25
23
|
): PluginConsoleRpc {
|
|
26
|
-
const store: EndpointConfigurationStore | undefined = resources.has(endpointConfigurationStoreToken)
|
|
27
|
-
? resources.use(endpointConfigurationStoreToken)
|
|
28
|
-
: undefined;
|
|
29
|
-
|
|
30
24
|
const spec: AdapterConsoleSpec = {
|
|
31
25
|
...qqConsoleSpec,
|
|
32
|
-
running: () => state.endpoints.values(),
|
|
33
26
|
getConfig,
|
|
34
27
|
extraStatus: () => {
|
|
35
28
|
const bind = state.consoleBind;
|
|
@@ -45,7 +38,6 @@ export function createQqConsoleRpc(
|
|
|
45
38
|
};
|
|
46
39
|
},
|
|
47
40
|
availableActions: () => {
|
|
48
|
-
if (!store) return [];
|
|
49
41
|
if (state.consoleBind?.pending) return ['qq-bind-kind', 'qq-bind-cancel'];
|
|
50
42
|
if (state.bindFlow || state.consoleBind?.state === 'waiting') return ['qq-bind-cancel'];
|
|
51
43
|
return ['qq-bind-start'];
|
|
@@ -54,15 +46,23 @@ export function createQqConsoleRpc(
|
|
|
54
46
|
'qq-bind-start': {
|
|
55
47
|
label: '扫码绑定新机器人',
|
|
56
48
|
labelI18n: { en: 'Bind a bot with QR' },
|
|
57
|
-
description: '用手机 QQ
|
|
58
|
-
descriptionI18n: { en: 'Scan with mobile QQ, choose public/private
|
|
59
|
-
handler: async () => {
|
|
60
|
-
if (!
|
|
61
|
-
const
|
|
49
|
+
description: '用手机 QQ 扫码,选择公域 / 私域后保存到账号库;保存后需手动启动。',
|
|
50
|
+
descriptionI18n: { en: 'Scan with mobile QQ, choose public/private and save the account; start it explicitly afterwards.' },
|
|
51
|
+
handler: async (input, call) => {
|
|
52
|
+
if (!call.runtime) throw new PluginConsoleRpcError('unsupported', '账号运行目录不可用');
|
|
53
|
+
const adapter = ownedConsoleAdapter(spec, call);
|
|
54
|
+
if (!adapter.canManage) throw new PluginConsoleRpcError('unsupported', '此环境未提供持久化账号管理');
|
|
55
|
+
if (input.expectedGeneration !== call.runtime.generation || input.expectedRevision !== adapter.revision) {
|
|
56
|
+
throw new PluginConsoleRpcError('conflict', '账号清单已变化,请刷新核实');
|
|
57
|
+
}
|
|
58
|
+
const starting = startQqConsoleBind(state);
|
|
59
|
+
state.consoleAdmission = { adapterId: adapter.descriptor.id, generation: call.runtime.generation, revision: adapter.revision };
|
|
60
|
+
const { url } = await starting;
|
|
62
61
|
return { message: '请用手机 QQ 扫码;扫码成功后选择公域或私域', url };
|
|
63
62
|
},
|
|
64
63
|
},
|
|
65
64
|
'qq-bind-kind': {
|
|
65
|
+
accountMutation: true,
|
|
66
66
|
label: '选择公域 / 私域',
|
|
67
67
|
labelI18n: { en: 'Choose public/private' },
|
|
68
68
|
description: '公域订阅 PUBLIC_GUILD_MESSAGES;私域订阅 GUILD_MESSAGES。',
|
|
@@ -71,11 +71,18 @@ export function createQqConsoleRpc(
|
|
|
71
71
|
{ value: 'public', label: '公域', labelI18n: { en: 'Public' } },
|
|
72
72
|
{ value: 'private', label: '私域', labelI18n: { en: 'Private' } },
|
|
73
73
|
],
|
|
74
|
-
handler: async (input) => {
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
handler: async (input, call) => {
|
|
75
|
+
const captured = state.consoleAdmission;
|
|
76
|
+
if (!captured || captured.generation !== input.expectedGeneration || captured.revision !== input.expectedRevision) {
|
|
77
|
+
throw new PluginConsoleRpcError('conflict', '扫码期间账号清单已变化,请刷新核实');
|
|
78
|
+
}
|
|
79
|
+
const admission = prepareConsoleAdapterMutation(spec, call, input);
|
|
80
|
+
if (admission.adapter.descriptor.id !== captured.adapterId) throw new PluginConsoleRpcError('conflict', '扫码所属 Adapter 已变化');
|
|
81
|
+
const result = await completeQqConsoleBind(state, config => admission.adapter.add(admission.revision,
|
|
82
|
+
{ endpointId: config.id, config: { ...config } }, admission.options), input.value);
|
|
77
83
|
return {
|
|
78
|
-
message:
|
|
84
|
+
message: `已保存账号「${result.endpointId}」(${result.botKind}),尚未启动`,
|
|
85
|
+
saved: true, restartRequired: false, execution: { ...result.mutation.execution },
|
|
79
86
|
details: { endpoint: result.endpointId, botKind: result.botKind, intents: result.intents.join(', ') },
|
|
80
87
|
};
|
|
81
88
|
},
|
|
@@ -92,5 +99,5 @@ export function createQqConsoleRpc(
|
|
|
92
99
|
},
|
|
93
100
|
},
|
|
94
101
|
};
|
|
95
|
-
return createAdapterConsoleRpc(spec
|
|
102
|
+
return createAdapterConsoleRpc(spec);
|
|
96
103
|
}
|