@zhin.js/adapter-onebot12 1.1.18 → 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.
Files changed (58) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +24 -5
  3. package/adapters/onebot12/index.js +3 -32
  4. package/adapters/onebot12/index.ts +2 -38
  5. package/commands/onebot12/endpoint/definition.js +2 -2
  6. package/commands/onebot12/endpoint/definition.ts +2 -2
  7. package/lib/adapter.d.ts +21 -0
  8. package/lib/adapter.js +34 -0
  9. package/lib/client.d.ts +9 -11
  10. package/lib/client.js +23 -13
  11. package/lib/console-spec.js +2 -0
  12. package/lib/console.d.ts +1 -1
  13. package/lib/console.js +2 -5
  14. package/lib/content-port.d.ts +2 -2
  15. package/lib/content-port.js +1 -1
  16. package/lib/endpoint-management.d.ts +5 -5
  17. package/lib/endpoint-management.js +17 -17
  18. package/lib/identity.d.ts +4 -0
  19. package/lib/identity.js +53 -0
  20. package/lib/index.d.ts +3 -2
  21. package/lib/index.js +2 -1
  22. package/lib/protocol.d.ts +32 -4
  23. package/lib/protocol.js +54 -53
  24. package/lib/response.d.ts +5 -0
  25. package/lib/response.js +17 -0
  26. package/lib/side-event-dispatch.d.ts +8 -1
  27. package/lib/side-event-dispatch.js +2 -1
  28. package/lib/webhook.d.ts +5 -11
  29. package/lib/webhook.js +72 -37
  30. package/lib/ws-endpoint.d.ts +5 -11
  31. package/lib/ws-endpoint.js +79 -45
  32. package/lib/ws-transport.d.ts +18 -10
  33. package/lib/ws-transport.js +26 -45
  34. package/lib/ws-types.d.ts +0 -6
  35. package/lib/wss-endpoint.d.ts +5 -11
  36. package/lib/wss-endpoint.js +82 -48
  37. package/package.json +15 -15
  38. package/plugin.js +0 -4
  39. package/schema.json +15 -3
  40. package/src/adapter.ts +36 -0
  41. package/src/client.ts +26 -29
  42. package/src/console-spec.ts +2 -0
  43. package/src/console.ts +2 -5
  44. package/src/content-port.ts +3 -3
  45. package/src/endpoint-management.ts +25 -24
  46. package/src/identity.ts +52 -0
  47. package/src/index.ts +4 -1
  48. package/src/protocol.ts +71 -62
  49. package/src/response.ts +19 -0
  50. package/src/side-event-dispatch.ts +9 -7
  51. package/src/webhook.ts +70 -55
  52. package/src/ws-endpoint.ts +75 -63
  53. package/src/ws-transport.ts +37 -73
  54. package/src/ws-types.ts +0 -7
  55. package/src/wss-endpoint.ts +80 -66
  56. package/lib/onebot12-runtime-state.d.ts +0 -1
  57. package/lib/onebot12-runtime-state.js +0 -6
  58. package/src/onebot12-runtime-state.ts +0 -7
@@ -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 { ClientEndpoint, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
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, createOnebot12EndpointClient, forwardOnebot12ClientEvents } from './client.js';
12
- import { callOneBot12WsAction, handleOneBot12WsMessage, rejectAllPending, } from './ws-transport.js';
13
- export class OneBot12WsEndpoint extends ClientEndpoint {
14
- client;
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.recallMessage(id));
19
- content;
18
+ #management;
19
+ #control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
20
+ #content;
21
+ #releaseClientEvents;
20
22
  #lifecycle;
21
23
  #ws;
