@zhin.js/adapter-napcat 1.0.1 → 1.1.2

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 (202) hide show
  1. package/CHANGELOG.md +757 -0
  2. package/{skills/napcat/PERMITS.md → PERMITS.md} +3 -3
  3. package/README.md +63 -95
  4. package/adapters/napcat/index.js +47 -0
  5. package/adapters/napcat/index.ts +64 -0
  6. package/agents/napcat/agent.json +20 -0
  7. package/agents/napcat/boundaries.md +3 -0
  8. package/agents/napcat/conventions.md +3 -0
  9. package/agents/napcat/skills/napcat-account/SKILL.md +25 -0
  10. package/agents/napcat/skills/napcat-account/tools/delete_friend/index.js +18 -0
  11. package/agents/napcat/skills/napcat-account/tools/delete_friend/index.ts +18 -0
  12. package/agents/napcat/skills/napcat-account/tools/get_user_status/index.js +16 -0
  13. package/agents/napcat/skills/napcat-account/tools/get_user_status/index.ts +16 -0
  14. package/agents/napcat/skills/napcat-account/tools/send_like/index.js +18 -0
  15. package/agents/napcat/skills/napcat-account/tools/send_like/index.ts +18 -0
  16. package/agents/napcat/skills/napcat-account/tools/set_avatar/index.js +17 -0
  17. package/agents/napcat/skills/napcat-account/tools/set_avatar/index.ts +17 -0
  18. package/agents/napcat/skills/napcat-account/tools/set_online_status/index.js +18 -0
  19. package/agents/napcat/skills/napcat-account/tools/set_online_status/index.ts +18 -0
  20. package/agents/napcat/skills/napcat-account/tools/set_profile/index.js +21 -0
  21. package/agents/napcat/skills/napcat-account/tools/set_profile/index.ts +21 -0
  22. package/agents/napcat/skills/napcat-account/tools/set_signature/index.js +17 -0
  23. package/agents/napcat/skills/napcat-account/tools/set_signature/index.ts +17 -0
  24. package/agents/napcat/skills/napcat-files-history/SKILL.md +22 -0
  25. package/agents/napcat/skills/napcat-files-history/tools/download_file/index.js +17 -0
  26. package/agents/napcat/skills/napcat-files-history/tools/download_file/index.ts +17 -0
  27. package/agents/napcat/skills/napcat-files-history/tools/get_friend_msg_history/index.js +18 -0
  28. package/agents/napcat/skills/napcat-files-history/tools/get_friend_msg_history/index.ts +18 -0
  29. package/agents/napcat/skills/napcat-files-history/tools/get_group_file_url/index.js +19 -0
  30. package/agents/napcat/skills/napcat-files-history/tools/get_group_file_url/index.ts +19 -0
  31. package/agents/napcat/skills/napcat-files-history/tools/get_group_msg_history/index.js +19 -0
  32. package/agents/napcat/skills/napcat-files-history/tools/get_group_msg_history/index.ts +19 -0
  33. package/agents/napcat/skills/napcat-files-history/tools/get_group_root_files/index.js +17 -0
  34. package/agents/napcat/skills/napcat-files-history/tools/get_group_root_files/index.ts +17 -0
  35. package/agents/napcat/skills/napcat-files-history/tools/upload_group_file/index.js +21 -0
  36. package/agents/napcat/skills/napcat-files-history/tools/upload_group_file/index.ts +21 -0
  37. package/agents/napcat/skills/napcat-group-content/SKILL.md +21 -0
  38. package/agents/napcat/skills/napcat-group-content/tools/del_group_notice/index.js +20 -0
  39. package/agents/napcat/skills/napcat-group-content/tools/del_group_notice/index.ts +20 -0
  40. package/agents/napcat/skills/napcat-group-content/tools/delete_essence_msg/index.js +19 -0
  41. package/agents/napcat/skills/napcat-group-content/tools/delete_essence_msg/index.ts +19 -0
  42. package/agents/napcat/skills/napcat-group-content/tools/get_essence_list/index.js +17 -0
  43. package/agents/napcat/skills/napcat-group-content/tools/get_essence_list/index.ts +17 -0
  44. package/agents/napcat/skills/napcat-group-content/tools/get_group_notice/index.js +17 -0
  45. package/agents/napcat/skills/napcat-group-content/tools/get_group_notice/index.ts +17 -0
  46. package/agents/napcat/skills/napcat-group-content/tools/send_group_notice/index.js +21 -0
  47. package/agents/napcat/skills/napcat-group-content/tools/send_group_notice/index.ts +21 -0
  48. package/agents/napcat/skills/napcat-group-content/tools/set_essence_msg/index.js +19 -0
  49. package/agents/napcat/skills/napcat-group-content/tools/set_essence_msg/index.ts +19 -0
  50. package/agents/napcat/skills/napcat-group-settings/SKILL.md +23 -0
  51. package/agents/napcat/skills/napcat-group-settings/tools/get_group_info_ex/index.js +17 -0
  52. package/agents/napcat/skills/napcat-group-settings/tools/get_group_info_ex/index.ts +17 -0
  53. package/agents/napcat/skills/napcat-group-settings/tools/get_group_shut_list/index.js +17 -0
  54. package/agents/napcat/skills/napcat-group-settings/tools/get_group_shut_list/index.ts +17 -0
  55. package/agents/napcat/skills/napcat-group-settings/tools/group_sign/index.js +18 -0
  56. package/agents/napcat/skills/napcat-group-settings/tools/group_sign/index.ts +18 -0
  57. package/agents/napcat/skills/napcat-group-settings/tools/set_group_portrait/index.js +20 -0
  58. package/agents/napcat/skills/napcat-group-settings/tools/set_group_portrait/index.ts +20 -0
  59. package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.js +21 -0
  60. package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.ts +21 -0
  61. package/agents/napcat/skills/napcat-media/SKILL.md +23 -0
  62. package/agents/napcat/skills/napcat-media/tools/ai_tts/index.js +19 -0
  63. package/agents/napcat/skills/napcat-media/tools/ai_tts/index.ts +19 -0
  64. package/agents/napcat/skills/napcat-media/tools/get_ai_characters/index.js +17 -0
  65. package/agents/napcat/skills/napcat-media/tools/get_ai_characters/index.ts +17 -0
  66. package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.js +20 -0
  67. package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.ts +20 -0
  68. package/agents/napcat/skills/napcat-media/tools/ocr_image/index.js +16 -0
  69. package/agents/napcat/skills/napcat-media/tools/ocr_image/index.ts +16 -0
  70. package/agents/napcat/skills/napcat-media/tools/translate/index.js +16 -0
  71. package/agents/napcat/skills/napcat-media/tools/translate/index.ts +16 -0
  72. package/agents/napcat/skills/napcat-messaging/SKILL.md +22 -0
  73. package/agents/napcat/skills/napcat-messaging/tools/forward_single_msg/index.js +22 -0
  74. package/agents/napcat/skills/napcat-messaging/tools/forward_single_msg/index.ts +28 -0
  75. package/agents/napcat/skills/napcat-messaging/tools/mark_msg_as_read/index.js +17 -0
  76. package/agents/napcat/skills/napcat-messaging/tools/mark_msg_as_read/index.ts +17 -0
  77. package/agents/napcat/skills/napcat-messaging/tools/send_forward_msg/index.js +19 -0
  78. package/agents/napcat/skills/napcat-messaging/tools/send_forward_msg/index.ts +27 -0
  79. package/agents/napcat/skills/napcat-messaging/tools/send_poke/index.js +18 -0
  80. package/agents/napcat/skills/napcat-messaging/tools/send_poke/index.ts +18 -0
  81. package/agents/napcat/skills/napcat-messaging/tools/set_emoji_reaction/index.js +18 -0
  82. package/agents/napcat/skills/napcat-messaging/tools/set_emoji_reaction/index.ts +18 -0
  83. package/agents/napcat/system.md +3 -0
  84. package/commands/napcat/endpoint/add/[id]/index.js +3 -0
  85. package/commands/napcat/endpoint/add/[id]/index.ts +3 -0
  86. package/commands/napcat/endpoint/definition.js +29 -0
  87. package/commands/napcat/endpoint/definition.ts +26 -0
  88. package/commands/napcat/endpoint/list/index.js +3 -0
  89. package/commands/napcat/endpoint/list/index.ts +3 -0
  90. package/commands/napcat/endpoint/remove/[id]/index.js +3 -0
  91. package/commands/napcat/endpoint/remove/[id]/index.ts +3 -0
  92. package/lib/client.d.ts +52 -0
  93. package/lib/client.js +62 -0
  94. package/lib/endpoint-management.d.ts +15 -0
  95. package/lib/endpoint-management.js +47 -0
  96. package/lib/http-endpoint.d.ts +27 -0
  97. package/lib/http-endpoint.js +177 -0
  98. package/lib/index.d.ts +11 -22
  99. package/lib/index.js +10 -59
  100. package/lib/napcat-inbound.d.ts +12 -5
  101. package/lib/napcat-inbound.js +28 -13
  102. package/lib/napcat-runtime-state.d.ts +1 -0
  103. package/lib/napcat-runtime-state.js +6 -0
  104. package/lib/onebot-get-msg.d.ts +16 -3
  105. package/lib/onebot-get-msg.js +53 -1
  106. package/lib/protocol.d.ts +147 -0
  107. package/lib/protocol.js +318 -0
  108. package/lib/side-event-dispatch.d.ts +7 -0
  109. package/lib/side-event-dispatch.js +36 -0
  110. package/lib/webhook.d.ts +2 -0
  111. package/lib/webhook.js +14 -0
  112. package/lib/ws-endpoint.d.ts +27 -0
  113. package/lib/ws-endpoint.js +241 -0
  114. package/lib/ws-transport.d.ts +13 -0
  115. package/lib/ws-transport.js +75 -0
  116. package/lib/ws-types.d.ts +17 -0
  117. package/lib/ws-types.js +1 -0
  118. package/lib/wss-auth.d.ts +2 -0
  119. package/lib/wss-auth.js +16 -0
  120. package/lib/wss-endpoint.d.ts +29 -0
  121. package/lib/wss-endpoint.js +196 -0
  122. package/package.json +76 -34
  123. package/plugin.js +18 -0
  124. package/schema.json +131 -0
  125. package/src/client.ts +79 -0
  126. package/src/endpoint-management.ts +72 -0
  127. package/src/http-endpoint.ts +234 -0
  128. package/src/index.ts +62 -87
  129. package/src/napcat-inbound.ts +34 -10
  130. package/src/napcat-runtime-state.ts +7 -0
  131. package/src/onebot-get-msg.ts +69 -5
  132. package/src/protocol.ts +465 -0
  133. package/src/side-event-dispatch.ts +48 -0
  134. package/src/webhook.ts +16 -0
  135. package/src/ws-endpoint.ts +301 -0
  136. package/src/ws-transport.ts +105 -0
  137. package/src/ws-types.ts +20 -0
  138. package/src/wss-auth.ts +17 -0
  139. package/src/wss-endpoint.ts +247 -0
  140. package/client/NapCatManagement.tsx +0 -113
  141. package/client/index.tsx +0 -11
  142. package/client/tsconfig.json +0 -7
  143. package/client/utils/api.ts +0 -30
  144. package/dist/index.js +0 -27
  145. package/lib/adapter.d.ts +0 -28
  146. package/lib/adapter.d.ts.map +0 -1
  147. package/lib/adapter.js +0 -94
  148. package/lib/adapter.js.map +0 -1
  149. package/lib/agent-prompt.d.ts +0 -3
  150. package/lib/agent-prompt.d.ts.map +0 -1
  151. package/lib/agent-prompt.js +0 -105
  152. package/lib/agent-prompt.js.map +0 -1
  153. package/lib/endpoint-base.d.ts +0 -143
  154. package/lib/endpoint-base.d.ts.map +0 -1
  155. package/lib/endpoint-base.js +0 -334
  156. package/lib/endpoint-base.js.map +0 -1
  157. package/lib/endpoint-http.d.ts +0 -18
  158. package/lib/endpoint-http.d.ts.map +0 -1
  159. package/lib/endpoint-http.js +0 -119
  160. package/lib/endpoint-http.js.map +0 -1
  161. package/lib/endpoint-ws-client.d.ts +0 -21
  162. package/lib/endpoint-ws-client.d.ts.map +0 -1
  163. package/lib/endpoint-ws-client.js +0 -165
  164. package/lib/endpoint-ws-client.js.map +0 -1
  165. package/lib/endpoint-ws-server.d.ts +0 -21
  166. package/lib/endpoint-ws-server.d.ts.map +0 -1
  167. package/lib/endpoint-ws-server.js +0 -150
  168. package/lib/endpoint-ws-server.js.map +0 -1
  169. package/lib/index.d.ts.map +0 -1
  170. package/lib/index.js.map +0 -1
  171. package/lib/napcat-inbound.d.ts.map +0 -1
  172. package/lib/napcat-inbound.js.map +0 -1
  173. package/lib/onebot-get-msg.d.ts.map +0 -1
  174. package/lib/onebot-get-msg.js.map +0 -1
  175. package/lib/routes.d.ts +0 -4
  176. package/lib/routes.d.ts.map +0 -1
  177. package/lib/routes.js +0 -61
  178. package/lib/routes.js.map +0 -1
  179. package/lib/tools.d.ts +0 -8
  180. package/lib/tools.d.ts.map +0 -1
  181. package/lib/tools.js +0 -605
  182. package/lib/tools.js.map +0 -1
  183. package/lib/types.d.ts +0 -293
  184. package/lib/types.d.ts.map +0 -1
  185. package/lib/types.js +0 -6
  186. package/lib/types.js.map +0 -1
  187. package/lib/typing-indicator.d.ts +0 -45
  188. package/lib/typing-indicator.d.ts.map +0 -1
  189. package/lib/typing-indicator.js +0 -132
  190. package/lib/typing-indicator.js.map +0 -1
  191. package/plugin.yml +0 -3
  192. package/skills/napcat/SKILL.md +0 -311
  193. package/src/adapter.ts +0 -101
  194. package/src/agent-prompt.ts +0 -114
  195. package/src/endpoint-base.ts +0 -376
  196. package/src/endpoint-http.ts +0 -109
  197. package/src/endpoint-ws-client.ts +0 -164
  198. package/src/endpoint-ws-server.ts +0 -150
  199. package/src/routes.ts +0 -61
  200. package/src/tools.ts +0 -632
  201. package/src/types.ts +0 -279
  202. package/src/typing-indicator.ts +0 -184
