@zhin.js/adapter-onebot11 6.0.2 → 6.0.5

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.
@@ -2,14 +2,14 @@
2
2
  * OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
- import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
6
  import { createOneBot11EndpointManagement } from './endpoint-management.js';
7
7
  import { registerOnebot11AgentEndpoint } from './onebot11-agent-deps.js';
8
- import { buildSendAction, formatInboundContent, formatInboundMetadata, formatInboundTarget, formatOutboundSegments, isMessageEvent, senderUserId, } from './protocol.js';
8
+ import { buildSendAction, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
9
9
  import { verifyOneBotAccessToken } from './wss-auth.js';
10
10
  import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, startOneBot11Heartbeat, } from './ws-transport.js';
11
- const logger = getLogger('onebot11');
12
11
  export class OneBot11WssEndpoint {
12
+ #logger;
13
13
  #options;
14
14
  management = createOneBot11EndpointManagement(this);
15
15
  #ws;
@@ -21,6 +21,7 @@ export class OneBot11WssEndpoint {
21
21
  #started = false;
22
22
  #unregisterAgent;
23
23
  constructor(options) {
24
+ this.#logger = getAdapterLogger('onebot11', options.config.id);
24
25
  this.#options = options;
25
26
  }
26
27
  async start() {
@@ -29,21 +30,21 @@ export class OneBot11WssEndpoint {
29
30
  this.#started = true;
30
31
  if (!this.#options.config.access_token) {
31
32
  // wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
32
- logger.warn(formatCompact({
33
- endpoint: this.#options.config.name,
33
+ this.#logger.warn(formatCompact({
34
+ endpoint: this.#options.config.id,
34
35
  mode: 'wss',
35
36
  ok: false,
36
37
  error: 'missing access_token',
37
38
  }));
38
39
  }
39
- this.#unregisterAgent = registerOnebot11AgentEndpoint(this.#options.config.name, this);
40
+ this.#unregisterAgent = registerOnebot11AgentEndpoint(this.#options.config.id, this);
40
41
  const handle = this.#options.http.ws(this.#options.config.path);
41
42
  this.#wsRelease = handle.onConnection((connection) => {
42
43
  this.#acceptConnection(connection);
43
44
  });
44
- logger.info(formatCompact({
45
+ this.#logger.info(formatCompact({
45
46
  op: 'listen',
46
- endpoint: this.#options.config.name,
47
+ endpoint: this.#options.config.id,
47
48
  mode: 'wss',
48
49
  path: this.#options.config.path,
49
50
  }));
@@ -76,9 +77,9 @@ export class OneBot11WssEndpoint {
76
77
  }
77
78
  this.#started = false;
78
79
  }
79
- async send({ target, payload }) {
80
+ async send({ conversation, payload }) {
80
81
  const message = formatOutboundSegments(payload);
81
- const { action, params } = buildSendAction(target, message);
82
+ const { action, params } = buildSendAction(conversation, message);
82
83
  const data = await this.callApi(action, params);
83
84
  return data?.message_id != null ? String(data.message_id) : '';
84
85
  }
@@ -102,18 +103,25 @@ export class OneBot11WssEndpoint {
102
103
  admit(ev) {
103
104
  if (!this.#open || !isMessageEvent(ev))
104
105
  return;
105
- const target = formatInboundTarget(ev);
106
+ const conversation = onebot11InboundConversation(String(this.#options.id), ev);
107
+ const inbound = formatInboundMetadata(ev, this.#options.config.id);
106
108
  void this.#options.gateway.receive({
107
- adapter: this.#options.id,
108
- target,
109
+ conversation,
110
+ message: { conversation, id: String(ev.message_id) },
109
111
  content: formatInboundContent(ev),
110
- sender: senderUserId(ev),
111
- id: String(ev.message_id),
112
- metadata: formatInboundMetadata(ev, this.#options.config.name),
112
+ sender: {
113
+ id: senderUserId(ev),
114
+ name: senderDisplayName(ev) || undefined,
115
+ ...(ev.sender?.role ? { roles: [ev.sender.role] } : {}),
116
+ },
117
+ endpointId: inbound.endpointId,
118
+ ...(inbound.mentioned ? { mentioned: true } : {}),
119
+ ...(inbound.replyTo ? { replyTo: inbound.replyTo } : {}),
120
+ metadata: inbound.metadata,
113
121
  }).catch((err) => {
114
- logger.warn(formatCompact({
122
+ this.#logger.warn(formatCompact({
115
123
  op: 'onebot11_gateway_receive_failed',
116
- target,
124
+ target: `${conversation.kind}:${conversation.id}`,
117
125
  error: err instanceof Error ? err.message : String(err),
118
126
  }));
119
127
  });
@@ -136,7 +144,7 @@ export class OneBot11WssEndpoint {
136
144
  this.#heartbeatTimer = startOneBot11Heartbeat(this.#ws, this.#options.config.heartbeat_interval, this.#heartbeatTimer);
137
145
  socket.on('message', (data) => {
138
146
  handleOneBot11WsMessage(data, {
139
- endpointName: this.#options.config.name,
147
+ endpointId: this.#options.config.id,
140
148
  pending: this.#pending,
141
149
  admit: (ev) => this.admit(ev),
142
150
  });
@@ -150,8 +158,8 @@ export class OneBot11WssEndpoint {
150
158
  }
151
159
  }
152
160
  });
153
- logger.debug(formatCompact({
154
- endpoint: this.#options.config.name,
161
+ this.#logger.debug(formatCompact({
162
+ endpoint: this.#options.config.id,
155
163
  mode: 'wss',
156
164
  peer: connection.request.socket.remoteAddress,
157
165
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot11",
3
- "version": "6.0.2",
3
+ "version": "6.0.5",
4
4
  "description": "Zhin.js OneBot 11 adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,12 +41,14 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "ws": "^8.21.1",
44
- "@zhin.js/adapter": "1.1.4",
45
- "@zhin.js/command": "1.0.6",
46
- "@zhin.js/core": "1.5.1",
47
- "@zhin.js/host-http": "1.0.5",
48
- "@zhin.js/logger": "1.0.75",
49
- "@zhin.js/plugin-runtime": "1.1.2"
44
+ "@zhin.js/adapter": "1.1.7",
45
+ "@zhin.js/command": "1.0.9",
46
+ "@zhin.js/core": "1.5.4",
47
+ "@zhin.js/host-http": "1.0.8",
48
+ "@zhin.js/im-contract": "1.0.3",
49
+ "@zhin.js/logger": "1.0.76",
50
+ "@zhin.js/permission": "1.0.1",
51
+ "@zhin.js/plugin-runtime": "1.1.5"
50
52
  },
51
53
  "devDependencies": {
52
54
  "@types/node": "^26.1.2",
@@ -54,16 +56,16 @@
54
56
  "typescript": "^6.0.3",
55
57
  "vitest": "^4.1.10",
56
58
  "zod": "^4.4.3",
57
- "@zhin.js/agent": "1.1.2",
58
- "@zhin.js/host-http": "1.0.5"
59
+ "@zhin.js/agent": "1.1.5",
60
+ "@zhin.js/host-http": "1.0.8"
59
61
  },
60
62
  "peerDependencies": {
61
63
  "zod": "^4.0.0",
62
- "@zhin.js/adapter": "1.1.4",
63
- "@zhin.js/agent": "1.1.2",
64
- "@zhin.js/core": "1.5.1",
65
- "@zhin.js/plugin-runtime": "1.1.2",
66
- "zhin.js": "6.0.1"
64
+ "@zhin.js/adapter": "1.1.7",
65
+ "@zhin.js/agent": "1.1.5",
66
+ "@zhin.js/core": "1.5.4",
67
+ "@zhin.js/plugin-runtime": "1.1.5",
68
+ "zhin.js": "6.0.4"
67
69
  },
68
70
  "peerDependenciesMeta": {
69
71
  "zhin.js": {
package/plugin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { createEndpointRuntimeState } from '@zhin.js/adapter';
3
3
  import { definePlugin } from '@zhin.js/plugin-runtime';
4
- import { registerDefaultScenePlatformPermitChecker } from '@zhin.js/core';
4
+ import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
5
5
  import { onebot11RuntimeStateToken } from "./lib/onebot11-runtime-state.js";
6
6
  export default definePlugin({
7
7
  name: 'onebot11',
@@ -9,9 +9,10 @@ export default definePlugin({
9
9
  displayName: 'OneBot 11 Adapter',
10
10
  },
11
11
  setup(context) {
12
- // 运行中 endpoint 注册表(onebot11.endpoint list 的"运行中"数据源)
13
12
  context.resources.provide(onebot11RuntimeStateToken, createEndpointRuntimeState());
14
- // 平台权限门禁:scene_admin / scene_owner 由 sender role 判定(见 ws/wss-endpoint admit metadata)
15
- return registerDefaultScenePlatformPermitChecker('onebot11');
13
+ if (context.resources.has(permissionHostToken)) {
14
+ const host = context.resources.use(permissionHostToken);
15
+ return host.registerPlatform('onebot11', createSceneRolePlatformChecker());
16
+ }
16
17
  },
17
18
  });
package/schema.json CHANGED
@@ -20,16 +20,48 @@
20
20
  "type": "number",
21
21
  "default": 30000
22
22
  },
23
+ "master": {
24
+ "type": [
25
+ "string",
26
+ "number"
27
+ ],
28
+ "description": "框架 master(platform user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
29
+ },
30
+ "trusted": {
31
+ "type": "array",
32
+ "items": {
33
+ "type": [
34
+ "string",
35
+ "number"
36
+ ],
37
+ "description": "Trusted platform user id"
38
+ },
39
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
40
+ },
23
41
  "endpoints": {
24
42
  "type": "array",
25
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
43
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
26
44
  "items": {
27
45
  "type": "object",
28
46
  "additionalProperties": true,
29
47
  "properties": {
30
- "name": {
31
- "type": "string",
32
- "description": "OneBot11 bot name"
48
+ "master": {
49
+ "type": [
50
+ "string",
51
+ "number"
52
+ ],
53
+ "description": "本 endpoint 的框架 master(platform user id);覆盖顶层 master"
54
+ },
55
+ "trusted": {
56
+ "type": "array",
57
+ "items": {
58
+ "type": [
59
+ "string",
60
+ "number"
61
+ ],
62
+ "description": "Trusted platform user id"
63
+ },
64
+ "description": "本 endpoint 的 trusted 列表"
33
65
  },
34
66
  "url": {
35
67
  "type": "string",
@@ -42,10 +74,14 @@
42
74
  "access_token": {
43
75
  "type": "string",
44
76
  "description": "OneBot access token"
77
+ },
78
+ "id": {
79
+ "type": "string",
80
+ "description": "OneBot11 bot name"
45
81
  }
46
82
  },
47
83
  "required": [
48
- "name"
84
+ "id"
49
85
  ]
50
86
  }
51
87
  },
package/src/index.ts CHANGED
@@ -4,13 +4,12 @@ export {
4
4
  extractQuoteId,
5
5
  formatInboundContent,
6
6
  formatInboundMetadata,
7
- formatInboundTarget,
8
7
  formatOutboundSegments,
9
8
  getChannelId,
10
9
  isMessageEvent,
11
10
  isOneBot11BotMentioned,
12
11
  mediaRefToOneBotFile,
13
- parseSendTarget,
12
+ onebot11InboundConversation,
14
13
  resolveOneBot11Config,
15
14
  senderDisplayName,
16
15
  senderNickname,
@@ -26,7 +25,6 @@ export {
26
25
  type OneBot11WireSegment,
27
26
  type OneBot11WsConfig,
28
27
  type OneBot11WssConfig,
29
- type ParsedSendTarget,
30
28
  type ResolvedOneBot11Config,
31
29
  } from './protocol.js';
32
30
 
@@ -14,20 +14,20 @@ export interface Onebot11AgentEndpoint {
14
14
  }
15
15
 
16
16
  export interface Onebot11AgentDeps {
17
- getEndpoint: (endpointId: string) => Onebot11AgentEndpoint;
17
+ getEndpoint: (endpointKey: string) => Onebot11AgentEndpoint;
18
18
  }
19
19
 
20
20
  const endpoints = new Map<string, Onebot11AgentEndpoint>();
21
21
  let override: Onebot11AgentDeps | null = null;
22
22
 
23
23
  export function registerOnebot11AgentEndpoint(
24
- endpointId: string,
24
+ endpointKey: string,
25
25
  endpoint: Onebot11AgentEndpoint,
26
26
  ): () => void {
27
- endpoints.set(endpointId, endpoint);
27
+ endpoints.set(endpointKey, endpoint);
28
28
  return () => {
29
- if (endpoints.get(endpointId) === endpoint) {
30
- endpoints.delete(endpointId);
29
+ if (endpoints.get(endpointKey) === endpoint) {
30
+ endpoints.delete(endpointKey);
31
31
  }
32
32
  };
33
33
  }
@@ -40,9 +40,9 @@ export function setOnebot11AgentDeps(deps: Onebot11AgentDeps): void {
40
40
  export function getOnebot11AgentDeps(): Onebot11AgentDeps {
41
41
  if (override) return override;
42
42
  return {
43
- getEndpoint(endpointId: string): Onebot11AgentEndpoint {
44
- const registered = endpoints.get(endpointId);
45
- if (!registered) throw new Error(`Endpoint ${endpointId} 不存在`);
43
+ getEndpoint(endpointKey: string): Onebot11AgentEndpoint {
44
+ const registered = endpoints.get(endpointKey);
45
+ if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
46
46
  return registered;
47
47
  },
48
48
  };
package/src/protocol.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * Spec: https://github.com/botuniverse/onebot-11
5
5
  */
6
6
  import { isMediaRef, type MediaRef } from '@zhin.js/core';
7
+ import type { ConversationRef } from '@zhin.js/im-contract';
7
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
9
 
9
10
  const logger = getLogger('onebot11');
@@ -14,7 +15,7 @@ export interface OneBot11LegacyEndpointRow {
14
15
  readonly connection?: 'ws' | 'wss';
15
16
  /** Legacy alias: `type: 'ws' | 'ws_reverse'` */
16
17
  readonly type?: string;
17
- readonly name?: string;
18
+ readonly id?: string;
18
19
  readonly access_token?: string;
19
20
  readonly url?: string;
20
21
  readonly path?: string;
@@ -25,7 +26,7 @@ export interface OneBot11LegacyEndpointRow {
25
26
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
26
27
  export interface OneBot11AdapterConfig {
27
28
  readonly connection?: 'ws' | 'wss';
28
- readonly name?: string;
29
+ readonly id?: string;
29
30
  readonly access_token?: string;
30
31
  readonly url?: string;
31
32
  readonly path?: string;
@@ -37,7 +38,7 @@ export interface OneBot11AdapterConfig {
37
38
 
38
39
  export interface OneBot11ConfigBase {
39
40
  readonly context: 'onebot11';
40
- readonly name: string;
41
+ readonly id: string;
41
42
  readonly access_token?: string;
42
43
  }
43
44
 
@@ -106,11 +107,6 @@ export interface OneBot11WireSegment {
106
107
  readonly data?: Record<string, unknown>;
107
108
  }
108
109
 
109
- export interface ParsedSendTarget {
110
- readonly message_type: 'private' | 'group';
111
- readonly id: string;
112
- }
113
-
114
110
  function normalizeConnection(
115
111
  connection: string | undefined,
116
112
  legacyType: string | undefined,
@@ -127,8 +123,8 @@ export function resolveOneBot11Config(config: OneBot11AdapterConfig = {}): Resol
127
123
  config.connection ?? entry?.connection,
128
124
  entry?.type,
129
125
  );
130
- const name = (typeof config.name === 'string' && config.name)
131
- || (typeof entry?.name === 'string' && entry.name)
126
+ const id = (typeof config.id === 'string' && config.id)
127
+ || (typeof entry?.id === 'string' && entry.id)
132
128
  || process.env.ONEBOT11_BOT_NAME
133
129
  || 'onebot11-bot';
134
130
  const access_token = config.access_token ?? entry?.access_token;
@@ -143,7 +139,7 @@ export function resolveOneBot11Config(config: OneBot11AdapterConfig = {}): Resol
143
139
  return {
144
140
  context: 'onebot11',
145
141
  connection: 'ws',
146
- name,
142
+ id,
147
143
  access_token,
148
144
  url,
149
145
  reconnect_interval: config.reconnect_interval ?? entry?.reconnect_interval ?? 5000,
@@ -159,7 +155,7 @@ export function resolveOneBot11Config(config: OneBot11AdapterConfig = {}): Resol
159
155
  return {
160
156
  context: 'onebot11',
161
157
  connection: 'wss',
162
- name,
158
+ id,
163
159
  access_token,
164
160
  path,
165
161
  heartbeat_interval: config.heartbeat_interval ?? entry?.heartbeat_interval ?? 30_000,
@@ -185,29 +181,19 @@ export function getChannelId(ev: OneBot11Event): string {
185
181
  }
186
182
 
187
183
  /**
188
- * Gateway reply target:`private:uid` / `group:gid`,便于 send() 还原动作参数。
184
+ * 入站归一化 → ConversationRef:OneBot 11 `group` 消息 → kind 'group',
185
+ * 其余(private 及带 group_id 的非 private 事件按群处理)→ kind 'private'。
186
+ * OneBot 11 无 guild/temp 容器概念,不填 parent。
189
187
  */
190
- export function formatInboundTarget(ev: OneBot11Event): string {
191
- const messageType = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
192
- ? 'group'
193
- : 'private';
194
- return `${messageType}:${getChannelId(ev)}`;
195
- }
196
-
197
- export function parseSendTarget(target: string): ParsedSendTarget {
198
- const sep = target.indexOf(':');
199
- if (sep <= 0) {
200
- return { message_type: 'private', id: target };
201
- }
202
- const head = target.slice(0, sep);
203
- const rest = target.slice(sep + 1);
204
- if (head === 'group') {
205
- return { message_type: 'group', id: rest };
206
- }
207
- if (head === 'private') {
208
- return { message_type: 'private', id: rest };
209
- }
210
- return { message_type: 'private', id: target };
188
+ export function onebot11InboundConversation(endpointKey: string, ev: OneBot11Event): ConversationRef {
189
+ const kind = ev.message_type === 'group' || (ev.group_id != null && ev.message_type !== 'private')
190
+ ? 'group' as const
191
+ : 'private' as const;
192
+ return {
193
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
194
+ kind,
195
+ id: getChannelId(ev),
196
+ };
211
197
  }
212
198
 
213
199
  /** Build inbound text for MessageGateway.receive */
@@ -273,27 +259,37 @@ export function isOneBot11BotMentioned(input: {
273
259
  });
274
260
  }
275
261
 
276
- /** 构造 gateway.receive 的 metadata(ws / wss 两个 endpoint 共用)。 */
262
+ /** Inbound fields extracted from an event for gateway.receive (ws / wss 两个 endpoint 共用). */
263
+ export interface OneBot11InboundFields {
264
+ readonly metadata: Readonly<Record<string, unknown>>;
265
+ readonly endpointId: string;
266
+ readonly mentioned: boolean;
267
+ readonly replyTo: { readonly id: string } | undefined;
268
+ }
269
+
270
+ /** 构造 gateway.receive 的 metadata 与提升字段(ws / wss 两个 endpoint 共用)。 */
277
271
  export function formatInboundMetadata(
278
272
  ev: OneBot11Event,
279
273
  endpoint: string,
280
- ): Readonly<Record<string, unknown>> {
274
+ ): OneBot11InboundFields {
281
275
  const selfId = ev.self_id != null ? String(ev.self_id) : undefined;
282
276
  const nickname = senderNickname(ev);
283
277
  const quoteId = extractQuoteId(ev);
284
278
  const mentioned = isOneBot11BotMentioned({ selfId, message: ev.message });
285
- return Object.freeze({
286
- message_type: ev.message_type,
287
- user_id: ev.user_id != null ? String(ev.user_id) : undefined,
288
- group_id: ev.group_id != null ? String(ev.group_id) : undefined,
289
- endpoint,
290
- time: ev.time,
291
- self_id: selfId,
292
- role: ev.sender?.role,
293
- ...(nickname ? { nickname } : {}),
294
- ...(quoteId ? { quote_id: quoteId } : {}),
295
- ...(mentioned ? { mentioned: true } : {}),
296
- });
279
+ return {
280
+ metadata: Object.freeze({
281
+ message_type: ev.message_type,
282
+ user_id: ev.user_id != null ? String(ev.user_id) : undefined,
283
+ group_id: ev.group_id != null ? String(ev.group_id) : undefined,
284
+ time: ev.time,
285
+ self_id: selfId,
286
+ role: ev.sender?.role,
287
+ ...(nickname ? { nickname } : {}),
288
+ }),
289
+ endpointId: endpoint,
290
+ mentioned,
291
+ replyTo: quoteId ? { id: quoteId } : undefined,
292
+ };
297
293
  }
298
294
 
299
295
  /**
@@ -420,15 +416,14 @@ export function formatOutboundSegments(payload: unknown): OneBot11Segment[] {
420
416
  }
421
417
 
422
418
  export function buildSendAction(
423
- target: string,
419
+ conversation: ConversationRef,
424
420
  message: OneBot11Segment[],
425
421
  ): { action: string; params: Record<string, unknown> } {
426
- const parsed = parseSendTarget(target);
427
- if (parsed.message_type === 'group') {
422
+ if (conversation.kind === 'group') {
428
423
  return {
429
424
  action: 'send_group_msg',
430
425
  params: {
431
- group_id: Number(parsed.id) || parsed.id,
426
+ group_id: Number(conversation.id) || conversation.id,
432
427
  message,
433
428
  },
434
429
  };
@@ -436,7 +431,7 @@ export function buildSendAction(
436
431
  return {
437
432
  action: 'send_private_msg',
438
433
  params: {
439
- user_id: Number(parsed.id) || parsed.id,
434
+ user_id: Number(conversation.id) || conversation.id,
440
435
  message,
441
436
  },
442
437
  };