@zhin.js/adapter-onebot12 5.0.12 → 5.0.13

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,27 @@
1
1
  # @zhin.js/adapter-onebot12
2
2
 
3
+ ## 5.0.13
4
+
5
+ ### Patch Changes
6
+
7
+ - b2ef6b1: Add canonical scoped message resolution and explicit unsupported results for protocol capabilities OneBot 12 does not standardize.
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 [5969c5b]
12
+ - Updated dependencies [5969c5b]
13
+ - Updated dependencies [974772e]
14
+ - Updated dependencies [5969c5b]
15
+ - Updated dependencies [2f786bd]
16
+ - Updated dependencies [1312ca0]
17
+ - Updated dependencies [985fa22]
18
+ - @zhin.js/im-contract@1.0.4
19
+ - @zhin.js/core@1.5.12
20
+ - @zhin.js/adapter@1.1.11
21
+ - @zhin.js/host-http@1.0.11
22
+ - @zhin.js/command@1.0.15
23
+ - zhin.js@6.0.12
24
+
3
25
  ## 5.0.12
4
26
 
5
27
  ### Patch Changes
@@ -3,7 +3,7 @@
3
3
  * Convention entry: discover `adapters/onebot12.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 { OneBot12WebhookEndpoint } from "../lib/webhook.js";
9
9
  import { OneBot12WsEndpoint } from "../lib/ws-endpoint.js";
@@ -24,6 +24,7 @@ export default defineAdapter({
24
24
  create(context) {
25
25
  const config = resolveOneBot12Config(context.config);
26
26
  const gateway = context.use(messageGatewayToken);
27
+ const sideEvents = context.use(sideEventGatewayToken);
27
28
  // 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
28
29
  context.use(onebot12RuntimeStateToken).endpoints.set(config.id, {
29
30
  id: config.id,
@@ -33,6 +34,7 @@ export default defineAdapter({
33
34
  return new OneBot12WebhookEndpoint({
34
35
  id: context.id,
35
36
  gateway,
37
+ sideEvents,
36
38
  http: context.use(httpHostToken),
37
39
  config,
38
40
  });
@@ -41,6 +43,7 @@ export default defineAdapter({
41
43
  return new OneBot12WssEndpoint({
42
44
  id: context.id,
43
45
  gateway,
46
+ sideEvents,
44
47
  http: context.use(httpHostToken),
45
48
  config,
46
49
  });
@@ -48,6 +51,7 @@ export default defineAdapter({
48
51
  return new OneBot12WsEndpoint({
49
52
  id: context.id,
50
53
  gateway,
54
+ sideEvents,
51
55
  config,
52
56
  });
53
57
  },
@@ -2,7 +2,7 @@
2
2
  * Convention entry: discover `adapters/onebot12.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 { OneBot12WebhookEndpoint } from '../src/webhook.js';
8
8
  import { OneBot12WsEndpoint } from '../src/ws-endpoint.js';
@@ -21,6 +21,12 @@ export { OneBot12WssEndpoint } from '../src/wss-endpoint.js';
21
21
  export type { OneBot12WssEndpointOptions } from '../src/wss-endpoint.js';
22
22
  export type { OneBot12WsSocket, OneBot12WsCreateOptions } from '../src/ws-types.js';
23
23
 