22
- #requestId = { value: 0 };
23
- #pending = new Map();
24
+ #rpc;
24
25
  constructor(options) {
25
- super();
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
- this.client = createOnebot12EndpointClient(options.config, (action, params) => this.#callAction(action, params ?? {}));
29
- const callApi = (action, params) => callOnebot12Client(this.client, action, params);
30
- this.management = createOneBot12EndpointManagement({ callApi });
31
- this.content = createOneBot12ContentPort(callApi);
32
- this.bindClientEvents((receive) => forwardOnebot12ClientEvents(this.client, receive), (name, payload) => {
33
- if (name === 'event')
34
- this.#admitRaw(payload);
35
- }, (_name, error) => this.#warnPlatformEvent(error));
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.close();
88
+ async #stop() {
89
+ this.#open = false;
71
90
  // 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
72
- await this.#lifecycle.stop();
73
- rejectAllPending(this.#pending);
74
- this.#ws = undefined;
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(payload, (action, params) => callOnebot12Client(this.client, action, params), (error) => {
78
- this.#logger.warn(formatCompact({
79
- op: 'onebot12_upload_failed',
80
- endpoint: this.#options.config.id,
81
- error: error instanceof Error ? error.message : String(error),
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 callOnebot12Client(this.client, 'send_message', params);
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 callOnebot12Client(this.client, 'delete_message', { message_id: messageId });
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((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot12Client(this.client, action, params) }, ev, this.#logger);
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.emit('message.receive', {
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
- handleOneBot12WsMessage(data, {
177
- endpointId: this.#options.config.id,
178
- pending: this.#pending,
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
- rejectAllPending(this.#pending);
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 callOneBot12WsAction(this.#ws, this.#pending, this.#requestId, action, params);
251
+ return this.#rpc.call({ action, params });
218
252
  }
219
253
  }
@@ -1,12 +1,20 @@
1
- import type { OneBot12ActionResponse, OneBot12Event } from './protocol.js';
2
- import { type OneBot12PendingAction, type OneBot12WsSocket } from './ws-types.js';
3
- export interface OneBot12WsMessageOptions {
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 pending: Map<string, OneBot12PendingAction>;
6
- readonly ingest: (event: OneBot12Event) => void;
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 declare function handleOneBot12WsMessage(data: unknown, options: OneBot12WsMessageOptions): void;
9
- export declare function callOneBot12WsAction(ws: OneBot12WsSocket | undefined, pending: Map<string, OneBot12PendingAction>, requestId: {
10
- value: number;
11
- }, action: string, params: Record<string, unknown>): Promise<OneBot12ActionResponse>;
12
- export declare function rejectAllPending(pending: Map<string, OneBot12PendingAction>, message?: string): void;
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;
@@ -1,53 +1,34 @@
1
- import { clearTimeout, setTimeout } from 'node:timers';
1
+ import { createPendingRpc } from 'zhin.js/adapter';
2
2
  import { formatCompact, getLogger } from '@zhin.js/logger';
3
- import { WS_OPEN, } from './ws-types.js';
3
+ import { WS_OPEN } from './ws-types.js';
4
4
  const logger = getLogger('onebot12');
5
- export function handleOneBot12WsMessage(data, options) {
6
- try {
7
- const message = JSON.parse(decodeOneBot12WsPayload(data));
8
- if ('echo' in message && typeof message.echo === 'string') {
9
- const response = message;
10
- const pending = options.pending.get(response.echo);
11
- if (pending) {
12
- options.pending.delete(response.echo);
13
- clearTimeout(pending.timeout);
14
- pending.resolve(response);
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
- return;
17
- }
18
- options.ingest(message);
19
- }
20
- catch (error) {
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 rejectAllPending(pending, message = '连接已关闭') {
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
- }
@@ -1,25 +1,19 @@
1
1
  /**
2
2
  * OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
- import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
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 { type Onebot12Client } from './client.js';
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 ClientEndpoint<Onebot12Client> {
15
+ export declare class OneBot12WssEndpoint extends Onebot12Client implements Endpoint {
15
16
  #private;
16
- readonly client: Onebot12Client;
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
  }
@@ -1,47 +1,66 @@
1
1
  /**
2
2
  * OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
- import { ClientEndpoint, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
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, createOnebot12EndpointClient, forwardOnebot12ClientEvents } from './client.js';
10
+ import { callOnebot12Client, readOnebot12Response, onebot12ClientOptions, forwardOnebot12ClientEvents, Onebot12Client } from './client.js';
11
11
  import { verifyOneBotAccessToken } from './wss-auth.js';
12
- import { callOneBot12WsAction, handleOneBot12WsMessage, rejectAllPending, } from './ws-transport.js';
13
- export class OneBot12WssEndpoint extends ClientEndpoint {
14
- client;
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.recallMessage(id));
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
- #requestId = { value: 0 };
25
- #pending = new Map();
26
+ #rpc;
26
27
  #started = false;
27
28
  constructor(options) {
28
- super();
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
- this.client = createOnebot12EndpointClient(options.config, (action, params) => this.#callAction(action, params ?? {}));
36
- const callApi = (action, params) => callOnebot12Client(this.client, action, params);
37
- this.management = createOneBot12EndpointManagement({ callApi });
38
- this.content = createOneBot12ContentPort(callApi);
39
- this.bindClientEvents((receive) => forwardOnebot12ClientEvents(this.client, receive), (name, payload) => {
40
- if (name === 'event')
41
- this.#admitRaw(payload);
42
- }, (_name, error) => this.#warnPlatformEvent(error));
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.close();
95
+ async #stop() {
96
+ this.#open = false;
78
97
  this.#wsRelease?.();
79
98
  this.#wsRelease = undefined;
80
- await this.#connectionTask;
81
- await this.#connectionLifecycle.stop();
82
- rejectAllPending(this.#pending);
83
- this.#ws = undefined;
84
- this.#started = false;
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(payload, (action, params) => callOnebot12Client(this.client, action, params), (error) => {
88
- this.#logger.warn(formatCompact({
89
- op: 'onebot12_upload_failed',
90
- endpoint: this.#options.config.id,
91
- error: error instanceof Error ? error.message : String(error),
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 callOnebot12Client(this.client, 'send_message', params);
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 callOnebot12Client(this.client, 'delete_message', { message_id: messageId });
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((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot12Client(this.client, action, params) }, ev, this.#logger);
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.emit('message.receive', {
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
- rejectAllPending(this.#pending, '连接已替换');
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
- handleOneBot12WsMessage(data, {
171
- endpointId: this.#options.config.id,
172
- pending: this.#pending,
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
- rejectAllPending(this.#pending);
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 callOneBot12WsAction(this.#ws, this.#pending, this.#requestId, action, params);
225
+ return this.#rpc.call({ action, params });
192
226
  }
193
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot12",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "Zhin.js OneBot 12 adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -42,30 +42,30 @@
42
42
  "@imhelper/onebot-v12": "1.0.7",
43
43
  "imhelper": "1.0.7",
44
44
  "ws": "^8.21.1",
45
- "@zhin.js/adapter": "1.1.25",
46
- "@zhin.js/client": "1.1.21",
45
+ "@zhin.js/adapter": "1.1.26",
46
+ "@zhin.js/client": "1.1.22",
47
47
  "@zhin.js/console-contract": "1.1.3",
48
- "@zhin.js/core": "1.1.53",
49
- "@zhin.js/feature-kit": "1.1.9",
50
- "@zhin.js/host-http": "1.1.11",
48
+ "@zhin.js/core": "1.1.54",
49
+ "@zhin.js/feature-kit": "1.1.10",
50
+ "@zhin.js/host-http": "1.1.12",
51
51
  "@zhin.js/im-contract": "1.1.9",
52
52
  "@zhin.js/logger": "1.1.2",
53
- "@zhin.js/page": "1.1.10",
54
- "@zhin.js/skill": "1.1.10"
53
+ "@zhin.js/page": "1.1.11",
54
+ "@zhin.js/skill": "1.1.11"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^26.1.2",
58
58
  "@types/ws": "^8.18.1",
59
59
  "typescript": "^6.0.3",
60
60
  "vitest": "^4.1.10",
61
- "@zhin.js/host-http": "1.1.11",
62
- "zhin.js": "1.1.18"
61
+ "@zhin.js/host-http": "1.1.12",
62
+ "zhin.js": "1.1.19"
63
63
  },
64
64
  "peerDependencies": {
65
- "@zhin.js/adapter": "^1.1.25",
66
- "@zhin.js/command": "^1.1.11",
67
- "@zhin.js/core": "^1.1.53",
68
- "zhin.js": "^1.1.18"
65
+ "@zhin.js/adapter": "^1.1.26",
66
+ "@zhin.js/command": "^1.1.12",
67
+ "@zhin.js/core": "^1.1.54",
68
+ "zhin.js": "^1.1.19"
69
69
  },
70
70
  "peerDependenciesMeta": {
71
71
  "@zhin.js/command": {
@@ -96,7 +96,7 @@
96
96
  "features": [
97
97
  {
98
98
  "package": "@zhin.js/adapter",
99
- "api": "^1.0.0"
99
+ "api": "^2.0.0"
100
100
  },
101
101
  {
102
102
  "package": "@zhin.js/command",
package/plugin.js CHANGED
@@ -1,7 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
2
  import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
4
- import { onebot12RuntimeStateToken } from "./lib/onebot12-runtime-state.js";
5
3
  import { createOnebot12ConsoleRpc } from "./lib/console.js";
6
4
  export default definePlugin({
7
5
  name: 'onebot12',
@@ -9,8 +7,6 @@ export default definePlugin({
9
7
  displayName: 'OneBot 12 Adapter',
10
8
  },
11
9
  setup(context) {
12
- // 运行中 endpoint 注册表(onebot12 endpoint list 的"运行中"数据源)
13
- context.resources.provide(onebot12RuntimeStateToken, createEndpointRuntimeState());
14
10
  context.resources.provide(pluginConsoleRpcToken, createOnebot12ConsoleRpc(context.resources, () => context.config.get()));
15
11
  },
16
12
  });