@zhin.js/adapter-napcat 1.0.0 → 1.1.0

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +674 -0
  2. package/README.md +60 -95
  3. package/adapters/napcat.js +47 -0
  4. package/adapters/napcat.ts +64 -0
  5. package/agent/PERMITS.md +19 -0
  6. package/{skills/napcat/SKILL.md → agent/skills/napcat.md} +6 -6
  7. package/agent/tools/ai_tts.ts +19 -0
  8. package/agent/tools/del_group_notice.ts +20 -0
  9. package/agent/tools/delete_essence_msg.ts +19 -0
  10. package/agent/tools/delete_friend.ts +18 -0
  11. package/agent/tools/download_file.ts +17 -0
  12. package/agent/tools/forward_single_msg.ts +28 -0
  13. package/agent/tools/get_ai_characters.ts +17 -0
  14. package/agent/tools/get_essence_list.ts +17 -0
  15. package/agent/tools/get_friend_msg_history.ts +18 -0
  16. package/agent/tools/get_group_file_url.ts +19 -0
  17. package/agent/tools/get_group_info_ex.ts +17 -0
  18. package/agent/tools/get_group_msg_history.ts +19 -0
  19. package/agent/tools/get_group_notice.ts +17 -0
  20. package/agent/tools/get_group_root_files.ts +17 -0
  21. package/agent/tools/get_group_shut_list.ts +17 -0
  22. package/agent/tools/get_mini_app_ark.ts +20 -0
  23. package/agent/tools/get_user_status.ts +16 -0
  24. package/agent/tools/group_sign.ts +18 -0
  25. package/agent/tools/mark_msg_as_read.ts +17 -0
  26. package/agent/tools/ocr_image.ts +16 -0
  27. package/agent/tools/send_forward_msg.ts +27 -0
  28. package/agent/tools/send_group_notice.ts +21 -0
  29. package/agent/tools/send_like.ts +18 -0
  30. package/agent/tools/send_poke.ts +18 -0
  31. package/agent/tools/set_avatar.ts +17 -0
  32. package/agent/tools/set_emoji_reaction.ts +18 -0
  33. package/agent/tools/set_essence_msg.ts +19 -0
  34. package/agent/tools/set_group_portrait.ts +20 -0
  35. package/agent/tools/set_online_status.ts +18 -0
  36. package/agent/tools/set_profile.ts +21 -0
  37. package/agent/tools/set_signature.ts +17 -0
  38. package/agent/tools/set_title.ts +21 -0
  39. package/agent/tools/translate.ts +16 -0
  40. package/agent/tools/upload_group_file.ts +21 -0
  41. package/commands/endpoint/add/[id].js +3 -0
  42. package/commands/endpoint/add/[id].ts +3 -0
  43. package/commands/endpoint/list.js +3 -0
  44. package/commands/endpoint/list.ts +3 -0
  45. package/commands/endpoint/remove/[id].js +3 -0
  46. package/commands/endpoint/remove/[id].ts +3 -0
  47. package/lib/client.d.ts +52 -0
  48. package/lib/client.js +62 -0
  49. package/lib/endpoint-management.d.ts +15 -0
  50. package/lib/endpoint-management.js +47 -0
  51. package/lib/http-endpoint.d.ts +27 -0
  52. package/lib/http-endpoint.js +177 -0
  53. package/lib/index.d.ts +11 -22
  54. package/lib/index.js +10 -58
  55. package/lib/napcat-endpoint-commands.d.ts +1 -0
  56. package/lib/napcat-endpoint-commands.js +28 -0
  57. package/lib/napcat-inbound.d.ts +12 -5
  58. package/lib/napcat-inbound.js +28 -13
  59. package/lib/napcat-runtime-state.d.ts +1 -0
  60. package/lib/napcat-runtime-state.js +6 -0
  61. package/lib/onebot-get-msg.d.ts +16 -3
  62. package/lib/onebot-get-msg.js +53 -1
  63. package/lib/protocol.d.ts +165 -0
  64. package/lib/protocol.js +305 -0
  65. package/lib/side-event-dispatch.d.ts +7 -0
  66. package/lib/side-event-dispatch.js +36 -0
  67. package/lib/webhook.d.ts +2 -0
  68. package/lib/webhook.js +14 -0
  69. package/lib/ws-endpoint.d.ts +27 -0
  70. package/lib/ws-endpoint.js +241 -0
  71. package/lib/ws-transport.d.ts +13 -0
  72. package/lib/ws-transport.js +75 -0
  73. package/lib/ws-types.d.ts +17 -0
  74. package/lib/ws-types.js +1 -0
  75. package/lib/wss-auth.d.ts +2 -0
  76. package/lib/wss-auth.js +16 -0
  77. package/lib/wss-endpoint.d.ts +26 -0
  78. package/lib/wss-endpoint.js +187 -0
  79. package/package.json +62 -30
  80. package/plugin.js +18 -0
  81. package/schema.json +110 -0
  82. package/src/client.ts +79 -0
  83. package/src/endpoint-management.ts +72 -0
  84. package/src/http-endpoint.ts +234 -0
  85. package/src/index.ts +63 -85
  86. package/src/napcat-endpoint-commands.ts +26 -0
  87. package/src/napcat-inbound.ts +34 -10
  88. package/src/napcat-runtime-state.ts +7 -0
  89. package/src/onebot-get-msg.ts +69 -5
  90. package/src/protocol.ts +468 -0
  91. package/src/side-event-dispatch.ts +48 -0
  92. package/src/webhook.ts +16 -0
  93. package/src/ws-endpoint.ts +301 -0
  94. package/src/ws-transport.ts +105 -0
  95. package/src/ws-types.ts +20 -0
  96. package/src/wss-auth.ts +17 -0
  97. package/src/wss-endpoint.ts +242 -0
  98. package/client/NapCatManagement.tsx +0 -113
  99. package/client/index.tsx +0 -11
  100. package/client/tsconfig.json +0 -7
  101. package/client/utils/api.ts +0 -30
  102. package/dist/index.js +0 -27
  103. package/lib/adapter.d.ts +0 -27
  104. package/lib/adapter.d.ts.map +0 -1
  105. package/lib/adapter.js +0 -93
  106. package/lib/adapter.js.map +0 -1
  107. package/lib/agent-prompt.d.ts +0 -3
  108. package/lib/agent-prompt.d.ts.map +0 -1
  109. package/lib/agent-prompt.js +0 -105
  110. package/lib/agent-prompt.js.map +0 -1
  111. package/lib/bot-base.d.ts +0 -143
  112. package/lib/bot-base.d.ts.map +0 -1
  113. package/lib/bot-base.js +0 -334
  114. package/lib/bot-base.js.map +0 -1
  115. package/lib/bot-http.d.ts +0 -18
  116. package/lib/bot-http.d.ts.map +0 -1
  117. package/lib/bot-http.js +0 -119
  118. package/lib/bot-http.js.map +0 -1
  119. package/lib/bot-ws-client.d.ts +0 -21
  120. package/lib/bot-ws-client.d.ts.map +0 -1
  121. package/lib/bot-ws-client.js +0 -165
  122. package/lib/bot-ws-client.js.map +0 -1
  123. package/lib/bot-ws-server.d.ts +0 -21
  124. package/lib/bot-ws-server.d.ts.map +0 -1
  125. package/lib/bot-ws-server.js +0 -150
  126. package/lib/bot-ws-server.js.map +0 -1
  127. package/lib/index.d.ts.map +0 -1
  128. package/lib/index.js.map +0 -1
  129. package/lib/napcat-inbound.d.ts.map +0 -1
  130. package/lib/napcat-inbound.js.map +0 -1
  131. package/lib/onebot-get-msg.d.ts.map +0 -1
  132. package/lib/onebot-get-msg.js.map +0 -1
  133. package/lib/routes.d.ts +0 -4
  134. package/lib/routes.d.ts.map +0 -1
  135. package/lib/routes.js +0 -61
  136. package/lib/routes.js.map +0 -1
  137. package/lib/tools.d.ts +0 -8
  138. package/lib/tools.d.ts.map +0 -1
  139. package/lib/tools.js +0 -605
  140. package/lib/tools.js.map +0 -1
  141. package/lib/types.d.ts +0 -293
  142. package/lib/types.d.ts.map +0 -1
  143. package/lib/types.js +0 -6
  144. package/lib/types.js.map +0 -1
  145. package/lib/typing-indicator.d.ts +0 -45
  146. package/lib/typing-indicator.d.ts.map +0 -1
  147. package/lib/typing-indicator.js +0 -132
  148. package/lib/typing-indicator.js.map +0 -1
  149. package/plugin.yml +0 -3
  150. package/src/adapter.ts +0 -99
  151. package/src/agent-prompt.ts +0 -114
  152. package/src/bot-base.ts +0 -376
  153. package/src/bot-http.ts +0 -109
  154. package/src/bot-ws-client.ts +0 -164
  155. package/src/bot-ws-server.ts +0 -150
  156. package/src/routes.ts +0 -61
  157. package/src/tools.ts +0 -632
  158. package/src/types.ts +0 -279
  159. package/src/typing-indicator.ts +0 -184
