@zhin.js/adapter-napcat 6.0.2 → 6.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @zhin.js/adapter-napcat
2
2
 
3
+ ## 6.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/host-http@1.0.6
16
+ - @zhin.js/agent@1.1.3
17
+ - zhin.js@6.0.2
18
+
3
19
  ## 6.0.2
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-napcat
24
24
  - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
 
27
- 入站:`gateway.receive({ adapter, target: "private:uid"|"group:gid", content, sender, metadata })`
28
- 出站:`send({ target, payload })` → WS `send_private_msg` / `send_group_msg`
27
+ 入站:`gateway.receive({ conversation, message: { conversation, id }, content, sender, metadata })`(`conversation` 为 ConversationRef:私聊 `kind: 'private'`、群聊 `kind: 'group'`,临时会话群容器进 `parent`)
28
+ 出站:`send({ conversation, payload })` → WS `send_private_msg` / `send_group_msg`
29
29
 
30
30
  ## 最小配置
31
31
 
@@ -1,4 +1,4 @@
1
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
1
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -18,10 +18,7 @@ export declare class NapCatHttpEndpoint implements EndpointInstance {
18
18
  open(): void;
19
19
  close(): void;
20
20
  stop(): Promise<void>;
21
- send({ target, payload }: {
22
- readonly target: string;
23
- readonly payload: unknown;
24
- }): Promise<string>;
21
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
25
22
  recallMessage(messageId: string): Promise<void>;
26
23
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
27
24
  admit(ev: NapCatEvent): void;
@@ -2,7 +2,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { createNapCatEndpointManagement } from './endpoint-management.js';
3
3
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
4
4
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
5
- import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatInboundTarget, formatOutboundSegments, isMessageEvent, senderNickname, senderUserId, } from './protocol.js';
5
+ import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
6
6
  import { readRequestBody } from './webhook.js';
7
7
  import { verifyNapCatAccessToken } from './wss-auth.js';
8
8
  const logger = getLogger('napcat');
@@ -48,9 +48,9 @@ export class NapCatHttpEndpoint {
48
48
  this.#started = false;
49
49
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
50
50
  }
51
- async send({ target, payload }) {
51
+ async send({ conversation, payload }) {
52
52
  const message = formatOutboundSegments(payload);
53
- const { action, params } = buildSendAction(target, message);
53
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
54
54
  const resp = await this.#callHttpAction({
55
55
  http_url: this.#options.config.http_url,
56
56
  access_token: this.#options.config.access_token,
@@ -60,7 +60,7 @@ export class NapCatHttpEndpoint {
60
60
  logger.debug(formatCompact({
61
61
  op: 'napcat_send',
62
62
  endpoint: this.#options.config.name,
63
- target,
63
+ target: `${conversation.kind}:${conversation.id}`,
64
64
  messageId,
65
65
  }));
66
66
  return messageId;
@@ -87,15 +87,14 @@ export class NapCatHttpEndpoint {
87
87
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
88
88
  ev = { ...ev, message: normalizeMessage(ev.message) };
89
89
  }
90
- const target = formatInboundTarget(ev);
90
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
91
91
  const nickname = senderNickname(ev);
92
92
  const mentioned = isNapCatBotMentioned(ev);
93
93
  void this.#options.gateway.receive({
94
- adapter: this.#options.id,
95
- target,
94
+ conversation,
95
+ message: { conversation, id: msgId },
96
96
  content: formatInboundContent(ev),
97
97
  sender: senderUserId(ev),
98
- id: msgId,
99
98
  metadata: Object.freeze({
100
99
  message_type: ev.message_type,
101
100
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -110,7 +109,7 @@ export class NapCatHttpEndpoint {
110
109
  }).catch((err) => {
111
110
  logger.warn(formatCompact({
112
111
  op: 'napcat_gateway_receive_failed',
113
- target,
112
+ target: `${conversation.kind}:${conversation.id}`,
114
113
  error: err instanceof Error ? err.message : String(err),
115
114
  }));
116
115
  });
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatInboundTarget, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, parseSendTarget, resolveNapCatConfig, senderNickname, senderUserId, type MessageSegment, type NapCatActionRequest, type NapCatActionResponse, type NapCatAdapterConfig, type NapCatConfigBase, type NapCatEndpointConfig, type NapCatEvent, type NapCatHttpConfig, type NapCatMessageEvent, type NapCatSender, type NapCatWireSegment, type NapCatWsConfig, type NapCatWssConfig, type ParsedSendTarget, type ResolvedNapCatConfig, } from './protocol.js';
1
+ export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, napcatInboundConversation, napcatOutboundTarget, resolveNapCatConfig, senderNickname, senderUserId, type MessageSegment, type NapCatActionRequest, type NapCatActionResponse, type NapCatAdapterConfig, type NapCatConfigBase, type NapCatEndpointConfig, type NapCatEvent, type NapCatHttpConfig, type NapCatMessageEvent, type NapCatSender, type NapCatWireSegment, type NapCatWsConfig, type NapCatWssConfig, type ParsedSendTarget, type ResolvedNapCatConfig, } from './protocol.js';
2
2
  export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
3
  export { getEndpoint, getNapcatAgentDeps, registerNapcatAgentEndpoint, setNapcatAgentDeps, type NapcatAgentDeps, type NapcatAgentEndpoint, } from './napcat-agent-deps.js';
4
4
  export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatInboundTarget, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, parseSendTarget, resolveNapCatConfig, senderNickname, senderUserId, } from './protocol.js';
1
+ export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, napcatInboundConversation, napcatOutboundTarget, resolveNapCatConfig, senderNickname, senderUserId, } from './protocol.js';
2
2
  export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
