@zhin.js/adapter-onebot11 7.0.0 → 7.0.1

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.
@@ -1,20 +1,19 @@
1
1
  /**
2
2
  * OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
- import { clearInterval } from 'node:timers';
5
4
  import {
5
+ ClientEndpoint,
6
+ createEndpointLifecycle,
6
7
  createRecallEndpointControl,
8
+ type EndpointLifecycle,
7
9
  type EndpointControl,
8
- type EndpointInstance,
9
10
  type EndpointManagement,
10
11
  type EndpointSendRequest,
11
12
  } from 'zhin.js/adapter';
12
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
13
13
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
14
14
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
15
  import type { CapabilityId } from 'zhin.js';
16
16
  import { createOneBot11EndpointManagement } from './endpoint-management.js';
17
- import { registerOnebot11AgentEndpoint } from './onebot11-agent-deps.js';
18
17
  import {
19
18
  buildSendAction,
20
19
  formatInboundContent,
@@ -34,46 +33,57 @@ import {
34
33
  callOneBot11WsAction,
35
34
  handleOneBot11WsMessage,
36
35
  rejectAllPending,
37
- startOneBot11Heartbeat,
38
36
  } from './ws-transport.js';
39
37
  import { OneBot11WsEndpoint } from './ws-endpoint.js';
40
38
  import {
41
39
  type OneBot11PendingAction,
42
40
  type OneBot11WsSocket,
43
41
  } from './ws-types.js';
42
+ import { callOnebot11Client, createOnebot11EndpointClient, forwardOnebot11ClientEvents, type Onebot11Client } from './client.js';
44
43
 
45
44
  export interface OneBot11WssEndpointOptions {
46
45
  readonly id: CapabilityId;
47
- readonly gateway: MessageGateway;
48
- readonly sideEvents?: SideEventGateway;
49
46
  readonly http: HttpHost;
50
47
  readonly config: OneBot11WssConfig;
51
48
  }
52
49
 
53
- export class OneBot11WssEndpoint implements EndpointInstance {
50
+ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
51
+ readonly client: Onebot11Client;
54
52
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
55
53
 
56
54
  readonly #options: OneBot11WssEndpointOptions;
57
- readonly management: EndpointManagement = createOneBot11EndpointManagement(this);
55
+ readonly management: EndpointManagement;
58
56
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
59
- readonly content = createOneBot11ContentPort((action, params) => this.callApi(action, params));
57
+ readonly content;
60
58
  #ws?: OneBot11WsSocket;
61
59
  #wsRelease?: () => void;
62
- #heartbeatTimer?: NodeJS.Timeout;
60
+ readonly #lifecycle: EndpointLifecycle;
63
61
  #requestId = { value: 0 };
64
62
  #pending = new Map<string, OneBot11PendingAction>();
65
- #open = false;
66
- #started = false;
67
- #unregisterAgent?: () => void;
68
63
 
69
64
  constructor(options: OneBot11WssEndpointOptions) {
65
+ super();
70
66
  this.#logger = getAdapterLogger('onebot11', options.config.id);
71
67
  this.#options = options;
68
+ this.#lifecycle = createEndpointLifecycle({
69
+ name: options.config.id,
70
+ reconnect: false,
71
+ heartbeat: { intervalMs: options.config.heartbeat_interval },
72
+ });
73
+ this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
74
+ const callApi = (action: string, params?: Record<string, unknown>) => callOnebot11Client(this.client, action, params);
75
+ this.management = createOneBot11EndpointManagement({ callApi });
76
+ this.content = createOneBot11ContentPort(callApi);
77
+ this.bindClientEvents(
78
+ (receive) => forwardOnebot11ClientEvents(this.client, receive),
79
+ (name, payload) => {
80
+ if (name === 'event') this.#admitRaw(payload as OneBot11Event);
81
+ },
82
+ (_name, error) => this.#warnPlatformEvent(error),
83
+ );
72
84
  }
73
85
 
74
86
  async start(): Promise<void> {
75
- if (this.#started) return;
76
- this.#started = true;
77
87
  if (!this.#options.config.access_token) {
78
88
  // wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
79
89
  this.#logger.warn(formatCompact({
@@ -83,13 +93,21 @@ export class OneBot11WssEndpoint implements EndpointInstance {
83
93
  error: 'missing access_token',
84
94
  }));
85
95
  }
86
- this.#unregisterAgent = registerOnebot11AgentEndpoint(
87
- this.#options.config.id,
88
- this as unknown as OneBot11WsEndpoint,
89
- );
90
- const handle = this.#options.http.ws(this.#options.config.path);
91
- this.#wsRelease = handle.onConnection((connection) => {
92
- this.#acceptConnection(connection);
96
+ await this.#lifecycle.start(async (lifecycleHandle) => {
97
+ const handle = this.#options.http.ws(this.#options.config.path);
98
+ this.#wsRelease = handle.onConnection((connection) => {
99
+ this.#acceptConnection(connection);
100
+ });
101
+ lifecycleHandle.onForceClose(() => {
102
+ this.#wsRelease?.();
103
+ this.#wsRelease = undefined;
104
+ try {
105
+ this.#ws?.close();
106
+ } catch {
107
+ /* ignore */
108
+ }
109
+ this.#ws = undefined;
110
+ });
93
111
  });
