@zhin.js/adapter-napcat 1.1.17 → 1.1.19
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 +66 -0
- package/README.md +17 -4
- package/adapters/napcat/index.js +2 -29
- package/adapters/napcat/index.ts +3 -35
- package/agents/napcat/skills/napcat-account/tools/set_profile/index.js +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_profile/index.ts +1 -1
- package/agents/napcat/skills/napcat-group-files-history/tools/upload_group_file/index.js +1 -1
- package/agents/napcat/skills/napcat-group-files-history/tools/upload_group_file/index.ts +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.js +1 -1
- package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.ts +1 -1
- package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.js +1 -1
- package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.ts +1 -1
- package/commands/napcat/endpoint/definition.js +0 -2
- package/commands/napcat/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +23 -0
- package/lib/adapter.js +42 -0
- package/lib/client.d.ts +53 -16
- package/lib/client.js +53 -10
- package/lib/console-spec.d.ts +12 -0
- package/lib/console-spec.js +14 -0
- package/lib/console.d.ts +8 -0
- package/lib/console.js +13 -0
- package/lib/endpoint-management.d.ts +2 -2
- package/lib/endpoint-management.js +13 -13
- package/lib/http-endpoint.d.ts +5 -15
- package/lib/http-endpoint.js +44 -36
- package/lib/identity.d.ts +3 -0
- package/lib/identity.js +32 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -2
- package/lib/onebot-get-msg.d.ts +2 -2
- package/lib/onebot-get-msg.js +2 -2
- package/lib/protocol.d.ts +5 -0
- package/lib/protocol.js +23 -11
- package/lib/ws-endpoint.d.ts +6 -16
- package/lib/ws-endpoint.js +57 -48
- package/lib/ws-transport.d.ts +18 -12
- package/lib/ws-transport.js +28 -72
- package/lib/ws-types.d.ts +0 -5
- package/lib/wss-endpoint.d.ts +7 -15
- package/lib/wss-endpoint.js +56 -47
- package/package.json +25 -17
- package/pages/index/index.js +15 -0
- package/pages/index/index.ts +16 -0
- package/plugin.js +3 -4
- package/src/adapter.ts +45 -0
- package/src/client.ts +91 -19
- package/src/console-spec.ts +19 -0
- package/src/console.ts +18 -0
- package/src/endpoint-management.ts +19 -15
- package/src/http-endpoint.ts +47 -45
- package/src/identity.ts +29 -0
- package/src/index.ts +9 -5
- package/src/onebot-get-msg.ts +4 -4
- package/src/protocol.ts +26 -14
- package/src/ws-endpoint.ts +61 -54
- package/src/ws-transport.ts +40 -101
- package/src/ws-types.ts +0 -6
- package/src/wss-endpoint.ts +60 -54
- package/lib/napcat-runtime-state.d.ts +0 -1
- package/lib/napcat-runtime-state.js +0 -6
- package/src/napcat-runtime-state.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-napcat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
4
4
|
"description": "Zhin.js NapCat adapter for Plugin Runtime (WebSocket client, OneBot11 + NapCat extensions)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"adapters",
|
|
16
16
|
"commands",
|
|
17
|
+
"pages",
|
|
17
18
|
"plugin.js",
|
|
18
19
|
"schema.json",
|
|
19
20
|
"src",
|
|
@@ -44,16 +45,19 @@
|
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"ws": "^8.21.1",
|
|
47
|
-
"@zhin.js/adapter": "1.1.
|
|
48
|
-
"@zhin.js/agent-feature": "1.1.
|
|
49
|
-
"@zhin.js/
|
|
50
|
-
"@zhin.js/
|
|
51
|
-
"@zhin.js/
|
|
52
|
-
"@zhin.js/
|
|
48
|
+
"@zhin.js/adapter": "1.1.26",
|
|
49
|
+
"@zhin.js/agent-feature": "1.1.11",
|
|
50
|
+
"@zhin.js/client": "1.1.22",
|
|
51
|
+
"@zhin.js/console-contract": "1.1.3",
|
|
52
|
+
"@zhin.js/core": "1.1.54",
|
|
53
|
+
"@zhin.js/feature-kit": "1.1.10",
|
|
54
|
+
"@zhin.js/host-http": "1.1.12",
|
|
55
|
+
"@zhin.js/im-contract": "1.1.9",
|
|
53
56
|
"@zhin.js/logger": "1.1.2",
|
|
54
|
-
"@zhin.js/
|
|
55
|
-
"@zhin.js/
|
|
56
|
-
"@zhin.js/
|
|
57
|
+
"@zhin.js/page": "1.1.11",
|
|
58
|
+
"@zhin.js/permission": "1.1.10",
|
|
59
|
+
"@zhin.js/skill": "1.1.11",
|
|
60
|
+
"@zhin.js/tool": "1.1.13"
|
|
57
61
|
},
|
|
58
62
|
"devDependencies": {
|
|
59
63
|
"@types/node": "^26.1.2",
|
|
@@ -61,15 +65,15 @@
|
|
|
61
65
|
"typescript": "^6.0.3",
|
|
62
66
|
"vitest": "^4.1.10",
|
|
63
67
|
"zod": "^4.4.3",
|
|
64
|
-
"@zhin.js/host-http": "1.1.
|
|
65
|
-
"zhin.js": "1.1.
|
|
68
|
+
"@zhin.js/host-http": "1.1.12",
|
|
69
|
+
"zhin.js": "1.1.19"
|
|
66
70
|
},
|
|
67
71
|
"peerDependencies": {
|
|
68
72
|
"zod": "^4.0.0",
|
|
69
|
-
"@zhin.js/adapter": "^1.1.
|
|
70
|
-
"@zhin.js/command": "^1.1.
|
|
71
|
-
"@zhin.js/core": "^1.1.
|
|
72
|
-
"zhin.js": "^1.1.
|
|
73
|
+
"@zhin.js/adapter": "^1.1.26",
|
|
74
|
+
"@zhin.js/command": "^1.1.12",
|
|
75
|
+
"@zhin.js/core": "^1.1.54",
|
|
76
|
+
"zhin.js": "^1.1.19"
|
|
73
77
|
},
|
|
74
78
|
"peerDependenciesMeta": {
|
|
75
79
|
"@zhin.js/command": {
|
|
@@ -103,7 +107,7 @@
|
|
|
103
107
|
"features": [
|
|
104
108
|
{
|
|
105
109
|
"package": "@zhin.js/adapter",
|
|
106
|
-
"api": "^
|
|
110
|
+
"api": "^2.0.0"
|
|
107
111
|
},
|
|
108
112
|
{
|
|
109
113
|
"package": "@zhin.js/command",
|
|
@@ -120,6 +124,10 @@
|
|
|
120
124
|
{
|
|
121
125
|
"package": "@zhin.js/agent-feature",
|
|
122
126
|
"api": "^1.0.0"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"package": "@zhin.js/page",
|
|
130
|
+
"api": "^1.0.0"
|
|
123
131
|
}
|
|
124
132
|
],
|
|
125
133
|
"plugins": []
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { definePage } from '@zhin.js/console-contract';
|
|
3
|
+
import { createAdapterConsolePage } from '@zhin.js/client';
|
|
4
|
+
export const meta = definePage({
|
|
5
|
+
title: 'NapCat',
|
|
6
|
+
titleI18n: { en: 'NapCat' },
|
|
7
|
+
icon: 'Cat',
|
|
8
|
+
order: 69,
|
|
9
|
+
requiredPermissions: ['napcat:read'],
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Convention page: `pages/index/index.ts` → `/napcat`(插件路径)。
|
|
13
|
+
* 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
|
|
14
|
+
*/
|
|
15
|
+
export default createAdapterConsolePage('napcat');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { definePage } from '@zhin.js/console-contract';
|
|
2
|
+
import { createAdapterConsolePage } from '@zhin.js/client';
|
|
3
|
+
|
|
4
|
+
export const meta = definePage({
|
|
5
|
+
title: 'NapCat',
|
|
6
|
+
titleI18n: { en: 'NapCat' },
|
|
7
|
+
icon: 'Cat',
|
|
8
|
+
order: 69,
|
|
9
|
+
requiredPermissions: ['napcat:read'],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convention page: `pages/index/index.ts` → `/napcat`(插件路径)。
|
|
14
|
+
* 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
|
|
15
|
+
*/
|
|
16
|
+
export default createAdapterConsolePage('napcat');
|
package/plugin.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import {
|
|
3
|
-
import { definePlugin } from 'zhin.js';
|
|
2
|
+
import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
|
|
4
3
|
import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
|
|
5
|
-
import {
|
|
4
|
+
import { createNapcatConsoleRpc } from "./lib/console.js";
|
|
6
5
|
export default definePlugin({
|
|
7
6
|
name: 'napcat',
|
|
8
7
|
metadata: {
|
|
9
8
|
displayName: 'NapCat Adapter',
|
|
10
9
|
},
|
|
11
10
|
setup(context) {
|
|
12
|
-
context.resources.provide(
|
|
11
|
+
context.resources.provide(pluginConsoleRpcToken, createNapcatConsoleRpc(context.resources, () => context.config.get()));
|
|
13
12
|
if (context.resources.has(permissionHostToken)) {
|
|
14
13
|
const host = context.resources.use(permissionHostToken);
|
|
15
14
|
return host.registerPlatform('napcat', createSceneRolePlatformChecker());
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Adapter, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
|
|
2
|
+
import { httpHostToken } from '@zhin.js/host-http';
|
|
3
|
+
import { callNapCatHttpAction, resolveNapCatConfig, type NapCatEndpointConfig } from './protocol.js';
|
|
4
|
+
import { NapCatWsEndpoint } from './ws-endpoint.js';
|
|
5
|
+
import { NapCatWssEndpoint } from './wss-endpoint.js';
|
|
6
|
+
import { NapCatHttpEndpoint } from './http-endpoint.js';
|
|
7
|
+
import { probeNapcatIdentity } from './identity.js';
|
|
8
|
+
import type { NapcatClientEventMap } from './client.js';
|
|
9
|
+
|
|
10
|
+
type NapcatEndpoint = NapCatWsEndpoint | NapCatWssEndpoint | NapCatHttpEndpoint;
|
|
11
|
+
export class NapcatAdapter extends Adapter<NapcatEndpoint, NapCatEndpointConfig, NapcatClientEventMap> {
|
|
12
|
+
readonly #context: AdapterContext<NapCatEndpointConfig>;
|
|
13
|
+
constructor(context: AdapterContext<NapCatEndpointConfig>) { super(); this.#context = context; }
|
|
14
|
+
override get configurationIdentityFields(): readonly string[] { return ['id']; }
|
|
15
|
+
override get configurationIdentityProbeFields(): readonly string[] { return ['connection', 'access_token', 'url', 'http_url']; }
|
|
16
|
+
override get configurationUpdateFields(): readonly string[] {
|
|
17
|
+
return ['connection', 'url', 'path', 'http_url', 'post_path', 'access_token', 'reconnect_interval', 'heartbeat_interval', 'poll_interval'];
|
|
18
|
+
}
|
|
19
|
+
protected override validateConfiguration(config: NapCatEndpointConfig): void { resolveNapCatConfig(config); }
|
|
20
|
+
protected override async identifyConfiguration(input: NapCatEndpointConfig, signal: AbortSignal): Promise<string | undefined> {
|
|
21
|
+
const config = resolveNapCatConfig(input);
|
|
22
|
+
if (config.connection === 'ws') return probeNapcatIdentity(config, signal);
|
|
23
|
+
if (config.connection === 'wss') return undefined;
|
|
24
|
+
signal.throwIfAborted();
|
|
25
|
+
const response = await callNapCatHttpAction({ ...config, signal }, 'get_login_info');
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
const data = response.data as { user_id?: unknown } | undefined;
|
|
28
|
+
if (data?.user_id === 0 || data?.user_id === '0' || (typeof data?.user_id === 'string' && !data.user_id.trim())) return undefined;
|
|
29
|
+
if ((typeof data?.user_id !== 'string' && typeof data?.user_id !== 'number') || !String(data.user_id).trim()) {
|
|
30
|
+
throw new Error('NapCat login identity is unavailable');
|
|
31
|
+
}
|
|
32
|
+
return `qq:${data.user_id}`;
|
|
33
|
+
}
|
|
34
|
+
protected createEndpoint(input: NapCatEndpointConfig, execution: EndpointCreationContext): NapcatEndpoint {
|
|
35
|
+
const config = resolveNapCatConfig(input), id = execution.canonicalId, signal = execution.signal;
|
|
36
|
+
switch (config.connection) {
|
|
37
|
+
case 'wss': return new NapCatWssEndpoint({ id, signal, config, http: this.#context.use(httpHostToken) });
|
|
38
|
+
case 'http': return new NapCatHttpEndpoint({ id, signal, config, http: this.#context.use(httpHostToken) });
|
|
39
|
+
case 'ws': return new NapCatWsEndpoint({ id, signal, config });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
declare module '@zhin.js/feature-kit' {
|
|
44
|
+
interface AdapterRegistry { readonly napcat: NapcatAdapter; }
|
|
45
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,16 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { callNapCatHttpAction, readNapCatActionResponse, type NapCatEvent, type ResolvedNapCatConfig } from './protocol.js';
|
|
3
|
+
import { createNapCatRpc, type NapCatRpc } from './ws-transport.js';
|
|
4
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
5
|
+
import type { PendingRpcRequest } from 'zhin.js/adapter';
|
|
6
|
+
import type { NapCatWsSocket } from './ws-types.js';
|
|
3
7
|
|
|
4
8
|
export type NapcatApiCall = (
|
|
5
9
|
action: string,
|
|
6
10
|
params?: Record<string, unknown>,
|
|
7
11
|
) => Promise<unknown>;
|
|
8
12
|
|
|
13
|
+
/** Platform actions use one named payload; wire field names stay inside this Client. */
|
|
14
|
+
export interface NapcatMemberTitleRequest {
|
|
15
|
+
readonly groupId: number;
|
|
16
|
+
readonly userId: number;
|
|
17
|
+
readonly title: string;
|
|
18
|
+
/** Platform duration; omission retains the existing indefinite value (-1). */
|
|
19
|
+
readonly duration?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface NapcatProfileRequest {
|
|
22
|
+
readonly nickname: string;
|
|
23
|
+
readonly company?: string;
|
|
24
|
+
readonly email?: string;
|
|
25
|
+
readonly college?: string;
|
|
26
|
+
readonly personalNote?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface NapcatGroupFileRequest {
|
|
29
|
+
readonly groupId: number;
|
|
30
|
+
readonly file: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly folder?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface NapcatMiniAppRequest {
|
|
35
|
+
readonly type: string;
|
|
36
|
+
readonly title: string;
|
|
37
|
+
readonly desc: string;
|
|
38
|
+
readonly picUrl: string;
|
|
39
|
+
readonly jumpUrl: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NapcatClientOptions {
|
|
43
|
+
readonly config: ResolvedNapCatConfig;
|
|
44
|
+
readonly httpAction?: typeof callNapCatHttpAction;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
|
|
9
48
|
/** Transport-independent NapCat protocol Client produced by every Endpoint mode. */
|
|
10
|
-
export class NapcatClient {
|
|
11
|
-
|
|
49
|
+
export class NapcatClient extends EventEmitter<Record<string, [NapCatEvent]>> {
|
|
50
|
+
protected transportSocket?: NapCatWsSocket;
|
|
51
|
+
protected readonly rpc: NapCatRpc;
|
|
52
|
+
protected readonly protocolConfig: ResolvedNapCatConfig;
|
|
53
|
+
readonly #httpAction: typeof callNapCatHttpAction;
|
|
54
|
+
readonly #executionSignal?: AbortSignal;
|
|
12
55
|
|
|
13
|
-
|
|
56
|
+
constructor(options: NapcatClientOptions) {
|
|
57
|
+
super();
|
|
58
|
+
this.protocolConfig = options.config;
|
|
59
|
+
this.#httpAction = options.httpAction ?? callNapCatHttpAction;
|
|
60
|
+
this.#executionSignal = options.signal;
|
|
61
|
+
this.rpc = createNapCatRpc({ endpointId: options.config.id, signal: options.signal,
|
|
62
|
+
socket: () => this.actionSocket });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ingest(event: NapCatEvent): void {
|
|
66
|
+
this.emit('event', event);
|
|
67
|
+
const name = [event.post_type, event.message_type ?? event.notice_type ?? event.request_type, event.sub_type].filter(Boolean).join('.');
|
|
68
|
+
if (name && name !== 'event') this.emit(name, event);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected get actionSocket(): NapCatWsSocket | undefined { return this.transportSocket; }
|
|
72
|
+
|
|
73
|
+
/** Existing platform methods retain their native action/params calling convention. */
|
|
74
|
+
readonly callApi: NapcatApiCall = (action, params = {}) => this.requestApi({ action, params });
|
|
75
|
+
|
|
76
|
+
/** A semantic operation supplies its caller lifetime to the same native protocol owner. */
|
|
77
|
+
async requestApi(request: PendingRpcRequest): Promise<unknown> {
|
|
78
|
+
const signals = [this.#executionSignal, request.signal].filter((signal): signal is AbortSignal => !!signal);
|
|
79
|
+
const signal = signals.length ? AbortSignal.any(signals) : undefined;
|
|
80
|
+
if (signal?.aborted) throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint operation ended', 'not_sent');
|
|
81
|
+
if (this.protocolConfig.connection === 'http') {
|
|
82
|
+
try {
|
|
83
|
+
const response = await this.#httpAction({ http_url: this.protocolConfig.http_url,
|
|
84
|
+
access_token: this.protocolConfig.access_token, signal }, request.action, request.params ?? {});
|
|
85
|
+
if (signal?.aborted) throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint operation cancelled', 'unknown');
|
|
86
|
+
return readNapCatActionResponse(response).data;
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
if (cause instanceof EndpointDeliveryError) throw cause;
|
|
89
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', cause instanceof Error ? cause.message : String(cause), 'unknown', { cause });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const response = await this.rpc.call({ ...request, signal });
|
|
93
|
+
return readNapCatActionResponse(response).data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async setTitle({ groupId, userId, title, duration = -1 }: NapcatMemberTitleRequest): Promise<boolean> {
|
|
14
97
|
await this.callApi('set_group_special_title', {
|
|
15
98
|
group_id: groupId,
|
|
16
99
|
user_id: userId,
|
|
@@ -24,7 +107,7 @@ export class NapcatClient {
|
|
|
24
107
|
deleteFriend(userId: number) { return this.callApi('delete_friend', { user_id: userId }); }
|
|
25
108
|
markMsgAsRead(messageId: number) { return this.callApi('mark_msg_as_read', { message_id: messageId }); }
|
|
26
109
|
ocrImage(image: string) { return this.callApi('ocr_image', { image }); }
|
|
27
|
-
setQQProfile(nickname
|
|
110
|
+
setQQProfile({ nickname, company, email, college, personalNote }: NapcatProfileRequest) {
|
|
28
111
|
return this.callApi('set_qq_profile', { nickname, company, email, college, personal_note: personalNote });
|
|
29
112
|
}
|
|
30
113
|
setGroupPortrait(groupId: number, file: string) { return this.callApi('set_group_portrait', { group_id: groupId, file }); }
|
|
@@ -35,7 +118,7 @@ export class NapcatClient {
|
|
|
35
118
|
sendGroupNotice(groupId: number, content: string, image?: string) { return this.callApi('_send_group_notice', { group_id: groupId, content, image }); }
|
|
36
119
|
getGroupNotice(groupId: number) { return this.callApi('_get_group_notice', { group_id: groupId }); }
|
|
37
120
|
deleteGroupNotice(groupId: number, noticeId: string) { return this.callApi('_del_group_notice', { group_id: groupId, notice_id: noticeId }); }
|
|
38
|
-
uploadGroupFile(groupId
|
|
121
|
+
uploadGroupFile({ groupId, file, name, folder }: NapcatGroupFileRequest) { return this.callApi('upload_group_file', { group_id: groupId, file, name, folder }); }
|
|
39
122
|
getGroupRootFiles(groupId: number) { return this.callApi('get_group_root_files', { group_id: groupId }); }
|
|
40
123
|
getGroupFileUrl(groupId: number, fileId: string, busid: number) { return this.callApi('get_group_file_url', { group_id: groupId, file_id: fileId, busid }); }
|
|
41
124
|
downloadFile(url: string, threadCount = 1, headers?: string[]) { return this.callApi('download_file', { url, thread_count: threadCount, headers }); }
|
|
@@ -59,21 +142,10 @@ export class NapcatClient {
|
|
|
59
142
|
sendPoke(userId: number, groupId?: number) { return this.callApi('send_poke', { user_id: userId, group_id: groupId }); }
|
|
60
143
|
ncGetUserStatus(userId: number) { return this.callApi('nc_get_user_status', { user_id: userId }); }
|
|
61
144
|
getGroupShutList(groupId: number) { return this.callApi('get_group_shut_list', { group_id: groupId }); }
|
|
62
|
-
getMiniAppArk(type
|
|
145
|
+
getMiniAppArk({ type, title, desc, picUrl, jumpUrl }: NapcatMiniAppRequest) { return this.callApi('get_mini_app_ark', { type, title, desc, picUrl, jumpUrl }); }
|
|
63
146
|
getAiCharacters(groupId: number) { return this.callApi('get_ai_characters', { group_id: groupId }); }
|
|
64
147
|
sendGroupAiRecord(groupId: number, characterId: string, text: string) {
|
|
65
148
|
return this.callApi('send_group_ai_record', { group_id: groupId, character: characterId, text });
|
|
66
149
|
}
|
|
67
150
|
}
|
|
68
151
|
export type NapcatClientEventMap = Record<string, NapCatEvent>;
|
|
69
|
-
|
|
70
|
-
declare module '@zhin.js/feature-kit' {
|
|
71
|
-
interface AdapterClientRegistry {
|
|
72
|
-
readonly napcat: {
|
|
73
|
-
readonly client: NapcatClient;
|
|
74
|
-
readonly events: NapcatClientEventMap;
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export const napcatClient = defineEndpointClient<NapcatClient, NapcatClientEventMap>('napcat');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NapCat Console 页面元数据(浏览器与服务端共享;不要在这里 import 服务端代码)。
|
|
3
|
+
*/
|
|
4
|
+
import type { AdapterConsoleField } from 'zhin.js/adapter';
|
|
5
|
+
|
|
6
|
+
export const napcatConsoleSpec = {
|
|
7
|
+
adapterKey: 'napcat',
|
|
8
|
+
adapterDisplayName: 'NapCat',
|
|
9
|
+
fields: [
|
|
10
|
+
{ key: 'url', description: 'NapCat WebSocket URL(connection: ws 必填)', descriptionI18n: { en: 'NapCat WebSocket URL (required for connection: ws)' } },
|
|
11
|
+
{ key: 'path', description: 'reverse-wss 路径(connection: wss)', descriptionI18n: { en: 'Reverse WebSocket path (connection: wss)' } },
|
|
12
|
+
{ key: 'http_url', description: 'HTTP API base URL(connection: http 出站)', descriptionI18n: { en: 'HTTP API base URL (outbound connection: http)' } },
|
|
13
|
+
{ key: 'post_path', description: 'HTTP POST 事件路径(connection: http 入站)', descriptionI18n: { en: 'HTTP POST event path (inbound connection: http)' } },
|
|
14
|
+
{ key: 'access_token', env: true, description: 'NapCat access token' },
|
|
15
|
+
] as readonly AdapterConsoleField[],
|
|
16
|
+
docsUrl: 'https://zhin.js.org/adapters/napcat',
|
|
17
|
+
readPermission: 'napcat:read',
|
|
18
|
+
writePermission: 'napcat:write',
|
|
19
|
+
} as const;
|
package/src/console.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NapCat Console RPC:运行状态 + endpoint 增删(原 `napcat endpoint` 聊天命令)。
|
|
3
|
+
*
|
|
4
|
+
* 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
|
|
5
|
+
*/
|
|
6
|
+
import { createAdapterConsoleRpc, type AdapterConsoleResources } from 'zhin.js/adapter';
|
|
7
|
+
import type { PluginConsoleRpc } from 'zhin.js';
|
|
8
|
+
import { napcatConsoleSpec } from './console-spec.js';
|
|
9
|
+
|
|
10
|
+
export function createNapcatConsoleRpc(
|
|
11
|
+
_resources: AdapterConsoleResources,
|
|
12
|
+
getConfig: () => unknown,
|
|
13
|
+
): PluginConsoleRpc {
|
|
14
|
+
return createAdapterConsoleRpc({
|
|
15
|
+
...napcatConsoleSpec,
|
|
16
|
+
getConfig,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -7,23 +7,27 @@
|
|
|
7
7
|
* - group → {group_id:number, name: group_name ?? name}
|
|
8
8
|
* - 群成员列表保持 OneBot11 原生形状,仅保证数组
|
|
9
9
|
*/
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
import {
|
|
11
|
+
type PendingRpcRequest,
|
|
12
|
+
type EndpointFriend,
|
|
13
|
+
type EndpointGroup,
|
|
14
|
+
type EndpointManagement,
|
|
15
|
+
type EndpointManagementRequest,
|
|
16
|
+
type EndpointGroupRequest,
|
|
17
|
+
type EndpointGroupNoticeRequest,
|
|
14
18
|
} from 'zhin.js/adapter';
|
|
15
19
|
|
|
16
20
|
/** 管理面只依赖 endpoint 的 callApi(ws/wss/http 三种传输各自实现)。 */
|
|
17
21
|
export interface NapCatManagementCaller {
|
|
18
|
-
|
|
22
|
+
requestApi(request: PendingRpcRequest): Promise<unknown>;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export function createNapCatEndpointManagement(
|
|
22
26
|
endpoint: NapCatManagementCaller,
|
|
23
27
|
): EndpointManagement {
|
|
24
28
|
return Object.freeze<EndpointManagement>({
|
|
25
|
-
async listFriends(): Promise<readonly EndpointFriend[]> {
|
|
26
|
-
const data = await endpoint.
|
|
29
|
+
async listFriends(request: EndpointManagementRequest = {}): Promise<readonly EndpointFriend[]> {
|
|
30
|
+
const data = await endpoint.requestApi({ action: 'get_friend_list', signal: request.signal });
|
|
27
31
|
return toArray(data).map((value) => {
|
|
28
32
|
const friend = asRecord(value);
|
|
29
33
|
return {
|
|
@@ -33,8 +37,8 @@ export function createNapCatEndpointManagement(
|
|
|
33
37
|
};
|
|
34
38
|
});
|
|
35
39
|
},
|
|
36
|
-
async listGroups(): Promise<readonly EndpointGroup[]> {
|
|
37
|
-
const data = await endpoint.
|
|
40
|
+
async listGroups(request: EndpointManagementRequest = {}): Promise<readonly EndpointGroup[]> {
|
|
41
|
+
const data = await endpoint.requestApi({ action: 'get_group_list', signal: request.signal });
|
|
38
42
|
return toArray(data).map((value) => {
|
|
39
43
|
const group = asRecord(value);
|
|
40
44
|
return {
|
|
@@ -43,17 +47,17 @@ export function createNapCatEndpointManagement(
|
|
|
43
47
|
};
|
|
44
48
|
});
|
|
45
49
|
},
|
|
46
|
-
async listGroupMembers(groupId:
|
|
47
|
-
const data = await endpoint.
|
|
50
|
+
async listGroupMembers({ groupId, signal }: EndpointGroupRequest): Promise<readonly unknown[]> {
|
|
51
|
+
const data = await endpoint.requestApi({ action: 'get_group_member_list', params: {
|
|
48
52
|
group_id: toNumberId(groupId, 'group_id'),
|
|
49
|
-
});
|
|
53
|
+
}, signal });
|
|
50
54
|
return toArray(data);
|
|
51
55
|
},
|
|
52
|
-
async sendGroupNotice(groupId
|
|
53
|
-
await endpoint.
|
|
56
|
+
async sendGroupNotice({ groupId, content, signal }: EndpointGroupNoticeRequest): Promise<void> {
|
|
57
|
+
await endpoint.requestApi({ action: '_send_group_notice', params: {
|
|
54
58
|
group_id: toNumberId(groupId, 'group_id'),
|
|
55
59
|
content,
|
|
56
|
-
});
|
|
60
|
+
}, signal });
|
|
57
61
|
},
|
|
58
62
|
});
|
|
59
63
|
}
|
package/src/http-endpoint.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
4
4
|
*/
|
|
5
5
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
6
|
import {
|
|
7
|
-
Endpoint,
|
|
7
|
+
createEndpointSpecification, endpointSpec, type Endpoint, type EndpointSpecification,
|
|
8
8
|
createRecallEndpointControl,
|
|
9
9
|
type EndpointControl,
|
|
10
10
|
type EndpointManagement,
|
|
@@ -38,43 +38,55 @@ import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
|
38
38
|
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
39
39
|
import { NapcatClient } from './client.js';
|
|
40
40
|
import { readRequestBody } from './webhook.js';
|
|
41
|
-
import { NapCatWsEndpoint } from './ws-endpoint.js';
|
|
42
41
|
import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
43
42
|
|
|
44
43
|
export interface NapCatHttpEndpointOptions {
|
|
45
44
|
readonly id: CapabilityId;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
46
|
readonly http: HttpHost;
|
|
47
47
|
readonly config: NapCatHttpConfig;
|
|
48
48
|
readonly callHttpAction?: typeof callNapCatHttpAction;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export class NapCatHttpEndpoint extends
|
|
52
|
-
readonly
|
|
51
|
+
export class NapCatHttpEndpoint extends NapcatClient implements Endpoint {
|
|
52
|
+
readonly [endpointSpec]: EndpointSpecification;
|
|
53
53
|
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
54
54
|
|
|
55
55
|
readonly #options: NapCatHttpEndpointOptions;
|
|
56
56
|
readonly #inboundDeduper = new InboundMessageDeduper();
|
|
57
|
-
readonly management: EndpointManagement = createNapCatEndpointManagement(this
|
|
58
|
-
readonly control: EndpointControl = createRecallEndpointControl((id) => this
|
|
59
|
-
readonly content = createNapCatContentPort(
|
|
60
|
-
readonly #callHttpAction: typeof callNapCatHttpAction;
|
|
57
|
+
readonly #management: EndpointManagement = createNapCatEndpointManagement(this);
|
|
58
|
+
readonly #control: EndpointControl = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
|
|
59
|
+
readonly #content = createNapCatContentPort(request => this.requestApi(request));
|
|
61
60
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
62
61
|
#open = false;
|
|
63
62
|
#started = false;
|
|
64
63
|
#stopped = false;
|
|
65
64
|
|
|
66
65
|
constructor(options: NapCatHttpEndpointOptions) {
|
|
67
|
-
super();
|
|
66
|
+
super({ config: options.config, httpAction: options.callHttpAction ?? callNapCatHttpAction, signal: options.signal });
|
|
68
67
|
this.#logger = getAdapterLogger('napcat', options.config.id);
|
|
69
68
|
this.#options = options;
|
|
70
|
-
|
|
69
|
+
const readTransportState = () => this.#transportState;
|
|
70
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
71
|
+
name: options.config.id,
|
|
72
|
+
start: () => this.#start(), open: () => this.#openEndpoint(),
|
|
73
|
+
close: () => this.#close(), stop: () => this.#stop(),
|
|
74
|
+
send: request => this.#send(request),
|
|
75
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
76
|
+
get transportState() { return readTransportState(); },
|
|
77
|
+
});
|
|
78
|
+
this.on('event', event => {
|
|
79
|
+
void this[endpointSpec].events.run(() => this.#admit(event)).catch((error: unknown) => {
|
|
80
|
+
this.#logger.debug(formatCompact({ op: 'napcat_receive_failed', error: error instanceof Error ? error.message : String(error) }));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
get transportState(): EndpointTransportState {
|
|
85
|
+
get #transportState(): EndpointTransportState {
|
|
74
86
|
return this.#stopped ? 'stopped' : this.#started ? 'open' : 'idle';
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
async start(): Promise<void> {
|
|
89
|
+
async #start(): Promise<void> {
|
|
78
90
|
if (this.#started) return;
|
|
79
91
|
this.#stopped = false;
|
|
80
92
|
this.#started = true;
|
|
@@ -87,15 +99,15 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
87
99
|
}));
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
|
|
102
|
+
#openEndpoint(): void {
|
|
91
103
|
this.#open = true;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
close(): void {
|
|
106
|
+
#close(): void {
|
|
95
107
|
this.#open = false;
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
async stop(): Promise<void> {
|
|
110
|
+
async #stop(): Promise<void> {
|
|
99
111
|
this.#stopped = true;
|
|
100
112
|
this.#open = false;
|
|
101
113
|
for (const release of this.#routeReleases.splice(0)) release();
|
|
@@ -104,18 +116,10 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
104
116
|
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
105
117
|
}
|
|
106
118
|
|
|
107
|
-
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
119
|
+
async #send({ conversation, payload, signal }: EndpointSendRequest): Promise<string> {
|
|
108
120
|
const message = formatOutboundSegments(payload);
|
|
109
121
|
const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
|
|
110
|
-
const
|
|
111
|
-
{
|
|
112
|
-
http_url: this.#options.config.http_url,
|
|
113
|
-
access_token: this.#options.config.access_token,
|
|
114
|
-
},
|
|
115
|
-
action,
|
|
116
|
-
params,
|
|
117
|
-
);
|
|
118
|
-
const data = resp.data as { message_id?: number | string } | undefined;
|
|
122
|
+
const data = await this.requestApi({ action, params, signal }) as { message_id?: number | string } | undefined;
|
|
119
123
|
const rawId = data?.message_id;
|
|
120
124
|
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
121
125
|
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
@@ -130,25 +134,17 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
130
134
|
return messageId;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
async recallMessage(messageId: string): Promise<void> {
|
|
137
|
+
async #recallMessage(messageId: string, signal?: AbortSignal): Promise<void> {
|
|
134
138
|
if (!messageId) return;
|
|
135
|
-
await this.
|
|
139
|
+
await this.requestApi({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal });
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
#callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
139
|
-
return this.#callHttpAction(
|
|
140
|
-
{
|
|
141
|
-
http_url: this.#options.config.http_url,
|
|
142
|
-
access_token: this.#options.config.access_token,
|
|
143
|
-
},
|
|
144
|
-
action,
|
|
145
|
-
params,
|
|
146
|
-
).then((resp) => resp.data);
|
|
147
|
-
}
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
|
|
144
|
+
#admit(ev: NapCatEvent): void {
|
|
145
|
+
const nativeEvent = ev;
|
|
146
|
+
if (!this.#started) return;
|
|
147
|
+
void this[endpointSpec].events.platform(napCatPlatformEventName(ev), ev).catch((error) => {
|
|
152
148
|
this.#logger.warn(formatCompact({
|
|
153
149
|
op: 'napcat_platform_event_failed',
|
|
154
150
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -156,9 +152,9 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
156
152
|
});
|
|
157
153
|
if (!isMessageEvent(ev)) {
|
|
158
154
|
receiveNapCatSideEvent(
|
|
159
|
-
(name, payload) => this.emit(name, payload),
|
|
155
|
+
(name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
160
156
|
this.#options.config.id,
|
|
161
|
-
this
|
|
157
|
+
this,
|
|
162
158
|
ev,
|
|
163
159
|
this.#logger,
|
|
164
160
|
);
|
|
@@ -170,10 +166,12 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
170
166
|
if (Array.isArray(ev.message) || typeof ev.message === 'string') {
|
|
171
167
|
ev = { ...ev, message: normalizeMessage(ev.message) };
|
|
172
168
|
}
|
|
173
|
-
const conversation = napcatInboundConversation(String(this.#options.id), ev);
|
|
169
|
+
const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
174
170
|
const nickname = senderNickname(ev);
|
|
175
171
|
const mentioned = isNapCatBotMentioned(ev);
|
|
176
|
-
void this.
|
|
172
|
+
void this[endpointSpec].events.message({
|
|
173
|
+
event: nativeEvent,
|
|
174
|
+
...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
|
|
177
175
|
conversation,
|
|
178
176
|
message: { conversation, id: msgId },
|
|
179
177
|
content: formatInboundContent(ev),
|
|
@@ -227,7 +225,11 @@ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
|
|
|
227
225
|
response.end(JSON.stringify({ message: 'Invalid JSON' }));
|
|
228
226
|
return;
|
|
229
227
|
}
|
|
230
|
-
if (this.#open)
|
|
228
|
+
if (this.#open) {
|
|
229
|
+
void this[endpointSpec].events.run(() => this.ingest(ev)).catch((error: unknown) => {
|
|
230
|
+
this.#logger.debug(formatCompact({ op: 'napcat_receive_failed', error: error instanceof Error ? error.message : String(error) }));
|
|
231
|
+
});
|
|
232
|
+
}
|
|
231
233
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
232
234
|
response.end(JSON.stringify({ status: 'ok' }));
|
|
233
235
|
} catch (error) {
|