@zhin.js/adapter-icqq 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 +37 -0
- package/README.md +12 -8
- package/adapters/icqq/index.js +4 -28
- package/adapters/icqq/index.ts +3 -34
- package/commands/icqq/endpoint/definition.js +3 -25
- package/commands/icqq/endpoint/definition.ts +3 -24
- package/commands/icqq/login/code/index.js +3 -3
- package/commands/icqq/login/code/index.ts +3 -3
- package/commands/icqq/login/confirm/index.js +3 -3
- package/commands/icqq/login/confirm/index.ts +3 -3
- package/commands/icqq/login/definition.js +2 -2
- package/commands/icqq/login/definition.ts +2 -2
- package/commands/icqq/login/qr/index.js +3 -3
- package/commands/icqq/login/qr/index.ts +3 -3
- package/commands/icqq/login/retry/index.js +3 -3
- package/commands/icqq/login/retry/index.ts +3 -3
- package/commands/icqq/login/status/index.js +3 -3
- package/commands/icqq/login/status/index.ts +3 -3
- package/lib/adapter.d.ts +13 -0
- package/lib/adapter.js +27 -0
- package/lib/client.d.ts +6 -9
- package/lib/client.js +1 -3
- package/lib/console-spec.js +11 -1
- package/lib/console.d.ts +1 -7
- package/lib/console.js +2 -5
- package/lib/endpoint.d.ts +6 -16
- package/lib/endpoint.js +195 -143
- package/lib/group-moderation.d.ts +6 -3
- package/lib/group-moderation.js +9 -8
- package/lib/group-scene.d.ts +3 -3
- package/lib/group-scene.js +8 -7
- package/lib/icqq-login-assist.d.ts +3 -1
- package/lib/icqq-login-assist.js +3 -1
- package/lib/icqq-login-control.d.ts +8 -2
- package/lib/icqq-login-control.js +22 -5
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +3 -0
- package/package.json +18 -18
- package/plugin.js +1 -8
- package/src/adapter.ts +29 -0
- package/src/client.ts +6 -11
- package/src/console-spec.ts +9 -0
- package/src/console.ts +2 -6
- package/src/endpoint.ts +205 -153
- package/src/group-moderation.ts +10 -10
- package/src/group-scene.ts +8 -8
- package/src/icqq-login-assist.ts +6 -2
- package/src/icqq-login-control.ts +20 -8
- package/src/index.ts +2 -1
- package/src/protocol.ts +3 -0
- package/lib/icqq-runtime-state.d.ts +0 -1
- package/lib/icqq-runtime-state.js +0 -6
- package/src/icqq-runtime-state.ts +0 -7
package/lib/endpoint.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseGroupMessageId, } from '@icqqjs/icqq';
|
|
2
2
|
import { inspect } from 'node:util';
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
-
import {
|
|
4
|
+
import { createEndpointSpecification, endpointSpec, createEndpointOperation, } from 'zhin.js/adapter';
|
|
5
5
|
import { receiveOneBotLikeSideEvent, buildSystem, toCanonicalSegments } from '@zhin.js/core';
|
|
6
6
|
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
7
7
|
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
@@ -33,8 +33,8 @@ const BOUND_EVENTS = [
|
|
|
33
33
|
'system.login.error',
|
|
34
34
|
'system.login.auth',
|
|
35
35
|
];
|
|
36
|
-
export class IcqqEndpoint extends
|
|
37
|
-
|
|
36
|
+
export class IcqqEndpoint extends ManagedIcqqClient {
|
|
37
|
+
[endpointSpec];
|
|
38
38
|
#logger;
|
|
39
39
|
#options;
|
|
40
40
|
endpointName;
|
|
@@ -55,69 +55,79 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
55
55
|
#inflightInbound = new Set();
|
|
56
56
|
#inboundOwner = new AsyncLocalStorage();
|
|
57
57
|
#contentResolver;
|
|
58
|
-
content;
|
|
58
|
+
#content;
|
|
59
59
|
loginControl;
|
|
60
60
|
#reloginTimer;
|
|
61
61
|
#reloginAttempts = 0;
|
|
62
62
|
#reloginInFlight;
|
|
63
63
|
#offlineNoticeAt = 0;
|
|
64
64
|
#challengeNoticeAt = 0;
|
|
65
|
-
management = Object.freeze({
|
|
66
|
-
inspectGroupMember:
|
|
67
|
-
inspectGroupScene:
|
|
68
|
-
inspectGroupMemberProfile:
|
|
69
|
-
moderateGroupMember:
|
|
70
|
-
listFriends: async () => Array.from(this.
|
|
65
|
+
#management = Object.freeze({
|
|
66
|
+
inspectGroupMember: request => inspectGroupMember(this, request),
|
|
67
|
+
inspectGroupScene: request => inspectGroupScene(this, request),
|
|
68
|
+
inspectGroupMemberProfile: request => inspectGroupMemberProfile(this, request),
|
|
69
|
+
moderateGroupMember: request => moderateGroupMember({ client: this, isActive: () => this.#open && !this.#retiring && !this.#stopped }, request),
|
|
70
|
+
listFriends: async (request = {}) => createEndpointOperation(request).run(() => Array.from(this.fl.values()).map((f) => ({
|
|
71
71
|
user_id: f.user_id,
|
|
72
72
|
nickname: f.nickname,
|
|
73
73
|
remark: f.remark ?? '',
|
|
74
|
-
})),
|
|
75
|
-
listGroups: async () => Array.from(this.
|
|
74
|
+
}))),
|
|
75
|
+
listGroups: async (request = {}) => createEndpointOperation(request).run(() => Array.from(this.gl.values()).map((g) => ({
|
|
76
76
|
group_id: g.group_id,
|
|
77
77
|
name: g.group_name,
|
|
78
|
-
})),
|
|
79
|
-
listChannels: async () => {
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
}))),
|
|
79
|
+
listChannels: async (request = {}) => {
|
|
80
|
+
const operation = createEndpointOperation(request);
|
|
81
|
+
await operation.run(() => this.#guildCatalog.syncAll(this));
|
|
82
|
+
return operation.run(() => this.#guildCatalog.getGuildChannelList());
|
|
82
83
|
},
|
|
83
|
-
listGroupMembers: async (groupId) => Array.from((await this.
|
|
84
|
-
listRequests: () => this.#listPendingRequests(),
|
|
85
|
-
approveRequest:
|
|
86
|
-
rejectRequest:
|
|
87
|
-
kickGroupMember: async (groupId, userId) => {
|
|
88
|
-
|
|
84
|
+
listGroupMembers: async ({ groupId, signal }) => Array.from((await createEndpointOperation({ signal }).run(() => this.getGroupMemberList(Number(groupId)))).values()),
|
|
85
|
+
listRequests: (request = {}) => createEndpointOperation(request).run(() => this.#listPendingRequests()),
|
|
86
|
+
approveRequest: request => this.#approveRequest(request),
|
|
87
|
+
rejectRequest: request => this.#rejectRequest(request),
|
|
88
|
+
kickGroupMember: async ({ groupId, userId, signal }) => {
|
|
89
|
+
const operation = createEndpointOperation({ signal });
|
|
90
|
+
await operation.run(() => this.setGroupKick(Number(groupId), Number(userId)));
|
|
89
91
|
},
|
|
90
|
-
muteGroupMember: async (groupId, userId, duration) => {
|
|
91
|
-
|
|
92
|
+
muteGroupMember: async ({ groupId, userId, durationSeconds: duration, signal }) => {
|
|
93
|
+
const operation = createEndpointOperation({ signal });
|
|
94
|
+
await operation.run(() => this.setGroupBan(Number(groupId), Number(userId), duration));
|
|
92
95
|
},
|
|
93
|
-
setGroupAdmin: async (groupId, userId, enabled) => {
|
|
94
|
-
|
|
96
|
+
setGroupAdmin: async ({ groupId, userId, enabled, signal }) => {
|
|
97
|
+
const operation = createEndpointOperation({ signal });
|
|
98
|
+
await operation.run(() => this.setGroupAdmin(Number(groupId), Number(userId), enabled));
|
|
95
99
|
},
|
|
96
|
-
deleteFriend: async (userId) => {
|
|
97
|
-
|
|
100
|
+
deleteFriend: async ({ userId, signal }) => {
|
|
101
|
+
const operation = createEndpointOperation({ signal });
|
|
102
|
+
await operation.run(() => this.deleteFriend(Number(userId)));
|
|
98
103
|
},
|
|
99
|
-
sendGroupNotice: async (groupId, content) => {
|
|
100
|
-
|
|
104
|
+
sendGroupNotice: async ({ groupId, content, signal }) => {
|
|
105
|
+
const operation = createEndpointOperation({ signal });
|
|
106
|
+
if (await operation.run(() => this.sendGroupNotice(Number(groupId), content)) === false) {
|
|
101
107
|
throw new Error('QQ did not confirm the group notice');
|
|
102
108
|
}
|
|
103
109
|
},
|
|
104
110
|
});
|
|
105
|
-
control = Object.freeze({
|
|
106
|
-
recall: async (message) => {
|
|
111
|
+
#control = Object.freeze({
|
|
112
|
+
recall: async ({ message, signal }) => {
|
|
107
113
|
if (!message.id || message.id.startsWith('outbound:'))
|
|
108
114
|
return;
|
|
109
|
-
if (!await this.
|
|
115
|
+
if (!await createEndpointOperation({ signal }).run(() => this.deleteMsg(message.id)))
|
|
110
116
|
throw new Error('QQ did not confirm message recall');
|
|
111
117
|
},
|
|
112
|
-
addReaction: async (message, emoji) => {
|
|
118
|
+
addReaction: async ({ message, emoji, signal }) => {
|
|
113
119
|
const target = resolveIcqqGroupReactionTarget(message);
|
|
114
120
|
if (!target)
|
|
115
121
|
return null;
|
|
116
122
|
try {
|
|
117
123
|
// Do not await protocol ACK — packet timeout must not stall the AI reply.
|
|
118
|
-
|
|
124
|
+
await createEndpointOperation({ signal }).run(() => {
|
|
125
|
+
this.#ignoreReactionAck('add', this.pickGroup(target.groupId).setReaction(target.seq, emoji));
|
|
126
|
+
});
|
|
119
127
|
}
|
|
120
128
|
catch (error) {
|
|
129
|
+
if (error instanceof EndpointDeliveryError && error.code === 'endpoint_inactive')
|
|
130
|
+
throw error;
|
|
121
131
|
this.#logger.debug(formatCompact({
|
|
122
132
|
op: 'reaction_add_failed',
|
|
123
133
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -125,14 +135,18 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
125
135
|
}
|
|
126
136
|
return emoji;
|
|
127
137
|
},
|
|
128
|
-
removeReaction: async (message, reactionId) => {
|
|
138
|
+
removeReaction: async ({ message, reactionId, signal }) => {
|
|
129
139
|
const target = resolveIcqqGroupReactionTarget(message);
|
|
130
140
|
if (!target)
|
|
131
141
|
return;
|
|
132
142
|
try {
|
|
133
|
-
|
|
143
|
+
await createEndpointOperation({ signal }).run(() => {
|
|
144
|
+
this.#ignoreReactionAck('remove', this.pickGroup(target.groupId).delReaction(target.seq, reactionId));
|
|
145
|
+
});
|
|
134
146
|
}
|
|
135
147
|
catch (error) {
|
|
148
|
+
if (error instanceof EndpointDeliveryError && error.code === 'endpoint_inactive')
|
|
149
|
+
throw error;
|
|
136
150
|
this.#logger.debug(formatCompact({
|
|
137
151
|
op: 'reaction_remove_failed',
|
|
138
152
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -141,11 +155,19 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
141
155
|
},
|
|
142
156
|
});
|
|
143
157
|
constructor(options) {
|
|
144
|
-
super();
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this
|
|
158
|
+
super(Number(options.config.id), resolveNativeClientConfig(options.config), (operation, error) => this.#handleBackgroundFailure(operation, error));
|
|
159
|
+
const name = () => this.endpointName;
|
|
160
|
+
const state = () => this.#transportState;
|
|
161
|
+
const content = () => this.#content;
|
|
162
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
163
|
+
start: signal => this.#startFramework(signal), open: () => this.#openFramework(), close: () => this.#closeFramework(), stop: () => this.#stopFramework(),
|
|
164
|
+
send: request => this.#sendFramework(request), get name() { return name(); },
|
|
165
|
+
get transportState() { return state(); },
|
|
166
|
+
management: this.#management, control: this.#control,
|
|
167
|
+
get content() { return content(); },
|
|
168
|
+
});
|
|
169
|
+
this.#contentResolver = new IcqqContentResolver(this);
|
|
170
|
+
this.#content = this.#contentResolver.port;
|
|
149
171
|
this.#logger = getAdapterLogger('icqq', options.config.id);
|
|
150
172
|
this.#options = options;
|
|
151
173
|
this.endpointName = options.config.id;
|
|
@@ -153,14 +175,14 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
153
175
|
endpointId: this.endpointName,
|
|
154
176
|
state: () => ({
|
|
155
177
|
endpointId: this.endpointName,
|
|
156
|
-
transportState: this
|
|
157
|
-
online: this.
|
|
178
|
+
transportState: this.#transportState,
|
|
179
|
+
online: this.isOnline(),
|
|
158
180
|
pending: this.#pendingLoginTasks(),
|
|
159
181
|
}),
|
|
160
182
|
pending: () => this.#pendingLoginTasks(),
|
|
161
|
-
relogin: () => this.relogin(),
|
|
162
|
-
submit: (taskId, value) => this.#options.loginAssist.submit(taskId, value),
|
|
163
|
-
cancel: (taskId, reason) => this.#options.loginAssist.cancel(taskId, reason),
|
|
183
|
+
relogin: async () => { this.#options.signal?.throwIfAborted(); await this.relogin(); },
|
|
184
|
+
submit: (taskId, value) => !this.#options.signal?.aborted && this.#options.loginAssist.submit(taskId, value, this.#options.endpointIdentity),
|
|
185
|
+
cancel: (taskId, reason) => !this.#options.signal?.aborted && this.#options.loginAssist.cancel(taskId, reason, this.#options.endpointIdentity),
|
|
164
186
|
});
|
|
165
187
|
}
|
|
166
188
|
#handleBackgroundFailure(operation, error) {
|
|
@@ -173,13 +195,13 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
173
195
|
: typeof error === 'string' ? error : 'Unknown ICQQ SDK background failure',
|
|
174
196
|
...(error && typeof error === 'object' && 'code' in error && typeof error.code === 'number' ? { code: error.code } : {}),
|
|
175
197
|
}));
|
|
176
|
-
if (operation === 'sso-heartbeat' && !this.
|
|
198
|
+
if (operation === 'sso-heartbeat' && !this.isOnline())
|
|
177
199
|
return;
|
|
178
200
|
if (operation === 'login' && !this.#everOnline)
|
|
179
201
|
return;
|
|
180
202
|
// Delegate to the SDK's existing transport termination, offline event and
|
|
181
203
|
// reconn_interval policy. No account-level logout or second retry timer.
|
|
182
|
-
this.
|
|
204
|
+
this.emit('internal.error.network', -2, `ICQQ ${operation} failed`);
|
|
183
205
|
}
|
|
184
206
|
/** Force a login attempt (manual recovery command). */
|
|
185
207
|
relogin() {
|
|
@@ -198,7 +220,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
198
220
|
void task.catch(() => undefined).finally(() => {
|
|
199
221
|
if (this.#reloginInFlight === task)
|
|
200
222
|
this.#reloginInFlight = undefined;
|
|
201
|
-
if (this.#started && !this.#stopped && !this.#retiring && !this.
|
|
223
|
+
if (this.#started && !this.#stopped && !this.#retiring && !this.isOnline()) {
|
|
202
224
|
this.#scheduleRelogin('relogin_failed');
|
|
203
225
|
}
|
|
204
226
|
});
|
|
@@ -206,10 +228,10 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
206
228
|
}
|
|
207
229
|
async #performRelogin() {
|
|
208
230
|
this.#clearReloginTimer();
|
|
209
|
-
this.
|
|
231
|
+
this.cancelReconnect();
|
|
210
232
|
this.#logger.info(formatCompact({ op: 'icqq_relogin', endpoint: this.endpointName }));
|
|
211
233
|
try {
|
|
212
|
-
await this.
|
|
234
|
+
await this.login(this.#options.config.password);
|
|
213
235
|
}
|
|
214
236
|
catch (error) {
|
|
215
237
|
this.#logger.warn(formatCompact({
|
|
@@ -251,8 +273,15 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
251
273
|
this.#reloginTimer = undefined;
|
|
252
274
|
}
|
|
253
275
|
#pendingLoginTasks() {
|
|
276
|
+
if (this.#options.signal?.aborted)
|
|
277
|
+
return [];
|
|
254
278
|
return this.#options.loginAssist.listPending()
|
|
255
|
-
.filter((task) => task.adapter === 'icqq' &&
|
|
279
|
+
.filter((task) => task.adapter === 'icqq' && (this.#options.endpointIdentity
|
|
280
|
+
? task.endpointIdentity?.canonicalId === this.#options.endpointIdentity.canonicalId
|
|
281
|
+
&& task.endpointIdentity.instanceId === this.#options.endpointIdentity.instanceId
|
|
282
|
+
&& task.endpointIdentity.epoch === this.#options.endpointIdentity.epoch
|
|
283
|
+
&& task.endpointIdentity.incarnationId === this.#options.endpointIdentity.incarnationId
|
|
284
|
+
: task.endpointKey === this.endpointName));
|
|
256
285
|
}
|
|
257
286
|
#notifyOffline(reason, willRetry) {
|
|
258
287
|
const notifier = this.#options.notifier;
|
|
@@ -331,18 +360,18 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
331
360
|
}));
|
|
332
361
|
});
|
|
333
362
|
}
|
|
334
|
-
get transportState() {
|
|
363
|
+
get #transportState() {
|
|
335
364
|
if (this.#stopped || this.#retiring)
|
|
336
365
|
return 'stopped';
|
|
337
366
|
if (!this.#started)
|
|
338
367
|
return 'idle';
|
|
339
|
-
if (this.
|
|
368
|
+
if (this.isOnline())
|
|
340
369
|
return 'open';
|
|
341
370
|
if (!this.#everOnline)
|
|
342
371
|
return 'connecting';
|
|
343
372
|
return this.#options.config.autoReconnect ? 'reconnecting' : 'closed';
|
|
344
373
|
}
|
|
345
|
-
async
|
|
374
|
+
async #startFramework(signal) {
|
|
346
375
|
if (this.#started)
|
|
347
376
|
return;
|
|
348
377
|
signal.throwIfAborted();
|
|
@@ -352,7 +381,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
352
381
|
this.#retirementEpoch += 1;
|
|
353
382
|
this.#retirementArmed = false;
|
|
354
383
|
this.#retiring = false;
|
|
355
|
-
this.
|
|
384
|
+
this.off('system.online');
|
|
356
385
|
this.#started = true;
|
|
357
386
|
let resolveOnline;
|
|
358
387
|
const onlineReady = new Promise((resolve) => {
|
|
@@ -363,16 +392,16 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
363
392
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
364
393
|
try {
|
|
365
394
|
this.#bindClientEvents();
|
|
366
|
-
await raceAbort(Promise.resolve(this.
|
|
395
|
+
await raceAbort(Promise.resolve(this.login(this.#options.config.password)), signal);
|
|
367
396
|
await onlineReady;
|
|
368
397
|
signal.throwIfAborted();
|
|
369
398
|
await this.#pullPendingSystemMessages(signal);
|
|
370
399
|
signal.throwIfAborted();
|
|
371
|
-
this.#logger.info(`connected (direct) | friends: ${this.
|
|
400
|
+
this.#logger.info(`connected (direct) | friends: ${this.fl.size} | groups: ${this.gl.size}`);
|
|
372
401
|
}
|
|
373
402
|
catch (error) {
|
|
374
403
|
this.#notifyOffline('start_failed', false);
|
|
375
|
-
await this
|
|
404
|
+
await this.#stopFramework();
|
|
376
405
|
this.#logger.debug(`Failed to connect ICQQ client: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
377
406
|
throw error;
|
|
378
407
|
}
|
|
@@ -382,11 +411,11 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
382
411
|
this.#resolveStartOnline = undefined;
|
|
383
412
|
}
|
|
384
413
|
}
|
|
385
|
-
|
|
414
|
+
#openFramework() {
|
|
386
415
|
if (this.#retirementArmed) {
|
|
387
416
|
this.#retirementEpoch += 1;
|
|
388
417
|
this.#retirementArmed = false;
|
|
389
|
-
this.
|
|
418
|
+
this.off('system.online');
|
|
390
419
|
this.#bindSystemOnlineEvent();
|
|
391
420
|
}
|
|
392
421
|
this.#retiring = false;
|
|
@@ -398,15 +427,15 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
398
427
|
});
|
|
399
428
|
}
|
|
400
429
|
}
|
|
401
|
-
async
|
|
430
|
+
async #closeFramework() {
|
|
402
431
|
this.#assertExternalLifecycle('close');
|
|
403
432
|
this.#armRetirementFence();
|
|
404
433
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
405
434
|
}
|
|
406
|
-
async
|
|
435
|
+
async #stopFramework() {
|
|
436
|
+
this.#assertExternalLifecycle('stop');
|
|
407
437
|
this.#stopped = true;
|
|
408
438
|
this.#clearReloginTimer();
|
|
409
|
-
this.#assertExternalLifecycle('stop');
|
|
410
439
|
this.#armRetirementFence();
|
|
411
440
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
412
441
|
this.#resolveStartOnline?.();
|
|
@@ -418,7 +447,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
418
447
|
// The retirement guard owns this matcher until a late login handshake
|
|
419
448
|
// has finished. start()/open() replace it with the normal listener.
|
|
420
449
|
if (event !== 'system.online')
|
|
421
|
-
this.
|
|
450
|
+
this.off(event);
|
|
422
451
|
}
|
|
423
452
|
// `Client.logout()` sends an account-level unregister packet before it
|
|
424
453
|
// closes this transport. During config HMR the replacement endpoint for
|
|
@@ -426,7 +455,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
426
455
|
// also closes the replacement session. Endpoint ownership is transport
|
|
427
456
|
// scoped: retire only this Client's TCP connection.
|
|
428
457
|
try {
|
|
429
|
-
this.
|
|
458
|
+
this.terminate();
|
|
430
459
|
}
|
|
431
460
|
catch { /* ignore */ }
|
|
432
461
|
this.#inboundDeduper.clear();
|
|
@@ -444,28 +473,39 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
444
473
|
const retirementEpoch = ++this.#retirementEpoch;
|
|
445
474
|
// ICQQ owns reconnect through this per-Client timer. Clear it before any
|
|
446
475
|
// asynchronous drain so a queued callback cannot enter while close waits.
|
|
447
|
-
this.
|
|
476
|
+
this.cancelReconnect();
|
|
448
477
|
// A login callback may already be inside the SDK handshake, which has no
|
|
449
478
|
// abort API. Replace the normal online listener with an epoch-owned guard
|
|
450
479
|
// before draining inbound work. Transport termination is account-local;
|
|
451
480
|
// logout would also unregister an HMR replacement using the same UIN.
|
|
452
|
-
this.
|
|
453
|
-
this.
|
|
481
|
+
this.off('system.online');
|
|
482
|
+
this.on('system.online', () => {
|
|
454
483
|
if (this.#retirementEpoch !== retirementEpoch || !this.#retiring)
|
|
455
484
|
return;
|
|
456
485
|
try {
|
|
457
|
-
this.
|
|
486
|
+
this.terminate();
|
|
458
487
|
}
|
|
459
488
|
catch { /* already closed */ }
|
|
460
489
|
});
|
|
461
490
|
}
|
|
462
|
-
async
|
|
491
|
+
async #sendFramework({ conversation, payload, signal }) {
|
|
463
492
|
if (!this.#started)
|
|
464
493
|
throw new EndpointDeliveryError('endpoint_disconnected', 'icqq endpoint 未连接', 'not_sent');
|
|
494
|
+
const operation = createEndpointOperation({ signal });
|
|
465
495
|
const mediaMode = resolveIcqqOutboundMediaMode(this.#options.config);
|
|
466
|
-
const prepared = isIcqqSegmentPayload(payload)
|
|
496
|
+
const prepared = await operation.prepare(() => isIcqqSegmentPayload(payload)
|
|
467
497
|
? prepareIcqqOutboundMedia(normalizeIcqqImageMedia(payload), mediaMode)
|
|
468
|
-
: { content: payload, dispose() { } };
|
|
498
|
+
: { content: payload, dispose() { } });
|
|
499
|
+
// Cancellation ends our wait, not the SDK's use of temporary media. The actual native
|
|
500
|
+
// promises own that resource until they settle, including a late rejection after cancellation.
|
|
501
|
+
const pendingSends = new Set();
|
|
502
|
+
const sendNative = (target, message) => {
|
|
503
|
+
const task = this.#sendNative(target, message);
|
|
504
|
+
pendingSends.add(task);
|
|
505
|
+
const settled = () => { pendingSends.delete(task); };
|
|
506
|
+
void task.then(settled, settled);
|
|
507
|
+
return task;
|
|
508
|
+
};
|
|
469
509
|
try {
|
|
470
510
|
// Standalone elements (share / file / …) are split into ordered messages.
|
|
471
511
|
const messages = formatOutboundMessageParts(prepared.content);
|
|
@@ -475,7 +515,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
475
515
|
const preview = formatIcqqSendablePreview(message);
|
|
476
516
|
let result;
|
|
477
517
|
try {
|
|
478
|
-
result = await
|
|
518
|
+
result = await operation.run(() => sendNative(parsed, message));
|
|
479
519
|
}
|
|
480
520
|
catch (error) {
|
|
481
521
|
this.#logger.warn(formatCompact({
|
|
@@ -503,33 +543,39 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
503
543
|
return { parts };
|
|
504
544
|
}
|
|
505
545
|
finally {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
546
|
+
const disposeMedia = () => {
|
|
547
|
+
try {
|
|
548
|
+
prepared.dispose();
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
// Temporary-file cleanup cannot change an observed send result.
|
|
552
|
+
this.#logger.warn(formatCompact({ op: 'icqq_media_cleanup_failed', error: describeUnknownError(error) }));
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
if (pendingSends.size)
|
|
556
|
+
void Promise.allSettled([...pendingSends]).then(disposeMedia);
|
|
557
|
+
else
|
|
558
|
+
disposeMedia();
|
|
513
559
|
}
|
|
514
560
|
}
|
|
515
561
|
async #sendNative(target, message) {
|
|
516
562
|
if (isIcqqShareElement(message)) {
|
|
517
563
|
switch (target.kind) {
|
|
518
564
|
case 'private': {
|
|
519
|
-
const friend = this.
|
|
565
|
+
const friend = this.pickFriend(target.userId);
|
|
520
566
|
await friend.share(message);
|
|
521
567
|
return {};
|
|
522
568
|
}
|
|
523
569
|
case 'group': {
|
|
524
|
-
const group = this.
|
|
570
|
+
const group = this.pickGroup(target.groupId);
|
|
525
571
|
await group.share(message);
|
|
526
572
|
return {};
|
|
527
573
|
}
|
|
528
574
|
case 'temp':
|
|
529
|
-
await this.
|
|
575
|
+
await this.pickMember(target.groupId, target.userId).share(message);
|
|
530
576
|
return {};
|
|
531
577
|
case 'channel': {
|
|
532
|
-
const channel = this.
|
|
578
|
+
const channel = this.pickGuild(target.guildId).channels.get(target.channelId);
|
|
533
579
|
if (!channel) {
|
|
534
580
|
throw new TypeError(`ICQQ guild channel is unavailable: ${target.guildId}/${target.channelId}`);
|
|
535
581
|
}
|
|
@@ -541,11 +587,11 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
541
587
|
if (isIcqqFileElement(message)) {
|
|
542
588
|
switch (target.kind) {
|
|
543
589
|
case 'private': {
|
|
544
|
-
await this.
|
|
590
|
+
await this.pickFriend(target.userId).sendFile(message.file, message.name);
|
|
545
591
|
return {};
|
|
546
592
|
}
|
|
547
593
|
case 'group': {
|
|
548
|
-
await this.
|
|
594
|
+
await this.pickGroup(target.groupId).sendFile(message.file, '/', message.name);
|
|
549
595
|
return {};
|
|
550
596
|
}
|
|
551
597
|
case 'temp':
|
|
@@ -555,13 +601,13 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
555
601
|
}
|
|
556
602
|
switch (target.kind) {
|
|
557
603
|
case 'private':
|
|
558
|
-
return this.
|
|
604
|
+
return this.sendPrivateMsg(target.userId, message);
|
|
559
605
|
case 'group':
|
|
560
|
-
return this.
|
|
606
|
+
return this.sendGroupMsg(target.groupId, message);
|
|
561
607
|
case 'temp':
|
|
562
|
-
return this.
|
|
608
|
+
return this.sendTempMsg(target.groupId, target.userId, message);
|
|
563
609
|
case 'channel':
|
|
564
|
-
return this.
|
|
610
|
+
return this.sendGuildMsg(target.guildId, target.channelId, message);
|
|
565
611
|
}
|
|
566
612
|
}
|
|
567
613
|
admit(msg) {
|
|
@@ -574,7 +620,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
574
620
|
this.#inflightInbound.add(settlement);
|
|
575
621
|
let operation;
|
|
576
622
|
try {
|
|
577
|
-
operation = this.#inboundOwner.run(owner, run);
|
|
623
|
+
operation = this.#inboundOwner.run(owner, () => this[endpointSpec].events.run(run));
|
|
578
624
|
}
|
|
579
625
|
catch (error) {
|
|
580
626
|
this.#inflightInbound.delete(settlement);
|
|
@@ -623,7 +669,9 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
623
669
|
+ (msg.sender ? ` from ${msg.sender.name ?? msg.sender.id}` : '')
|
|
624
670
|
+ (mentioned ? ' (mentioned)' : '')
|
|
625
671
|
+ ` | ${truncatePreview(msg.content, 80)}`);
|
|
626
|
-
await this.emit('message.receive', {
|
|
672
|
+
await this[endpointSpec].events.emit('message.receive', {
|
|
673
|
+
event: msg.event,
|
|
674
|
+
platformSentAt: Number.isFinite(msg.platformSentAt) ? msg.platformSentAt : undefined,
|
|
627
675
|
conversation,
|
|
628
676
|
message: { conversation, id: msg.id },
|
|
629
677
|
content: msg.content,
|
|
@@ -649,77 +697,69 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
649
697
|
#bindClientEvents() {
|
|
650
698
|
this.#bindNativeEvents();
|
|
651
699
|
const safe = (label, fn) => {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
catch (error) {
|
|
656
|
-
this.#logger.warn(formatCompact({
|
|
657
|
-
op: 'icqq_event_handler',
|
|
658
|
-
endpoint: this.endpointName,
|
|
659
|
-
event: label,
|
|
660
|
-
error: error instanceof Error ? error.message : String(error),
|
|
661
|
-
}));
|
|
662
|
-
}
|
|
700
|
+
void this[endpointSpec].events.run(fn).catch(() => {
|
|
701
|
+
this.#logger.debug(formatCompact({ op: 'icqq_event_not_admitted', endpoint: this.endpointName, event: label }));
|
|
702
|
+
});
|
|
663
703
|
};
|
|
664
|
-
this.
|
|
704
|
+
this.on('message.private', (event) => {
|
|
665
705
|
this.#startMessageEvent('message.private', event);
|
|
666
706
|
});
|
|
667
|
-
this.
|
|
707
|
+
this.on('message.group', (event) => {
|
|
668
708
|
this.#startMessageEvent('message.group', event);
|
|
669
709
|
});
|
|
670
|
-
this.
|
|
671
|
-
void this.#handleGuildEvent(event).catch((error) => {
|
|
710
|
+
this.on('message.guild', (event) => {
|
|
711
|
+
void this.#trackInbound(() => this.#handleGuildEvent(event)).catch((error) => {
|
|
672
712
|
this.#logInboundHandlerFailure('message.guild', error);
|
|
673
713
|
});
|
|
674
714
|
});
|
|
675
|
-
this.
|
|
715
|
+
this.on('request.friend', (event) => safe('request.friend', () => {
|
|
676
716
|
this.#onRequestEvent(serializeIcqqEvent(event));
|
|
677
717
|
}));
|
|
678
|
-
this.
|
|
718
|
+
this.on('request.group', (event) => safe('request.group', () => {
|
|
679
719
|
this.#onRequestEvent(serializeIcqqEvent(event));
|
|
680
720
|
}));
|
|
681
|
-
this.
|
|
721
|
+
this.on('notice.friend', (event) => safe('notice.friend', () => {
|
|
682
722
|
this.#onNoticeEvent(serializeIcqqEvent(event));
|
|
683
723
|
}));
|
|
684
|
-
this.
|
|
724
|
+
this.on('notice.group', (event) => safe('notice.group', () => {
|
|
685
725
|
this.#onNoticeEvent(serializeIcqqEvent(event));
|
|
686
726
|
}));
|
|
687
727
|
this.#bindSystemOnlineEvent();
|
|
688
|
-
this.
|
|
728
|
+
this.on('system.offline', (event) => {
|
|
689
729
|
this.#logSystemEvent('offline', event);
|
|
690
730
|
this.#notifyOffline('offline', this.#options.config.autoReconnect);
|
|
691
731
|
});
|
|
692
|
-
this.
|
|
732
|
+
this.on('system.offline.network', (event) => {
|
|
693
733
|
this.#logSystemEvent('offline.network', event);
|
|
694
734
|
this.#notifyOffline('network', this.#options.config.autoReconnect);
|
|
695
735
|
});
|
|
696
|
-
this.
|
|
736
|
+
this.on('system.offline.kickoff', (event) => {
|
|
697
737
|
this.#logSystemEvent('offline.kickoff', event);
|
|
698
738
|
this.#notifyOffline('kickoff', this.#options.config.autoReconnect);
|
|
699
739
|
this.#scheduleRelogin('kickoff');
|
|
700
740
|
});
|
|
701
|
-
this.
|
|
741
|
+
this.on('system.login.qrcode', (event) => {
|
|
702
742
|
this.#logger.info('icqq 收到二维码登录事件,等待 LoginAssist / 终端确认后继续');
|
|
703
743
|
this.#handleLoginChallenge('qrcode', event);
|
|
704
744
|
});
|
|
705
|
-
this.
|
|
745
|
+
this.on('system.login.device', (event) => {
|
|
706
746
|
this.#handleLoginChallenge('device', event);
|
|
707
747
|
});
|
|
708
|
-
this.
|
|
748
|
+
this.on('system.login.slider', (event) => {
|
|
709
749
|
this.#handleLoginChallenge('slider', event);
|
|
710
750
|
});
|
|
711
|
-
this.
|
|
751
|
+
this.on('system.login.error', (event) => {
|
|
712
752
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'login_error');
|
|
713
753
|
this.#logSystemEvent('login.error', event);
|
|
714
754
|
this.#notifyOffline('login_error', this.#options.config.autoReconnect);
|
|
715
755
|
this.#scheduleRelogin('login_error');
|
|
716
756
|
});
|
|
717
|
-
this.
|
|
757
|
+
this.on('system.login.auth', (event) => {
|
|
718
758
|
this.#handleLoginChallenge('auth', event);
|
|
719
759
|
});
|
|
720
760
|
}
|
|
721
761
|
#bindSystemOnlineEvent() {
|
|
722
|
-
this.
|
|
762
|
+
this.on('system.online', (event) => {
|
|
723
763
|
if (this.#retiring)
|
|
724
764
|
return;
|
|
725
765
|
this.#everOnline = true;
|
|
@@ -731,7 +771,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
731
771
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'online');
|
|
732
772
|
try {
|
|
733
773
|
// transport 重连不清除(账号不变);stop/retire 由基类撤销登记。上报失败不影响上线流程。
|
|
734
|
-
this.reportSelfUserId(resolveIcqqSelfUserId(this
|
|
774
|
+
this[endpointSpec].reportSelfUserId(resolveIcqqSelfUserId(this));
|
|
735
775
|
}
|
|
736
776
|
catch (error) {
|
|
737
777
|
this.#logger.warn(formatCompact({
|
|
@@ -756,7 +796,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
756
796
|
#bindNativeEvents() {
|
|
757
797
|
this.#disposeNativeEvents?.();
|
|
758
798
|
let matchedName;
|
|
759
|
-
const registration = this.
|
|
799
|
+
const registration = this.on((eventName) => {
|
|
760
800
|
matchedName = typeof eventName === 'string' && !eventName.startsWith('internal.')
|
|
761
801
|
? eventName
|
|
762
802
|
: undefined;
|
|
@@ -769,7 +809,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
769
809
|
this.#disposeNativeEvents = () => registration();
|
|
770
810
|
}
|
|
771
811
|
#emitNativeEvent(name, event) {
|
|
772
|
-
void this.
|
|
812
|
+
void this[endpointSpec].events.platform(name, event).catch((error) => {
|
|
773
813
|
this.#logger.warn(formatCompact({
|
|
774
814
|
op: 'icqq_platform_event_failed',
|
|
775
815
|
endpoint: this.endpointName,
|
|
@@ -779,6 +819,8 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
779
819
|
});
|
|
780
820
|
}
|
|
781
821
|
#handleLoginChallenge(step, event) {
|
|
822
|
+
if (this.#retiring || this.#stopped || this.#options.signal?.aborted)
|
|
823
|
+
return;
|
|
782
824
|
const payload = serializeIcqqEvent(event) ?? (event && typeof event === 'object'
|
|
783
825
|
? event
|
|
784
826
|
: null);
|
|
@@ -788,10 +830,11 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
788
830
|
void runIcqqLoginAssistStep({
|
|
789
831
|
assist: this.#options.loginAssist,
|
|
790
832
|
locale: this.#options.locale,
|
|
791
|
-
client: this
|
|
833
|
+
client: this,
|
|
792
834
|
adapter: 'icqq',
|
|
793
835
|
endpointKey: this.endpointName,
|
|
794
836
|
owner: this.#loginAssistOwner,
|
|
837
|
+
signal: this.#options.signal, endpointIdentity: this.#options.endpointIdentity,
|
|
795
838
|
logger: this.#logger,
|
|
796
839
|
step,
|
|
797
840
|
event: payload ?? event,
|
|
@@ -810,14 +853,14 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
810
853
|
const postType = String(payload.post_type ?? '');
|
|
811
854
|
const isRequest = postType === 'request' || postType.startsWith('request.');
|
|
812
855
|
const flag = payload.flag != null ? String(payload.flag) : '';
|
|
813
|
-
void receiveOneBotLikeSideEvent((name, payload) => this.emit(name, payload), {
|
|
856
|
+
void receiveOneBotLikeSideEvent((name, payload) => this[endpointSpec].events.emit(name, payload), {
|
|
814
857
|
adapter: 'icqq',
|
|
815
858
|
endpointKey: this.endpointName,
|
|
816
859
|
platform: 'icqq',
|
|
817
860
|
raw: payload,
|
|
818
861
|
...(isRequest ? {
|
|
819
|
-
approve: (id, remark) => this.#approveRequest(flag || id, remark),
|
|
820
|
-
reject: (id, reason) => this.#rejectRequest(flag || id, reason),
|
|
862
|
+
approve: (id, remark) => this.#approveRequest({ requestId: flag || id, remark }),
|
|
863
|
+
reject: (id, reason) => this.#rejectRequest({ requestId: flag || id, reason }),
|
|
821
864
|
} : {}),
|
|
822
865
|
}).catch((err) => {
|
|
823
866
|
this.#logger.warn(formatCompact({
|
|
@@ -829,7 +872,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
829
872
|
});
|
|
830
873
|
}
|
|
831
874
|
#dispatchIcqqSystemSideEvent(type, payload) {
|
|
832
|
-
void this.emit('system.receive', buildSystem(payload ?? {}, {
|
|
875
|
+
void this[endpointSpec].events.emit('system.receive', buildSystem(payload ?? {}, {
|
|
833
876
|
id: `system:${this.endpointName}:${type}:${Date.now()}`,
|
|
834
877
|
clientAdapter: 'icqq',
|
|
835
878
|
endpointId: this.endpointName,
|
|
@@ -921,6 +964,8 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
921
964
|
}));
|
|
922
965
|
}
|
|
923
966
|
await this.#deliverInbound({
|
|
967
|
+
event,
|
|
968
|
+
platformSentAt: normalized.timestampMs,
|
|
924
969
|
id: normalized.messageId,
|
|
925
970
|
conversation,
|
|
926
971
|
content: formatInboundContent(normalized.rawMessage),
|
|
@@ -951,13 +996,13 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
951
996
|
return null;
|
|
952
997
|
if (data.message_type === 'group' || data.group_id != null) {
|
|
953
998
|
const groupId = Number(data.group_id);
|
|
954
|
-
return Number.isFinite(groupId) && groupId > 0 ? this.
|
|
999
|
+
return Number.isFinite(groupId) && groupId > 0 ? this.pickGroup(groupId) : null;
|
|
955
1000
|
}
|
|
956
1001
|
const sourceGroupId = Number(data.sender?.group_id);
|
|
957
1002
|
if (Number.isFinite(sourceGroupId) && sourceGroupId > 0) {
|
|
958
|
-
return this.
|
|
1003
|
+
return this.pickMember(sourceGroupId, userId);
|
|
959
1004
|
}
|
|
960
|
-
return this.
|
|
1005
|
+
return this.pickFriend(userId);
|
|
961
1006
|
}
|
|
962
1007
|
#logInboundHandlerFailure(event, error) {
|
|
963
1008
|
this.#logger.warn(formatCompact({
|
|
@@ -991,6 +1036,9 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
991
1036
|
return;
|
|
992
1037
|
}
|
|
993
1038
|
await this.admit({
|
|
1039
|
+
event,
|
|
1040
|
+
platformSentAt: typeof normalized.raw.time === 'number' && Number.isFinite(normalized.raw.time)
|
|
1041
|
+
? normalized.timestampMs : undefined,
|
|
994
1042
|
id: normalized.messageId,
|
|
995
1043
|
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
996
1044
|
channelType: 'channel',
|
|
@@ -1023,7 +1071,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
1023
1071
|
this.#dispatchIcqqSideEvent(payload);
|
|
1024
1072
|
}
|
|
1025
1073
|
async #listPendingRequests() {
|
|
1026
|
-
const messages = await this.
|
|
1074
|
+
const messages = await this.getSystemMsg();
|
|
1027
1075
|
const rows = [];
|
|
1028
1076
|
for (const raw of messages) {
|
|
1029
1077
|
const msg = raw;
|
|
@@ -1051,7 +1099,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
1051
1099
|
}
|
|
1052
1100
|
async #pullPendingSystemMessages(signal) {
|
|
1053
1101
|
try {
|
|
1054
|
-
const messages = await raceAbort(Promise.resolve(this.
|
|
1102
|
+
const messages = await raceAbort(Promise.resolve(this.getSystemMsg()), signal);
|
|
1055
1103
|
for (const raw of messages) {
|
|
1056
1104
|
signal.throwIfAborted();
|
|
1057
1105
|
const msg = raw;
|
|
@@ -1081,8 +1129,9 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
1081
1129
|
}));
|
|
1082
1130
|
}
|
|
1083
1131
|
}
|
|
1084
|
-
async #approveRequest(id, remark) {
|
|
1085
|
-
const
|
|
1132
|
+
async #approveRequest({ requestId: id, remark, signal }) {
|
|
1133
|
+
const operation = createEndpointOperation({ signal });
|
|
1134
|
+
const messages = await operation.run(() => this.getSystemMsg());
|
|
1086
1135
|
const target = messages.find((m) => {
|
|
1087
1136
|
const msg = m;
|
|
1088
1137
|
return msg.flag === id || (msg.seq != null && String(msg.seq) === id);
|
|
@@ -1090,15 +1139,17 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
1090
1139
|
if (!target?.flag) {
|
|
1091
1140
|
throw new Error(`icqq 未找到待处理请求: ${id}`);
|
|
1092
1141
|
}
|
|
1142
|
+
const flag = target.flag;
|
|
1093
1143
|
if (target.request_type === 'friend') {
|
|
1094
|
-
await this.
|
|
1144
|
+
await operation.run(() => this.setFriendAddRequest(flag, true, remark));
|
|
1095
1145
|
}
|
|
1096
1146
|
else {
|
|
1097
|
-
await this.
|
|
1147
|
+
await operation.run(() => this.setGroupAddRequest(flag, true));
|
|
1098
1148
|
}
|
|
1099
1149
|
}
|
|
1100
|
-
async #rejectRequest(id, reason) {
|
|
1101
|
-
const
|
|
1150
|
+
async #rejectRequest({ requestId: id, reason, signal }) {
|
|
1151
|
+
const operation = createEndpointOperation({ signal });
|
|
1152
|
+
const messages = await operation.run(() => this.getSystemMsg());
|
|
1102
1153
|
const target = messages.find((m) => {
|
|
1103
1154
|
const msg = m;
|
|
1104
1155
|
return msg.flag === id || (msg.seq != null && String(msg.seq) === id);
|
|
@@ -1106,11 +1157,12 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
1106
1157
|
if (!target?.flag) {
|
|
1107
1158
|
throw new Error(`icqq 未找到待处理请求: ${id}`);
|
|
1108
1159
|
}
|
|
1160
|
+
const flag = target.flag;
|
|
1109
1161
|
if (target.request_type === 'friend') {
|
|
1110
|
-
await this.
|
|
1162
|
+
await operation.run(() => this.setFriendAddRequest(flag, false));
|
|
1111
1163
|
}
|
|
1112
1164
|
else {
|
|
1113
|
-
await this.
|
|
1165
|
+
await operation.run(() => this.setGroupAddRequest(flag, false, reason));
|
|
1114
1166
|
}
|
|
1115
1167
|
}
|
|
1116
1168
|
}
|