94
112
  this.#logger.info(formatCompact({
95
113
  op: 'listen',
@@ -99,69 +117,37 @@ export class OneBot11WssEndpoint implements EndpointInstance {
99
117
  }));
100
118
  }
101
119
 
102
- open(): void {
103
- this.#open = true;
104
- }
105
-
106
- close(): void {
107
- this.#open = false;
108
- }
109
-
110
120
  async stop(): Promise<void> {
111
- this.#open = false;
112
- this.#unregisterAgent?.();
113
- this.#unregisterAgent = undefined;
114
- this.#wsRelease?.();
115
- this.#wsRelease = undefined;
116
- if (this.#heartbeatTimer) {
117
- clearInterval(this.#heartbeatTimer);
118
- this.#heartbeatTimer = undefined;
119
- }
121
+ this.close();
122
+ await this.#lifecycle.stop();
120
123
  rejectAllPending(this.#pending);
121
- if (this.#ws) {
122
- try {
123
- this.#ws.close();
124
- } catch {
125
- /* ignore */
126
- }
127
- this.#ws = undefined;
128
- }
129
- this.#started = false;
130
124
  }
131
125
 
132
126
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
133
127
  const message = formatOutboundSegments(payload);
134
128
  const { action, params } = buildSendAction(conversation, message);
135
- const data = await this.callApi(action, params) as { message_id?: number | string } | undefined;
129
+ const data = await callOnebot11Client<{ message_id?: number | string }>(this.client, action, params);
136
130
  return data?.message_id != null ? String(data.message_id) : '';
137
131
  }
138
132
 
139
133
  async recallMessage(messageId: string): Promise<void> {
140
134
  if (!messageId) return;
141
- await this.callApi('delete_msg', { message_id: Number(messageId) });
135
+ await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
142
136
  }
143
137
 
144
- callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
138
+ #callApi(
139
+ action: string,
140
+ params: Record<string, unknown> = {},
141
+ ): ReturnType<typeof callOneBot11WsAction> {
145
142
  return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
146
143
  }
147
144
 
148
- async setTitle(groupId: number, userId: number, title: string, duration = -1): Promise<boolean> {
149
- await this.callApi('set_group_special_title', {
150
- group_id: groupId,
151
- user_id: userId,
152
- special_title: title,
153
- duration,
154
- });
155
- return true;
156
- }
157
-
158
- admit(ev: OneBot11Event): void {
159
- if (!this.#open) return;
145
+ #admitRaw(ev: OneBot11Event): void {
160
146
  if (!isMessageEvent(ev)) {
161
147
  receiveOneBot11SideEvent(
162
- this.#options.sideEvents,
148
+ (name, payload) => this.emit(name, payload),
163
149
  this.#options.config.id,
164
- this,
150
+ { callApi: (action, params) => callOnebot11Client(this.client, action, params) },
165
151
  ev,
166
152
  this.#logger,
167
153
  );
@@ -169,7 +155,7 @@ export class OneBot11WssEndpoint implements EndpointInstance {
169
155
  }
170
156
  const conversation = onebot11InboundConversation(String(this.#options.id), ev);
171
157
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
172
- void this.#options.gateway.receive({
158
+ void this.emit('message.receive', {
173
159
  conversation,
174
160
  message: { conversation, id: String(ev.message_id) },
175
161
  content: formatInboundContent(ev),
@@ -191,6 +177,13 @@ export class OneBot11WssEndpoint implements EndpointInstance {
191
177
  });
192
178
  }
193
179
 
180
+ #warnPlatformEvent(error: unknown): void {
181
+ this.#logger.warn(formatCompact({
182
+ op: 'onebot11_platform_event_failed',
183
+ error: error instanceof Error ? error.message : String(error),
184
+ }));
185
+ }
186
+
194
187
  #acceptConnection(connection: WsConnection): void {
195
188
  if (!verifyOneBotAccessToken(this.#options.config.access_token, connection.request)) {
196
189
  connection.socket.close(4003, 'Unauthorized');
@@ -205,25 +198,25 @@ export class OneBot11WssEndpoint implements EndpointInstance {
205
198
  }
206
199
  }
207
200
  this.#ws = socket;
208
- this.#heartbeatTimer = startOneBot11Heartbeat(
209
- this.#ws,
210
- this.#options.config.heartbeat_interval,
211
- this.#heartbeatTimer,
212
- );
201
+ this.#lifecycle.startHeartbeat(() => {
202
+ try {
203
+ this.#ws?.ping?.();
204
+ } catch {
205
+ /* ignore */
206
+ }
207
+ });
213
208
  socket.on('message', (data) => {
209
+ this.#lifecycle.notifyHeartbeatAck();
214
210
  handleOneBot11WsMessage(data, {
215
211
  endpointId: this.#options.config.id,
216
212
  pending: this.#pending,
217
- admit: (ev) => this.admit(ev),
213
+ ingest: (ev) => this.client.ingest(ev as Parameters<Onebot11Client['ingest']>[0]),
218
214
  });
219
215
  });