3
  export { getEndpoint, getNapcatAgentDeps, registerNapcatAgentEndpoint, setNapcatAgentDeps, } from './napcat-agent-deps.js';
4
4
  export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
package/lib/protocol.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * Canonicalization is owned by gateway/core before endpoint.send.
5
5
  */
6
6
  import { type MediaRef } from '@zhin.js/core';
7
+ import type { ConversationRef } from '@zhin.js/im-contract';
7
8
  /** Transitional legacy endpoint row (`endpoints[]` with `context: napcat`). */
8
9
  export interface NapCatLegacyEndpointRow {
9
10
  readonly context?: string;
@@ -116,8 +117,13 @@ export interface ParsedSendTarget {
116
117
  export declare function resolveNapCatConfig(config?: NapCatAdapterConfig): ResolvedNapCatConfig;
117
118
  export declare function isMessageEvent(ev: NapCatEvent): ev is NapCatMessageEvent;
118
119
  export declare function getChannelId(ev: NapCatEvent): string;
119
- export declare function formatInboundTarget(ev: NapCatEvent): string;
120
- export declare function parseSendTarget(target: string): ParsedSendTarget;
120
+ /**
121
+ * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
122
+ * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
123
+ */
124
+ export declare function napcatInboundConversation(endpointId: string, ev: NapCatEvent): ConversationRef;
125
+ /** 出站:ConversationRef → OneBot 私聊/群聊目标。 */
126
+ export declare function napcatOutboundTarget(conversation: ConversationRef): ParsedSendTarget;
121
127
  export declare function formatInboundContent(ev: NapCatEvent): string;
122
128
  /**
123
129
  * 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
@@ -138,7 +144,7 @@ export declare function mediaRefToOneBotFile(media: MediaRef): string;
138
144
  * 非媒体的 wire 段(at 等)原样透传。
139
145
  */
140
146
  export declare function formatOutboundSegments(payload: unknown): MessageSegment[];
141
- export declare function buildSendAction(target: string, message: MessageSegment[]): {
147
+ export declare function buildSendAction(target: ParsedSendTarget, message: MessageSegment[]): {
142
148
  action: string;
143
149
  params: Record<string, unknown>;
144
150
  };
package/lib/protocol.js CHANGED
@@ -78,24 +78,32 @@ export function getChannelId(ev) {
78
78
  return String(ev.user_id);
79
79
  return '';
80
80
  }
81
- export function formatInboundTarget(ev) {
82
- const messageType = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
83
- ? 'group'
84
- : 'private';
85
- return `${messageType}:${getChannelId(ev)}`;
86
- }
87
- export function parseSendTarget(target) {
88
- const sep = target.indexOf(':');
89
- if (sep <= 0) {
90
- return { message_type: 'private', id: target };
81
+ /**
82
+ * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
83
+ * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
84
+ */
85
+ export function napcatInboundConversation(endpointId, ev) {
86
+ const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
87
+ const isGroup = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private');
88
+ if (isGroup && ev.group_id != null) {
89
+ return { endpoint, kind: 'group', id: String(ev.group_id) };
91
90
  }
92
- const head = target.slice(0, sep);
93
- const rest = target.slice(sep + 1);
94
- if (head === 'group')
95
- return { message_type: 'group', id: rest };
96
- if (head === 'private')
97
- return { message_type: 'private', id: rest };
98
- return { message_type: 'private', id: target };
91
+ if (ev.sub_type === 'group' && ev.group_id != null) {
92
+ return {
93
+ endpoint,
94
+ kind: 'private',
95
+ id: ev.user_id != null ? String(ev.user_id) : '',
96
+ parent: { kind: 'group', id: String(ev.group_id) },
97
+ };
98
+ }
99
+ return { endpoint, kind: 'private', id: ev.user_id != null ? String(ev.user_id) : '' };
100
+ }
101
+ /** 出站:ConversationRef → OneBot 私聊/群聊目标。 */
102
+ export function napcatOutboundTarget(conversation) {
103
+ return {
104
+ message_type: conversation.kind === 'group' ? 'group' : 'private',
105
+ id: conversation.id,
106
+ };
99
107
  }
100
108
  export function formatInboundContent(ev) {
101
109
  if (typeof ev.raw_message === 'string' && ev.raw_message)
@@ -231,12 +239,11 @@ export function formatOutboundSegments(payload) {
231
239
  return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
232
240
  }
233
241
  export function buildSendAction(target, message) {
234
- const parsed = parseSendTarget(target);
235
- if (parsed.message_type === 'group') {
242
+ if (target.message_type === 'group') {
236
243
  return {
237
244
  action: 'send_group_msg',
238
245
  params: {
239
- group_id: Number(parsed.id) || parsed.id,
246
+ group_id: Number(target.id) || target.id,
240
247
  message,
241
248
  },
242
249
  };
@@ -244,7 +251,7 @@ export function buildSendAction(target, message) {
244
251
  return {
245
252
  action: 'send_private_msg',
246
253
  params: {
247
- user_id: Number(parsed.id) || parsed.id,
254
+ user_id: Number(target.id) || target.id,
248
255
  message,
249
256
  },
250
257
  };
@@ -1,4 +1,4 @@
1
- import { type EndpointInstance, type EndpointManagement } from '@zhin.js/adapter';
1
+ import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
4
4
  import { type NapCatEvent, type NapCatWsConfig } from './protocol.js';
@@ -17,10 +17,7 @@ export declare class NapCatWsEndpoint implements EndpointInstance {
17
17
  open(): void;
18
18
  close(): void;
19
19
  stop(): Promise<void>;
20
- send({ target, payload }: {
21
- readonly target: string;
22
- readonly payload: unknown;
23
- }): Promise<string>;
20
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
24
21
  recallMessage(messageId: string): Promise<void>;
25
22
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
26
23
  setTitle(groupId: number, userId: number, title: string, duration?: number): Promise<boolean>;
@@ -7,7 +7,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
7
7
  import { createNapCatEndpointManagement } from './endpoint-management.js';
8
8
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
9
9
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
10
- import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatInboundTarget, formatOutboundSegments, isMessageEvent, senderNickname, senderUserId, } from './protocol.js';
10
+ import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
11
11
  import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, } from './ws-transport.js';
12
12
  const logger = getLogger('napcat');
13
13
  export class NapCatWsEndpoint {
@@ -70,15 +70,15 @@ export class NapCatWsEndpoint {
70
70
  this.#ws = undefined;
71
71
  }
72
72
  }
73
- async send({ target, payload }) {
73
+ async send({ conversation, payload }) {
74
74
  const message = formatOutboundSegments(payload);
75
- const { action, params } = buildSendAction(target, message);
75
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
76
76
  const data = await this.callApi(action, params);
77
77
  const messageId = data?.message_id != null ? String(data.message_id) : '';
78
78
  logger.debug(formatCompact({
79
79
  op: 'napcat_send',
80
80
  endpoint: this.#options.config.name,
81
- target,
81
+ target: `${conversation.kind}:${conversation.id}`,
82
82
  messageId,
83
83
  }));
84
84
  return messageId;
@@ -237,16 +237,15 @@ export class NapCatWsEndpoint {
237
237
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
238
238
  ev = { ...ev, message: normalizeMessage(ev.message) };
239
239
  }
240
- const target = formatInboundTarget(ev);
240
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
241
241
  const content = formatInboundContent(ev);
242
242
  const nickname = senderNickname(ev);
243
243
  const mentioned = isNapCatBotMentioned(ev);
244
244
  void this.#options.gateway.receive({
245
- adapter: this.#options.id,
246
- target,
245
+ conversation,
246
+ message: { conversation, id: msgId },
247
247
  content,
248
248
  sender: senderUserId(ev),
249
- id: msgId,
250
249
  metadata: Object.freeze({
251
250
  message_type: ev.message_type,
252
251
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -261,7 +260,7 @@ export class NapCatWsEndpoint {
261
260
  }).catch((err) => {
262
261
  logger.warn(formatCompact({
263
262
  op: 'napcat_gateway_receive_failed',
264
- target,
263
+ target: `${conversation.kind}:${conversation.id}`,
265
264
  error: err instanceof Error ? err.message : String(err),
266
265
  }));
267
266
  });
@@ -1,4 +1,4 @@
1
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
1
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -17,10 +17,7 @@ export declare class NapCatWssEndpoint implements EndpointInstance {
17
17
  open(): void;
18
18
  close(): void;
19
19
  stop(): Promise<void>;
20
- send({ target, payload }: {
21
- readonly target: string;
22
- readonly payload: unknown;
23
- }): Promise<string>;
20
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
24
21
  recallMessage(messageId: string): Promise<void>;
25
22
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
26
23
  admit(ev: NapCatEvent): void;
@@ -6,7 +6,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
6
6
  import { createNapCatEndpointManagement } from './endpoint-management.js';
7
7
  import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
8
8
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
9
- import { buildSendAction, formatInboundContent, formatInboundTarget, formatOutboundSegments, isMessageEvent, senderNickname, senderUserId, } from './protocol.js';
9
+ import { buildSendAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
10
10
  import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
11
11
  import { verifyNapCatAccessToken } from './wss-auth.js';
12
12
  const logger = getLogger('napcat');
@@ -70,9 +70,9 @@ export class NapCatWssEndpoint {
70
70
  }
71
71
  this.#started = false;
72
72
  }
73
- async send({ target, payload }) {
73
+ async send({ conversation, payload }) {
74
74
  const message = formatOutboundSegments(payload);
75
- const { action, params } = buildSendAction(target, message);
75
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
76
76
  const data = await this.callApi(action, params);
77
77
  return data?.message_id != null ? String(data.message_id) : '';
78
78
  }
@@ -95,15 +95,14 @@ export class NapCatWssEndpoint {
95
95
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
96
96
  ev = { ...ev, message: normalizeMessage(ev.message) };
97
97
  }
98
- const target = formatInboundTarget(ev);
98
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
99
99
  const nickname = senderNickname(ev);
100
100
  const mentioned = isNapCatBotMentioned(ev);
101
101
  void this.#options.gateway.receive({
102
- adapter: this.#options.id,
103
- target,
102
+ conversation,
103
+ message: { conversation, id: msgId },
104
104
  content: formatInboundContent(ev),
105
105
  sender: senderUserId(ev),
106
- id: msgId,
107
106
  metadata: Object.freeze({
108
107
  message_type: ev.message_type,
109
108
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -118,7 +117,7 @@ export class NapCatWssEndpoint {
118
117
  }).catch((err) => {
119
118
  logger.warn(formatCompact({
120
119
  op: 'napcat_gateway_receive_failed',
121
- target,
120
+ target: `${conversation.kind}:${conversation.id}`,
122
121
  error: err instanceof Error ? err.message : String(err),
123
122
  }));
124
123
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-napcat",
3
- "version": "6.0.2",
3
+ "version": "6.0.3",
4
4
  "description": "Zhin.js NapCat adapter for Plugin Runtime (WebSocket client, OneBot11 + NapCat extensions)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -43,12 +43,13 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "ws": "^8.21.1",
46
- "@zhin.js/adapter": "1.1.4",
47
- "@zhin.js/command": "1.0.6",
48
- "@zhin.js/core": "1.5.1",
49
- "@zhin.js/host-http": "1.0.5",
46
+ "@zhin.js/adapter": "1.1.5",
47
+ "@zhin.js/command": "1.0.7",
48
+ "@zhin.js/core": "1.5.2",
49
+ "@zhin.js/host-http": "1.0.6",
50
+ "@zhin.js/im-contract": "1.0.1",
50
51
  "@zhin.js/logger": "1.0.75",
51
- "@zhin.js/plugin-runtime": "1.1.2"
52
+ "@zhin.js/plugin-runtime": "1.1.3"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/node": "^26.1.2",
@@ -56,16 +57,16 @@
56
57
  "typescript": "^6.0.3",
57
58
  "vitest": "^4.1.10",
58
59
  "zod": "^4.4.3",
59
- "@zhin.js/agent": "1.1.2",
60
- "@zhin.js/host-http": "1.0.5"
60
+ "@zhin.js/agent": "1.1.3",
61
+ "@zhin.js/host-http": "1.0.6"
61
62
  },
62
63
  "peerDependencies": {
63
64
  "zod": "^4.0.0",
64
- "@zhin.js/adapter": "1.1.4",
65
- "@zhin.js/agent": "1.1.2",
66
- "@zhin.js/core": "1.5.1",
67
- "@zhin.js/plugin-runtime": "1.1.2",
68
- "zhin.js": "6.0.1"
65
+ "@zhin.js/adapter": "1.1.5",
66
+ "@zhin.js/agent": "1.1.3",
67
+ "@zhin.js/core": "1.5.2",
68
+ "@zhin.js/plugin-runtime": "1.1.3",
69
+ "zhin.js": "6.0.2"
69
70
  },
70
71
  "peerDependenciesMeta": {
71
72
  "zhin.js": {
@@ -2,7 +2,7 @@
2
2
  * NapCat HTTP endpoint — POST inbound events + HTTP API outbound.
3
3
  */
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
5
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -19,9 +19,10 @@ import {
19
19
  buildSendAction,
20
20
  callNapCatHttpAction,
21
21
  formatInboundContent,
22
- formatInboundTarget,
23
22
  formatOutboundSegments,
24
23
  isMessageEvent,
24
+ napcatInboundConversation,
25
+ napcatOutboundTarget,
25
26
  senderNickname,
26
27
  senderUserId,
27
28
  type NapCatEvent,
@@ -90,9 +91,9 @@ export class NapCatHttpEndpoint implements EndpointInstance {
90
91
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
91
92
  }
92
93
 
93
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
94
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
94
95
  const message = formatOutboundSegments(payload);
95
- const { action, params } = buildSendAction(target, message);
96
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
96
97
  const resp = await this.#callHttpAction(
97
98
  {
98
99
  http_url: this.#options.config.http_url,
@@ -106,7 +107,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
106
107
  logger.debug(formatCompact({
107
108
  op: 'napcat_send',
108
109
  endpoint: this.#options.config.name,
109
- target,
110
+ target: `${conversation.kind}:${conversation.id}`,
110
111
  messageId,
111
112
  }));
112
113
  return messageId;
@@ -136,15 +137,14 @@ export class NapCatHttpEndpoint implements EndpointInstance {
136
137
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
137
138
  ev = { ...ev, message: normalizeMessage(ev.message) };
138
139
  }
139
- const target = formatInboundTarget(ev);
140
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
140
141
  const nickname = senderNickname(ev);
141
142
  const mentioned = isNapCatBotMentioned(ev);
142
143
  void this.#options.gateway.receive({
143
- adapter: this.#options.id,
144
- target,
144
+ conversation,
145
+ message: { conversation, id: msgId },
145
146
  content: formatInboundContent(ev),
146
147
  sender: senderUserId(ev),
147
- id: msgId,
148
148
  metadata: Object.freeze({
149
149
  message_type: ev.message_type,
150
150
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -159,7 +159,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
159
159
  }).catch((err) => {
160
160
  logger.warn(formatCompact({
161
161
  op: 'napcat_gateway_receive_failed',
162
- target,
162
+ target: `${conversation.kind}:${conversation.id}`,
163
163
  error: err instanceof Error ? err.message : String(err),
164
164
  }));
165
165
  });
package/src/index.ts CHANGED
@@ -3,12 +3,12 @@ export {
3
3
  buildWsConnectOptions,
4
4
  callNapCatHttpAction,
5
5
  formatInboundContent,
6
- formatInboundTarget,
7
6
  formatOutboundSegments,
8
7
  getChannelId,
9
8
  isMessageEvent,
10
9
  mediaRefToOneBotFile,
11
- parseSendTarget,
10
+ napcatInboundConversation,
11
+ napcatOutboundTarget,
12
12
  resolveNapCatConfig,
13
13
  senderNickname,
14
14
  senderUserId,
package/src/protocol.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * Canonicalization is owned by gateway/core before endpoint.send.
5
5
  */
6
6
  import { isMediaRef, type MediaRef } from '@zhin.js/core';
7
+ import type { ConversationRef } from '@zhin.js/im-contract';
7
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
9
 
9
10
  const logger = getLogger('napcat');
@@ -213,23 +214,33 @@ export function getChannelId(ev: NapCatEvent): string {
213
214
  return '';
214
215
  }
215
216
 
216
- export function formatInboundTarget(ev: NapCatEvent): string {
217
- const messageType = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
218
- ? 'group'
219
- : 'private';
220
- return `${messageType}:${getChannelId(ev)}`;
217
+ /**
218
+ * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
219
+ * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
220
+ */
221
+ export function napcatInboundConversation(endpointId: string, ev: NapCatEvent): ConversationRef {
222
+ const endpoint = { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId };
223
+ const isGroup = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private');
224
+ if (isGroup && ev.group_id != null) {
225
+ return { endpoint, kind: 'group', id: String(ev.group_id) };
226
+ }
227
+ if (ev.sub_type === 'group' && ev.group_id != null) {
228
+ return {
229
+ endpoint,
230
+ kind: 'private',
231
+ id: ev.user_id != null ? String(ev.user_id) : '',
232
+ parent: { kind: 'group', id: String(ev.group_id) },
233
+ };
234
+ }
235
+ return { endpoint, kind: 'private', id: ev.user_id != null ? String(ev.user_id) : '' };
221
236
  }
222
237
 
223
- export function parseSendTarget(target: string): ParsedSendTarget {
224
- const sep = target.indexOf(':');
225
- if (sep <= 0) {
226
- return { message_type: 'private', id: target };
227
- }
228
- const head = target.slice(0, sep);
229
- const rest = target.slice(sep + 1);
230
- if (head === 'group') return { message_type: 'group', id: rest };
231
- if (head === 'private') return { message_type: 'private', id: rest };
232
- return { message_type: 'private', id: target };
238
+ /** 出站:ConversationRef → OneBot 私聊/群聊目标。 */
239
+ export function napcatOutboundTarget(conversation: ConversationRef): ParsedSendTarget {
240
+ return {
241
+ message_type: conversation.kind === 'group' ? 'group' : 'private',
242
+ id: conversation.id,
243
+ };
233
244
  }
234
245
 
235
246
  export function formatInboundContent(ev: NapCatEvent): string {
@@ -373,15 +384,14 @@ export function formatOutboundSegments(payload: unknown): MessageSegment[] {
373
384
  }
374
385
 
375
386
  export function buildSendAction(
376
- target: string,
387
+ target: ParsedSendTarget,
377
388
  message: MessageSegment[],
378
389
  ): { action: string; params: Record<string, unknown> } {
379
- const parsed = parseSendTarget(target);
380
- if (parsed.message_type === 'group') {
390
+ if (target.message_type === 'group') {
381
391
  return {
382
392
  action: 'send_group_msg',
383
393
  params: {
384
- group_id: Number(parsed.id) || parsed.id,
394
+ group_id: Number(target.id) || target.id,
385
395
  message,
386
396
  },
387
397
  };
@@ -389,7 +399,7 @@ export function buildSendAction(
389
399
  return {
390
400
  action: 'send_private_msg',
391
401
  params: {
392
- user_id: Number(parsed.id) || parsed.id,
402
+ user_id: Number(target.id) || target.id,
393
403
  message,
394
404
  },
395
405
  };
@@ -8,6 +8,7 @@ import {
8
8
  type EndpointInstance,
9
9
  type EndpointLifecycle,
10
10
  type EndpointManagement,
11
+ type EndpointSendRequest,
11
12
  } from '@zhin.js/adapter';
12
13
  import type { MessageGateway } from '@zhin.js/core/runtime';
13
14
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -24,9 +25,10 @@ import {
24
25
  buildSendAction,
25
26
  buildWsConnectOptions,
26
27
  formatInboundContent,
27
- formatInboundTarget,
28
28
  formatOutboundSegments,
29
29
  isMessageEvent,
30
+ napcatInboundConversation,
31
+ napcatOutboundTarget,
30
32
  senderNickname,
31
33
  senderUserId,
32
34
  type NapCatEvent,
@@ -118,15 +120,15 @@ export class NapCatWsEndpoint implements EndpointInstance {
118
120
  }
119
121
  }
120
122
 
121
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
123
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
122
124
  const message = formatOutboundSegments(payload);
123
- const { action, params } = buildSendAction(target, message);
125
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
124
126
  const data = await this.callApi(action, params) as { message_id?: number | string } | undefined;
125
127
  const messageId = data?.message_id != null ? String(data.message_id) : '';
126
128
  logger.debug(formatCompact({
127
129
  op: 'napcat_send',
128
130
  endpoint: this.#options.config.name,
129
- target,
131
+ target: `${conversation.kind}:${conversation.id}`,
130
132
  messageId,
131
133
  }));
132
134
  return messageId;
@@ -326,16 +328,15 @@ export class NapCatWsEndpoint implements EndpointInstance {
326
328
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
327
329
  ev = { ...ev, message: normalizeMessage(ev.message) };
328
330
  }
329
- const target = formatInboundTarget(ev);
331
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
330
332
  const content = formatInboundContent(ev);
331
333
  const nickname = senderNickname(ev);
332
334
  const mentioned = isNapCatBotMentioned(ev);
333
335
  void this.#options.gateway.receive({
334
- adapter: this.#options.id,
335
- target,
336
+ conversation,
337
+ message: { conversation, id: msgId },
336
338
  content,
337
339
  sender: senderUserId(ev),
338
- id: msgId,
339
340
  metadata: Object.freeze({
340
341
  message_type: ev.message_type,
341
342
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -350,7 +351,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
350
351
  }).catch((err) => {
351
352
  logger.warn(formatCompact({
352
353
  op: 'napcat_gateway_receive_failed',
353
- target,
354
+ target: `${conversation.kind}:${conversation.id}`,
354
355
  error: err instanceof Error ? err.message : String(err),
355
356
  }));
356
357
  });
@@ -2,7 +2,7 @@
2
2
  * NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
5
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
8
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -18,9 +18,10 @@ import {
18
18
  import {
19
19
  buildSendAction,
20
20
  formatInboundContent,
21
- formatInboundTarget,
22
21
  formatOutboundSegments,
23
22
  isMessageEvent,
23
+ napcatInboundConversation,
24
+ napcatOutboundTarget,
24
25
  senderNickname,
25
26
  senderUserId,
26
27
  type NapCatEvent,
@@ -115,9 +116,9 @@ export class NapCatWssEndpoint implements EndpointInstance {
115
116
  this.#started = false;
116
117
  }
117
118
 
118
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
119
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
119
120
  const message = formatOutboundSegments(payload);
120
- const { action, params } = buildSendAction(target, message);
121
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
121
122
  const data = await this.callApi(action, params) as { message_id?: number | string } | undefined;
122
123
  return data?.message_id != null ? String(data.message_id) : '';
123
124
  }
@@ -139,15 +140,14 @@ export class NapCatWssEndpoint implements EndpointInstance {
139
140
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
140
141
  ev = { ...ev, message: normalizeMessage(ev.message) };
141
142
  }
142
- const target = formatInboundTarget(ev);
143
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
143
144
  const nickname = senderNickname(ev);
144
145
  const mentioned = isNapCatBotMentioned(ev);
145
146
  void this.#options.gateway.receive({
146
- adapter: this.#options.id,
147
- target,
147
+ conversation,
148
+ message: { conversation, id: msgId },
148
149
  content: formatInboundContent(ev),
149
150
  sender: senderUserId(ev),
150
- id: msgId,
151
151
  metadata: Object.freeze({
152
152
  message_type: ev.message_type,
153
153
  user_id: ev.user_id != null ? String(ev.user_id) : undefined,
@@ -162,7 +162,7 @@ export class NapCatWssEndpoint implements EndpointInstance {
162
162
  }).catch((err) => {
163
163
  logger.warn(formatCompact({
164
164
  op: 'napcat_gateway_receive_failed',
165
- target,
165
+ target: `${conversation.kind}:${conversation.id}`,
166
166
  error: err instanceof Error ? err.message : String(err),
167
167
  }));
168
168
  });