@zhin.js/adapter-onebot12 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 +62 -0
- package/README.md +33 -4
- package/adapters/onebot12/index.js +3 -32
- package/adapters/onebot12/index.ts +2 -38
- package/commands/onebot12/endpoint/definition.js +2 -2
- package/commands/onebot12/endpoint/definition.ts +2 -2
- package/lib/adapter.d.ts +21 -0
- package/lib/adapter.js +34 -0
- package/lib/client.d.ts +9 -11
- package/lib/client.js +23 -13
- package/lib/console-spec.d.ts +12 -0
- package/lib/console-spec.js +15 -0
- package/lib/console.d.ts +8 -0
- package/lib/console.js +13 -0
- package/lib/content-port.d.ts +2 -2
- package/lib/content-port.js +1 -1
- package/lib/endpoint-management.d.ts +5 -5
- package/lib/endpoint-management.js +17 -17
- package/lib/identity.d.ts +4 -0
- package/lib/identity.js +53 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -1
- package/lib/protocol.d.ts +32 -4
- package/lib/protocol.js +54 -53
- package/lib/response.d.ts +5 -0
- package/lib/response.js +17 -0
- package/lib/side-event-dispatch.d.ts +8 -1
- package/lib/side-event-dispatch.js +2 -1
- package/lib/webhook.d.ts +5 -11
- package/lib/webhook.js +72 -37
- package/lib/ws-endpoint.d.ts +5 -11
- package/lib/ws-endpoint.js +79 -45
- package/lib/ws-transport.d.ts +18 -10
- package/lib/ws-transport.js +26 -45
- package/lib/ws-types.d.ts +0 -6
- package/lib/wss-endpoint.d.ts +5 -11
- package/lib/wss-endpoint.js +82 -48
- package/package.json +22 -14
- package/pages/index/index.js +15 -0
- package/pages/index/index.ts +16 -0
- package/plugin.js +3 -5
- package/schema.json +15 -3
- package/src/adapter.ts +36 -0
- package/src/client.ts +26 -29
- package/src/console-spec.ts +20 -0
- package/src/console.ts +18 -0
- package/src/content-port.ts +3 -3
- package/src/endpoint-management.ts +25 -24
- package/src/identity.ts +52 -0
- package/src/index.ts +4 -1
- package/src/protocol.ts +71 -62
- package/src/response.ts +19 -0
- package/src/side-event-dispatch.ts +9 -7
- package/src/webhook.ts +70 -55
- package/src/ws-endpoint.ts +75 -63
- package/src/ws-transport.ts +37 -73
- package/src/ws-types.ts +0 -7
- package/src/wss-endpoint.ts +80 -66
- package/lib/onebot12-runtime-state.d.ts +0 -1
- package/lib/onebot12-runtime-state.js +0 -6
- package/src/onebot12-runtime-state.ts +0 -7
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { endpointSpec, type Endpoint, type EndpointSpecification } from 'zhin.js/adapter';
|
|
2
2
|
import type { CapabilityId } from 'zhin.js';
|
|
3
3
|
import { type OneBot12WsConfig } from './protocol.js';
|
|
4
|
-
import {
|
|
4
|
+
import { Onebot12Client } from './client.js';
|
|
5
5
|
import { type OneBot12WsCreateOptions, type OneBot12WsSocket } from './ws-types.js';
|
|
6
6
|
export interface OneBot12WsEndpointOptions {
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
7
8
|
readonly id: CapabilityId;
|
|
8
9
|
readonly config: OneBot12WsConfig;
|
|
9
10
|
readonly createWebSocket?: (url: string, options: OneBot12WsCreateOptions) => OneBot12WsSocket;
|
|
10
11
|
}
|
|
11
|
-
export declare class OneBot12WsEndpoint extends
|
|
12
|
+
export declare class OneBot12WsEndpoint extends Onebot12Client implements Endpoint {
|
|
12
13
|
#private;
|
|
13
|
-
readonly
|
|
14
|
-
readonly management: EndpointManagement;
|
|
15
|
-
readonly control: EndpointControl;
|
|
16
|
-
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
14
|
+
readonly [endpointSpec]: EndpointSpecification;
|
|
17
15
|
constructor(options: OneBot12WsEndpointOptions);
|
|
18
|
-
start(): Promise<void>;
|
|
19
|
-
stop(): Promise<void>;
|
|
20
|
-
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
21
|
-
recallMessage(messageId: string): Promise<void>;
|
|
22
16
|
}
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -2,37 +2,56 @@
|
|
|
2
2
|
* OneBot12 WS client endpoint — outbound connect to OneBot implementation.
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
|
-
import {
|
|
5
|
+
import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
6
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createOneBot12EndpointManagement } from './endpoint-management.js';
|
|
8
|
-
import { buildSendMessageParams, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
8
|
+
import { buildSendMessageParams, matchesOneBot12Self, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
9
9
|
import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
|
|
10
10
|
import { createOneBot12ContentPort } from './content-port.js';
|
|
11
|
-
import { callOnebot12Client,
|
|
12
|
-
import {
|
|
13
|
-
export class OneBot12WsEndpoint extends
|
|
14
|
-
|
|
11
|
+
import { callOnebot12Client, readOnebot12Response, onebot12ClientOptions, forwardOnebot12ClientEvents, Onebot12Client } from './client.js';
|
|
12
|
+
import { createOneBot12Rpc } from './ws-transport.js';
|
|
13
|
+
export class OneBot12WsEndpoint extends Onebot12Client {
|
|
14
|
+
[endpointSpec];
|
|
15
|
+
#open = false;
|
|
15
16
|
#logger;
|
|
16
17
|
#options;
|
|
17
|
-
management;
|
|
18
|
-
control = createRecallEndpointControl((id) => this
|
|
19
|
-
content;
|
|
18
|
+
#management;
|
|
19
|
+
#control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
|
|
20
|
+
#content;
|
|
21
|
+
#releaseClientEvents;
|
|
20
22
|
#lifecycle;
|
|
21
23
|
#ws;
|
|
22
|
-
#
|
|
23
|
-
#pending = new Map();
|
|
24
|
+
#rpc;
|
|
24
25
|
constructor(options) {
|
|
25
|
-
|
|
26
|
+
const rpc = createOneBot12Rpc({ endpointId: options.config.id, signal: options.signal,
|
|
27
|
+
self: { platform: options.config.platform, user_id: options.config.userId },
|
|
28
|
+
socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
|
|
29
|
+
});
|
|
30
|
+
super(onebot12ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
|
|
31
|
+
this.#rpc = rpc;
|
|
26
32
|
this.#logger = getAdapterLogger('onebot12', options.config.id);
|
|
27
33
|
this.#options = options;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this
|
|
31
|
-
|
|
32
|
-
this
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const callApi = (request) => this.#rpc.call(request).then(readOnebot12Response);
|
|
35
|
+
this.#management = createOneBot12EndpointManagement({ callApi });
|
|
36
|
+
this.#content = createOneBot12ContentPort(callApi);
|
|
37
|
+
const readTransportState = () => this.#lifecycle.state;
|
|
38
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
39
|
+
name: options.config.id,
|
|
40
|
+
start: () => this.#start(), open: () => { this.#open = true; },
|
|
41
|
+
close: () => { this.#open = false; }, stop: () => this.#stop(),
|
|
42
|
+
send: request => this.#send(request),
|
|
43
|
+
get transportState() { return readTransportState(); },
|
|
44
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
45
|
+
});
|
|
46
|
+
this.#releaseClientEvents = forwardOnebot12ClientEvents(this, (name, payload) => {
|
|
47
|
+
void this[endpointSpec].events.run(() => {
|
|
48
|
+
if (!this.#open && !this.#lifecycle.started)
|
|
49
|
+
return;
|
|
50
|
+
void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
|
|
51
|
+
if (name === 'event')
|
|
52
|
+
this.#admitRaw(payload);
|
|
53
|
+
}).catch(error => this.#warnPlatformEvent(error));
|
|
54
|
+
});
|
|
36
55
|
const { config } = options;
|
|
37
56
|
this.#lifecycle = createEndpointLifecycle({
|
|
38
57
|
name: config.id,
|
|
@@ -46,7 +65,7 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
46
65
|
heartbeat: { intervalMs: config.heartbeat_interval },
|
|
47
66
|
});
|
|
48
67
|
}
|
|
49
|
-
async start() {
|
|
68
|
+
async #start() {
|
|
50
69
|
if (this.#lifecycle.started)
|
|
51
70
|
return;
|
|
52
71
|
try {
|
|
@@ -66,24 +85,30 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
66
85
|
throw err;
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
|
-
async stop() {
|
|
70
|
-
this
|
|
88
|
+
async #stop() {
|
|
89
|
+
this.#open = false;
|
|
71
90
|
// 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
try {
|
|
92
|
+
await this.#lifecycle.stop();
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.#rpc.dispose();
|
|
96
|
+
this.#releaseClientEvents();
|
|
97
|
+
this.#ws = undefined;
|
|
98
|
+
}
|
|
75
99
|
}
|
|
76
|
-
async send({ conversation, payload }) {
|
|
77
|
-
const materialized = await uploadOneBot12MediaSegments(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
async #send({ conversation, payload, signal }) {
|
|
101
|
+
const materialized = await uploadOneBot12MediaSegments({
|
|
102
|
+
payload, signal,
|
|
103
|
+
callAction: (action, params) => this.#rpc.call({ action, params, signal }).then(readOnebot12Response),
|
|
104
|
+
onUploadFailed: error => {
|
|
105
|
+
this.#logger.warn(formatCompact({ op: 'onebot12_upload_failed', endpoint: this.#options.config.id,
|
|
106
|
+
error: error instanceof Error ? error.message : String(error) }));
|
|
107
|
+
},
|
|
83
108
|
});
|
|
84
109
|
const message = formatOutboundSegments(materialized);
|
|
85
110
|
const params = buildSendMessageParams(conversation, message);
|
|
86
|
-
const data = await
|
|
111
|
+
const data = readOnebot12Response(await this.#rpc.call({ action: 'send_message', params, signal }));
|
|
87
112
|
const messageId = data?.message_id ?? '';
|
|
88
113
|
this.#logger.debug(formatCompact({
|
|
89
114
|
op: 'onebot12_send',
|
|
@@ -94,21 +119,31 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
94
119
|
}));
|
|
95
120
|
return messageId;
|
|
96
121
|
}
|
|
97
|
-
async recallMessage(messageId) {
|
|
122
|
+
async #recallMessage(messageId, signal) {
|
|
98
123
|
if (!messageId)
|
|
99
124
|
return;
|
|
100
|
-
await
|
|
125
|
+
await this.#rpc.call({ action: 'delete_message', params: { message_id: messageId }, signal }).then(readOnebot12Response);
|
|
101
126
|
}
|
|
102
127
|
#admitRaw(ev) {
|
|
128
|
+
if (!matchesOneBot12Self(this.#options.config, ev))
|
|
129
|
+
return;
|
|
130
|
+
this[endpointSpec].reportSelfUserId(ev.self.user_id);
|
|
103
131
|
if (!isMessageEvent(ev)) {
|
|
104
|
-
receiveOneBot12SideEvent(
|
|
132
|
+
receiveOneBot12SideEvent(ev, {
|
|
133
|
+
emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
134
|
+
endpointKey: this.#options.config.id,
|
|
135
|
+
caller: { callApi: (action, params) => callOnebot12Client({ client: this, action, params }) },
|
|
136
|
+
logger: this.#logger,
|
|
137
|
+
});
|
|
105
138
|
return;
|
|
106
139
|
}
|
|
107
|
-
const conversation = onebot12InboundConversation(String(this.#options.id), ev);
|
|
140
|
+
const conversation = { ...onebot12InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
108
141
|
const content = formatInboundContent(ev);
|
|
109
142
|
const nickname = senderNickname(ev);
|
|
110
143
|
const mentioned = isBotMentioned(ev);
|
|
111
|
-
void this.
|
|
144
|
+
void this[endpointSpec].events.message({
|
|
145
|
+
event: ev,
|
|
146
|
+
...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
|
|
112
147
|
conversation,
|
|
113
148
|
message: { conversation, id: ev.message_id },
|
|
114
149
|
content,
|
|
@@ -173,10 +208,9 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
173
208
|
if (this.#ws !== ws)
|
|
174
209
|
return;
|
|
175
210
|
this.#lifecycle.notifyHeartbeatAck();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
ingest: (event) => this.client.ingest(event),
|
|
211
|
+
this.#rpc.receive(data, event => {
|
|
212
|
+
if (matchesOneBot12Self(this.#options.config, event))
|
|
213
|
+
this.ingest(event);
|
|
180
214
|
});
|
|
181
215
|
});
|
|
182
216
|
ws.on('close', (code, reason) => {
|
|
@@ -192,7 +226,7 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
192
226
|
}
|
|
193
227
|
if (this.#ws === ws) {
|
|
194
228
|
this.#ws = undefined;
|
|
195
|
-
|
|
229
|
+
this.#rpc.disconnect();
|
|
196
230
|
}
|
|
197
231
|
// 断开日志与重连武装均由基座负责;仅曾 open 的连接才会武装重连,
|
|
198
232
|
// 初始连接失败由 start() 的拒绝路径复位,不产生僵尸重连。
|
|
@@ -214,6 +248,6 @@ export class OneBot12WsEndpoint extends ClientEndpoint {
|
|
|
214
248
|
});
|
|
215
249
|
}
|
|
216
250
|
#callAction(action, params) {
|
|
217
|
-
return
|
|
251
|
+
return this.#rpc.call({ action, params });
|
|
218
252
|
}
|
|
219
253
|
}
|
package/lib/ws-transport.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { type PendingRpc, type PendingRpcRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { OneBot12ActionResponse, OneBot12Event, OneBot12Self } from './protocol.js';
|
|
3
|
+
import { type OneBot12WsSocket } from './ws-types.js';
|
|
4
|
+
export interface OneBot12RpcOptions {
|
|
4
5
|
readonly endpointId: string;
|
|
5
|
-
readonly
|
|
6
|
-
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
/** Reads the lifecycle's selected socket; RPC never opens or reconnects it. */
|
|
8
|
+
readonly socket: () => OneBot12WsSocket | undefined;
|
|
9
|
+
readonly self: OneBot12Self;
|
|
7
10
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export interface OneBot12Rpc {
|
|
12
|
+
call(request: PendingRpcRequest): Promise<OneBot12ActionResponse>;
|
|
13
|
+
receive(data: unknown, ingest: (event: OneBot12Event) => void): void;
|
|
14
|
+
disconnect: PendingRpc<OneBot12ActionResponse>['disconnect'];
|
|
15
|
+
dispose(): void;
|
|
16
|
+
readonly pendingCount: number;
|
|
17
|
+
}
|
|
18
|
+
/** A Client's one request owner serves native SDK actions and framework operations. */
|
|
19
|
+
export declare function createOneBot12Rpc(options: OneBot12RpcOptions): OneBot12Rpc;
|
|
20
|
+
export declare function decodeWsPayload(data: unknown): string;
|
package/lib/ws-transport.js
CHANGED
|
@@ -1,53 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPendingRpc } from 'zhin.js/adapter';
|
|
2
2
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
3
|
-
import { WS_OPEN
|
|
3
|
+
import { WS_OPEN } from './ws-types.js';
|
|
4
4
|
const logger = getLogger('onebot12');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
/** A Client's one request owner serves native SDK actions and framework operations. */
|
|
6
|
+
export function createOneBot12Rpc(options) {
|
|
7
|
+
const pending = createPendingRpc({
|
|
8
|
+
signal: options.signal, requestPrefix: 'ob12_', timeoutMs: 30_000,
|
|
9
|
+
transport: () => { const socket = options.socket(); return socket?.readyState === WS_OPEN ? socket : undefined; },
|
|
10
|
+
serialize: request => JSON.stringify({ action: request.action, params: request.params ?? {}, echo: request.id, self: options.self }),
|
|
11
|
+
});
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
call: pending.call, disconnect: pending.disconnect, dispose: pending.dispose,
|
|
14
|
+
get pendingCount() { return pending.pendingCount; },
|
|
15
|
+
receive(data, ingest) {
|
|
16
|
+
try {
|
|
17
|
+
const message = JSON.parse(decodeWsPayload(data));
|
|
18
|
+
if ('echo' in message && typeof message.echo === 'string') {
|
|
19
|
+
pending.accept({ id: message.echo, value: message });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
ingest(message);
|
|
15
23
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
logger.warn(formatCompact({
|
|
22
|
-
op: 'onebot12_parse_failed',
|
|
23
|
-
endpoint: options.endpointId,
|
|
24
|
-
error: error instanceof Error ? error.message : String(error),
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export function callOneBot12WsAction(ws, pending, requestId, action, params) {
|
|
29
|
-
if (!ws || ws.readyState !== WS_OPEN) {
|
|
30
|
-
return Promise.reject(new Error('WebSocket 未连接'));
|
|
31
|
-
}
|
|
32
|
-
const echo = `ob12_${++requestId.value}`;
|
|
33
|
-
const request = { action, params, echo };
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
const timeout = setTimeout(() => {
|
|
36
|
-
pending.delete(echo);
|
|
37
|
-
reject(new Error(`OneBot12 动作超时: ${action}`));
|
|
38
|
-
}, 30_000);
|
|
39
|
-
pending.set(echo, { resolve, reject, timeout });
|
|
40
|
-
ws.send(JSON.stringify(request));
|
|
24
|
+
catch (error) {
|
|
25
|
+
logger.warn(formatCompact({ op: 'onebot12_parse_failed', endpoint: options.endpointId,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error) }));
|
|
27
|
+
}
|
|
28
|
+
},
|
|
41
29
|
});
|
|
42
30
|
}
|
|
43
|
-
export function
|
|
44
|
-
for (const [, entry] of pending) {
|
|
45
|
-
clearTimeout(entry.timeout);
|
|
46
|
-
entry.reject(new Error(message));
|
|
47
|
-
}
|
|
48
|
-
pending.clear();
|
|
49
|
-
}
|
|
50
|
-
function decodeOneBot12WsPayload(data) {
|
|
31
|
+
export function decodeWsPayload(data) {
|
|
51
32
|
if (typeof data === 'string')
|
|
52
33
|
return data;
|
|
53
34
|
if (Buffer.isBuffer(data))
|
package/lib/ws-types.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { OneBot12ActionResponse } from './protocol.js';
|
|
2
1
|
/** Minimal WS surface used by the endpoint (real `ws` or test mock). */
|
|
3
2
|
export interface OneBot12WsSocket {
|
|
4
3
|
readonly readyState: number;
|
|
@@ -10,8 +9,3 @@ export interface OneBot12WsCreateOptions {
|
|
|
10
9
|
readonly headers?: Record<string, string>;
|
|
11
10
|
}
|
|
12
11
|
export declare const WS_OPEN = 1;
|
|
13
|
-
export interface OneBot12PendingAction {
|
|
14
|
-
resolve: (value: OneBot12ActionResponse) => void;
|
|
15
|
-
reject: (err: Error) => void;
|
|
16
|
-
timeout: NodeJS.Timeout;
|
|
17
|
-
}
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { endpointSpec, type Endpoint, type EndpointSpecification } from 'zhin.js/adapter';
|
|
5
5
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
6
6
|
import type { CapabilityId } from 'zhin.js';
|
|
7
7
|
import { type OneBot12WssConfig } from './protocol.js';
|
|
8
|
-
import {
|
|
8
|
+
import { Onebot12Client } from './client.js';
|
|
9
9
|
export interface OneBot12WssEndpointOptions {
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
10
11
|
readonly id: CapabilityId;
|
|
11
12
|
readonly http: HttpHost;
|
|
12
13
|
readonly config: OneBot12WssConfig;
|
|
13
14
|
}
|
|
14
|
-
export declare class OneBot12WssEndpoint extends
|
|
15
|
+
export declare class OneBot12WssEndpoint extends Onebot12Client implements Endpoint {
|
|
15
16
|
#private;
|
|
16
|
-
readonly
|
|
17
|
-
readonly management: EndpointManagement;
|
|
18
|
-
readonly control: EndpointControl;
|
|
19
|
-
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
17
|
+
readonly [endpointSpec]: EndpointSpecification;
|
|
20
18
|
constructor(options: OneBot12WssEndpointOptions);
|
|
21
|
-
start(): Promise<void>;
|
|
22
|
-
stop(): Promise<void>;
|
|
23
|
-
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
24
|
-
recallMessage(messageId: string): Promise<void>;
|
|
25
19
|
}
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -1,47 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { createEndpointSpecification, endpointSpec, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
5
5
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
6
|
import { createOneBot12EndpointManagement } from './endpoint-management.js';
|
|
7
|
-
import { buildSendMessageParams, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
7
|
+
import { buildSendMessageParams, matchesOneBot12Self, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
|
|
8
8
|
import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
|
|
9
9
|
import { createOneBot12ContentPort } from './content-port.js';
|
|
10
|
-
import { callOnebot12Client,
|
|
10
|
+
import { callOnebot12Client, readOnebot12Response, onebot12ClientOptions, forwardOnebot12ClientEvents, Onebot12Client } from './client.js';
|
|
11
11
|
import { verifyOneBotAccessToken } from './wss-auth.js';
|
|
12
|
-
import {
|
|
13
|
-
export class OneBot12WssEndpoint extends
|
|
14
|
-
|
|
12
|
+
import { createOneBot12Rpc } from './ws-transport.js';
|
|
13
|
+
export class OneBot12WssEndpoint extends Onebot12Client {
|
|
14
|
+
[endpointSpec];
|
|
15
|
+
#open = false;
|
|
15
16
|
#logger;
|
|
16
17
|
#options;
|
|
17
|
-
management;
|
|
18
|
-
control = createRecallEndpointControl((id) => this
|
|
19
|
-
content;
|
|
18
|
+
#management;
|
|
19
|
+
#control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
|
|
20
|
+
#content;
|
|
21
|
+
#releaseClientEvents;
|
|
20
22
|
#ws;
|
|
21
23
|
#wsRelease;
|
|
22
24
|
#connectionLifecycle;
|
|
23
25
|
#connectionTask = Promise.resolve();
|
|
24
|
-
#
|
|
25
|
-
#pending = new Map();
|
|
26
|
+
#rpc;
|
|
26
27
|
#started = false;
|
|
27
28
|
constructor(options) {
|
|
28
|
-
|
|
29
|
+
const rpc = createOneBot12Rpc({ endpointId: options.config.id, signal: options.signal,
|
|
30
|
+
self: { platform: options.config.platform, user_id: options.config.userId },
|
|
31
|
+
socket: () => this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws,
|
|
32
|
+
});
|
|
33
|
+
super(onebot12ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
|
|
34
|
+
this.#rpc = rpc;
|
|
29
35
|
this.#logger = getAdapterLogger('onebot12', options.config.id);
|
|
30
36
|
this.#options = options;
|
|
31
37
|
this.#connectionLifecycle = createEndpointLifecycle({
|
|
32
38
|
name: `${options.config.id}:inbound`,
|
|
33
39
|
reconnect: false,
|
|
34
40
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this
|
|
38
|
-
|
|
39
|
-
this
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const callApi = (request) => this.#rpc.call(request).then(readOnebot12Response);
|
|
42
|
+
this.#management = createOneBot12EndpointManagement({ callApi });
|
|
43
|
+
this.#content = createOneBot12ContentPort(callApi);
|
|
44
|
+
const readTransportState = () => this.#connectionLifecycle.state;
|
|
45
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
46
|
+
name: options.config.id,
|
|
47
|
+
start: () => this.#start(), open: () => { this.#open = true; },
|
|
48
|
+
close: () => { this.#open = false; }, stop: () => this.#stop(),
|
|
49
|
+
send: request => this.#send(request),
|
|
50
|
+
get transportState() { return readTransportState(); },
|
|
51
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
52
|
+
});
|
|
53
|
+
this.#releaseClientEvents = forwardOnebot12ClientEvents(this, (name, payload) => {
|
|
54
|
+
void this[endpointSpec].events.run(() => {
|
|
55
|
+
if (!this.#open && !this.#started)
|
|
56
|
+
return;
|
|
57
|
+
void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
|
|
58
|
+
if (name === 'event')
|
|
59
|
+
this.#admitRaw(payload);
|
|
60
|
+
}).catch(error => this.#warnPlatformEvent(error));
|
|
61
|
+
});
|
|
43
62
|
}
|
|
44
|
-
async start() {
|
|
63
|
+
async #start() {
|
|
45
64
|
if (this.#started)
|
|
46
65
|
return;
|
|
47
66
|
if (!this.#options.config.access_token) {
|
|
@@ -73,43 +92,59 @@ export class OneBot12WssEndpoint extends ClientEndpoint {
|
|
|
73
92
|
path: this.#options.config.path,
|
|
74
93
|
}));
|
|
75
94
|
}
|
|
76
|
-
async stop() {
|
|
77
|
-
this
|
|
95
|
+
async #stop() {
|
|
96
|
+
this.#open = false;
|
|
78
97
|
this.#wsRelease?.();
|
|
79
98
|
this.#wsRelease = undefined;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
try {
|
|
100
|
+
await this.#connectionTask;
|
|
101
|
+
await this.#connectionLifecycle.stop();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
this.#rpc.dispose();
|
|
105
|
+
this.#releaseClientEvents();
|
|
106
|
+
this.#ws = undefined;
|
|
107
|
+
this.#started = false;
|
|
108
|
+
}
|
|
85
109
|
}
|
|
86
|
-
async send({ conversation, payload }) {
|
|
87
|
-
const materialized = await uploadOneBot12MediaSegments(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
async #send({ conversation, payload, signal }) {
|
|
111
|
+
const materialized = await uploadOneBot12MediaSegments({
|
|
112
|
+
payload, signal,
|
|
113
|
+
callAction: (action, params) => this.#rpc.call({ action, params, signal }).then(readOnebot12Response),
|
|
114
|
+
onUploadFailed: error => {
|
|
115
|
+
this.#logger.warn(formatCompact({ op: 'onebot12_upload_failed', endpoint: this.#options.config.id,
|
|
116
|
+
error: error instanceof Error ? error.message : String(error) }));
|
|
117
|
+
},
|
|
93
118
|
});
|
|
94
119
|
const message = formatOutboundSegments(materialized);
|
|
95
120
|
const params = buildSendMessageParams(conversation, message);
|
|
96
|
-
const data = await
|
|
121
|
+
const data = readOnebot12Response(await this.#rpc.call({ action: 'send_message', params, signal }));
|
|
97
122
|
return data?.message_id ?? '';
|
|
98
123
|
}
|
|
99
|
-
async recallMessage(messageId) {
|
|
124
|
+
async #recallMessage(messageId, signal) {
|
|
100
125
|
if (!messageId)
|
|
101
126
|
return;
|
|
102
|
-
await
|
|
127
|
+
await this.#rpc.call({ action: 'delete_message', params: { message_id: messageId }, signal }).then(readOnebot12Response);
|
|
103
128
|
}
|
|
104
129
|
#admitRaw(ev) {
|
|
130
|
+
if (!matchesOneBot12Self(this.#options.config, ev))
|
|
131
|
+
return;
|
|
132
|
+
this[endpointSpec].reportSelfUserId(ev.self.user_id);
|
|
105
133
|
if (!isMessageEvent(ev)) {
|
|
106
|
-
receiveOneBot12SideEvent(
|
|
134
|
+
receiveOneBot12SideEvent(ev, {
|
|
135
|
+
emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
136
|
+
endpointKey: this.#options.config.id,
|
|
137
|
+
caller: { callApi: (action, params) => callOnebot12Client({ client: this, action, params }) },
|
|
138
|
+
logger: this.#logger,
|
|
139
|
+
});
|
|
107
140
|
return;
|
|
108
141
|
}
|
|
109
|
-
const conversation = onebot12InboundConversation(String(this.#options.id), ev);
|
|
142
|
+
const conversation = { ...onebot12InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
110
143
|
const nickname = senderNickname(ev);
|
|
111
144
|
const mentioned = isBotMentioned(ev);
|
|
112
|
-
void this.
|
|
145
|
+
void this[endpointSpec].events.message({
|
|
146
|
+
event: ev,
|
|
147
|
+
...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
|
|
113
148
|
conversation,
|
|
114
149
|
message: { conversation, id: ev.message_id },
|
|
115
150
|
content: formatInboundContent(ev),
|
|
@@ -147,7 +182,7 @@ export class OneBot12WssEndpoint extends ClientEndpoint {
|
|
|
147
182
|
}
|
|
148
183
|
const socket = connection.socket;
|
|
149
184
|
await this.#connectionLifecycle.stop();
|
|
150
|
-
|
|
185
|
+
this.#rpc.disconnect('连接已替换');
|
|
151
186
|
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
152
187
|
this.#ws = socket;
|
|
153
188
|
lifecycleHandle.onForceClose(() => {
|
|
@@ -167,16 +202,15 @@ export class OneBot12WssEndpoint extends ClientEndpoint {
|
|
|
167
202
|
if (this.#ws !== socket)
|
|
168
203
|
return;
|
|
169
204
|
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
ingest: (event) => this.client.ingest(event),
|
|
205
|
+
this.#rpc.receive(data, event => {
|
|
206
|
+
if (matchesOneBot12Self(this.#options.config, event))
|
|
207
|
+
this.ingest(event);
|
|
174
208
|
});
|
|
175
209
|
});
|
|
176
210
|
socket.on('close', () => {
|
|
177
211
|
if (this.#ws === socket) {
|
|
178
212
|
this.#ws = undefined;
|
|
179
|
-
|
|
213
|
+
this.#rpc.disconnect();
|
|
180
214
|
}
|
|
181
215
|
lifecycleHandle.notifyClosed(new Error('OneBot12 reverse WebSocket closed'));
|
|
182
216
|
});
|
|
@@ -188,6 +222,6 @@ export class OneBot12WssEndpoint extends ClientEndpoint {
|
|
|
188
222
|
}));
|
|
189
223
|
}
|
|
190
224
|
#callAction(action, params) {
|
|
191
|
-
return
|
|
225
|
+
return this.#rpc.call({ action, params });
|
|
192
226
|
}
|
|
193
227
|
}
|