@@ -0,0 +1,177 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
2
+ import { createRecallEndpointControl, } from 'zhin.js/adapter';
3
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
4
+ import { createNapCatEndpointManagement } from './endpoint-management.js';
5
+ import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
6
+ import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
7
+ import { receiveNapCatSideEvent } from './side-event-dispatch.js';
8
+ import { createNapCatContentPort } from './onebot-get-msg.js';
9
+ import { NapcatClient } from './client.js';
10
+ import { readRequestBody } from './webhook.js';
11
+ import { verifyNapCatAccessToken } from './wss-auth.js';
12
+ export class NapCatHttpEndpoint extends Endpoint {
13
+ client = new NapcatClient((action, params) => this.#callApi(action, params));
14
+ #logger;
15
+ #options;
16
+ #inboundDeduper = new InboundMessageDeduper();
17
+ management = createNapCatEndpointManagement(this.client);
18
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
19
+ content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
20
+ #callHttpAction;
21
+ #routeReleases = [];
22
+ #open = false;
23
+ #started = false;
24
+ constructor(options) {
25
+ super();
26
+ this.#logger = getAdapterLogger('napcat', options.config.id);
27
+ this.#options = options;
28
+ this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
29
+ }
30
+ async start() {
31
+ if (this.#started)
32
+ return;
33
+ this.#started = true;
34
+ this.#setupRoutes();
35
+ this.#logger.info(formatCompact({
36
+ op: 'listen',
37
+ endpoint: this.#options.config.id,
38
+ mode: 'http',
39
+ path: this.#options.config.post_path,
40
+ }));
41
+ }
42
+ open() {
43
+ this.#open = true;
44
+ }
45
+ close() {
46
+ this.#open = false;
47
+ }
48
+ async stop() {
49
+ this.#open = false;
50
+ for (const release of this.#routeReleases.splice(0))
51
+ release();
52
+ this.#inboundDeduper.clear();
53
+ this.#started = false;
54
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
55
+ }
56
+ async send({ conversation, payload }) {
57
+ const message = formatOutboundSegments(payload);
58
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
59
+ const resp = await this.#callHttpAction({
60
+ http_url: this.#options.config.http_url,
61
+ access_token: this.#options.config.access_token,
62
+ }, action, params);
63
+ const data = resp.data;
64
+ const messageId = data?.message_id != null ? String(data.message_id) : '';
65
+ this.#logger.debug(formatCompact({
66
+ op: 'napcat_send',
67
+ endpoint: this.#options.config.id,
68
+ target: `${conversation.kind}:${conversation.id}`,
69
+ messageId,
70
+ }));
71
+ return messageId;
72
+ }
73
+ async recallMessage(messageId) {
74
+ if (!messageId)
75
+ return;
76
+ await this.client.callApi('delete_msg', { message_id: Number(messageId) });
77
+ }
78
+ #callApi(action, params = {}) {
79
+ return this.#callHttpAction({
80
+ http_url: this.#options.config.http_url,
81
+ access_token: this.#options.config.access_token,
82
+ }, action, params).then((resp) => resp.data);
83
+ }
84
+ admit(ev) {
85
+ if (!this.#open)
86
+ return;
87
+ void this.emitPlatform(napCatPlatformEventName(ev), ev).catch((error) => {
88
+ this.#logger.warn(formatCompact({
89
+ op: 'napcat_platform_event_failed',
90
+ error: error instanceof Error ? error.message : String(error),
91
+ }));
92
+ });
93
+ if (!isMessageEvent(ev)) {
94
+ receiveNapCatSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, this.client, ev, this.#logger);
95
+ return;
96
+ }
97
+ if (isSelfMessage(ev))
98
+ return;
99
+ const msgId = String(ev.message_id);
100
+ if (!this.#inboundDeduper.shouldProcess(msgId))
101
+ return;
102
+ if (Array.isArray(ev.message) || typeof ev.message === 'string') {
103
+ ev = { ...ev, message: normalizeMessage(ev.message) };
104
+ }
105
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
106
+ const nickname = senderNickname(ev);
107
+ const mentioned = isNapCatBotMentioned(ev);
108
+ void this.emit('message.receive', {
109
+ conversation,
110
+ message: { conversation, id: msgId },
111
+ content: formatInboundContent(ev),
112
+ sender: {
113
+ id: senderUserId(ev),
114
+ name: nickname,
115
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
116
+ },
117
+ endpointId: this.#options.config.id,
118
+ ...(mentioned ? { mentioned: true } : {}),
119
+ metadata: Object.freeze({
120
+ message_type: ev.message_type,
121
+ user_id: ev.user_id != null ? String(ev.user_id) : undefined,
122
+ group_id: ev.group_id != null ? String(ev.group_id) : undefined,
123
+ time: ev.time,
124
+ self_id: ev.self_id != null ? String(ev.self_id) : undefined,
125
+ role: ev.sender?.role,
126
+ ...(nickname ? { nickname } : {}),
127
+ }),
128
+ }).catch((err) => {
129
+ this.#logger.warn(formatCompact({
130
+ op: 'napcat_gateway_receive_failed',
131
+ target: `${conversation.kind}:${conversation.id}`,
132
+ error: err instanceof Error ? err.message : String(err),
133
+ }));
134
+ });
135
+ }
136
+ #setupRoutes() {
137
+ const path = this.#options.config.post_path;
138
+ this.#routeReleases.push(this.#options.http.route('POST', path, async (request, response) => {
139
+ await this.#handlePost(request, response);
140
+ }, { summary: 'NapCat HTTP event callback', tags: ['napcat'] }));
141
+ }
142
+ async #handlePost(request, response) {
143
+ try {
144
+ if (!verifyNapCatAccessToken(this.#options.config.access_token, request)) {
145
+ response.writeHead(403, { 'Content-Type': 'application/json' });
146
+ response.end(JSON.stringify({ message: 'Unauthorized' }));
147
+ return;
148
+ }
149
+ const raw = await readRequestBody(request);
150
+ let ev;
151
+ try {
152
+ ev = JSON.parse(raw);
153
+ }
154
+ catch {
155
+ response.writeHead(400, { 'Content-Type': 'application/json' });
156
+ response.end(JSON.stringify({ message: 'Invalid JSON' }));
157
+ return;
158
+ }
159
+ if (this.#open)
160
+ this.admit(ev);
161
+ response.writeHead(200, { 'Content-Type': 'application/json' });
162
+ response.end(JSON.stringify({ status: 'ok' }));
163
+ }
164
+ catch (error) {
165
+ this.#logger.error('NapCat HTTP webhook error:', error);
166
+ if (!response.headersSent) {
167
+ response.writeHead(500, { 'Content-Type': 'application/json' });
168
+ response.end(JSON.stringify({ message: 'Internal Server Error' }));
169
+ }
170
+ }
171
+ }
172
+ }
173
+ function napCatPlatformEventName(ev) {
174
+ return [ev.post_type, ev.message_type ?? ev.notice_type ?? ev.request_type, ev.sub_type]
175
+ .filter(Boolean)
176
+ .join('.');
177
+ }
package/lib/index.d.ts CHANGED
@@ -1,22 +1,11 @@
1
- import type { Router } from '@zhin.js/host-router';
2
- import { PageManager } from '@zhin.js/host-api';
3
- import { NapCatAdapter } from './adapter.js';
4
- export * from './types.js';
5
- export { NapCatWsClient } from './bot-ws-client.js';
6
- export { NapCatWsServer } from './bot-ws-server.js';
7
- export { NapCatHttpBot } from './bot-http.js';
8
- export { NapCatAdapter, type NapCatBot } from './adapter.js';
9
- export { NapCatBotBase } from './bot-base.js';
10
- export { NapCatTypingIndicatorManager, enableTypingIndicator, type NapCatTypingIndicatorConfig, } from './typing-indicator.js';
11
- declare module 'zhin.js' {
12
- namespace Plugin {
13
- interface Contexts {
14
- web: PageManager;
15
- router: Router;
16
- }
17
- }
18
- interface Adapters {
19
- napcat: NapCatAdapter;
20
- }
21
- }
22
- //# sourceMappingURL=index.d.ts.map
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
+ export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
+ export { napcatClient, type NapcatClient, type NapcatClientEventMap, } from './client.js';
4
+ export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
5
+ export { NapCatWsEndpoint, type NapCatWsEndpointOptions, } from './ws-endpoint.js';
6
+ export { NapCatWssEndpoint, type NapCatWssEndpointOptions, } from './wss-endpoint.js';
7
+ export { NapCatHttpEndpoint, type NapCatHttpEndpointOptions, } from './http-endpoint.js';
8
+ export type { NapCatWsSocket, NapCatWsCreateOptions } from './ws-types.js';
9
+ export { callNapCatWsAction, decodeWsPayload, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
10
+ export { verifyNapCatAccessToken } from './wss-auth.js';
11
+ export { readRequestBody } from './webhook.js';
package/lib/index.js CHANGED
@@ -1,58 +1,10 @@
1
- /**
2
- * NapCat 适配器入口
3
- * 支持 OneBot11 标准 + go-cqhttp 扩展 + NapCat 独有 API
4
- * 连接方式:正向 WS / 反向 WS / HTTP
5
- */
6
- import path from 'path';
7
- import { usePlugin, createGroupManagementTools, registerAgentPromptContributor, unregisterAgentPromptContributor, } from 'zhin.js';
8
- import { NapCatAdapter } from './adapter.js';
9
- import { createNapCatTools } from './tools.js';
10
- import { createNapCatAgentPromptContributor } from './agent-prompt.js';
11
- import { registerRoutes } from './routes.js';
12
- export * from './types.js';
13
- export { NapCatWsClient } from './bot-ws-client.js';
14
- export { NapCatWsServer } from './bot-ws-server.js';
15
- export { NapCatHttpBot } from './bot-http.js';
16
- export { NapCatAdapter } from './adapter.js';
17
- export { NapCatBotBase } from './bot-base.js';
18
- export { NapCatTypingIndicatorManager, enableTypingIndicator, } from './typing-indicator.js';
19
- const plugin = usePlugin();
20
- const { provide, useContext } = plugin;
21
- // ── 适配器注册 ─────────────────────────────────────────────────────
22
- provide({
23
- name: 'napcat',
24
- description: 'NapCatQQ 适配器(OneBot11 + go-cqhttp + NapCat 扩展,正向/反向 WS + HTTP)',
25
- mounted: async (p) => {
26
- registerAgentPromptContributor(createNapCatAgentPromptContributor());
27
- const adapter = new NapCatAdapter(p);
28
- await adapter.start();
29
- return adapter;
30
- },
31
- dispose: async (adapter) => {
32
- unregisterAgentPromptContributor('napcat');
33
- await adapter.stop();
34
- },
35
- });
36
- // ── AI 工具注册 ────────────────────────────────────────────────────
37
- useContext('tool', 'napcat', (toolService, napcat) => {
38
- const disposers = [];
39
- const groupTools = createGroupManagementTools(napcat, 'napcat');
40
- disposers.push(...groupTools.map(t => toolService.addTool(t, plugin.name)));
41
- const napcatTools = createNapCatTools(napcat);
42
- disposers.push(...napcatTools.map(t => toolService.addTool(t, plugin.name)));
43
- return () => disposers.forEach(d => d());
44
- });
45
- // ── Web 控制台入口 ─────────────────────────────────────────────────
46
- useContext('web', (pageManager) => {
47
- pageManager.addEntry({
48
- id: 'napcat',
49
- development: path.resolve(import.meta.dirname, '../client/index.tsx'),
50
- production: path.resolve(import.meta.dirname, '../dist/index.js'),
51
- meta: { name: 'NapCat' },
52
- });
53
- });
54
- // ── HTTP 路由 ──────────────────────────────────────────────────────
55
- useContext('router', 'napcat', async (router, napcat) => {
56
- registerRoutes(router, napcat);
57
- });
58
- //# sourceMappingURL=index.js.map
1
+ export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, napcatInboundConversation, napcatOutboundTarget, resolveNapCatConfig, senderNickname, senderUserId, } from './protocol.js';
2
+ export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
+ export { napcatClient, } from './client.js';
4
+ export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
5
+ export { NapCatWsEndpoint, } from './ws-endpoint.js';
6
+ export { NapCatWssEndpoint, } from './wss-endpoint.js';
7
+ export { NapCatHttpEndpoint, } from './http-endpoint.js';
8
+ export { callNapCatWsAction, decodeWsPayload, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
9
+ export { verifyNapCatAccessToken } from './wss-auth.js';
10
+ export { readRequestBody } from './webhook.js';
@@ -0,0 +1 @@
1
+ export declare const napcatEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `napcat.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
+ * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
+ */
5
+ import { createEndpointCommands } from 'zhin.js/adapter';
6
+ import { defineCommand } from 'zhin.js/command';
7
+ import { napcatRuntimeStateToken } from './napcat-runtime-state.js';
8
+ export const napcatEndpointCommands = createEndpointCommands({
9
+ adapterKey: 'napcat',
10
+ adapterDisplayName: 'NapCat',
11
+ fields: [
12
+ { key: 'url', description: 'NapCat WebSocket URL(connection: ws 必填)' },
13
+ { key: 'path', description: 'reverse-wss 路径(connection: wss)' },
14
+ { key: 'http_url', description: 'HTTP API base URL(connection: http 出站)' },
15
+ { key: 'post_path', description: 'HTTP POST 事件路径(connection: http 入站)' },
16
+ { key: 'access_token', env: true, description: 'NapCat access token' },
17
+ ],
18
+ running: (use) => use(napcatRuntimeStateToken).endpoints.values(),
19
+ describeEntry: (entry) => {
20
+ if (entry.url)
21
+ return `url: ${String(entry.url)}`;
22
+ if (entry.path)
23
+ return `path: ${String(entry.path)}`;
24
+ if (entry.http_url)
25
+ return `http_url: ${String(entry.http_url)}`;
26
+ return '';
27
+ },
28
+ }, defineCommand);
@@ -1,21 +1,28 @@
1
1
  /**
2
2
  * NapCat 入站消息治理:去重、自发过滤、消息归一化
3
3
  */
4
- import type { NapCatMessageEvent, MessageSegment } from './types.js';
4
+ import type { NapCatMessageEvent, MessageSegment } from './protocol.js';
5
5
  export declare class InboundMessageDeduper {
6
6
  private readonly seen;
7
7
  shouldProcess(messageId: string): boolean;
8
8
  clear(): void;
9
9
  }
10
10
  /** 判断是否为 bot 自身发出的消息 */
11
- export declare function isSelfMessage(event: NapCatMessageEvent): boolean;
11
+ export declare function isSelfMessage(event: NapCatMessageEvent | {
12
+ post_type?: string;
13
+ self_id?: number | string;
14
+ user_id?: number | string;
15
+ }): boolean;
12
16
  /**
13
17
  * 将 message 字段归一化为 MessageSegment[]。
14
18
  * NapCat 通常返回数组,但某些配置下可能返回 CQ 字符串。
15
19
  */
16
20
  export declare function normalizeMessage(message: MessageSegment[] | string): MessageSegment[];
17
21
  /**
18
- * 生成用于 notice / request 事件的去重 key
22
+ * OneBot11 mentioned 判定:消息段 {type:'at', data:{qq}} 的 qq 等于本机 uin
23
+ * (事件 self_id)时为 true;`qq === 'all'` 不算。兼容 CQ 字符串形式。
19
24
  */
20
- export declare function resolveSideEventDedupeKey(event: any, prefix: string): string;
21
- //# sourceMappingURL=napcat-inbound.d.ts.map
25
+ export declare function isNapCatBotMentioned(ev: {
26
+ self_id?: number | string;
27
+ message?: MessageSegment[] | string;
28
+ }): boolean;
@@ -37,19 +37,34 @@ export function normalizeMessage(message) {
37
37
  }
38
38
  return [];
39
39
  }
40
+ const CQ_AT_RE = /\[CQ:at,qq=([^\],\s]+)\]/g;
41
+ function atTargetMatchesUin(target, uin) {
42
+ const qq = String(target ?? '').trim();
43
+ return Boolean(qq) && qq !== 'all' && Number(qq) === Number(uin);
44
+ }
40
45
  /**
41
- * 生成用于 notice / request 事件的去重 key
46
+ * OneBot11 mentioned 判定:消息段 {type:'at', data:{qq}} 的 qq 等于本机 uin
47
+ * (事件 self_id)时为 true;`qq === 'all'` 不算。兼容 CQ 字符串形式。
42
48
  */
43
- export function resolveSideEventDedupeKey(event, prefix) {
44
- const parts = [prefix, event.time, event.notice_type || event.request_type];
45
- if (event.group_id)
46
- parts.push(event.group_id);
47
- if (event.user_id)
48
- parts.push(event.user_id);
49
- if (event.message_id)
50
- parts.push(event.message_id);
51
- if (event.flag)
52
- parts.push(event.flag);
53
- return parts.join(':');
49
+ export function isNapCatBotMentioned(ev) {
50
+ if (ev.self_id == null)
51
+ return false;
52
+ const uin = String(ev.self_id).trim();
53
+ if (!uin)
54
+ return false;
55
+ const segments = normalizeMessage(ev.message ?? []);
56
+ for (const seg of segments) {
57
+ if (seg.type === 'at' && atTargetMatchesUin(seg.data?.qq, uin))
58
+ return true;
59
+ }
60
+ const text = segments
61
+ .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
62
+ .join('');
63
+ if (!text)
64
+ return false;
65
+ for (const match of text.matchAll(CQ_AT_RE)) {
66
+ if (atTargetMatchesUin(match[1], uin))
67
+ return true;
68
+ }
69
+ return false;
54
70
  }
55
- //# sourceMappingURL=napcat-inbound.js.map
@@ -0,0 +1 @@
1
+ export declare const napcatRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * NapCat 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `napcat.endpoint` 命令共享(同一 owner generation)。
4
+ */
5
+ import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
+ export const napcatRuntimeStateToken = defineEndpointRuntimeStateToken('napcat');
@@ -1,3 +1,16 @@
1
- import type { QuotedMessagePayload } from 'zhin.js';
2
- export declare function parseOneBotGetMsgResponse(messageId: string, data: unknown): QuotedMessagePayload;
3
- //# sourceMappingURL=onebot-get-msg.d.ts.map
1
+ export interface NapcatMessageLookup {
2
+ messageId: string;
3
+ sender?: {
4
+ id: string;
5
+ name: string;
6
+ };
7
+ content: Array<{
8
+ type: string;
9
+ data?: Record<string, unknown>;
10
+ }>;
11
+ raw?: string;
12
+ time?: number;
13
+ }
14
+ export declare function parseOneBotGetMsgResponse(messageId: string, data: unknown): NapcatMessageLookup;
15
+ export declare function createNapCatContentPort(callApi: (action: string, params?: Record<string, unknown>) => Promise<unknown>): EndpointContentPort;
16
+ import type { EndpointContentPort } from 'zhin.js/adapter';
@@ -24,4 +24,56 @@ export function parseOneBotGetMsgResponse(messageId, data) {
24
24
  time: typeof record.time === 'number' ? record.time : undefined,
25
25
  };
26
26
  }
27
- //# sourceMappingURL=onebot-get-msg.js.map
27
+ export function createNapCatContentPort(callApi) {
28
+ return Object.freeze({
29
+ async resolve(reference, context) {
30
+ try {
31
+ context.signal.throwIfAborted();
32
+ if (reference.kind === 'media') {
33
+ return reference.media.kind === 'file'
34
+ ? Object.freeze({ status: 'unsupported', code: 'napcat_opaque_media_requires_platform_file_metadata' })
35
+ : Object.freeze({ status: 'resolved', reference, value: reference.media });
36
+ }
37
+ if (reference.kind === 'message') {
38
+ const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi('get_msg', { message_id: reference.message.id }));
39
+ context.signal.throwIfAborted();
40
+ const value = Object.freeze({
41
+ ref: reference.message,
42
+ ...(parsed.sender?.id ? { actor: Object.freeze({ id: parsed.sender.id, ...(parsed.sender.name ? { displayName: parsed.sender.name } : {}) }) } : {}),
43
+ segments: canonicalSegments(parsed.content),
44
+ timestamp: toMillis(parsed.time),
45
+ });
46
+ return Object.freeze({ status: 'resolved', reference, value });
47
+ }
48
+ const raw = await callApi('get_forward_msg', { message_id: reference.forwardId, id: reference.forwardId });
49
+ context.signal.throwIfAborted();
50
+ const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
51
+ const value = Object.freeze(nodes.map((node) => {
52
+ const row = node;
53
+ const sender = row.sender;
54
+ return Object.freeze({
55
+ ...(sender?.user_id != null ? { actor: Object.freeze({ id: String(sender.user_id), ...(sender.nickname ? { displayName: String(sender.nickname) } : {}) }) } : {}),
56
+ segments: canonicalSegments(Array.isArray(row.content) ? row.content : []),
57
+ timestamp: toMillis(row.time),
58
+ });
59
+ }));
60
+ return Object.freeze({ status: 'resolved', reference, value });
61
+ }
62
+ catch (error) {
63
+ if (context.signal.aborted)
64
+ return Object.freeze({ status: 'expired', code: 'turn_aborted' });
65
+ return Object.freeze({ status: 'failed', code: 'napcat_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
66
+ }
67
+ },
68
+ });
69
+ }
70
+ function canonicalSegments(content) {
71
+ return Object.freeze(content.map((segment) => Object.freeze({
72
+ type: segment.type,
73
+ data: Object.freeze(segment.data ?? {}),
74
+ })));
75
+ }
76
+ function toMillis(value) {
77
+ const time = Number(value);
78
+ return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
79
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * NapCat protocol helpers (OneBot 11 + NapCat extensions).
3
+ * No legacy Adapter/Endpoint / segment-mapper.
4
+ * Canonicalization is owned by gateway/core before endpoint.send.
5
+ */
6
+ import { type MediaRef } from '@zhin.js/core';
7
+ import type { ConversationRef } from '@zhin.js/im-contract';
8
+ /** Transitional legacy endpoint row (`endpoints[]` with `context: napcat`). */
9
+ export interface NapCatLegacyEndpointRow {
10
+ readonly context?: string;
11
+ readonly connection?: 'ws' | 'wss' | 'http';
12
+ readonly id?: string;
13
+ readonly access_token?: string;
14
+ readonly url?: string;
15
+ readonly path?: string;
16
+ readonly http_url?: string;
17
+ readonly post_path?: string;
18
+ readonly reconnect_interval?: number;
19
+ readonly heartbeat_interval?: number;
20
+ readonly poll_interval?: number;
21
+ }
22
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
23
+ export interface NapCatAdapterConfig {
24
+ readonly connection?: 'ws' | 'wss' | 'http';
25
+ readonly id?: string;
26
+ readonly access_token?: string;
27
+ readonly url?: string;
28
+ readonly path?: string;
29
+ readonly http_url?: string;
30
+ readonly post_path?: string;
31
+ readonly reconnect_interval?: number;
32
+ readonly heartbeat_interval?: number;
33
+ readonly poll_interval?: number;
34
+ /** Transitional: legacy root `endpoints[]` with `context: napcat`. */
35
+ readonly endpoints?: ReadonlyArray<NapCatLegacyEndpointRow>;
36
+ }
37
+ export interface NapCatConfigBase {
38
+ readonly context: 'napcat';
39
+ readonly id: string;
40
+ readonly access_token?: string;
41
+ }
42
+ /** 正向 WebSocket:应用连 NapCat WS */
43
+ export interface NapCatWsConfig extends NapCatConfigBase {
44
+ readonly connection: 'ws';
45
+ readonly url: string;
46
+ readonly reconnect_interval: number;
47
+ readonly heartbeat_interval: number;
48
+ }
49
+ /** 反向 WebSocket:httpHostToken WS upgrade 入站/出站 */
50
+ export interface NapCatWssConfig extends NapCatConfigBase {
51
+ readonly connection: 'wss';
52
+ readonly path: string;
53
+ readonly heartbeat_interval: number;
54
+ }
55
+ /** HTTP API + POST 上报:httpHostToken POST 入站 + http_url/{action} 出站 */
56
+ export interface NapCatHttpConfig extends NapCatConfigBase {
57
+ readonly connection: 'http';
58
+ readonly http_url: string;
59
+ readonly post_path: string;
60
+ readonly poll_interval: number;
61
+ }
62
+ export type ResolvedNapCatConfig = NapCatWsConfig | NapCatWssConfig | NapCatHttpConfig;
63
+ export type NapCatEndpointConfig = ResolvedNapCatConfig;
64
+ export interface NapCatSender {
65
+ readonly role?: string;
66
+ readonly nickname?: string;
67
+ readonly card?: string;
68
+ readonly title?: string;
69
+ readonly user_id?: number;
70
+ }
71
+ export interface MessageSegment {
72
+ type: string;
73
+ data?: Record<string, unknown>;
74
+ }
75
+ export interface NapCatEvent {
76
+ post_type: string;
77
+ self_id?: number | string;
78
+ message_type?: string;
79
+ sub_type?: string;
80
+ message_id?: number | string;
81
+ user_id?: number | string;
82
+ group_id?: number | string;
83
+ sender?: NapCatSender;
84
+ message?: MessageSegment[] | string;
85
+ raw_message?: string;
86
+ time?: number;
87
+ notice_type?: string;
88
+ request_type?: string;
89
+ [key: string]: unknown;
90
+ }
91
+ export type NapCatMessageEvent = NapCatEvent & {
92
+ post_type: 'message' | 'message_sent';
93
+ message_id: number | string;
94
+ user_id: number | string;
95
+ };
96
+ export interface NapCatActionRequest {
97
+ action: string;
98
+ params: Record<string, unknown>;
99
+ echo?: string;
100
+ }
101
+ export interface NapCatActionResponse {
102
+ status: string;
103
+ retcode: number;
104
+ data?: unknown;
105
+ echo?: string;
106
+ message?: string;
107
+ wording?: string;
108
+ }
109
+ export interface NapCatWireSegment {
110
+ readonly type: string;
111
+ readonly data?: Record<string, unknown>;
112
+ }
113
+ export interface ParsedSendTarget {
114
+ readonly message_type: 'private' | 'group';
115
+ readonly id: string;
116
+ }
117
+ export declare function resolveNapCatConfig(config?: NapCatAdapterConfig): ResolvedNapCatConfig;
118
+ export declare function isMessageEvent(ev: NapCatEvent): ev is NapCatMessageEvent;
119
+ export declare function getChannelId(ev: NapCatEvent): string;
120
+ /**
121
+ * 入站归一化 → ConversationRef:群消息 → kind 'group';私聊临时会话
122
+ * (sub_type 'group')→ kind 'private' + 群容器进 `parent`;其余私聊 → 'private'。
123
+ */
124
+ export declare function napcatInboundConversation(endpointKey: string, ev: NapCatEvent): ConversationRef;
125
+ /** 出站:ConversationRef → OneBot 私聊/群聊目标。 */
126
+ export declare function napcatOutboundTarget(conversation: ConversationRef): ParsedSendTarget;
127
+ export declare function formatInboundContent(ev: NapCatEvent): string;
128
+ /**
129
+ * 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
130
+ * 显示名走 metadata.nickname,见 senderNickname。
131
+ */
132
+ export declare function senderUserId(ev: NapCatEvent): string;
133
+ /** 显示名(群名片 card 优先于 nickname),没有则 undefined。 */
134
+ export declare function senderNickname(ev: NapCatEvent): string | undefined;
135
+ /**
136
+ * canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
137
+ * 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
138
+ */
139
+ export declare function mediaRefToOneBotFile(media: MediaRef): string;
140
+ /**
141
+ * Wire-encode an already-rendered outbound payload into OneBot message segments.
142
+ * 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
143
+ * 媒体段(image/audio/video)仅认 canonical `data.media`,缺失则 warn + 丢弃;
144
+ * 非媒体的 wire 段(at 等)原样透传。
145
+ */
146
+ export declare function formatOutboundSegments(payload: unknown): MessageSegment[];
147
+ export declare function buildSendAction(target: ParsedSendTarget, message: MessageSegment[]): {
148
+ action: string;
149
+ params: Record<string, unknown>;
150
+ };
151
+ /** Build WS connect URL + headers (access_token via Bearer + query). */
152
+ export declare function buildWsConnectOptions(config: NapCatWsConfig): {
153
+ readonly url: string;
154
+ readonly headers: Record<string, string>;
155
+ readonly safeUrl: string;
156
+ };
157
+ export interface NapCatHttpOptions {
158
+ readonly http_url: string;
159
+ readonly access_token?: string;
160
+ }
161
+ /**
162
+ * 向 NapCat HTTP API 发送动作请求:POST {http_url}/{action}。
163
+ * 供 connection:http 出站与纯协议测试使用。
164
+ */
165
+ export declare function callNapCatHttpAction(options: NapCatHttpOptions, action: string, params?: Record<string, unknown>): Promise<NapCatActionResponse>;