@zhin.js/adapter-icqq 9.0.0 → 9.0.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 +35 -0
- package/README.md +82 -3
- package/adapters/icqq.js +1 -3
- package/adapters/icqq.ts +2 -8
- package/agent/prompt-sections/platform.ts +17 -0
- package/lib/client.d.ts +19 -0
- package/lib/client.js +3 -0
- package/lib/endpoint.d.ts +9 -6
- package/lib/endpoint.js +232 -83
- package/lib/icqq-endpoint-commands.d.ts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.js +1 -1
- package/package.json +21 -14
- package/plugin.js +1 -24
- package/src/client.ts +29 -0
- package/src/endpoint.ts +236 -85
- package/src/index.ts +4 -6
- package/src/protocol.ts +1 -1
- package/tools/announce.js +3 -6
- package/tools/announce.ts +4 -7
- package/tools/essence.js +5 -7
- package/tools/essence.ts +6 -8
- package/tools/friend_list.js +6 -9
- package/tools/friend_list.ts +7 -10
- package/tools/group_files.js +3 -6
- package/tools/group_files.ts +4 -7
- package/tools/group_invite.js +6 -8
- package/tools/group_invite.ts +7 -9
- package/tools/group_kick.js +6 -8
- package/tools/group_kick.ts +7 -9
- package/tools/group_list.js +7 -10
- package/tools/group_list.ts +8 -11
- package/tools/group_member_info.js +3 -6
- package/tools/group_member_info.ts +3 -7
- package/tools/group_sign.js +5 -7
- package/tools/group_sign.ts +6 -8
- package/tools/list_muted.js +3 -6
- package/tools/list_muted.ts +4 -7
- package/tools/poke.js +3 -6
- package/tools/poke.ts +4 -7
- package/tools/send_user_like.js +3 -6
- package/tools/send_user_like.ts +4 -7
- package/tools/set_anonymous.js +5 -7
- package/tools/set_anonymous.ts +6 -8
- package/tools/set_mute.js +6 -8
- package/tools/set_mute.ts +7 -9
- package/tools/set_title.js +6 -8
- package/tools/set_title.ts +7 -9
- package/tools/user_info.js +3 -6
- package/tools/user_info.ts +4 -7
- package/lib/agent-prompt.d.ts +0 -2
- package/lib/agent-prompt.js +0 -97
- package/lib/icqq-agent-deps.d.ts +0 -13
- package/lib/icqq-agent-deps.js +0 -27
- package/src/agent-prompt.ts +0 -110
- package/src/icqq-agent-deps.ts +0 -43
package/lib/endpoint.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Client, parseGroupMessageId, } from '@icqqjs/icqq';
|
|
2
2
|
import { inspect } from 'node:util';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
3
5
|
import { receiveOneBotLikeSideEvent, SystemEvent, toCanonicalSegments } from '@zhin.js/core';
|
|
4
6
|
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
5
|
-
import { registerIcqqAgentEndpoint } from './icqq-agent-deps.js';
|
|
6
7
|
import { runIcqqLoginAssistStep } from './icqq-login-assist.js';
|
|
7
8
|
import { InboundMessageDeduper, findIcqqNestedMessageSource, isIcqqBotMentioned, isIcqqMessagePostType, normalizeIcqqInboundMessage, messageLookupFromIcqqSource, resolveIcqqQuoteIdFromEvent, resolveIcqqInboundMedia, shouldSkipSelfInboundMessage, } from './icqq-inbound.js';
|
|
8
9
|
import { isIcqqNoticePayload, isIcqqRequestPayload, } from './icqq-inbox.js';
|
|
@@ -28,7 +29,16 @@ const BOUND_EVENTS = [
|
|
|
28
29
|
'system.login.error',
|
|
29
30
|
'system.login.auth',
|
|
30
31
|
];
|
|
31
|
-
|
|
32
|
+
class ManagedIcqqClient extends Client {
|
|
33
|
+
cancelReconnect() {
|
|
34
|
+
if (!this.login_timer)
|
|
35
|
+
return;
|
|
36
|
+
clearTimeout(this.login_timer);
|
|
37
|
+
this.login_timer = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class IcqqEndpoint extends Endpoint {
|
|
41
|
+
client;
|
|
32
42
|
#logger;
|
|
33
43
|
#options;
|
|
34
44
|
endpointName;
|
|
@@ -39,10 +49,13 @@ export class IcqqEndpoint extends Client {
|
|
|
39
49
|
#open = false;
|
|
40
50
|
#retiring = false;
|
|
41
51
|
#started = false;
|
|
52
|
+
#retirementEpoch = 0;
|
|
53
|
+
#retirementArmed = false;
|
|
54
|
+
#disposeNativeEvents;
|
|
42
55
|
#resolveStartOnline;
|
|
43
56
|
#heldInbound = [];
|
|
44
|
-
#unregisterAgent;
|
|
45
57
|
#inflightInbound = new Set();
|
|
58
|
+
#inboundOwner = new AsyncLocalStorage();
|
|
46
59
|
#messageContent = new Map();
|
|
47
60
|
content = Object.freeze({
|
|
48
61
|
resolve: async (reference, context) => {
|
|
@@ -85,7 +98,7 @@ export class IcqqEndpoint extends Client {
|
|
|
85
98
|
// ICQQ does not expose a cancellable getForwardMsg API. Keep the caller's
|
|
86
99
|
// generation lease until the native operation actually settles, then honor
|
|
87
100
|
// cancellation before publishing any resolved content.
|
|
88
|
-
const raw = await this.getForwardMsg(forwardId);
|
|
101
|
+
const raw = await this.client.getForwardMsg(forwardId);
|
|
89
102
|
context.signal.throwIfAborted();
|
|
90
103
|
const entries = normalizeForwardMsgResponse(raw).slice(0, state.remainingEntries);
|
|
91
104
|
state.remainingEntries -= entries.length;
|
|
@@ -118,41 +131,41 @@ export class IcqqEndpoint extends Client {
|
|
|
118
131
|
}
|
|
119
132
|
}
|
|
120
133
|
management = Object.freeze({
|
|
121
|
-
listFriends: async () => Array.from(this.fl.values()).map((f) => ({
|
|
134
|
+
listFriends: async () => Array.from(this.client.fl.values()).map((f) => ({
|
|
122
135
|
user_id: f.user_id,
|
|
123
136
|
nickname: f.nickname,
|
|
124
137
|
remark: f.remark ?? '',
|
|
125
138
|
})),
|
|
126
|
-
listGroups: async () => Array.from(this.gl.values()).map((g) => ({
|
|
139
|
+
listGroups: async () => Array.from(this.client.gl.values()).map((g) => ({
|
|
127
140
|
group_id: g.group_id,
|
|
128
141
|
name: g.group_name,
|
|
129
142
|
})),
|
|
130
143
|
listChannels: async () => {
|
|
131
|
-
await this.#guildCatalog.syncAll(this);
|
|
144
|
+
await this.#guildCatalog.syncAll(this.client);
|
|
132
145
|
return this.#guildCatalog.getGuildChannelList();
|
|
133
146
|
},
|
|
134
|
-
listGroupMembers: async (groupId) => Array.from((await this.getGroupMemberList(Number(groupId))).values()),
|
|
147
|
+
listGroupMembers: async (groupId) => Array.from((await this.client.getGroupMemberList(Number(groupId))).values()),
|
|
135
148
|
listRequests: () => this.#listPendingRequests(),
|
|
136
149
|
approveRequest: (requestId, remark) => this.#approveRequest(requestId, remark),
|
|
137
150
|
rejectRequest: (requestId, reason) => this.#rejectRequest(requestId, reason),
|
|
138
151
|
kickGroupMember: async (groupId, userId) => {
|
|
139
|
-
await this.setGroupKick(Number(groupId), Number(userId));
|
|
152
|
+
await this.client.setGroupKick(Number(groupId), Number(userId));
|
|
140
153
|
},
|
|
141
154
|
muteGroupMember: async (groupId, userId, duration) => {
|
|
142
|
-
await this.setGroupBan(Number(groupId), Number(userId), duration);
|
|
155
|
+
await this.client.setGroupBan(Number(groupId), Number(userId), duration);
|
|
143
156
|
},
|
|
144
157
|
setGroupAdmin: async (groupId, userId, enabled) => {
|
|
145
|
-
await this.setGroupAdmin(Number(groupId), Number(userId), enabled);
|
|
158
|
+
await this.client.setGroupAdmin(Number(groupId), Number(userId), enabled);
|
|
146
159
|
},
|
|
147
160
|
deleteFriend: async (userId) => {
|
|
148
|
-
await this.deleteFriend(Number(userId));
|
|
161
|
+
await this.client.deleteFriend(Number(userId));
|
|
149
162
|
},
|
|
150
163
|
});
|
|
151
164
|
control = Object.freeze({
|
|
152
165
|
recall: async (message) => {
|
|
153
166
|
if (!message.id || message.id.startsWith('outbound:'))
|
|
154
167
|
return;
|
|
155
|
-
await this.deleteMsg(message.id);
|
|
168
|
+
await this.client.deleteMsg(message.id);
|
|
156
169
|
},
|
|
157
170
|
addReaction: async (message, emoji) => {
|
|
158
171
|
const target = resolveIcqqGroupReactionTarget(message);
|
|
@@ -160,7 +173,7 @@ export class IcqqEndpoint extends Client {
|
|
|
160
173
|
return null;
|
|
161
174
|
try {
|
|
162
175
|
// Do not await protocol ACK — packet timeout must not stall the AI reply.
|
|
163
|
-
this.#ignoreReactionAck('add', this.pickGroup(target.groupId).setReaction(target.seq, emoji));
|
|
176
|
+
this.#ignoreReactionAck('add', this.client.pickGroup(target.groupId).setReaction(target.seq, emoji));
|
|
164
177
|
}
|
|
165
178
|
catch (error) {
|
|
166
179
|
this.#logger.debug(formatCompact({
|
|
@@ -175,7 +188,7 @@ export class IcqqEndpoint extends Client {
|
|
|
175
188
|
if (!target)
|
|
176
189
|
return;
|
|
177
190
|
try {
|
|
178
|
-
this.#ignoreReactionAck('remove', this.pickGroup(target.groupId).delReaction(target.seq, reactionId));
|
|
191
|
+
this.#ignoreReactionAck('remove', this.client.pickGroup(target.groupId).delReaction(target.seq, reactionId));
|
|
179
192
|
}
|
|
180
193
|
catch (error) {
|
|
181
194
|
this.#logger.debug(formatCompact({
|
|
@@ -186,8 +199,9 @@ export class IcqqEndpoint extends Client {
|
|
|
186
199
|
},
|
|
187
200
|
});
|
|
188
201
|
constructor(options) {
|
|
202
|
+
super();
|
|
189
203
|
const nativeConfig = resolveNativeClientConfig(options.config);
|
|
190
|
-
|
|
204
|
+
this.client = new ManagedIcqqClient(Number(options.config.id), nativeConfig);
|
|
191
205
|
this.#logger = getAdapterLogger('icqq', options.config.id);
|
|
192
206
|
this.#options = options;
|
|
193
207
|
this.endpointName = options.config.id;
|
|
@@ -204,6 +218,12 @@ export class IcqqEndpoint extends Client {
|
|
|
204
218
|
if (this.#started)
|
|
205
219
|
return;
|
|
206
220
|
signal.throwIfAborted();
|
|
221
|
+
// Invalidate a guard left by a prior lifecycle epoch before admitting a
|
|
222
|
+
// new login attempt on this endpoint instance.
|
|
223
|
+
this.#retirementEpoch += 1;
|
|
224
|
+
this.#retirementArmed = false;
|
|
225
|
+
this.#retiring = false;
|
|
226
|
+
this.client.off('system.online');
|
|
207
227
|
this.#started = true;
|
|
208
228
|
let resolveOnline;
|
|
209
229
|
const onlineReady = new Promise((resolve) => {
|
|
@@ -214,13 +234,12 @@ export class IcqqEndpoint extends Client {
|
|
|
214
234
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
215
235
|
try {
|
|
216
236
|
this.#bindClientEvents();
|
|
217
|
-
await raceAbort(Promise.resolve(this.login(this.#options.config.password)), signal);
|
|
237
|
+
await raceAbort(Promise.resolve(this.client.login(this.#options.config.password)), signal);
|
|
218
238
|
await onlineReady;
|
|
219
239
|
signal.throwIfAborted();
|
|
220
240
|
await this.#pullPendingSystemMessages(signal);
|
|
221
241
|
signal.throwIfAborted();
|
|
222
|
-
this.#
|
|
223
|
-
this.#logger.info(`connected (direct) | friends: ${this.fl.size} | groups: ${this.gl.size}`);
|
|
242
|
+
this.#logger.info(`connected (direct) | friends: ${this.client.fl.size} | groups: ${this.client.gl.size}`);
|
|
224
243
|
}
|
|
225
244
|
catch (error) {
|
|
226
245
|
await this.stop();
|
|
@@ -234,34 +253,48 @@ export class IcqqEndpoint extends Client {
|
|
|
234
253
|
}
|
|
235
254
|
}
|
|
236
255
|
open() {
|
|
256
|
+
if (this.#retirementArmed) {
|
|
257
|
+
this.#retirementEpoch += 1;
|
|
258
|
+
this.#retirementArmed = false;
|
|
259
|
+
this.client.off('system.online');
|
|
260
|
+
this.#bindSystemOnlineEvent();
|
|
261
|
+
}
|
|
237
262
|
this.#retiring = false;
|
|
238
263
|
this.#open = true;
|
|
239
264
|
const held = this.#heldInbound.splice(0);
|
|
240
|
-
for (const msg of held)
|
|
241
|
-
this.admit(msg)
|
|
265
|
+
for (const msg of held) {
|
|
266
|
+
void this.admit(msg).catch((error) => {
|
|
267
|
+
this.#logInboundHandlerFailure('held', error);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
242
270
|
}
|
|
243
271
|
async close() {
|
|
244
|
-
this.#
|
|
245
|
-
this.#
|
|
272
|
+
this.#assertExternalLifecycle('close');
|
|
273
|
+
this.#armRetirementFence();
|
|
246
274
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
247
275
|
}
|
|
248
276
|
async stop() {
|
|
249
|
-
this.#
|
|
250
|
-
this.#
|
|
277
|
+
this.#assertExternalLifecycle('stop');
|
|
278
|
+
this.#armRetirementFence();
|
|
251
279
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
252
280
|
this.#resolveStartOnline?.();
|
|
253
281
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'endpoint_stopped');
|
|
254
282
|
this.#heldInbound.length = 0;
|
|
255
|
-
this.#
|
|
256
|
-
this.#
|
|
257
|
-
for (const event of BOUND_EVENTS)
|
|
258
|
-
this
|
|
259
|
-
|
|
260
|
-
|
|
283
|
+
this.#disposeNativeEvents?.();
|
|
284
|
+
this.#disposeNativeEvents = undefined;
|
|
285
|
+
for (const event of BOUND_EVENTS) {
|
|
286
|
+
// The retirement guard owns this matcher until a late login handshake
|
|
287
|
+
// has finished. start()/open() replace it with the normal listener.
|
|
288
|
+
if (event !== 'system.online')
|
|
289
|
+
this.client.off(event);
|
|
261
290
|
}
|
|
262
|
-
|
|
291
|
+
// `Client.logout()` sends an account-level unregister packet before it
|
|
292
|
+
// closes this transport. During config HMR the replacement endpoint for
|
|
293
|
+
// the same UIN is already online, so unregistering the retiring generation
|
|
294
|
+
// also closes the replacement session. Endpoint ownership is transport
|
|
295
|
+
// scoped: retire only this Client's TCP connection.
|
|
263
296
|
try {
|
|
264
|
-
this.terminate();
|
|
297
|
+
this.client.terminate();
|
|
265
298
|
}
|
|
266
299
|
catch { /* ignore */ }
|
|
267
300
|
this.#inboundDeduper.clear();
|
|
@@ -270,6 +303,30 @@ export class IcqqEndpoint extends Client {
|
|
|
270
303
|
this.#started = false;
|
|
271
304
|
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
272
305
|
}
|
|
306
|
+
#armRetirementFence() {
|
|
307
|
+
this.#retiring = true;
|
|
308
|
+
this.#open = false;
|
|
309
|
+
if (this.#retirementArmed)
|
|
310
|
+
return;
|
|
311
|
+
this.#retirementArmed = true;
|
|
312
|
+
const retirementEpoch = ++this.#retirementEpoch;
|
|
313
|
+
// ICQQ owns reconnect through this per-Client timer. Clear it before any
|
|
314
|
+
// asynchronous drain so a queued callback cannot enter while close waits.
|
|
315
|
+
this.client.cancelReconnect();
|
|
316
|
+
// A login callback may already be inside the SDK handshake, which has no
|
|
317
|
+
// abort API. Replace the normal online listener with an epoch-owned guard
|
|
318
|
+
// before draining inbound work. Transport termination is account-local;
|
|
319
|
+
// logout would also unregister an HMR replacement using the same UIN.
|
|
320
|
+
this.client.off('system.online');
|
|
321
|
+
this.client.on('system.online', () => {
|
|
322
|
+
if (this.#retirementEpoch !== retirementEpoch || !this.#retiring)
|
|
323
|
+
return;
|
|
324
|
+
try {
|
|
325
|
+
this.client.terminate();
|
|
326
|
+
}
|
|
327
|
+
catch { /* already closed */ }
|
|
328
|
+
});
|
|
329
|
+
}
|
|
273
330
|
async send({ conversation, payload }) {
|
|
274
331
|
if (!this.#started)
|
|
275
332
|
throw new Error('icqq endpoint 未连接');
|
|
@@ -306,11 +363,11 @@ export class IcqqEndpoint extends Client {
|
|
|
306
363
|
if (isIcqqFileElement(message)) {
|
|
307
364
|
switch (target.kind) {
|
|
308
365
|
case 'private': {
|
|
309
|
-
const id = await this.pickFriend(target.userId).sendFile(message.file, message.name);
|
|
366
|
+
const id = await this.client.pickFriend(target.userId).sendFile(message.file, message.name);
|
|
310
367
|
return { message_id: id };
|
|
311
368
|
}
|
|
312
369
|
case 'group': {
|
|
313
|
-
const stat = await this.pickGroup(target.groupId).sendFile(message.file, '/', message.name);
|
|
370
|
+
const stat = await this.client.pickGroup(target.groupId).sendFile(message.file, '/', message.name);
|
|
314
371
|
return { message_id: stat.fid };
|
|
315
372
|
}
|
|
316
373
|
case 'temp':
|
|
@@ -320,16 +377,43 @@ export class IcqqEndpoint extends Client {
|
|
|
320
377
|
}
|
|
321
378
|
switch (target.kind) {
|
|
322
379
|
case 'private':
|
|
323
|
-
return this.sendPrivateMsg(target.userId, message);
|
|
380
|
+
return this.client.sendPrivateMsg(target.userId, message);
|
|
324
381
|
case 'group':
|
|
325
|
-
return this.sendGroupMsg(target.groupId, message);
|
|
382
|
+
return this.client.sendGroupMsg(target.groupId, message);
|
|
326
383
|
case 'temp':
|
|
327
|
-
return this.sendTempMsg(target.groupId, target.userId, message);
|
|
384
|
+
return this.client.sendTempMsg(target.groupId, target.userId, message);
|
|
328
385
|
case 'channel':
|
|
329
|
-
return this.sendGuildMsg(target.guildId, target.channelId, message);
|
|
386
|
+
return this.client.sendGuildMsg(target.guildId, target.channelId, message);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
admit(msg) {
|
|
390
|
+
return this.#trackInbound(() => this.#deliverInbound(msg));
|
|
391
|
+
}
|
|
392
|
+
#trackInbound(run) {
|
|
393
|
+
const owner = Symbol('icqq-inbound');
|
|
394
|
+
let settle;
|
|
395
|
+
const settlement = new Promise((resolve) => { settle = resolve; });
|
|
396
|
+
this.#inflightInbound.add(settlement);
|
|
397
|
+
let operation;
|
|
398
|
+
try {
|
|
399
|
+
operation = this.#inboundOwner.run(owner, run);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
this.#inflightInbound.delete(settlement);
|
|
403
|
+
settle();
|
|
404
|
+
return Promise.reject(error);
|
|
330
405
|
}
|
|
406
|
+
return operation.finally(() => {
|
|
407
|
+
this.#inflightInbound.delete(settlement);
|
|
408
|
+
settle();
|
|
409
|
+
});
|
|
331
410
|
}
|
|
332
|
-
|
|
411
|
+
#assertExternalLifecycle(operation) {
|
|
412
|
+
if (!this.#inboundOwner.getStore())
|
|
413
|
+
return;
|
|
414
|
+
throw new Error(`ICQQ endpoint ${operation} cannot run inside its own inbound receive; invoke it from the owning lifecycle`);
|
|
415
|
+
}
|
|
416
|
+
async #deliverInbound(msg, admittedBeforeRetire = false) {
|
|
333
417
|
const conversation = msg.conversation;
|
|
334
418
|
if (!this.#open && !admittedBeforeRetire) {
|
|
335
419
|
if (this.#retiring)
|
|
@@ -361,7 +445,7 @@ export class IcqqEndpoint extends Client {
|
|
|
361
445
|
+ (msg.sender ? ` from ${msg.sender.name ?? msg.sender.id}` : '')
|
|
362
446
|
+ (mentioned ? ' (mentioned)' : '')
|
|
363
447
|
+ ` | ${truncatePreview(msg.content, 80)}`);
|
|
364
|
-
|
|
448
|
+
await this.emit('message.receive', {
|
|
365
449
|
conversation,
|
|
366
450
|
message: { conversation, id: msg.id },
|
|
367
451
|
content: msg.content,
|
|
@@ -385,6 +469,7 @@ export class IcqqEndpoint extends Client {
|
|
|
385
469
|
});
|
|
386
470
|
}
|
|
387
471
|
#bindClientEvents() {
|
|
472
|
+
this.#bindNativeEvents();
|
|
388
473
|
const safe = (label, fn) => {
|
|
389
474
|
try {
|
|
390
475
|
fn();
|
|
@@ -398,32 +483,101 @@ export class IcqqEndpoint extends Client {
|
|
|
398
483
|
}));
|
|
399
484
|
}
|
|
400
485
|
};
|
|
401
|
-
this.on('message.private', (event) =>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
this.on('
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
this.on('
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
486
|
+
this.client.on('message.private', (event) => {
|
|
487
|
+
this.#startMessageEvent('message.private', event);
|
|
488
|
+
});
|
|
489
|
+
this.client.on('message.group', (event) => {
|
|
490
|
+
this.#startMessageEvent('message.group', event);
|
|
491
|
+
});
|
|
492
|
+
this.client.on('message.guild', (event) => {
|
|
493
|
+
void this.#handleGuildEvent(event).catch((error) => {
|
|
494
|
+
this.#logInboundHandlerFailure('message.guild', error);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
this.client.on('request.friend', (event) => safe('request.friend', () => {
|
|
498
|
+
this.#onRequestEvent(serializeIcqqEvent(event));
|
|
499
|
+
}));
|
|
500
|
+
this.client.on('request.group', (event) => safe('request.group', () => {
|
|
501
|
+
this.#onRequestEvent(serializeIcqqEvent(event));
|
|
502
|
+
}));
|
|
503
|
+
this.client.on('notice.friend', (event) => safe('notice.friend', () => {
|
|
504
|
+
this.#onNoticeEvent(serializeIcqqEvent(event));
|
|
412
505
|
}));
|
|
413
|
-
this.on('
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
this
|
|
506
|
+
this.client.on('notice.group', (event) => safe('notice.group', () => {
|
|
507
|
+
this.#onNoticeEvent(serializeIcqqEvent(event));
|
|
508
|
+
}));
|
|
509
|
+
this.#bindSystemOnlineEvent();
|
|
510
|
+
this.client.on('system.offline', (event) => {
|
|
511
|
+
this.#logSystemEvent('offline', event);
|
|
512
|
+
});
|
|
513
|
+
this.client.on('system.offline.network', (event) => {
|
|
514
|
+
this.#logSystemEvent('offline.network', event);
|
|
515
|
+
});
|
|
516
|
+
this.client.on('system.offline.kickoff', (event) => {
|
|
517
|
+
this.#logSystemEvent('offline.kickoff', event);
|
|
518
|
+
});
|
|
519
|
+
this.client.on('system.login.qrcode', (event) => {
|
|
417
520
|
this.#logger.info('icqq 收到二维码登录事件,等待 LoginAssist / 终端确认后继续');
|
|
418
521
|
this.#handleLoginChallenge('qrcode', event);
|
|
419
522
|
});
|
|
420
|
-
this.on('system.login.device', (event) =>
|
|
421
|
-
|
|
422
|
-
|
|
523
|
+
this.client.on('system.login.device', (event) => {
|
|
524
|
+
this.#handleLoginChallenge('device', event);
|
|
525
|
+
});
|
|
526
|
+
this.client.on('system.login.slider', (event) => {
|
|
527
|
+
this.#handleLoginChallenge('slider', event);
|
|
528
|
+
});
|
|
529
|
+
this.client.on('system.login.error', (event) => {
|
|
423
530
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'login_error');
|
|
424
531
|
this.#logSystemEvent('login.error', event);
|
|
425
532
|
});
|
|
426
|
-
this.on('system.login.auth', (event) =>
|
|
533
|
+
this.client.on('system.login.auth', (event) => {
|
|
534
|
+
this.#handleLoginChallenge('auth', event);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
#bindSystemOnlineEvent() {
|
|
538
|
+
this.client.on('system.online', (event) => {
|
|
539
|
+
if (this.#retiring)
|
|
540
|
+
return;
|
|
541
|
+
try {
|
|
542
|
+
this.#resolveStartOnline?.();
|
|
543
|
+
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'online');
|
|
544
|
+
this.#logSystemEvent('online', event);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
this.#logger.warn(formatCompact({
|
|
548
|
+
op: 'icqq_event_handler',
|
|
549
|
+
endpoint: this.endpointName,
|
|
550
|
+
event: 'system.online',
|
|
551
|
+
error: error instanceof Error ? error.message : String(error),
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/** Forward every public SDK event, including protocol extensions unknown to Zhin. */
|
|
557
|
+
#bindNativeEvents() {
|
|
558
|
+
this.#disposeNativeEvents?.();
|
|
559
|
+
let matchedName;
|
|
560
|
+
const registration = this.client.on((eventName) => {
|
|
561
|
+
matchedName = typeof eventName === 'string' && !eventName.startsWith('internal.')
|
|
562
|
+
? eventName
|
|
563
|
+
: undefined;
|
|
564
|
+
return matchedName !== undefined;
|
|
565
|
+
}, (event) => {
|
|
566
|
+
const name = matchedName;
|
|
567
|
+
if (name)
|
|
568
|
+
this.#emitNativeEvent(name, event);
|
|
569
|
+
});
|
|
570
|
+
this.#disposeNativeEvents = () => registration();
|
|
571
|
+
}
|
|
572
|
+
#emitNativeEvent(name, event) {
|
|
573
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
574
|
+
this.#logger.warn(formatCompact({
|
|
575
|
+
op: 'icqq_platform_event_failed',
|
|
576
|
+
endpoint: this.endpointName,
|
|
577
|
+
event: name,
|
|
578
|
+
error: error instanceof Error ? error.message : String(error),
|
|
579
|
+
}));
|
|
580
|
+
});
|
|
427
581
|
}
|
|
428
582
|
#handleLoginChallenge(step, event) {
|
|
429
583
|
const payload = serializeIcqqEvent(event) ?? (event && typeof event === 'object'
|
|
@@ -432,7 +586,7 @@ export class IcqqEndpoint extends Client {
|
|
|
432
586
|
this.#logSystemEvent(`login.${step}`, event);
|
|
433
587
|
void runIcqqLoginAssistStep({
|
|
434
588
|
assist: this.#options.loginAssist,
|
|
435
|
-
client: this,
|
|
589
|
+
client: this.client,
|
|
436
590
|
adapter: 'icqq',
|
|
437
591
|
endpointKey: this.endpointName,
|
|
438
592
|
owner: this.#loginAssistOwner,
|
|
@@ -451,11 +605,10 @@ export class IcqqEndpoint extends Client {
|
|
|
451
605
|
this.#dispatchIcqqSystemSideEvent(type, payload);
|
|
452
606
|
}
|
|
453
607
|
#dispatchIcqqSideEvent(payload) {
|
|
454
|
-
const sideEvents = this.#options.sideEvents;
|
|
455
608
|
const postType = String(payload.post_type ?? '');
|
|
456
609
|
const isRequest = postType === 'request' || postType.startsWith('request.');
|
|
457
610
|
const flag = payload.flag != null ? String(payload.flag) : '';
|
|
458
|
-
void receiveOneBotLikeSideEvent(
|
|
611
|
+
void receiveOneBotLikeSideEvent((name, payload) => this.emit(name, payload), {
|
|
459
612
|
adapter: 'icqq',
|
|
460
613
|
endpointKey: this.endpointName,
|
|
461
614
|
platform: 'icqq',
|
|
@@ -474,9 +627,8 @@ export class IcqqEndpoint extends Client {
|
|
|
474
627
|
});
|
|
475
628
|
}
|
|
476
629
|
#dispatchIcqqSystemSideEvent(type, payload) {
|
|
477
|
-
const sideEvents = this.#options.sideEvents;
|
|
478
630
|
const { scene_type, sub_type } = parseIcqqSystemScene(type);
|
|
479
|
-
void
|
|
631
|
+
void this.emit('system.receive', SystemEvent.from(payload ?? {}, {
|
|
480
632
|
$id: `system:${this.endpointName}:${type}:${Date.now()}`,
|
|
481
633
|
$adapter: 'icqq',
|
|
482
634
|
$endpoint: this.endpointName,
|
|
@@ -568,7 +720,7 @@ export class IcqqEndpoint extends Client {
|
|
|
568
720
|
timestamp: quoted.time ? quoted.time * (quoted.time < 1e12 ? 1000 : 1) : Date.now(),
|
|
569
721
|
}));
|
|
570
722
|
}
|
|
571
|
-
this
|
|
723
|
+
await this.#deliverInbound({
|
|
572
724
|
id: normalized.messageId,
|
|
573
725
|
conversation,
|
|
574
726
|
content: formatInboundContent(normalized.rawMessage),
|
|
@@ -587,12 +739,9 @@ export class IcqqEndpoint extends Client {
|
|
|
587
739
|
}, admittedBeforeRetire);
|
|
588
740
|
}
|
|
589
741
|
#startMessageEvent(eventName, event) {
|
|
590
|
-
const operation = this.#handleMessageEvent(eventName, event);
|
|
591
|
-
this.#inflightInbound.add(operation);
|
|
742
|
+
const operation = this.#trackInbound(() => this.#handleMessageEvent(eventName, event));
|
|
592
743
|
void operation.catch((error) => {
|
|
593
744
|
this.#logInboundHandlerFailure(eventName, error);
|
|
594
|
-
}).finally(() => {
|
|
595
|
-
this.#inflightInbound.delete(operation);
|
|
596
745
|
});
|
|
597
746
|
}
|
|
598
747
|
#resolveInboundContact(data) {
|
|
@@ -601,13 +750,13 @@ export class IcqqEndpoint extends Client {
|
|
|
601
750
|
return null;
|
|
602
751
|
if (data.message_type === 'group' || data.group_id != null) {
|
|
603
752
|
const groupId = Number(data.group_id);
|
|
604
|
-
return Number.isFinite(groupId) && groupId > 0 ? this.pickGroup(groupId) : null;
|
|
753
|
+
return Number.isFinite(groupId) && groupId > 0 ? this.client.pickGroup(groupId) : null;
|
|
605
754
|
}
|
|
606
755
|
const sourceGroupId = Number(data.sender?.group_id);
|
|
607
756
|
if (Number.isFinite(sourceGroupId) && sourceGroupId > 0) {
|
|
608
|
-
return this.pickMember(sourceGroupId, userId);
|
|
757
|
+
return this.client.pickMember(sourceGroupId, userId);
|
|
609
758
|
}
|
|
610
|
-
return this.pickFriend(userId);
|
|
759
|
+
return this.client.pickFriend(userId);
|
|
611
760
|
}
|
|
612
761
|
#logInboundHandlerFailure(event, error) {
|
|
613
762
|
this.#logger.warn(formatCompact({
|
|
@@ -617,7 +766,7 @@ export class IcqqEndpoint extends Client {
|
|
|
617
766
|
error: describeUnknownError(error),
|
|
618
767
|
}));
|
|
619
768
|
}
|
|
620
|
-
#handleGuildEvent(event) {
|
|
769
|
+
async #handleGuildEvent(event) {
|
|
621
770
|
const payload = serializeIcqqEvent(event);
|
|
622
771
|
if (!payload || typeof payload !== 'object')
|
|
623
772
|
return;
|
|
@@ -640,7 +789,7 @@ export class IcqqEndpoint extends Client {
|
|
|
640
789
|
}));
|
|
641
790
|
return;
|
|
642
791
|
}
|
|
643
|
-
this.admit({
|
|
792
|
+
await this.admit({
|
|
644
793
|
id: normalized.messageId,
|
|
645
794
|
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
646
795
|
channelType: 'channel',
|
|
@@ -679,7 +828,7 @@ export class IcqqEndpoint extends Client {
|
|
|
679
828
|
this.#dispatchIcqqSideEvent(payload);
|
|
680
829
|
}
|
|
681
830
|
async #listPendingRequests() {
|
|
682
|
-
const messages = await this.getSystemMsg();
|
|
831
|
+
const messages = await this.client.getSystemMsg();
|
|
683
832
|
const rows = [];
|
|
684
833
|
for (const raw of messages) {
|
|
685
834
|
const msg = raw;
|
|
@@ -707,7 +856,7 @@ export class IcqqEndpoint extends Client {
|
|
|
707
856
|
}
|
|
708
857
|
async #pullPendingSystemMessages(signal) {
|
|
709
858
|
try {
|
|
710
|
-
const messages = await raceAbort(Promise.resolve(this.getSystemMsg()), signal);
|
|
859
|
+
const messages = await raceAbort(Promise.resolve(this.client.getSystemMsg()), signal);
|
|
711
860
|
for (const raw of messages) {
|
|
712
861
|
signal.throwIfAborted();
|
|
713
862
|
const msg = raw;
|
|
@@ -738,7 +887,7 @@ export class IcqqEndpoint extends Client {
|
|
|
738
887
|
}
|
|
739
888
|
}
|
|
740
889
|
async #approveRequest(id, remark) {
|
|
741
|
-
const messages = await this.getSystemMsg();
|
|
890
|
+
const messages = await this.client.getSystemMsg();
|
|
742
891
|
const target = messages.find((m) => {
|
|
743
892
|
const msg = m;
|
|
744
893
|
return msg.flag === id || (msg.seq != null && String(msg.seq) === id);
|
|
@@ -747,14 +896,14 @@ export class IcqqEndpoint extends Client {
|
|
|
747
896
|
throw new Error(`icqq 未找到待处理请求: ${id}`);
|
|
748
897
|
}
|
|
749
898
|
if (target.request_type === 'friend') {
|
|
750
|
-
await this.setFriendAddRequest(target.flag, true, remark);
|
|
899
|
+
await this.client.setFriendAddRequest(target.flag, true, remark);
|
|
751
900
|
}
|
|
752
901
|
else {
|
|
753
|
-
await this.setGroupAddRequest(target.flag, true);
|
|
902
|
+
await this.client.setGroupAddRequest(target.flag, true);
|
|
754
903
|
}
|
|
755
904
|
}
|
|
756
905
|
async #rejectRequest(id, reason) {
|
|
757
|
-
const messages = await this.getSystemMsg();
|
|
906
|
+
const messages = await this.client.getSystemMsg();
|
|
758
907
|
const target = messages.find((m) => {
|
|
759
908
|
const msg = m;
|
|
760
909
|
return msg.flag === id || (msg.seq != null && String(msg.seq) === id);
|
|
@@ -763,10 +912,10 @@ export class IcqqEndpoint extends Client {
|
|
|
763
912
|
throw new Error(`icqq 未找到待处理请求: ${id}`);
|
|
764
913
|
}
|
|
765
914
|
if (target.request_type === 'friend') {
|
|
766
|
-
await this.setFriendAddRequest(target.flag, false);
|
|
915
|
+
await this.client.setFriendAddRequest(target.flag, false);
|
|
767
916
|
}
|
|
768
917
|
else {
|
|
769
|
-
await this.setGroupAddRequest(target.flag, false, reason);
|
|
918
|
+
await this.client.setGroupAddRequest(target.flag, false, reason);
|
|
770
919
|
}
|
|
771
920
|
}
|
|
772
921
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const icqqEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage>>>;
|
|
1
|
+
export declare const icqqEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, type ActionResult, type IcqqAdapterConfig, type IcqqInboundMessage, type IcqqWireSegment, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
|
|
2
2
|
export { IcqqEndpoint, type IcqqEndpointOptions, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
|
-
export {
|
|
4
|
+
export { icqqClient, type IcqqClient, type IcqqClientEventMap, } from './client.js';
|
|
5
5
|
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
|
|
2
2
|
export { IcqqEndpoint, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
|
-
export {
|
|
4
|
+
export { icqqClient, } from './client.js';
|
|
5
5
|
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
package/lib/protocol.js
CHANGED
|
@@ -180,7 +180,7 @@ export function resolveIcqqConfig(config = {}) {
|
|
|
180
180
|
}
|
|
181
181
|
const autoReconnect = config.autoReconnect ?? entry?.autoReconnect ?? true;
|
|
182
182
|
const outboundMedia = config.outboundMedia ?? entry?.outboundMedia;
|
|
183
|
-
const password = entry?.password;
|
|
183
|
+
const password = config.password ?? entry?.password;
|
|
184
184
|
const platform = config.platform ?? entry?.platform ?? 2;
|
|
185
185
|
const ver = config.ver ?? entry?.ver ?? '9.1.70';
|
|
186
186
|
const dataDir = config.dataDir ?? entry?.dataDir ?? `data/icqq/${id}`;
|