220
216
  socket.on('close', () => {
221
217
  if (this.#ws === socket) {
222
218
  this.#ws = undefined;
223
- if (this.#heartbeatTimer) {
224
- clearInterval(this.#heartbeatTimer);
225
- this.#heartbeatTimer = undefined;
226
- }
219
+ this.#lifecycle.stopHeartbeat();
227
220
  }
228
221
  });
229
222
  this.#logger.debug(formatCompact({
@@ -1,15 +0,0 @@
1
- /**
2
- * Agent tool deps for onebot11 (set_title etc.).
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- export interface Onebot11AgentEndpoint {
6
- setTitle(groupId: number, userId: number, title: string, duration?: number): Promise<boolean>;
7
- callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
8
- }
9
- export interface Onebot11AgentDeps {
10
- getEndpoint: (endpointKey: string) => Onebot11AgentEndpoint;
11
- }
12
- export declare function registerOnebot11AgentEndpoint(endpointKey: string, endpoint: Onebot11AgentEndpoint): () => void;
13
- /** Optional override used by tests / transitional callers. */
14
- export declare function setOnebot11AgentDeps(deps: Onebot11AgentDeps): void;
15
- export declare function getOnebot11AgentDeps(): Onebot11AgentDeps;
@@ -1,30 +0,0 @@
1
- /**
2
- * Agent tool deps for onebot11 (set_title etc.).
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- const endpoints = new Map();
6
- let override = null;
7
- export function registerOnebot11AgentEndpoint(endpointKey, endpoint) {
8
- endpoints.set(endpointKey, endpoint);
9
- return () => {
10
- if (endpoints.get(endpointKey) === endpoint) {
11
- endpoints.delete(endpointKey);
12
- }
13
- };
14
- }
15
- /** Optional override used by tests / transitional callers. */
16
- export function setOnebot11AgentDeps(deps) {
17
- override = deps;
18
- }
19
- export function getOnebot11AgentDeps() {
20
- if (override)
21
- return override;
22
- return {
23
- getEndpoint(endpointKey) {
24
- const registered = endpoints.get(endpointKey);
25
- if (!registered)
26
- throw new Error(`Endpoint ${endpointKey} 不存在`);
27
- return registered;
28
- },
29
- };
30
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * Agent tool deps for onebot11 (set_title etc.).
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
-
6
- export interface Onebot11AgentEndpoint {
7
- setTitle(
8
- groupId: number,
9
- userId: number,
10
- title: string,
11
- duration?: number,
12
- ): Promise<boolean>;
13
- callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
14
- }
15
-
16
- export interface Onebot11AgentDeps {
17
- getEndpoint: (endpointKey: string) => Onebot11AgentEndpoint;
18
- }
19
-
20
- const endpoints = new Map<string, Onebot11AgentEndpoint>();
21
- let override: Onebot11AgentDeps | null = null;
22
-
23
- export function registerOnebot11AgentEndpoint(
24
- endpointKey: string,
25
- endpoint: Onebot11AgentEndpoint,
26
- ): () => void {
27
- endpoints.set(endpointKey, endpoint);
28
- return () => {
29
- if (endpoints.get(endpointKey) === endpoint) {
30
- endpoints.delete(endpointKey);
31
- }
32
- };
33
- }
34
-
35
- /** Optional override used by tests / transitional callers. */
36
- export function setOnebot11AgentDeps(deps: Onebot11AgentDeps): void {
37
- override = deps;
38
- }
39
-
40
- export function getOnebot11AgentDeps(): Onebot11AgentDeps {
41
- if (override) return override;
42
- return {
43
- getEndpoint(endpointKey: string): Onebot11AgentEndpoint {
44
- const registered = endpoints.get(endpointKey);
45
- if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
46
- return registered;
47
- },
48
- };
49
- }