@zhin.js/adapter-onebot11 6.0.12 → 6.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @zhin.js/adapter-onebot11
2
2
 
3
+ ## 6.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - ffe7463: Add generation-bound message and merged-forward resolution through the canonical endpoint content port.
8
+ - 5969c5b: Add SideEventGateway so adapters forward notice/request/system into HandlerIndex. HandlerContext now exposes only generation-safe capabilities and prompt ports; live Endpoint escape hatches are removed.
9
+ - Updated dependencies [5969c5b]
10
+ - Updated dependencies [d336a3f]
11
+ - Updated dependencies [0c82a7e]
12
+ - Updated dependencies [b9217e4]
13
+ - Updated dependencies [5969c5b]
14
+ - Updated dependencies [5969c5b]
15
+ - Updated dependencies [974772e]
16
+ - Updated dependencies [5969c5b]
17
+ - Updated dependencies [5969c5b]
18
+ - Updated dependencies [2f786bd]
19
+ - Updated dependencies [63d89f9]
20
+ - Updated dependencies [71c7cdd]
21
+ - Updated dependencies [3cca0ea]
22
+ - Updated dependencies [1312ca0]
23
+ - Updated dependencies [985fa22]
24
+ - Updated dependencies [04b861d]
25
+ - Updated dependencies [a23d544]
26
+ - Updated dependencies [8cddabf]
27
+ - Updated dependencies [dbe5081]
28
+ - @zhin.js/im-contract@1.0.4
29
+ - @zhin.js/core@1.5.12
30
+ - @zhin.js/adapter@1.1.11
31
+ - @zhin.js/agent@1.1.14
32
+ - @zhin.js/host-http@1.0.11
33
+ - @zhin.js/command@1.0.15
34
+ - zhin.js@6.0.12
35
+ - @zhin.js/permission@1.0.3
36
+
37
+ ## 6.0.13
38
+
39
+ ### Patch Changes
40
+
41
+ - @zhin.js/core@1.5.11
42
+ - @zhin.js/agent@1.1.13
43
+ - zhin.js@6.0.11
44
+
3
45
  ## 6.0.12
4
46
 
5
47
  ### Patch Changes
package/README.md CHANGED
@@ -70,7 +70,7 @@ plugins:
70
70
 
71
71
  ## 迁移说明(Plugin Runtime)
72
72
 
73
- - **notice / request 侧事件已移除**:旧 Adapter 对 `post_type: notice|request` 构建 `notice.receive` / `request.receive` 事件;新 Plugin Runtime(`messageGatewayToken`)暂无侧事件总线,入站仅处理 `post_type: message`,notice / request 事件静默丢弃。
73
+ - **notice / request / meta 侧事件**:经 `sideEventGatewayToken` 归一后分发到 `handlers`;消息仍走 `messageGatewayToken`。
74
74
  - **群管工具暂未迁移**:旧 Adapter 经 `createSceneManagementTools` 注册踢人 / 禁言 / 群名片等成套 agent 工具;迁移后仅保留 `onebot11_set_title`,其余群管能力可通过 `callApi`(如 `set_group_kick`、`set_group_ban`)作为逃生舱调用。
75
75
  - **平台权限门禁**:`plugin.ts` setup 已注册 `registerDefaultScenePlatformPermitChecker('onebot11')`,`scene_admin` / `scene_owner` 依据入站 metadata 中的 sender `role`(owner / admin)判定。
76
76
 
@@ -3,7 +3,7 @@
3
3
  * Convention entry: discover `adapters/onebot11.ts` → defineAdapter.
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
7
7
  import { httpHostToken } from '@zhin.js/host-http';
8
8
  import { OneBot11WsEndpoint } from "../lib/ws-endpoint.js";
9
9
  import { OneBot11WssEndpoint } from "../lib/wss-endpoint.js";