24
+ declare module '@zhin.js/core' {
25
+ interface AdapterEndpoints {
26
+ onebot12: import('../src/ws-endpoint.js').OneBot12WsEndpoint;
27
+ }
28
+ }
29
+
24
30
  export default defineAdapter<OneBot12AdapterConfig>({
25
31
  capabilities: ['inbound', 'outbound'],
26
32
  // 媒体段经 upload_file 物化为 file_id(spec 正式投递);上传失败降级扩展字段透传。
@@ -32,6 +38,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
32
38
  create(context) {
33
39
  const config = resolveOneBot12Config(context.config);
34
40
  const gateway = context.use(messageGatewayToken);
41
+ const sideEvents = context.use(sideEventGatewayToken);
35
42
  // 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
36
43
  context.use(onebot12RuntimeStateToken).endpoints.set(config.id, {
37
44
  id: config.id,
@@ -41,6 +48,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
41
48
  return new OneBot12WebhookEndpoint({
42
49
  id: context.id,
43
50
  gateway,
51
+ sideEvents,
44
52
  http: context.use(httpHostToken),
45
53
  config,
46
54
  });
@@ -49,6 +57,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
49
57
  return new OneBot12WssEndpoint({
50
58
  id: context.id,
51
59
  gateway,
60
+ sideEvents,
52
61
  http: context.use(httpHostToken),
53
62
  config,
54
63
  });
@@ -56,6 +65,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
56
65
  return new OneBot12WsEndpoint({
57
66
  id: context.id,
58
67
  gateway,
68
+ sideEvents,
59
69
  config,
60
70
  });
61
71
  },
@@ -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 createOneBot12ContentPort(callApi: CallApi): EndpointContentPort;
4
+ export {};
@@ -0,0 +1,45 @@
1
+ export function createOneBot12ContentPort(callApi) {
2
+ return Object.freeze({
3
+ async resolve(reference, context) {
4
+ try {
5
+ context.signal.throwIfAborted();
6
+ if (reference.kind === 'forward') {
7
+ return Object.freeze({ status: 'unsupported', code: 'onebot12_forward_lookup_not_standardized' });
8
+ }
9
+ if (reference.kind === 'media') {
10
+ return reference.media.kind === 'file'
11
+ ? Object.freeze({ status: 'unsupported', code: 'onebot12_opaque_media' })
12
+ : Object.freeze({ status: 'resolved', reference, value: reference.media });
13
+ }
14
+ const row = await callApi('get_message', { message_id: reference.message.id });
15
+ context.signal.throwIfAborted();
16
+ const message = (row.message && typeof row.message === 'object' ? row.message : row);
17
+ const segments = normalizeSegments(message.message ?? message.content);
18
+ const value = Object.freeze({
19
+ ref: reference.message,
20
+ ...(message.user_id != null ? { actor: Object.freeze({ id: String(message.user_id) }) } : {}),
21
+ segments,
22
+ timestamp: toMillis(message.time),
23
+ });
24
+ return Object.freeze({ status: 'resolved', reference, value });
25
+ }
26
+ catch (error) {
27
+ if (context.signal.aborted)
28
+ return Object.freeze({ status: 'expired', code: 'turn_aborted' });
29
+ return Object.freeze({ status: 'failed', code: 'onebot12_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
30
+ }
31
+ },
32
+ });
33
+ }
34
+ function normalizeSegments(value) {
35
+ if (!Array.isArray(value))
36
+ return Object.freeze([{ type: 'text', data: Object.freeze({ text: String(value ?? '') }) }]);
37
+ return Object.freeze(value.map((item) => {
38
+ const row = item;
39
+ return Object.freeze({ type: String(row.type ?? 'text'), data: Object.freeze((row.data ?? {})) });
40
+ }));
41
+ }
42
+ function toMillis(value) {
43
+ const time = Number(value);
44
+ return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
45
+ }
package/lib/protocol.js CHANGED
@@ -105,14 +105,14 @@ export function onebot12InboundConversation(endpointKey, ev) {
105
105
  }
106
106
  /** Build inbound text for MessageGateway.receive */
107
107
  export function formatInboundContent(ev) {
108
- if (typeof ev.alt_message === 'string' && ev.alt_message)
109
- return ev.alt_message;
110
108
  if (Array.isArray(ev.message)) {
111
109
  return ev.message
112
110
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
113
111
  .join('');
114
112
  }
115
- return '';
113
+ return typeof ev.alt_message === 'string'
114
+ ? ev.alt_message.replace(/\[[^\]]*(?:image|audio|video|file)[^\]]*\]/gi, '').trim()
115
+ : '';
116
116
  }
117
117
  /**
118
118
  * Runtime Message sender 必须是用户 ID(agent bridge 以 sender 与 endpointMaster 比对)。
@@ -0,0 +1,7 @@
1
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import { type getAdapterLogger } from '@zhin.js/logger';
3
+ import type { OneBot12Event } from './protocol.js';
4
+ export interface OneBot12SideEventCaller {
5
+ callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
6
+ }
7
+ export declare function receiveOneBot12SideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, caller: OneBot12SideEventCaller, raw: OneBot12Event, 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 receiveOneBot12SideEvent(sideEvents, endpointKey, caller, raw, logger) {
4
+ if (!sideEvents)
5
+ return;
6
+ const record = raw;
7
+ const eventType = String(record.type ?? record.post_type ?? '');
8
+ const detailType = String(record.detail_type ?? '');
9
+ const isRequest = eventType === 'request' || eventType.startsWith('request.');
10
+ const isFriend = detailType.includes('friend') || String(record.request_type ?? '') === 'friend';
11
+ void receiveOneBotLikeSideEvent(sideEvents, {
12
+ adapter: 'onebot12',
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: 'onebot12_side_event_failed',
31
+ endpoint: endpointKey,
32
+ type: eventType || undefined,
33
+ error: err instanceof Error ? err.message : String(err),
34
+ }));
35
+ });
36
+ }
package/lib/webhook.d.ts CHANGED
@@ -1,11 +1,12 @@
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 { callOneBot12Action, type OneBot12Event, type OneBot12WebhookConfig } from './protocol.js';
6
6
  export interface OneBot12WebhookEndpointOptions {
7
7
  readonly id: CapabilityId;
8
8
  readonly gateway: MessageGateway;
9
+ readonly sideEvents?: SideEventGateway;
9
10
  readonly http: HttpHost;
10
11
  readonly config: OneBot12WebhookConfig;
11
12
  readonly callAction?: typeof callOneBot12Action;
@@ -13,6 +14,7 @@ export interface OneBot12WebhookEndpointOptions {
13
14
  export declare class OneBot12WebhookEndpoint implements EndpointInstance {
14
15
  #private;
15
16
  readonly management: EndpointManagement;
17
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
16
18
  constructor(options: OneBot12WebhookEndpointOptions);
17
19
  start(): Promise<void>;
18
20
  open(): void;
package/lib/webhook.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
3
3
  import { buildSendMessageParams, callOneBot12Action, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
4
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
5
+ import { createOneBot12ContentPort } from './content-port.js';
4
6
  import { verifyOneBotAccessToken } from './wss-auth.js';
5
7
  const logger = getLogger('onebot12');
6
8
  export class OneBot12WebhookEndpoint {
7
9
  #options;
8
10
  management = createOneBot12EndpointManagement(this);
11
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
9
12
  #callAction;
10
13
  #routeReleases = [];
11
14
  #open = false;
@@ -79,8 +82,12 @@ export class OneBot12WebhookEndpoint {
79
82
  return resp.data;
80
83
  }
81
84
  admit(ev) {
82
- if (!this.#open || !isMessageEvent(ev))
85
+ if (!this.#open)
83
86
  return;
87
+ if (!isMessageEvent(ev)) {
88
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, logger);
89
+ return;
90
+ }
84
91
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
85
92
  const content = formatInboundContent(ev);
86
93
  const nickname = senderNickname(ev);
@@ -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 OneBot12Event, type OneBot12WsConfig } from './protocol.js';
5
5
  import { type OneBot12WsCreateOptions, type OneBot12WsSocket } from './ws-types.js';
6
6
  export interface OneBot12WsEndpointOptions {
7
7
  readonly id: CapabilityId;
8
8
  readonly gateway: MessageGateway;
9
+ readonly sideEvents?: SideEventGateway;
9
10
  readonly config: OneBot12WsConfig;
10
11
  readonly createWebSocket?: (url: string, options: OneBot12WsCreateOptions) => OneBot12WsSocket;
11
12
  }
12
13
  export declare class OneBot12WsEndpoint implements EndpointInstance {
13
14
  #private;
14
15
  readonly management: EndpointManagement;
16
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
17
  constructor(options: OneBot12WsEndpointOptions);
16
18
  start(): Promise<void>;
17
19
  open(): void;
@@ -7,11 +7,14 @@ import { createEndpointLifecycle, } from 'zhin.js/adapter';
7
7
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
8
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
9
9
  import { buildSendMessageParams, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
10
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
11
+ import { createOneBot12ContentPort } from './content-port.js';
10
12
  import { WS_OPEN, } from './ws-types.js';
11
13
  export class OneBot12WsEndpoint {
12
14
  #logger;
13
15
  #options;
14
16
  management = createOneBot12EndpointManagement(this);
17
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
15
18
  #lifecycle;
16
19
  #ws;
17
20
  #requestId = 0;
@@ -102,8 +105,12 @@ export class OneBot12WsEndpoint {
102
105
  }
103
106
  /** Test / internal: admit a parsed event when the endpoint is open. */
104
107
  admit(ev) {
105
- if (!this.#open || !isMessageEvent(ev))
108
+ if (!this.#open)
106
109
  return;
110
+ if (!isMessageEvent(ev)) {
111
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
112
+ return;
113
+ }
107
114
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
108
115
  const content = formatInboundContent(ev);
109
116
  const nickname = senderNickname(ev);
@@ -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 OneBot12Event, type OneBot12WssConfig } from './protocol.js';
6
6
  export interface OneBot12WssEndpointOptions {
7
7
  readonly id: CapabilityId;
8
8
  readonly gateway: MessageGateway;
9
+ readonly sideEvents?: SideEventGateway;
9
10
  readonly http: HttpHost;
10
11
  readonly config: OneBot12WssConfig;
11
12
  }
12
13
  export declare class OneBot12WssEndpoint implements EndpointInstance {
13
14
  #private;
14
15
  readonly management: EndpointManagement;
16
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
17
  constructor(options: OneBot12WssEndpointOptions);
16
18
  start(): Promise<void>;
17
19
  open(): void;
@@ -5,12 +5,15 @@ import { clearInterval } from 'node:timers';
5
5
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
6
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
7
7
  import { buildSendMessageParams, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
8
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
9
+ import { createOneBot12ContentPort } from './content-port.js';
8
10
  import { verifyOneBotAccessToken } from './wss-auth.js';
9
11
  import { WS_OPEN } from './ws-types.js';
10
12
  export class OneBot12WssEndpoint {
11
13
  #logger;
12
14
  #options;
13
15
  management = createOneBot12EndpointManagement(this);
16
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
14
17
  #ws;
15
18
  #wsRelease;
16
19
  #heartbeatTimer;
@@ -99,8 +102,12 @@ export class OneBot12WssEndpoint {
99
102
  return this.#callAction(action, params);
100
103
  }
101
104
  admit(ev) {
102
- if (!this.#open || !isMessageEvent(ev))
105
+ if (!this.#open)
103
106
  return;
107
+ if (!isMessageEvent(ev)) {
108
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
109
+ return;
110
+ }
104
111
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
105
112
  const nickname = senderNickname(ev);
106
113
  const mentioned = isBotMentioned(ev);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot12",
3
- "version": "5.0.12",
3
+ "version": "5.0.13",
4
4
  "description": "Zhin.js OneBot 12 adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -40,10 +40,10 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "ws": "^8.21.1",
43
- "@zhin.js/adapter": "1.1.10",
44
- "@zhin.js/core": "1.5.11",
45
- "@zhin.js/host-http": "1.0.10",
46
- "@zhin.js/im-contract": "1.0.3",
43
+ "@zhin.js/adapter": "1.1.11",
44
+ "@zhin.js/core": "1.5.12",
45
+ "@zhin.js/host-http": "1.0.11",
46
+ "@zhin.js/im-contract": "1.0.4",
47
47
  "@zhin.js/logger": "1.0.76"
48
48
  },
49
49
  "devDependencies": {
@@ -51,14 +51,14 @@
51
51
  "@types/ws": "^8.18.1",
52
52
  "typescript": "^6.0.3",
53
53
  "vitest": "^4.1.10",
54
- "@zhin.js/host-http": "1.0.10",
55
- "zhin.js": "6.0.11"
54
+ "@zhin.js/host-http": "1.0.11",
55
+ "zhin.js": "6.0.12"
56
56
  },
57
57
  "peerDependencies": {
58
- "@zhin.js/adapter": "1.1.10",
59
- "@zhin.js/command": "1.0.14",
60
- "@zhin.js/core": "1.5.11",
61
- "zhin.js": "6.0.11"
58
+ "@zhin.js/adapter": "1.1.11",
59
+ "@zhin.js/command": "1.0.15",
60
+ "@zhin.js/core": "1.5.12",
61
+ "zhin.js": "6.0.12"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "@zhin.js/command": {
@@ -0,0 +1,49 @@
1
+ import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
2
+ import type { ConversationMessage, ConversationReference } from '@zhin.js/im-contract';
3
+
4
+ type CallApi = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
5
+
6
+ export function createOneBot12ContentPort(callApi: CallApi): EndpointContentPort {
7
+ return Object.freeze({
8
+ async resolve(reference: ConversationReference, context: EndpointContentResolveContext) {
9
+ try {
10
+ context.signal.throwIfAborted();
11
+ if (reference.kind === 'forward') {
12
+ return Object.freeze({ status: 'unsupported' as const, code: 'onebot12_forward_lookup_not_standardized' });
13
+ }
14
+ if (reference.kind === 'media') {
15
+ return reference.media.kind === 'file'
16
+ ? Object.freeze({ status: 'unsupported' as const, code: 'onebot12_opaque_media' })
17
+ : Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
18
+ }
19
+ const row = await callApi('get_message', { message_id: reference.message.id }) as Record<string, unknown>;
20
+ context.signal.throwIfAborted();
21
+ const message = (row.message && typeof row.message === 'object' ? row.message : row) as Record<string, unknown>;
22
+ const segments = normalizeSegments(message.message ?? message.content);
23
+ const value: ConversationMessage = Object.freeze({
24
+ ref: reference.message,
25
+ ...(message.user_id != null ? { actor: Object.freeze({ id: String(message.user_id) }) } : {}),
26
+ segments,
27
+ timestamp: toMillis(message.time),
28
+ });
29
+ return Object.freeze({ status: 'resolved' as const, reference, value });
30
+ } catch (error) {
31
+ if (context.signal.aborted) return Object.freeze({ status: 'expired' as const, code: 'turn_aborted' });
32
+ return Object.freeze({ status: 'failed' as const, code: 'onebot12_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
33
+ }
34
+ },
35
+ });
36
+ }
37
+
38
+ function normalizeSegments(value: unknown) {
39
+ if (!Array.isArray(value)) return Object.freeze([{ type: 'text', data: Object.freeze({ text: String(value ?? '') }) }]);
40
+ return Object.freeze(value.map((item) => {
41
+ const row = item as Record<string, unknown>;
42
+ return Object.freeze({ type: String(row.type ?? 'text'), data: Object.freeze((row.data ?? {}) as Record<string, unknown>) });
43
+ }));
44
+ }
45
+
46
+ function toMillis(value: unknown): number {
47
+ const time = Number(value);
48
+ return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
49
+ }
package/src/protocol.ts CHANGED
@@ -228,13 +228,14 @@ export function onebot12InboundConversation(endpointKey: string, ev: OneBot12Eve
228
228
 
229
229
  /** Build inbound text for MessageGateway.receive */
230
230
  export function formatInboundContent(ev: OneBot12Event): string {
231
- if (typeof ev.alt_message === 'string' && ev.alt_message) return ev.alt_message;
232
231
  if (Array.isArray(ev.message)) {
233
232
  return ev.message
234
233
  .map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
235
234
  .join('');
236
235
  }
237
- return '';
236
+ return typeof ev.alt_message === 'string'
237
+ ? ev.alt_message.replace(/\[[^\]]*(?:image|audio|video|file)[^\]]*\]/gi, '').trim()
238
+ : '';
238
239
  }
239
240
 
240
241
  /**
@@ -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 { OneBot12Event } from './protocol.js';
5
+
6
+ export interface OneBot12SideEventCaller {
7
+ callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
8
+ }
9
+
10
+ export function receiveOneBot12SideEvent(
11
+ sideEvents: SideEventGateway | undefined,
12
+ endpointKey: string,
13
+ caller: OneBot12SideEventCaller,
14
+ raw: OneBot12Event,
15
+ logger: ReturnType<typeof getAdapterLogger>,
16
+ ): void {
17
+ if (!sideEvents) return;
18
+ const record = raw as Record<string, unknown>;
19
+ const eventType = String(record.type ?? record.post_type ?? '');
20
+ const detailType = String(record.detail_type ?? '');
21
+ const isRequest = eventType === 'request' || eventType.startsWith('request.');
22
+ const isFriend = detailType.includes('friend') || String(record.request_type ?? '') === 'friend';
23
+ void receiveOneBotLikeSideEvent(sideEvents, {
24
+ adapter: 'onebot12',
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: 'onebot12_side_event_failed',
43
+ endpoint: endpointKey,
44
+ type: eventType || undefined,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ }));
47
+ });
48
+ }
package/src/webhook.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
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, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
9
9
  import type { CapabilityId } from 'zhin.js';
@@ -22,6 +22,8 @@ import {
22
22
  type OneBot12Event,
23
23
  type OneBot12WebhookConfig,
24
24
  } from './protocol.js';
25
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
26
+ import { createOneBot12ContentPort } from './content-port.js';
25
27
  import { verifyOneBotAccessToken } from './wss-auth.js';
26
28
 
27
29
  const logger = getLogger('onebot12');
@@ -29,6 +31,7 @@ const logger = getLogger('onebot12');
29
31
  export interface OneBot12WebhookEndpointOptions {
30
32
  readonly id: CapabilityId;
31
33
  readonly gateway: MessageGateway;
34
+ readonly sideEvents?: SideEventGateway;
32
35
  readonly http: HttpHost;
33
36
  readonly config: OneBot12WebhookConfig;
34
37
  readonly callAction?: typeof callOneBot12Action;
@@ -37,6 +40,7 @@ export interface OneBot12WebhookEndpointOptions {
37
40
  export class OneBot12WebhookEndpoint implements EndpointInstance {
38
41
  readonly #options: OneBot12WebhookEndpointOptions;
39
42
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
43
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
40
44
  readonly #callAction: typeof callOneBot12Action;
41
45
  #routeReleases: HttpRouteRegistration[] = [];
42
46
  #open = false;
@@ -124,7 +128,17 @@ export class OneBot12WebhookEndpoint implements EndpointInstance {
124
128
  }
125
129
 
126
130
  admit(ev: OneBot12Event): void {
127
- if (!this.#open || !isMessageEvent(ev)) return;
131
+ if (!this.#open) return;
132
+ if (!isMessageEvent(ev)) {
133
+ receiveOneBot12SideEvent(
134
+ this.#options.sideEvents,
135
+ this.#options.config.id,
136
+ this,
137
+ ev,
138
+ logger,
139
+ );
140
+ return;
141
+ }
128
142
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
129
143
  const content = formatInboundContent(ev);
130
144
  const nickname = senderNickname(ev);
@@ -11,7 +11,7 @@ import {
11
11
  type EndpointManagement,
12
12
  type EndpointSendRequest,
13
13
  } from 'zhin.js/adapter';
14
- import type { MessageGateway } from '@zhin.js/core/runtime';
14
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
15
15
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
16
16
  import type { CapabilityId } from 'zhin.js';
17
17
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
@@ -31,6 +31,8 @@ import {
31
31
  type OneBot12Event,
32
32
  type OneBot12WsConfig,
33
33
  } from './protocol.js';
34
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
35
+ import { createOneBot12ContentPort } from './content-port.js';
34
36
  import {
35
37
  type OneBot12WsCreateOptions,
36
38
  type OneBot12WsSocket,
@@ -40,6 +42,7 @@ import {
40
42
  export interface OneBot12WsEndpointOptions {
41
43
  readonly id: CapabilityId;
42
44
  readonly gateway: MessageGateway;
45
+ readonly sideEvents?: SideEventGateway;
43
46
  readonly config: OneBot12WsConfig;
44
47
  readonly createWebSocket?: (
45
48
  url: string,
@@ -52,6 +55,7 @@ export class OneBot12WsEndpoint implements EndpointInstance {
52
55
 
53
56
  readonly #options: OneBot12WsEndpointOptions;
54
57
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
58
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
55
59
  readonly #lifecycle: EndpointLifecycle;
56
60
  #ws?: OneBot12WsSocket;
57
61
  #requestId = 0;
@@ -155,7 +159,17 @@ export class OneBot12WsEndpoint implements EndpointInstance {
155
159
 
156
160
  /** Test / internal: admit a parsed event when the endpoint is open. */
157
161
  admit(ev: OneBot12Event): void {
158
- if (!this.#open || !isMessageEvent(ev)) return;
162
+ if (!this.#open) return;
163
+ if (!isMessageEvent(ev)) {
164
+ receiveOneBot12SideEvent(
165
+ this.#options.sideEvents,
166
+ this.#options.config.id,
167
+ this,
168
+ ev,
169
+ this.#logger,
170
+ );
171
+ return;
172
+ }
159
173
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
160
174
  const content = formatInboundContent(ev);
161
175
  const nickname = senderNickname(ev);
@@ -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';
@@ -23,12 +23,15 @@ import {
23
23
  type OneBot12Event,
24
24
  type OneBot12WssConfig,
25
25
  } from './protocol.js';
26
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
27
+ import { createOneBot12ContentPort } from './content-port.js';
26
28
  import { verifyOneBotAccessToken } from './wss-auth.js';
27
29
  import { type OneBot12WsSocket, WS_OPEN } from './ws-types.js';
28
30
 
29
31
  export interface OneBot12WssEndpointOptions {
30
32
  readonly id: CapabilityId;
31
33
  readonly gateway: MessageGateway;
34
+ readonly sideEvents?: SideEventGateway;
32
35
  readonly http: HttpHost;
33
36
  readonly config: OneBot12WssConfig;
34
37
  }
@@ -38,6 +41,7 @@ export class OneBot12WssEndpoint implements EndpointInstance {
38
41
 
39
42
  readonly #options: OneBot12WssEndpointOptions;
40
43
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
44
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
41
45
  #ws?: OneBot12WsSocket;
42
46
  #wsRelease?: () => void;
43
47
  #heartbeatTimer?: NodeJS.Timeout;
@@ -140,7 +144,17 @@ export class OneBot12WssEndpoint implements EndpointInstance {
140
144
  }
141
145
 
142
146
  admit(ev: OneBot12Event): void {
143
- if (!this.#open || !isMessageEvent(ev)) return;
147
+ if (!this.#open) return;
148
+ if (!isMessageEvent(ev)) {
149
+ receiveOneBot12SideEvent(
150
+ this.#options.sideEvents,
151
+ this.#options.config.id,
152
+ this,
153
+ ev,
154
+ this.#logger,
155
+ );
156
+ return;
157
+ }
144
158
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
145
159
  const nickname = senderNickname(ev);
146
160
  const mentioned = isBotMentioned(ev);