@zhin.js/adapter-napcat 1.1.0 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/README.md +11 -8
- package/adapters/{napcat.js → napcat/index.js} +10 -10
- package/adapters/{napcat.ts → napcat/index.ts} +13 -13
- package/agents/napcat/agent.json +20 -0
- package/agents/napcat/boundaries.md +3 -0
- package/agents/napcat/conventions.md +3 -0
- package/agents/napcat/skills/napcat-account/SKILL.md +25 -0
- package/agents/napcat/skills/napcat-account/tools/delete_friend/index.js +18 -0
- package/{agent/tools/delete_friend.ts → agents/napcat/skills/napcat-account/tools/delete_friend/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/get_user_status/index.js +16 -0
- package/{agent/tools/get_user_status.ts → agents/napcat/skills/napcat-account/tools/get_user_status/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/send_like/index.js +18 -0
- package/{agent/tools/send_like.ts → agents/napcat/skills/napcat-account/tools/send_like/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_avatar/index.js +17 -0
- package/{agent/tools/set_avatar.ts → agents/napcat/skills/napcat-account/tools/set_avatar/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_online_status/index.js +18 -0
- package/{agent/tools/set_online_status.ts → agents/napcat/skills/napcat-account/tools/set_online_status/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_profile/index.js +21 -0
- package/{agent/tools/set_profile.ts → agents/napcat/skills/napcat-account/tools/set_profile/index.ts} +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_signature/index.js +17 -0
- package/{agent/tools/set_signature.ts → agents/napcat/skills/napcat-account/tools/set_signature/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/SKILL.md +22 -0
- package/agents/napcat/skills/napcat-files-history/tools/download_file/index.js +17 -0
- package/{agent/tools/download_file.ts → agents/napcat/skills/napcat-files-history/tools/download_file/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/tools/get_friend_msg_history/index.js +18 -0
- package/{agent/tools/get_friend_msg_history.ts → agents/napcat/skills/napcat-files-history/tools/get_friend_msg_history/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/tools/get_group_file_url/index.js +19 -0
- package/{agent/tools/get_group_file_url.ts → agents/napcat/skills/napcat-files-history/tools/get_group_file_url/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/tools/get_group_msg_history/index.js +19 -0
- package/{agent/tools/get_group_msg_history.ts → agents/napcat/skills/napcat-files-history/tools/get_group_msg_history/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/tools/get_group_root_files/index.js +17 -0
- package/{agent/tools/get_group_root_files.ts → agents/napcat/skills/napcat-files-history/tools/get_group_root_files/index.ts} +1 -1
- package/agents/napcat/skills/napcat-files-history/tools/upload_group_file/index.js +21 -0
- package/{agent/tools/upload_group_file.ts → agents/napcat/skills/napcat-files-history/tools/upload_group_file/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/SKILL.md +21 -0
- package/agents/napcat/skills/napcat-group-content/tools/del_group_notice/index.js +20 -0
- package/{agent/tools/del_group_notice.ts → agents/napcat/skills/napcat-group-content/tools/del_group_notice/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/tools/delete_essence_msg/index.js +19 -0
- package/{agent/tools/delete_essence_msg.ts → agents/napcat/skills/napcat-group-content/tools/delete_essence_msg/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/tools/get_essence_list/index.js +17 -0
- package/{agent/tools/get_essence_list.ts → agents/napcat/skills/napcat-group-content/tools/get_essence_list/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/tools/get_group_notice/index.js +17 -0
- package/{agent/tools/get_group_notice.ts → agents/napcat/skills/napcat-group-content/tools/get_group_notice/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/tools/send_group_notice/index.js +21 -0
- package/{agent/tools/send_group_notice.ts → agents/napcat/skills/napcat-group-content/tools/send_group_notice/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-content/tools/set_essence_msg/index.js +19 -0
- package/{agent/tools/set_essence_msg.ts → agents/napcat/skills/napcat-group-content/tools/set_essence_msg/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-settings/SKILL.md +23 -0
- package/agents/napcat/skills/napcat-group-settings/tools/get_group_info_ex/index.js +17 -0
- package/{agent/tools/get_group_info_ex.ts → agents/napcat/skills/napcat-group-settings/tools/get_group_info_ex/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/get_group_shut_list/index.js +17 -0
- package/{agent/tools/get_group_shut_list.ts → agents/napcat/skills/napcat-group-settings/tools/get_group_shut_list/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/group_sign/index.js +18 -0
- package/{agent/tools/group_sign.ts → agents/napcat/skills/napcat-group-settings/tools/group_sign/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/set_group_portrait/index.js +20 -0
- package/{agent/tools/set_group_portrait.ts → agents/napcat/skills/napcat-group-settings/tools/set_group_portrait/index.ts} +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.js +21 -0
- package/{agent/tools/set_title.ts → agents/napcat/skills/napcat-group-settings/tools/set_title/index.ts} +1 -1
- package/agents/napcat/skills/napcat-media/SKILL.md +23 -0
- package/agents/napcat/skills/napcat-media/tools/ai_tts/index.js +19 -0
- package/{agent/tools/ai_tts.ts → agents/napcat/skills/napcat-media/tools/ai_tts/index.ts} +1 -1
- package/agents/napcat/skills/napcat-media/tools/get_ai_characters/index.js +17 -0
- package/{agent/tools/get_ai_characters.ts → agents/napcat/skills/napcat-media/tools/get_ai_characters/index.ts} +1 -1
- package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.js +20 -0
- package/{agent/tools/get_mini_app_ark.ts → agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.ts} +1 -1
- package/agents/napcat/skills/napcat-media/tools/ocr_image/index.js +16 -0
- package/{agent/tools/ocr_image.ts → agents/napcat/skills/napcat-media/tools/ocr_image/index.ts} +1 -1
- package/agents/napcat/skills/napcat-media/tools/translate/index.js +16 -0
- package/{agent/tools/translate.ts → agents/napcat/skills/napcat-media/tools/translate/index.ts} +1 -1
- package/agents/napcat/skills/napcat-messaging/SKILL.md +22 -0
- package/agents/napcat/skills/napcat-messaging/tools/forward_single_msg/index.js +22 -0
- package/{agent/tools/forward_single_msg.ts → agents/napcat/skills/napcat-messaging/tools/forward_single_msg/index.ts} +1 -1
- package/agents/napcat/skills/napcat-messaging/tools/mark_msg_as_read/index.js +17 -0
- package/{agent/tools/mark_msg_as_read.ts → agents/napcat/skills/napcat-messaging/tools/mark_msg_as_read/index.ts} +1 -1
- package/agents/napcat/skills/napcat-messaging/tools/send_forward_msg/index.js +19 -0
- package/{agent/tools/send_forward_msg.ts → agents/napcat/skills/napcat-messaging/tools/send_forward_msg/index.ts} +1 -1
- package/agents/napcat/skills/napcat-messaging/tools/send_poke/index.js +18 -0
- package/{agent/tools/send_poke.ts → agents/napcat/skills/napcat-messaging/tools/send_poke/index.ts} +1 -1
- package/agents/napcat/skills/napcat-messaging/tools/set_emoji_reaction/index.js +18 -0
- package/{agent/tools/set_emoji_reaction.ts → agents/napcat/skills/napcat-messaging/tools/set_emoji_reaction/index.ts} +1 -1
- package/agents/napcat/system.md +3 -0
- package/commands/{endpoint/add/[id].js → napcat/endpoint/add/[id]/index.js} +1 -1
- package/commands/napcat/endpoint/add/[id]/index.ts +3 -0
- package/{lib/napcat-endpoint-commands.js → commands/napcat/endpoint/definition.js} +4 -3
- package/{src/napcat-endpoint-commands.ts → commands/napcat/endpoint/definition.ts} +3 -3
- package/commands/{endpoint/list.js → napcat/endpoint/list/index.js} +1 -1
- package/commands/napcat/endpoint/list/index.ts +3 -0
- package/commands/{endpoint/remove/[id].js → napcat/endpoint/remove/[id]/index.js} +1 -1
- package/commands/napcat/endpoint/remove/[id]/index.ts +3 -0
- package/lib/index.d.ts +1 -1
- package/lib/napcat-runtime-state.js +1 -1
- package/lib/protocol.d.ts +5 -23
- package/lib/protocol.js +40 -27
- package/lib/wss-endpoint.d.ts +3 -0
- package/lib/wss-endpoint.js +51 -42
- package/package.json +33 -21
- package/schema.json +22 -1
- package/src/index.ts +0 -1
- package/src/napcat-runtime-state.ts +1 -1
- package/src/protocol.ts +47 -50
- package/src/wss-endpoint.ts +48 -43
- package/agent/skills/napcat.md +0 -311
- package/commands/endpoint/add/[id].ts +0 -3
- package/commands/endpoint/list.ts +0 -3
- package/commands/endpoint/remove/[id].ts +0 -3
- package/lib/napcat-endpoint-commands.d.ts +0 -1
- /package/{agent/PERMITS.md → PERMITS.md} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NapCat 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `napcat
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `napcat endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
6
|
|
package/src/protocol.ts
CHANGED
|
@@ -3,31 +3,15 @@
|
|
|
3
3
|
* No legacy Adapter/Endpoint / segment-mapper.
|
|
4
4
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef, type MediaRef } from '@zhin.js/
|
|
7
|
-
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
|
+
import { isMediaRef, type MediaRef, type ConversationRef } from '@zhin.js/im-contract';
|
|
8
7
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
9
8
|
|
|
10
9
|
const logger = getLogger('napcat');
|
|
11
10
|
|
|
12
|
-
/**
|
|
13
|
-
export interface
|
|
14
|
-
readonly context?: string;
|
|
11
|
+
/** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
|
|
12
|
+
export interface NapCatEndpointConfig {
|
|
15
13
|
readonly connection?: 'ws' | 'wss' | 'http';
|
|
16
|
-
readonly id
|
|
17
|
-
readonly access_token?: string;
|
|
18
|
-
readonly url?: string;
|
|
19
|
-
readonly path?: string;
|
|
20
|
-
readonly http_url?: string;
|
|
21
|
-
readonly post_path?: string;
|
|
22
|
-
readonly reconnect_interval?: number;
|
|
23
|
-
readonly heartbeat_interval?: number;
|
|
24
|
-
readonly poll_interval?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
28
|
-
export interface NapCatAdapterConfig {
|
|
29
|
-
readonly connection?: 'ws' | 'wss' | 'http';
|
|
30
|
-
readonly id?: string;
|
|
14
|
+
readonly id: string;
|
|
31
15
|
readonly access_token?: string;
|
|
32
16
|
readonly url?: string;
|
|
33
17
|
readonly path?: string;
|
|
@@ -36,8 +20,6 @@ export interface NapCatAdapterConfig {
|
|
|
36
20
|
readonly reconnect_interval?: number;
|
|
37
21
|
readonly heartbeat_interval?: number;
|
|
38
22
|
readonly poll_interval?: number;
|
|
39
|
-
/** Transitional: legacy root `endpoints[]` with `context: napcat`. */
|
|
40
|
-
readonly endpoints?: ReadonlyArray<NapCatLegacyEndpointRow>;
|
|
41
23
|
}
|
|
42
24
|
|
|
43
25
|
export interface NapCatConfigBase {
|
|
@@ -70,7 +52,6 @@ export interface NapCatHttpConfig extends NapCatConfigBase {
|
|
|
70
52
|
}
|
|
71
53
|
|
|
72
54
|
export type ResolvedNapCatConfig = NapCatWsConfig | NapCatWssConfig | NapCatHttpConfig;
|
|
73
|
-
export type NapCatEndpointConfig = ResolvedNapCatConfig;
|
|
74
55
|
|
|
75
56
|
export interface NapCatSender {
|
|
76
57
|
readonly role?: string;
|
|
@@ -140,52 +121,39 @@ function normalizeConnection(
|
|
|
140
121
|
return 'ws';
|
|
141
122
|
}
|
|
142
123
|
|
|
143
|
-
export function resolveNapCatConfig(config:
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|| (typeof entry?.id === 'string' && entry.id)
|
|
148
|
-
|| process.env.NAPCAT_BOT_NAME
|
|
149
|
-
|| 'napcat-bot';
|
|
150
|
-
const access_token = config.access_token ?? entry?.access_token;
|
|
124
|
+
export function resolveNapCatConfig(config: NapCatEndpointConfig): ResolvedNapCatConfig {
|
|
125
|
+
const connection = normalizeConnection(config.connection);
|
|
126
|
+
const id = requiredEndpointField(config.id, 'id');
|
|
127
|
+
const access_token = optionalEndpointField(config.access_token);
|
|
151
128
|
|
|
152
129
|
if (connection === 'ws') {
|
|
153
|
-
const url = config.url
|
|
154
|
-
if (!url) {
|
|
155
|
-
throw new TypeError(
|
|
156
|
-
'NapCat connection:ws requires url (plugins.<key>.url or endpoints with context: napcat)',
|
|
157
|
-
);
|
|
158
|
-
}
|
|
130
|
+
const url = requiredEndpointField(config.url, 'url');
|
|
159
131
|
return {
|
|
160
132
|
context: 'napcat',
|
|
161
133
|
connection: 'ws',
|
|
162
134
|
id,
|
|
163
135
|
access_token,
|
|
164
136
|
url,
|
|
165
|
-
reconnect_interval: config.reconnect_interval ??
|
|
166
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
137
|
+
reconnect_interval: config.reconnect_interval ?? 5000,
|
|
138
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
167
139
|
};
|
|
168
140
|
}
|
|
169
141
|
|
|
170
142
|
if (connection === 'wss') {
|
|
171
|
-
const path = config.path
|
|
172
|
-
if (!path) throw new TypeError('NapCat connection:wss requires path');
|
|
143
|
+
const path = requiredEndpointField(config.path, 'path');
|
|
173
144
|
return {
|
|
174
145
|
context: 'napcat',
|
|
175
146
|
connection: 'wss',
|
|
176
147
|
id,
|
|
177
148
|
access_token,
|
|
178
149
|
path,
|
|
179
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
150
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
180
151
|
};
|
|
181
152
|
}
|
|
182
153
|
|
|
183
154
|
if (connection === 'http') {
|
|
184
|
-
const http_url = config.http_url
|
|
185
|
-
const post_path = config.post_path
|
|
186
|
-
if (!http_url || !post_path) {
|
|
187
|
-
throw new TypeError('NapCat connection:http requires http_url and post_path');
|
|
188
|
-
}
|
|
155
|
+
const http_url = requiredEndpointField(config.http_url, 'http_url');
|
|
156
|
+
const post_path = requiredEndpointField(config.post_path, 'post_path');
|
|
189
157
|
return {
|
|
190
158
|
context: 'napcat',
|
|
191
159
|
connection: 'http',
|
|
@@ -193,13 +161,28 @@ export function resolveNapCatConfig(config: NapCatAdapterConfig = {}): ResolvedN
|
|
|
193
161
|
access_token,
|
|
194
162
|
http_url,
|
|
195
163
|
post_path,
|
|
196
|
-
poll_interval: config.poll_interval ??
|
|
164
|
+
poll_interval: config.poll_interval ?? 30_000,
|
|
197
165
|
};
|
|
198
166
|
}
|
|
199
167
|
|
|
200
168
|
throw new TypeError(`Unknown NapCat connection: ${String(connection)}`);
|
|
201
169
|
}
|
|
202
170
|
|
|
171
|
+
function requiredEndpointField(
|
|
172
|
+
value: unknown,
|
|
173
|
+
field: 'id' | 'url' | 'path' | 'http_url' | 'post_path',
|
|
174
|
+
): string {
|
|
175
|
+
const resolved = optionalEndpointField(value);
|
|
176
|
+
if (!resolved) {
|
|
177
|
+
throw new TypeError(`NapCat endpoint requires a non-empty ${field}`);
|
|
178
|
+
}
|
|
179
|
+
return resolved;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function optionalEndpointField(value: unknown): string | undefined {
|
|
183
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
203
186
|
export function isMessageEvent(
|
|
204
187
|
ev: NapCatEvent,
|
|
205
188
|
): ev is NapCatMessageEvent {
|
|
@@ -249,12 +232,26 @@ export function formatInboundContent(ev: NapCatEvent): string {
|
|
|
249
232
|
.map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
|
|
250
233
|
.join('');
|
|
251
234
|
}
|
|
252
|
-
if (typeof ev.message === 'string') return ev.message
|
|
235
|
+
if (typeof ev.message === 'string') return stripCqCodes(ev.message).trim();
|
|
253
236
|
return typeof ev.raw_message === 'string'
|
|
254
|
-
? ev.raw_message
|
|
237
|
+
? stripCqCodes(ev.raw_message).trim()
|
|
255
238
|
: '';
|
|
256
239
|
}
|
|
257
240
|
|
|
241
|
+
function stripCqCodes(input: string): string {
|
|
242
|
+
let result = '';
|
|
243
|
+
let cursor = 0;
|
|
244
|
+
while (cursor < input.length) {
|
|
245
|
+
const start = input.indexOf('[CQ:', cursor);
|
|
246
|
+
if (start < 0) return result + input.slice(cursor);
|
|
247
|
+
result += input.slice(cursor, start);
|
|
248
|
+
const end = input.indexOf(']', start + 4);
|
|
249
|
+
if (end < 0) return result + input.slice(start);
|
|
250
|
+
cursor = end + 1;
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
258
255
|
/**
|
|
259
256
|
* 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
|
|
260
257
|
* 显示名走 metadata.nickname,见 senderNickname。
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { Endpoint } from 'zhin.js/adapter';
|
|
|
2
2
|
/**
|
|
3
3
|
* NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
|
|
4
4
|
*/
|
|
5
|
-
import { clearInterval } from 'node:timers';
|
|
6
5
|
import {
|
|
6
|
+
createEndpointLifecycle,
|
|
7
7
|
createRecallEndpointControl,
|
|
8
|
+
type EndpointLifecycle,
|
|
8
9
|
type EndpointControl,
|
|
9
10
|
type EndpointManagement,
|
|
10
11
|
type EndpointSendRequest,
|
|
@@ -38,7 +39,6 @@ import {
|
|
|
38
39
|
callNapCatWsAction,
|
|
39
40
|
handleNapCatWsMessage,
|
|
40
41
|
rejectAllPending,
|
|
41
|
-
startNapCatHeartbeat,
|
|
42
42
|
} from './ws-transport.js';
|
|
43
43
|
import { NapCatWsEndpoint } from './ws-endpoint.js';
|
|
44
44
|
import {
|
|
@@ -62,9 +62,10 @@ export class NapCatWssEndpoint extends Endpoint<NapcatClient> {
|
|
|
62
62
|
readonly management: EndpointManagement = createNapCatEndpointManagement(this.client);
|
|
63
63
|
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
64
64
|
readonly content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
|
|
65
|
+
readonly #connectionLifecycle: EndpointLifecycle;
|
|
65
66
|
#ws?: NapCatWsSocket;
|
|
66
67
|
#wsRelease?: () => void;
|
|
67
|
-
#
|
|
68
|
+
#connectionTask = Promise.resolve();
|
|
68
69
|
#requestId = { value: 0 };
|
|
69
70
|
#pending = new Map<string, NapCatPendingAction>();
|
|
70
71
|
#open = false;
|
|
@@ -74,6 +75,10 @@ export class NapCatWssEndpoint extends Endpoint<NapcatClient> {
|
|
|
74
75
|
super();
|
|
75
76
|
this.#logger = getAdapterLogger('napcat', options.config.id);
|
|
76
77
|
this.#options = options;
|
|
78
|
+
this.#connectionLifecycle = createEndpointLifecycle({
|
|
79
|
+
name: `${options.config.id}:inbound`,
|
|
80
|
+
reconnect: false,
|
|
81
|
+
});
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
async start(): Promise<void> {
|
|
@@ -81,7 +86,15 @@ export class NapCatWssEndpoint extends Endpoint<NapcatClient> {
|
|
|
81
86
|
this.#started = true;
|
|
82
87
|
const handle = this.#options.http.ws(this.#options.config.path);
|
|
83
88
|
this.#wsRelease = handle.onConnection((connection) => {
|
|
84
|
-
this.#
|
|
89
|
+
this.#connectionTask = this.#connectionTask
|
|
90
|
+
.then(() => this.#acceptConnection(connection))
|
|
91
|
+
.catch((error) => {
|
|
92
|
+
this.#logger.warn(formatCompact({
|
|
93
|
+
op: 'wss_connection_failed',
|
|
94
|
+
endpoint: this.#options.config.id,
|
|
95
|
+
error: error instanceof Error ? error.message : String(error),
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
85
98
|
});
|
|
86
99
|
this.#logger.info(formatCompact({
|
|
87
100
|
op: 'listen',
|
|
@@ -103,20 +116,11 @@ export class NapCatWssEndpoint extends Endpoint<NapcatClient> {
|
|
|
103
116
|
this.#open = false;
|
|
104
117
|
this.#wsRelease?.();
|
|
105
118
|
this.#wsRelease = undefined;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.#heartbeatTimer = undefined;
|
|
109
|
-
}
|
|
119
|
+
await this.#connectionTask;
|
|
120
|
+
await this.#connectionLifecycle.stop();
|
|
110
121
|
rejectAllPending(this.#pending);
|
|
111
122
|
this.#inboundDeduper.clear();
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
this.#ws.close();
|
|
115
|
-
} catch {
|
|
116
|
-
/* ignore */
|
|
117
|
-
}
|
|
118
|
-
this.#ws = undefined;
|
|
119
|
-
}
|
|
123
|
+
this.#ws = undefined;
|
|
120
124
|
this.#started = false;
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -192,40 +196,41 @@ export class NapCatWssEndpoint extends Endpoint<NapcatClient> {
|
|
|
192
196
|
});
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
#acceptConnection(connection: WsConnection): void {
|
|
199
|
+
async #acceptConnection(connection: WsConnection): Promise<void> {
|
|
196
200
|
if (!verifyNapCatAccessToken(this.#options.config.access_token, connection.request)) {
|
|
197
201
|
connection.socket.close(4003, 'Unauthorized');
|
|
198
202
|
return;
|
|
199
203
|
}
|
|
200
204
|
const socket = connection.socket as unknown as NapCatWsSocket;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.#ws,
|
|
211
|
-
this.#options.config.heartbeat_interval,
|
|
212
|
-
this.#heartbeatTimer,
|
|
213
|
-
);
|
|
214
|
-
socket.on('message', (data) => {
|
|
215
|
-
handleNapCatWsMessage(data, {
|
|
216
|
-
endpointId: this.#options.config.id,
|
|
217
|
-
pending: this.#pending,
|
|
218
|
-
admit: (event) => this.admit(event),
|
|
205
|
+
await this.#connectionLifecycle.stop();
|
|
206
|
+
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
207
|
+
this.#ws = socket;
|
|
208
|
+
lifecycleHandle.onForceClose(() => {
|
|
209
|
+
try {
|
|
210
|
+
socket.close();
|
|
211
|
+
} catch {
|
|
212
|
+
/* ignore */
|
|
213
|
+
}
|
|
219
214
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
clearInterval(this.#heartbeatTimer);
|
|
226
|
-
this.#heartbeatTimer = undefined;
|
|
215
|
+
this.#connectionLifecycle.startHeartbeat(() => {
|
|
216
|
+
try {
|
|
217
|
+
socket.ping?.();
|
|
218
|
+
} catch {
|
|
219
|
+
/* ignore */
|
|
227
220
|
}
|
|
228
|
-
}
|
|
221
|
+
}, this.#options.config.heartbeat_interval);
|
|
222
|
+
socket.on('message', (data) => {
|
|
223
|
+
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
224
|
+
handleNapCatWsMessage(data, {
|
|
225
|
+
endpointId: this.#options.config.id,
|
|
226
|
+
pending: this.#pending,
|
|
227
|
+
admit: (event) => this.admit(event),
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
socket.on('close', () => {
|
|
231
|
+
if (this.#ws === socket) this.#ws = undefined;
|
|
232
|
+
lifecycleHandle.notifyClosed(new Error('NapCat reverse WebSocket closed'));
|
|
233
|
+
});
|
|
229
234
|
});
|
|
230
235
|
this.#logger.debug(formatCompact({
|
|
231
236
|
endpoint: this.#options.config.id,
|
package/agent/skills/napcat.md
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: napcat
|
|
3
|
-
platforms:
|
|
4
|
-
- napcat
|
|
5
|
-
description: >-
|
|
6
|
-
NapCatQQ 适配器完整能力集。当用户在 QQ 群或私聊中请求群管理(踢人、禁言、设管理、改名片、
|
|
7
|
-
设头衔)、社交互动(戳一戳、点赞、表情回应)、消息操作(合并转发、单条转发、
|
|
8
|
-
消息历史回溯)、群运营(精华消息、群公告、群签到、群文件管理)、多媒体能力(AI 语音
|
|
9
|
-
TTS、图片 OCR 文字识别、小程序卡片)、个人设置(修改资料/头像/签名/在线状态)、
|
|
10
|
-
或任何 QQ 特有功能时,都应使用此技能。即使用户没有明确提到 NapCat 或 OneBot,
|
|
11
|
-
只要上下文是 QQ 群聊/私聊场景且涉及上述能力,就应触发。覆盖 OneBot11 标准 +
|
|
12
|
-
go-cqhttp 扩展 + NapCat 独有 API 共 92 个接口。
|
|
13
|
-
keywords:
|
|
14
|
-
- napcat
|
|
15
|
-
- napneko
|
|
16
|
-
- onebot11
|
|
17
|
-
- adapter:napcat
|
|
18
|
-
- qq
|
|
19
|
-
- 群管理
|
|
20
|
-
- 头衔
|
|
21
|
-
- 戳一戳
|
|
22
|
-
- 表情回应
|
|
23
|
-
- 合并转发
|
|
24
|
-
- 群文件
|
|
25
|
-
- 精华消息
|
|
26
|
-
- 群公告
|
|
27
|
-
- AI语音
|
|
28
|
-
- OCR
|
|
29
|
-
- 小程序
|
|
30
|
-
- list_members
|
|
31
|
-
- 禁言
|
|
32
|
-
- 踢人
|
|
33
|
-
- 点赞
|
|
34
|
-
- 签到
|
|
35
|
-
- 转发
|
|
36
|
-
- 语音
|
|
37
|
-
- 翻译
|
|
38
|
-
- 头像
|
|
39
|
-
- 在线状态
|
|
40
|
-
tags:
|
|
41
|
-
- group
|
|
42
|
-
- management
|
|
43
|
-
- im
|
|
44
|
-
- qq
|
|
45
|
-
- napcat
|
|
46
|
-
tools:
|
|
47
|
-
- napcat_kick_member
|
|
48
|
-
- napcat_mute_member
|
|
49
|
-
- napcat_mute_all
|
|
50
|
-
- napcat_set_admin
|
|
51
|
-
- napcat_set_nickname
|
|
52
|
-
- napcat_set_group_name
|
|
53
|
-
- napcat_list_members
|
|
54
|
-
- napcat_get_group_info
|
|
55
|
-
- napcat_send_poke
|
|
56
|
-
- napcat_set_emoji_reaction
|
|
57
|
-
- napcat_send_forward_msg
|
|
58
|
-
- napcat_forward_single_msg
|
|
59
|
-
- napcat_send_like
|
|
60
|
-
- napcat_set_essence_msg
|
|
61
|
-
- napcat_delete_essence_msg
|
|
62
|
-
- napcat_get_essence_list
|
|
63
|
-
- napcat_send_group_notice
|
|
64
|
-
- napcat_get_group_notice
|
|
65
|
-
- napcat_del_group_notice
|
|
66
|
-
- napcat_upload_group_file
|
|
67
|
-
- napcat_get_group_file_url
|
|
68
|
-
- napcat_get_group_root_files
|
|
69
|
-
- napcat_get_group_shut_list
|
|
70
|
-
- napcat_set_group_portrait
|
|
71
|
-
- napcat_set_title
|
|
72
|
-
- napcat_group_sign
|
|
73
|
-
- napcat_ai_tts
|
|
74
|
-
- napcat_get_ai_characters
|
|
75
|
-
- napcat_ocr_image
|
|
76
|
-
- napcat_get_mini_app_ark
|
|
77
|
-
- napcat_get_group_msg_history
|
|
78
|
-
- napcat_get_friend_msg_history
|
|
79
|
-
- napcat_get_user_status
|
|
80
|
-
- napcat_get_group_info_ex
|
|
81
|
-
- napcat_set_profile
|
|
82
|
-
- napcat_set_avatar
|
|
83
|
-
- napcat_set_online_status
|
|
84
|
-
- napcat_set_signature
|
|
85
|
-
- napcat_translate
|
|
86
|
-
- napcat_mark_msg_as_read
|
|
87
|
-
- napcat_download_file
|
|
88
|
-
- napcat_delete_friend
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
# NapCat QQ 适配器技能
|
|
92
|
-
|
|
93
|
-
NapCat 是 OneBot11 协议的超集实现,提供标准群管理之外大量 QQ 特有功能。这份技能帮助你正确选择工具、组合调用、处理边界情况。
|
|
94
|
-
|
|
95
|
-
## 核心原则
|
|
96
|
-
|
|
97
|
-
### 先查后操作
|
|
98
|
-
|
|
99
|
-
用户说「把小明踢了」,但工具需要的是 QQ 号(数字),不是昵称。所有针对具体成员的操作,如果手头只有昵称/名片,第一步永远是 `napcat_list_members` 获取成员列表,从中匹配到 `user_id`,然后再执行后续操作。不要猜测 QQ 号。
|
|
100
|
-
|
|
101
|
-
**Example:**
|
|
102
|
-
```
|
|
103
|
-
用户: 把群里那个叫"小飞"的禁言10分钟
|
|
104
|
-
步骤1: napcat_list_members → 找到 小飞 的 user_id = 123456
|
|
105
|
-
步骤2: napcat_mute_member(user_id=123456, duration=600)
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### 权限意识
|
|
109
|
-
|
|
110
|
-
工具分三个权限层级,调用前先判断 Endpoint 在群里的身份:
|
|
111
|
-
|
|
112
|
-
- **user** — 普通成员即可,如查询、点赞、戳一戳
|
|
113
|
-
- **scene_admin** — 需要管理员权限,如踢人、禁言、设精华、发公告
|
|
114
|
-
- **scene_owner** — 需要群主权限,如设管理员、设专属头衔
|
|
115
|
-
|
|
116
|
-
如果 Endpoint 权限不足,API 会返回错误。遇到权限不足时,向用户说明需要什么权限,而不是重试。
|
|
117
|
-
|
|
118
|
-
### 数值单位
|
|
119
|
-
|
|
120
|
-
- **禁言 duration**:单位是秒。600 = 10 分钟,3600 = 1 小时,86400 = 1 天。设为 **0** 是解除禁言,不是永久禁言。
|
|
121
|
-
- **message_id**:消息收到时自带的数字 ID,用于回应、精华、转发等操作。
|
|
122
|
-
- **group_id / user_id**:QQ 号,纯数字。
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## 工具分类与使用指南
|
|
127
|
-
|
|
128
|
-
### 群管理(基础)
|
|
129
|
-
|
|
130
|
-
日常群管理操作。Bot 需要管理员权限(`set_admin` 需群主权限)。
|
|
131
|
-
|
|
132
|
-
| 工具 | 用途 | 关键参数 |
|
|
133
|
-
|------|------|----------|
|
|
134
|
-
| `napcat_kick_member` | 踢出成员 | `user_id` |
|
|
135
|
-
| `napcat_mute_member` | 禁言/解禁 | `user_id`, `duration`(秒,0=解禁) |
|
|
136
|
-
| `napcat_mute_all` | 全员禁言/解除 | `enable`(true/false) |
|
|
137
|
-
| `napcat_set_admin` | 设/取消管理员 | `user_id`, `enable` |
|
|
138
|
-
| `napcat_set_nickname` | 改群名片 | `user_id`, `nickname` |
|
|
139
|
-
| `napcat_set_group_name` | 改群名称 | `name` |
|
|
140
|
-
| `napcat_set_title` | 设专属头衔 | `user_id`, `title`(需群主) |
|
|
141
|
-
| `napcat_list_members` | 获取成员列表 | — |
|
|
142
|
-
| `napcat_get_group_info` | 获取群基本信息 | — |
|
|
143
|
-
| `napcat_get_group_info_ex` | 获取群详细信息 | — |
|
|
144
|
-
| `napcat_get_group_shut_list` | 获取当前禁言列表 | — |
|
|
145
|
-
|
|
146
|
-
**批量禁言场景:** 用户说「把最近刷屏的人都禁言」时,先用 `napcat_get_group_msg_history` 分析最近消息,识别刷屏用户,然后逐个 `napcat_mute_member`。
|
|
147
|
-
|
|
148
|
-
### 社交互动
|
|
149
|
-
|
|
150
|
-
不需要管理员权限的轻量交互。
|
|
151
|
-
|
|
152
|
-
| 工具 | 用途 | 说明 |
|
|
153
|
-
|------|------|------|
|
|
154
|
-
| `napcat_send_poke` | 戳一戳 | `group_id` 不传 = 私聊戳 |
|
|
155
|
-
| `napcat_set_emoji_reaction` | 表情回应 | 对某条消息贴表情,需 `message_id` + `emoji_id` |
|
|
156
|
-
| `napcat_send_like` | 给好友点赞 | 每人每天上限 10 次 |
|
|
157
|
-
| `napcat_group_sign` | 群签到/打卡 | 每天一次 |
|
|
158
|
-
|
|
159
|
-
**Example:**
|
|
160
|
-
```
|
|
161
|
-
用户: 戳一下群里的张三
|
|
162
|
-
步骤1: napcat_list_members → 找到张三 user_id = 789
|
|
163
|
-
步骤2: napcat_send_poke(user_id=789, group_id=当前群号)
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### 消息操作
|
|
167
|
-
|
|
168
|
-
转发、历史查询是 NapCat 超越标准 OneBot11 的核心能力。
|
|
169
|
-
|
|
170
|
-
| 工具 | 用途 | 说明 |
|
|
171
|
-
|------|------|------|
|
|
172
|
-
| `napcat_send_forward_msg` | 合并转发 | 多条消息打包为一条转发消息 |
|
|
173
|
-
| `napcat_forward_single_msg` | 单条转发 | 把某条消息转到另一个群/好友 |
|
|
174
|
-
| `napcat_get_group_msg_history` | 群消息历史 | 支持指定起始序号和条数 |
|
|
175
|
-
| `napcat_get_friend_msg_history` | 私聊历史 | 同上 |
|
|
176
|
-
| `napcat_mark_msg_as_read` | 标记已读 | — |
|
|
177
|
-
|
|
178
|
-
**合并转发的 messages 格式:**
|
|
179
|
-
|
|
180
|
-
`messages` 参数是 JSON 字符串,每个元素是一个 node 节点:
|
|
181
|
-
|
|
182
|
-
```json
|
|
183
|
-
[
|
|
184
|
-
{
|
|
185
|
-
"type": "node",
|
|
186
|
-
"data": {
|
|
187
|
-
"name": "发送者昵称",
|
|
188
|
-
"uin": "10001",
|
|
189
|
-
"content": [{"type": "text", "data": {"text": "消息内容"}}]
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
]
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
也可以用 `id` 引用已有消息:`{"type": "node", "data": {"id": "消息ID"}}`
|
|
196
|
-
|
|
197
|
-
### 群运营
|
|
198
|
-
|
|
199
|
-
精华消息、群公告、群文件、群头像 — 让群看起来正规且有序。
|
|
200
|
-
|
|
201
|
-
| 工具 | 用途 | 权限 |
|
|
202
|
-
|------|------|------|
|
|
203
|
-
| `napcat_set_essence_msg` | 设为精华消息 | admin |
|
|
204
|
-
| `napcat_delete_essence_msg` | 取消精华 | admin |
|
|
205
|
-
| `napcat_get_essence_list` | 精华消息列表 | user |
|
|
206
|
-
| `napcat_send_group_notice` | 发群公告 | admin |
|
|
207
|
-
| `napcat_get_group_notice` | 查看公告 | user |
|
|
208
|
-
| `napcat_del_group_notice` | 删除公告 | admin |
|
|
209
|
-
| `napcat_upload_group_file` | 上传群文件 | user |
|
|
210
|
-
| `napcat_get_group_file_url` | 获取文件下载链接 | user |
|
|
211
|
-
| `napcat_get_group_root_files` | 群文件列表 | user |
|
|
212
|
-
| `napcat_set_group_portrait` | 修改群头像 | admin |
|
|
213
|
-
|
|
214
|
-
**群公告带图:** `napcat_send_group_notice` 的 `image` 参数支持 URL 或 base64,方便把生成的图片直接发到公告里。
|
|
215
|
-
|
|
216
|
-
**群文件工作流:** 用户说「帮我把这个文件发到群里」→ 用 `napcat_upload_group_file`,`file` 可以是本地路径或 URL。如果用户问「群里那个文件在哪下载」→ 先 `napcat_get_group_root_files` 列出文件,找到 `file_id` 和 `busid`,再 `napcat_get_group_file_url` 获取下载链接。
|
|
217
|
-
|
|
218
|
-
### AI 与多媒体
|
|
219
|
-
|
|
220
|
-
NapCat 独有的多媒体处理能力,标准 OneBot11 不支持这些。
|
|
221
|
-
|
|
222
|
-
| 工具 | 用途 | 说明 |
|
|
223
|
-
|------|------|------|
|
|
224
|
-
| `napcat_ai_tts` | AI 文字转语音 | 在群里发语音,需指定角色 |
|
|
225
|
-
| `napcat_get_ai_characters` | 语音角色列表 | 配合 ai_tts 使用 |
|
|
226
|
-
| `napcat_ocr_image` | 图片文字识别 | 传入图片的 file 字段 |
|
|
227
|
-
| `napcat_get_mini_app_ark` | 小程序卡片签名 | 生成可分享的小程序卡片 |
|
|
228
|
-
| `napcat_translate` | 英译中翻译 | NapCat 内置翻译 |
|
|
229
|
-
| `napcat_download_file` | 下载文件到缓存 | 返回本地路径 |
|
|
230
|
-
|
|
231
|
-
**AI 语音工作流:** 用户说「用甜美女声读一段话」→ 先 `napcat_get_ai_characters` 列出可用角色,从中选择匹配的 `character_id`,然后 `napcat_ai_tts(character=所选ID, text=要读的内容)`。角色列表因群不同可能不一样,所以每次都应重新查询。
|
|
232
|
-
|
|
233
|
-
**OCR 使用:** 当用户发了一张带文字的图片并问「这上面写了什么」,从消息中提取图片的 `file` 字段(通常是 `file://...` 或一个 hash),传给 `napcat_ocr_image`。
|
|
234
|
-
|
|
235
|
-
### 个人设置
|
|
236
|
-
|
|
237
|
-
修改 Endpoint 自身的 QQ 资料,通常是用户明确要求时才使用。
|
|
238
|
-
|
|
239
|
-
| 工具 | 用途 |
|
|
240
|
-
|------|------|
|
|
241
|
-
| `napcat_set_profile` | 修改昵称、公司、邮箱等 |
|
|
242
|
-
| `napcat_set_avatar` | 换头像(URL 或 base64) |
|
|
243
|
-
| `napcat_set_online_status` | 设在线状态 |
|
|
244
|
-
| `napcat_set_signature` | 设个性签名 |
|
|
245
|
-
| `napcat_delete_friend` | 删除好友 |
|
|
246
|
-
| `napcat_get_user_status` | 查看某人在线状态 |
|
|
247
|
-
|
|
248
|
-
**在线状态码:** 11=在线, 21=离开, 31=隐身, 41=忙碌, 50=请勿打扰, 60=Q我吧。
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## 常见场景决策树
|
|
253
|
-
|
|
254
|
-
### 用户想管理群成员
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
用户提供了 QQ 号?
|
|
258
|
-
├─ 是 → 直接操作
|
|
259
|
-
└─ 否(只有昵称/名片)
|
|
260
|
-
└─ napcat_list_members → 匹配 → 操作
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### 用户想查看/操作消息历史
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
群聊还是私聊?
|
|
267
|
-
├─ 群聊 → napcat_get_group_msg_history(group_id)
|
|
268
|
-
└─ 私聊 → napcat_get_friend_msg_history(user_id)
|
|
269
|
-
|
|
270
|
-
想转发其中某条?
|
|
271
|
-
├─ 转单条 → napcat_forward_single_msg(message_id)
|
|
272
|
-
└─ 打包多条 → napcat_send_forward_msg(messages=[node...])
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 用户想发语音
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
napcat_get_ai_characters(group_id) → 列出角色
|
|
279
|
-
选择最匹配的 character_id
|
|
280
|
-
napcat_ai_tts(group_id, character, text)
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### 用户想发公告/管理精华
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
发新公告 → napcat_send_group_notice(content, image?)
|
|
287
|
-
查看公告 → napcat_get_group_notice
|
|
288
|
-
删除公告 → 先 get 拿到 notice_id → napcat_del_group_notice
|
|
289
|
-
|
|
290
|
-
设精华 → napcat_set_essence_msg(message_id)
|
|
291
|
-
取消精华 → napcat_delete_essence_msg(message_id)
|
|
292
|
-
看精华列表 → napcat_get_essence_list
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
## 易错点
|
|
298
|
-
|
|
299
|
-
1. **禁言 duration=0 是解禁**,不是永久禁言。没有「永久禁言」的概念,最长可以设很大的数(如 2592000 = 30 天)。
|
|
300
|
-
|
|
301
|
-
2. **合并转发的 messages 必须是 JSON 字符串**,不是对象。工具内部会解析,但传参时要传字符串化的 JSON。
|
|
302
|
-
|
|
303
|
-
3. **头衔(set_title)只有群主能设**,管理员不行。用户说「给他加个头衔」但 Endpoint 不是群主时,需告知无法操作。
|
|
304
|
-
|
|
305
|
-
4. **点赞有每日上限**(每人 10 次),用户说「给他点 100 个赞」时要说明限制。
|
|
306
|
-
|
|
307
|
-
5. **AI 语音只能在群聊用**,私聊不支持。如果用户在私聊请求 AI 语音,建议改用群聊。
|
|
308
|
-
|
|
309
|
-
6. **群文件操作需要 file_id 和 busid 两个参数**,它们来自 `get_group_root_files` 的返回,不要编造。
|
|
310
|
-
|
|
311
|
-
7. **OCR 的 image 参数**不是图片 URL,而是消息中图片的 `file` 字段值(形如 `xxxxx.image` 或本地路径)。
|
|
@@ -1 +0,0 @@
|
|
|
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>>>;
|
|
File without changes
|