@zhin.js/adapter-onebot12 5.0.12 → 6.0.0

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,39 @@
1
1
  # @zhin.js/adapter-onebot12
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f2c532f: Expose exact per-Endpoint message operations through one validated Adapter capability model, route Core control calls through declared active capabilities, and connect existing recall, edit, reaction, and typing implementations across platform adapters.
8
+ - Updated dependencies [b10d058]
9
+ - Updated dependencies [f2c532f]
10
+ - @zhin.js/host-http@1.0.12
11
+ - @zhin.js/adapter@1.2.0
12
+ - @zhin.js/core@1.5.13
13
+ - zhin.js@6.0.13
14
+
15
+ ## 5.0.13
16
+
17
+ ### Patch Changes
18
+
19
+ - b2ef6b1: Add canonical scoped message resolution and explicit unsupported results for protocol capabilities OneBot 12 does not standardize.
20
+ - 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.
21
+ - Updated dependencies [5969c5b]
22
+ - Updated dependencies [d336a3f]
23
+ - Updated dependencies [5969c5b]
24
+ - Updated dependencies [5969c5b]
25
+ - Updated dependencies [974772e]
26
+ - Updated dependencies [5969c5b]
27
+ - Updated dependencies [2f786bd]
28
+ - Updated dependencies [1312ca0]
29
+ - Updated dependencies [985fa22]
30
+ - @zhin.js/im-contract@1.0.4
31
+ - @zhin.js/core@1.5.12
32
+ - @zhin.js/adapter@1.1.11
33
+ - @zhin.js/host-http@1.0.11
34
+ - @zhin.js/command@1.0.15
35
+ - zhin.js@6.0.12
36
+
3
37
  ## 5.0.12
4
38
 
5
39
  ### 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";
@@ -15,6 +15,7 @@ export { OneBot12WsEndpoint } from "../lib/ws-endpoint.js";
15
15
  export { OneBot12WssEndpoint } from "../lib/wss-endpoint.js";
