@zhin.js/adapter-napcat 3.0.1 → 4.0.1

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 (140) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +43 -96
  3. package/adapters/napcat.ts +39 -0
  4. package/agent/tools/ai_tts.ts +21 -0
  5. package/agent/tools/del_group_notice.ts +22 -0
  6. package/agent/tools/delete_essence_msg.ts +21 -0
  7. package/agent/tools/delete_friend.ts +20 -0
  8. package/agent/tools/download_file.ts +19 -0
  9. package/agent/tools/forward_single_msg.ts +32 -0
  10. package/agent/tools/get_ai_characters.ts +19 -0
  11. package/agent/tools/get_essence_list.ts +19 -0
  12. package/agent/tools/get_friend_msg_history.ts +20 -0
  13. package/agent/tools/get_group_file_url.ts +21 -0
  14. package/agent/tools/get_group_info_ex.ts +19 -0
  15. package/agent/tools/get_group_msg_history.ts +21 -0
  16. package/agent/tools/get_group_notice.ts +19 -0
  17. package/agent/tools/get_group_root_files.ts +19 -0
  18. package/agent/tools/get_group_shut_list.ts +19 -0
  19. package/agent/tools/get_mini_app_ark.ts +22 -0
  20. package/agent/tools/get_user_status.ts +18 -0
  21. package/agent/tools/group_sign.ts +20 -0
  22. package/agent/tools/mark_msg_as_read.ts +19 -0
  23. package/agent/tools/ocr_image.ts +18 -0
  24. package/agent/tools/send_forward_msg.ts +31 -0
  25. package/agent/tools/send_group_notice.ts +23 -0
  26. package/agent/tools/send_like.ts +20 -0
  27. package/agent/tools/send_poke.ts +20 -0
  28. package/agent/tools/set_avatar.ts +19 -0
  29. package/agent/tools/set_emoji_reaction.ts +20 -0
  30. package/agent/tools/set_essence_msg.ts +21 -0
  31. package/agent/tools/set_group_portrait.ts +22 -0
  32. package/agent/tools/set_online_status.ts +20 -0
  33. package/agent/tools/set_profile.ts +23 -0
  34. package/agent/tools/set_signature.ts +19 -0
  35. package/agent/tools/set_title.ts +23 -0
  36. package/agent/tools/translate.ts +18 -0
  37. package/agent/tools/upload_group_file.ts +23 -0
  38. package/lib/http-endpoint.d.ts +26 -0
  39. package/lib/http-endpoint.js +147 -0
  40. package/lib/index.d.ts +11 -21
  41. package/lib/index.js +10 -59
  42. package/lib/napcat-agent-deps.d.ts +51 -0
  43. package/lib/napcat-agent-deps.js +33 -0
  44. package/lib/napcat-inbound.d.ts +12 -5
  45. package/lib/napcat-inbound.js +28 -13
  46. package/lib/onebot-get-msg.d.ts +13 -2
  47. package/lib/onebot-get-msg.js +0 -1
  48. package/lib/protocol.d.ts +151 -0
  49. package/lib/protocol.js +217 -0
  50. package/lib/webhook.d.ts +2 -0
  51. package/lib/webhook.js +14 -0
  52. package/lib/ws-endpoint.d.ts +61 -0
  53. package/lib/ws-endpoint.js +357 -0
  54. package/lib/ws-transport.d.ts +13 -0
  55. package/lib/ws-transport.js +75 -0
  56. package/lib/ws-types.d.ts +17 -0
  57. package/lib/ws-types.js +1 -0
  58. package/lib/wss-auth.d.ts +2 -0
  59. package/lib/wss-auth.js +16 -0
  60. package/lib/wss-endpoint.d.ts +25 -0
  61. package/lib/wss-endpoint.js +157 -0
  62. package/package.json +48 -32
  63. package/plugin.ts +13 -0
  64. package/schema.json +48 -0
  65. package/src/http-endpoint.ts +197 -0
  66. package/src/index.ts +66 -82
  67. package/src/napcat-agent-deps.ts +95 -0
  68. package/src/napcat-inbound.ts +34 -10
  69. package/src/onebot-get-msg.ts +8 -2
  70. package/src/protocol.ts +373 -0
  71. package/src/webhook.ts +16 -0
  72. package/src/ws-endpoint.ts +452 -0
  73. package/src/ws-transport.ts +105 -0
  74. package/src/ws-types.ts +20 -0
  75. package/src/wss-auth.ts +17 -0
  76. package/src/wss-endpoint.ts +205 -0
  77. package/client/NapCatManagement.tsx +0 -113
  78. package/client/index.tsx +0 -11
  79. package/client/tsconfig.json +0 -7
  80. package/client/utils/api.ts +0 -30
  81. package/dist/index.js +0 -27
  82. package/lib/adapter.d.ts +0 -36
  83. package/lib/adapter.d.ts.map +0 -1
  84. package/lib/adapter.js +0 -102
  85. package/lib/adapter.js.map +0 -1
  86. package/lib/agent-prompt.d.ts +0 -3
  87. package/lib/agent-prompt.d.ts.map +0 -1
  88. package/lib/agent-prompt.js +0 -106
  89. package/lib/agent-prompt.js.map +0 -1
  90. package/lib/endpoint-base.d.ts +0 -144
  91. package/lib/endpoint-base.d.ts.map +0 -1
  92. package/lib/endpoint-base.js +0 -341
  93. package/lib/endpoint-base.js.map +0 -1
  94. package/lib/endpoint-http.d.ts +0 -17
  95. package/lib/endpoint-http.d.ts.map +0 -1
  96. package/lib/endpoint-http.js +0 -106
  97. package/lib/endpoint-http.js.map +0 -1
  98. package/lib/endpoint-ws-client.d.ts +0 -20
  99. package/lib/endpoint-ws-client.d.ts.map +0 -1
  100. package/lib/endpoint-ws-client.js +0 -139
  101. package/lib/endpoint-ws-client.js.map +0 -1
  102. package/lib/endpoint-ws-server.d.ts +0 -20
  103. package/lib/endpoint-ws-server.d.ts.map +0 -1
  104. package/lib/endpoint-ws-server.js +0 -137
  105. package/lib/endpoint-ws-server.js.map +0 -1
  106. package/lib/index.d.ts.map +0 -1
  107. package/lib/index.js.map +0 -1
  108. package/lib/napcat-inbound.d.ts.map +0 -1
  109. package/lib/napcat-inbound.js.map +0 -1
  110. package/lib/onebot-get-msg.d.ts.map +0 -1
  111. package/lib/onebot-get-msg.js.map +0 -1
  112. package/lib/routes.d.ts +0 -4
  113. package/lib/routes.d.ts.map +0 -1
  114. package/lib/routes.js +0 -61
  115. package/lib/routes.js.map +0 -1
  116. package/lib/segment-mapper.d.ts +0 -2
  117. package/lib/segment-mapper.d.ts.map +0 -1
  118. package/lib/segment-mapper.js +0 -2
  119. package/lib/segment-mapper.js.map +0 -1
  120. package/lib/tools.d.ts +0 -8
  121. package/lib/tools.d.ts.map +0 -1
  122. package/lib/tools.js +0 -605
  123. package/lib/tools.js.map +0 -1
  124. package/lib/types.d.ts +0 -275
  125. package/lib/types.d.ts.map +0 -1
  126. package/lib/types.js +0 -6
  127. package/lib/types.js.map +0 -1
  128. package/plugin.yml +0 -3
  129. package/src/adapter.ts +0 -109
  130. package/src/agent-prompt.ts +0 -116
  131. package/src/endpoint-base.ts +0 -386
  132. package/src/endpoint-http.ts +0 -95
  133. package/src/endpoint-ws-client.ts +0 -137
  134. package/src/endpoint-ws-server.ts +0 -136
  135. package/src/routes.ts +0 -61
  136. package/src/segment-mapper.ts +0 -1
  137. package/src/tools.ts +0 -632
  138. package/src/types.ts +0 -266
  139. /package/{skills/napcat → agent}/PERMITS.md +0 -0
  140. /package/{skills/napcat/SKILL.md → agent/skills/napcat.md} +0 -0
