@zhin.js/adapter-icqq 1.1.4 → 1.1.5
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 +22 -0
- package/README.md +6 -0
- package/lib/endpoint.d.ts +2 -1
- package/lib/endpoint.js +27 -6
- package/package.json +12 -12
- package/src/endpoint.ts +25 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @zhin.js/adapter-icqq
|
|
2
2
|
|
|
3
|
+
## 1.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b9b3953: Fix adapter connection races, transport health reporting, candidate rollback and delivery acknowledgement boundaries. Separate generation admission from locally observed connectivity, reject unconfirmed message receipts, and mark uncertain delivery results without recommending automatic replay.
|
|
8
|
+
|
|
9
|
+
Class and compact endpoints should expose a `transportState` getter to report physical connection or local listener health. Unobserved endpoints now report an unknown transport and remain offline in diagnostics. Platform stability tiers remain unchanged until real account acceptance evidence is available.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [b9b3953]
|
|
12
|
+
- Updated dependencies [b9b3953]
|
|
13
|
+
- Updated dependencies [b9b3953]
|
|
14
|
+
- Updated dependencies [b9b3953]
|
|
15
|
+
- Updated dependencies [b9b3953]
|
|
16
|
+
- Updated dependencies [b9b3953]
|
|
17
|
+
- Updated dependencies [b9b3953]
|
|
18
|
+
- @zhin.js/adapter@1.1.16
|
|
19
|
+
- @zhin.js/im-contract@1.1.3
|
|
20
|
+
- @zhin.js/core@1.1.40
|
|
21
|
+
- @zhin.js/tool@1.1.3
|
|
22
|
+
- @zhin.js/skill@1.1.3
|
|
23
|
+
- @zhin.js/agent-feature@1.1.3
|
|
24
|
+
|
|
3
25
|
## 1.1.4
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -175,3 +175,9 @@ IM turn 的场景,才使用 `icqqClient.get(context, endpointId)` 显式选择
|
|
|
175
175
|
## License
|
|
176
176
|
|
|
177
177
|
MIT
|
|
178
|
+
|
|
179
|
+
### 发送回执与未知结果
|
|
180
|
+
|
|
181
|
+
发送成功回执只接受平台真实消息 ID。网络超时、断线、缺少消息 ID 的响应会报告 `deliveryUnknown`,框架不会自动重发;手动重发可能重复发送。
|
|
182
|
+
|
|
183
|
+
ICQQ 原生分享和文件 API 可能已经成功,但只返回空值或文件 ID;这类结果不能当作可引用、可撤回的消息 ID,因此报告投递结果未知。平台操作仍只执行一次。文件撤回应使用平台专用文件 API。
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client } from '@icqqjs/icqq';
|
|
2
|
-
import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
+
import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
|
|
3
3
|
import type { EndpointContentPort } from '@zhin.js/adapter';
|
|
4
4
|
import { type LoginAssist } from '@zhin.js/core';
|
|
5
5
|
import type { CapabilityId } from 'zhin.js';
|
|
@@ -20,6 +20,7 @@ export declare class IcqqEndpoint extends Endpoint<Client> {
|
|
|
20
20
|
readonly management: EndpointManagement;
|
|
21
21
|
readonly control: EndpointControl;
|
|
22
22
|
constructor(options: IcqqEndpointOptions);
|
|
23
|
+
get transportState(): EndpointTransportState;
|
|
23
24
|
start(signal: AbortSignal): Promise<void>;
|
|
24
25
|
open(): void;
|
|
25
26
|
close(): Promise<void>;
|
package/lib/endpoint.js
CHANGED
|
@@ -3,6 +3,7 @@ import { inspect } from 'node:util';
|
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
4
|
import { Endpoint, } from 'zhin.js/adapter';
|
|
5
5
|
import { receiveOneBotLikeSideEvent, buildSystem, toCanonicalSegments } from '@zhin.js/core';
|
|
6
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
6
7
|
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
7
8
|
import { runIcqqLoginAssistStep } from './icqq-login-assist.js';
|
|
8
9
|
import { IcqqContentResolver } from './content-resolver.js';
|
|
@@ -49,6 +50,8 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
49
50
|
#open = false;
|
|
50
51
|
#retiring = false;
|
|
51
52
|
#started = false;
|
|
53
|
+
#stopped = false;
|
|
54
|
+
#everOnline = false;
|
|
52
55
|
#retirementEpoch = 0;
|
|
53
56
|
#retirementArmed = false;
|
|
54
57
|
#disposeNativeEvents;
|
|
@@ -144,10 +147,22 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
144
147
|
}));
|
|
145
148
|
});
|
|
146
149
|
}
|
|
150
|
+
get transportState() {
|
|
151
|
+
if (this.#stopped || this.#retiring)
|
|
152
|
+
return 'stopped';
|
|
153
|
+
if (!this.#started)
|
|
154
|
+
return 'idle';
|
|
155
|
+
if (this.client.isOnline())
|
|
156
|
+
return 'open';
|
|
157
|
+
if (!this.#everOnline)
|
|
158
|
+
return 'connecting';
|
|
159
|
+
return this.#options.config.autoReconnect ? 'reconnecting' : 'closed';
|
|
160
|
+
}
|
|
147
161
|
async start(signal) {
|
|
148
162
|
if (this.#started)
|
|
149
163
|
return;
|
|
150
164
|
signal.throwIfAborted();
|
|
165
|
+
this.#stopped = false;
|
|
151
166
|
// Invalidate a guard left by a prior lifecycle epoch before admitting a
|
|
152
167
|
// new login attempt on this endpoint instance.
|
|
153
168
|
this.#retirementEpoch += 1;
|
|
@@ -204,6 +219,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
204
219
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
205
220
|
}
|
|
206
221
|
async stop() {
|
|
222
|
+
this.#stopped = true;
|
|
207
223
|
this.#assertExternalLifecycle('stop');
|
|
208
224
|
this.#armRetirementFence();
|
|
209
225
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
@@ -259,7 +275,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
259
275
|
}
|
|
260
276
|
async send({ conversation, payload }) {
|
|
261
277
|
if (!this.#started)
|
|
262
|
-
throw new
|
|
278
|
+
throw new EndpointDeliveryError('endpoint_disconnected', 'icqq endpoint 未连接', 'not_sent');
|
|
263
279
|
const mediaMode = resolveIcqqOutboundMediaMode(this.#options.config);
|
|
264
280
|
const prepared = isIcqqSegmentPayload(payload)
|
|
265
281
|
? prepareIcqqOutboundMedia(payload, mediaMode)
|
|
@@ -281,7 +297,11 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
281
297
|
}));
|
|
282
298
|
throw error;
|
|
283
299
|
}
|
|
284
|
-
const
|
|
300
|
+
const rawId = result?.message_id;
|
|
301
|
+
if ((typeof rawId !== 'string' && typeof rawId !== 'number') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
302
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: ICQQ returned no message_id', 'unknown');
|
|
303
|
+
}
|
|
304
|
+
const messageId = String(rawId);
|
|
285
305
|
this.#logger.info(`send ${conversation.kind}:${conversation.id} | id: ${messageId} | ${truncatePreview(preview, 80)}`);
|
|
286
306
|
return messageId;
|
|
287
307
|
}
|
|
@@ -314,12 +334,12 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
314
334
|
if (isIcqqFileElement(message)) {
|
|
315
335
|
switch (target.kind) {
|
|
316
336
|
case 'private': {
|
|
317
|
-
|
|
318
|
-
return {
|
|
337
|
+
await this.client.pickFriend(target.userId).sendFile(message.file, message.name);
|
|
338
|
+
return {};
|
|
319
339
|
}
|
|
320
340
|
case 'group': {
|
|
321
|
-
|
|
322
|
-
return {
|
|
341
|
+
await this.client.pickGroup(target.groupId).sendFile(message.file, '/', message.name);
|
|
342
|
+
return {};
|
|
323
343
|
}
|
|
324
344
|
case 'temp':
|
|
325
345
|
case 'channel':
|
|
@@ -489,6 +509,7 @@ export class IcqqEndpoint extends Endpoint {
|
|
|
489
509
|
this.client.on('system.online', (event) => {
|
|
490
510
|
if (this.#retiring)
|
|
491
511
|
return;
|
|
512
|
+
this.#everOnline = true;
|
|
492
513
|
try {
|
|
493
514
|
this.#resolveStartOnline?.();
|
|
494
515
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'online');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-icqq",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Zhin.js ICQQ adapter — direct connection to @icqqjs/icqq",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
"directory": "plugins/adapters/icqq"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/agent-feature": "1.1.
|
|
39
|
-
"@zhin.js/core": "1.1.
|
|
37
|
+
"@zhin.js/adapter": "1.1.16",
|
|
38
|
+
"@zhin.js/agent-feature": "1.1.3",
|
|
39
|
+
"@zhin.js/core": "1.1.40",
|
|
40
40
|
"@zhin.js/feature-kit": "1.1.2",
|
|
41
|
-
"@zhin.js/im-contract": "1.1.
|
|
41
|
+
"@zhin.js/im-contract": "1.1.3",
|
|
42
42
|
"@zhin.js/logger": "1.1.1",
|
|
43
43
|
"@zhin.js/permission": "1.1.3",
|
|
44
|
-
"@zhin.js/skill": "1.1.
|
|
45
|
-
"@zhin.js/tool": "1.1.
|
|
44
|
+
"@zhin.js/skill": "1.1.3",
|
|
45
|
+
"@zhin.js/tool": "1.1.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@icqqjs/icqq": "^1.12.2",
|
|
49
49
|
"zod": "^4.0.0",
|
|
50
|
-
"@zhin.js/adapter": "^1.1.
|
|
51
|
-
"@zhin.js/command": "^1.1.
|
|
52
|
-
"@zhin.js/core": "^1.1.
|
|
50
|
+
"@zhin.js/adapter": "^1.1.16",
|
|
51
|
+
"@zhin.js/command": "^1.1.4",
|
|
52
|
+
"@zhin.js/core": "^1.1.40",
|
|
53
53
|
"@zhin.js/prompt-section": "^1.1.2",
|
|
54
|
-
"zhin.js": "^1.1.
|
|
54
|
+
"zhin.js": "^1.1.5"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"@icqqjs/icqq": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"vitest": "^4.1.10",
|
|
78
78
|
"zod": "^4.4.3",
|
|
79
79
|
"@zhin.js/prompt-section": "1.1.2",
|
|
80
|
-
"zhin.js": "1.1.
|
|
80
|
+
"zhin.js": "1.1.5"
|
|
81
81
|
},
|
|
82
82
|
"files": [
|
|
83
83
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -16,10 +16,11 @@ import {
|
|
|
16
16
|
type EndpointManagement,
|
|
17
17
|
type EndpointPendingRequest,
|
|
18
18
|
type EndpointSendRequest,
|
|
19
|
+
type EndpointTransportState,
|
|
19
20
|
} from 'zhin.js/adapter';
|
|
20
21
|
import type { EndpointContentPort } from '@zhin.js/adapter';
|
|
21
22
|
import { receiveOneBotLikeSideEvent, buildSystem, toCanonicalSegments, type LoginAssist } from '@zhin.js/core';
|
|
22
|
-
import type
|
|
23
|
+
import { EndpointDeliveryError, type MessageRef } from '@zhin.js/im-contract';
|
|
23
24
|
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
24
25
|
import type { CapabilityId } from 'zhin.js';
|
|
25
26
|
import { runIcqqLoginAssistStep } from './icqq-login-assist.js';
|
|
@@ -103,6 +104,8 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
103
104
|
#open = false;
|
|
104
105
|
#retiring = false;
|
|
105
106
|
#started = false;
|
|
107
|
+
#stopped = false;
|
|
108
|
+
#everOnline = false;
|
|
106
109
|
#retirementEpoch = 0;
|
|
107
110
|
#retirementArmed = false;
|
|
108
111
|
#disposeNativeEvents?: () => void;
|
|
@@ -207,9 +210,18 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
207
210
|
});
|
|
208
211
|
}
|
|
209
212
|
|
|
213
|
+
get transportState(): EndpointTransportState {
|
|
214
|
+
if (this.#stopped || this.#retiring) return 'stopped';
|
|
215
|
+
if (!this.#started) return 'idle';
|
|
216
|
+
if (this.client.isOnline()) return 'open';
|
|
217
|
+
if (!this.#everOnline) return 'connecting';
|
|
218
|
+
return this.#options.config.autoReconnect ? 'reconnecting' : 'closed';
|
|
219
|
+
}
|
|
220
|
+
|
|
210
221
|
async start(signal: AbortSignal): Promise<void> {
|
|
211
222
|
if (this.#started) return;
|
|
212
223
|
signal.throwIfAborted();
|
|
224
|
+
this.#stopped = false;
|
|
213
225
|
// Invalidate a guard left by a prior lifecycle epoch before admitting a
|
|
214
226
|
// new login attempt on this endpoint instance.
|
|
215
227
|
this.#retirementEpoch += 1;
|
|
@@ -268,6 +280,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
268
280
|
}
|
|
269
281
|
|
|
270
282
|
async stop(): Promise<void> {
|
|
283
|
+
this.#stopped = true;
|
|
271
284
|
this.#assertExternalLifecycle('stop');
|
|
272
285
|
this.#armRetirementFence();
|
|
273
286
|
await Promise.allSettled([...this.#inflightInbound]);
|
|
@@ -319,7 +332,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
319
332
|
}
|
|
320
333
|
|
|
321
334
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
322
|
-
if (!this.#started) throw new
|
|
335
|
+
if (!this.#started) throw new EndpointDeliveryError('endpoint_disconnected', 'icqq endpoint 未连接', 'not_sent');
|
|
323
336
|
const mediaMode = resolveIcqqOutboundMediaMode(this.#options.config);
|
|
324
337
|
const prepared = isIcqqSegmentPayload(payload)
|
|
325
338
|
? prepareIcqqOutboundMedia(payload, mediaMode)
|
|
@@ -340,7 +353,11 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
340
353
|
}));
|
|
341
354
|
throw error;
|
|
342
355
|
}
|
|
343
|
-
const
|
|
356
|
+
const rawId = result?.message_id;
|
|
357
|
+
if ((typeof rawId !== 'string' && typeof rawId !== 'number') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
358
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: ICQQ returned no message_id', 'unknown');
|
|
359
|
+
}
|
|
360
|
+
const messageId = String(rawId);
|
|
344
361
|
this.#logger.info(
|
|
345
362
|
`send ${conversation.kind}:${conversation.id} | id: ${messageId} | ${truncatePreview(preview, 80)}`,
|
|
346
363
|
);
|
|
@@ -378,12 +395,12 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
378
395
|
if (isIcqqFileElement(message)) {
|
|
379
396
|
switch (target.kind) {
|
|
380
397
|
case 'private': {
|
|
381
|
-
|
|
382
|
-
return {
|
|
398
|
+
await this.client.pickFriend(target.userId).sendFile(message.file, message.name);
|
|
399
|
+
return {};
|
|
383
400
|
}
|
|
384
401
|
case 'group': {
|
|
385
|
-
|
|
386
|
-
return {
|
|
402
|
+
await this.client.pickGroup(target.groupId).sendFile(message.file, '/', message.name);
|
|
403
|
+
return {};
|
|
387
404
|
}
|
|
388
405
|
case 'temp':
|
|
389
406
|
case 'channel':
|
|
@@ -560,6 +577,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
560
577
|
#bindSystemOnlineEvent(): void {
|
|
561
578
|
this.client.on('system.online', (event) => {
|
|
562
579
|
if (this.#retiring) return;
|
|
580
|
+
this.#everOnline = true;
|
|
563
581
|
try {
|
|
564
582
|
this.#resolveStartOnline?.();
|
|
565
583
|
this.#options.loginAssist.cancelOwned(this.#loginAssistOwner, 'online');
|