@zhin.js/adapter-onebot11 1.1.18 → 1.1.20
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 +46 -0
- package/README.md +13 -6
- package/adapters/onebot11/index.js +3 -26
- package/adapters/onebot11/index.ts +2 -32
- package/commands/onebot11/endpoint/definition.js +0 -2
- package/commands/onebot11/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +20 -0
- package/lib/adapter.js +26 -0
- package/lib/client.d.ts +13 -11
- package/lib/client.js +35 -12
- package/lib/console.d.ts +1 -1
- package/lib/console.js +2 -5
- package/lib/content-port.d.ts +2 -2
- package/lib/content-port.js +2 -2
- package/lib/endpoint-management.d.ts +2 -2
- package/lib/endpoint-management.js +8 -8
- package/lib/identity.d.ts +3 -0
- package/lib/identity.js +32 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +3 -2
- package/lib/self-id.js +1 -1
- package/lib/side-event-dispatch.d.ts +8 -1
- package/lib/side-event-dispatch.js +2 -1
- package/lib/ws-endpoint.d.ts +5 -12
- package/lib/ws-endpoint.js +75 -50
- package/lib/ws-transport.d.ts +18 -11
- package/lib/ws-transport.js +28 -56
- package/lib/ws-types.d.ts +0 -6
- package/lib/wss-endpoint.d.ts +5 -12
- package/lib/wss-endpoint.js +76 -53
- package/package.json +18 -18
- package/plugin.js +0 -3
- package/src/adapter.ts +32 -0
- package/src/client.ts +38 -28
- package/src/console.ts +2 -5
- package/src/content-port.ts +4 -4
- package/src/endpoint-management.ts +15 -12
- package/src/identity.ts +30 -0
- package/src/index.ts +4 -4
- package/src/self-id.ts +1 -1
- package/src/side-event-dispatch.ts +9 -7
- package/src/ws-endpoint.ts +71 -64
- package/src/ws-transport.ts +41 -82
- package/src/ws-types.ts +0 -7
- package/src/wss-endpoint.ts +73 -68
- package/lib/onebot11-runtime-state.d.ts +0 -1
- package/lib/onebot11-runtime-state.js +0 -6
- package/src/onebot11-runtime-state.ts +0 -7
package/lib/ws-endpoint.js
CHANGED
|
@@ -3,39 +3,59 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
3
3
|
* OneBot11 WS client endpoint — outbound connect to OneBot implementation.
|
|
4
4
|
*/
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
|
-
import {
|
|
6
|
+
import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
7
7
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
8
|
import { createOneBot11EndpointManagement } from './endpoint-management.js';
|
|
9
9
|
import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
|
|
10
10
|
import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
|
|
11
11
|
import { createOneBot11ContentPort } from './content-port.js';
|
|
12
|
-
import {
|
|
12
|
+
import { createOneBot11Rpc } from './ws-transport.js';
|
|
13
13
|
import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
|
|
14
|
-
import { callOnebot11Client,
|
|
15
|
-
export class OneBot11WsEndpoint extends
|
|
16
|
-
|
|
14
|
+
import { callOnebot11Client, readOnebot11Response, onebot11ClientOptions, forwardOnebot11ClientEvents, Onebot11Client } from './client.js';
|
|
15
|
+
export class OneBot11WsEndpoint extends Onebot11Client {
|
|
16
|
+
[endpointSpec];
|
|
17
|
+
#open = false;
|
|
17
18
|
#logger;
|
|
18
19
|
#options;
|
|
19
|
-
management;
|
|
20
|
-
control = createRecallEndpointControl((id) => this
|
|
21
|
-
content;
|
|
20
|
+
#management;
|
|
21
|
+
#control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
|
|
22
|
+
#content;
|
|
23
|
+
#releaseClientEvents;
|
|
22
24
|
#lifecycle;
|
|
23
25
|
#ws;
|
|
24
26
|
#stopped = false;
|
|
25
|
-
#
|
|
26
|
-
#pending = new Map();
|
|
27
|
+
#rpc;
|
|
27
28
|
constructor(options) {
|
|
28
|
-
|
|
29
|
+
const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
|
|
30
|
+
socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
|
|
31
|
+
});
|
|
32
|
+
super(onebot11ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
|
|
33
|
+
this.#rpc = rpc;
|
|
29
34
|
this.#logger = getAdapterLogger('onebot11', options.config.id);
|
|
30
35
|
this.#options = options;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this
|
|
34
|
-
|
|
35
|
-
this
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
|
|
37
|
+
this.#management = createOneBot11EndpointManagement({ callApi });
|
|
38
|
+
this.#content = createOneBot11ContentPort(callApi);
|
|
39
|
+
const readTransportState = () => this.#lifecycle.state;
|
|
40
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
41
|
+
name: options.config.id,
|
|
42
|
+
start: () => this.#start(), open: () => { this.#open = true; },
|
|
43
|
+
close: () => { this.#open = false; }, stop: () => this.#stop(),
|
|
44
|
+
send: request => this.#send(request),
|
|
45
|
+
get transportState() { return readTransportState(); },
|
|
46
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
47
|
+
});
|
|
48
|
+
this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
|
|
49
|
+
void this[endpointSpec].events.run(() => {
|
|
50
|
+
// A native frame can arrive before start resolves. The spec owns candidate
|
|
51
|
+
// buffering and Root/child admission; transport start is not publication.
|
|
52
|
+
if (this.#stopped || (!this.#open && !this.#lifecycle.started))
|
|
53
|
+
return;
|
|
54
|
+
void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
|
|
55
|
+
if (name === 'event')
|
|
56
|
+
this.#admitRaw(payload);
|
|
57
|
+
}).catch(error => this.#warnPlatformEvent(error));
|
|
58
|
+
});
|
|
39
59
|
const { config } = options;
|
|
40
60
|
this.#lifecycle = createEndpointLifecycle({
|
|
41
61
|
name: config.id,
|
|
@@ -49,10 +69,10 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
49
69
|
heartbeat: { intervalMs: config.heartbeat_interval, watchdogMisses: 2 },
|
|
50
70
|
});
|
|
51
71
|
}
|
|
52
|
-
get transportState() {
|
|
72
|
+
get #transportState() {
|
|
53
73
|
return this.#lifecycle.state;
|
|
54
74
|
}
|
|
55
|
-
async start() {
|
|
75
|
+
async #start() {
|
|
56
76
|
if (this.#lifecycle.started)
|
|
57
77
|
return;
|
|
58
78
|
this.#stopped = false;
|
|
@@ -75,19 +95,24 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
75
95
|
throw err;
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
|
-
async stop() {
|
|
98
|
+
async #stop() {
|
|
79
99
|
this.#stopped = true;
|
|
80
|
-
this
|
|
100
|
+
this.#open = false;
|
|
81
101
|
// 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
try {
|
|
103
|
+
await this.#lifecycle.stop();
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
this.#rpc.dispose();
|
|
107
|
+
this.#releaseClientEvents();
|
|
108
|
+
this.#ws = undefined;
|
|
109
|
+
this[endpointSpec].reportSelfUserId(undefined);
|
|
110
|
+
}
|
|
86
111
|
}
|
|
87
|
-
async send({ conversation, payload }) {
|
|
112
|
+
async #send({ conversation, payload, signal }) {
|
|
88
113
|
const message = formatOutboundSegments(payload);
|
|
89
114
|
const { action, params } = buildSendAction(conversation, message);
|
|
90
|
-
const data = await
|
|
115
|
+
const data = readOnebot11Response(await this.#rpc.call({ action, params, signal }));
|
|
91
116
|
const rawId = data?.message_id;
|
|
92
117
|
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
93
118
|
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
@@ -101,26 +126,30 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
101
126
|
}));
|
|
102
127
|
return messageId;
|
|
103
128
|
}
|
|
104
|
-
async recallMessage(messageId) {
|
|
129
|
+
async #recallMessage(messageId, signal) {
|
|
105
130
|
if (!messageId)
|
|
106
131
|
return;
|
|
107
|
-
await
|
|
108
|
-
}
|
|
109
|
-
#callApi(action, params = {}) {
|
|
110
|
-
return callOneBot11WsAction(this.#lifecycle.state === 'open' ? this.#ws : undefined, this.#pending, this.#requestId, action, params);
|
|
132
|
+
await this.#rpc.call({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal }).then(readOnebot11Response);
|
|
111
133
|
}
|
|
112
134
|
#admitRaw(ev) {
|
|
113
135
|
const selfId = resolveOnebot11SelfId(ev.self_id);
|
|
114
|
-
if (!this.#stopped && selfId
|
|
115
|
-
this.reportSelfUserId(selfId);
|
|
136
|
+
if (!this.#stopped && selfId)
|
|
137
|
+
this[endpointSpec].reportSelfUserId(selfId);
|
|
116
138
|
if (!isMessageEvent(ev)) {
|
|
117
|
-
receiveOneBot11SideEvent(
|
|
139
|
+
receiveOneBot11SideEvent(ev, {
|
|
140
|
+
emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
141
|
+
endpointKey: this.#options.config.id,
|
|
142
|
+
caller: { callApi: (action, params) => callOnebot11Client({ client: this, action, params }) },
|
|
143
|
+
logger: this.#logger,
|
|
144
|
+
});
|
|
118
145
|
return;
|
|
119
146
|
}
|
|
120
|
-
const conversation = onebot11InboundConversation(String(this.#options.id), ev);
|
|
147
|
+
const conversation = { ...onebot11InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
121
148
|
const content = formatInboundContent(ev);
|
|
122
149
|
const inbound = formatInboundMetadata(ev, this.#options.config.id);
|
|
123
|
-
void this.
|
|
150
|
+
void this[endpointSpec].events.message({
|
|
151
|
+
event: ev,
|
|
152
|
+
...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
|
|
124
153
|
conversation,
|
|
125
154
|
message: { conversation, id: String(ev.message_id) },
|
|
126
155
|
content,
|
|
@@ -143,16 +172,16 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
143
172
|
}
|
|
144
173
|
/** Best-effort 登录账号探测;失败仅 debug,不影响连接。 */
|
|
145
174
|
async #probeSelfUserId() {
|
|
146
|
-
const selfId = await probeOnebot11SelfId(this
|
|
175
|
+
const selfId = await probeOnebot11SelfId(this, (error) => {
|
|
147
176
|
this.#logger.debug(formatCompact({
|
|
148
177
|
op: 'onebot11_get_login_info_failed',
|
|
149
178
|
error: error instanceof Error ? error.message : String(error),
|
|
150
179
|
}));
|
|
151
180
|
});
|
|
152
|
-
if (this.#stopped || this
|
|
181
|
+
if (this.#stopped || this.#transportState !== 'open')
|
|
153
182
|
return;
|
|
154
|
-
if (selfId
|
|
155
|
-
this.reportSelfUserId(selfId);
|
|
183
|
+
if (selfId)
|
|
184
|
+
this[endpointSpec].reportSelfUserId(selfId);
|
|
156
185
|
}
|
|
157
186
|
#warnPlatformEvent(error) {
|
|
158
187
|
this.#logger.warn(formatCompact({
|
|
@@ -170,7 +199,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
170
199
|
this.#ws = ws;
|
|
171
200
|
handle.onForceClose(() => {
|
|
172
201
|
if (this.#ws === ws) {
|
|
173
|
-
|
|
202
|
+
this.#rpc.disconnect();
|
|
174
203
|
}
|
|
175
204
|
try {
|
|
176
205
|
ws.close();
|
|
@@ -206,17 +235,13 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
206
235
|
if (this.#ws !== ws || ws.readyState !== 1 || !this.#lifecycle.started)
|
|
207
236
|
return;
|
|
208
237
|
this.#lifecycle.notifyHeartbeatAck();
|
|
209
|
-
|
|
210
|
-
endpointId: this.#options.config.id,
|
|
211
|
-
pending: this.#pending,
|
|
212
|
-
ingest: (ev) => this.client.ingest(ev),
|
|
213
|
-
});
|
|
238
|
+
this.#rpc.receive(data, (ev) => this.ingest(ev));
|
|
214
239
|
});
|
|
215
240
|
ws.on('close', (code, reason) => {
|
|
216
241
|
if (this.#ws !== ws)
|
|
217
242
|
return;
|
|
218
243
|
this.#ws = undefined;
|
|
219
|
-
|
|
244
|
+
this.#rpc.disconnect();
|
|
220
245
|
const reasonStr = typeof reason === 'string'
|
|
221
246
|
? reason
|
|
222
247
|
: Buffer.isBuffer(reason)
|
package/lib/ws-transport.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export
|
|
1
|
+
import { type PendingRpc, type PendingRpcRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { OneBot11ActionResponse, OneBot11Event } from './protocol.js';
|
|
3
|
+
import { type OneBot11WsSocket } from './ws-types.js';
|
|
4
|
+
export interface OneBot11RpcOptions {
|
|
5
5
|
readonly endpointId: string;
|
|
6
|
-
readonly
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
/** Reads the lifecycle's selected socket; RPC never opens or reconnects it. */
|
|
8
|
+
readonly socket: () => OneBot11WsSocket | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface OneBot11Rpc {
|
|
11
|
+
call(request: PendingRpcRequest): Promise<OneBot11ActionResponse>;
|
|
12
|
+
receive(data: unknown, ingest: (event: OneBot11Event) => void): void;
|
|
13
|
+
disconnect: PendingRpc<OneBot11ActionResponse>['disconnect'];
|
|
14
|
+
dispose(): void;
|
|
15
|
+
readonly pendingCount: number;
|
|
16
|
+
}
|
|
17
|
+
/** A Client's one request owner serves native SDK actions and framework operations. */
|
|
18
|
+
export declare function createOneBot11Rpc(options: OneBot11RpcOptions): OneBot11Rpc;
|
|
19
|
+
export declare function decodeWsPayload(data: unknown): string;
|
package/lib/ws-transport.js
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { clearTimeout, setTimeout } from 'node:timers';
|
|
1
|
+
import { createPendingRpc } from 'zhin.js/adapter';
|
|
3
2
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
4
|
-
import { WS_OPEN
|
|
3
|
+
import { WS_OPEN } from './ws-types.js';
|
|
5
4
|
const logger = getLogger('onebot11');
|
|
5
|
+
/** A Client's one request owner serves native SDK actions and framework operations. */
|
|
6
|
+
export function createOneBot11Rpc(options) {
|
|
7
|
+
const pending = createPendingRpc({
|
|
8
|
+
signal: options.signal, requestPrefix: 'ob11_', 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 }),
|
|
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);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
logger.warn(formatCompact({ op: 'onebot11_parse_failed', endpoint: options.endpointId,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error) }));
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
6
31
|
export function decodeWsPayload(data) {
|
|
7
32
|
if (typeof data === 'string')
|
|
8
33
|
return data;
|
|
@@ -12,56 +37,3 @@ export function decodeWsPayload(data) {
|
|
|
12
37
|
return new TextDecoder().decode(data);
|
|
13
38
|
return String(data ?? '');
|
|
14
39
|
}
|
|
15
|
-
export function handleOneBot11WsMessage(data, options) {
|
|
16
|
-
try {
|
|
17
|
-
const raw = decodeWsPayload(data);
|
|
18
|
-
const msg = JSON.parse(raw);
|
|
19
|
-
if ('echo' in msg && typeof msg.echo === 'string') {
|
|
20
|
-
const resp = msg;
|
|
21
|
-
const pending = options.pending.get(resp.echo);
|
|
22
|
-
if (pending) {
|
|
23
|
-
options.pending.delete(resp.echo);
|
|
24
|
-
clearTimeout(pending.timeout);
|
|
25
|
-
pending.resolve(resp);
|
|
26
|
-
}
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
options.ingest(msg);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
logger.warn(formatCompact({
|
|
33
|
-
op: 'onebot11_parse_failed',
|
|
34
|
-
endpoint: options.endpointId,
|
|
35
|
-
error: error instanceof Error ? error.message : String(error),
|
|
36
|
-
}));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export function callOneBot11WsAction(ws, pending, requestId, action, params) {
|
|
40
|
-
if (!ws || ws.readyState !== WS_OPEN) {
|
|
41
|
-
return Promise.reject(new EndpointDeliveryError('endpoint_disconnected', 'WebSocket 未连接', 'not_sent'));
|
|
42
|
-
}
|
|
43
|
-
const echo = `ob11_${++requestId.value}`;
|
|
44
|
-
const req = { action, params, echo };
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
const timeout = setTimeout(() => {
|
|
47
|
-
pending.delete(echo);
|
|
48
|
-
reject(new EndpointDeliveryError('endpoint_timeout', `OneBot11 动作超时: ${action}`, 'unknown'));
|
|
49
|
-
}, 30_000);
|
|
50
|
-
pending.set(echo, { resolve, reject, timeout });
|
|
51
|
-
try {
|
|
52
|
-
ws.send(JSON.stringify(req));
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
pending.delete(echo);
|
|
56
|
-
clearTimeout(timeout);
|
|
57
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
export function rejectAllPending(pending, message = '连接已关闭') {
|
|
62
|
-
for (const [, entry] of pending) {
|
|
63
|
-
clearTimeout(entry.timeout);
|
|
64
|
-
entry.reject(new EndpointDeliveryError('endpoint_disconnected', message, 'unknown'));
|
|
65
|
-
}
|
|
66
|
-
pending.clear();
|
|
67
|
-
}
|
package/lib/ws-types.d.ts
CHANGED
|
@@ -10,9 +10,3 @@ export interface OneBot11WsCreateOptions {
|
|
|
10
10
|
readonly headers?: Record<string, string>;
|
|
11
11
|
}
|
|
12
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/wss-endpoint.d.ts
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OneBot11 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 OneBot11WssConfig } from './protocol.js';
|
|
8
|
-
import {
|
|
8
|
+
import { Onebot11Client } from './client.js';
|
|
9
9
|
export interface OneBot11WssEndpointOptions {
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
10
11
|
readonly id: CapabilityId;
|
|
11
12
|
/** 连接后 best-effort 调用 get_login_info 上报账号(Adapter 工厂开启)。 */
|
|
12
13
|
readonly probeLoginInfo?: boolean;
|
|
13
14
|
readonly http: HttpHost;
|
|
14
15
|
readonly config: OneBot11WssConfig;
|
|
15
16
|
}
|
|
16
|
-
export declare class OneBot11WssEndpoint extends
|
|
17
|
+
export declare class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
|
|
17
18
|
#private;
|
|
18
|
-
readonly
|
|
19
|
-
readonly management: EndpointManagement;
|
|
20
|
-
readonly control: EndpointControl;
|
|
21
|
-
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
19
|
+
readonly [endpointSpec]: EndpointSpecification;
|
|
22
20
|
constructor(options: OneBot11WssEndpointOptions);
|
|
23
|
-
get transportState(): EndpointTransportState;
|
|
24
|
-
start(): Promise<void>;
|
|
25
|
-
stop(): Promise<void>;
|
|
26
|
-
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
27
|
-
recallMessage(messageId: string): Promise<void>;
|
|
28
21
|
}
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -2,33 +2,38 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
2
2
|
/**
|
|
3
3
|
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { createEndpointSpecification, endpointSpec, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
6
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createOneBot11EndpointManagement } from './endpoint-management.js';
|
|
8
8
|
import { buildSendAction, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
|
|
9
9
|
import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
|
|
10
10
|
import { createOneBot11ContentPort } from './content-port.js';
|
|
11
11
|
import { verifyOneBotAccessToken } from './wss-auth.js';
|
|
12
|
-
import {
|
|
12
|
+
import { createOneBot11Rpc } from './ws-transport.js';
|
|
13
13
|
import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
|
|
14
|
-
import { callOnebot11Client,
|
|
15
|
-
export class OneBot11WssEndpoint extends
|
|
16
|
-
|
|
14
|
+
import { callOnebot11Client, readOnebot11Response, onebot11ClientOptions, forwardOnebot11ClientEvents, Onebot11Client } from './client.js';
|
|
15
|
+
export class OneBot11WssEndpoint extends Onebot11Client {
|
|
16
|
+
[endpointSpec];
|
|
17
|
+
#open = false;
|
|
17
18
|
#logger;
|
|
18
19
|
#options;
|
|
19
|
-
management;
|
|
20
|
-
control = createRecallEndpointControl((id) => this
|
|
21
|
-
content;
|
|
20
|
+
#management;
|
|
21
|
+
#control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
|
|
22
|
+
#content;
|
|
23
|
+
#releaseClientEvents;
|
|
22
24
|
#ws;
|
|
23
25
|
#wsRelease;
|
|
24
26
|
#connectionLifecycle;
|
|
25
27
|
#connectionTask = Promise.resolve();
|
|
26
|
-
#
|
|
27
|
-
#pending = new Map();
|
|
28
|
+
#rpc;
|
|
28
29
|
#started = false;
|
|
29
30
|
#stopped = false;
|
|
30
31
|
constructor(options) {
|
|
31
|
-
|
|
32
|
+
const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
|
|
33
|
+
socket: () => this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws,
|
|
34
|
+
});
|
|
35
|
+
super(onebot11ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
|
|
36
|
+
this.#rpc = rpc;
|
|
32
37
|
this.#logger = getAdapterLogger('onebot11', options.config.id);
|
|
33
38
|
this.#options = options;
|
|
34
39
|
this.#connectionLifecycle = createEndpointLifecycle({
|
|
@@ -36,16 +41,29 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
36
41
|
reconnect: false,
|
|
37
42
|
heartbeat: { intervalMs: options.config.heartbeat_interval, watchdogMisses: 2 },
|
|
38
43
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this
|
|
42
|
-
|
|
43
|
-
this
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
|
|
45
|
+
this.#management = createOneBot11EndpointManagement({ callApi });
|
|
46
|
+
this.#content = createOneBot11ContentPort(callApi);
|
|
47
|
+
const readTransportState = () => this.#transportState;
|
|
48
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
49
|
+
name: options.config.id,
|
|
50
|
+
start: () => this.#start(), open: () => { this.#open = true; },
|
|
51
|
+
close: () => { this.#open = false; }, stop: () => this.#stop(),
|
|
52
|
+
send: request => this.#send(request),
|
|
53
|
+
get transportState() { return readTransportState(); },
|
|
54
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
55
|
+
});
|
|
56
|
+
this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
|
|
57
|
+
void this[endpointSpec].events.run(() => {
|
|
58
|
+
if (this.#stopped || (!this.#open && !this.#started))
|
|
59
|
+
return;
|
|
60
|
+
void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
|
|
61
|
+
if (name === 'event')
|
|
62
|
+
this.#admitRaw(payload);
|
|
63
|
+
}).catch(error => this.#warnPlatformEvent(error));
|
|
64
|
+
});
|
|
47
65
|
}
|
|
48
|
-
get transportState() {
|
|
66
|
+
get #transportState() {
|
|
49
67
|
if (this.#stopped)
|
|
50
68
|
return 'stopped';
|
|
51
69
|
if (!this.#started)
|
|
@@ -56,7 +74,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
56
74
|
return 'open';
|
|
57
75
|
return this.#connectionLifecycle.state === 'connecting' ? 'connecting' : 'idle';
|
|
58
76
|
}
|
|
59
|
-
async start() {
|
|
77
|
+
async #start() {
|
|
60
78
|
if (this.#started)
|
|
61
79
|
return;
|
|
62
80
|
this.#stopped = false;
|
|
@@ -89,47 +107,56 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
89
107
|
path: this.#options.config.path,
|
|
90
108
|
}));
|
|
91
109
|
}
|
|
92
|
-
async stop() {
|
|
110
|
+
async #stop() {
|
|
93
111
|
this.#stopped = true;
|
|
94
|
-
this
|
|
112
|
+
this.#open = false;
|
|
95
113
|
this.#wsRelease?.();
|
|
96
114
|
this.#wsRelease = undefined;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
try {
|
|
116
|
+
await this.#connectionTask;
|
|
117
|
+
await this.#connectionLifecycle.stop();
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.#rpc.dispose();
|
|
121
|
+
this.#releaseClientEvents();
|
|
122
|
+
this.#ws = undefined;
|
|
123
|
+
this.#started = false;
|
|
124
|
+
this[endpointSpec].reportSelfUserId(undefined);
|
|
125
|
+
}
|
|
103
126
|
}
|
|
104
|
-
async send({ conversation, payload }) {
|
|
127
|
+
async #send({ conversation, payload, signal }) {
|
|
105
128
|
const message = formatOutboundSegments(payload);
|
|
106
129
|
const { action, params } = buildSendAction(conversation, message);
|
|
107
|
-
const data = await
|
|
130
|
+
const data = readOnebot11Response(await this.#rpc.call({ action, params, signal }));
|
|
108
131
|
const rawId = data?.message_id;
|
|
109
132
|
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
110
133
|
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
111
134
|
}
|
|
112
135
|
return String(rawId);
|
|
113
136
|
}
|
|
114
|
-
async recallMessage(messageId) {
|
|
137
|
+
async #recallMessage(messageId, signal) {
|
|
115
138
|
if (!messageId)
|
|
116
139
|
return;
|
|
117
|
-
await
|
|
118
|
-
}
|
|
119
|
-
#callApi(action, params = {}) {
|
|
120
|
-
return callOneBot11WsAction(this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws, this.#pending, this.#requestId, action, params);
|
|
140
|
+
await this.#rpc.call({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal }).then(readOnebot11Response);
|
|
121
141
|
}
|
|
122
142
|
#admitRaw(ev) {
|
|
123
143
|
const selfId = resolveOnebot11SelfId(ev.self_id);
|
|
124
|
-
if (!this.#stopped && selfId
|
|
125
|
-
this.reportSelfUserId(selfId);
|
|
144
|
+
if (!this.#stopped && selfId)
|
|
145
|
+
this[endpointSpec].reportSelfUserId(selfId);
|
|
126
146
|
if (!isMessageEvent(ev)) {
|
|
127
|
-
receiveOneBot11SideEvent(
|
|
147
|
+
receiveOneBot11SideEvent(ev, {
|
|
148
|
+
emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
149
|
+
endpointKey: this.#options.config.id,
|
|
150
|
+
caller: { callApi: (action, params) => callOnebot11Client({ client: this, action, params }) },
|
|
151
|
+
logger: this.#logger,
|
|
152
|
+
});
|
|
128
153
|
return;
|
|
129
154
|
}
|
|
130
|
-
const conversation = onebot11InboundConversation(String(this.#options.id), ev);
|
|
155
|
+
const conversation = { ...onebot11InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
131
156
|
const inbound = formatInboundMetadata(ev, this.#options.config.id);
|
|
132
|
-
void this.
|
|
157
|
+
void this[endpointSpec].events.message({
|
|
158
|
+
event: ev,
|
|
159
|
+
...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
|
|
133
160
|
conversation,
|
|
134
161
|
message: { conversation, id: String(ev.message_id) },
|
|
135
162
|
content: formatInboundContent(ev),
|
|
@@ -152,16 +179,16 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
152
179
|
}
|
|
153
180
|
/** Best-effort 登录账号探测;失败仅 debug,不影响连接。 */
|
|
154
181
|
async #probeSelfUserId() {
|
|
155
|
-
const selfId = await probeOnebot11SelfId(this
|
|
182
|
+
const selfId = await probeOnebot11SelfId(this, (error) => {
|
|
156
183
|
this.#logger.debug(formatCompact({
|
|
157
184
|
op: 'onebot11_get_login_info_failed',
|
|
158
185
|
error: error instanceof Error ? error.message : String(error),
|
|
159
186
|
}));
|
|
160
187
|
});
|
|
161
|
-
if (this.#stopped || this
|
|
188
|
+
if (this.#stopped || this.#transportState !== 'open')
|
|
162
189
|
return;
|
|
163
|
-
if (selfId
|
|
164
|
-
this.reportSelfUserId(selfId);
|
|
190
|
+
if (selfId)
|
|
191
|
+
this[endpointSpec].reportSelfUserId(selfId);
|
|
165
192
|
}
|
|
166
193
|
#warnPlatformEvent(error) {
|
|
167
194
|
this.#logger.warn(formatCompact({
|
|
@@ -180,12 +207,12 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
180
207
|
}
|
|
181
208
|
const socket = connection.socket;
|
|
182
209
|
await this.#connectionLifecycle.stop();
|
|
183
|
-
|
|
210
|
+
this.#rpc.disconnect('连接已替换');
|
|
184
211
|
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
185
212
|
this.#ws = socket;
|
|
186
213
|
lifecycleHandle.onForceClose(() => {
|
|
187
214
|
if (this.#ws === socket) {
|
|
188
|
-
|
|
215
|
+
this.#rpc.disconnect();
|
|
189
216
|
}
|
|
190
217
|
try {
|
|
191
218
|
socket.close();
|
|
@@ -212,11 +239,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
212
239
|
if (this.#ws !== socket || this.#connectionLifecycle.state === 'closed')
|
|
213
240
|
return;
|
|
214
241
|
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
215
|
-
|
|
216
|
-
endpointId: this.#options.config.id,
|
|
217
|
-
pending: this.#pending,
|
|
218
|
-
ingest: (ev) => this.client.ingest(ev),
|
|
219
|
-
});
|
|
242
|
+
this.#rpc.receive(data, (ev) => this.ingest(ev));
|
|
220
243
|
});
|
|
221
244
|
socket.on('error', (error) => {
|
|
222
245
|
if (this.#ws !== socket)
|
|
@@ -228,7 +251,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
228
251
|
return;
|
|
229
252
|
if (this.#ws === socket) {
|
|
230
253
|
this.#ws = undefined;
|
|
231
|
-
|
|
254
|
+
this.#rpc.disconnect();
|
|
232
255
|
}
|
|
233
256
|
lifecycleHandle.notifyClosed(new Error('OneBot11 reverse WebSocket closed'));
|
|
234
257
|
});
|