@@ -0,0 +1,373 @@
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
+
7
+ /** Transitional legacy endpoint row (`endpoints[]` with `context: napcat`). */
8
+ export interface NapCatLegacyEndpointRow {
9
+ readonly context?: string;
10
+ readonly connection?: 'ws' | 'wss' | 'http';
11
+ readonly name?: string;
12
+ readonly access_token?: string;
13
+ readonly url?: string;
14
+ readonly path?: string;
15
+ readonly http_url?: string;
16
+ readonly post_path?: string;
17
+ readonly reconnect_interval?: number;
18
+ readonly heartbeat_interval?: number;
19
+ readonly poll_interval?: number;
20
+ }
21
+
22
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
23
+ export interface NapCatAdapterConfig {
24
+ readonly connection?: 'ws' | 'wss' | 'http';
25
+ readonly name?: 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
+
38
+ export interface NapCatConfigBase {
39
+ readonly context: 'napcat';
40
+ readonly name: string;
41
+ readonly access_token?: string;
42
+ }
43
+
44
+ /** 正向 WebSocket:应用连 NapCat WS */
45
+ export interface NapCatWsConfig extends NapCatConfigBase {
46
+ readonly connection: 'ws';
47
+ readonly url: string;
48
+ readonly reconnect_interval: number;
49
+ readonly heartbeat_interval: number;
50
+ }
51
+
52
+ /** 反向 WebSocket:httpHostToken WS upgrade 入站/出站 */
53
+ export interface NapCatWssConfig extends NapCatConfigBase {
54
+ readonly connection: 'wss';
55
+ readonly path: string;
56
+ readonly heartbeat_interval: number;
57
+ }
58
+
59
+ /** HTTP API + POST 上报:httpHostToken POST 入站 + http_url/{action} 出站 */
60
+ export interface NapCatHttpConfig extends NapCatConfigBase {
61
+ readonly connection: 'http';
62
+ readonly http_url: string;
63
+ readonly post_path: string;
64
+ readonly poll_interval: number;
65
+ }
66
+
67
+ export type ResolvedNapCatConfig = NapCatWsConfig | NapCatWssConfig | NapCatHttpConfig;
68
+ export type NapCatEndpointConfig = ResolvedNapCatConfig;
69
+
70
+ export interface NapCatSender {
71
+ readonly role?: string;
72
+ readonly nickname?: string;
73
+ readonly card?: string;
74
+ readonly title?: string;
75
+ readonly user_id?: number;
76
+ }
77
+
78
+ export interface MessageSegment {
79
+ type: string;
80
+ data?: Record<string, unknown>;
81
+ }
82
+
83
+ export interface NapCatEvent {
84
+ post_type: string;
85
+ self_id?: number | string;
86
+ message_type?: string;
87
+ sub_type?: string;
88
+ message_id?: number | string;
89
+ user_id?: number | string;
90
+ group_id?: number | string;
91
+ sender?: NapCatSender;
92
+ message?: MessageSegment[] | string;
93
+ raw_message?: string;
94
+ time?: number;
95
+ notice_type?: string;
96
+ request_type?: string;
97
+ [key: string]: unknown;
98
+ }
99
+
100
+ export type NapCatMessageEvent = NapCatEvent & {
101
+ post_type: 'message' | 'message_sent';
102
+ message_id: number | string;
103
+ user_id: number | string;
104
+ };
105
+
106
+ export interface NapCatActionRequest {
107
+ action: string;
108
+ params: Record<string, unknown>;
109
+ echo?: string;
110
+ }
111
+
112
+ export interface NapCatActionResponse {
113
+ status: string;
114
+ retcode: number;
115
+ data?: unknown;
116
+ echo?: string;
117
+ message?: string;
118
+ wording?: string;
119
+ }
120
+
121
+ export interface NapCatWireSegment {
122
+ readonly type: string;
123
+ readonly data?: Record<string, unknown>;
124
+ }
125
+
126
+ export interface ParsedSendTarget {
127
+ readonly message_type: 'private' | 'group';
128
+ readonly id: string;
129
+ }
130
+
131
+ function normalizeConnection(
132
+ connection: string | undefined,
133
+ ): 'ws' | 'wss' | 'http' {
134
+ if (connection === 'wss' || connection === 'http') return connection;
135
+ return 'ws';
136
+ }
137
+
138
+ export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedNapCatConfig {
139
+ const entry = config.endpoints?.find((item) => item.context === 'napcat');
140
+ const connection = normalizeConnection(config.connection ?? entry?.connection);
141
+ const name = (typeof config.name === 'string' && config.name)
142
+ || (typeof entry?.name === 'string' && entry.name)
143
+ || process.env.NAPCAT_BOT_NAME
144
+ || 'napcat-bot';
145
+ const access_token = config.access_token ?? entry?.access_token;
146
+
147
+ if (connection === 'ws') {
148
+ const url = config.url ?? entry?.url;
149
+ if (!url) {
150
+ throw new TypeError(
151
+ 'NapCat connection:ws requires url (plugins.<key>.url or endpoints with context: napcat)',
152
+ );
153
+ }
154
+ return {
155
+ context: 'napcat',
156
+ connection: 'ws',
157
+ name,
158
+ access_token,
159
+ url,
160
+ reconnect_interval: config.reconnect_interval ?? entry?.reconnect_interval ?? 5000,
161
+ heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
162
+ };
163
+ }
164
+
165
+ if (connection === 'wss') {
166
+ const path = config.path ?? entry?.path;
167
+ if (!path) throw new TypeError('NapCat connection:wss requires path');
168
+ return {
169
+ context: 'napcat',
170
+ connection: 'wss',
171
+ name,
172
+ access_token,
173
+ path,
174
+ heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
175
+ };
176
+ }
177
+
178
+ if (connection === 'http') {
179
+ const http_url = config.http_url ?? entry?.http_url;
180
+ const post_path = config.post_path ?? entry?.post_path;
181
+ if (!http_url || !post_path) {
182
+ throw new TypeError('NapCat connection:http requires http_url and post_path');
183
+ }
184
+ return {
185
+ context: 'napcat',
186
+ connection: 'http',
187
+ name,
188
+ access_token,
189
+ http_url,
190
+ post_path,
191
+ poll_interval: config.poll_interval ?? entry?.poll_interval ?? 30_000,
192
+ };
193
+ }
194
+
195
+ throw new TypeError(`Unknown NapCat connection: ${String(connection)}`);
196
+ }
197
+
198
+ export function isMessageEvent(
199
+ ev: NapCatEvent,
200
+ ): ev is NapCatMessageEvent {
201
+ return (ev.post_type === 'message' || ev.post_type === 'message_sent')
202
+ && ev.message_id != null;
203
+ }
204
+
205
+ export function getChannelId(ev: NapCatEvent): string {
206
+ if (ev.message_type === 'group' && ev.group_id != null) return String(ev.group_id);
207
+ if (ev.group_id != null && ev.message_type !== 'private') return String(ev.group_id);
208
+ if (ev.user_id != null) return String(ev.user_id);
209
+ return '';
210
+ }
211
+
212
+ export function formatInboundTarget(ev: NapCatEvent): string {
213
+ const messageType = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
214
+ ? 'group'
215
+ : 'private';
216
+ return `${messageType}:${getChannelId(ev)}`;
217
+ }
218
+
219
+ export function parseSendTarget(target: string): ParsedSendTarget {
220
+ const sep = target.indexOf(':');
221
+ if (sep <= 0) {
222
+ return { message_type: 'private', id: target };
223
+ }
224
+ const head = target.slice(0, sep);
225
+ const rest = target.slice(sep + 1);
226
+ if (head === 'group') return { message_type: 'group', id: rest };
227
+ if (head === 'private') return { message_type: 'private', id: rest };
228
+ return { message_type: 'private', id: target };
229
+ }
230
+
231
+ export function formatInboundContent(ev: NapCatEvent): string {
232
+ if (typeof ev.raw_message === 'string' && ev.raw_message) return ev.raw_message;
233
+ if (Array.isArray(ev.message)) {
234
+ return ev.message
235
+ .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
236
+ .join('');
237
+ }
238
+ if (typeof ev.message === 'string') return ev.message;
239
+ return '';
240
+ }
241
+
242
+ /**
243
+ * 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
244
+ * 显示名走 metadata.nickname,见 senderNickname。
245
+ */
246
+ export function senderUserId(ev: NapCatEvent): string {
247
+ return ev.user_id != null ? String(ev.user_id) : '';
248
+ }
249
+
250
+ /** 显示名(群名片 card 优先于 nickname),没有则 undefined。 */
251
+ export function senderNickname(ev: NapCatEvent): string | undefined {
252
+ const name = ev.sender?.card || ev.sender?.nickname;
253
+ return typeof name === 'string' && name ? name : undefined;
254
+ }
255
+
256
+ /**
257
+ * Wire-encode an already-rendered outbound payload into OneBot message segments.
258
+ * Segment canonicalization is intentionally not done here.
259
+ */
260
+ export function formatOutboundSegments(payload: unknown): MessageSegment[] {
261
+ if (typeof payload === 'string') {
262
+ return [{ type: 'text', data: { text: payload } }];
263
+ }
264
+
265
+ const items: Array<string | NapCatWireSegment> = Array.isArray(payload)
266
+ ? payload as Array<string | NapCatWireSegment>
267
+ : payload && typeof payload === 'object' && 'type' in (payload as object)
268
+ ? [payload as NapCatWireSegment]
269
+ : [];
270
+
271
+ if (items.length === 0) {
272
+ const text = payload == null
273
+ ? ''
274
+ : typeof payload === 'object'
275
+ ? JSON.stringify(payload)
276
+ : String(payload);
277
+ return [{ type: 'text', data: { text } }];
278
+ }
279
+
280
+ const segs: MessageSegment[] = [];
281
+ for (const item of items) {
282
+ if (typeof item === 'string') {
283
+ segs.push({ type: 'text', data: { text: item } });
284
+ continue;
285
+ }
286
+ segs.push({ type: item.type, data: item.data ?? {} });
287
+ }
288
+ return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
289
+ }
290
+
291
+ export function buildSendAction(
292
+ target: string,
293
+ message: MessageSegment[],
294
+ ): { action: string; params: Record<string, unknown> } {
295
+ const parsed = parseSendTarget(target);
296
+ if (parsed.message_type === 'group') {
297
+ return {
298
+ action: 'send_group_msg',
299
+ params: {
300
+ group_id: Number(parsed.id) || parsed.id,
301
+ message,
302
+ },
303
+ };
304
+ }
305
+ return {
306
+ action: 'send_private_msg',
307
+ params: {
308
+ user_id: Number(parsed.id) || parsed.id,
309
+ message,
310
+ },
311
+ };
312
+ }
313
+
314
+ /** Build WS connect URL + headers (access_token via Bearer + query). */
315
+ export function buildWsConnectOptions(config: NapCatWsConfig): {
316
+ readonly url: string;
317
+ readonly headers: Record<string, string>;
318
+ readonly safeUrl: string;
319
+ } {
320
+ const headers: Record<string, string> = {};
321
+ let connectUrl = config.url;
322
+ if (config.access_token) {
323
+ headers.Authorization = `Bearer ${config.access_token}`;
324
+ const url = new URL(config.url);
325
+ url.searchParams.set('access_token', config.access_token);
326
+ connectUrl = url.toString();
327
+ }
328
+ const safeUrl = new URL(connectUrl);
329
+ safeUrl.searchParams.delete('access_token');
330
+ return { url: connectUrl, headers, safeUrl: safeUrl.toString() };
331
+ }
332
+
333
+ export interface NapCatHttpOptions {
334
+ readonly http_url: string;
335
+ readonly access_token?: string;
336
+ }
337
+
338
+ /**
339
+ * 向 NapCat HTTP API 发送动作请求:POST {http_url}/{action}。
340
+ * 供 connection:http 出站与纯协议测试使用。
341
+ */
342
+ export async function callNapCatHttpAction(
343
+ options: NapCatHttpOptions,
344
+ action: string,
345
+ params: Record<string, unknown> = {},
346
+ ): Promise<NapCatActionResponse> {
347
+ const base = options.http_url.replace(/\/$/, '');
348
+ const url = `${base}/${action}`;
349
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
350
+ if (options.access_token) {
351
+ headers.Authorization = `Bearer ${options.access_token}`;
352
+ }
353
+ const res = await fetch(url, {
354
+ method: 'POST',
355
+ headers,
356
+ body: JSON.stringify(params),
357
+ });
358
+ const text = await res.text();
359
+ if (res.status === 401) throw new Error(`NapCat HTTP auth failed: ${text}`);
360
+ if (res.status !== 200) throw new Error(`NapCat HTTP ${res.status}: ${text}`);
361
+ let data: NapCatActionResponse;
362
+ try {
363
+ data = JSON.parse(text) as NapCatActionResponse;
364
+ } catch {
365
+ throw new Error(`NapCat HTTP invalid response: ${text.slice(0, 200)}`);
366
+ }
367
+ if (data.status !== 'ok' && data.retcode !== 0) {
368
+ throw new Error(
369
+ `NapCat HTTP action failed [${data.retcode}]: ${data.message || data.wording || 'unknown'}`,
370
+ );
371
+ }
372
+ return data;
373
+ }
package/src/webhook.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+
3
+ export async function readRequestBody(request: IncomingMessage): Promise<string> {
4
+ const chunks: Buffer[] = [];
5
+ let size = 0;
6
+ for await (const chunk of request) {
7
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8
+ size += buffer.length;
9
+ if (size > 1_048_576) {
10
+ request.destroy();
11
+ throw new Error('Request body exceeds 1MB');
12
+ }
13
+ chunks.push(buffer);
14
+ }
15
+ return Buffer.concat(chunks).toString('utf8');
16
+ }