16
16
  export default defineAdapter({
17
17
  capabilities: ['inbound', 'outbound'],
18
+ operations: ['recall'],
18
19
  // 媒体段经 upload_file 物化为 file_id(spec 正式投递);上传失败降级扩展字段透传。
19
20
  // 无卡片交互面,交互段降级纯文本。
20
21
  segments: {
@@ -24,6 +25,7 @@ export default defineAdapter({
24
25
  create(context) {
25
26
  const config = resolveOneBot12Config(context.config);
26
27
  const gateway = context.use(messageGatewayToken);
28
+ const sideEvents = context.use(sideEventGatewayToken);
27
29
  // 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
28
30
  context.use(onebot12RuntimeStateToken).endpoints.set(config.id, {
29
31
  id: config.id,
@@ -33,6 +35,7 @@ export default defineAdapter({
33
35
  return new OneBot12WebhookEndpoint({
34
36
  id: context.id,
35
37
  gateway,
38
+ sideEvents,
36
39
  http: context.use(httpHostToken),
37
40
  config,
38
41
  });
@@ -41,6 +44,7 @@ export default defineAdapter({
41
44
  return new OneBot12WssEndpoint({
42
45
  id: context.id,
43
46
  gateway,
47
+ sideEvents,
44
48
  http: context.use(httpHostToken),
45
49
  config,
46
50
  });
@@ -48,6 +52,7 @@ export default defineAdapter({
48
52
  return new OneBot12WsEndpoint({
49
53
  id: context.id,
50
54
  gateway,
55
+ sideEvents,
51
56
  config,
52
57
  });
53
58
  },
@@ -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,8 +21,15 @@ 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'],
32
+ operations: ['recall'],
26
33
  // 媒体段经 upload_file 物化为 file_id(spec 正式投递);上传失败降级扩展字段透传。
27
34
  // 无卡片交互面,交互段降级纯文本。
28
35
  segments: {
@@ -32,6 +39,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
32
39
  create(context) {
33
40
  const config = resolveOneBot12Config(context.config);
34
41
  const gateway = context.use(messageGatewayToken);
42
+ const sideEvents = context.use(sideEventGatewayToken);
35
43
  // 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
36
44
  context.use(onebot12RuntimeStateToken).endpoints.set(config.id, {
37
45
  id: config.id,
@@ -41,6 +49,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
41
49
  return new OneBot12WebhookEndpoint({
42
50
  id: context.id,
43
51
  gateway,
52
+ sideEvents,
44
53
  http: context.use(httpHostToken),
45
54
  config,
46
55
  });
@@ -49,6 +58,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
49
58
  return new OneBot12WssEndpoint({
50
59
  id: context.id,
51
60
  gateway,
61
+ sideEvents,
52
62
  http: context.use(httpHostToken),
53
63
  config,
54
64
  });
@@ -56,6 +66,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
56
66
  return new OneBot12WsEndpoint({
57
67
  id: context.id,
58
68
  gateway,
69
+ sideEvents,
59
70
  config,
60
71
  });
61
72
  },
@@ -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
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
1
+ import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
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,12 +14,15 @@ export interface OneBot12WebhookEndpointOptions {
13
14
  export declare class OneBot12WebhookEndpoint implements EndpointInstance {
14
15
  #private;
15
16
  readonly management: EndpointManagement;
17
+ readonly control: EndpointControl;
18
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
16
19
  constructor(options: OneBot12WebhookEndpointOptions);
17
20
  start(): Promise<void>;
18
21
  open(): void;
19
22
  close(): void;
20
23
  stop(): Promise<void>;
21
24
  send({ conversation, payload }: EndpointSendRequest): Promise<string>;
25
+ recallMessage(messageId: string): Promise<void>;
22
26
  /** Public API for management surface / callers;webhook 模式走 api_url。 */
23
27
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
24
28
  admit(ev: OneBot12Event): void;
package/lib/webhook.js CHANGED
@@ -1,11 +1,16 @@
1
+ import { createRecallEndpointControl, } from 'zhin.js/adapter';
1
2
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
3
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
3
4
  import { buildSendMessageParams, callOneBot12Action, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
5
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
6
+ import { createOneBot12ContentPort } from './content-port.js';
4
7
  import { verifyOneBotAccessToken } from './wss-auth.js';
5
8
  const logger = getLogger('onebot12');
6
9
  export class OneBot12WebhookEndpoint {
7
10
  #options;
8
11
  management = createOneBot12EndpointManagement(this);
12
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
13
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
9
14
  #callAction;
10
15
  #routeReleases = [];
11
16
  #open = false;
@@ -69,6 +74,11 @@ export class OneBot12WebhookEndpoint {
69
74
  }));
70
75
  return messageId;
71
76
  }
77
+ async recallMessage(messageId) {
78
+ if (!messageId)
79
+ return;
80
+ await this.callApi('delete_message', { message_id: messageId });
81
+ }
72
82
  /** Public API for management surface / callers;webhook 模式走 api_url。 */
73
83
  async callApi(action, params = {}) {
74
84
  const apiUrl = this.#options.config.api_url;
@@ -79,8 +89,12 @@ export class OneBot12WebhookEndpoint {
79
89
  return resp.data;
80
90
  }
81
91
  admit(ev) {
82
- if (!this.#open || !isMessageEvent(ev))
92
+ if (!this.#open)
83
93
  return;
94
+ if (!isMessageEvent(ev)) {
95
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, logger);
96
+ return;
97
+ }
84
98
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
85
99
  const content = formatInboundContent(ev);
86
100
  const nickname = senderNickname(ev);
@@ -1,17 +1,20 @@
1
- import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
1
+ import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
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 control: EndpointControl;
17
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
18
  constructor(options: OneBot12WsEndpointOptions);
16
19
  start(): Promise<void>;
17
20
  open(): void;
@@ -3,15 +3,19 @@
3
3
  */
4
4
  import WebSocket from 'ws';
5
5
  import { clearTimeout } from 'node:timers';
6
- import { createEndpointLifecycle, } from 'zhin.js/adapter';
6
+ import { createRecallEndpointControl, 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
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
18
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
15
19
  #lifecycle;
16
20
  #ws;
17
21
  #requestId = 0;
@@ -102,8 +106,12 @@ export class OneBot12WsEndpoint {
102
106
  }
103
107
  /** Test / internal: admit a parsed event when the endpoint is open. */
104
108
  admit(ev) {
105
- if (!this.#open || !isMessageEvent(ev))
109
+ if (!this.#open)
106
110
  return;
111
+ if (!isMessageEvent(ev)) {
112
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
113
+ return;
114
+ }
107
115
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
108
116
  const content = formatInboundContent(ev);
109
117
  const nickname = senderNickname(ev);
@@ -1,17 +1,20 @@
1
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
2
- import type { MessageGateway } from '@zhin.js/core/runtime';
1
+ import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
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 control: EndpointControl;
17
+ readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
18
  constructor(options: OneBot12WssEndpointOptions);
16
19
  start(): Promise<void>;
17
20
  open(): void;
@@ -2,15 +2,20 @@
2
2
  * OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
+ import { createRecallEndpointControl, } from 'zhin.js/adapter';
5
6
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
7
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
7
8
  import { buildSendMessageParams, formatInboundContent, formatOutboundSegments, isBotMentioned, isMessageEvent, onebot12InboundConversation, senderNickname, senderUserId, uploadOneBot12MediaSegments, } from './protocol.js';
9
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
10
+ import { createOneBot12ContentPort } from './content-port.js';
8
11
  import { verifyOneBotAccessToken } from './wss-auth.js';
9
12
  import { WS_OPEN } from './ws-types.js';
10
13
  export class OneBot12WssEndpoint {
11
14
  #logger;
12
15
  #options;
13
16
  management = createOneBot12EndpointManagement(this);
17
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
18
+ content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
14
19
  #ws;
15
20
  #wsRelease;
16
21
  #heartbeatTimer;
@@ -99,8 +104,12 @@ export class OneBot12WssEndpoint {
99
104
  return this.#callAction(action, params);
100
105
  }
101
106
  admit(ev) {
102
- if (!this.#open || !isMessageEvent(ev))
107
+ if (!this.#open)
103
108
  return;
109
+ if (!isMessageEvent(ev)) {
110
+ receiveOneBot12SideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
111
+ return;
112
+ }
104
113
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
105
114
  const nickname = senderNickname(ev);
106
115
  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": "6.0.0",
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.2.0",
44
+ "@zhin.js/core": "1.5.13",
45
+ "@zhin.js/host-http": "1.0.12",
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.12",
55
+ "zhin.js": "6.0.13"
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.2.0",
59
+ "@zhin.js/command": "1.0.15",
60
+ "@zhin.js/core": "1.5.13",
61
+ "zhin.js": "6.0.13"
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
@@ -2,8 +2,14 @@
2
2
  * OneBot12 HTTP webhook endpoint — POST inbound + api_url outbound.
3
3
  */
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
6
- import type { MessageGateway } from '@zhin.js/core/runtime';
5
+ import {
6
+ createRecallEndpointControl,
7
+ type EndpointControl,
8
+ type EndpointInstance,
9
+ type EndpointManagement,
10
+ type EndpointSendRequest,
11
+ } from 'zhin.js/adapter';
12
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
7
13
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
14
  import { formatCompact, getLogger } from '@zhin.js/logger';
9
15
  import type { CapabilityId } from 'zhin.js';
@@ -22,6 +28,8 @@ import {
22
28
  type OneBot12Event,
23
29
  type OneBot12WebhookConfig,
24
30
  } from './protocol.js';
31
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
32
+ import { createOneBot12ContentPort } from './content-port.js';
25
33
  import { verifyOneBotAccessToken } from './wss-auth.js';
26
34
 
27
35
  const logger = getLogger('onebot12');
@@ -29,6 +37,7 @@ const logger = getLogger('onebot12');
29
37
  export interface OneBot12WebhookEndpointOptions {
30
38
  readonly id: CapabilityId;
31
39
  readonly gateway: MessageGateway;
40
+ readonly sideEvents?: SideEventGateway;
32
41
  readonly http: HttpHost;
33
42
  readonly config: OneBot12WebhookConfig;
34
43
  readonly callAction?: typeof callOneBot12Action;
@@ -37,6 +46,8 @@ export interface OneBot12WebhookEndpointOptions {
37
46
  export class OneBot12WebhookEndpoint implements EndpointInstance {
38
47
  readonly #options: OneBot12WebhookEndpointOptions;
39
48
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
49
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
50
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
40
51
  readonly #callAction: typeof callOneBot12Action;
41
52
  #routeReleases: HttpRouteRegistration[] = [];
42
53
  #open = false;
@@ -109,6 +120,11 @@ export class OneBot12WebhookEndpoint implements EndpointInstance {
109
120
  return messageId;
110
121
  }
111
122
 
123
+ async recallMessage(messageId: string): Promise<void> {
124
+ if (!messageId) return;
125
+ await this.callApi('delete_message', { message_id: messageId });
126
+ }
127
+
112
128
  /** Public API for management surface / callers;webhook 模式走 api_url。 */
113
129
  async callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
114
130
  const apiUrl = this.#options.config.api_url;
@@ -124,7 +140,17 @@ export class OneBot12WebhookEndpoint implements EndpointInstance {
124
140
  }
125
141
 
126
142
  admit(ev: OneBot12Event): void {
127
- if (!this.#open || !isMessageEvent(ev)) return;
143
+ if (!this.#open) return;
144
+ if (!isMessageEvent(ev)) {
145
+ receiveOneBot12SideEvent(
146
+ this.#options.sideEvents,
147
+ this.#options.config.id,
148
+ this,
149
+ ev,
150
+ logger,
151
+ );
152
+ return;
153
+ }
128
154
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
129
155
  const content = formatInboundContent(ev);
130
156
  const nickname = senderNickname(ev);
@@ -4,14 +4,16 @@
4
4
  import WebSocket from 'ws';
5
5
  import { clearTimeout } from 'node:timers';
6
6
  import {
7
+ createRecallEndpointControl,
7
8
  createEndpointLifecycle,
8
9
  type EndpointConnectHandle,
10
+ type EndpointControl,
9
11
  type EndpointInstance,
10
12
  type EndpointLifecycle,
11
13
  type EndpointManagement,
12
14
  type EndpointSendRequest,
13
15
  } from 'zhin.js/adapter';
14
- import type { MessageGateway } from '@zhin.js/core/runtime';
16
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
15
17
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
16
18
  import type { CapabilityId } from 'zhin.js';
17
19
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
@@ -31,6 +33,8 @@ import {
31
33
  type OneBot12Event,
32
34
  type OneBot12WsConfig,
33
35
  } from './protocol.js';
36
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
37
+ import { createOneBot12ContentPort } from './content-port.js';
34
38
  import {
35
39
  type OneBot12WsCreateOptions,
36
40
  type OneBot12WsSocket,
@@ -40,6 +44,7 @@ import {
40
44
  export interface OneBot12WsEndpointOptions {
41
45
  readonly id: CapabilityId;
42
46
  readonly gateway: MessageGateway;
47
+ readonly sideEvents?: SideEventGateway;
43
48
  readonly config: OneBot12WsConfig;
44
49
  readonly createWebSocket?: (
45
50
  url: string,
@@ -52,6 +57,8 @@ export class OneBot12WsEndpoint implements EndpointInstance {
52
57
 
53
58
  readonly #options: OneBot12WsEndpointOptions;
54
59
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
60
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
61
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
55
62
  readonly #lifecycle: EndpointLifecycle;
56
63
  #ws?: OneBot12WsSocket;
57
64
  #requestId = 0;
@@ -155,7 +162,17 @@ export class OneBot12WsEndpoint implements EndpointInstance {
155
162
 
156
163
  /** Test / internal: admit a parsed event when the endpoint is open. */
157
164
  admit(ev: OneBot12Event): void {
158
- if (!this.#open || !isMessageEvent(ev)) return;
165
+ if (!this.#open) return;
166
+ if (!isMessageEvent(ev)) {
167
+ receiveOneBot12SideEvent(
168
+ this.#options.sideEvents,
169
+ this.#options.config.id,
170
+ this,
171
+ ev,
172
+ this.#logger,
173
+ );
174
+ return;
175
+ }
159
176
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
160
177
  const content = formatInboundContent(ev);
161
178
  const nickname = senderNickname(ev);
@@ -2,8 +2,14 @@
2
2
  * OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
4
  import { clearInterval } from 'node:timers';
5
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
6
- import type { MessageGateway } from '@zhin.js/core/runtime';
5
+ import {
6
+ createRecallEndpointControl,
7
+ type EndpointControl,
8
+ type EndpointInstance,
9
+ type EndpointManagement,
10
+ type EndpointSendRequest,
11
+ } from 'zhin.js/adapter';
12
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
7
13
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
8
14
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
15
  import type { CapabilityId } from 'zhin.js';
@@ -23,12 +29,15 @@ import {
23
29
  type OneBot12Event,
24
30
  type OneBot12WssConfig,
25
31
  } from './protocol.js';
32
+ import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
33
+ import { createOneBot12ContentPort } from './content-port.js';
26
34
  import { verifyOneBotAccessToken } from './wss-auth.js';
27
35
  import { type OneBot12WsSocket, WS_OPEN } from './ws-types.js';
28
36
 
29
37
  export interface OneBot12WssEndpointOptions {
30
38
  readonly id: CapabilityId;
31
39
  readonly gateway: MessageGateway;
40
+ readonly sideEvents?: SideEventGateway;
32
41
  readonly http: HttpHost;
33
42
  readonly config: OneBot12WssConfig;
34
43
  }
@@ -38,6 +47,8 @@ export class OneBot12WssEndpoint implements EndpointInstance {
38
47
 
39
48
  readonly #options: OneBot12WssEndpointOptions;
40
49
  readonly management: EndpointManagement = createOneBot12EndpointManagement(this);
50
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
51
+ readonly content = createOneBot12ContentPort((action, params) => this.callApi(action, params));
41
52
  #ws?: OneBot12WsSocket;
42
53
  #wsRelease?: () => void;
43
54
  #heartbeatTimer?: NodeJS.Timeout;
@@ -140,7 +151,17 @@ export class OneBot12WssEndpoint implements EndpointInstance {
140
151
  }
141
152
 
142
153
  admit(ev: OneBot12Event): void {
143
- if (!this.#open || !isMessageEvent(ev)) return;
154
+ if (!this.#open) return;
155
+ if (!isMessageEvent(ev)) {
156
+ receiveOneBot12SideEvent(
157
+ this.#options.sideEvents,
158
+ this.#options.config.id,
159
+ this,
160
+ ev,
161
+ this.#logger,
162
+ );
163
+ return;
164
+ }
144
165
  const conversation = onebot12InboundConversation(String(this.#options.id), ev);
145
166
  const nickname = senderNickname(ev);
146
167
  const mentioned = isBotMentioned(ev);