@@ -21,6 +21,7 @@ export default defineAdapter({
21
21
  create(context) {
22
22
  const config = resolveOneBot11Config(context.config);
23
23
  const gateway = context.use(messageGatewayToken);
24
+ const sideEvents = context.use(sideEventGatewayToken);
24
25
  // 注册到插件运行时状态(onebot11.endpoint list 的"运行中"数据源)
25
26
  context.use(onebot11RuntimeStateToken).endpoints.set(config.id, {
26
27
  id: config.id,
@@ -30,6 +31,7 @@ export default defineAdapter({
30
31
  return new OneBot11WssEndpoint({
31
32
  id: context.id,
32
33
  gateway,
34
+ sideEvents,
33
35
  http: context.use(httpHostToken),
34
36
  config,
35
37
  });
@@ -37,6 +39,7 @@ export default defineAdapter({
37
39
  return new OneBot11WsEndpoint({
38
40
  id: context.id,
39
41
  gateway,
42
+ sideEvents,
40
43
  config,
41
44
  });
42
45
  },
@@ -2,7 +2,7 @@
2
2
  * Convention entry: discover `adapters/onebot11.ts` → defineAdapter.
3
3
  */
4
4
  import { defineAdapter } from 'zhin.js/adapter';
5
- import { messageGatewayToken } from '@zhin.js/core/runtime';
5
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
6
6
  import { httpHostToken } from '@zhin.js/host-http';
7
7
  import { OneBot11WsEndpoint } from '../src/ws-endpoint.js';
8
8
  import { OneBot11WssEndpoint } from '../src/wss-endpoint.js';
@@ -18,6 +18,12 @@ export { OneBot11WssEndpoint } from '../src/wss-endpoint.js';
18
18
  export type { OneBot11WssEndpointOptions } from '../src/wss-endpoint.js';
19
19
  export type { OneBot11WsSocket, OneBot11WsCreateOptions } from '../src/ws-types.js';
20
20
 
21
+ declare module '@zhin.js/core' {
22
+ interface AdapterEndpoints {
23
+ onebot11: import('../src/ws-endpoint.js').OneBot11WsEndpoint;
24
+ }
25
+ }
26
+
21
27
  export default defineAdapter<OneBot11AdapterConfig>({
22
28
  capabilities: ['inbound', 'outbound'],
23
29
  // OneBot file 参数原生消费 url / base64:// 媒体;无卡片交互面,交互段降级纯文本。
@@ -28,6 +34,7 @@ export default defineAdapter<OneBot11AdapterConfig>({
28
34
  create(context) {
29
35
  const config = resolveOneBot11Config(context.config);
30
36
  const gateway = context.use(messageGatewayToken);
37
+ const sideEvents = context.use(sideEventGatewayToken);
31
38
  // 注册到插件运行时状态(onebot11.endpoint list 的"运行中"数据源)
32
39
  context.use(onebot11RuntimeStateToken).endpoints.set(config.id, {
33
40
  id: config.id,
@@ -37,6 +44,7 @@ export default defineAdapter<OneBot11AdapterConfig>({
37
44
  return new OneBot11WssEndpoint({
38
45
  id: context.id,
39
46
  gateway,
47
+ sideEvents,
40
48
  http: context.use(httpHostToken),
41
49
  config,
42
50
  });
@@ -44,6 +52,7 @@ export default defineAdapter<OneBot11AdapterConfig>({
44
52
  return new OneBot11WsEndpoint({
45
53
  id: context.id,
46
54
  gateway,
55
+ sideEvents,
47
56
  config,
48
57
  });
49
58
  },
@@ -0,0 +1,4 @@
1
+ import type { EndpointContentPort } from 'zhin.js/adapter';
2
+ type CallApi = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
3
+ export declare function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort;
4
+ export {};
@@ -0,0 +1,63 @@
1
+ import { toCanonicalSegments } from '@zhin.js/core';
2
+ export function createOneBot11ContentPort(callApi) {
3
+ return Object.freeze({
4
+ async resolve(reference, context) {
5
+ try {
6
+ context.signal.throwIfAborted();
7
+ if (reference.kind === 'media') {
8
+ return reference.media.kind === 'file'
9
+ ? Object.freeze({ status: 'unsupported', code: 'onebot11_opaque_media' })
10
+ : Object.freeze({ status: 'resolved', reference, value: reference.media });
11
+ }
12
+ if (reference.kind === 'message') {
13
+ const row = await callApi('get_msg', { message_id: reference.message.id });
14
+ context.signal.throwIfAborted();
15
+ const segments = normalizeSegments(row.message ?? row.raw_message);
16
+ const sender = row.sender;
17
+ const value = Object.freeze({
18
+ ref: reference.message,
19
+ ...(sender?.user_id != null ? { actor: Object.freeze({
20
+ id: String(sender.user_id),
21
+ ...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
22
+ }) } : {}),
23
+ segments,
24
+ timestamp: toMillis(row.time),
25
+ });
26
+ return Object.freeze({ status: 'resolved', reference, value });
27
+ }
28
+ const raw = await callApi('get_forward_msg', { id: reference.forwardId });
29
+ context.signal.throwIfAborted();
30
+ const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
31
+ const value = Object.freeze(nodes.map((node) => {
32
+ const row = node;
33
+ const sender = row.sender;
34
+ return Object.freeze({
35
+ ...(sender?.user_id != null ? { actor: Object.freeze({
36
+ id: String(sender.user_id),
37
+ ...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
38
+ }) } : {}),
39
+ timestamp: toMillis(row.time),
40
+ segments: normalizeSegments(row.content ?? row.message),
41
+ });
42
+ }));
43
+ return Object.freeze({ status: 'resolved', reference, value, truncated: nodes.length >= context.maxEntries });
44
+ }
45
+ catch (error) {
46
+ if (context.signal.aborted)
47
+ return Object.freeze({ status: 'expired', code: 'turn_aborted' });
48
+ return Object.freeze({ status: 'failed', code: 'onebot11_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
49
+ }
50
+ },
51
+ });
52
+ }
53
+ function normalizeSegments(value) {
54
+ if (Array.isArray(value))
55
+ return Object.freeze(toCanonicalSegments(value));
56
+ return Object.freeze([{ type: 'text', data: Object.freeze({ text: String(value ?? '') }) }]);
57
+ }
58
+ function toMillis(value) {
59
+ const time = Number(value);
60
+ if (!Number.isFinite(time))
61
+ return Date.now();
62
+ return time < 10_000_000_000 ? time * 1000 : time;
63
+ }
package/lib/protocol.js CHANGED
@@ -85,14 +85,14 @@ export function onebot11InboundConversation(endpointKey, ev) {
85
85
  }
86
86
  /** Build inbound text for MessageGateway.receive */
87
87
  export function formatInboundContent(ev) {
88
- if (typeof ev.raw_message === 'string' && ev.raw_message)
89
- return ev.raw_message;
90
88
  if (Array.isArray(ev.message)) {
91
89
  return ev.message
92
90
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
93
91
  .join('');
94
92
  }
95
- return '';
93
+ return typeof ev.raw_message === 'string'
94
+ ? ev.raw_message.replace(/\[CQ:[^\]]+\]/g, '').trim()
95
+ : '';
96
96
  }
97
97
  export function senderDisplayName(ev) {
98
98
  const name = ev.sender?.card || ev.sender?.nickname;
@@ -0,0 +1,7 @@
1
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import { type getAdapterLogger } from '@zhin.js/logger';
3
+ import type { OneBot11Event } from './protocol.js';
4
+ export interface OneBot11SideEventCaller {
5
+ callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
6
+ }
7
+ export declare function receiveOneBot11SideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, caller: OneBot11SideEventCaller, raw: OneBot11Event, logger: ReturnType<typeof getAdapterLogger>): void;
@@ -0,0 +1,36 @@
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
2
+ import { formatCompact } from '@zhin.js/logger';
3
+ export function receiveOneBot11SideEvent(sideEvents, endpointKey, caller, raw, logger) {
4
+ if (!sideEvents)
5
+ return;
6
+ const record = raw;
7
+ const postType = String(record.post_type ?? '');
8
+ const requestType = String(record.request_type ?? '');
9
+ const isRequest = postType === 'request' || postType.startsWith('request.');
10
+ const isFriend = requestType === 'friend' || postType.includes('friend');
11
+ void receiveOneBotLikeSideEvent(sideEvents, {
12
+ adapter: 'onebot11',
13
+ endpointKey,
14
+ platform: 'onebot',
15
+ raw: record,
16
+ ...(isRequest ? {
17
+ approve: async (flag, remark) => {
18
+ await (isFriend
19
+ ? caller.callApi('set_friend_add_request', { flag, approve: true, remark })
20
+ : caller.callApi('set_group_add_request', { flag, approve: true, reason: remark }));
21
+ },
22
+ reject: async (flag, reason) => {
23
+ await (isFriend
24
+ ? caller.callApi('set_friend_add_request', { flag, approve: false })
25
+ : caller.callApi('set_group_add_request', { flag, approve: false, reason }));
26
+ },
27
+ } : {}),
28
+ }).catch((err) => {
29
+ logger.warn(formatCompact({
30
+ op: 'onebot11_side_event_failed',
31
+ endpoint: endpointKey,
32
+ post_type: postType || undefined,
33
+ error: err instanceof Error ? err.message : String(err),
34
+ }));
35
+ });
36
+ }
@@ -1,17 +1,19 @@
1
1
  import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
2
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
3
3
  import type { CapabilityId } from 'zhin.js';
4
4
  import { type OneBot11Event, type OneBot11WsConfig } from './protocol.js';
5
5
  import { type OneBot11WsCreateOptions, type OneBot11WsSocket } from './ws-types.js';
6
6
  export interface OneBot11WsEndpointOptions {
7
7
  readonly id: CapabilityId;
8
8
  readonly gateway: MessageGateway;
9
+ readonly sideEvents?: SideEventGateway;
9
10
  readonly config: OneBot11WsConfig;
10
11
  readonly createWebSocket?: (url: string, options: OneBot11WsCreateOptions) => OneBot11WsSocket;
11
12
  }
12
13
  export declare class OneBot11WsEndpoint implements EndpointInstance {
13
14
  #private;
14
15
  readonly management: EndpointManagement;
16
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
17
  constructor(options: OneBot11WsEndpointOptions);
16
18
  start(): Promise<void>;
17
19
  open(): void;
@@ -7,11 +7,14 @@ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
7
  import { createOneBot11EndpointManagement } from './endpoint-management.js';
8
8
  import { registerOnebot11AgentEndpoint } from './onebot11-agent-deps.js';
9
9
  import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
10
+ import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
11
+ import { createOneBot11ContentPort } from './content-port.js';
10
12
  import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, } from './ws-transport.js';
11
13
  export class OneBot11WsEndpoint {
12
14
  #logger;
13
15
  #options;
14
16
  management = createOneBot11EndpointManagement(this);
17
+ content = createOneBot11ContentPort((action, params) => this.callApi(action, params));
15
18
  #lifecycle;
16
19
  #ws;
17
20
  #requestId = { value: 0 };
@@ -106,8 +109,12 @@ export class OneBot11WsEndpoint {
106
109
  }
107
110
  /** Test / internal: admit a parsed event when the endpoint is open. */
108
111
  admit(ev) {
109
- if (!this.#open || !isMessageEvent(ev))
112
+ if (!this.#open)
110
113
  return;
114
+ if (!isMessageEvent(ev)) {
115
+ receiveOneBot11SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
116
+ return;
117
+ }
111
118
  const conversation = onebot11InboundConversation(String(this.#options.id), ev);
112
119
  const content = formatInboundContent(ev);
113
120
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
@@ -1,17 +1,19 @@
1
1
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
2
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from 'zhin.js';
5
5
  import { type OneBot11Event, type OneBot11WssConfig } from './protocol.js';
6
6
  export interface OneBot11WssEndpointOptions {
7
7
  readonly id: CapabilityId;
8
8
  readonly gateway: MessageGateway;
9
+ readonly sideEvents?: SideEventGateway;
9
10
  readonly http: HttpHost;
10
11
  readonly config: OneBot11WssConfig;
11
12
  }
12
13
  export declare class OneBot11WssEndpoint implements EndpointInstance {
13
14
  #private;
14
15
  readonly management: EndpointManagement;
16
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
17
  constructor(options: OneBot11WssEndpointOptions);
16
18
  start(): Promise<void>;
17
19
  open(): void;
@@ -6,12 +6,15 @@ 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
8
  import { buildSendAction, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
9
+ import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
10
+ import { createOneBot11ContentPort } from './content-port.js';
9
11
  import { verifyOneBotAccessToken } from './wss-auth.js';
10
12
  import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, startOneBot11Heartbeat, } from './ws-transport.js';
11
13
  export class OneBot11WssEndpoint {
12
14
  #logger;
13
15
  #options;
14
16
  management = createOneBot11EndpointManagement(this);
17
+ content = createOneBot11ContentPort((action, params) => this.callApi(action, params));
15
18
  #ws;
16
19
  #wsRelease;
17
20
  #heartbeatTimer;
@@ -101,8 +104,12 @@ export class OneBot11WssEndpoint {
101
104
  return true;
102
105
  }
103
106
  admit(ev) {
104
- if (!this.#open || !isMessageEvent(ev))
107
+ if (!this.#open)
105
108
  return;
109
+ if (!isMessageEvent(ev)) {
110
+ receiveOneBot11SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
111
+ return;
112
+ }
106
113
  const conversation = onebot11InboundConversation(String(this.#options.id), ev);
107
114
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
108
115
  void this.#options.gateway.receive({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot11",
3
- "version": "6.0.12",
3
+ "version": "6.0.14",
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,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "ws": "^8.21.1",
44
- "@zhin.js/adapter": "1.1.10",
45
- "@zhin.js/core": "1.5.10",
46
- "@zhin.js/host-http": "1.0.10",
47
- "@zhin.js/im-contract": "1.0.3",
44
+ "@zhin.js/adapter": "1.1.11",
45
+ "@zhin.js/core": "1.5.12",
46
+ "@zhin.js/host-http": "1.0.11",
47
+ "@zhin.js/im-contract": "1.0.4",
48
48
  "@zhin.js/logger": "1.0.76",
49
- "@zhin.js/permission": "1.0.2"
49
+ "@zhin.js/permission": "1.0.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^26.1.2",
@@ -54,17 +54,17 @@
54
54
  "typescript": "^6.0.3",
55
55
  "vitest": "^4.1.10",
56
56
  "zod": "^4.4.3",
57
- "@zhin.js/agent": "1.1.12",
58
- "@zhin.js/host-http": "1.0.10",
59
- "zhin.js": "6.0.10"
57
+ "@zhin.js/agent": "1.1.14",
58
+ "@zhin.js/host-http": "1.0.11",
59
+ "zhin.js": "6.0.12"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "zod": "^4.0.0",
63
- "@zhin.js/adapter": "1.1.10",
64
- "@zhin.js/agent": "1.1.12",
65
- "@zhin.js/command": "1.0.14",
66
- "@zhin.js/core": "1.5.10",
67
- "zhin.js": "6.0.10"
63
+ "@zhin.js/adapter": "1.1.11",
64
+ "@zhin.js/agent": "1.1.14",
65
+ "@zhin.js/command": "1.0.15",
66
+ "@zhin.js/core": "1.5.12",
67
+ "zhin.js": "6.0.12"
68
68
  },
69
69
  "peerDependenciesMeta": {
70
70
  "@zhin.js/agent": {
@@ -0,0 +1,66 @@
1
+ import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
2
+ import { toCanonicalSegments } from '@zhin.js/core';
3
+ import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
4
+
5
+ type CallApi = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
6
+
7
+ export function createOneBot11ContentPort(callApi: CallApi): EndpointContentPort {
8
+ return Object.freeze({
9
+ async resolve(reference: ConversationReference, context: EndpointContentResolveContext) {
10
+ try {
11
+ context.signal.throwIfAborted();
12
+ if (reference.kind === 'media') {
13
+ return reference.media.kind === 'file'
14
+ ? Object.freeze({ status: 'unsupported' as const, code: 'onebot11_opaque_media' })
15
+ : Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
16
+ }
17
+ if (reference.kind === 'message') {
18
+ const row = await callApi('get_msg', { message_id: reference.message.id }) as Record<string, unknown>;
19
+ context.signal.throwIfAborted();
20
+ const segments = normalizeSegments(row.message ?? row.raw_message);
21
+ const sender = row.sender as Record<string, unknown> | undefined;
22
+ const value: ConversationMessage = Object.freeze({
23
+ ref: reference.message,
24
+ ...(sender?.user_id != null ? { actor: Object.freeze({
25
+ id: String(sender.user_id),
26
+ ...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
27
+ }) } : {}),
28
+ segments,
29
+ timestamp: toMillis(row.time),
30
+ });
31
+ return Object.freeze({ status: 'resolved' as const, reference, value });
32
+ }
33
+ const raw = await callApi('get_forward_msg', { id: reference.forwardId }) as Record<string, unknown>;
34
+ context.signal.throwIfAborted();
35
+ const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
36
+ const value: readonly ForwardEntry[] = Object.freeze(nodes.map((node) => {
37
+ const row = node as Record<string, unknown>;
38
+ const sender = row.sender as Record<string, unknown> | undefined;
39
+ return Object.freeze({
40
+ ...(sender?.user_id != null ? { actor: Object.freeze({
41
+ id: String(sender.user_id),
42
+ ...(sender.nickname ? { displayName: String(sender.nickname) } : {}),
43
+ }) } : {}),
44
+ timestamp: toMillis(row.time),
45
+ segments: normalizeSegments(row.content ?? row.message),
46
+ });
47
+ }));
48
+ return Object.freeze({ status: 'resolved' as const, reference, value, truncated: nodes.length >= context.maxEntries });
49
+ } catch (error) {
50
+ if (context.signal.aborted) return Object.freeze({ status: 'expired' as const, code: 'turn_aborted' });
51
+ return Object.freeze({ status: 'failed' as const, code: 'onebot11_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
52
+ }
53
+ },
54
+ });
55
+ }
56
+
57
+ function normalizeSegments(value: unknown) {
58
+ if (Array.isArray(value)) return Object.freeze(toCanonicalSegments(value as never));
59
+ return Object.freeze([{ type: 'text', data: Object.freeze({ text: String(value ?? '') }) }]);
60
+ }
61
+
62
+ function toMillis(value: unknown): number {
63
+ const time = Number(value);
64
+ if (!Number.isFinite(time)) return Date.now();
65
+ return time < 10_000_000_000 ? time * 1000 : time;
66
+ }
package/src/protocol.ts CHANGED
@@ -198,13 +198,14 @@ export function onebot11InboundConversation(endpointKey: string, ev: OneBot11Eve
198
198
 
199
199
  /** Build inbound text for MessageGateway.receive */
200
200
  export function formatInboundContent(ev: OneBot11Event): string {
201
- if (typeof ev.raw_message === 'string' && ev.raw_message) return ev.raw_message;
202
201
  if (Array.isArray(ev.message)) {
203
202
  return ev.message
204
203
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
205
204
  .join('');
206
205
  }
207
- return '';
206
+ return typeof ev.raw_message === 'string'
207
+ ? ev.raw_message.replace(/\[CQ:[^\]]+\]/g, '').trim()
208
+ : '';
208
209
  }
209
210
 
210
211
  export function senderDisplayName(ev: OneBot11Event): string {
@@ -0,0 +1,48 @@
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
2
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
3
+ import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
+ import type { OneBot11Event } from './protocol.js';
5
+
6
+ export interface OneBot11SideEventCaller {
7
+ callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
8
+ }
9
+
10
+ export function receiveOneBot11SideEvent(
11
+ sideEvents: SideEventGateway | undefined,
12
+ endpointKey: string,
13
+ caller: OneBot11SideEventCaller,
14
+ raw: OneBot11Event,
15
+ logger: ReturnType<typeof getAdapterLogger>,
16
+ ): void {
17
+ if (!sideEvents) return;
18
+ const record = raw as Record<string, unknown>;
19
+ const postType = String(record.post_type ?? '');
20
+ const requestType = String(record.request_type ?? '');
21
+ const isRequest = postType === 'request' || postType.startsWith('request.');
22
+ const isFriend = requestType === 'friend' || postType.includes('friend');
23
+ void receiveOneBotLikeSideEvent(sideEvents, {
24
+ adapter: 'onebot11',
25
+ endpointKey,
26
+ platform: 'onebot',
27
+ raw: record,
28
+ ...(isRequest ? {
29
+ approve: async (flag, remark) => {
30
+ await (isFriend
31
+ ? caller.callApi('set_friend_add_request', { flag, approve: true, remark })
32
+ : caller.callApi('set_group_add_request', { flag, approve: true, reason: remark }));
33
+ },
34
+ reject: async (flag, reason) => {
35
+ await (isFriend
36
+ ? caller.callApi('set_friend_add_request', { flag, approve: false })
37
+ : caller.callApi('set_group_add_request', { flag, approve: false, reason }));
38
+ },
39
+ } : {}),
40
+ }).catch((err) => {
41
+ logger.warn(formatCompact({
42
+ op: 'onebot11_side_event_failed',
43
+ endpoint: endpointKey,
44
+ post_type: postType || undefined,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ }));
47
+ });
48
+ }
@@ -10,7 +10,7 @@ import {
10
10
  type EndpointManagement,
11
11
  type EndpointSendRequest,
12
12
  } from 'zhin.js/adapter';
13
- import type { MessageGateway } from '@zhin.js/core/runtime';
13
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
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';
@@ -28,6 +28,8 @@ import {
28
28
  type OneBot11Event,
29
29
  type OneBot11WsConfig,
30
30
  } from './protocol.js';
31
+ import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
32
+ import { createOneBot11ContentPort } from './content-port.js';
31
33
  import {
32
34
  callOneBot11WsAction,
33
35
  handleOneBot11WsMessage,
@@ -42,6 +44,7 @@ import {
42
44
  export interface OneBot11WsEndpointOptions {
43
45
  readonly id: CapabilityId;
44
46
  readonly gateway: MessageGateway;
47
+ readonly sideEvents?: SideEventGateway;
45
48
  readonly config: OneBot11WsConfig;
46
49
  readonly createWebSocket?: (
47
50
  url: string,
@@ -54,6 +57,7 @@ export class OneBot11WsEndpoint implements EndpointInstance {
54
57
 
55
58
  readonly #options: OneBot11WsEndpointOptions;
56
59
  readonly management: EndpointManagement = createOneBot11EndpointManagement(this);
60
+ readonly content = createOneBot11ContentPort((action, params) => this.callApi(action, params));
57
61
  readonly #lifecycle: EndpointLifecycle;
58
62
  #ws?: OneBot11WsSocket;
59
63
  #requestId = { value: 0 };
@@ -159,7 +163,17 @@ export class OneBot11WsEndpoint implements EndpointInstance {
159
163
 
160
164
  /** Test / internal: admit a parsed event when the endpoint is open. */
161
165
  admit(ev: OneBot11Event): void {
162
- if (!this.#open || !isMessageEvent(ev)) return;
166
+ if (!this.#open) return;
167
+ if (!isMessageEvent(ev)) {
168
+ receiveOneBot11SideEvent(
169
+ this.#options.sideEvents,
170
+ this.#options.config.id,
171
+ this,
172
+ ev,
173
+ this.#logger,
174
+ );
175
+ return;
176
+ }
163
177
  const conversation = onebot11InboundConversation(String(this.#options.id), ev);
164
178
  const content = formatInboundContent(ev);
165
179
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
5
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
6
- import type { MessageGateway } from '@zhin.js/core/runtime';
6
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
8
8
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
9
  import type { CapabilityId } from 'zhin.js';
@@ -21,6 +21,8 @@ import {
21
21
  type OneBot11Event,
22
22
  type OneBot11WssConfig,
23
23
  } from './protocol.js';
24
+ import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
25
+ import { createOneBot11ContentPort } from './content-port.js';
24
26
  import { verifyOneBotAccessToken } from './wss-auth.js';
25
27
  import {
26
28
  callOneBot11WsAction,
@@ -37,6 +39,7 @@ import {
37
39
  export interface OneBot11WssEndpointOptions {
38
40
  readonly id: CapabilityId;
39
41
  readonly gateway: MessageGateway;
42
+ readonly sideEvents?: SideEventGateway;
40
43
  readonly http: HttpHost;
41
44
  readonly config: OneBot11WssConfig;
42
45
  }
@@ -46,6 +49,7 @@ export class OneBot11WssEndpoint implements EndpointInstance {
46
49
 
47
50
  readonly #options: OneBot11WssEndpointOptions;
48
51
  readonly management: EndpointManagement = createOneBot11EndpointManagement(this);
52
+ readonly content = createOneBot11ContentPort((action, params) => this.callApi(action, params));
49
53
  #ws?: OneBot11WsSocket;
50
54
  #wsRelease?: () => void;
51
55
  #heartbeatTimer?: NodeJS.Timeout;
@@ -145,7 +149,17 @@ export class OneBot11WssEndpoint implements EndpointInstance {
145
149
  }
146
150
 
147
151
  admit(ev: OneBot11Event): void {
148
- if (!this.#open || !isMessageEvent(ev)) return;
152
+ if (!this.#open) return;
153
+ if (!isMessageEvent(ev)) {
154
+ receiveOneBot11SideEvent(
155
+ this.#options.sideEvents,
156
+ this.#options.config.id,
157
+ this,
158
+ ev,
159
+ this.#logger,
160
+ );
161
+ return;
162
+ }
149
163
  const conversation = onebot11InboundConversation(String(this.#options.id), ev);
150
164
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
151
165
  void this.#options.gateway.receive({