@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +22 -5
  3. package/adapters/onebot11/index.js +3 -26
  4. package/adapters/onebot11/index.ts +2 -32
  5. package/commands/onebot11/endpoint/definition.js +0 -2
  6. package/commands/onebot11/endpoint/definition.ts +0 -2
  7. package/lib/adapter.d.ts +20 -0
  8. package/lib/adapter.js +26 -0
  9. package/lib/client.d.ts +13 -11
  10. package/lib/client.js +35 -12
  11. package/lib/console-spec.d.ts +12 -0
  12. package/lib/console-spec.js +12 -0
  13. package/lib/console.d.ts +8 -0
  14. package/lib/console.js +13 -0
  15. package/lib/content-port.d.ts +2 -2
  16. package/lib/content-port.js +2 -2
  17. package/lib/endpoint-management.d.ts +2 -2
  18. package/lib/endpoint-management.js +8 -8
  19. package/lib/identity.d.ts +3 -0
  20. package/lib/identity.js +32 -0
  21. package/lib/index.d.ts +3 -2
  22. package/lib/index.js +3 -2
  23. package/lib/self-id.js +1 -1
  24. package/lib/side-event-dispatch.d.ts +8 -1
  25. package/lib/side-event-dispatch.js +2 -1
  26. package/lib/ws-endpoint.d.ts +5 -12
  27. package/lib/ws-endpoint.js +75 -50
  28. package/lib/ws-transport.d.ts +18 -11
  29. package/lib/ws-transport.js +28 -56
  30. package/lib/ws-types.d.ts +0 -6
  31. package/lib/wss-endpoint.d.ts +5 -12
  32. package/lib/wss-endpoint.js +76 -53
  33. package/package.json +25 -17
  34. package/pages/index/index.js +15 -0
  35. package/pages/index/index.ts +16 -0
  36. package/plugin.js +3 -4
  37. package/src/adapter.ts +32 -0
  38. package/src/client.ts +38 -28
  39. package/src/console-spec.ts +17 -0
  40. package/src/console.ts +18 -0
  41. package/src/content-port.ts +4 -4
  42. package/src/endpoint-management.ts +15 -12
  43. package/src/identity.ts +30 -0
  44. package/src/index.ts +4 -4
  45. package/src/self-id.ts +1 -1
  46. package/src/side-event-dispatch.ts +9 -7
  47. package/src/ws-endpoint.ts +71 -64
  48. package/src/ws-transport.ts +41 -82
  49. package/src/ws-types.ts +0 -7
  50. package/src/wss-endpoint.ts +73 -68
  51. package/lib/onebot11-runtime-state.d.ts +0 -1
  52. package/lib/onebot11-runtime-state.js +0 -6
  53. package/src/onebot11-runtime-state.ts +0 -7
