@zhin.js/adapter-icqq 8.0.2 → 8.0.3
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 +15 -0
- package/README.md +9 -9
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +12 -12
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/ipc-client.d.ts +1 -1
- package/lib/ipc-client.js +1 -1
- package/lib/protocol.d.ts +13 -5
- package/lib/protocol.js +36 -30
- package/package.json +13 -12
- package/src/endpoint.ts +19 -14
- package/src/index.ts +2 -2
- package/src/ipc-client.ts +1 -1
- package/src/protocol.ts +39 -30
- /package/commands/endpoint/add/{[name:string=].js → [[name]].js} +0 -0
- /package/commands/endpoint/add/{[name:string=].ts → [[name]].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @zhin.js/adapter-icqq
|
|
2
2
|
|
|
3
|
+
## 8.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [afc0e66]
|
|
8
|
+
- Updated dependencies [2e41ad5]
|
|
9
|
+
- Updated dependencies [9f57124]
|
|
10
|
+
- @zhin.js/core@1.5.2
|
|
11
|
+
- @zhin.js/adapter@1.1.5
|
|
12
|
+
- @zhin.js/im-contract@1.0.1
|
|
13
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
14
|
+
- @zhin.js/command@1.0.7
|
|
15
|
+
- @zhin.js/agent@1.1.3
|
|
16
|
+
- zhin.js@6.0.2
|
|
17
|
+
|
|
3
18
|
## 8.0.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ ICQQ Plugin Runtime 适配器 — 通过 [@icqqjs/cli](https://github.com/icqqjs
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
7
7
|
- 群聊 / 私聊 / 群临时会话 / QQ 频道消息
|
|
8
|
-
- 入站:`messageGatewayToken`(IPC
|
|
9
|
-
- 出站:`send({
|
|
8
|
+
- 入站:`messageGatewayToken`(IPC 事件订阅),归一为 `gateway.receive({ conversation, message, content, ... })`
|
|
9
|
+
- 出站:`send({ conversation, payload })` → `send_group_msg` / `send_private_msg` / …(conversation kind/id/parent 路由)
|
|
10
10
|
- Agent 工具:`agent/tools/`(戳一戳、群管、好友列表等)保留
|
|
11
11
|
- Console Endpoint 管理:`src/endpoint.ts` 显式实现 `EndpointManagement`(好友/群/群成员列表、请求审批、删好友、踢人、禁言、设管理),Console 使用 `endpoint.friends` / `endpoint.groups` / `endpoint.group_members` 等规范 RPC
|
|
12
12
|
|
|
@@ -48,14 +48,14 @@ plugins:
|
|
|
48
48
|
|
|
49
49
|
先执行 `icqq login`,再启动 Zhin。
|
|
50
50
|
|
|
51
|
-
## Send
|
|
51
|
+
## Send conversation
|
|
52
52
|
|
|
53
|
-
| 类型 |
|
|
54
|
-
|
|
55
|
-
| 私聊 | `private:uin` |
|
|
56
|
-
| 群聊 | `group:gid` |
|
|
57
|
-
| 群临时会话 | `
|
|
58
|
-
| 频道 | `channel:guildId
|
|
53
|
+
| 类型 | conversation |
|
|
54
|
+
|------|--------------|
|
|
55
|
+
| 私聊 | `{ kind: 'private', id: uin }` |
|
|
56
|
+
| 群聊 | `{ kind: 'group', id: gid }` |
|
|
57
|
+
| 群临时会话 | `{ kind: 'private', id: uin, parent: { kind: 'group', id: gid } }` |
|
|
58
|
+
| 频道 | `{ kind: 'channel', id: channelId, parent: { kind: 'channel', id: guildId } }` |
|
|
59
59
|
|
|
60
60
|
## 架构
|
|
61
61
|
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IcqqIpcEndpoint — lifecycle, outbound, IPC subscribe/admit for ICQQ daemon.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointControl, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointControl, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
7
7
|
import { type IcqqInboundMessage, type IpcEvent, type IpcResponse, type ResolvedIcqqConfig } from './protocol.js';
|
|
@@ -48,10 +48,7 @@ export declare class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
48
48
|
open(): void;
|
|
49
49
|
close(): void;
|
|
50
50
|
stop(): Promise<void>;
|
|
51
|
-
send({
|
|
52
|
-
readonly target: string;
|
|
53
|
-
readonly payload: unknown;
|
|
54
|
-
}): Promise<string>;
|
|
51
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
55
52
|
private static readonly EMOJI_MAP;
|
|
56
53
|
private getEmojiId;
|
|
57
54
|
/** Activity-feedback / OutboundHost reaction surface. */
|
package/lib/endpoint.js
CHANGED
|
@@ -5,7 +5,7 @@ import { buildIcqqInboxNoticeRow, buildIcqqInboxRequestRow, buildIcqqSystemReque
|
|
|
5
5
|
import { IcqqGuildCatalog, isIcqqGuildIpcEvent, normalizeIcqqGuildInboundMessage, } from './icqq-guild.js';
|
|
6
6
|
import { IpcClient } from './ipc-client.js';
|
|
7
7
|
import { materializeOutboundBase64, resolveIcqqOutboundMediaMode, } from './outbound-media.js';
|
|
8
|
-
import { Actions, formatInboundContent,
|
|
8
|
+
import { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
|
|
9
9
|
const logger = getLogger('icqq');
|
|
10
10
|
export class IcqqIpcEndpoint {
|
|
11
11
|
#options;
|
|
@@ -187,7 +187,7 @@ export class IcqqIpcEndpoint {
|
|
|
187
187
|
this.#started = false;
|
|
188
188
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.name }));
|
|
189
189
|
}
|
|
190
|
-
async send({
|
|
190
|
+
async send({ conversation, payload }) {
|
|
191
191
|
if (!this.ipc) {
|
|
192
192
|
throw new Error(`icqq endpoint ${this.name} 未连接(IPC 未初始化或已停止),无法发送消息`);
|
|
193
193
|
}
|
|
@@ -196,7 +196,7 @@ export class IcqqIpcEndpoint {
|
|
|
196
196
|
? materializeOutboundBase64(payload, mediaMode)
|
|
197
197
|
: payload;
|
|
198
198
|
const message = formatOutboundBody(content);
|
|
199
|
-
const parsed =
|
|
199
|
+
const parsed = icqqOutboundTarget(conversation);
|
|
200
200
|
let action;
|
|
201
201
|
let params;
|
|
202
202
|
switch (parsed.kind) {
|
|
@@ -233,7 +233,7 @@ export class IcqqIpcEndpoint {
|
|
|
233
233
|
logger.debug(formatCompact({
|
|
234
234
|
op: 'icqq_send',
|
|
235
235
|
endpoint: this.name,
|
|
236
|
-
target
|
|
236
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
237
237
|
messageId,
|
|
238
238
|
}));
|
|
239
239
|
return messageId;
|
|
@@ -499,13 +499,14 @@ export class IcqqIpcEndpoint {
|
|
|
499
499
|
}
|
|
500
500
|
/** Test / internal: admit when open. */
|
|
501
501
|
admit(msg) {
|
|
502
|
+
const conversation = msg.conversation;
|
|
502
503
|
if (!this.#open) {
|
|
503
504
|
logger.debug(formatCompact({
|
|
504
505
|
op: 'icqq_inbound_skip',
|
|
505
506
|
reason: 'endpoint_closed',
|
|
506
507
|
endpoint: this.name,
|
|
507
508
|
id: msg.id,
|
|
508
|
-
target:
|
|
509
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
509
510
|
}));
|
|
510
511
|
return;
|
|
511
512
|
}
|
|
@@ -519,7 +520,7 @@ export class IcqqIpcEndpoint {
|
|
|
519
520
|
op: 'icqq_inbound_admit',
|
|
520
521
|
endpoint: this.name,
|
|
521
522
|
id: msg.id,
|
|
522
|
-
target:
|
|
523
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
523
524
|
sender: msg.sender,
|
|
524
525
|
channelType: msg.channelType,
|
|
525
526
|
mentioned: mentioned || undefined,
|
|
@@ -527,12 +528,11 @@ export class IcqqIpcEndpoint {
|
|
|
527
528
|
segments: summarizeSegmentTypes(msg.segments),
|
|
528
529
|
}));
|
|
529
530
|
void this.#options.gateway.receive({
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
conversation,
|
|
532
|
+
message: { conversation, id: msg.id },
|
|
532
533
|
content: msg.content,
|
|
533
534
|
...(msg.segments ? { segments: msg.segments } : {}),
|
|
534
535
|
sender: msg.sender,
|
|
535
|
-
id: msg.id,
|
|
536
536
|
metadata: Object.freeze({
|
|
537
537
|
endpoint: this.name,
|
|
538
538
|
channelType: msg.channelType,
|
|
@@ -542,7 +542,7 @@ export class IcqqIpcEndpoint {
|
|
|
542
542
|
}).catch((err) => {
|
|
543
543
|
logger.warn(formatCompact({
|
|
544
544
|
op: 'icqq_gateway_receive_failed',
|
|
545
|
-
target:
|
|
545
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
546
546
|
error: err instanceof Error ? err.message : String(err),
|
|
547
547
|
}));
|
|
548
548
|
});
|
|
@@ -616,7 +616,7 @@ export class IcqqIpcEndpoint {
|
|
|
616
616
|
}
|
|
617
617
|
this.admit({
|
|
618
618
|
id: normalized.messageId,
|
|
619
|
-
|
|
619
|
+
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
620
620
|
channelType: normalized.channelType,
|
|
621
621
|
channelId: normalized.channelId,
|
|
622
622
|
channelParentGroupId: normalized.channelParentGroupId,
|
|
@@ -656,7 +656,7 @@ export class IcqqIpcEndpoint {
|
|
|
656
656
|
}
|
|
657
657
|
this.admit({
|
|
658
658
|
id: normalized.messageId,
|
|
659
|
-
|
|
659
|
+
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
660
660
|
channelType: 'channel',
|
|
661
661
|
channelId: normalized.channelId,
|
|
662
662
|
guildId: normalized.guildId,
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Actions, formatInboundContent,
|
|
1
|
+
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, resolveIcqqConfig, type IcqqAdapterConfig, type IcqqInboundMessage, type IcqqWireSegment, type IpcEvent, type IpcMessage, type IpcRequest, type IpcResponse, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
|
|
2
2
|
export { IcqqIpcEndpoint, type CreateIcqqIpc, type IcqqEndpointOptions, type IcqqInboxHooks, type IcqqIpcTransport, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, type IcqqAgentDeps, type IcqqAgentEndpoint, } from './icqq-agent-deps.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Actions, formatInboundContent,
|
|
1
|
+
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, resolveIcqqConfig, } from './protocol.js';
|
|
2
2
|
export { IcqqIpcEndpoint, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, } from './icqq-agent-deps.js';
|
package/lib/ipc-client.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export declare class IpcClient {
|
|
|
61
61
|
*/
|
|
62
62
|
request(action: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<IpcResponse>;
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* 订阅 daemon 推送事件:服务端自动推送全部事件,type/id 过滤在客户端完成。
|
|
65
65
|
* @returns 订阅句柄,包含 unsubscribe() 方法
|
|
66
66
|
*/
|
|
67
67
|
subscribe(_action: string, params: Record<string, unknown>, onEvent: (event: IpcEvent) => void): {
|
package/lib/ipc-client.js
CHANGED
package/lib/protocol.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 登录相关:**推送**走 IpcEvent(`system.login.*`)。
|
|
13
13
|
*/
|
|
14
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
14
15
|
import type { MessageSegment } from "zhin.js";
|
|
15
16
|
/** CLI → Daemon 请求 */
|
|
16
17
|
export type IpcRequest = {
|
|
@@ -236,7 +237,7 @@ export interface IcqqWireSegment {
|
|
|
236
237
|
}
|
|
237
238
|
export interface IcqqInboundMessage {
|
|
238
239
|
readonly id: string;
|
|
239
|
-
readonly
|
|
240
|
+
readonly conversation: ConversationRef;
|
|
240
241
|
readonly content: string;
|
|
241
242
|
/**
|
|
242
243
|
* 结构化段视图(canonical Segment,经 toCanonicalSegments 归一)。
|
|
@@ -263,13 +264,20 @@ export type ParsedIcqqSendTarget = {
|
|
|
263
264
|
readonly channelId: string;
|
|
264
265
|
};
|
|
265
266
|
export declare function resolveIcqqConfig(config?: IcqqAdapterConfig): ResolvedIcqqConfig;
|
|
266
|
-
/**
|
|
267
|
-
|
|
267
|
+
/**
|
|
268
|
+
* 入站归一化 → ConversationRef:群临时会话(temp)映射为群容器内的 private
|
|
269
|
+
* 会话(parent kind 'group');频道消息的所属 guild 进 `parent`(kind 'channel')。
|
|
270
|
+
*/
|
|
271
|
+
export declare function icqqInboundConversation(endpointId: string, input: {
|
|
268
272
|
readonly channelType: 'private' | 'group' | 'channel';
|
|
269
273
|
readonly channelId: string;
|
|
270
274
|
readonly channelParentGroupId?: string;
|
|
271
275
|
readonly guildId?: string;
|
|
272
|
-
}):
|
|
273
|
-
|
|
276
|
+
}): ConversationRef;
|
|
277
|
+
/**
|
|
278
|
+
* 出站:从 ConversationRef 派生 IPC action 路由。
|
|
279
|
+
* 群容器(parent kind 'group')内的 private 会话即 ICQQ 群临时会话(temp)。
|
|
280
|
+
*/
|
|
281
|
+
export declare function icqqOutboundTarget(conversation: ConversationRef): ParsedIcqqSendTarget;
|
|
274
282
|
export declare function formatOutboundBody(payload: unknown): string;
|
|
275
283
|
export declare function formatInboundContent(rawMessage: string): string;
|
package/lib/protocol.js
CHANGED
|
@@ -206,47 +206,53 @@ export function resolveIcqqConfig(config = {}) {
|
|
|
206
206
|
...(rpc ? { rpc } : {}),
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
-
/**
|
|
210
|
-
|
|
209
|
+
/**
|
|
210
|
+
* 入站归一化 → ConversationRef:群临时会话(temp)映射为群容器内的 private
|
|
211
|
+
* 会话(parent kind 'group');频道消息的所属 guild 进 `parent`(kind 'channel')。
|
|
212
|
+
*/
|
|
213
|
+
export function icqqInboundConversation(endpointId, input) {
|
|
214
|
+
const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
|
|
211
215
|
if (input.channelType === 'private' && input.channelParentGroupId) {
|
|
212
|
-
return
|
|
216
|
+
return {
|
|
217
|
+
endpoint,
|
|
218
|
+
kind: 'private',
|
|
219
|
+
id: input.channelId,
|
|
220
|
+
parent: { kind: 'group', id: input.channelParentGroupId },
|
|
221
|
+
};
|
|
213
222
|
}
|
|
214
223
|
if (input.channelType === 'channel' && input.guildId) {
|
|
215
|
-
return
|
|
224
|
+
return {
|
|
225
|
+
endpoint,
|
|
226
|
+
kind: 'channel',
|
|
227
|
+
id: input.channelId,
|
|
228
|
+
parent: { kind: 'channel', id: input.guildId },
|
|
229
|
+
};
|
|
216
230
|
}
|
|
217
|
-
return
|
|
231
|
+
return { endpoint, kind: input.channelType, id: input.channelId };
|
|
218
232
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
throw new TypeError(`Invalid icqq temp target: ${target}`);
|
|
226
|
-
return { kind: 'temp', groupId: Number(groupId), userId: Number(userId) };
|
|
227
|
-
}
|
|
228
|
-
if (trimmed.startsWith('channel:')) {
|
|
229
|
-
const rest = trimmed.slice(8);
|
|
230
|
-
const idx = rest.indexOf(':');
|
|
231
|
-
if (idx <= 0)
|
|
232
|
-
throw new TypeError(`Invalid icqq channel target: ${target}`);
|
|
233
|
+
/**
|
|
234
|
+
* 出站:从 ConversationRef 派生 IPC action 路由。
|
|
235
|
+
* 群容器(parent kind 'group')内的 private 会话即 ICQQ 群临时会话(temp)。
|
|
236
|
+
*/
|
|
237
|
+
export function icqqOutboundTarget(conversation) {
|
|
238
|
+
if (conversation.kind === 'private' && conversation.parent?.kind === 'group') {
|
|
233
239
|
return {
|
|
234
|
-
kind: '
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
kind: 'temp',
|
|
241
|
+
groupId: Number(conversation.parent.id),
|
|
242
|
+
userId: Number(conversation.id),
|
|
237
243
|
};
|
|
238
244
|
}
|
|
239
|
-
if (
|
|
240
|
-
return { kind: 'private', userId: Number(
|
|
245
|
+
if (conversation.kind === 'private') {
|
|
246
|
+
return { kind: 'private', userId: Number(conversation.id) };
|
|
241
247
|
}
|
|
242
|
-
if (
|
|
243
|
-
return { kind: 'group', groupId: Number(
|
|
248
|
+
if (conversation.kind === 'group') {
|
|
249
|
+
return { kind: 'group', groupId: Number(conversation.id) };
|
|
244
250
|
}
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
251
|
+
const guildId = conversation.parent?.kind === 'channel' ? conversation.parent.id : undefined;
|
|
252
|
+
if (!guildId) {
|
|
253
|
+
throw new TypeError(`Invalid icqq channel conversation (missing guild parent): ${conversation.id}`);
|
|
248
254
|
}
|
|
249
|
-
|
|
255
|
+
return { kind: 'channel', guildId, channelId: conversation.id };
|
|
250
256
|
}
|
|
251
257
|
export function formatOutboundBody(payload) {
|
|
252
258
|
if (typeof payload === 'string') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-icqq",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.3",
|
|
4
4
|
"description": "Zhin.js ICQQ adapter for Plugin Runtime (IPC daemon client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,19 +34,20 @@
|
|
|
34
34
|
"directory": "plugins/adapters/icqq"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/command": "1.0.
|
|
39
|
-
"@zhin.js/core": "1.5.
|
|
37
|
+
"@zhin.js/adapter": "1.1.5",
|
|
38
|
+
"@zhin.js/command": "1.0.7",
|
|
39
|
+
"@zhin.js/core": "1.5.2",
|
|
40
|
+
"@zhin.js/im-contract": "1.0.1",
|
|
40
41
|
"@zhin.js/logger": "1.0.75",
|
|
41
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
42
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"zod": "^4.0.0",
|
|
45
|
-
"@zhin.js/adapter": "1.1.
|
|
46
|
-
"@zhin.js/agent": "1.1.
|
|
47
|
-
"@zhin.js/core": "1.5.
|
|
48
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
49
|
-
"zhin.js": "6.0.
|
|
46
|
+
"@zhin.js/adapter": "1.1.5",
|
|
47
|
+
"@zhin.js/agent": "1.1.3",
|
|
48
|
+
"@zhin.js/core": "1.5.2",
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.1.3",
|
|
50
|
+
"zhin.js": "6.0.2"
|
|
50
51
|
},
|
|
51
52
|
"peerDependenciesMeta": {
|
|
52
53
|
"zhin.js": {
|
|
@@ -64,8 +65,8 @@
|
|
|
64
65
|
"typescript": "^6.0.3",
|
|
65
66
|
"vitest": "^4.1.10",
|
|
66
67
|
"zod": "^4.4.3",
|
|
67
|
-
"@zhin.js/agent": "1.1.
|
|
68
|
-
"zhin.js": "6.0.
|
|
68
|
+
"@zhin.js/agent": "1.1.3",
|
|
69
|
+
"zhin.js": "6.0.2"
|
|
69
70
|
},
|
|
70
71
|
"files": [
|
|
71
72
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IcqqIpcEndpoint — lifecycle, outbound, IPC subscribe/admit for ICQQ daemon.
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
EndpointControl,
|
|
6
|
+
EndpointInstance,
|
|
7
|
+
EndpointManagement,
|
|
8
|
+
EndpointSendRequest,
|
|
9
|
+
} from '@zhin.js/adapter';
|
|
5
10
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
11
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
12
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -38,9 +43,9 @@ import {
|
|
|
38
43
|
import {
|
|
39
44
|
Actions,
|
|
40
45
|
formatInboundContent,
|
|
41
|
-
formatInboundTarget,
|
|
42
46
|
formatOutboundBody,
|
|
43
|
-
|
|
47
|
+
icqqInboundConversation,
|
|
48
|
+
icqqOutboundTarget,
|
|
44
49
|
type IcqqInboundMessage,
|
|
45
50
|
type IpcEvent,
|
|
46
51
|
type IpcResponse,
|
|
@@ -265,7 +270,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
265
270
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.name }));
|
|
266
271
|
}
|
|
267
272
|
|
|
268
|
-
async send({
|
|
273
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
269
274
|
if (!this.ipc) {
|
|
270
275
|
throw new Error(`icqq endpoint ${this.name} 未连接(IPC 未初始化或已停止),无法发送消息`);
|
|
271
276
|
}
|
|
@@ -274,7 +279,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
274
279
|
? materializeOutboundBase64(payload, mediaMode)
|
|
275
280
|
: payload;
|
|
276
281
|
const message = formatOutboundBody(content);
|
|
277
|
-
const parsed =
|
|
282
|
+
const parsed = icqqOutboundTarget(conversation);
|
|
278
283
|
let action: string;
|
|
279
284
|
let params: Record<string, unknown>;
|
|
280
285
|
switch (parsed.kind) {
|
|
@@ -313,7 +318,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
313
318
|
logger.debug(formatCompact({
|
|
314
319
|
op: 'icqq_send',
|
|
315
320
|
endpoint: this.name,
|
|
316
|
-
target
|
|
321
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
317
322
|
messageId,
|
|
318
323
|
}));
|
|
319
324
|
return messageId;
|
|
@@ -630,13 +635,14 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
630
635
|
|
|
631
636
|
/** Test / internal: admit when open. */
|
|
632
637
|
admit(msg: IcqqInboundMessage): void {
|
|
638
|
+
const conversation = msg.conversation;
|
|
633
639
|
if (!this.#open) {
|
|
634
640
|
logger.debug(formatCompact({
|
|
635
641
|
op: 'icqq_inbound_skip',
|
|
636
642
|
reason: 'endpoint_closed',
|
|
637
643
|
endpoint: this.name,
|
|
638
644
|
id: msg.id,
|
|
639
|
-
target:
|
|
645
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
640
646
|
}));
|
|
641
647
|
return;
|
|
642
648
|
}
|
|
@@ -650,7 +656,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
650
656
|
op: 'icqq_inbound_admit',
|
|
651
657
|
endpoint: this.name,
|
|
652
658
|
id: msg.id,
|
|
653
|
-
target:
|
|
659
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
654
660
|
sender: msg.sender,
|
|
655
661
|
channelType: msg.channelType,
|
|
656
662
|
mentioned: mentioned || undefined,
|
|
@@ -658,12 +664,11 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
658
664
|
segments: summarizeSegmentTypes(msg.segments),
|
|
659
665
|
}));
|
|
660
666
|
void this.#options.gateway.receive({
|
|
661
|
-
|
|
662
|
-
|
|
667
|
+
conversation,
|
|
668
|
+
message: { conversation, id: msg.id },
|
|
663
669
|
content: msg.content,
|
|
664
670
|
...(msg.segments ? { segments: msg.segments } : {}),
|
|
665
671
|
sender: msg.sender,
|
|
666
|
-
id: msg.id,
|
|
667
672
|
metadata: Object.freeze({
|
|
668
673
|
endpoint: this.name,
|
|
669
674
|
channelType: msg.channelType,
|
|
@@ -673,7 +678,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
673
678
|
}).catch((err) => {
|
|
674
679
|
logger.warn(formatCompact({
|
|
675
680
|
op: 'icqq_gateway_receive_failed',
|
|
676
|
-
target:
|
|
681
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
677
682
|
error: err instanceof Error ? err.message : String(err),
|
|
678
683
|
}));
|
|
679
684
|
});
|
|
@@ -747,7 +752,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
747
752
|
}
|
|
748
753
|
this.admit({
|
|
749
754
|
id: normalized.messageId,
|
|
750
|
-
|
|
755
|
+
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
751
756
|
channelType: normalized.channelType,
|
|
752
757
|
channelId: normalized.channelId,
|
|
753
758
|
channelParentGroupId: normalized.channelParentGroupId,
|
|
@@ -789,7 +794,7 @@ export class IcqqIpcEndpoint implements EndpointInstance {
|
|
|
789
794
|
}
|
|
790
795
|
this.admit({
|
|
791
796
|
id: normalized.messageId,
|
|
792
|
-
|
|
797
|
+
conversation: icqqInboundConversation(String(this.#options.id), {
|
|
793
798
|
channelType: 'channel',
|
|
794
799
|
channelId: normalized.channelId,
|
|
795
800
|
guildId: normalized.guildId,
|
package/src/index.ts
CHANGED
package/src/ipc-client.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { pickCredential } from '@zhin.js/adapter';
|
|
16
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
16
17
|
import type { MessageSegment } from "zhin.js";
|
|
17
18
|
import { resolveCqMediaArg } from "./cq-message.js";
|
|
18
19
|
|
|
@@ -313,7 +314,7 @@ export interface IcqqWireSegment {
|
|
|
313
314
|
|
|
314
315
|
export interface IcqqInboundMessage {
|
|
315
316
|
readonly id: string;
|
|
316
|
-
readonly
|
|
317
|
+
readonly conversation: ConversationRef;
|
|
317
318
|
readonly content: string;
|
|
318
319
|
/**
|
|
319
320
|
* 结构化段视图(canonical Segment,经 toCanonicalSegments 归一)。
|
|
@@ -354,51 +355,59 @@ export function resolveIcqqConfig(config: IcqqAdapterConfig = {}): ResolvedIcqqC
|
|
|
354
355
|
};
|
|
355
356
|
}
|
|
356
357
|
|
|
357
|
-
/**
|
|
358
|
-
|
|
358
|
+
/**
|
|
359
|
+
* 入站归一化 → ConversationRef:群临时会话(temp)映射为群容器内的 private
|
|
360
|
+
* 会话(parent kind 'group');频道消息的所属 guild 进 `parent`(kind 'channel')。
|
|
361
|
+
*/
|
|
362
|
+
export function icqqInboundConversation(endpointId: string, input: {
|
|
359
363
|
readonly channelType: 'private' | 'group' | 'channel';
|
|
360
364
|
readonly channelId: string;
|
|
361
365
|
readonly channelParentGroupId?: string;
|
|
362
366
|
readonly guildId?: string;
|
|
363
|
-
}):
|
|
367
|
+
}): ConversationRef {
|
|
368
|
+
const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
|
|
364
369
|
if (input.channelType === 'private' && input.channelParentGroupId) {
|
|
365
|
-
return
|
|
370
|
+
return {
|
|
371
|
+
endpoint,
|
|
372
|
+
kind: 'private',
|
|
373
|
+
id: input.channelId,
|
|
374
|
+
parent: { kind: 'group', id: input.channelParentGroupId },
|
|
375
|
+
};
|
|
366
376
|
}
|
|
367
377
|
if (input.channelType === 'channel' && input.guildId) {
|
|
368
|
-
return
|
|
378
|
+
return {
|
|
379
|
+
endpoint,
|
|
380
|
+
kind: 'channel',
|
|
381
|
+
id: input.channelId,
|
|
382
|
+
parent: { kind: 'channel', id: input.guildId },
|
|
383
|
+
};
|
|
369
384
|
}
|
|
370
|
-
return
|
|
385
|
+
return { endpoint, kind: input.channelType, id: input.channelId };
|
|
371
386
|
}
|
|
372
387
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return { kind: 'temp', groupId: Number(groupId), userId: Number(userId) };
|
|
380
|
-
}
|
|
381
|
-
if (trimmed.startsWith('channel:')) {
|
|
382
|
-
const rest = trimmed.slice(8);
|
|
383
|
-
const idx = rest.indexOf(':');
|
|
384
|
-
if (idx <= 0) throw new TypeError(`Invalid icqq channel target: ${target}`);
|
|
388
|
+
/**
|
|
389
|
+
* 出站:从 ConversationRef 派生 IPC action 路由。
|
|
390
|
+
* 群容器(parent kind 'group')内的 private 会话即 ICQQ 群临时会话(temp)。
|
|
391
|
+
*/
|
|
392
|
+
export function icqqOutboundTarget(conversation: ConversationRef): ParsedIcqqSendTarget {
|
|
393
|
+
if (conversation.kind === 'private' && conversation.parent?.kind === 'group') {
|
|
385
394
|
return {
|
|
386
|
-
kind: '
|
|
387
|
-
|
|
388
|
-
|
|
395
|
+
kind: 'temp',
|
|
396
|
+
groupId: Number(conversation.parent.id),
|
|
397
|
+
userId: Number(conversation.id),
|
|
389
398
|
};
|
|
390
399
|
}
|
|
391
|
-
if (
|
|
392
|
-
return { kind: 'private', userId: Number(
|
|
400
|
+
if (conversation.kind === 'private') {
|
|
401
|
+
return { kind: 'private', userId: Number(conversation.id) };
|
|
393
402
|
}
|
|
394
|
-
if (
|
|
395
|
-
return { kind: 'group', groupId: Number(
|
|
403
|
+
if (conversation.kind === 'group') {
|
|
404
|
+
return { kind: 'group', groupId: Number(conversation.id) };
|
|
396
405
|
}
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
|
|
406
|
+
const guildId = conversation.parent?.kind === 'channel' ? conversation.parent.id : undefined;
|
|
407
|
+
if (!guildId) {
|
|
408
|
+
throw new TypeError(`Invalid icqq channel conversation (missing guild parent): ${conversation.id}`);
|
|
400
409
|
}
|
|
401
|
-
|
|
410
|
+
return { kind: 'channel', guildId, channelId: conversation.id };
|
|
402
411
|
}
|
|
403
412
|
|
|
404
413
|
export function formatOutboundBody(payload: unknown): string {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|