@@ -0,0 +1,234 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
2
+ /**
3
+ * NapCat HTTP endpoint — POST inbound events + HTTP API outbound.
4
+ */
5
+ import type { IncomingMessage, ServerResponse } from 'node:http';
6
+ import {
7
+ createRecallEndpointControl,
8
+ type EndpointControl,
9
+ type EndpointManagement,
10
+ type EndpointSendRequest,
11
+ } from 'zhin.js/adapter';
12
+ import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
13
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
14
+ import type { CapabilityId } from 'zhin.js';
15
+ import { createNapCatEndpointManagement } from './endpoint-management.js';
16
+ import {
17
+ InboundMessageDeduper,
18
+ isNapCatBotMentioned,
19
+ isSelfMessage,
20
+ normalizeMessage,
21
+ } from './napcat-inbound.js';
22
+ import {
23
+ buildSendAction,
24
+ callNapCatHttpAction,
25
+ formatInboundContent,
26
+ formatOutboundSegments,
27
+ isMessageEvent,
28
+ napcatInboundConversation,
29
+ napcatOutboundTarget,
30
+ senderNickname,
31
+ senderUserId,
32
+ type NapCatEvent,
33
+ type NapCatHttpConfig,
34
+ } from './protocol.js';
35
+ import { receiveNapCatSideEvent } from './side-event-dispatch.js';
36
+ import { createNapCatContentPort } from './onebot-get-msg.js';
37
+ import { NapcatClient } from './client.js';
38
+ import { readRequestBody } from './webhook.js';
39
+ import { NapCatWsEndpoint } from './ws-endpoint.js';
40
+ import { verifyNapCatAccessToken } from './wss-auth.js';
41
+
42
+ export interface NapCatHttpEndpointOptions {
43
+ readonly id: CapabilityId;
44
+ readonly http: HttpHost;
45
+ readonly config: NapCatHttpConfig;
46
+ readonly callHttpAction?: typeof callNapCatHttpAction;
47
+ }
48
+
49
+ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
50
+ readonly client = new NapcatClient((action, params) => this.#callApi(action, params));
51
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
52
+
53
+ readonly #options: NapCatHttpEndpointOptions;
54
+ readonly #inboundDeduper = new InboundMessageDeduper();
55
+ readonly management: EndpointManagement = createNapCatEndpointManagement(this.client);
56
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
57
+ readonly content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
58
+ readonly #callHttpAction: typeof callNapCatHttpAction;
59
+ #routeReleases: HttpRouteRegistration[] = [];
60
+ #open = false;
61
+ #started = false;
62
+
63
+ constructor(options: NapCatHttpEndpointOptions) {
64
+ super();
65
+ this.#logger = getAdapterLogger('napcat', options.config.id);
66
+ this.#options = options;
67
+ this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
68
+ }
69
+
70
+ async start(): Promise<void> {
71
+ if (this.#started) return;
72
+ this.#started = true;
73
+ this.#setupRoutes();
74
+ this.#logger.info(formatCompact({
75
+ op: 'listen',
76
+ endpoint: this.#options.config.id,
77
+ mode: 'http',
78
+ path: this.#options.config.post_path,
79
+ }));
80
+ }
81
+
82
+ open(): void {
83
+ this.#open = true;
84
+ }
85
+
86
+ close(): void {
87
+ this.#open = false;
88
+ }
89
+
90
+ async stop(): Promise<void> {
91
+ this.#open = false;
92
+ for (const release of this.#routeReleases.splice(0)) release();
93
+ this.#inboundDeduper.clear();
94
+ this.#started = false;
95
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
96
+ }
97
+
98
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
99
+ const message = formatOutboundSegments(payload);
100
+ const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
101
+ const resp = await this.#callHttpAction(
102
+ {
103
+ http_url: this.#options.config.http_url,
104
+ access_token: this.#options.config.access_token,
105
+ },
106
+ action,
107
+ params,
108
+ );
109
+ const data = resp.data as { message_id?: number | string } | undefined;
110
+ const messageId = data?.message_id != null ? String(data.message_id) : '';
111
+ this.#logger.debug(formatCompact({
112
+ op: 'napcat_send',
113
+ endpoint: this.#options.config.id,
114
+ target: `${conversation.kind}:${conversation.id}`,
115
+ messageId,
116
+ }));
117
+ return messageId;
118
+ }
119
+
120
+ async recallMessage(messageId: string): Promise<void> {
121
+ if (!messageId) return;
122
+ await this.client.callApi('delete_msg', { message_id: Number(messageId) });
123
+ }
124
+
125
+ #callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
126
+ return this.#callHttpAction(
127
+ {
128
+ http_url: this.#options.config.http_url,
129
+ access_token: this.#options.config.access_token,
130
+ },
131
+ action,
132
+ params,
133
+ ).then((resp) => resp.data);
134
+ }
135
+
136
+ admit(ev: NapCatEvent): void {
137
+ if (!this.#open) return;
138
+ void this.emitPlatform(napCatPlatformEventName(ev), ev).catch((error) => {
139
+ this.#logger.warn(formatCompact({
140
+ op: 'napcat_platform_event_failed',
141
+ error: error instanceof Error ? error.message : String(error),
142
+ }));
143
+ });
144
+ if (!isMessageEvent(ev)) {
145
+ receiveNapCatSideEvent(
146
+ (name, payload) => this.emit(name, payload),
147
+ this.#options.config.id,
148
+ this.client,
149
+ ev,
150
+ this.#logger,
151
+ );
152
+ return;
153
+ }
154
+ if (isSelfMessage(ev)) return;
155
+ const msgId = String(ev.message_id);
156
+ if (!this.#inboundDeduper.shouldProcess(msgId)) return;
157
+ if (Array.isArray(ev.message) || typeof ev.message === 'string') {
158
+ ev = { ...ev, message: normalizeMessage(ev.message) };
159
+ }
160
+ const conversation = napcatInboundConversation(String(this.#options.id), ev);
161
+ const nickname = senderNickname(ev);
162
+ const mentioned = isNapCatBotMentioned(ev);
163
+ void this.emit('message.receive', {
164
+ conversation,
165
+ message: { conversation, id: msgId },
166
+ content: formatInboundContent(ev),
167
+ sender: {
168
+ id: senderUserId(ev),
169
+ name: nickname,
170
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
171
+ },
172
+ endpointId: this.#options.config.id,
173
+ ...(mentioned ? { mentioned: true } : {}),
174
+ metadata: Object.freeze({
175
+ message_type: ev.message_type,
176
+ user_id: ev.user_id != null ? String(ev.user_id) : undefined,
177
+ group_id: ev.group_id != null ? String(ev.group_id) : undefined,
178
+ time: ev.time,
179
+ self_id: ev.self_id != null ? String(ev.self_id) : undefined,
180
+ role: ev.sender?.role,
181
+ ...(nickname ? { nickname } : {}),
182
+ }),
183
+ }).catch((err) => {
184
+ this.#logger.warn(formatCompact({
185
+ op: 'napcat_gateway_receive_failed',
186
+ target: `${conversation.kind}:${conversation.id}`,
187
+ error: err instanceof Error ? err.message : String(err),
188
+ }));
189
+ });
190
+ }
191
+
192
+ #setupRoutes(): void {
193
+ const path = this.#options.config.post_path;
194
+ this.#routeReleases.push(
195
+ this.#options.http.route('POST', path, async (request, response) => {
196
+ await this.#handlePost(request, response);
197
+ }, { summary: 'NapCat HTTP event callback', tags: ['napcat'] }),
198
+ );
199
+ }
200
+
201
+ async #handlePost(request: IncomingMessage, response: ServerResponse): Promise<void> {
202
+ try {
203
+ if (!verifyNapCatAccessToken(this.#options.config.access_token, request)) {
204
+ response.writeHead(403, { 'Content-Type': 'application/json' });
205
+ response.end(JSON.stringify({ message: 'Unauthorized' }));
206
+ return;
207
+ }
208
+ const raw = await readRequestBody(request);
209
+ let ev: NapCatEvent;
210
+ try {
211
+ ev = JSON.parse(raw) as NapCatEvent;
212
+ } catch {
213
+ response.writeHead(400, { 'Content-Type': 'application/json' });
214
+ response.end(JSON.stringify({ message: 'Invalid JSON' }));
215
+ return;
216
+ }
217
+ if (this.#open) this.admit(ev);
218
+ response.writeHead(200, { 'Content-Type': 'application/json' });
219
+ response.end(JSON.stringify({ status: 'ok' }));
220
+ } catch (error) {
221
+ this.#logger.error('NapCat HTTP webhook error:', error);
222
+ if (!response.headersSent) {
223
+ response.writeHead(500, { 'Content-Type': 'application/json' });
224
+ response.end(JSON.stringify({ message: 'Internal Server Error' }));
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ function napCatPlatformEventName(ev: NapCatEvent): string {
231
+ return [ev.post_type, ev.message_type ?? ev.notice_type ?? ev.request_type, ev.sub_type]
232
+ .filter(Boolean)
233
+ .join('.');
234
+ }
package/src/index.ts CHANGED
@@ -1,98 +1,73 @@
1
- /**
2
- * NapCat 适配器入口
3
- * 支持 OneBot11 标准 + go-cqhttp 扩展 + NapCat 独有 API
4
- * 连接方式:正向 WS / 反向 WS / HTTP
5
- */
6
- import path from 'path';
7
- import {
8
- usePlugin,
9
- type Plugin,
10
- type Context,
11
- type IGroupManagement,
12
- createGroupManagementTools,
13
- registerDefaultGroupPlatformPermitChecker,
14
- type ToolFeature,
15
- registerAgentPromptContributor,
16
- unregisterAgentPromptContributor,
17
- } from 'zhin.js';
18
- import type { Router } from '@zhin.js/host-router';
19
- import { PageManager } from '@zhin.js/host-api';
20
- import { NapCatAdapter } from './adapter.js';
21
- import { createNapCatTools } from './tools.js';
22
- import { createNapCatAgentPromptContributor } from './agent-prompt.js';
23
- import { registerRoutes } from './routes.js';
1
+ export {
2
+ buildSendAction,
3
+ buildWsConnectOptions,
4
+ callNapCatHttpAction,
5
+ formatInboundContent,
6
+ formatOutboundSegments,
7
+ getChannelId,
8
+ isMessageEvent,
9
+ mediaRefToOneBotFile,
10
+ napcatInboundConversation,
11
+ napcatOutboundTarget,
12
+ resolveNapCatConfig,
13
+ senderNickname,
14
+ senderUserId,
15
+ type MessageSegment,
16
+ type NapCatActionRequest,
17
+ type NapCatActionResponse,
18
+ type NapCatConfigBase,
19
+ type NapCatEndpointConfig,
20
+ type NapCatEvent,
21
+ type NapCatHttpConfig,
22
+ type NapCatMessageEvent,
23
+ type NapCatSender,
24
+ type NapCatWireSegment,
25
+ type NapCatWsConfig,
26
+ type NapCatWssConfig,
27
+ type ParsedSendTarget,
28
+ type ResolvedNapCatConfig,
29
+ } from './protocol.js';
24
30
 
25
- export * from './types.js';
26
- export { NapCatWsClient } from './endpoint-ws-client.js';
27
- export { NapCatWsServer } from './endpoint-ws-server.js';
28
- export { NapCatHttpEndpoint } from './endpoint-http.js';
29
- export { NapCatAdapter, type NapCatEndpoint } from './adapter.js';
30
- export { NapCatEndpointBase } from './endpoint-base.js';
31
31
  export {
32
- NapCatTypingIndicatorManager,
33
- enableTypingIndicator,
34
- type NapCatTypingIndicatorConfig,
35
- } from './typing-indicator.js';
32
+ InboundMessageDeduper,
33
+ isNapCatBotMentioned,
34
+ isSelfMessage,
35
+ normalizeMessage,
36
+ } from './napcat-inbound.js';
36
37
 
37
- declare module 'zhin.js' {
38
- namespace Plugin {
39
- interface Contexts {
40
- web: PageManager;
41
- router: Router;
42
- }
43
- }
44
- interface Adapters {
45
- napcat: NapCatAdapter;
46
- }
47
- }
38
+ export {
39
+ napcatClient,
40
+ type NapcatClient,
41
+ type NapcatClientEventMap,
42
+ } from './client.js';
48
43
 
49
- const plugin = usePlugin();
50
- const { provide, useContext } = plugin;
44
+ export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
51
45
 
52
- // ── 适配器注册 ─────────────────────────────────────────────────────
53
- provide({
54
- name: 'napcat',
55
- description: 'NapCatQQ 适配器(OneBot11 + go-cqhttp + NapCat 扩展,正向/反向 WS + HTTP)',
56
- mounted: async (p: Plugin) => {
57
- registerAgentPromptContributor(createNapCatAgentPromptContributor());
58
- const adapter = new NapCatAdapter(p);
59
- await adapter.start();
60
- return adapter;
61
- },
62
- dispose: async (adapter: NapCatAdapter) => {
63
- unregisterAgentPromptContributor('napcat');
64
- await adapter.stop();
65
- },
66
- } as unknown as Context<'napcat'>);
46
+ export {
47
+ NapCatWsEndpoint,
48
+ type NapCatWsEndpointOptions,
49
+ } from './ws-endpoint.js';
67
50
 
68
- // ── AI 工具注册 ────────────────────────────────────────────────────
69
- useContext('tool', 'napcat', (toolService: ToolFeature, napcat: NapCatAdapter) => {
70
- const disposers: (() => void)[] = [];
71
- disposers.push(registerDefaultGroupPlatformPermitChecker('napcat'));
51
+ export {
52
+ NapCatWssEndpoint,
53
+ type NapCatWssEndpointOptions,
54
+ } from './wss-endpoint.js';
72
55
 
73
- const groupTools = createGroupManagementTools(
74
- napcat as unknown as IGroupManagement,
75
- 'napcat',
76
- );
77
- disposers.push(...groupTools.map(t => toolService.addTool(t, plugin.name)));
56
+ export {
57
+ NapCatHttpEndpoint,
58
+ type NapCatHttpEndpointOptions,
59
+ } from './http-endpoint.js';
78
60
 
79
- const napcatTools = createNapCatTools(napcat);
80
- disposers.push(...napcatTools.map(t => toolService.addTool(t, plugin.name)));
61
+ export type { NapCatWsSocket, NapCatWsCreateOptions } from './ws-types.js';
81
62
 
82
- return () => disposers.forEach(d => d());
83
- });
63
+ export {
64
+ callNapCatWsAction,
65
+ decodeWsPayload,
66
+ handleNapCatWsMessage,
67
+ rejectAllPending,
68
+ startNapCatHeartbeat,
69
+ } from './ws-transport.js';
84
70
 
85
- // ── Web 控制台入口 ─────────────────────────────────────────────────
86
- useContext('web', (pageManager) => {
87
- pageManager.addEntry({
88
- id: 'napcat',
89
- development: path.resolve(import.meta.dirname, '../client/index.tsx'),
90
- production: path.resolve(import.meta.dirname, '../dist/index.js'),
91
- meta: { name: 'NapCat' },
92
- });
93
- });
71
+ export { verifyNapCatAccessToken } from './wss-auth.js';
94
72
 
95
- // ── HTTP 路由 ──────────────────────────────────────────────────────
96
- useContext('router', 'napcat', async (router: Router, napcat: NapCatAdapter) => {
97
- registerRoutes(router, napcat);
98
- });
73
+ export { readRequestBody } from './webhook.js';
@@ -1,7 +1,7 @@
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
 
6
6
  const DEDUPE_TTL_MS = 120_000;
7
7
 
@@ -24,7 +24,11 @@ export class InboundMessageDeduper {
24
24
  }
25
25
 
26
26
  /** 判断是否为 bot 自身发出的消息 */
27
- export function isSelfMessage(event: NapCatMessageEvent): boolean {
27
+ export function isSelfMessage(event: NapCatMessageEvent | {
28
+ post_type?: string;
29
+ self_id?: number | string;
30
+ user_id?: number | string;
31
+ }): boolean {
28
32
  if (event.post_type === 'message_sent') return true;
29
33
  if (event.self_id != null && event.user_id != null) {
30
34
  return Number(event.self_id) === Number(event.user_id);
@@ -44,14 +48,34 @@ export function normalizeMessage(message: MessageSegment[] | string): MessageSeg
44
48
  return [];
45
49
  }
46
50
 
51
+ const CQ_AT_RE = /\[CQ:at,qq=([^\],\s]+)\]/g;
52
+
53
+ function atTargetMatchesUin(target: unknown, uin: string): boolean {
54
+ const qq = String(target ?? '').trim();
55
+ return Boolean(qq) && qq !== 'all' && Number(qq) === Number(uin);
56
+ }
57
+
47
58
  /**
48
- * 生成用于 notice / request 事件的去重 key
59
+ * OneBot11 mentioned 判定:消息段 {type:'at', data:{qq}} 的 qq 等于本机 uin
60
+ * (事件 self_id)时为 true;`qq === 'all'` 不算。兼容 CQ 字符串形式。
49
61
  */
50
- export function resolveSideEventDedupeKey(event: any, prefix: string): string {
51
- const parts = [prefix, event.time, event.notice_type || event.request_type];
52
- if (event.group_id) parts.push(event.group_id);
53
- if (event.user_id) parts.push(event.user_id);
54
- if (event.message_id) parts.push(event.message_id);
55
- if (event.flag) parts.push(event.flag);
56
- return parts.join(':');
62
+ export function isNapCatBotMentioned(ev: {
63
+ self_id?: number | string;
64
+ message?: MessageSegment[] | string;
65
+ }): boolean {
66
+ if (ev.self_id == null) return false;
67
+ const uin = String(ev.self_id).trim();
68
+ if (!uin) return false;
69
+ const segments = normalizeMessage(ev.message ?? []);
70
+ for (const seg of segments) {
71
+ if (seg.type === 'at' && atTargetMatchesUin(seg.data?.qq, uin)) return true;
72
+ }
73
+ const text = segments
74
+ .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
75
+ .join('');
76
+ if (!text) return false;
77
+ for (const match of text.matchAll(CQ_AT_RE)) {
78
+ if (atTargetMatchesUin(match[1], uin)) return true;
79
+ }
80
+ return false;
57
81
  }
@@ -0,0 +1,7 @@
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
+
7
+ export const napcatRuntimeStateToken = defineEndpointRuntimeStateToken('napcat');
@@ -1,20 +1,26 @@
1
- import type { MessageSegment, QuotedMessagePayload } from 'zhin.js';
1
+ export interface NapcatMessageLookup {
2
+ messageId: string;
3
+ sender?: { id: string; name: string };
4
+ content: Array<{ type: string; data?: Record<string, unknown> }>;
5
+ raw?: string;
6
+ time?: number;
7
+ }
2
8
 
3
9
  export function parseOneBotGetMsgResponse(
4
10
  messageId: string,
5
11
  data: unknown,
6
- ): QuotedMessagePayload {
12
+ ): NapcatMessageLookup {
7
13
  const record =
8
14
  data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
9
- let content: QuotedMessagePayload['content'] = [];
15
+ let content: NapcatMessageLookup['content'] = [];
10
16
  if (Array.isArray(record.message)) {
11
- content = record.message as MessageSegment[];
17
+ content = record.message as NapcatMessageLookup['content'];
12
18
  } else if (typeof record.raw_message === 'string' && record.raw_message) {
13
19
  content = [{ type: 'text', data: { text: record.raw_message } }];
14
20
  }
15
21
 
16
22
  const senderRaw = record.sender;
17
- let sender: QuotedMessagePayload['sender'];
23
+ let sender: NapcatMessageLookup['sender'];
18
24
  if (senderRaw && typeof senderRaw === 'object') {
19
25
  const s = senderRaw as Record<string, unknown>;
20
26
  sender = {
@@ -31,3 +37,61 @@ export function parseOneBotGetMsgResponse(
31
37
  time: typeof record.time === 'number' ? record.time : undefined,
32
38
  };
33
39
  }
40
+
41
+ export function createNapCatContentPort(
42
+ callApi: (action: string, params?: Record<string, unknown>) => Promise<unknown>,
43
+ ): EndpointContentPort {
44
+ return Object.freeze({
45
+ async resolve(reference: ConversationReference, context: EndpointContentResolveContext) {
46
+ try {
47
+ context.signal.throwIfAborted();
48
+ if (reference.kind === 'media') {
49
+ return reference.media.kind === 'file'
50
+ ? Object.freeze({ status: 'unsupported' as const, code: 'napcat_opaque_media_requires_platform_file_metadata' })
51
+ : Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
52
+ }
53
+ if (reference.kind === 'message') {
54
+ const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi('get_msg', { message_id: reference.message.id }));
55
+ context.signal.throwIfAborted();
56
+ const value: ConversationMessage = Object.freeze({
57
+ ref: reference.message,
58
+ ...(parsed.sender?.id ? { actor: Object.freeze({ id: parsed.sender.id, ...(parsed.sender.name ? { displayName: parsed.sender.name } : {}) }) } : {}),
59
+ segments: canonicalSegments(parsed.content),
60
+ timestamp: toMillis(parsed.time),
61
+ });
62
+ return Object.freeze({ status: 'resolved' as const, reference, value });
63
+ }
64
+ const raw = await callApi('get_forward_msg', { message_id: reference.forwardId, id: reference.forwardId }) as Record<string, unknown>;
65
+ context.signal.throwIfAborted();
66
+ const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
67
+ const value: readonly ForwardEntry[] = Object.freeze(nodes.map((node) => {
68
+ const row = node as Record<string, unknown>;
69
+ const sender = row.sender as Record<string, unknown> | undefined;
70
+ return Object.freeze({
71
+ ...(sender?.user_id != null ? { actor: Object.freeze({ id: String(sender.user_id), ...(sender.nickname ? { displayName: String(sender.nickname) } : {}) }) } : {}),
72
+ segments: canonicalSegments(Array.isArray(row.content) ? row.content as never[] : []),
73
+ timestamp: toMillis(row.time),
74
+ });
75
+ }));
76
+ return Object.freeze({ status: 'resolved' as const, reference, value });
77
+ } catch (error) {
78
+ if (context.signal.aborted) return Object.freeze({ status: 'expired' as const, code: 'turn_aborted' });
79
+ return Object.freeze({ status: 'failed' as const, code: 'napcat_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
80
+ }
81
+ },
82
+ });
83
+ }
84
+
85
+ function canonicalSegments(content: Array<{ type: string; data?: Record<string, unknown> }>) {
86
+ return Object.freeze(content.map((segment) => Object.freeze({
87
+ type: segment.type,
88
+ data: Object.freeze(segment.data ?? {}),
89
+ })));
90
+ }
91
+
92
+ function toMillis(value: unknown): number {
93
+ const time = Number(value);
94
+ return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
95
+ }
96
+ import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
97
+ import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';