@@ -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 { ClientEndpoint, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
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 { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, } from './ws-transport.js';
12
+ import { createOneBot11Rpc } from './ws-transport.js';
13
13
  import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
14
- import { callOnebot11Client, createOnebot11EndpointClient, forwardOnebot11ClientEvents } from './client.js';
15
- export class OneBot11WssEndpoint extends ClientEndpoint {
16
- client;
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.recallMessage(id));
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
- #requestId = { value: 0 };
27
- #pending = new Map();
28
+ #rpc;
28
29
  #started = false;
29
30
  #stopped = false;
30
31
  constructor(options) {
31
- super();
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
- this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
40
- const callApi = (action, params) => callOnebot11Client(this.client, action, params);
41
- this.management = createOneBot11EndpointManagement({ callApi });
42
- this.content = createOneBot11ContentPort(callApi);
43
- this.bindClientEvents((receive) => forwardOnebot11ClientEvents(this.client, receive), (name, payload) => {
44
- if (name === 'event')
45
- this.#admitRaw(payload);
46
- }, (_name, error) => this.#warnPlatformEvent(error));
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.close();
112
+ this.#open = false;
95
113
  this.#wsRelease?.();
96
114
  this.#wsRelease = undefined;
97
- await this.#connectionTask;
98
- await this.#connectionLifecycle.stop();
99
- rejectAllPending(this.#pending);
100
- this.#ws = undefined;
101
- this.#started = false;
102
- this.reportSelfUserId(undefined);
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 callOnebot11Client(this.client, action, params);
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 callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
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 && selfId !== this.selfUserId)
125
- this.reportSelfUserId(selfId);
144
+ if (!this.#stopped && selfId)
145
+ this[endpointSpec].reportSelfUserId(selfId);
126
146
  if (!isMessageEvent(ev)) {
127
- receiveOneBot11SideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot11Client(this.client, action, params) }, ev, this.#logger);
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.emit('message.receive', {
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.client, (error) => {
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.transportState !== 'open')
188
+ if (this.#stopped || this.#transportState !== 'open')
162
189
  return;
163
- if (selfId && selfId !== this.selfUserId)
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
- rejectAllPending(this.#pending, '连接已替换');
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
- rejectAllPending(this.#pending);
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
- handleOneBot11WsMessage(data, {
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
- rejectAllPending(this.#pending);
254
+ this.#rpc.disconnect();
232
255
  }
233
256
  lifecycleHandle.notifyClosed(new Error('OneBot11 reverse WebSocket closed'));
234
257
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot11",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Zhin.js OneBot 11 adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -14,6 +14,7 @@
14
14
  "files": [
15
15
  "adapters",
16
16
  "commands",
17
+ "pages",
17
18
  "plugin.js",
18
19
  "schema.json",
19
20
  "src",
@@ -44,16 +45,19 @@
44
45
  "@imhelper/onebot-v11": "1.0.7",
45
46
  "imhelper": "1.0.7",
46
47
  "ws": "^8.21.1",
47
- "@zhin.js/adapter": "1.1.24",
48
- "@zhin.js/agent-feature": "1.1.10",
49
- "@zhin.js/core": "1.1.52",
50
- "@zhin.js/feature-kit": "1.1.9",
51
- "@zhin.js/host-http": "1.1.11",
52
- "@zhin.js/im-contract": "1.1.8",
48
+ "@zhin.js/adapter": "1.1.26",
49
+ "@zhin.js/agent-feature": "1.1.11",
50
+ "@zhin.js/client": "1.1.22",
51
+ "@zhin.js/console-contract": "1.1.3",
52
+ "@zhin.js/core": "1.1.54",
53
+ "@zhin.js/feature-kit": "1.1.10",
54
+ "@zhin.js/host-http": "1.1.12",
55
+ "@zhin.js/im-contract": "1.1.9",
53
56
  "@zhin.js/logger": "1.1.2",
54
- "@zhin.js/permission": "1.1.9",
55
- "@zhin.js/skill": "1.1.10",
56
- "@zhin.js/tool": "1.1.11"
57
+ "@zhin.js/page": "1.1.11",
58
+ "@zhin.js/permission": "1.1.10",
59
+ "@zhin.js/skill": "1.1.11",
60
+ "@zhin.js/tool": "1.1.13"
57
61
  },
58
62
  "devDependencies": {
59
63
  "@types/node": "^26.1.2",
@@ -61,15 +65,15 @@
61
65
  "typescript": "^6.0.3",
62
66
  "vitest": "^4.1.10",
63
67
  "zod": "^4.4.3",
64
- "@zhin.js/host-http": "1.1.11",
65
- "zhin.js": "1.1.17"
68
+ "@zhin.js/host-http": "1.1.12",
69
+ "zhin.js": "1.1.19"
66
70
  },
67
71
  "peerDependencies": {
68
72
  "zod": "^4.0.0",
69
- "@zhin.js/adapter": "^1.1.24",
70
- "@zhin.js/command": "^1.1.11",
71
- "@zhin.js/core": "^1.1.52",
72
- "zhin.js": "^1.1.17"
73
+ "@zhin.js/adapter": "^1.1.26",
74
+ "@zhin.js/command": "^1.1.12",
75
+ "@zhin.js/core": "^1.1.54",
76
+ "zhin.js": "^1.1.19"
73
77
  },
74
78
  "peerDependenciesMeta": {
75
79
  "@zhin.js/command": {
@@ -103,7 +107,7 @@
103
107
  "features": [
104
108
  {
105
109
  "package": "@zhin.js/adapter",
106
- "api": "^1.0.0"
110
+ "api": "^2.0.0"
107
111
  },
108
112
  {
109
113
  "package": "@zhin.js/command",
@@ -120,6 +124,10 @@
120
124
  {
121
125
  "package": "@zhin.js/agent-feature",
122
126
  "api": "^1.0.0"
127
+ },
128
+ {
129
+ "package": "@zhin.js/page",
130
+ "api": "^1.0.0"
123
131
  }
124
132
  ],
125
133
  "plugins": []
@@ -0,0 +1,15 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { definePage } from '@zhin.js/console-contract';
3
+ import { createAdapterConsolePage } from '@zhin.js/client';
4
+ export const meta = definePage({
5
+ title: 'OneBot 11',
6
+ titleI18n: { en: 'OneBot 11' },
7
+ icon: 'Bot',
8
+ order: 70,
9
+ requiredPermissions: ['onebot11:read'],
10
+ });
11
+ /**
12
+ * Convention page: `pages/index/index.ts` → `/onebot11`(插件路径)。
13
+ * 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
14
+ */
15
+ export default createAdapterConsolePage('onebot11');
@@ -0,0 +1,16 @@
1
+ import { definePage } from '@zhin.js/console-contract';
2
+ import { createAdapterConsolePage } from '@zhin.js/client';
3
+
4
+ export const meta = definePage({
5
+ title: 'OneBot 11',
6
+ titleI18n: { en: 'OneBot 11' },
7
+ icon: 'Bot',
8
+ order: 70,
9
+ requiredPermissions: ['onebot11:read'],
10
+ });
11
+
12
+ /**
13
+ * Convention page: `pages/index/index.ts` → `/onebot11`(插件路径)。
14
+ * 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
15
+ */
16
+ export default createAdapterConsolePage('onebot11');
package/plugin.js CHANGED
@@ -1,15 +1,14 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
- import { definePlugin } from 'zhin.js';
2
+ import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
4
3
  import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
5
- import { onebot11RuntimeStateToken } from "./lib/onebot11-runtime-state.js";
4
+ import { createOnebot11ConsoleRpc } from "./lib/console.js";
6
5
  export default definePlugin({
7
6
  name: 'onebot11',
8
7
  metadata: {
9
8
  displayName: 'OneBot 11 Adapter',
10
9
  },
11
10
  setup(context) {
12
- context.resources.provide(onebot11RuntimeStateToken, createEndpointRuntimeState());
11
+ context.resources.provide(pluginConsoleRpcToken, createOnebot11ConsoleRpc(context.resources, () => context.config.get()));
13
12
  if (context.resources.has(permissionHostToken)) {
14
13
  const host = context.resources.use(permissionHostToken);
15
14
  return host.registerPlatform('onebot11', createSceneRolePlatformChecker());
package/src/adapter.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { Adapter, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
2
+ import { httpHostToken } from '@zhin.js/host-http';
3
+ import { resolveOneBot11Config, type OneBot11EndpointConfig } from './protocol.js';
4
+ import { OneBot11WsEndpoint } from './ws-endpoint.js';
5
+ import { OneBot11WssEndpoint } from './wss-endpoint.js';
6
+ import { probeOnebot11Identity } from './identity.js';
7
+ import type { Onebot11ClientEventMap } from './client.js';
8
+
9
+ export class Onebot11Adapter extends Adapter<OneBot11WsEndpoint | OneBot11WssEndpoint, OneBot11EndpointConfig, Onebot11ClientEventMap> {
10
+ readonly #context: AdapterContext<OneBot11EndpointConfig>;
11
+ constructor(context: AdapterContext<OneBot11EndpointConfig>) { super(); this.#context = context; }
12
+ override get configurationIdentityFields(): readonly string[] { return ['id']; }
13
+ override get configurationIdentityProbeFields(): readonly string[] { return ['connection', 'access_token', 'url']; }
14
+ override get configurationUpdateFields(): readonly string[] {
15
+ return ['connection', 'url', 'path', 'access_token', 'reconnect_interval', 'heartbeat_interval'];
16
+ }
17
+ protected override validateConfiguration(config: OneBot11EndpointConfig): void { resolveOneBot11Config(config); }
18
+ protected override async identifyConfiguration(input: OneBot11EndpointConfig, signal: AbortSignal): Promise<string | undefined> {
19
+ const config = resolveOneBot11Config(input);
20
+ return config.connection === 'ws' ? probeOnebot11Identity(config, signal) : undefined;
21
+ }
22
+ protected createEndpoint(input: OneBot11EndpointConfig, execution: EndpointCreationContext): OneBot11WsEndpoint | OneBot11WssEndpoint {
23
+ const config = resolveOneBot11Config(input);
24
+ return config.connection === 'wss'
25
+ ? new OneBot11WssEndpoint({ id: execution.canonicalId, signal: execution.signal, http: this.#context.use(httpHostToken), config, probeLoginInfo: true })
26
+ : new OneBot11WsEndpoint({ id: execution.canonicalId, signal: execution.signal, config, probeLoginInfo: true });
27
+ }
28
+ }
29
+
30
+ declare module '@zhin.js/feature-kit' {
31
+ interface AdapterRegistry { readonly onebot11: Onebot11Adapter; }
32
+ }
package/src/client.ts CHANGED
@@ -1,11 +1,11 @@
1
+ import { EndpointDeliveryError } from '@zhin.js/im-contract';
1
2
  import {
2
3
  OneBotV11Client,
3
4
  type OneBotV11Event as ImHelperOneBot11Event,
4
5
  type OneBotV11Response,
5
6
  } from '@imhelper/onebot-v11';
6
- import { EventFactory, type EventMap, type ImHelperEventMap } from 'imhelper';
7
+ import { ProtocolError, EventFactory, type EventMap, type ImHelperEventMap } from 'imhelper';
7
8
  import {
8
- defineEndpointClient,
9
9
  forwardEndpointClientEvents,
10
10
  type ClientEventPayloads,
11
11
  } from 'zhin.js/adapter';
@@ -25,11 +25,11 @@ type Onebot11ClientEvents = ImHelperEventMap<
25
25
  >;
26
26
  export type Onebot11ClientEventMap = ClientEventPayloads<Onebot11ClientEvents>;
27
27
 
28
- export function createOnebot11EndpointClient(
28
+ export function onebot11ClientOptions(
29
29
  config: ResolvedOneBot11Config,
30
30
  request: Onebot11ApiCall,
31
- ): OneBotV11Client {
32
- return new OneBotV11Client({
31
+ ): ConstructorParameters<typeof OneBotV11Client>[0] {
32
+ return {
33
33
  baseUrl: config.connection === 'ws'
34
34
  ? config.url.replace(/^ws(s?):/, 'http$1:')
35
35
  : 'http://localhost',
@@ -37,21 +37,42 @@ export function createOnebot11EndpointClient(
37
37
  accessToken: config.access_token,
38
38
  receiveMode: 'manual',
39
39
  call: request,
40
- });
40
+ };
41
41
  }
42
42
 
43
- export async function callOnebot11Client<T = unknown>(
44
- client: OneBotV11Client,
45
- action: string,
46
- params?: Record<string, unknown>,
47
- ): Promise<T | undefined> {
48
- const response: OneBotV11Response<T> = await client.call<T>(action, params);
49
- if (response.status !== 'ok') {
50
- throw new Error(
51
- `OneBot 11 API ${action}: retcode=${response.retcode}${response.message ? ` ${response.message}` : ''}`,
52
- );
43
+ export interface Onebot11ClientRequest {
44
+ readonly client: OneBotV11Client;
45
+ readonly action: string;
46
+ readonly params?: Record<string, unknown>;
47
+ }
48
+ /** Native methods retain ProtocolError; framework ports preserve the underlying delivery evidence. */
49
+ export async function callOnebot11Client<T = unknown>(request: Onebot11ClientRequest): Promise<T | undefined> {
50
+ try {
51
+ return readOnebot11Response<T>(await request.client.call<T>(request.action, request.params));
52
+ } catch (error) {
53
+ if (error instanceof ProtocolError) {
54
+ if (error.cause instanceof EndpointDeliveryError) throw error.cause;
55
+ if (error.response !== undefined) readOnebot11Response(error.response);
56
+ }
57
+ throw error;
53
58
  }
54
- return response.data;
59
+ }
60
+
61
+ /**
62
+ * Framework evidence follows the OB11 response envelope, including async/1 submission.
63
+ * @see https://github.com/botuniverse/onebot-11/blob/master/communication/http.md
64
+ */
65
+ export function readOnebot11Response<T = unknown>(response: unknown): T | undefined {
66
+ if (response && typeof response === 'object') {
67
+ const result = response as Partial<OneBotV11Response>;
68
+ if (result.status === 'ok' && result.retcode === 0) return result.data as T | undefined;
69
+ if (result.status === 'failed' && Number.isSafeInteger(result.retcode) && result.retcode !== 0 && result.retcode !== 1) {
70
+ throw new EndpointDeliveryError('platform_rejected',
71
+ `OneBot 11 API rejected: retcode=${result.retcode}${result.message ? ` ${result.message}` : ''}`, 'rejected');
72
+ }
73
+ }
74
+ // OB11 async/1 acknowledges submission, never the final outcome.
75
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'OneBot 11 response did not confirm the action result', 'unknown');
55
76
  }
56
77
 
57
78
  const onebot11ClientEventNames = Object.freeze([
@@ -65,14 +86,3 @@ export function forwardOnebot11ClientEvents(
65
86
  ): () => void {
66
87
  return forwardEndpointClientEvents(client, onebot11ClientEventNames, receive);
67
88
  }
68
-
69
- declare module '@zhin.js/feature-kit' {
70
- interface AdapterClientRegistry {
71
- readonly onebot11: {
72
- readonly client: OneBotV11Client;
73
- readonly events: Onebot11ClientEventMap;
74
- };
75
- }
76
- }
77
-
78
- export const onebot11Client = defineEndpointClient<OneBotV11Client, Onebot11ClientEventMap>('onebot11');
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OneBot 11 Console 页面元数据(浏览器与服务端共享;不要在这里 import 服务端代码)。
3
+ */
4
+ import type { AdapterConsoleField } from 'zhin.js/adapter';
5
+
6
+ export const onebot11ConsoleSpec = {
7
+ adapterKey: 'onebot11',
8
+ adapterDisplayName: 'OneBot 11',
9
+ fields: [
10
+ { key: 'url', description: 'OneBot 实现 WebSocket URL(connection: ws 必填)', descriptionI18n: { en: 'OneBot WebSocket URL (required for connection: ws)' } },
11
+ { key: 'path', description: 'reverse-wss 路径(connection: wss)', descriptionI18n: { en: 'Reverse WebSocket path (connection: wss)' } },
12
+ { key: 'access_token', env: true, description: 'OneBot access token' },
13
+ ] as readonly AdapterConsoleField[],
14
+ docsUrl: 'https://zhin.js.org/adapters/onebot11',
15
+ readPermission: 'onebot11:read',
16
+ writePermission: 'onebot11:write',
17
+ } as const;
package/src/console.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * OneBot 11 Console RPC:运行状态 + endpoint 增删(原 `onebot11 endpoint` 聊天命令)。
3
+ *
4
+ * 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
5
+ */
6
+ import { createAdapterConsoleRpc, type AdapterConsoleResources } from 'zhin.js/adapter';
7
+ import type { PluginConsoleRpc } from 'zhin.js';
8
+ import { onebot11ConsoleSpec } from './console-spec.js';
9
+
10
+ export function createOnebot11ConsoleRpc(
11
+ _resources: AdapterConsoleResources,
12
+ getConfig: () => unknown,
13
+ ): PluginConsoleRpc {
14
+ return createAdapterConsoleRpc({
15
+ ...onebot11ConsoleSpec,
16
+ getConfig,
17
+ });
18
+ }
@@ -1,8 +1,8 @@
1
- import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
1
+ import type { EndpointContentPort, EndpointContentResolveContext, PendingRpcRequest } from 'zhin.js/adapter';
2
2
  import { toCanonicalSegments } from '@zhin.js/core';
3
3
  import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
4
4
 
5
- type CallApi = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
5
+ type CallApi = (request: PendingRpcRequest) => Promise<unknown>;
6
6
 
7
7
  export function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort {
8
8
  return Object.freeze({
@@ -15,7 +15,7 @@ export function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort
15
15
  : Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
16
16
  }
17
17
  if (reference.kind === 'message') {
18
- const row = await callApi('get_msg', { message_id: reference.message.id }) as Record<string, unknown>;
18
+ const row = await callApi({ action: 'get_msg', params: { message_id: reference.message.id }, signal: context.signal }) as Record<string, unknown>;
19
19
  context.signal.throwIfAborted();
20
20
  const segments = normalizeSegments(row.message ?? row.raw_message);
21
21
  const sender = row.sender as Record<string, unknown> | undefined;
@@ -30,7 +30,7 @@ export function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort
30
30
  });
31
31
  return Object.freeze({ status: 'resolved' as const, reference, value });
32
32
  }
33
- const raw = await callApi('get_forward_msg', { id: reference.forwardId }) as Record<string, unknown>;
33
+ const raw = await callApi({ action: 'get_forward_msg', params: { id: reference.forwardId }, signal: context.signal }) as Record<string, unknown>;
34
34
  context.signal.throwIfAborted();
35
35
  const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
36
36
  const value: readonly ForwardEntry[] = Object.freeze(nodes.map((node) => {
@@ -7,23 +7,26 @@
7
7
  * - group → {group_id:number, name: group_name ?? name}
8
8
  * - 群成员列表保持 OneBot11 原生形状,仅保证数组
9
9
  */
10
- import type {
11
- EndpointFriend,
12
- EndpointGroup,
13
- EndpointManagement,
10
+ import {
11
+ type EndpointFriend,
12
+ type EndpointGroup,
13
+ type EndpointManagement,
14
+ type EndpointManagementRequest,
15
+ type EndpointGroupRequest,
16
+ type PendingRpcRequest,
14
17
  } from 'zhin.js/adapter';
15
18
 
16
19
  /** 管理面只依赖 endpoint 的 callApi(ws/wss 两种传输各自实现)。 */
17
20
  export interface OneBot11ManagementCaller {
18
- callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
21
+ callApi(request: PendingRpcRequest): Promise<unknown>;
19
22
  }
20
23
 
21
24
  export function createOneBot11EndpointManagement(
22
25
  endpoint: OneBot11ManagementCaller,
23
26
  ): EndpointManagement {
24
27
  return Object.freeze<EndpointManagement>({
25
- async listFriends(): Promise<readonly EndpointFriend[]> {
26
- const data = await endpoint.callApi('get_friend_list');
28
+ async listFriends(request: EndpointManagementRequest = {}): Promise<readonly EndpointFriend[]> {
29
+ const data = await endpoint.callApi({ action: 'get_friend_list', signal: request.signal });
27
30
  return toArray(data).map((value) => {
28
31
  const friend = asRecord(value);
29
32
  return {
@@ -33,8 +36,8 @@ export function createOneBot11EndpointManagement(
33
36
  };
34
37
  });
35
38
  },
36
- async listGroups(): Promise<readonly EndpointGroup[]> {
37
- const data = await endpoint.callApi('get_group_list');
39
+ async listGroups(request: EndpointManagementRequest = {}): Promise<readonly EndpointGroup[]> {
40
+ const data = await endpoint.callApi({ action: 'get_group_list', signal: request.signal });
38
41
  return toArray(data).map((value) => {
39
42
  const group = asRecord(value);
40
43
  return {
@@ -43,10 +46,10 @@ export function createOneBot11EndpointManagement(
43
46
  };
44
47
  });
45
48
  },
46
- async listGroupMembers(groupId: string): Promise<readonly unknown[]> {
47
- const data = await endpoint.callApi('get_group_member_list', {
49
+ async listGroupMembers({ groupId, signal }: EndpointGroupRequest): Promise<readonly unknown[]> {
50
+ const data = await endpoint.callApi({ action: 'get_group_member_list', signal, params: {
48
51
  group_id: toNumberId(groupId, 'group_id'),
49
- });
52
+ } });
50
53
  return toArray(data);
51
54
  },
52
55
  });