@zhin.js/adapter-napcat 3.0.2 → 5.0.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 (183) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +47 -98
  3. package/adapters/napcat.ts +39 -0
  4. package/agent/tools/ai_tts.ts +2 -2
  5. package/agent/tools/del_group_notice.ts +2 -2
  6. package/agent/tools/delete_essence_msg.ts +2 -2
  7. package/agent/tools/delete_friend.ts +2 -2
  8. package/agent/tools/download_file.ts +2 -2
  9. package/agent/tools/forward_single_msg.ts +2 -2
  10. package/agent/tools/get_ai_characters.ts +2 -2
  11. package/agent/tools/get_essence_list.ts +2 -2
  12. package/agent/tools/get_friend_msg_history.ts +2 -2
  13. package/agent/tools/get_group_file_url.ts +2 -2
  14. package/agent/tools/get_group_info_ex.ts +2 -2
  15. package/agent/tools/get_group_msg_history.ts +2 -2
  16. package/agent/tools/get_group_notice.ts +2 -2
  17. package/agent/tools/get_group_root_files.ts +2 -2
  18. package/agent/tools/get_group_shut_list.ts +2 -2
  19. package/agent/tools/get_mini_app_ark.ts +2 -2
  20. package/agent/tools/get_user_status.ts +2 -2
  21. package/agent/tools/group_sign.ts +2 -2
  22. package/agent/tools/mark_msg_as_read.ts +2 -2
  23. package/agent/tools/ocr_image.ts +2 -2
  24. package/agent/tools/send_forward_msg.ts +2 -2
  25. package/agent/tools/send_group_notice.ts +2 -2
  26. package/agent/tools/send_like.ts +2 -2
  27. package/agent/tools/send_poke.ts +2 -2
  28. package/agent/tools/set_avatar.ts +2 -2
  29. package/agent/tools/set_emoji_reaction.ts +2 -2
  30. package/agent/tools/set_essence_msg.ts +2 -2
  31. package/agent/tools/set_group_portrait.ts +2 -2
  32. package/agent/tools/set_online_status.ts +2 -2
  33. package/agent/tools/set_profile.ts +2 -2
  34. package/agent/tools/set_signature.ts +2 -2
  35. package/agent/tools/set_title.ts +2 -2
  36. package/agent/tools/translate.ts +2 -2
  37. package/agent/tools/upload_group_file.ts +2 -2
  38. package/lib/http-endpoint.d.ts +26 -0
  39. package/lib/http-endpoint.js +147 -0
  40. package/lib/index.d.ts +11 -0
  41. package/lib/index.js +10 -0
  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 +28 -0
  45. package/lib/{src/napcat-inbound.js → napcat-inbound.js} +28 -13
  46. package/lib/onebot-get-msg.d.ts +14 -0
  47. package/lib/{src/onebot-get-msg.js → 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 +44 -33
  63. package/plugin.ts +13 -0
  64. package/schema.json +74 -0
  65. package/src/http-endpoint.ts +197 -0
  66. package/src/index.ts +66 -81
  67. package/src/napcat-agent-deps.ts +81 -15
  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/agent/tools/ai_tts.js +0 -21
  83. package/lib/agent/tools/ai_tts.js.map +0 -1
  84. package/lib/agent/tools/del_group_notice.js +0 -22
  85. package/lib/agent/tools/del_group_notice.js.map +0 -1
  86. package/lib/agent/tools/delete_essence_msg.js +0 -21
  87. package/lib/agent/tools/delete_essence_msg.js.map +0 -1
  88. package/lib/agent/tools/delete_friend.js +0 -20
  89. package/lib/agent/tools/delete_friend.js.map +0 -1
  90. package/lib/agent/tools/download_file.js +0 -19
  91. package/lib/agent/tools/download_file.js.map +0 -1
  92. package/lib/agent/tools/forward_single_msg.js +0 -24
  93. package/lib/agent/tools/forward_single_msg.js.map +0 -1
  94. package/lib/agent/tools/get_ai_characters.js +0 -19
  95. package/lib/agent/tools/get_ai_characters.js.map +0 -1
  96. package/lib/agent/tools/get_essence_list.js +0 -19
  97. package/lib/agent/tools/get_essence_list.js.map +0 -1
  98. package/lib/agent/tools/get_friend_msg_history.js +0 -20
  99. package/lib/agent/tools/get_friend_msg_history.js.map +0 -1
  100. package/lib/agent/tools/get_group_file_url.js +0 -21
  101. package/lib/agent/tools/get_group_file_url.js.map +0 -1
  102. package/lib/agent/tools/get_group_info_ex.js +0 -19
  103. package/lib/agent/tools/get_group_info_ex.js.map +0 -1
  104. package/lib/agent/tools/get_group_msg_history.js +0 -21
  105. package/lib/agent/tools/get_group_msg_history.js.map +0 -1
  106. package/lib/agent/tools/get_group_notice.js +0 -19
  107. package/lib/agent/tools/get_group_notice.js.map +0 -1
  108. package/lib/agent/tools/get_group_root_files.js +0 -19
  109. package/lib/agent/tools/get_group_root_files.js.map +0 -1
  110. package/lib/agent/tools/get_group_shut_list.js +0 -19
  111. package/lib/agent/tools/get_group_shut_list.js.map +0 -1
  112. package/lib/agent/tools/get_mini_app_ark.js +0 -22
  113. package/lib/agent/tools/get_mini_app_ark.js.map +0 -1
  114. package/lib/agent/tools/get_user_status.js +0 -18
  115. package/lib/agent/tools/get_user_status.js.map +0 -1
  116. package/lib/agent/tools/group_sign.js +0 -20
  117. package/lib/agent/tools/group_sign.js.map +0 -1
  118. package/lib/agent/tools/mark_msg_as_read.js +0 -19
  119. package/lib/agent/tools/mark_msg_as_read.js.map +0 -1
  120. package/lib/agent/tools/ocr_image.js +0 -18
  121. package/lib/agent/tools/ocr_image.js.map +0 -1
  122. package/lib/agent/tools/send_forward_msg.js +0 -21
  123. package/lib/agent/tools/send_forward_msg.js.map +0 -1
  124. package/lib/agent/tools/send_group_notice.js +0 -23
  125. package/lib/agent/tools/send_group_notice.js.map +0 -1
  126. package/lib/agent/tools/send_like.js +0 -20
  127. package/lib/agent/tools/send_like.js.map +0 -1
  128. package/lib/agent/tools/send_poke.js +0 -20
  129. package/lib/agent/tools/send_poke.js.map +0 -1
  130. package/lib/agent/tools/set_avatar.js +0 -19
  131. package/lib/agent/tools/set_avatar.js.map +0 -1
  132. package/lib/agent/tools/set_emoji_reaction.js +0 -20
  133. package/lib/agent/tools/set_emoji_reaction.js.map +0 -1
  134. package/lib/agent/tools/set_essence_msg.js +0 -21
  135. package/lib/agent/tools/set_essence_msg.js.map +0 -1
  136. package/lib/agent/tools/set_group_portrait.js +0 -22
  137. package/lib/agent/tools/set_group_portrait.js.map +0 -1
  138. package/lib/agent/tools/set_online_status.js +0 -20
  139. package/lib/agent/tools/set_online_status.js.map +0 -1
  140. package/lib/agent/tools/set_profile.js +0 -23
  141. package/lib/agent/tools/set_profile.js.map +0 -1
  142. package/lib/agent/tools/set_signature.js +0 -19
  143. package/lib/agent/tools/set_signature.js.map +0 -1
  144. package/lib/agent/tools/set_title.js +0 -23
  145. package/lib/agent/tools/set_title.js.map +0 -1
  146. package/lib/agent/tools/translate.js +0 -18
  147. package/lib/agent/tools/translate.js.map +0 -1
  148. package/lib/agent/tools/upload_group_file.js +0 -23
  149. package/lib/agent/tools/upload_group_file.js.map +0 -1
  150. package/lib/src/adapter.js +0 -102
  151. package/lib/src/adapter.js.map +0 -1
  152. package/lib/src/agent-prompt.js +0 -106
  153. package/lib/src/agent-prompt.js.map +0 -1
  154. package/lib/src/endpoint-base.js +0 -315
  155. package/lib/src/endpoint-base.js.map +0 -1
  156. package/lib/src/endpoint-http.js +0 -106
  157. package/lib/src/endpoint-http.js.map +0 -1
  158. package/lib/src/endpoint-ws-client.js +0 -139
  159. package/lib/src/endpoint-ws-client.js.map +0 -1
  160. package/lib/src/endpoint-ws-server.js +0 -137
  161. package/lib/src/endpoint-ws-server.js.map +0 -1
  162. package/lib/src/index.js +0 -58
  163. package/lib/src/index.js.map +0 -1
  164. package/lib/src/napcat-agent-deps.js +0 -19
  165. package/lib/src/napcat-agent-deps.js.map +0 -1
  166. package/lib/src/napcat-inbound.js.map +0 -1
  167. package/lib/src/onebot-get-msg.js.map +0 -1
  168. package/lib/src/routes.js +0 -61
  169. package/lib/src/routes.js.map +0 -1
  170. package/lib/src/segment-mapper.js +0 -2
  171. package/lib/src/segment-mapper.js.map +0 -1
  172. package/lib/src/types.js +0 -6
  173. package/lib/src/types.js.map +0 -1
  174. package/plugin.yml +0 -3
  175. package/src/adapter.ts +0 -109
  176. package/src/agent-prompt.ts +0 -115
  177. package/src/endpoint-base.ts +0 -362
  178. package/src/endpoint-http.ts +0 -95
  179. package/src/endpoint-ws-client.ts +0 -137
  180. package/src/endpoint-ws-server.ts +0 -136
  181. package/src/routes.ts +0 -61
  182. package/src/segment-mapper.ts +0 -1
  183. package/src/types.ts +0 -266
@@ -1,95 +0,0 @@
1
- /**
2
- * NapCat HTTP 连接方式
3
- * 出站:HTTP POST 调用 NapCat API
4
- * 入站:挂载 webhook 路由接收 NapCat 的 HTTP POST 事件上报
5
- */
6
- import { formatCompact } from 'zhin.js';
7
- import { NapCatEndpointBase } from './endpoint-base.js';
8
- import type { NapCatHttpConfig, ApiResponse } from './types.js';
9
- import type { NapCatAdapter } from './adapter.js';
10
- import { registerFetchRoute, type Router, type RouterContext } from '@zhin.js/host-router/router';
11
- import * as crypto from 'crypto';
12
-
13
- export class NapCatHttpEndpoint extends NapCatEndpointBase {
14
- private pollTimer?: NodeJS.Timeout;
15
-
16
- declare $config: NapCatHttpConfig;
17
-
18
- constructor(adapter: NapCatAdapter, public router: Router, config: NapCatHttpConfig) {
19
- super(adapter, config);
20
- }
21
-
22
- async $connect(): Promise<void> {
23
- this.mountWebhook();
24
- await this.checkConnection();
25
- this.startPoll();
26
- this.$connected = true;
27
- this.logger.debug(formatCompact({ endpoint: this.$id, mode: 'http' }));
28
- }
29
-
30
- async $disconnect(): Promise<void> {
31
- if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = undefined; }
32
- this.inboundDeduper.clear();
33
- this.$connected = false;
34
- }
35
-
36
- async callApi<T = any>(action: string, params: Record<string, any> = {}): Promise<T> {
37
- const url = `${this.$config.http_url.replace(/\/$/, '')}/${action}`;
38
- const headers: Record<string, string> = { 'Content-Type': 'application/json' };
39
- if (this.$config.access_token) headers['Authorization'] = `Bearer ${this.$config.access_token}`;
40
-
41
- const resp = await fetch(url, { method: 'POST', headers, body: JSON.stringify(params) });
42
- if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText} for ${action}`);
43
- const json = await resp.json() as ApiResponse<T>;
44
- if (json.status !== 'ok' && json.retcode !== 0) {
45
- throw new Error(`API error [${json.retcode}]: ${json.message || json.wording || 'unknown'}`);
46
- }
47
- return json.data;
48
- }
49
-
50
- private mountWebhook(): void {
51
- const postPath = this.$config.post_path;
52
- registerFetchRoute(this.router, 'POST', postPath, async (ctx: RouterContext) => {
53
- const body = ctx.request.body;
54
- if (!body || typeof body !== 'object') { ctx.status = 400; ctx.body = { error: 'invalid body' }; return; }
55
-
56
- if (this.$config.access_token) {
57
- const sig = ctx.get('x-signature');
58
- const authHeader = ctx.get('authorization');
59
- if (sig) {
60
- const expected = 'sha1=' + crypto.createHmac('sha1', this.$config.access_token).update(JSON.stringify(body)).digest('hex');
61
- if (sig !== expected) { ctx.status = 403; ctx.body = { error: 'signature mismatch' }; return; }
62
- } else if (authHeader) {
63
- if (authHeader !== `Bearer ${this.$config.access_token}`) { ctx.status = 403; ctx.body = { error: 'auth failed' }; return; }
64
- }
65
- }
66
-
67
- ctx.status = 204;
68
- ctx.body = '';
69
- try { this.dispatchEvent(body); } catch (e) {
70
- this.logger.warn(formatCompact( { op: 'recv', endpoint: this.$id, ok: false, error: String(e) }));
71
- }
72
- });
73
- this.logger.debug(formatCompact( { op: 'webhook', endpoint: this.$id, path: postPath }));
74
- }
75
-
76
- private async checkConnection(): Promise<void> {
77
- try {
78
- await this.callApi('get_login_info');
79
- } catch (e) {
80
- throw new Error(`${this.$id} cannot connect to NapCat HTTP API at ${this.$config.http_url}: ${e}`);
81
- }
82
- }
83
-
84
- private startPoll(): void {
85
- const interval = this.$config.poll_interval || 30000;
86
- this.pollTimer = setInterval(async () => {
87
- try {
88
- await this.callApi('get_status');
89
- } catch {
90
- this.$connected = false;
91
- this.logger.warn(formatCompact( { op: 'heartbeat', endpoint: this.$id, ok: false }));
92
- }
93
- }, interval);
94
- }
95
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * NapCat 正向 WebSocket 连接
3
- */
4
- import WebSocket from 'ws';
5
- import { formatCompact } from 'zhin.js';
6
- import { NapCatEndpointBase } from './endpoint-base.js';
7
- import type { NapCatWsClientConfig, ApiResponse } from './types.js';
8
- import type { NapCatAdapter } from './adapter.js';
9
-
10
- export class NapCatWsClient extends NapCatEndpointBase {
11
- private ws?: WebSocket;
12
- private reconnectTimer?: NodeJS.Timeout;
13
- private heartbeatTimer?: NodeJS.Timeout;
14
- private requestId = 0;
15
- private pendingRequests = new Map<string, {
16
- resolve: (value: any) => void;
17
- reject: (error: Error) => void;
18
- timeout: NodeJS.Timeout;
19
- }>();
20
-
21
- declare $config: NapCatWsClientConfig;
22
-
23
- constructor(adapter: NapCatAdapter, config: NapCatWsClientConfig) {
24
- super(adapter, config);
25
- }
26
-
27
- async $connect(): Promise<void> {
28
- return new Promise((resolve, reject) => {
29
- const headers: Record<string, string> = {};
30
- let url = this.$config.url;
31
- if (this.$config.access_token) {
32
- headers['Authorization'] = `Bearer ${this.$config.access_token}`;
33
- const u = new URL(url);
34
- u.searchParams.set('access_token', this.$config.access_token);
35
- url = u.toString();
36
- }
37
- this.ws = new WebSocket(url, { headers });
38
-
39
- this.ws.on('open', () => {
40
- this.$connected = true;
41
- if (!this.$config.access_token) {
42
- this.logger.warn(formatCompact({ endpoint: this.$id, ok: false, error: 'missing access_token' }));
43
- }
44
- this.logger.debug(formatCompact({ endpoint: this.$id, mode: 'ws' }));
45
- this.startHeartbeat();
46
- resolve();
47
- });
48
-
49
- this.ws.on('message', (data) => {
50
- try {
51
- this.handleWsMessage(JSON.parse(data.toString()));
52
- } catch (error) {
53
- this.emit('error', error);
54
- }
55
- });
56
-
57
- this.ws.on('close', (code, reason) => {
58
- this.$connected = false;
59
- const reasonStr = reason?.toString?.() || '';
60
- const codeHint = code === 1005 ? ' [no status]' : code === 1006 ? ' [abnormal]' : '';
61
- this.logger.warn(formatCompact( {
62
- op: 'disconnect',
63
- endpoint: this.$id,
64
- code,
65
- error: reasonStr || 'closed',
66
- reconnect_ms: this.$config.reconnect_interval || 5000,
67
- }));
68
- reject({ code, reason });
69
- this.scheduleReconnect();
70
- });
71
-
72
- this.ws.on('error', (error) => {
73
- this.logger.warn(formatCompact( {
74
- op: 'ws_error',
75
- endpoint: this.$id,
76
- ok: false,
77
- error: error instanceof Error ? error.message : String(error),
78
- }));
79
- reject(error);
80
- });
81
- });
82
- }
83
-
84
- async $disconnect(): Promise<void> {
85
- if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; }
86
- if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; }
87
- for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
88
- this.pendingRequests.clear();
89
- if (this.ws) { this.ws.close(); this.ws = undefined; }
90
- this.inboundDeduper.clear();
91
- this.$connected = false;
92
- }
93
-
94
- async callApi<T = any>(action: string, params: Record<string, any> = {}): Promise<T> {
95
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not connected');
96
- const echo = `req_${++this.requestId}`;
97
- return new Promise((resolve, reject) => {
98
- const timeout = setTimeout(() => { this.pendingRequests.delete(echo); reject(new Error(`API call timeout: ${action}`)); }, 30000);
99
- this.pendingRequests.set(echo, { resolve, reject, timeout });
100
- this.ws!.send(JSON.stringify({ action, params, echo }));
101
- });
102
- }
103
-
104
- private handleWsMessage(message: any): void {
105
- if (message.echo && this.pendingRequests.has(message.echo)) {
106
- const req = this.pendingRequests.get(message.echo)!;
107
- this.pendingRequests.delete(message.echo);
108
- clearTimeout(req.timeout);
109
- const resp = message as ApiResponse;
110
- if (resp.status === 'ok') return req.resolve(resp.data);
111
- return req.reject(new Error(`API error [${resp.retcode}]: ${resp.message || resp.wording || 'unknown'}`));
112
- }
113
- this.dispatchEvent(message);
114
- }
115
-
116
- private startHeartbeat(): void {
117
- const interval = this.$config.heartbeat_interval || 30000;
118
- this.heartbeatTimer = setInterval(() => {
119
- if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.ping();
120
- }, interval);
121
- }
122
-
123
- private scheduleReconnect(): void {
124
- if (this.reconnectTimer) return;
125
- const interval = this.$config.reconnect_interval || 5000;
126
- this.reconnectTimer = setTimeout(async () => {
127
- this.reconnectTimer = undefined;
128
- try { await this.$connect(); } catch { this.scheduleReconnect(); }
129
- }, interval);
130
- }
131
-
132
- protected handleMeta(event: any): void {
133
- if (event.meta_event_type === 'lifecycle' && event.sub_type === 'connect') {
134
- this.logger.debug(formatCompact({ endpoint: this.$id, lifecycle: 'connect', self_id: event.self_id }));
135
- }
136
- }
137
- }
@@ -1,136 +0,0 @@
1
- /**
2
- * NapCat 反向 WebSocket 连接
3
- */
4
- import WebSocket, { WebSocketServer } from 'ws';
5
- import { formatCompact } from 'zhin.js';
6
- import type { IncomingMessage } from 'http';
7
- import { NapCatEndpointBase } from './endpoint-base.js';
8
- import type { NapCatWsServerConfig, ApiResponse } from './types.js';
9
- import type { NapCatAdapter } from './adapter.js';
10
- import type { Router } from '@zhin.js/host-router';
11
-
12
- export class NapCatWsServer extends NapCatEndpointBase {
13
- #wss?: WebSocketServer;
14
- #clientMap = new Map<string, WebSocket>();
15
- private heartbeatTimer?: NodeJS.Timeout;
16
- private requestId = 0;
17
- private pendingRequests = new Map<string, {
18
- resolve: (value: any) => void;
19
- reject: (error: Error) => void;
20
- timeout: NodeJS.Timeout;
21
- }>();
22
-
23
- declare $config: NapCatWsServerConfig;
24
-
25
- constructor(adapter: NapCatAdapter, public router: Router, config: NapCatWsServerConfig) {
26
- super(adapter, config);
27
- }
28
-
29
- async $connect(): Promise<void> {
30
- if (!this.$config.access_token) {
31
- this.logger.warn(formatCompact({ endpoint: this.$id, ok: false, error: 'missing access_token' }));
32
- }
33
- this.#wss = this.router.ws(this.$config.path, {
34
- verifyClient: (info: { origin: string; secure: boolean; req: IncomingMessage }) => {
35
- const authorization = info.req.headers['authorization'] || '';
36
- if (this.$config.access_token && authorization !== `Bearer ${this.$config.access_token}`) {
37
- this.logger.error(`[${this.$id}] auth failed`);
38
- return false;
39
- }
40
- return true;
41
- },
42
- });
43
- this.logger.debug(formatCompact( { op: 'listen', endpoint: this.$id, mode: 'wss', path: this.$config.path }));
44
-
45
- this.#wss.on('connection', (client, req) => {
46
- this.startHeartbeat();
47
- this.logger.debug(formatCompact({ endpoint: this.$id, peer: req.socket.remoteAddress }));
48
-
49
- client.on('error', (err) => this.logger.warn(formatCompact( {
50
- op: 'ws_error',
51
- endpoint: this.$id,
52
- ok: false,
53
- error: err instanceof Error ? err.message : String(err),
54
- })));
55
- client.on('close', (code, reason) => {
56
- const reasonStr = reason?.toString?.() || '';
57
- this.logger.warn(formatCompact( {
58
- op: 'disconnect',
59
- endpoint: this.$id,
60
- code,
61
- error: reasonStr || undefined,
62
- }));
63
- for (const [key, val] of this.#clientMap) {
64
- if (val === client) this.#clientMap.delete(key);
65
- }
66
- if (this.#clientMap.size === 0) this.$connected = false;
67
- });
68
- client.on('message', (data) => {
69
- try { this.handleWsMessage(client, JSON.parse(data.toString())); } catch (e) { this.emit('error', e); }
70
- });
71
- });
72
- }
73
-
74
- async $disconnect(): Promise<void> {
75
- this.#wss?.close();
76
- if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; }
77
- for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
78
- this.pendingRequests.clear();
79
- this.inboundDeduper.clear();
80
- this.$connected = false;
81
- }
82
-
83
- async callApi<T = any>(action: string, params: Record<string, any> = {}): Promise<T> {
84
- const selfId = this.getFirstSelfId();
85
- const client = this.#clientMap.get(selfId);
86
- if (!client || client.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not connected');
87
- const echo = `req_${++this.requestId}`;
88
- return new Promise((resolve, reject) => {
89
- const timeout = setTimeout(() => { this.pendingRequests.delete(echo); reject(new Error(`API call timeout: ${action}`)); }, 30000);
90
- this.pendingRequests.set(echo, { resolve, reject, timeout });
91
- client.send(JSON.stringify({ action, params, echo }));
92
- });
93
- }
94
-
95
- private getFirstSelfId(): string {
96
- const first = this.#clientMap.keys().next().value;
97
- if (!first) throw new Error('No NapCat client connected to reverse WS');
98
- return first;
99
- }
100
-
101
- private handleWsMessage(client: WebSocket, message: any): void {
102
- if (message.self_id != null) {
103
- const selfIdStr = String(message.self_id);
104
- if (!this.#clientMap.has(selfIdStr) || this.#clientMap.get(selfIdStr) !== client) {
105
- this.#clientMap.set(selfIdStr, client);
106
- if (!this.$connected) this.$connected = true;
107
- }
108
- }
109
- if (message.echo && this.pendingRequests.has(message.echo)) {
110
- const req = this.pendingRequests.get(message.echo)!;
111
- this.pendingRequests.delete(message.echo);
112
- clearTimeout(req.timeout);
113
- const resp = message as ApiResponse;
114
- if (resp.status === 'ok') return req.resolve(resp.data);
115
- return req.reject(new Error(`API error [${resp.retcode}]: ${resp.message || resp.wording || 'unknown'}`));
116
- }
117
-
118
- if (message.post_type === 'meta_event' && message.sub_type === 'connect') {
119
- this.#clientMap.set(String(message.self_id), client);
120
- this.$connected = true;
121
- this.logger.debug(formatCompact({ endpoint: this.$id, self_id: message.self_id }));
122
- return;
123
- }
124
- this.dispatchEvent(message);
125
- }
126
-
127
- private startHeartbeat(): void {
128
- if (this.heartbeatTimer) return;
129
- const interval = this.$config.heartbeat_interval || 30000;
130
- this.heartbeatTimer = setInterval(() => {
131
- for (const client of this.#wss?.clients || []) {
132
- if (client.readyState === WebSocket.OPEN) client.ping();
133
- }
134
- }, interval);
135
- }
136
- }
package/src/routes.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { Router } from '@zhin.js/host-router';
2
- import type { NapCatAdapter } from './adapter.js';
3
-
4
- export function registerRoutes(router: Router, napcat: NapCatAdapter): void {
5
- router.get('/api/napcat/endpoints', async (ctx) => {
6
- try {
7
- const endpoints = Array.from(napcat.endpoints.values());
8
- if (endpoints.length === 0) {
9
- ctx.body = { success: true, data: [], message: '暂无 NapCat Endpoint 实例' };
10
- return;
11
- }
12
- const result = await Promise.all(
13
- endpoints.map(async (endpoint) => {
14
- try {
15
- const connection = (endpoint.$config as any).connection ?? 'ws';
16
- const base: Record<string, unknown> = {
17
- name: endpoint.$config.name,
18
- connected: endpoint.$connected || false,
19
- connection,
20
- status: endpoint.$connected ? 'online' : 'offline',
21
- lastActivity: new Date().toISOString(),
22
- };
23
- if (endpoint.$connected) {
24
- try {
25
- const friends = await endpoint.getFriendList();
26
- const groups = await endpoint.getGroupList();
27
- base.friendCount = Array.isArray(friends) ? friends.length : 0;
28
- base.groupCount = Array.isArray(groups) ? groups.length : 0;
29
- } catch {
30
- base.friendCount = 0;
31
- base.groupCount = 0;
32
- }
33
- } else {
34
- base.friendCount = 0;
35
- base.groupCount = 0;
36
- }
37
- return base;
38
- } catch {
39
- return {
40
- name: endpoint.$config.name,
41
- connected: false,
42
- connection: 'unknown',
43
- status: 'error',
44
- friendCount: 0,
45
- groupCount: 0,
46
- };
47
- }
48
- }),
49
- );
50
- ctx.body = { success: true, data: result, timestamp: new Date().toISOString() };
51
- } catch (error) {
52
- ctx.status = 500;
53
- ctx.body = {
54
- success: false,
55
- error: 'NAPCAT_API_ERROR',
56
- message: '获取 Endpoint 数据失败',
57
- details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined,
58
- };
59
- }
60
- });
61
- }
@@ -1 +0,0 @@
1
- export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
package/src/types.ts DELETED
@@ -1,266 +0,0 @@
1
- /**
2
- * NapCat 适配器类型定义
3
- * 覆盖 OneBot11 标准 + go-cqhttp 扩展 + NapCat 独有 API
4
- */
5
-
6
- // ── 配置 ─────────────────────────────────────────────────────────────
7
-
8
- export interface NapCatConfigBase {
9
- context: 'napcat';
10
- name: string;
11
- access_token?: string;
12
- }
13
-
14
- /** 正向 WebSocket */
15
- export interface NapCatWsClientConfig extends NapCatConfigBase {
16
- connection: 'ws';
17
- url: string;
18
- reconnect_interval?: number;
19
- heartbeat_interval?: number;
20
- }
21
-
22
- /** 反向 WebSocket */
23
- export interface NapCatWsServerConfig extends NapCatConfigBase {
24
- connection: 'wss';
25
- path: string;
26
- heartbeat_interval?: number;
27
- }
28
-
29
- /** HTTP API + POST 上报 */
30
- export interface NapCatHttpConfig extends NapCatConfigBase {
31
- connection: 'http';
32
- /** NapCat HTTP API 地址,如 http://127.0.0.1:3000 */
33
- http_url: string;
34
- /** HTTP POST 事件上报接收路径,如 /napcat/post */
35
- post_path: string;
36
- /** 轮询间隔(ms),用于 HTTP 心跳检测,默认 30000 */
37
- poll_interval?: number;
38
- }
39
-
40
- export type NapCatEndpointConfig = NapCatWsClientConfig | NapCatWsServerConfig | NapCatHttpConfig;
41
-
42
- // ── API 响应 ─────────────────────────────────────────────────────────
43
-
44
- export interface ApiResponse<T = any> {
45
- status: string;
46
- retcode: number;
47
- data: T;
48
- echo?: string;
49
- message?: string;
50
- wording?: string;
51
- }
52
-
53
- // ── OneBot11 消息段 ──────────────────────────────────────────────────
54
-
55
- export interface MessageSegment {
56
- type: string;
57
- data: Record<string, any>;
58
- }
59
-
60
- // ── OneBot11 事件基础 ────────────────────────────────────────────────
61
-
62
- export interface NapCatEvent {
63
- post_type: 'message' | 'message_sent' | 'notice' | 'request' | 'meta_event';
64
- self_id: number;
65
- time: number;
66
- }
67
-
68
- export interface NapCatMessageEvent extends NapCatEvent {
69
- post_type: 'message' | 'message_sent';
70
- message_type: 'private' | 'group';
71
- sub_type: string;
72
- message_id: number;
73
- user_id: number;
74
- group_id?: number;
75
- message: MessageSegment[];
76
- raw_message: string;
77
- font: number;
78
- sender: {
79
- user_id: number;
80
- nickname: string;
81
- card?: string;
82
- sex?: string;
83
- age?: number;
84
- area?: string;
85
- level?: string;
86
- role?: 'owner' | 'admin' | 'member';
87
- title?: string;
88
- };
89
- }
90
-
91
- export interface NapCatNoticeEvent extends NapCatEvent {
92
- post_type: 'notice';
93
- notice_type: string;
94
- sub_type?: string;
95
- group_id?: number;
96
- user_id?: number;
97
- operator_id?: number;
98
- message_id?: number;
99
- target_id?: number;
100
- file?: { id: string; name: string; size: number; busid: number; url?: string };
101
- duration?: number;
102
- sender_id?: number;
103
- title?: string;
104
- action?: string;
105
- suffix?: string;
106
- comment?: string;
107
- flag?: string;
108
- }
109
-
110
- export interface NapCatRequestEvent extends NapCatEvent {
111
- post_type: 'request';
112
- request_type: 'friend' | 'group';
113
- sub_type?: string;
114
- user_id: number;
115
- group_id?: number;
116
- comment?: string;
117
- flag: string;
118
- }
119
-
120
- export interface NapCatMetaEvent extends NapCatEvent {
121
- post_type: 'meta_event';
122
- meta_event_type: string;
123
- sub_type?: string;
124
- status?: any;
125
- interval?: number;
126
- }
127
-
128
- // ── 发送者信息 ───────────────────────────────────────────────────────
129
-
130
- export interface SenderInfo {
131
- id: string;
132
- name: string;
133
- role?: 'owner' | 'admin' | 'member';
134
- isOwner?: boolean;
135
- isAdmin?: boolean;
136
- card?: string;
137
- title?: string;
138
- }
139
-
140
- // ── API 数据类型 ─────────────────────────────────────────────────────
141
-
142
- export interface LoginInfo {
143
- user_id: number;
144
- nickname: string;
145
- }
146
-
147
- export interface StrangerInfo {
148
- user_id: number;
149
- nickname: string;
150
- sex: string;
151
- age: number;
152
- qid?: string;
153
- level?: number;
154
- login_days?: number;
155
- }
156
-
157
- export interface FriendInfo {
158
- user_id: number;
159
- nickname: string;
160
- remark: string;
161
- }
162
-
163
- export interface GroupInfo {
164
- group_id: number;
165
- group_name: string;
166
- group_memo?: string;
167
- group_create_time?: number;
168
- group_level?: number;
169
- member_count: number;
170
- max_member_count: number;
171
- }
172
-
173
- export interface GroupMemberInfo {
174
- group_id: number;
175
- user_id: number;
176
- nickname: string;
177
- card: string;
178
- sex: string;
179
- age: number;
180
- area: string;
181
- join_time: number;
182
- last_sent_time: number;
183
- level: string;
184
- role: 'owner' | 'admin' | 'member';
185
- unfriendly: boolean;
186
- title: string;
187
- title_expire_time: number;
188
- card_changeable: boolean;
189
- shut_up_timestamp?: number;
190
- }
191
-
192
- export interface MessageInfo {
193
- time: number;
194
- message_type: string;
195
- message_id: number;
196
- real_id: number;
197
- sender: { user_id: number; nickname: string; card?: string };
198
- message: MessageSegment[];
199
- }
200
-
201
- export interface ForwardMessage {
202
- messages: Array<{ content: MessageSegment[]; sender: { nickname: string; user_id: number }; time: number }>;
203
- }
204
-
205
- export interface GroupHonorInfo {
206
- group_id: number;
207
- current_talkative?: { user_id: number; nickname: string; avatar: string; day_count: number };
208
- talkative_list?: any[];
209
- performer_list?: any[];
210
- legend_list?: any[];
211
- strong_newbie_list?: any[];
212
- emotion_list?: any[];
213
- }
214
-
215
- export interface EssenceMessage {
216
- sender_id: number;
217
- sender_nick: string;
218
- sender_time: number;
219
- operator_id: number;
220
- operator_nick: string;
221
- operator_time: number;
222
- message_id: number;
223
- }
224
-
225
- export interface GroupFileSystemInfo {
226
- file_count: number;
227
- limit_count: number;
228
- used_space: number;
229
- total_space: number;
230
- }
231
-
232
- export interface GroupFileInfo {
233
- files: Array<{ group_id: number; file_id: string; file_name: string; busid: number; file_size: number; upload_time: number; dead_time: number; modify_time: number; download_times: number; uploader: number; uploader_name: string }>;
234
- folders: Array<{ group_id: number; folder_id: string; folder_name: string; create_time: number; creator: number; creator_name: string; total_file_count: number }>;
235
- }
236
-
237
- export interface GroupNotice {
238
- sender_id: number;
239
- publish_time: number;
240
- message: { text: string; images?: Array<{ id: string; height: string; width: string }> };
241
- }
242
-
243
- export interface VersionInfo {
244
- app_name: string;
245
- app_version: string;
246
- protocol_version: string;
247
- [key: string]: any;
248
- }
249
-
250
- export interface OnlineStatus {
251
- status: number;
252
- ext_status: number;
253
- }
254
-
255
- export interface AiCharacter {
256
- character_id: string;
257
- character_name: string;
258
- preview_url?: string;
259
- }
260
-
261
- export interface CollectionItem {
262
- id: string;
263
- type: number;
264
- content: string;
265
- create_time: number;
266
- }