@zhin.js/adapter-onebot11 1.1.17 → 1.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +22 -5
- 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-spec.d.ts +12 -0
- package/lib/console-spec.js +12 -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 +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 +25 -17
- package/pages/index/index.js +15 -0
- package/pages/index/index.ts +16 -0
- package/plugin.js +3 -4
- package/src/adapter.ts +32 -0
- package/src/client.ts +38 -28
- package/src/console-spec.ts +17 -0
- package/src/console.ts +18 -0
- 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/src/identity.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { buildWsConnectOptions, type OneBot11WsConfig } from './protocol.js';
|
|
3
|
+
import { createOneBot11Rpc } from './ws-transport.js';
|
|
4
|
+
import { resolveOnebot11SelfId } from './self-id.js';
|
|
5
|
+
|
|
6
|
+
/** Separate read-only connection: never changes credentials or state of the running Endpoint. */
|
|
7
|
+
export async function probeOnebot11Identity(config: OneBot11WsConfig, signal: AbortSignal): Promise<string | undefined> {
|
|
8
|
+
signal.throwIfAborted();
|
|
9
|
+
const { url, headers } = buildWsConnectOptions(config);
|
|
10
|
+
const socket = new WebSocket(url, { headers });
|
|
11
|
+
const rpc = createOneBot11Rpc({ endpointId: config.id, signal, socket: () => socket, });
|
|
12
|
+
let rejectOpen: (error: unknown) => void = () => undefined;
|
|
13
|
+
const abort = () => { rejectOpen(signal.reason); rpc.disconnect(); socket.terminate(); };
|
|
14
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
15
|
+
socket.on('message', data => rpc.receive(data, () => undefined));
|
|
16
|
+
socket.on('close', () => { rejectOpen(new Error('Identity connection closed')); rpc.disconnect(); });
|
|
17
|
+
try {
|
|
18
|
+
await new Promise<void>((resolve, reject) => { rejectOpen = reject; socket.once('open', resolve); socket.on('error', reject); });
|
|
19
|
+
signal.throwIfAborted();
|
|
20
|
+
|
|
21
|
+
const response = await rpc.call({ action: 'get_login_info' });
|
|
22
|
+
signal.throwIfAborted();
|
|
23
|
+
if (response.status !== 'ok' || response.retcode !== 0) throw new Error('OneBot11 identity lookup failed');
|
|
24
|
+
const data: unknown = response.data;
|
|
25
|
+
const id = resolveOnebot11SelfId(data && typeof data === 'object' && 'user_id' in data ? data.user_id : undefined);
|
|
26
|
+
return id === undefined ? undefined : `qq:${id}`;
|
|
27
|
+
} finally {
|
|
28
|
+
signal.removeEventListener('abort', abort); rpc.dispose(); socket.terminate();
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,17 +40,16 @@ export {
|
|
|
40
40
|
export type { OneBot11WsSocket, OneBot11WsCreateOptions } from './ws-types.js';
|
|
41
41
|
|
|
42
42
|
export {
|
|
43
|
-
|
|
43
|
+
createOneBot11Rpc,
|
|
44
|
+
type OneBot11Rpc,
|
|
45
|
+
type OneBot11RpcOptions,
|
|
44
46
|
decodeWsPayload,
|
|
45
|
-
handleOneBot11WsMessage,
|
|
46
|
-
rejectAllPending,
|
|
47
47
|
} from './ws-transport.js';
|
|
48
48
|
|
|
49
49
|
export { verifyOneBotAccessToken } from './wss-auth.js';
|
|
50
50
|
|
|
51
51
|
export {
|
|
52
52
|
Onebot11Client,
|
|
53
|
-
onebot11Client,
|
|
54
53
|
type Onebot11ClientEventMap,
|
|
55
54
|
} from './client.js';
|
|
56
55
|
|
|
@@ -64,3 +63,4 @@ export type {
|
|
|
64
63
|
} from '@imhelper/onebot-v11';
|
|
65
64
|
export { ProtocolError } from '@imhelper/onebot-v11';
|
|
66
65
|
export type { ProtocolErrorKind, ProtocolErrorOptions } from 'imhelper';
|
|
66
|
+
export { Onebot11Adapter } from './adapter.js';
|
package/src/self-id.ts
CHANGED
|
@@ -19,7 +19,7 @@ export async function probeOnebot11SelfId(
|
|
|
19
19
|
onError: (error: unknown) => void,
|
|
20
20
|
): Promise<string | undefined> {
|
|
21
21
|
try {
|
|
22
|
-
const data = await callOnebot11Client<{ user_id?: number | string }>(client, 'get_login_info');
|
|
22
|
+
const data = await callOnebot11Client<{ user_id?: number | string }>({ client, action: 'get_login_info' });
|
|
23
23
|
return resolveOnebot11SelfId(data?.user_id);
|
|
24
24
|
} catch (error) {
|
|
25
25
|
onError(error);
|
|
@@ -7,13 +7,15 @@ export interface OneBot11SideEventCaller {
|
|
|
7
7
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
logger: ReturnType<typeof getAdapterLogger
|
|
16
|
-
|
|
10
|
+
/** Exact instance-owned dependencies for one canonical side-event projection. */
|
|
11
|
+
export interface OneBot11SideEventContext {
|
|
12
|
+
readonly emit: EndpointEventEmitter;
|
|
13
|
+
readonly endpointKey: string;
|
|
14
|
+
readonly caller: OneBot11SideEventCaller;
|
|
15
|
+
readonly logger: ReturnType<typeof getAdapterLogger>;
|
|
16
|
+
}
|
|
17
|
+
export function receiveOneBot11SideEvent(raw: OneBot11Event, context: OneBot11SideEventContext): void {
|
|
18
|
+
const { emit, endpointKey, caller, logger } = context;
|
|
17
19
|
if (!emit) return;
|
|
18
20
|
const record = raw as Record<string, unknown>;
|
|
19
21
|
const postType = String(record.post_type ?? '');
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
4
4
|
*/
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
createEndpointSpecification, endpointSpec,
|
|
8
|
+
type Endpoint, type EndpointSpecification,
|
|
8
9
|
createRecallEndpointControl,
|
|
9
10
|
createEndpointLifecycle,
|
|
10
11
|
type EndpointConnectHandle,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type EndpointLifecycle,
|
|
13
14
|
type EndpointManagement,
|
|
14
15
|
type EndpointSendRequest,
|
|
16
|
+
type PendingRpcRequest,
|
|
15
17
|
type EndpointTransportState,
|
|
16
18
|
} from 'zhin.js/adapter';
|
|
17
19
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
@@ -32,20 +34,16 @@ import {
|
|
|
32
34
|
} from './protocol.js';
|
|
33
35
|
import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
|
|
34
36
|
import { createOneBot11ContentPort } from './content-port.js';
|
|
37
|
+
import { createOneBot11Rpc, type OneBot11Rpc } from './ws-transport.js';
|
|
35
38
|
import {
|
|
36
|
-
callOneBot11WsAction,
|
|
37
|
-
handleOneBot11WsMessage,
|
|
38
|
-
rejectAllPending,
|
|
39
|
-
} from './ws-transport.js';
|
|
40
|
-
import {
|
|
41
|
-
type OneBot11PendingAction,
|
|
42
39
|
type OneBot11WsCreateOptions,
|
|
43
40
|
type OneBot11WsSocket,
|
|
44
41
|
} from './ws-types.js';
|
|
45
42
|
import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
|
|
46
|
-
import { callOnebot11Client,
|
|
43
|
+
import { callOnebot11Client, readOnebot11Response, onebot11ClientOptions, forwardOnebot11ClientEvents, Onebot11Client } from './client.js';
|
|
47
44
|
|
|
48
45
|
export interface OneBot11WsEndpointOptions {
|
|
46
|
+
readonly signal?: AbortSignal;
|
|
49
47
|
readonly id: CapabilityId;
|
|
50
48
|
/** 连接后 best-effort 调用 get_login_info 上报账号(Adapter 工厂开启;默认关闭以免占用首个 echo)。 */
|
|
51
49
|
readonly probeLoginInfo?: boolean;
|
|
@@ -56,35 +54,50 @@ export interface OneBot11WsEndpointOptions {
|
|
|
56
54
|
) => OneBot11WsSocket;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
export class OneBot11WsEndpoint extends
|
|
60
|
-
readonly
|
|
57
|
+
export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
|
|
58
|
+
readonly [endpointSpec]: EndpointSpecification;
|
|
59
|
+
#open = false;
|
|
61
60
|
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
62
61
|
|
|
63
62
|
readonly #options: OneBot11WsEndpointOptions;
|
|
64
|
-
readonly management: EndpointManagement;
|
|
65
|
-
readonly control: EndpointControl = createRecallEndpointControl((id) => this
|
|
66
|
-
readonly content;
|
|
63
|
+
readonly #management: EndpointManagement;
|
|
64
|
+
readonly #control: EndpointControl = createRecallEndpointControl((id, signal?: AbortSignal) => this.#recallMessage(id, signal));
|
|
65
|
+
readonly #content;
|
|
66
|
+
readonly #releaseClientEvents: () => void;
|
|
67
67
|
readonly #lifecycle: EndpointLifecycle;
|
|
68
68
|
#ws?: OneBot11WsSocket;
|
|
69
69
|
#stopped = false;
|
|
70
|
-
#
|
|
71
|
-
#pending = new Map<string, OneBot11PendingAction>();
|
|
70
|
+
readonly #rpc: OneBot11Rpc;
|
|
72
71
|
|
|
73
72
|
constructor(options: OneBot11WsEndpointOptions) {
|
|
74
|
-
|
|
73
|
+
const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
|
|
74
|
+
socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
|
|
75
|
+
});
|
|
76
|
+
super(onebot11ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
|
|
77
|
+
this.#rpc = rpc;
|
|
75
78
|
this.#logger = getAdapterLogger('onebot11', options.config.id);
|
|
76
79
|
this.#options = options;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this
|
|
80
|
-
|
|
81
|
-
this
|
|
82
|
-
|
|
83
|
-
(
|
|
80
|
+
const callApi = (request: PendingRpcRequest) => this.#rpc.call(request).then(readOnebot11Response);
|
|
81
|
+
this.#management = createOneBot11EndpointManagement({ callApi });
|
|
82
|
+
this.#content = createOneBot11ContentPort(callApi);
|
|
83
|
+
const readTransportState = () => this.#lifecycle.state;
|
|
84
|
+
this[endpointSpec] = createEndpointSpecification(this, {
|
|
85
|
+
name: options.config.id,
|
|
86
|
+
start: () => this.#start(), open: () => { this.#open = true; },
|
|
87
|
+
close: () => { this.#open = false; }, stop: () => this.#stop(),
|
|
88
|
+
send: request => this.#send(request),
|
|
89
|
+
get transportState() { return readTransportState(); },
|
|
90
|
+
management: this.#management, control: this.#control, content: this.#content,
|
|
91
|
+
});
|
|
92
|
+
this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
|
|
93
|
+
void this[endpointSpec].events.run(() => {
|
|
94
|
+
// A native frame can arrive before start resolves. The spec owns candidate
|
|
95
|
+
// buffering and Root/child admission; transport start is not publication.
|
|
96
|
+
if (this.#stopped || (!this.#open && !this.#lifecycle.started)) return;
|
|
97
|
+
void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
|
|
84
98
|
if (name === 'event') this.#admitRaw(payload as OneBot11Event);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
);
|
|
99
|
+
}).catch(error => this.#warnPlatformEvent(error));
|
|
100
|
+
});
|
|
88
101
|
const { config } = options;
|
|
89
102
|
this.#lifecycle = createEndpointLifecycle({
|
|
90
103
|
name: config.id,
|
|
@@ -99,11 +112,11 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
99
112
|
});
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
get transportState(): EndpointTransportState {
|
|
115
|
+
get #transportState(): EndpointTransportState {
|
|
103
116
|
return this.#lifecycle.state;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
async start(): Promise<void> {
|
|
119
|
+
async #start(): Promise<void> {
|
|
107
120
|
if (this.#lifecycle.started) return;
|
|
108
121
|
this.#stopped = false;
|
|
109
122
|
try {
|
|
@@ -123,20 +136,23 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
138
|
|
|
126
|
-
async stop(): Promise<void> {
|
|
139
|
+
async #stop(): Promise<void> {
|
|
127
140
|
this.#stopped = true;
|
|
128
|
-
this
|
|
141
|
+
this.#open = false;
|
|
129
142
|
// 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
|
|
130
|
-
await this.#lifecycle.stop();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
try { await this.#lifecycle.stop(); }
|
|
144
|
+
finally {
|
|
145
|
+
this.#rpc.dispose();
|
|
146
|
+
this.#releaseClientEvents();
|
|
147
|
+
this.#ws = undefined;
|
|
148
|
+
this[endpointSpec].reportSelfUserId(undefined);
|
|
149
|
+
}
|
|
134
150
|
}
|
|
135
151
|
|
|
136
|
-
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
152
|
+
async #send({ conversation, payload, signal }: EndpointSendRequest): Promise<string> {
|
|
137
153
|
const message = formatOutboundSegments(payload);
|
|
138
154
|
const { action, params } = buildSendAction(conversation, message);
|
|
139
|
-
const data =
|
|
155
|
+
const data = readOnebot11Response<{ message_id?: number | string }>(await this.#rpc.call({ action, params, signal }));
|
|
140
156
|
const rawId = data?.message_id;
|
|
141
157
|
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
142
158
|
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
@@ -151,35 +167,30 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
151
167
|
return messageId;
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
async recallMessage(messageId: string): Promise<void> {
|
|
170
|
+
async #recallMessage(messageId: string, signal?: AbortSignal): Promise<void> {
|
|
155
171
|
if (!messageId) return;
|
|
156
|
-
await
|
|
172
|
+
await this.#rpc.call({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal }).then(readOnebot11Response);
|
|
157
173
|
}
|
|
158
174
|
|
|
159
|
-
#callApi(
|
|
160
|
-
action: string,
|
|
161
|
-
params: Record<string, unknown> = {},
|
|
162
|
-
): ReturnType<typeof callOneBot11WsAction> {
|
|
163
|
-
return callOneBot11WsAction(this.#lifecycle.state === 'open' ? this.#ws : undefined, this.#pending, this.#requestId, action, params);
|
|
164
|
-
}
|
|
165
175
|
|
|
166
176
|
#admitRaw(ev: OneBot11Event): void {
|
|
167
177
|
const selfId = resolveOnebot11SelfId(ev.self_id);
|
|
168
|
-
if (!this.#stopped && selfId
|
|
178
|
+
if (!this.#stopped && selfId) this[endpointSpec].reportSelfUserId(selfId);
|
|
169
179
|
if (!isMessageEvent(ev)) {
|
|
170
|
-
receiveOneBot11SideEvent(
|
|
171
|
-
(name, payload) => this.emit(name, payload),
|
|
172
|
-
this.#options.config.id,
|
|
173
|
-
{ callApi: (action, params) => callOnebot11Client(this
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
);
|
|
180
|
+
receiveOneBot11SideEvent(ev, {
|
|
181
|
+
emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
|
|
182
|
+
endpointKey: this.#options.config.id,
|
|
183
|
+
caller: { callApi: (action, params) => callOnebot11Client({ client: this, action, params }) },
|
|
184
|
+
logger: this.#logger,
|
|
185
|
+
});
|
|
177
186
|
return;
|
|
178
187
|
}
|
|
179
|
-
const conversation = onebot11InboundConversation(String(this.#options.id), ev);
|
|
188
|
+
const conversation = { ...onebot11InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
|
|
180
189
|
const content = formatInboundContent(ev);
|
|
181
190
|
const inbound = formatInboundMetadata(ev, this.#options.config.id);
|
|
182
|
-
void this.
|
|
191
|
+
void this[endpointSpec].events.message({
|
|
192
|
+
event: ev,
|
|
193
|
+
...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
|
|
183
194
|
conversation,
|
|
184
195
|
message: { conversation, id: String(ev.message_id) },
|
|
185
196
|
content,
|
|
@@ -203,14 +214,14 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
203
214
|
|
|
204
215
|
/** Best-effort 登录账号探测;失败仅 debug,不影响连接。 */
|
|
205
216
|
async #probeSelfUserId(): Promise<void> {
|
|
206
|
-
const selfId = await probeOnebot11SelfId(this
|
|
217
|
+
const selfId = await probeOnebot11SelfId(this, (error) => {
|
|
207
218
|
this.#logger.debug(formatCompact({
|
|
208
219
|
op: 'onebot11_get_login_info_failed',
|
|
209
220
|
error: error instanceof Error ? error.message : String(error),
|
|
210
221
|
}));
|
|
211
222
|
});
|
|
212
|
-
if (this.#stopped || this
|
|
213
|
-
if (selfId
|
|
223
|
+
if (this.#stopped || this.#transportState !== 'open') return;
|
|
224
|
+
if (selfId) this[endpointSpec].reportSelfUserId(selfId);
|
|
214
225
|
}
|
|
215
226
|
|
|
216
227
|
#warnPlatformEvent(error: unknown): void {
|
|
@@ -232,7 +243,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
232
243
|
this.#ws = ws;
|
|
233
244
|
handle.onForceClose(() => {
|
|
234
245
|
if (this.#ws === ws) {
|
|
235
|
-
|
|
246
|
+
this.#rpc.disconnect();
|
|
236
247
|
}
|
|
237
248
|
try {
|
|
238
249
|
ws.close();
|
|
@@ -266,17 +277,13 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
266
277
|
ws.on('message', (data) => {
|
|
267
278
|
if (this.#ws !== ws || ws.readyState !== 1 || !this.#lifecycle.started) return;
|
|
268
279
|
this.#lifecycle.notifyHeartbeatAck();
|
|
269
|
-
|
|
270
|
-
endpointId: this.#options.config.id,
|
|
271
|
-
pending: this.#pending,
|
|
272
|
-
ingest: (ev) => this.client.ingest(ev as Parameters<Onebot11Client['ingest']>[0]),
|
|
273
|
-
});
|
|
280
|
+
this.#rpc.receive(data, (ev) => this.ingest(ev as Parameters<Onebot11Client['ingest']>[0]));
|
|
274
281
|
});
|
|
275
282
|
|
|
276
283
|
ws.on('close', (code, reason) => {
|
|
277
284
|
if (this.#ws !== ws) return;
|
|
278
285
|
this.#ws = undefined;
|
|
279
|
-
|
|
286
|
+
this.#rpc.disconnect();
|
|
280
287
|
const reasonStr = typeof reason === 'string'
|
|
281
288
|
? reason
|
|
282
289
|
: Buffer.isBuffer(reason)
|
package/src/ws-transport.ts
CHANGED
|
@@ -1,92 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { clearTimeout, setTimeout } from 'node:timers';
|
|
1
|
+
import { createPendingRpc, type PendingRpc, type PendingRpcRequest } from 'zhin.js/adapter';
|
|
3
2
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type OneBot11ActionResponse,
|
|
7
|
-
type OneBot11Event,
|
|
8
|
-
} from './protocol.js';
|
|
9
|
-
import {
|
|
10
|
-
WS_OPEN,
|
|
11
|
-
type OneBot11PendingAction,
|
|
12
|
-
type OneBot11WsSocket,
|
|
13
|
-
} from './ws-types.js';
|
|
3
|
+
import type { OneBot11ActionResponse, OneBot11Event } from './protocol.js';
|
|
4
|
+
import { WS_OPEN, type OneBot11WsSocket } from './ws-types.js';
|
|
14
5
|
|
|
15
6
|
const logger = getLogger('onebot11');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return String(data ?? '');
|
|
7
|
+
export interface OneBot11RpcOptions {
|
|
8
|
+
readonly endpointId: string;
|
|
9
|
+
readonly signal?: AbortSignal;
|
|
10
|
+
/** Reads the lifecycle's selected socket; RPC never opens or reconnects it. */
|
|
11
|
+
readonly socket: () => OneBot11WsSocket | undefined;
|
|
22
12
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
data: unknown,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
readonly ingest: (ev: OneBot11Event) => void;
|
|
30
|
-
},
|
|
31
|
-
): void {
|
|
32
|
-
try {
|
|
33
|
-
const raw = decodeWsPayload(data);
|
|
34
|
-
const msg = JSON.parse(raw) as OneBot11Event | OneBot11ActionResponse;
|
|
35
|
-
if ('echo' in msg && typeof (msg as OneBot11ActionResponse).echo === 'string') {
|
|
36
|
-
const resp = msg as OneBot11ActionResponse;
|
|
37
|
-
const pending = options.pending.get(resp.echo!);
|
|
38
|
-
if (pending) {
|
|
39
|
-
options.pending.delete(resp.echo!);
|
|
40
|
-
clearTimeout(pending.timeout);
|
|
41
|
-
pending.resolve(resp);
|
|
42
|
-
}
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
options.ingest(msg as OneBot11Event);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
logger.warn(formatCompact({
|
|
48
|
-
op: 'onebot11_parse_failed',
|
|
49
|
-
endpoint: options.endpointId,
|
|
50
|
-
error: error instanceof Error ? error.message : String(error),
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
13
|
+
export interface OneBot11Rpc {
|
|
14
|
+
call(request: PendingRpcRequest): Promise<OneBot11ActionResponse>;
|
|
15
|
+
receive(data: unknown, ingest: (event: OneBot11Event) => void): void;
|
|
16
|
+
disconnect: PendingRpc<OneBot11ActionResponse>['disconnect'];
|
|
17
|
+
dispose(): void;
|
|
18
|
+
readonly pendingCount: number;
|
|
53
19
|
}
|
|
54
20
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pending
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
clearTimeout(timeout);
|
|
78
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
79
|
-
}
|
|
21
|
+
/** A Client's one request owner serves native SDK actions and framework operations. */
|
|
22
|
+
export function createOneBot11Rpc(options: OneBot11RpcOptions): OneBot11Rpc {
|
|
23
|
+
const pending = createPendingRpc<OneBot11ActionResponse>({
|
|
24
|
+
signal: options.signal, requestPrefix: 'ob11_', timeoutMs: 30_000,
|
|
25
|
+
transport: () => { const socket = options.socket(); return socket?.readyState === WS_OPEN ? socket : undefined; },
|
|
26
|
+
serialize: request => JSON.stringify({ action: request.action, params: request.params ?? {}, echo: request.id }),
|
|
27
|
+
});
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
call: pending.call, disconnect: pending.disconnect, dispose: pending.dispose,
|
|
30
|
+
get pendingCount() { return pending.pendingCount; },
|
|
31
|
+
receive(data: unknown, ingest: (event: OneBot11Event) => void) {
|
|
32
|
+
try {
|
|
33
|
+
const message = JSON.parse(decodeWsPayload(data)) as OneBot11Event | OneBot11ActionResponse;
|
|
34
|
+
if ('echo' in message && typeof message.echo === 'string') {
|
|
35
|
+
pending.accept({ id: message.echo, value: message as OneBot11ActionResponse }); return;
|
|
36
|
+
}
|
|
37
|
+
ingest(message as OneBot11Event);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.warn(formatCompact({ op: 'onebot11_parse_failed', endpoint: options.endpointId,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error) }));
|
|
41
|
+
}
|
|
42
|
+
},
|
|
80
43
|
});
|
|
81
44
|
}
|
|
82
45
|
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
clearTimeout(entry.timeout);
|
|
89
|
-
entry.reject(new EndpointDeliveryError('endpoint_disconnected', message, 'unknown'));
|
|
90
|
-
}
|
|
91
|
-
pending.clear();
|
|
46
|
+
export function decodeWsPayload(data: unknown): string {
|
|
47
|
+
if (typeof data === 'string') return data;
|
|
48
|
+
if (Buffer.isBuffer(data)) return data.toString();
|
|
49
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
50
|
+
return String(data ?? '');
|
|
92
51
|
}
|
package/src/ws-types.ts
CHANGED
|
@@ -12,10 +12,3 @@ export interface OneBot11WsCreateOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const WS_OPEN = 1;
|
|
15
|
-
|
|
16
|
-
export interface OneBot11PendingAction {
|
|
17
|
-
resolve: (value: OneBot11ActionResponse) => void;
|
|
18
|
-
reject: (err: Error) => void;
|
|
19
|
-
timeout: NodeJS.Timeout;
|
|
20
|
-
}
|
|
21
|
-
import type { OneBot11ActionResponse } from './protocol.js';
|