@zhin.js/adapter-onebot11 1.0.98 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +650 -3
- package/README.md +58 -368
- package/adapters/onebot11.js +40 -0
- package/adapters/onebot11.ts +53 -0
- package/agent/PERMITS.md +14 -0
- package/{skills/onebot11/SKILL.md → agent/skills/onebot11.md} +4 -4
- package/agent/tools/set_title.ts +24 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +20 -0
- package/lib/client.js +30 -0
- package/lib/content-port.d.ts +4 -0
- package/lib/content-port.js +63 -0
- package/lib/endpoint-management.d.ts +15 -0
- package/lib/endpoint-management.js +47 -0
- package/lib/index.d.ts +10 -16
- package/lib/index.js +7 -53
- package/lib/onebot11-endpoint-commands.d.ts +1 -0
- package/lib/onebot11-endpoint-commands.js +24 -0
- package/lib/onebot11-runtime-state.d.ts +1 -0
- package/lib/onebot11-runtime-state.js +6 -0
- package/lib/protocol.d.ts +160 -0
- package/lib/protocol.js +316 -0
- package/lib/side-event-dispatch.d.ts +7 -0
- package/lib/side-event-dispatch.js +36 -0
- package/lib/ws-endpoint.d.ts +22 -0
- package/lib/ws-endpoint.js +209 -0
- package/lib/ws-transport.d.ts +13 -0
- package/lib/ws-transport.js +71 -0
- package/lib/ws-types.d.ts +18 -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 +173 -0
- package/package.json +69 -22
- package/plugin.js +18 -0
- package/schema.json +97 -0
- package/src/client.ts +78 -0
- package/src/content-port.ts +66 -0
- package/src/endpoint-management.ts +72 -0
- package/src/index.ts +62 -65
- package/src/onebot11-endpoint-commands.ts +23 -0
- package/src/onebot11-runtime-state.ts +7 -0
- package/src/protocol.ts +459 -0
- package/src/side-event-dispatch.ts +48 -0
- package/src/ws-endpoint.ts +272 -0
- package/src/ws-transport.ts +100 -0
- package/src/ws-types.ts +21 -0
- package/src/wss-auth.ts +17 -0
- package/src/wss-endpoint.ts +228 -0
- package/lib/adapter.d.ts +0 -32
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -92
- package/lib/adapter.js.map +0 -1
- package/lib/bot-ws-client.d.ts +0 -40
- package/lib/bot-ws-client.d.ts.map +0 -1
- package/lib/bot-ws-client.js +0 -397
- package/lib/bot-ws-client.js.map +0 -1
- package/lib/bot-ws-server.d.ts +0 -42
- package/lib/bot-ws-server.d.ts.map +0 -1
- package/lib/bot-ws-server.js +0 -344
- package/lib/bot-ws-server.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/onebot-get-msg.d.ts +0 -3
- package/lib/onebot-get-msg.d.ts.map +0 -1
- package/lib/onebot-get-msg.js +0 -27
- package/lib/onebot-get-msg.js.map +0 -1
- package/lib/types.d.ts +0 -54
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -5
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -104
- package/src/bot-ws-client.ts +0 -421
- package/src/bot-ws-server.ts +0 -382
- package/src/onebot-get-msg.ts +0 -33
- package/src/types.ts +0 -58
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Minimal WS surface used by the endpoint (real `ws` or test mock). */
|
|
2
|
+
export interface OneBot11WsSocket {
|
|
3
|
+
readonly readyState: number;
|
|
4
|
+
send(data: string): void;
|
|
5
|
+
close(code?: number, reason?: string): void;
|
|
6
|
+
ping?(): void;
|
|
7
|
+
on(event: 'open' | 'message' | 'close' | 'error', listener: (...args: unknown[]) => void): void;
|
|
8
|
+
}
|
|
9
|
+
export interface OneBot11WsCreateOptions {
|
|
10
|
+
readonly headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare const WS_OPEN = 1;
|
|
13
|
+
export interface OneBot11PendingAction {
|
|
14
|
+
resolve: (value: OneBot11ActionResponse) => void;
|
|
15
|
+
reject: (err: Error) => void;
|
|
16
|
+
timeout: NodeJS.Timeout;
|
|
17
|
+
}
|
|
18
|
+
import type { OneBot11ActionResponse } from './protocol.js';
|
package/lib/ws-types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WS_OPEN = 1;
|
package/lib/wss-auth.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function verifyOneBotAccessToken(accessToken, request) {
|
|
2
|
+
if (!accessToken)
|
|
3
|
+
return true;
|
|
4
|
+
const auth = request.headers.authorization ?? '';
|
|
5
|
+
if (auth === `Bearer ${accessToken}`)
|
|
6
|
+
return true;
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
9
|
+
if (url.searchParams.get('access_token') === accessToken)
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
/* ignore */
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
|
+
*/
|
|
4
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
5
|
+
import type { HttpHost } from '@zhin.js/host-http';
|
|
6
|
+
import type { CapabilityId } from 'zhin.js';
|
|
7
|
+
import { type OneBot11WssConfig } from './protocol.js';
|
|
8
|
+
import { type Onebot11Client } from './client.js';
|
|
9
|
+
export interface OneBot11WssEndpointOptions {
|
|
10
|
+
readonly id: CapabilityId;
|
|
11
|
+
readonly http: HttpHost;
|
|
12
|
+
readonly config: OneBot11WssConfig;
|
|
13
|
+
}
|
|
14
|
+
export declare class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
15
|
+
#private;
|
|
16
|
+
readonly client: Onebot11Client;
|
|
17
|
+
readonly management: EndpointManagement;
|
|
18
|
+
readonly control: EndpointControl;
|
|
19
|
+
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
20
|
+
constructor(options: OneBot11WssEndpointOptions);
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
stop(): Promise<void>;
|
|
23
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
24
|
+
recallMessage(messageId: string): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
|
+
*/
|
|
4
|
+
import { ClientEndpoint, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
5
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
|
+
import { createOneBot11EndpointManagement } from './endpoint-management.js';
|
|
7
|
+
import { buildSendAction, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
|
|
8
|
+
import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
|
|
9
|
+
import { createOneBot11ContentPort } from './content-port.js';
|
|
10
|
+
import { verifyOneBotAccessToken } from './wss-auth.js';
|
|
11
|
+
import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, } from './ws-transport.js';
|
|
12
|
+
import { callOnebot11Client, createOnebot11EndpointClient, forwardOnebot11ClientEvents } from './client.js';
|
|
13
|
+
export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
14
|
+
client;
|
|
15
|
+
#logger;
|
|
16
|
+
#options;
|
|
17
|
+
management;
|
|
18
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
19
|
+
content;
|
|
20
|
+
#ws;
|
|
21
|
+
#wsRelease;
|
|
22
|
+
#lifecycle;
|
|
23
|
+
#requestId = { value: 0 };
|
|
24
|
+
#pending = new Map();
|
|
25
|
+
constructor(options) {
|
|
26
|
+
super();
|
|
27
|
+
this.#logger = getAdapterLogger('onebot11', options.config.id);
|
|
28
|
+
this.#options = options;
|
|
29
|
+
this.#lifecycle = createEndpointLifecycle({
|
|
30
|
+
name: options.config.id,
|
|
31
|
+
reconnect: false,
|
|
32
|
+
heartbeat: { intervalMs: options.config.heartbeat_interval },
|
|
33
|
+
});
|
|
34
|
+
this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
|
|
35
|
+
const callApi = (action, params) => callOnebot11Client(this.client, action, params);
|
|
36
|
+
this.management = createOneBot11EndpointManagement({ callApi });
|
|
37
|
+
this.content = createOneBot11ContentPort(callApi);
|
|
38
|
+
this.bindClientEvents((receive) => forwardOnebot11ClientEvents(this.client, receive), (name, payload) => {
|
|
39
|
+
if (name === 'event')
|
|
40
|
+
this.#admitRaw(payload);
|
|
41
|
+
}, (_name, error) => this.#warnPlatformEvent(error));
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
if (!this.#options.config.access_token) {
|
|
45
|
+
// wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
|
|
46
|
+
this.#logger.warn(formatCompact({
|
|
47
|
+
endpoint: this.#options.config.id,
|
|
48
|
+
mode: 'wss',
|
|
49
|
+
ok: false,
|
|
50
|
+
error: 'missing access_token',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
await this.#lifecycle.start(async (lifecycleHandle) => {
|
|
54
|
+
const handle = this.#options.http.ws(this.#options.config.path);
|
|
55
|
+
this.#wsRelease = handle.onConnection((connection) => {
|
|
56
|
+
this.#acceptConnection(connection);
|
|
57
|
+
});
|
|
58
|
+
lifecycleHandle.onForceClose(() => {
|
|
59
|
+
this.#wsRelease?.();
|
|
60
|
+
this.#wsRelease = undefined;
|
|
61
|
+
try {
|
|
62
|
+
this.#ws?.close();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* ignore */
|
|
66
|
+
}
|
|
67
|
+
this.#ws = undefined;
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
this.#logger.info(formatCompact({
|
|
71
|
+
op: 'listen',
|
|
72
|
+
endpoint: this.#options.config.id,
|
|
73
|
+
mode: 'wss',
|
|
74
|
+
path: this.#options.config.path,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
async stop() {
|
|
78
|
+
this.close();
|
|
79
|
+
await this.#lifecycle.stop();
|
|
80
|
+
rejectAllPending(this.#pending);
|
|
81
|
+
}
|
|
82
|
+
async send({ conversation, payload }) {
|
|
83
|
+
const message = formatOutboundSegments(payload);
|
|
84
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
85
|
+
const data = await callOnebot11Client(this.client, action, params);
|
|
86
|
+
return data?.message_id != null ? String(data.message_id) : '';
|
|
87
|
+
}
|
|
88
|
+
async recallMessage(messageId) {
|
|
89
|
+
if (!messageId)
|
|
90
|
+
return;
|
|
91
|
+
await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
|
|
92
|
+
}
|
|
93
|
+
#callApi(action, params = {}) {
|
|
94
|
+
return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
95
|
+
}
|
|
96
|
+
#admitRaw(ev) {
|
|
97
|
+
if (!isMessageEvent(ev)) {
|
|
98
|
+
receiveOneBot11SideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot11Client(this.client, action, params) }, ev, this.#logger);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const conversation = onebot11InboundConversation(String(this.#options.id), ev);
|
|
102
|
+
const inbound = formatInboundMetadata(ev, this.#options.config.id);
|
|
103
|
+
void this.emit('message.receive', {
|
|
104
|
+
conversation,
|
|
105
|
+
message: { conversation, id: String(ev.message_id) },
|
|
106
|
+
content: formatInboundContent(ev),
|
|
107
|
+
sender: {
|
|
108
|
+
id: senderUserId(ev),
|
|
109
|
+
name: senderDisplayName(ev) || undefined,
|
|
110
|
+
...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
|
|
111
|
+
},
|
|
112
|
+
endpointId: inbound.endpointId,
|
|
113
|
+
...(inbound.mentioned ? { mentioned: true } : {}),
|
|
114
|
+
...(inbound.replyTo ? { replyTo: inbound.replyTo } : {}),
|
|
115
|
+
metadata: inbound.metadata,
|
|
116
|
+
}).catch((err) => {
|
|
117
|
+
this.#logger.warn(formatCompact({
|
|
118
|
+
op: 'onebot11_gateway_receive_failed',
|
|
119
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
120
|
+
error: err instanceof Error ? err.message : String(err),
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
#warnPlatformEvent(error) {
|
|
125
|
+
this.#logger.warn(formatCompact({
|
|
126
|
+
op: 'onebot11_platform_event_failed',
|
|
127
|
+
error: error instanceof Error ? error.message : String(error),
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
#acceptConnection(connection) {
|
|
131
|
+
if (!verifyOneBotAccessToken(this.#options.config.access_token, connection.request)) {
|
|
132
|
+
connection.socket.close(4003, 'Unauthorized');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const socket = connection.socket;
|
|
136
|
+
if (this.#ws) {
|
|
137
|
+
try {
|
|
138
|
+
this.#ws.close();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* ignore */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.#ws = socket;
|
|
145
|
+
this.#lifecycle.startHeartbeat(() => {
|
|
146
|
+
try {
|
|
147
|
+
this.#ws?.ping?.();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* ignore */
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
socket.on('message', (data) => {
|
|
154
|
+
this.#lifecycle.notifyHeartbeatAck();
|
|
155
|
+
handleOneBot11WsMessage(data, {
|
|
156
|
+
endpointId: this.#options.config.id,
|
|
157
|
+
pending: this.#pending,
|
|
158
|
+
ingest: (ev) => this.client.ingest(ev),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
socket.on('close', () => {
|
|
162
|
+
if (this.#ws === socket) {
|
|
163
|
+
this.#ws = undefined;
|
|
164
|
+
this.#lifecycle.stopHeartbeat();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
this.#logger.debug(formatCompact({
|
|
168
|
+
endpoint: this.#options.config.id,
|
|
169
|
+
mode: 'wss',
|
|
170
|
+
peer: connection.request.socket.remoteAddress,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-onebot11",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Zhin.js adapter for
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Zhin.js OneBot 11 adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
+
"adapters",
|
|
17
|
+
"commands",
|
|
18
|
+
"plugin.js",
|
|
19
|
+
"schema.json",
|
|
16
20
|
"src",
|
|
17
21
|
"lib",
|
|
18
|
-
"
|
|
19
|
-
"dist",
|
|
20
|
-
"skills",
|
|
21
|
-
"plugin.yml",
|
|
22
|
+
"agent",
|
|
22
23
|
"README.md",
|
|
23
24
|
"CHANGELOG.md"
|
|
24
25
|
],
|
|
@@ -32,43 +33,89 @@
|
|
|
32
33
|
"qq",
|
|
33
34
|
"chatbot"
|
|
34
35
|
],
|
|
35
|
-
"author": {
|
|
36
|
-
"name": "lc-cn",
|
|
37
|
-
"email": "admin@liucl.cn",
|
|
38
|
-
"url": "https://github.com/lc-cn"
|
|
39
|
-
},
|
|
40
36
|
"license": "MIT",
|
|
41
37
|
"repository": {
|
|
42
|
-
"url": "git+https://github.com/zhinjs/zhin.git",
|
|
43
38
|
"type": "git",
|
|
39
|
+
"url": "https://github.com/zhinjs/zhin",
|
|
44
40
|
"directory": "plugins/adapters/onebot11"
|
|
45
41
|
},
|
|
46
42
|
"dependencies": {
|
|
47
|
-
"
|
|
43
|
+
"@imhelper/onebot-v11": "1.0.7",
|
|
44
|
+
"imhelper": "1.0.7",
|
|
45
|
+
"ws": "^8.21.1",
|
|
46
|
+
"@zhin.js/adapter": "1.1.12",
|
|
47
|
+
"@zhin.js/core": "1.1.35",
|
|
48
|
+
"@zhin.js/feature-kit": "1.1.0",
|
|
49
|
+
"@zhin.js/host-http": "1.1.0",
|
|
50
|
+
"@zhin.js/im-contract": "1.1.0",
|
|
51
|
+
"@zhin.js/logger": "1.1.0",
|
|
52
|
+
"@zhin.js/permission": "1.1.0"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
|
-
"@types/node": "^
|
|
55
|
+
"@types/node": "^26.1.2",
|
|
51
56
|
"@types/ws": "^8.18.1",
|
|
52
57
|
"typescript": "^6.0.3",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"zhin.js": "1.
|
|
58
|
+
"vitest": "^4.1.10",
|
|
59
|
+
"zod": "^4.4.3",
|
|
60
|
+
"@zhin.js/agent": "1.1.23",
|
|
61
|
+
"@zhin.js/host-http": "1.1.0",
|
|
62
|
+
"zhin.js": "1.1.0"
|
|
56
63
|
},
|
|
57
64
|
"peerDependencies": {
|
|
58
|
-
"
|
|
59
|
-
"zhin.js": "1.
|
|
65
|
+
"zod": "^4.0.0",
|
|
66
|
+
"@zhin.js/adapter": "^1.1.12",
|
|
67
|
+
"@zhin.js/agent": "^1.1.23",
|
|
68
|
+
"@zhin.js/command": "^1.1.0",
|
|
69
|
+
"@zhin.js/core": "^1.1.35",
|
|
70
|
+
"zhin.js": "^1.1.0"
|
|
60
71
|
},
|
|
61
72
|
"peerDependenciesMeta": {
|
|
62
|
-
"@zhin.js/
|
|
73
|
+
"@zhin.js/agent": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@zhin.js/command": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"zhin.js": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"zod": {
|
|
63
83
|
"optional": true
|
|
64
84
|
}
|
|
65
85
|
},
|
|
86
|
+
"author": {
|
|
87
|
+
"name": "lc-cn",
|
|
88
|
+
"email": "admin@liucl.cn",
|
|
89
|
+
"url": "https://github.com/lc-cn"
|
|
90
|
+
},
|
|
66
91
|
"publishConfig": {
|
|
67
92
|
"access": "public",
|
|
68
93
|
"registry": "https://registry.npmjs.org"
|
|
69
94
|
},
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
97
|
+
},
|
|
98
|
+
"zhin": {
|
|
99
|
+
"protocol": 1,
|
|
100
|
+
"type": "plugin",
|
|
101
|
+
"entry": "./plugin.js",
|
|
102
|
+
"engine": "^1.0.0",
|
|
103
|
+
"runtime": "trusted",
|
|
104
|
+
"features": [
|
|
105
|
+
{
|
|
106
|
+
"package": "@zhin.js/adapter",
|
|
107
|
+
"api": "^1.0.0"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"package": "@zhin.js/command",
|
|
111
|
+
"api": "^1.0.0"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"plugins": []
|
|
115
|
+
},
|
|
70
116
|
"scripts": {
|
|
71
|
-
"build": "
|
|
72
|
-
"clean": "rimraf lib"
|
|
117
|
+
"build": "tsc",
|
|
118
|
+
"clean": "rimraf lib",
|
|
119
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/onebot11/tests"
|
|
73
120
|
}
|
|
74
121
|
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { createEndpointRuntimeState } from 'zhin.js/adapter';
|
|
3
|
+
import { definePlugin } from 'zhin.js';
|
|
4
|
+
import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
|
|
5
|
+
import { onebot11RuntimeStateToken } from "./lib/onebot11-runtime-state.js";
|
|
6
|
+
export default definePlugin({
|
|
7
|
+
name: 'onebot11',
|
|
8
|
+
metadata: {
|
|
9
|
+
displayName: 'OneBot 11 Adapter',
|
|
10
|
+
},
|
|
11
|
+
setup(context) {
|
|
12
|
+
context.resources.provide(onebot11RuntimeStateToken, createEndpointRuntimeState());
|
|
13
|
+
if (context.resources.has(permissionHostToken)) {
|
|
14
|
+
const host = context.resources.use(permissionHostToken);
|
|
15
|
+
return host.registerPlatform('onebot11', createSceneRolePlatformChecker());
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
});
|
package/schema.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"connection": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"enum": [
|
|
9
|
+
"ws",
|
|
10
|
+
"wss"
|
|
11
|
+
],
|
|
12
|
+
"default": "ws",
|
|
13
|
+
"description": "connection: ws (forward WS client, default) or wss (reverse WS via httpHostToken)"
|
|
14
|
+
},
|
|
15
|
+
"reconnect_interval": {
|
|
16
|
+
"type": "number",
|
|
17
|
+
"default": 5000
|
|
18
|
+
},
|
|
19
|
+
"heartbeat_interval": {
|
|
20
|
+
"type": "number",
|
|
21
|
+
"default": 30000
|
|
22
|
+
},
|
|
23
|
+
"master": {
|
|
24
|
+
"type": [
|
|
25
|
+
"string",
|
|
26
|
+
"number"
|
|
27
|
+
],
|
|
28
|
+
"description": "框架 master(platform user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
29
|
+
},
|
|
30
|
+
"trusted": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": [
|
|
34
|
+
"string",
|
|
35
|
+
"number"
|
|
36
|
+
],
|
|
37
|
+
"description": "Trusted platform user id"
|
|
38
|
+
},
|
|
39
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
40
|
+
},
|
|
41
|
+
"endpoints": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
44
|
+
"items": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": true,
|
|
47
|
+
"properties": {
|
|
48
|
+
"master": {
|
|
49
|
+
"type": [
|
|
50
|
+
"string",
|
|
51
|
+
"number"
|
|
52
|
+
],
|
|
53
|
+
"description": "本 endpoint 的框架 master(platform user id);覆盖顶层 master"
|
|
54
|
+
},
|
|
55
|
+
"trusted": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": [
|
|
59
|
+
"string",
|
|
60
|
+
"number"
|
|
61
|
+
],
|
|
62
|
+
"description": "Trusted platform user id"
|
|
63
|
+
},
|
|
64
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
65
|
+
},
|
|
66
|
+
"url": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "OneBot implementation WebSocket URL (required for connection: ws)"
|
|
69
|
+
},
|
|
70
|
+
"path": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "WS path for reverse-wss (connection: wss)"
|
|
73
|
+
},
|
|
74
|
+
"access_token": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "OneBot access token"
|
|
77
|
+
},
|
|
78
|
+
"id": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "OneBot11 bot name"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"required": [
|
|
84
|
+
"id"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"commandPrefix": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"default": "",
|
|
91
|
+
"description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"required": [
|
|
95
|
+
"endpoints"
|
|
96
|
+
]
|
|
97
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OneBotV11Client,
|
|
3
|
+
type OneBotV11Event as ImHelperOneBot11Event,
|
|
4
|
+
type OneBotV11Response,
|
|
5
|
+
} from '@imhelper/onebot-v11';
|
|
6
|
+
import { EventFactory, type EventMap, type ImHelperEventMap } from 'imhelper';
|
|
7
|
+
import {
|
|
8
|
+
defineEndpointClient,
|
|
9
|
+
forwardEndpointClientEvents,
|
|
10
|
+
type ClientEventPayloads,
|
|
11
|
+
} from 'zhin.js/adapter';
|
|
12
|
+
import type { ResolvedOneBot11Config } from './protocol.js';
|
|
13
|
+
|
|
14
|
+
export { OneBotV11Client as Onebot11Client } from '@imhelper/onebot-v11';
|
|
15
|
+
|
|
16
|
+
export type Onebot11ApiCall = (
|
|
17
|
+
action: string,
|
|
18
|
+
params?: Record<string, unknown>,
|
|
19
|
+
) => Promise<OneBotV11Response>;
|
|
20
|
+
|
|
21
|
+
type Onebot11ClientEvents = ImHelperEventMap<
|
|
22
|
+
number,
|
|
23
|
+
ImHelperOneBot11Event,
|
|
24
|
+
EventMap<number>
|
|
25
|
+
>;
|
|
26
|
+
export type Onebot11ClientEventMap = ClientEventPayloads<Onebot11ClientEvents>;
|
|
27
|
+
|
|
28
|
+
export function createOnebot11EndpointClient(
|
|
29
|
+
config: ResolvedOneBot11Config,
|
|
30
|
+
request: Onebot11ApiCall,
|
|
31
|
+
): OneBotV11Client {
|
|
32
|
+
return new OneBotV11Client({
|
|
33
|
+
baseUrl: config.connection === 'ws'
|
|
34
|
+
? config.url.replace(/^ws(s?):/, 'http$1:')
|
|
35
|
+
: 'http://localhost',
|
|
36
|
+
selfId: config.id,
|
|
37
|
+
accessToken: config.access_token,
|
|
38
|
+
receiveMode: 'manual',
|
|
39
|
+
call: request,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function callOnebot11Client<T = unknown>(
|
|
44
|
+
client: OneBotV11Client,
|
|
45
|
+
action: string,
|
|
46
|
+
params?: Record<string, unknown>,
|
|
47
|
+
): Promise<T | undefined> {
|
|
48
|
+
const response: OneBotV11Response<T> = await client.call<T>(action, params);
|
|
49
|
+
if (response.status !== 'ok') {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`OneBot 11 API ${action}: retcode=${response.retcode}${response.message ? ` ${response.message}` : ''}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return response.data;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const onebot11ClientEventNames = Object.freeze([
|
|
58
|
+
...EventFactory.getSupportedEventTypes<number>(),
|
|
59
|
+
'event',
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
export function forwardOnebot11ClientEvents(
|
|
63
|
+
client: OneBotV11Client,
|
|
64
|
+
receive: (name: string, payload: unknown) => void,
|
|
65
|
+
): () => void {
|
|
66
|
+
return forwardEndpointClientEvents(client, onebot11ClientEventNames, receive);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
declare module '@zhin.js/feature-kit' {
|
|
70
|
+
interface AdapterClientRegistry {
|
|
71
|
+
readonly onebot11: {
|
|
72
|
+
readonly client: OneBotV11Client;
|
|
73
|
+
readonly events: Onebot11ClientEventMap;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const onebot11Client = defineEndpointClient<OneBotV11Client, Onebot11ClientEventMap>('onebot11');
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
|
|
2
|
+
import { toCanonicalSegments } from '@zhin.js/core';
|
|
3
|
+
import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
|
|
4
|
+
|
|
5
|
+
type CallApi = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
6
|
+
|
|
7
|
+
export function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort {
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
async resolve(reference: ConversationReference, context: EndpointContentResolveContext) {
|
|
10
|
+
try {
|
|
11
|
+
context.signal.throwIfAborted();
|
|
12
|
+
if (reference.kind === 'media') {
|
|
13
|
+
return reference.media.kind === 'file'
|
|
14
|
+
? Object.freeze({ status: 'unsupported' as const, code: 'onebot11_opaque_media' })
|
|
15
|
+
: Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
|
|
16
|
+
}
|
|
17
|
+
if (reference.kind === 'message') {
|
|
18
|
+
const row = await callApi('get_msg', { message_id: reference.message.id }) as Record<string, unknown>;
|
|
19
|
+
context.signal.throwIfAborted();
|
|
20
|
+
const segments = normalizeSegments(row.message ?? row.raw_message);
|
|
21
|
+
const sender = row.sender as Record<string, unknown> | undefined;
|
|
22
|
+
const value: ConversationMessage = Object.freeze({
|
|
23
|
+
ref: reference.message,
|
|
24
|
+
...(sender?.user_id != null ? { actor: Object.freeze({
|
|
25
|
+
id: String(sender.user_id),
|
|
26
|
+
...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
|
|
27
|
+
}) } : {}),
|
|
28
|
+
segments,
|
|
29
|
+
timestamp: toMillis(row.time),
|
|
30
|
+
});
|
|
31
|
+
return Object.freeze({ status: 'resolved' as const, reference, value });
|
|
32
|
+
}
|
|
33
|
+
const raw = await callApi('get_forward_msg', { id: reference.forwardId }) as Record<string, unknown>;
|
|
34
|
+
context.signal.throwIfAborted();
|
|
35
|
+
const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
|
|
36
|
+
const value: readonly ForwardEntry[] = Object.freeze(nodes.map((node) => {
|
|
37
|
+
const row = node as Record<string, unknown>;
|
|
38
|
+
const sender = row.sender as Record<string, unknown> | undefined;
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
...(sender?.user_id != null ? { actor: Object.freeze({
|
|
41
|
+
id: String(sender.user_id),
|
|
42
|
+
...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
|
|
43
|
+
}) } : {}),
|
|
44
|
+
timestamp: toMillis(row.time),
|
|
45
|
+
segments: normalizeSegments(row.content ?? row.message),
|
|
46
|
+
});
|
|
47
|
+
}));
|
|
48
|
+
return Object.freeze({ status: 'resolved' as const, reference, value, truncated: nodes.length >= context.maxEntries });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (context.signal.aborted) return Object.freeze({ status: 'expired' as const, code: 'turn_aborted' });
|
|
51
|
+
return Object.freeze({ status: 'failed' as const, code: 'onebot11_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeSegments(value: unknown) {
|
|
58
|
+
if (Array.isArray(value)) return Object.freeze(toCanonicalSegments(value as never));
|
|
59
|
+
return Object.freeze([{ type: 'text', data: Object.freeze({ text: String(value ?? '') }) }]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toMillis(value: unknown): number {
|
|
63
|
+
const time = Number(value);
|
|
64
|
+
if (!Number.isFinite(time)) return Date.now();
|
|
65
|
+
return time < 10_000_000_000 ? time * 1000 : time;
|
|
66
|
+
}
|