@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.
- package/CHANGELOG.md +58 -0
- package/README.md +43 -96
- package/adapters/napcat.ts +39 -0
- package/agent/tools/ai_tts.ts +21 -0
- package/agent/tools/del_group_notice.ts +22 -0
- package/agent/tools/delete_essence_msg.ts +21 -0
- package/agent/tools/delete_friend.ts +20 -0
- package/agent/tools/download_file.ts +19 -0
- package/agent/tools/forward_single_msg.ts +32 -0
- package/agent/tools/get_ai_characters.ts +19 -0
- package/agent/tools/get_essence_list.ts +19 -0
- package/agent/tools/get_friend_msg_history.ts +20 -0
- package/agent/tools/get_group_file_url.ts +21 -0
- package/agent/tools/get_group_info_ex.ts +19 -0
- package/agent/tools/get_group_msg_history.ts +21 -0
- package/agent/tools/get_group_notice.ts +19 -0
- package/agent/tools/get_group_root_files.ts +19 -0
- package/agent/tools/get_group_shut_list.ts +19 -0
- package/agent/tools/get_mini_app_ark.ts +22 -0
- package/agent/tools/get_user_status.ts +18 -0
- package/agent/tools/group_sign.ts +20 -0
- package/agent/tools/mark_msg_as_read.ts +19 -0
- package/agent/tools/ocr_image.ts +18 -0
- package/agent/tools/send_forward_msg.ts +31 -0
- package/agent/tools/send_group_notice.ts +23 -0
- package/agent/tools/send_like.ts +20 -0
- package/agent/tools/send_poke.ts +20 -0
- package/agent/tools/set_avatar.ts +19 -0
- package/agent/tools/set_emoji_reaction.ts +20 -0
- package/agent/tools/set_essence_msg.ts +21 -0
- package/agent/tools/set_group_portrait.ts +22 -0
- package/agent/tools/set_online_status.ts +20 -0
- package/agent/tools/set_profile.ts +23 -0
- package/agent/tools/set_signature.ts +19 -0
- package/agent/tools/set_title.ts +23 -0
- package/agent/tools/translate.ts +18 -0
- package/agent/tools/upload_group_file.ts +23 -0
- package/lib/http-endpoint.d.ts +26 -0
- package/lib/http-endpoint.js +147 -0
- package/lib/index.d.ts +11 -21
- package/lib/index.js +10 -59
- package/lib/napcat-agent-deps.d.ts +51 -0
- package/lib/napcat-agent-deps.js +33 -0
- package/lib/napcat-inbound.d.ts +12 -5
- package/lib/napcat-inbound.js +28 -13
- package/lib/onebot-get-msg.d.ts +13 -2
- package/lib/onebot-get-msg.js +0 -1
- package/lib/protocol.d.ts +151 -0
- package/lib/protocol.js +217 -0
- package/lib/webhook.d.ts +2 -0
- package/lib/webhook.js +14 -0
- package/lib/ws-endpoint.d.ts +61 -0
- package/lib/ws-endpoint.js +357 -0
- package/lib/ws-transport.d.ts +13 -0
- package/lib/ws-transport.js +75 -0
- package/lib/ws-types.d.ts +17 -0
- package/lib/ws-types.js +1 -0
- package/lib/wss-auth.d.ts +2 -0
- package/lib/wss-auth.js +16 -0
- package/lib/wss-endpoint.d.ts +25 -0
- package/lib/wss-endpoint.js +157 -0
- package/package.json +48 -32
- package/plugin.ts +13 -0
- package/schema.json +48 -0
- package/src/http-endpoint.ts +197 -0
- package/src/index.ts +66 -82
- package/src/napcat-agent-deps.ts +95 -0
- package/src/napcat-inbound.ts +34 -10
- package/src/onebot-get-msg.ts +8 -2
- package/src/protocol.ts +373 -0
- package/src/webhook.ts +16 -0
- package/src/ws-endpoint.ts +452 -0
- package/src/ws-transport.ts +105 -0
- package/src/ws-types.ts +20 -0
- package/src/wss-auth.ts +17 -0
- package/src/wss-endpoint.ts +205 -0
- package/client/NapCatManagement.tsx +0 -113
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -27
- package/lib/adapter.d.ts +0 -36
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -102
- package/lib/adapter.js.map +0 -1
- package/lib/agent-prompt.d.ts +0 -3
- package/lib/agent-prompt.d.ts.map +0 -1
- package/lib/agent-prompt.js +0 -106
- package/lib/agent-prompt.js.map +0 -1
- package/lib/endpoint-base.d.ts +0 -144
- package/lib/endpoint-base.d.ts.map +0 -1
- package/lib/endpoint-base.js +0 -341
- package/lib/endpoint-base.js.map +0 -1
- package/lib/endpoint-http.d.ts +0 -17
- package/lib/endpoint-http.d.ts.map +0 -1
- package/lib/endpoint-http.js +0 -106
- package/lib/endpoint-http.js.map +0 -1
- package/lib/endpoint-ws-client.d.ts +0 -20
- package/lib/endpoint-ws-client.d.ts.map +0 -1
- package/lib/endpoint-ws-client.js +0 -139
- package/lib/endpoint-ws-client.js.map +0 -1
- package/lib/endpoint-ws-server.d.ts +0 -20
- package/lib/endpoint-ws-server.d.ts.map +0 -1
- package/lib/endpoint-ws-server.js +0 -137
- package/lib/endpoint-ws-server.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/napcat-inbound.d.ts.map +0 -1
- package/lib/napcat-inbound.js.map +0 -1
- package/lib/onebot-get-msg.d.ts.map +0 -1
- package/lib/onebot-get-msg.js.map +0 -1
- package/lib/routes.d.ts +0 -4
- package/lib/routes.d.ts.map +0 -1
- package/lib/routes.js +0 -61
- package/lib/routes.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/tools.d.ts +0 -8
- package/lib/tools.d.ts.map +0 -1
- package/lib/tools.js +0 -605
- package/lib/tools.js.map +0 -1
- package/lib/types.d.ts +0 -275
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -6
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -109
- package/src/agent-prompt.ts +0 -116
- package/src/endpoint-base.ts +0 -386
- package/src/endpoint-http.ts +0 -95
- package/src/endpoint-ws-client.ts +0 -137
- package/src/endpoint-ws-server.ts +0 -136
- package/src/routes.ts +0 -61
- package/src/segment-mapper.ts +0 -1
- package/src/tools.ts +0 -632
- package/src/types.ts +0 -266
- /package/{skills/napcat → agent}/PERMITS.md +0 -0
- /package/{skills/napcat/SKILL.md → agent/skills/napcat.md} +0 -0
package/lib/index.js
CHANGED
|
@@ -1,59 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { createNapCatAgentPromptContributor } from './agent-prompt.js';
|
|
12
|
-
import { registerRoutes } from './routes.js';
|
|
13
|
-
export * from './types.js';
|
|
14
|
-
export { NapCatWsClient } from './endpoint-ws-client.js';
|
|
15
|
-
export { NapCatWsServer } from './endpoint-ws-server.js';
|
|
16
|
-
export { NapCatHttpEndpoint } from './endpoint-http.js';
|
|
17
|
-
export { NapCatAdapter } from './adapter.js';
|
|
18
|
-
export { NapCatEndpointBase } from './endpoint-base.js';
|
|
19
|
-
const plugin = usePlugin();
|
|
20
|
-
const { provide, useContext } = plugin;
|
|
21
|
-
// ── 适配器注册 ─────────────────────────────────────────────────────
|
|
22
|
-
provide({
|
|
23
|
-
name: 'napcat',
|
|
24
|
-
description: 'NapCatQQ 适配器(OneBot11 + go-cqhttp + NapCat 扩展,正向/反向 WS + HTTP)',
|
|
25
|
-
mounted: async (p) => {
|
|
26
|
-
registerAgentPromptContributor(createNapCatAgentPromptContributor());
|
|
27
|
-
const adapter = new NapCatAdapter(p);
|
|
28
|
-
await adapter.start();
|
|
29
|
-
return adapter;
|
|
30
|
-
},
|
|
31
|
-
dispose: async (adapter) => {
|
|
32
|
-
unregisterAgentPromptContributor('napcat');
|
|
33
|
-
await adapter.stop();
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
// ── AI 工具注册 ────────────────────────────────────────────────────
|
|
37
|
-
useContext('tool', 'napcat', (toolService, napcat) => {
|
|
38
|
-
const disposers = [];
|
|
39
|
-
disposers.push(registerDefaultScenePlatformPermitChecker('napcat'));
|
|
40
|
-
const sceneTools = createSceneManagementTools(napcat, 'napcat');
|
|
41
|
-
disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
|
|
42
|
-
const napcatTools = createNapCatTools(napcat);
|
|
43
|
-
disposers.push(...napcatTools.map(t => toolService.addTool(t, plugin.name)));
|
|
44
|
-
return () => disposers.forEach(d => d());
|
|
45
|
-
});
|
|
46
|
-
// ── Web 控制台入口 ─────────────────────────────────────────────────
|
|
47
|
-
useContext('web', (pageManager) => {
|
|
48
|
-
pageManager.addEntry({
|
|
49
|
-
id: 'napcat',
|
|
50
|
-
development: path.resolve(import.meta.dirname, '../client/index.tsx'),
|
|
51
|
-
production: path.resolve(import.meta.dirname, '../dist/index.js'),
|
|
52
|
-
meta: { name: 'NapCat' },
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
// ── HTTP 路由 ──────────────────────────────────────────────────────
|
|
56
|
-
useContext('router', 'napcat', async (router, napcat) => {
|
|
57
|
-
registerRoutes(router, napcat);
|
|
58
|
-
});
|
|
59
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatInboundTarget, formatOutboundSegments, getChannelId, isMessageEvent, parseSendTarget, resolveNapCatConfig, senderNickname, senderUserId, } from './protocol.js';
|
|
2
|
+
export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
3
|
+
export { getEndpoint, getNapcatAgentDeps, registerNapcatAgentEndpoint, setNapcatAgentDeps, } from './napcat-agent-deps.js';
|
|
4
|
+
export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
|
|
5
|
+
export { NapCatWsEndpoint, } from './ws-endpoint.js';
|
|
6
|
+
export { NapCatWssEndpoint, } from './wss-endpoint.js';
|
|
7
|
+
export { NapCatHttpEndpoint, } from './http-endpoint.js';
|
|
8
|
+
export { callNapCatWsAction, decodeWsPayload, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
|
|
9
|
+
export { verifyNapCatAccessToken } from './wss-auth.js';
|
|
10
|
+
export { readRequestBody } from './webhook.js';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for napcat.
|
|
3
|
+
* Endpoints register themselves on start; tools look up by config name / endpoint id.
|
|
4
|
+
*/
|
|
5
|
+
export interface NapcatAgentEndpoint {
|
|
6
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
7
|
+
setTitle(groupId: number, userId: number, title: string, duration?: number): Promise<boolean>;
|
|
8
|
+
sendLike(userId: number, times?: number): Promise<unknown>;
|
|
9
|
+
deleteFriend(userId: number): Promise<unknown>;
|
|
10
|
+
markMsgAsRead(messageId: number): Promise<unknown>;
|
|
11
|
+
ocrImage(image: string): Promise<unknown>;
|
|
12
|
+
setQQProfile(nickname: string, company?: string, email?: string, college?: string, personalNote?: string): Promise<unknown>;
|
|
13
|
+
setGroupPortrait(groupId: number, file: string): Promise<unknown>;
|
|
14
|
+
setEssenceMsg(messageId: number): Promise<unknown>;
|
|
15
|
+
deleteEssenceMsg(messageId: number): Promise<unknown>;
|
|
16
|
+
getEssenceMsgList(groupId: number): Promise<unknown>;
|
|
17
|
+
sendGroupSign(groupId: number): Promise<unknown>;
|
|
18
|
+
sendGroupNotice(groupId: number, content: string, image?: string): Promise<unknown>;
|
|
19
|
+
getGroupNotice(groupId: number): Promise<unknown>;
|
|
20
|
+
deleteGroupNotice(groupId: number, noticeId: string): Promise<unknown>;
|
|
21
|
+
uploadGroupFile(groupId: number, file: string, name: string, folder?: string): Promise<unknown>;
|
|
22
|
+
getGroupRootFiles(groupId: number): Promise<unknown>;
|
|
23
|
+
getGroupFileUrl(groupId: number, fileId: string, busid: number): Promise<unknown>;
|
|
24
|
+
downloadFile(url: string, threadCount?: number, headers?: string[]): Promise<unknown>;
|
|
25
|
+
setOnlineStatus(status: number, extStatus: number): Promise<unknown>;
|
|
26
|
+
setQQAvatar(file: string): Promise<unknown>;
|
|
27
|
+
forwardFriendSingleMsg(userId: number, messageId: number): Promise<unknown>;
|
|
28
|
+
forwardGroupSingleMsg(groupId: number, messageId: number): Promise<unknown>;
|
|
29
|
+
translateEn2Zh(sourceText: string): Promise<unknown>;
|
|
30
|
+
setMsgEmojiLike(messageId: number, emojiId: string): Promise<unknown>;
|
|
31
|
+
sendForwardMsg(messageType: 'private' | 'group', id: number, messages: unknown[]): Promise<unknown>;
|
|
32
|
+
getFriendMsgHistory(userId: number, messageSeq?: number, count?: number): Promise<unknown>;
|
|
33
|
+
getGroupMsgHistory(groupId: number, messageSeq?: number, count?: number): Promise<unknown>;
|
|
34
|
+
setSelfLongnick(longnick: string): Promise<unknown>;
|
|
35
|
+
getGroupInfoEx(groupId: number): Promise<unknown>;
|
|
36
|
+
sendPoke(userId: number, groupId?: number): Promise<unknown>;
|
|
37
|
+
ncGetUserStatus(userId: number): Promise<unknown>;
|
|
38
|
+
getGroupShutList(groupId: number): Promise<unknown>;
|
|
39
|
+
getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string): Promise<unknown>;
|
|
40
|
+
getAiCharacters(groupId: number): Promise<unknown>;
|
|
41
|
+
sendGroupAiRecord(groupId: number, characterId: string, text: string): Promise<unknown>;
|
|
42
|
+
}
|
|
43
|
+
export interface NapcatAgentDeps {
|
|
44
|
+
getEndpoint: (endpointId: string) => NapcatAgentEndpoint;
|
|
45
|
+
}
|
|
46
|
+
export declare function registerNapcatAgentEndpoint(endpointId: string, endpoint: NapcatAgentEndpoint): () => void;
|
|
47
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
48
|
+
export declare function setNapcatAgentDeps(deps: NapcatAgentDeps | null): void;
|
|
49
|
+
export declare function getNapcatAgentDeps(): NapcatAgentDeps;
|
|
50
|
+
/** Convenience for agent/tools (unchanged call sites). */
|
|
51
|
+
export declare function getEndpoint(endpointId: string): NapcatAgentEndpoint;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for napcat.
|
|
3
|
+
* Endpoints register themselves on start; tools look up by config name / endpoint id.
|
|
4
|
+
*/
|
|
5
|
+
const endpoints = new Map();
|
|
6
|
+
let override = null;
|
|
7
|
+
export function registerNapcatAgentEndpoint(endpointId, endpoint) {
|
|
8
|
+
endpoints.set(endpointId, endpoint);
|
|
9
|
+
return () => {
|
|
10
|
+
if (endpoints.get(endpointId) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointId);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
|
+
export function setNapcatAgentDeps(deps) {
|
|
17
|
+
override = deps;
|
|
18
|
+
}
|
|
19
|
+
function lookup(endpointId) {
|
|
20
|
+
const registered = endpoints.get(endpointId);
|
|
21
|
+
if (!registered)
|
|
22
|
+
throw new Error(`Endpoint ${endpointId} not found`);
|
|
23
|
+
return registered;
|
|
24
|
+
}
|
|
25
|
+
export function getNapcatAgentDeps() {
|
|
26
|
+
if (override)
|
|
27
|
+
return override;
|
|
28
|
+
return { getEndpoint: lookup };
|
|
29
|
+
}
|
|
30
|
+
/** Convenience for agent/tools (unchanged call sites). */
|
|
31
|
+
export function getEndpoint(endpointId) {
|
|
32
|
+
return getNapcatAgentDeps().getEndpoint(endpointId);
|
|
33
|
+
}
|
package/lib/napcat-inbound.d.ts
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NapCat 入站消息治理:去重、自发过滤、消息归一化
|
|
3
3
|
*/
|
|
4
|
-
import type { NapCatMessageEvent, MessageSegment } from './
|
|
4
|
+
import type { NapCatMessageEvent, MessageSegment } from './protocol.js';
|
|
5
5
|
export declare class InboundMessageDeduper {
|
|
6
6
|
private readonly seen;
|
|
7
7
|
shouldProcess(messageId: string): boolean;
|
|
8
8
|
clear(): void;
|
|
9
9
|
}
|
|
10
10
|
/** 判断是否为 bot 自身发出的消息 */
|
|
11
|
-
export declare function isSelfMessage(event: NapCatMessageEvent
|
|
11
|
+
export declare function isSelfMessage(event: NapCatMessageEvent | {
|
|
12
|
+
post_type?: string;
|
|
13
|
+
self_id?: number | string;
|
|
14
|
+
user_id?: number | string;
|
|
15
|
+
}): boolean;
|
|
12
16
|
/**
|
|
13
17
|
* 将 message 字段归一化为 MessageSegment[]。
|
|
14
18
|
* NapCat 通常返回数组,但某些配置下可能返回 CQ 字符串。
|
|
15
19
|
*/
|
|
16
20
|
export declare function normalizeMessage(message: MessageSegment[] | string): MessageSegment[];
|
|
17
21
|
/**
|
|
18
|
-
*
|
|
22
|
+
* OneBot11 mentioned 判定:消息段 {type:'at', data:{qq}} 的 qq 等于本机 uin
|
|
23
|
+
* (事件 self_id)时为 true;`qq === 'all'` 不算。兼容 CQ 字符串形式。
|
|
19
24
|
*/
|
|
20
|
-
export declare function
|
|
21
|
-
|
|
25
|
+
export declare function isNapCatBotMentioned(ev: {
|
|
26
|
+
self_id?: number | string;
|
|
27
|
+
message?: MessageSegment[] | string;
|
|
28
|
+
}): boolean;
|
package/lib/napcat-inbound.js
CHANGED
|
@@ -37,19 +37,34 @@ export function normalizeMessage(message) {
|
|
|
37
37
|
}
|
|
38
38
|
return [];
|
|
39
39
|
}
|
|
40
|
+
const CQ_AT_RE = /\[CQ:at,qq=([^\],\s]+)\]/g;
|
|
41
|
+
function atTargetMatchesUin(target, uin) {
|
|
42
|
+
const qq = String(target ?? '').trim();
|
|
43
|
+
return Boolean(qq) && qq !== 'all' && Number(qq) === Number(uin);
|
|
44
|
+
}
|
|
40
45
|
/**
|
|
41
|
-
*
|
|
46
|
+
* OneBot11 mentioned 判定:消息段 {type:'at', data:{qq}} 的 qq 等于本机 uin
|
|
47
|
+
* (事件 self_id)时为 true;`qq === 'all'` 不算。兼容 CQ 字符串形式。
|
|
42
48
|
*/
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
export function isNapCatBotMentioned(ev) {
|
|
50
|
+
if (ev.self_id == null)
|
|
51
|
+
return false;
|
|
52
|
+
const uin = String(ev.self_id).trim();
|
|
53
|
+
if (!uin)
|
|
54
|
+
return false;
|
|
55
|
+
const segments = normalizeMessage(ev.message ?? []);
|
|
56
|
+
for (const seg of segments) {
|
|
57
|
+
if (seg.type === 'at' && atTargetMatchesUin(seg.data?.qq, uin))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const text = segments
|
|
61
|
+
.map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
|
|
62
|
+
.join('');
|
|
63
|
+
if (!text)
|
|
64
|
+
return false;
|
|
65
|
+
for (const match of text.matchAll(CQ_AT_RE)) {
|
|
66
|
+
if (atTargetMatchesUin(match[1], uin))
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
54
70
|
}
|
|
55
|
-
//# sourceMappingURL=napcat-inbound.js.map
|
package/lib/onebot-get-msg.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
export interface QuotedMessagePayload {
|
|
2
|
+
messageId: string;
|
|
3
|
+
sender?: {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
content: Array<{
|
|
8
|
+
type: string;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
}>;
|
|
11
|
+
raw?: string;
|
|
12
|
+
time?: number;
|
|
13
|
+
}
|
|
2
14
|
export declare function parseOneBotGetMsgResponse(messageId: string, data: unknown): QuotedMessagePayload;
|
|
3
|
-
//# sourceMappingURL=onebot-get-msg.d.ts.map
|
package/lib/onebot-get-msg.js
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
/** Transitional legacy endpoint row (`endpoints[]` with `context: napcat`). */
|
|
7
|
+
export interface NapCatLegacyEndpointRow {
|
|
8
|
+
readonly context?: string;
|
|
9
|
+
readonly connection?: 'ws' | 'wss' | 'http';
|
|
10
|
+
readonly name?: string;
|
|
11
|
+
readonly access_token?: string;
|
|
12
|
+
readonly url?: string;
|
|
13
|
+
readonly path?: string;
|
|
14
|
+
readonly http_url?: string;
|
|
15
|
+
readonly post_path?: string;
|
|
16
|
+
readonly reconnect_interval?: number;
|
|
17
|
+
readonly heartbeat_interval?: number;
|
|
18
|
+
readonly poll_interval?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
21
|
+
export interface NapCatAdapterConfig {
|
|
22
|
+
readonly connection?: 'ws' | 'wss' | 'http';
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly access_token?: string;
|
|
25
|
+
readonly url?: string;
|
|
26
|
+
readonly path?: string;
|
|
27
|
+
readonly http_url?: string;
|
|
28
|
+
readonly post_path?: string;
|
|
29
|
+
readonly reconnect_interval?: number;
|
|
30
|
+
readonly heartbeat_interval?: number;
|
|
31
|
+
readonly poll_interval?: number;
|
|
32
|
+
/** Transitional: legacy root `endpoints[]` with `context: napcat`. */
|
|
33
|
+
readonly endpoints?: ReadonlyArray<NapCatLegacyEndpointRow>;
|
|
34
|
+
}
|
|
35
|
+
export interface NapCatConfigBase {
|
|
36
|
+
readonly context: 'napcat';
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly access_token?: string;
|
|
39
|
+
}
|
|
40
|
+
/** 正向 WebSocket:应用连 NapCat WS */
|
|
41
|
+
export interface NapCatWsConfig extends NapCatConfigBase {
|
|
42
|
+
readonly connection: 'ws';
|
|
43
|
+
readonly url: string;
|
|
44
|
+
readonly reconnect_interval: number;
|
|
45
|
+
readonly heartbeat_interval: number;
|
|
46
|
+
}
|
|
47
|
+
/** 反向 WebSocket:httpHostToken WS upgrade 入站/出站 */
|
|
48
|
+
export interface NapCatWssConfig extends NapCatConfigBase {
|
|
49
|
+
readonly connection: 'wss';
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly heartbeat_interval: number;
|
|
52
|
+
}
|
|
53
|
+
/** HTTP API + POST 上报:httpHostToken POST 入站 + http_url/{action} 出站 */
|
|
54
|
+
export interface NapCatHttpConfig extends NapCatConfigBase {
|
|
55
|
+
readonly connection: 'http';
|
|
56
|
+
readonly http_url: string;
|
|
57
|
+
readonly post_path: string;
|
|
58
|
+
readonly poll_interval: number;
|
|
59
|
+
}
|
|
60
|
+
export type ResolvedNapCatConfig = NapCatWsConfig | NapCatWssConfig | NapCatHttpConfig;
|
|
61
|
+
export type NapCatEndpointConfig = ResolvedNapCatConfig;
|
|
62
|
+
export interface NapCatSender {
|
|
63
|
+
readonly role?: string;
|
|
64
|
+
readonly nickname?: string;
|
|
65
|
+
readonly card?: string;
|
|
66
|
+
readonly title?: string;
|
|
67
|
+
readonly user_id?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface MessageSegment {
|
|
70
|
+
type: string;
|
|
71
|
+
data?: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
export interface NapCatEvent {
|
|
74
|
+
post_type: string;
|
|
75
|
+
self_id?: number | string;
|
|
76
|
+
message_type?: string;
|
|
77
|
+
sub_type?: string;
|
|
78
|
+
message_id?: number | string;
|
|
79
|
+
user_id?: number | string;
|
|
80
|
+
group_id?: number | string;
|
|
81
|
+
sender?: NapCatSender;
|
|
82
|
+
message?: MessageSegment[] | string;
|
|
83
|
+
raw_message?: string;
|
|
84
|
+
time?: number;
|
|
85
|
+
notice_type?: string;
|
|
86
|
+
request_type?: string;
|
|
87
|
+
[key: string]: unknown;
|
|
88
|
+
}
|
|
89
|
+
export type NapCatMessageEvent = NapCatEvent & {
|
|
90
|
+
post_type: 'message' | 'message_sent';
|
|
91
|
+
message_id: number | string;
|
|
92
|
+
user_id: number | string;
|
|
93
|
+
};
|
|
94
|
+
export interface NapCatActionRequest {
|
|
95
|
+
action: string;
|
|
96
|
+
params: Record<string, unknown>;
|
|
97
|
+
echo?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface NapCatActionResponse {
|
|
100
|
+
status: string;
|
|
101
|
+
retcode: number;
|
|
102
|
+
data?: unknown;
|
|
103
|
+
echo?: string;
|
|
104
|
+
message?: string;
|
|
105
|
+
wording?: string;
|
|
106
|
+
}
|
|
107
|
+
export interface NapCatWireSegment {
|
|
108
|
+
readonly type: string;
|
|
109
|
+
readonly data?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
export interface ParsedSendTarget {
|
|
112
|
+
readonly message_type: 'private' | 'group';
|
|
113
|
+
readonly id: string;
|
|
114
|
+
}
|
|
115
|
+
export declare function resolveNapCatConfig(config?: NapCatAdapterConfig): ResolvedNapCatConfig;
|
|
116
|
+
export declare function isMessageEvent(ev: NapCatEvent): ev is NapCatMessageEvent;
|
|
117
|
+
export declare function getChannelId(ev: NapCatEvent): string;
|
|
118
|
+
export declare function formatInboundTarget(ev: NapCatEvent): string;
|
|
119
|
+
export declare function parseSendTarget(target: string): ParsedSendTarget;
|
|
120
|
+
export declare function formatInboundContent(ev: NapCatEvent): string;
|
|
121
|
+
/**
|
|
122
|
+
* 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
|
|
123
|
+
* 显示名走 metadata.nickname,见 senderNickname。
|
|
124
|
+
*/
|
|
125
|
+
export declare function senderUserId(ev: NapCatEvent): string;
|
|
126
|
+
/** 显示名(群名片 card 优先于 nickname),没有则 undefined。 */
|
|
127
|
+
export declare function senderNickname(ev: NapCatEvent): string | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* Wire-encode an already-rendered outbound payload into OneBot message segments.
|
|
130
|
+
* Segment canonicalization is intentionally not done here.
|
|
131
|
+
*/
|
|
132
|
+
export declare function formatOutboundSegments(payload: unknown): MessageSegment[];
|
|
133
|
+
export declare function buildSendAction(target: string, message: MessageSegment[]): {
|
|
134
|
+
action: string;
|
|
135
|
+
params: Record<string, unknown>;
|
|
136
|
+
};
|
|
137
|
+
/** Build WS connect URL + headers (access_token via Bearer + query). */
|
|
138
|
+
export declare function buildWsConnectOptions(config: NapCatWsConfig): {
|
|
139
|
+
readonly url: string;
|
|
140
|
+
readonly headers: Record<string, string>;
|
|
141
|
+
readonly safeUrl: string;
|
|
142
|
+
};
|
|
143
|
+
export interface NapCatHttpOptions {
|
|
144
|
+
readonly http_url: string;
|
|
145
|
+
readonly access_token?: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 向 NapCat HTTP API 发送动作请求:POST {http_url}/{action}。
|
|
149
|
+
* 供 connection:http 出站与纯协议测试使用。
|
|
150
|
+
*/
|
|
151
|
+
export declare function callNapCatHttpAction(options: NapCatHttpOptions, action: string, params?: Record<string, unknown>): Promise<NapCatActionResponse>;
|
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
function normalizeConnection(connection) {
|
|
7
|
+
if (connection === 'wss' || connection === 'http')
|
|
8
|
+
return connection;
|
|
9
|
+
return 'ws';
|
|
10
|
+
}
|
|
11
|
+
export function resolveNapCatConfig(config = {}) {
|
|
12
|
+
const entry = config.endpoints?.find((item) => item.context === 'napcat');
|
|
13
|
+
const connection = normalizeConnection(config.connection ?? entry?.connection);
|
|
14
|
+
const name = (typeof config.name === 'string' && config.name)
|
|
15
|
+
|| (typeof entry?.name === 'string' && entry.name)
|
|
16
|
+
|| process.env.NAPCAT_BOT_NAME
|
|
17
|
+
|| 'napcat-bot';
|
|
18
|
+
const access_token = config.access_token ?? entry?.access_token;
|
|
19
|
+
if (connection === 'ws') {
|
|
20
|
+
const url = config.url ?? entry?.url;
|
|
21
|
+
if (!url) {
|
|
22
|
+
throw new TypeError('NapCat connection:ws requires url (plugins.<key>.url or endpoints with context: napcat)');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
context: 'napcat',
|
|
26
|
+
connection: 'ws',
|
|
27
|
+
name,
|
|
28
|
+
access_token,
|
|
29
|
+
url,
|
|
30
|
+
reconnect_interval: config.reconnect_interval ?? entry?.reconnect_interval ?? 5000,
|
|
31
|
+
heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (connection === 'wss') {
|
|
35
|
+
const path = config.path ?? entry?.path;
|
|
36
|
+
if (!path)
|
|
37
|
+
throw new TypeError('NapCat connection:wss requires path');
|
|
38
|
+
return {
|
|
39
|
+
context: 'napcat',
|
|
40
|
+
connection: 'wss',
|
|
41
|
+
name,
|
|
42
|
+
access_token,
|
|
43
|
+
path,
|
|
44
|
+
heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (connection === 'http') {
|
|
48
|
+
const http_url = config.http_url ?? entry?.http_url;
|
|
49
|
+
const post_path = config.post_path ?? entry?.post_path;
|
|
50
|
+
if (!http_url || !post_path) {
|
|
51
|
+
throw new TypeError('NapCat connection:http requires http_url and post_path');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
context: 'napcat',
|
|
55
|
+
connection: 'http',
|
|
56
|
+
name,
|
|
57
|
+
access_token,
|
|
58
|
+
http_url,
|
|
59
|
+
post_path,
|
|
60
|
+
poll_interval: config.poll_interval ?? entry?.poll_interval ?? 30_000,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new TypeError(`Unknown NapCat connection: ${String(connection)}`);
|
|
64
|
+
}
|
|
65
|
+
export function isMessageEvent(ev) {
|
|
66
|
+
return (ev.post_type === 'message' || ev.post_type === 'message_sent')
|
|
67
|
+
&& ev.message_id != null;
|
|
68
|
+
}
|
|
69
|
+
export function getChannelId(ev) {
|
|
70
|
+
if (ev.message_type === 'group' && ev.group_id != null)
|
|
71
|
+
return String(ev.group_id);
|
|
72
|
+
if (ev.group_id != null && ev.message_type !== 'private')
|
|
73
|
+
return String(ev.group_id);
|
|
74
|
+
if (ev.user_id != null)
|
|
75
|
+
return String(ev.user_id);
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
export function formatInboundTarget(ev) {
|
|
79
|
+
const messageType = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
|
|
80
|
+
? 'group'
|
|
81
|
+
: 'private';
|
|
82
|
+
return `${messageType}:${getChannelId(ev)}`;
|
|
83
|
+
}
|
|
84
|
+
export function parseSendTarget(target) {
|
|
85
|
+
const sep = target.indexOf(':');
|
|
86
|
+
if (sep <= 0) {
|
|
87
|
+
return { message_type: 'private', id: target };
|
|
88
|
+
}
|
|
89
|
+
const head = target.slice(0, sep);
|
|
90
|
+
const rest = target.slice(sep + 1);
|
|
91
|
+
if (head === 'group')
|
|
92
|
+
return { message_type: 'group', id: rest };
|
|
93
|
+
if (head === 'private')
|
|
94
|
+
return { message_type: 'private', id: rest };
|
|
95
|
+
return { message_type: 'private', id: target };
|
|
96
|
+
}
|
|
97
|
+
export function formatInboundContent(ev) {
|
|
98
|
+
if (typeof ev.raw_message === 'string' && ev.raw_message)
|
|
99
|
+
return ev.raw_message;
|
|
100
|
+
if (Array.isArray(ev.message)) {
|
|
101
|
+
return ev.message
|
|
102
|
+
.map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
|
|
103
|
+
.join('');
|
|
104
|
+
}
|
|
105
|
+
if (typeof ev.message === 'string')
|
|
106
|
+
return ev.message;
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
|
|
111
|
+
* 显示名走 metadata.nickname,见 senderNickname。
|
|
112
|
+
*/
|
|
113
|
+
export function senderUserId(ev) {
|
|
114
|
+
return ev.user_id != null ? String(ev.user_id) : '';
|
|
115
|
+
}
|
|
116
|
+
/** 显示名(群名片 card 优先于 nickname),没有则 undefined。 */
|
|
117
|
+
export function senderNickname(ev) {
|
|
118
|
+
const name = ev.sender?.card || ev.sender?.nickname;
|
|
119
|
+
return typeof name === 'string' && name ? name : undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Wire-encode an already-rendered outbound payload into OneBot message segments.
|
|
123
|
+
* Segment canonicalization is intentionally not done here.
|
|
124
|
+
*/
|
|
125
|
+
export function formatOutboundSegments(payload) {
|
|
126
|
+
if (typeof payload === 'string') {
|
|
127
|
+
return [{ type: 'text', data: { text: payload } }];
|
|
128
|
+
}
|
|
129
|
+
const items = Array.isArray(payload)
|
|
130
|
+
? payload
|
|
131
|
+
: payload && typeof payload === 'object' && 'type' in payload
|
|
132
|
+
? [payload]
|
|
133
|
+
: [];
|
|
134
|
+
if (items.length === 0) {
|
|
135
|
+
const text = payload == null
|
|
136
|
+
? ''
|
|
137
|
+
: typeof payload === 'object'
|
|
138
|
+
? JSON.stringify(payload)
|
|
139
|
+
: String(payload);
|
|
140
|
+
return [{ type: 'text', data: { text } }];
|
|
141
|
+
}
|
|
142
|
+
const segs = [];
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
if (typeof item === 'string') {
|
|
145
|
+
segs.push({ type: 'text', data: { text: item } });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
segs.push({ type: item.type, data: item.data ?? {} });
|
|
149
|
+
}
|
|
150
|
+
return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
|
|
151
|
+
}
|
|
152
|
+
export function buildSendAction(target, message) {
|
|
153
|
+
const parsed = parseSendTarget(target);
|
|
154
|
+
if (parsed.message_type === 'group') {
|
|
155
|
+
return {
|
|
156
|
+
action: 'send_group_msg',
|
|
157
|
+
params: {
|
|
158
|
+
group_id: Number(parsed.id) || parsed.id,
|
|
159
|
+
message,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
action: 'send_private_msg',
|
|
165
|
+
params: {
|
|
166
|
+
user_id: Number(parsed.id) || parsed.id,
|
|
167
|
+
message,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Build WS connect URL + headers (access_token via Bearer + query). */
|
|
172
|
+
export function buildWsConnectOptions(config) {
|
|
173
|
+
const headers = {};
|
|
174
|
+
let connectUrl = config.url;
|
|
175
|
+
if (config.access_token) {
|
|
176
|
+
headers.Authorization = `Bearer ${config.access_token}`;
|
|
177
|
+
const url = new URL(config.url);
|
|
178
|
+
url.searchParams.set('access_token', config.access_token);
|
|
179
|
+
connectUrl = url.toString();
|
|
180
|
+
}
|
|
181
|
+
const safeUrl = new URL(connectUrl);
|
|
182
|
+
safeUrl.searchParams.delete('access_token');
|
|
183
|
+
return { url: connectUrl, headers, safeUrl: safeUrl.toString() };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 向 NapCat HTTP API 发送动作请求:POST {http_url}/{action}。
|
|
187
|
+
* 供 connection:http 出站与纯协议测试使用。
|
|
188
|
+
*/
|
|
189
|
+
export async function callNapCatHttpAction(options, action, params = {}) {
|
|
190
|
+
const base = options.http_url.replace(/\/$/, '');
|
|
191
|
+
const url = `${base}/${action}`;
|
|
192
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
193
|
+
if (options.access_token) {
|
|
194
|
+
headers.Authorization = `Bearer ${options.access_token}`;
|
|
195
|
+
}
|
|
196
|
+
const res = await fetch(url, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers,
|
|
199
|
+
body: JSON.stringify(params),
|
|
200
|
+
});
|
|
201
|
+
const text = await res.text();
|
|
202
|
+
if (res.status === 401)
|
|
203
|
+
throw new Error(`NapCat HTTP auth failed: ${text}`);
|
|
204
|
+
if (res.status !== 200)
|
|
205
|
+
throw new Error(`NapCat HTTP ${res.status}: ${text}`);
|
|
206
|
+
let data;
|
|
207
|
+
try {
|
|
208
|
+
data = JSON.parse(text);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
throw new Error(`NapCat HTTP invalid response: ${text.slice(0, 200)}`);
|
|
212
|
+
}
|
|
213
|
+
if (data.status !== 'ok' && data.retcode !== 0) {
|
|
214
|
+
throw new Error(`NapCat HTTP action failed [${data.retcode}]: ${data.message || data.wording || 'unknown'}`);
|
|
215
|
+
}
|
|
216
|
+
return data;
|
|
217
|
+
}
|
package/lib/webhook.d.ts
ADDED
package/lib/webhook.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function readRequestBody(request) {
|
|
2
|
+
const chunks = [];
|
|
3
|
+
let size = 0;
|
|
4
|
+
for await (const chunk of request) {
|
|
5
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
6
|
+
size += buffer.length;
|
|
7
|
+
if (size > 1_048_576) {
|
|
8
|
+
request.destroy();
|
|
9
|
+
throw new Error('Request body exceeds 1MB');
|
|
10
|
+
}
|
|
11
|
+
chunks.push(buffer);
|
|
12
|
+
}
|
|
13
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
14
|
+
}
|