@zhin.js/adapter-onebot12 4.0.0 → 4.0.2
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/adapters/onebot12.js +54 -0
- package/adapters/onebot12.ts +12 -0
- package/commands/endpoint/add/[name:string].js +3 -0
- package/commands/endpoint/add/[name:string].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[name:string].js +3 -0
- package/commands/endpoint/remove/[name:string].ts +3 -0
- package/lib/endpoint-management.d.ts +16 -0
- package/lib/endpoint-management.js +47 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/onebot12-endpoint-commands.d.ts +1 -0
- package/lib/onebot12-endpoint-commands.js +25 -0
- package/lib/onebot12-runtime-state.d.ts +1 -0
- package/lib/onebot12-runtime-state.js +6 -0
- package/lib/protocol.d.ts +21 -1
- package/lib/protocol.js +161 -2
- package/lib/webhook.d.ts +4 -1
- package/lib/webhook.js +30 -8
- package/lib/ws-endpoint.d.ts +4 -1
- package/lib/ws-endpoint.js +65 -65
- package/lib/wss-endpoint.d.ts +4 -1
- package/lib/wss-endpoint.js +24 -2
- package/package.json +18 -12
- package/plugin.js +14 -0
- package/src/endpoint-management.ts +73 -0
- package/src/index.ts +4 -0
- package/src/onebot12-endpoint-commands.ts +24 -0
- package/src/onebot12-runtime-state.ts +7 -0
- package/src/protocol.ts +179 -2
- package/src/webhook.ts +40 -12
- package/src/ws-endpoint.ts +73 -65
- package/src/wss-endpoint.ts +30 -2
- package/plugin.ts +0 -8
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EndpointInstance, type EndpointManagement } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
4
4
|
import { type OneBot12Event, type OneBot12WsConfig } from './protocol.js';
|
|
@@ -11,6 +11,7 @@ export interface OneBot12WsEndpointOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export declare class OneBot12WsEndpoint implements EndpointInstance {
|
|
13
13
|
#private;
|
|
14
|
+
readonly management: EndpointManagement;
|
|
14
15
|
constructor(options: OneBot12WsEndpointOptions);
|
|
15
16
|
start(): Promise<void>;
|
|
16
17
|
open(): void;
|
|
@@ -20,6 +21,8 @@ export declare class OneBot12WsEndpoint implements EndpointInstance {
|
|
|
20
21
|
readonly target: string;
|
|
21
22
|
readonly payload: unknown;
|
|
22
23
|
}): Promise<string>;
|
|
24
|
+
/** Public API for management surface / callers. */
|
|
25
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
23
26
|
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
24
27
|
admit(ev: OneBot12Event): void;
|
|
25
28
|
}
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -2,30 +2,55 @@
|
|
|
2
2
|
* OneBot12 WS client endpoint — outbound connect to OneBot implementation.
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
|
-
import {
|
|
5
|
+
import { clearTimeout } from 'node:timers';
|
|
6
|
+
import { createEndpointLifecycle, } from '@zhin.js/adapter';
|
|
6
7
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
|
-
import {
|
|
8
|
+
import { createOneBot12EndpointManagement } from './endpoint-management.js';
|
|
9
|
+
import { buildSendMessageParams, buildWsConnectOptions, formatInboundContent, formatInboundTarget, formatOutboundSegments, isBotMentioned, isMessageEvent, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
8
10
|
import { WS_OPEN, } from './ws-types.js';
|
|
9
11
|
const logger = getLogger('onebot12');
|
|
10
12
|
export class OneBot12WsEndpoint {
|
|
11
13
|
#options;
|
|
14
|
+
management = createOneBot12EndpointManagement(this);
|
|
15
|
+
#lifecycle;
|
|
12
16
|
#ws;
|
|
13
|
-
#reconnectTimer;
|
|
14
|
-
#heartbeatTimer;
|
|
15
17
|
#requestId = 0;
|
|
16
18
|
#pending = new Map();
|
|
17
19
|
#open = false;
|
|
18
|
-
#started = false;
|
|
19
|
-
#stopping = false;
|
|
20
20
|
constructor(options) {
|
|
21
21
|
this.#options = options;
|
|
22
|
+
const { config } = options;
|
|
23
|
+
this.#lifecycle = createEndpointLifecycle({
|
|
24
|
+
name: config.name,
|
|
25
|
+
reconnect: {
|
|
26
|
+
initialIntervalMs: config.reconnect_interval,
|
|
27
|
+
// 固定间隔(multiplier 1、无抖动),对齐旧 reconnect_interval 语义
|
|
28
|
+
multiplier: 1,
|
|
29
|
+
maxIntervalMs: config.reconnect_interval,
|
|
30
|
+
jitterMs: 0,
|
|
31
|
+
},
|
|
32
|
+
heartbeat: { intervalMs: config.heartbeat_interval },
|
|
33
|
+
});
|
|
22
34
|
}
|
|
23
35
|
async start() {
|
|
24
|
-
if (this.#started)
|
|
36
|
+
if (this.#lifecycle.started)
|
|
25
37
|
return;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
try {
|
|
39
|
+
await this.#lifecycle.start((handle) => this.#connect(handle));
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// start 失败必须清理现场(状态复位由基座保证)
|
|
43
|
+
if (this.#ws) {
|
|
44
|
+
try {
|
|
45
|
+
this.#ws.close();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* ignore */
|
|
49
|
+
}
|
|
50
|
+
this.#ws = undefined;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
29
54
|
}
|
|
30
55
|
open() {
|
|
31
56
|
this.#open = true;
|
|
@@ -35,34 +60,24 @@ export class OneBot12WsEndpoint {
|
|
|
35
60
|
}
|
|
36
61
|
async stop() {
|
|
37
62
|
this.#open = false;
|
|
38
|
-
|
|
39
|
-
this.#
|
|
40
|
-
if (this.#reconnectTimer) {
|
|
41
|
-
clearTimeout(this.#reconnectTimer);
|
|
42
|
-
this.#reconnectTimer = undefined;
|
|
43
|
-
}
|
|
44
|
-
if (this.#heartbeatTimer) {
|
|
45
|
-
clearInterval(this.#heartbeatTimer);
|
|
46
|
-
this.#heartbeatTimer = undefined;
|
|
47
|
-
}
|
|
63
|
+
// 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
|
|
64
|
+
await this.#lifecycle.stop();
|
|
48
65
|
for (const [, pending] of this.#pending) {
|
|
49
66
|
clearTimeout(pending.timeout);
|
|
50
67
|
pending.reject(new Error('连接已关闭'));
|
|
51
68
|
}
|
|
52
69
|
this.#pending.clear();
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
this.#ws.close();
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
/* ignore */
|
|
59
|
-
}
|
|
60
|
-
this.#ws = undefined;
|
|
61
|
-
}
|
|
62
|
-
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
70
|
+
this.#ws = undefined;
|
|
63
71
|
}
|
|
64
72
|
async send({ target, payload }) {
|
|
65
|
-
const
|
|
73
|
+
const materialized = await uploadOneBot12MediaSegments(payload, (action, params) => this.callApi(action, params), (error) => {
|
|
74
|
+
logger.warn(formatCompact({
|
|
75
|
+
op: 'onebot12_upload_failed',
|
|
76
|
+
endpoint: this.#options.config.name,
|
|
77
|
+
error: error instanceof Error ? error.message : String(error),
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
const message = formatOutboundSegments(materialized);
|
|
66
81
|
const params = buildSendMessageParams(target, message);
|
|
67
82
|
const data = await this.#callAction('send_message', params);
|
|
68
83
|
const messageId = data?.message_id ?? '';
|
|
@@ -74,6 +89,10 @@ export class OneBot12WsEndpoint {
|
|
|
74
89
|
}));
|
|
75
90
|
return messageId;
|
|
76
91
|
}
|
|
92
|
+
/** Public API for management surface / callers. */
|
|
93
|
+
callApi(action, params = {}) {
|
|
94
|
+
return this.#callAction(action, params);
|
|
95
|
+
}
|
|
77
96
|
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
78
97
|
admit(ev) {
|
|
79
98
|
if (!this.#open || !isMessageEvent(ev))
|
|
@@ -107,7 +126,7 @@ export class OneBot12WsEndpoint {
|
|
|
107
126
|
}));
|
|
108
127
|
});
|
|
109
128
|
}
|
|
110
|
-
async #connect() {
|
|
129
|
+
async #connect(handle) {
|
|
111
130
|
const { url, headers, safeUrl } = buildWsConnectOptions(this.#options.config);
|
|
112
131
|
const create = this.#options.createWebSocket
|
|
113
132
|
?? ((connectUrl, options) => new WebSocket(connectUrl, { headers: options.headers }));
|
|
@@ -115,6 +134,14 @@ export class OneBot12WsEndpoint {
|
|
|
115
134
|
let settled = false;
|
|
116
135
|
const ws = create(url, { headers });
|
|
117
136
|
this.#ws = ws;
|
|
137
|
+
handle.onForceClose(() => {
|
|
138
|
+
try {
|
|
139
|
+
ws.close();
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* ignore */
|
|
143
|
+
}
|
|
144
|
+
});
|
|
118
145
|
ws.on('open', () => {
|
|
119
146
|
if (settled)
|
|
120
147
|
return;
|
|
@@ -124,7 +151,9 @@ export class OneBot12WsEndpoint {
|
|
|
124
151
|
mode: 'ws',
|
|
125
152
|
url: safeUrl,
|
|
126
153
|
}));
|
|
127
|
-
this.#startHeartbeat()
|
|
154
|
+
this.#lifecycle.startHeartbeat(() => {
|
|
155
|
+
this.#callAction('get_status', {}).catch(() => { });
|
|
156
|
+
});
|
|
128
157
|
resolve();
|
|
129
158
|
});
|
|
130
159
|
ws.on('message', (data) => {
|
|
@@ -137,18 +166,13 @@ export class OneBot12WsEndpoint {
|
|
|
137
166
|
? reason.toString()
|
|
138
167
|
: String(reason ?? '');
|
|
139
168
|
const codeNum = typeof code === 'number' ? code : Number(code ?? 0);
|
|
140
|
-
logger.warn(formatCompact({
|
|
141
|
-
op: 'disconnect',
|
|
142
|
-
endpoint: this.#options.config.name,
|
|
143
|
-
code: codeNum,
|
|
144
|
-
error: reasonStr || 'closed',
|
|
145
|
-
reconnect_ms: this.#options.config.reconnect_interval,
|
|
146
|
-
}));
|
|
147
169
|
if (!settled) {
|
|
148
170
|
settled = true;
|
|
149
171
|
reject(new Error(`OneBot12 WS 关闭: ${codeNum} ${reasonStr}`));
|
|
150
172
|
}
|
|
151
|
-
|
|
173
|
+
// 断开日志与重连武装均由基座负责;仅曾 open 的连接才会武装重连,
|
|
174
|
+
// 初始连接失败由 start() 的拒绝路径复位,不产生僵尸重连。
|
|
175
|
+
handle.notifyClosed(`OneBot12 WS 关闭: ${codeNum} ${reasonStr || 'closed'}`);
|
|
152
176
|
});
|
|
153
177
|
ws.on('error', (err) => {
|
|
154
178
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -213,28 +237,4 @@ export class OneBot12WsEndpoint {
|
|
|
213
237
|
this.#ws.send(JSON.stringify(req));
|
|
214
238
|
});
|
|
215
239
|
}
|
|
216
|
-
#startHeartbeat() {
|
|
217
|
-
if (this.#heartbeatTimer) {
|
|
218
|
-
clearInterval(this.#heartbeatTimer);
|
|
219
|
-
}
|
|
220
|
-
this.#heartbeatTimer = setInterval(() => {
|
|
221
|
-
this.#callAction('get_status', {}).catch(() => { });
|
|
222
|
-
}, this.#options.config.heartbeat_interval);
|
|
223
|
-
}
|
|
224
|
-
#scheduleReconnect() {
|
|
225
|
-
if (this.#stopping || !this.#started || this.#reconnectTimer)
|
|
226
|
-
return;
|
|
227
|
-
const delay = this.#options.config.reconnect_interval;
|
|
228
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
229
|
-
this.#reconnectTimer = undefined;
|
|
230
|
-
void this.#connect().catch((err) => {
|
|
231
|
-
logger.warn(formatCompact({
|
|
232
|
-
op: 'reconnect',
|
|
233
|
-
endpoint: this.#options.config.name,
|
|
234
|
-
ok: false,
|
|
235
|
-
error: err instanceof Error ? err.message : String(err),
|
|
236
|
-
}));
|
|
237
|
-
});
|
|
238
|
-
}, delay);
|
|
239
|
-
}
|
|
240
240
|
}
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointInstance } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -11,6 +11,7 @@ export interface OneBot12WssEndpointOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export declare class OneBot12WssEndpoint implements EndpointInstance {
|
|
13
13
|
#private;
|
|
14
|
+
readonly management: EndpointManagement;
|
|
14
15
|
constructor(options: OneBot12WssEndpointOptions);
|
|
15
16
|
start(): Promise<void>;
|
|
16
17
|
open(): void;
|
|
@@ -20,5 +21,7 @@ export declare class OneBot12WssEndpoint implements EndpointInstance {
|
|
|
20
21
|
readonly target: string;
|
|
21
22
|
readonly payload: unknown;
|
|
22
23
|
}): Promise<string>;
|
|
24
|
+
/** Public API for management surface / callers. */
|
|
25
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
23
26
|
admit(ev: OneBot12Event): void;
|
|
24
27
|
}
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { clearInterval } from 'node:timers';
|
|
5
5
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
6
|
-
import {
|
|
6
|
+
import { createOneBot12EndpointManagement } from './endpoint-management.js';
|
|
7
|
+
import { buildSendMessageParams, formatInboundContent, formatInboundTarget, formatOutboundSegments, isBotMentioned, isMessageEvent, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
7
8
|
import { verifyOneBotAccessToken } from './wss-auth.js';
|
|
8
9
|
import { WS_OPEN } from './ws-types.js';
|
|
9
10
|
const logger = getLogger('onebot12');
|
|
10
11
|
export class OneBot12WssEndpoint {
|
|
11
12
|
#options;
|
|
13
|
+
management = createOneBot12EndpointManagement(this);
|
|
12
14
|
#ws;
|
|
13
15
|
#wsRelease;
|
|
14
16
|
#heartbeatTimer;
|
|
@@ -23,6 +25,15 @@ export class OneBot12WssEndpoint {
|
|
|
23
25
|
if (this.#started)
|
|
24
26
|
return;
|
|
25
27
|
this.#started = true;
|
|
28
|
+
if (!this.#options.config.access_token) {
|
|
29
|
+
// wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
|
|
30
|
+
logger.warn(formatCompact({
|
|
31
|
+
endpoint: this.#options.config.name,
|
|
32
|
+
mode: 'wss',
|
|
33
|
+
ok: false,
|
|
34
|
+
error: 'missing access_token',
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
26
37
|
const handle = this.#options.http.ws(this.#options.config.path);
|
|
27
38
|
this.#wsRelease = handle.onConnection((connection) => {
|
|
28
39
|
this.#acceptConnection(connection);
|
|
@@ -65,11 +76,22 @@ export class OneBot12WssEndpoint {
|
|
|
65
76
|
this.#started = false;
|
|
66
77
|
}
|
|
67
78
|
async send({ target, payload }) {
|
|
68
|
-
const
|
|
79
|
+
const materialized = await uploadOneBot12MediaSegments(payload, (action, params) => this.callApi(action, params), (error) => {
|
|
80
|
+
logger.warn(formatCompact({
|
|
81
|
+
op: 'onebot12_upload_failed',
|
|
82
|
+
endpoint: this.#options.config.name,
|
|
83
|
+
error: error instanceof Error ? error.message : String(error),
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
const message = formatOutboundSegments(materialized);
|
|
69
87
|
const params = buildSendMessageParams(target, message);
|
|
70
88
|
const data = await this.#callAction('send_message', params);
|
|
71
89
|
return data?.message_id ?? '';
|
|
72
90
|
}
|
|
91
|
+
/** Public API for management surface / callers. */
|
|
92
|
+
callApi(action, params = {}) {
|
|
93
|
+
return this.#callAction(action, params);
|
|
94
|
+
}
|
|
73
95
|
admit(ev) {
|
|
74
96
|
if (!this.#open || !isMessageEvent(ev))
|
|
75
97
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-onebot12",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Zhin.js OneBot 12 adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"adapters",
|
|
17
|
-
"
|
|
17
|
+
"commands",
|
|
18
|
+
"plugin.js",
|
|
18
19
|
"schema.json",
|
|
19
20
|
"src",
|
|
20
21
|
"lib",
|
|
@@ -39,24 +40,25 @@
|
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"ws": "^8.21.0",
|
|
42
|
-
"@zhin.js/adapter": "1.1.
|
|
43
|
-
"@zhin.js/
|
|
44
|
-
"@zhin.js/
|
|
43
|
+
"@zhin.js/adapter": "1.1.2",
|
|
44
|
+
"@zhin.js/command": "1.0.4",
|
|
45
|
+
"@zhin.js/core": "1.4.2",
|
|
46
|
+
"@zhin.js/host-http": "1.0.3",
|
|
45
47
|
"@zhin.js/logger": "1.0.75",
|
|
46
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
48
|
+
"@zhin.js/plugin-runtime": "1.1.1"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@types/node": "^26.1.0",
|
|
50
52
|
"@types/ws": "^8.18.1",
|
|
51
53
|
"typescript": "^6.0.3",
|
|
52
54
|
"vitest": "^4.1.10",
|
|
53
|
-
"@zhin.js/host-http": "1.0.
|
|
55
|
+
"@zhin.js/host-http": "1.0.3"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
|
-
"@zhin.js/adapter": "1.1.
|
|
57
|
-
"@zhin.js/core": "1.4.
|
|
58
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
59
|
-
"zhin.js": "5.0.
|
|
58
|
+
"@zhin.js/adapter": "1.1.2",
|
|
59
|
+
"@zhin.js/core": "1.4.2",
|
|
60
|
+
"@zhin.js/plugin-runtime": "1.1.1",
|
|
61
|
+
"zhin.js": "5.0.2"
|
|
60
62
|
},
|
|
61
63
|
"peerDependenciesMeta": {
|
|
62
64
|
"zhin.js": {
|
|
@@ -78,13 +80,17 @@
|
|
|
78
80
|
"zhin": {
|
|
79
81
|
"protocol": 1,
|
|
80
82
|
"type": "plugin",
|
|
81
|
-
"entry": "./plugin.
|
|
83
|
+
"entry": "./plugin.js",
|
|
82
84
|
"engine": "^1.0.0",
|
|
83
85
|
"runtime": "trusted",
|
|
84
86
|
"features": [
|
|
85
87
|
{
|
|
86
88
|
"package": "@zhin.js/adapter",
|
|
87
89
|
"api": "^1.0.0"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"package": "@zhin.js/command",
|
|
93
|
+
"api": "^1.0.0"
|
|
88
94
|
}
|
|
89
95
|
],
|
|
90
96
|
"plugins": []
|
package/plugin.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
|
+
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
+
import { onebot12RuntimeStateToken } from "./lib/onebot12-runtime-state.js";
|
|
5
|
+
export default definePlugin({
|
|
6
|
+
name: 'onebot12',
|
|
7
|
+
metadata: {
|
|
8
|
+
displayName: 'OneBot 12 Adapter',
|
|
9
|
+
},
|
|
10
|
+
setup(context) {
|
|
11
|
+
// 运行中 endpoint 注册表(onebot12 endpoint list 的"运行中"数据源)
|
|
12
|
+
context.resources.provide(onebot12RuntimeStateToken, createEndpointRuntimeState());
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneBot12 endpoint management 语义端口(Console 社交面 RPC 消费,见
|
|
3
|
+
* packages/im/adapter/src/endpoint-management.ts)。
|
|
4
|
+
*
|
|
5
|
+
* 动作名对齐 OB12 规范:get_friend_list / get_group_list / get_group_member_list。
|
|
6
|
+
* 归一化取舍(OB12 id 为字符串,收敛为数字):
|
|
7
|
+
* - friend → {user_id:number, nickname: user_name, remark: user_displayname ?? ''}
|
|
8
|
+
* - group → {group_id:number, name: group_name ?? name}
|
|
9
|
+
* - 群成员列表保持 OB12 原生形状,仅保证数组
|
|
10
|
+
*/
|
|
11
|
+
import type {
|
|
12
|
+
EndpointFriend,
|
|
13
|
+
EndpointGroup,
|
|
14
|
+
EndpointManagement,
|
|
15
|
+
} from '@zhin.js/adapter';
|
|
16
|
+
|
|
17
|
+
/** 管理面只依赖 endpoint 的 callApi(ws/wss/webhook 三种传输各自实现)。 */
|
|
18
|
+
export interface OneBot12ManagementCaller {
|
|
19
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createOneBot12EndpointManagement(
|
|
23
|
+
endpoint: OneBot12ManagementCaller,
|
|
24
|
+
): EndpointManagement {
|
|
25
|
+
return Object.freeze<EndpointManagement>({
|
|
26
|
+
async listFriends(): Promise<readonly EndpointFriend[]> {
|
|
27
|
+
const data = await endpoint.callApi('get_friend_list');
|
|
28
|
+
return toArray(data).map((value) => {
|
|
29
|
+
const friend = asRecord(value);
|
|
30
|
+
return {
|
|
31
|
+
user_id: toNumberId(friend.user_id, 'user_id'),
|
|
32
|
+
nickname: String(friend.user_name ?? friend.nickname ?? ''),
|
|
33
|
+
remark: String(friend.user_displayname ?? friend.remark ?? ''),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
async listGroups(): Promise<readonly EndpointGroup[]> {
|
|
38
|
+
const data = await endpoint.callApi('get_group_list');
|
|
39
|
+
return toArray(data).map((value) => {
|
|
40
|
+
const group = asRecord(value);
|
|
41
|
+
return {
|
|
42
|
+
group_id: toNumberId(group.group_id, 'group_id'),
|
|
43
|
+
name: String(group.group_name ?? group.name ?? ''),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
async listGroupMembers(groupId: string): Promise<readonly unknown[]> {
|
|
48
|
+
const data = await endpoint.callApi('get_group_member_list', {
|
|
49
|
+
group_id: groupId,
|
|
50
|
+
});
|
|
51
|
+
return toArray(data);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
57
|
+
return value !== null && typeof value === 'object'
|
|
58
|
+
? value as Record<string, unknown>
|
|
59
|
+
: {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toArray(value: unknown): readonly unknown[] {
|
|
63
|
+
return Array.isArray(value) ? value : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** OB12 id 是字符串;QQ 系实现均为数字串,统一收敛为数字。 */
|
|
67
|
+
function toNumberId(value: unknown, label: string): number {
|
|
68
|
+
const n = Number(value);
|
|
69
|
+
if (!Number.isFinite(n) || String(value ?? '').trim() === '') {
|
|
70
|
+
throw new TypeError(`onebot12 ${label} 必须是数字: ${String(value)}`);
|
|
71
|
+
}
|
|
72
|
+
return n;
|
|
73
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,13 +8,17 @@ export {
|
|
|
8
8
|
getChannelId,
|
|
9
9
|
isBotMentioned,
|
|
10
10
|
isMessageEvent,
|
|
11
|
+
mediaRefToOneBot12Fields,
|
|
12
|
+
mediaRefToOneBot12UploadParams,
|
|
11
13
|
parseSendTarget,
|
|
12
14
|
resolveOneBot12Config,
|
|
13
15
|
senderNickname,
|
|
14
16
|
senderUserId,
|
|
17
|
+
uploadOneBot12MediaSegments,
|
|
15
18
|
type OneBot12ActionRequest,
|
|
16
19
|
type OneBot12ActionResponse,
|
|
17
20
|
type OneBot12AdapterConfig,
|
|
21
|
+
type OneBot12CallAction,
|
|
18
22
|
type OneBot12ConfigBase,
|
|
19
23
|
type OneBot12EndpointConfig,
|
|
20
24
|
type OneBot12Event,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `onebot12 endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
|
+
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
|
+
*/
|
|
5
|
+
import { createEndpointCommands } from '@zhin.js/adapter';
|
|
6
|
+
import { defineCommand } from '@zhin.js/command';
|
|
7
|
+
import { onebot12RuntimeStateToken } from './onebot12-runtime-state.js';
|
|
8
|
+
|
|
9
|
+
export const onebot12EndpointCommands = createEndpointCommands({
|
|
10
|
+
adapterKey: 'onebot12',
|
|
11
|
+
adapterDisplayName: 'OneBot 12',
|
|
12
|
+
fields: [
|
|
13
|
+
{ key: 'url', description: 'OneBot 实现 WebSocket URL(connection: ws 必填)' },
|
|
14
|
+
{ key: 'path', description: 'HTTP/WS 路径(webhook / reverse-wss)' },
|
|
15
|
+
{ key: 'api_url', description: 'HTTP action 端点(connection: webhook 出站必填)' },
|
|
16
|
+
{ key: 'access_token', env: true, description: 'OneBot access token' },
|
|
17
|
+
],
|
|
18
|
+
running: (use) => use(onebot12RuntimeStateToken).endpoints.values(),
|
|
19
|
+
describeEntry: (entry) => {
|
|
20
|
+
if (entry.url) return `url: ${String(entry.url)}`;
|
|
21
|
+
if (entry.path) return `path: ${String(entry.path)}`;
|
|
22
|
+
return '';
|
|
23
|
+
},
|
|
24
|
+
}, defineCommand);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneBot12 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `onebot12 endpoint` 命令共享(同一 owner generation)。
|
|
4
|
+
*/
|
|
5
|
+
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
|
+
|
|
7
|
+
export const onebot12RuntimeStateToken = defineEndpointRuntimeStateToken('onebot12');
|