@zhin.js/adapter-kook 7.0.13 → 8.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,58 @@
1
1
  # @zhin.js/adapter-kook
2
2
 
3
+ ## 8.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
+ - Updated dependencies [3dbf990]
11
+ - @zhin.js/host-http@1.0.12
12
+ - @zhin.js/adapter@1.2.0
13
+ - @zhin.js/core@1.5.13
14
+ - @zhin.js/agent@1.1.15
15
+ - zhin.js@6.0.13
16
+
17
+ ## 7.0.14
18
+
19
+ ### Patch Changes
20
+
21
+ - 974772e: Replace the user-facing `Prompt` vocabulary with the `UserInteraction` authoring surface for input, confirmation, and selection. Commands and handlers now expose `interaction`; IM Runtime exposes `createInteraction`; schema-driven endpoint collection is named `SchemaInteraction`. The old prompt-named interaction types and properties are removed rather than aliased. User interactions render through one canonical Markdown and keyboard/list presentation module shared by commands and Agent `ask_user` turns.
22
+
23
+ Extract the transport-neutral interaction contract into `@zhin.js/interaction`. A discriminated `ask()` API supports text, number, confirmation, single-select, multi-select, and typed lists with structured `title`, `description`, and `tip` content. Typed `sequence()` interactions return one result object keyed by step id, render progress, and retry invalid replies without leaking invalid values to callers.
24
+
25
+ Preserve AI Markdown and card command actions through outbound publishing. QQ delivers Markdown with native command buttons; KOOK, Discord, Telegram, DingTalk, and Lark/Feishu now declare and encode their native Markdown dialects while retaining each adapter's interaction policy. Correct QQ callback button action encoding and button style mapping.
26
+
27
+ - 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.
28
+ - Updated dependencies [5969c5b]
29
+ - Updated dependencies [d336a3f]
30
+ - Updated dependencies [0c82a7e]
31
+ - Updated dependencies [b9217e4]
32
+ - Updated dependencies [5969c5b]
33
+ - Updated dependencies [5969c5b]
34
+ - Updated dependencies [974772e]
35
+ - Updated dependencies [5969c5b]
36
+ - Updated dependencies [5969c5b]
37
+ - Updated dependencies [2f786bd]
38
+ - Updated dependencies [63d89f9]
39
+ - Updated dependencies [71c7cdd]
40
+ - Updated dependencies [3cca0ea]
41
+ - Updated dependencies [1312ca0]
42
+ - Updated dependencies [985fa22]
43
+ - Updated dependencies [04b861d]
44
+ - Updated dependencies [a23d544]
45
+ - Updated dependencies [8cddabf]
46
+ - Updated dependencies [dbe5081]
47
+ - @zhin.js/im-contract@1.0.4
48
+ - @zhin.js/core@1.5.12
49
+ - @zhin.js/adapter@1.1.11
50
+ - @zhin.js/agent@1.1.14
51
+ - @zhin.js/host-http@1.0.11
52
+ - @zhin.js/command@1.0.15
53
+ - zhin.js@6.0.12
54
+ - @zhin.js/permission@1.0.3
55
+
3
56
  ## 7.0.13
4
57
 
5
58
  ### Patch Changes
package/README.md CHANGED
@@ -8,6 +8,7 @@ Zhin.js KOOK(开黑啦)适配器(Plugin Runtime),默认通过 **WebSoc
8
8
  - Webhook 入站(`connection: webhook` + `httpHostToken` + `verify_token`)
9
9
  - 解析频道与私聊文本消息
10
10
  - 出站 `send({ conversation, payload })` → KOOK KMarkdown(`kind: 'channel' | 'private'`)
11
+ - canonical `markdown` 段原生保留为 [KMarkdown](https://developer.kookapp.cn/doc/kmarkdown-desc)
11
12
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
13
 
13
14
  ## 安装
@@ -96,7 +97,7 @@ platform permit checker 由 `plugin.ts` 的 generation 生命周期注册;Capa
96
97
 
97
98
  ## 迁移后出站能力变化
98
99
 
99
- 迁移到 Plugin Runtime 后,出站统一经 `messageGatewayToken` 渲染为文本后发送(`sendChannelMsg` / `sendPrivateMsg`,KMarkdown 文本)。旧 Adapter 的富媒体出站能力(图片 / 卡片消息 / 附件等多模态 segment 直发)暂未迁移,当前出站等价于纯文本(KMarkdown)。如需发送卡片或附件,可直接使用 endpoint 上的 KOOK OpenAPI 封装(`getRoleList` 等同款 client)作为逃生舱。
100
+ 迁移到 Plugin Runtime 后,出站统一经 `messageGatewayToken` 渲染并由 endpoint 编码为 KMarkdown(`sendChannelMsg` / `sendPrivateMsg`)。canonical `markdown` 保留格式;图片、视频、音频和文件仅有远程 URL 时可表示为 KMarkdown 链接。卡片消息与附件上传仍未接入统一出站通道。
100
101
 
101
102
  ## 许可证
102
103
 
package/adapters/kook.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Implementation lives under `src/` (endpoint / webhook / ws / protocol).
5
5
  */
6
6
  import { defineAdapter } from 'zhin.js/adapter';
7
- import { messageGatewayToken } from '@zhin.js/core/runtime';
7
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
8
8
  import { httpHostToken } from '@zhin.js/host-http';
9
9
  import { KookWebhookEndpoint, KookWebsocketEndpoint, } from "../lib/endpoint.js";
10
10
  import { resolveKookConfig, } from "../lib/protocol.js";
@@ -12,14 +12,18 @@ import { kookRuntimeStateToken } from "../lib/kook-runtime-state.js";
12
12
  export { KookWebhookEndpoint, KookWebsocketEndpoint, } from "../lib/endpoint.js";
13
13
  export default defineAdapter({
14
14
  capabilities: ['inbound', 'outbound'],
15
- // KOOK 图片消息消费远程 URL;无按钮交互面,交互段降级纯文本。
15
+ operations: ['recall'],
16
+ // KOOK 图片消息消费远程 URL;KMarkdown 由 endpoint codec 原生消费;
17
+ // 无按钮交互面,交互段降级纯文本。
16
18
  segments: {
17
19
  outboundMedia: ['url'],
18
20
  interactive: 'text',
21
+ markdown: 'native',
19
22
  },
20
23
  create(context) {
21
24
  const config = resolveKookConfig(context.config);
22
25
  const gateway = context.use(messageGatewayToken);
26
+ const sideEvents = context.use(sideEventGatewayToken);
23
27
  // 注册到插件运行时状态(kook.endpoint list 的"运行中"数据源)
24
28
  context.use(kookRuntimeStateToken).endpoints.set(config.id, {
25
29
  id: config.id,
@@ -29,6 +33,7 @@ export default defineAdapter({
29
33
  return new KookWebhookEndpoint({
30
34
  id: context.id,
31
35
  gateway,
36
+ sideEvents,
32
37
  http: context.use(httpHostToken),
33
38
  config,
34
39
  });
@@ -36,6 +41,7 @@ export default defineAdapter({
36
41
  return new KookWebsocketEndpoint({
37
42
  id: context.id,
38
43
  gateway,
44
+ sideEvents,
39
45
  config,
40
46
  });
41
47
  },
package/adapters/kook.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Implementation lives under `src/` (endpoint / webhook / ws / protocol).
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 {
9
9
  KookWebhookEndpoint,
@@ -27,14 +27,18 @@ export type { CreateKookClient, KookClientTransport } from '../src/ws.js';
27
27
 
28
28
  export default defineAdapter<KookAdapterConfig>({
29
29
  capabilities: ['inbound', 'outbound'],
30
- // KOOK 图片消息消费远程 URL;无按钮交互面,交互段降级纯文本。
30
+ operations: ['recall'],
31
+ // KOOK 图片消息消费远程 URL;KMarkdown 由 endpoint codec 原生消费;
32
+ // 无按钮交互面,交互段降级纯文本。
31
33
  segments: {
32
34
  outboundMedia: ['url'],
33
35
  interactive: 'text',
36
+ markdown: 'native',
34
37
  },
35
38
  create(context) {
36
39
  const config = resolveKookConfig(context.config);
37
40
  const gateway = context.use(messageGatewayToken);
41
+ const sideEvents = context.use(sideEventGatewayToken);
38
42
  // 注册到插件运行时状态(kook.endpoint list 的"运行中"数据源)
39
43
  context.use(kookRuntimeStateToken).endpoints.set(config.id, {
40
44
  id: config.id,
@@ -44,6 +48,7 @@ export default defineAdapter<KookAdapterConfig>({
44
48
  return new KookWebhookEndpoint({
45
49
  id: context.id,
46
50
  gateway,
51
+ sideEvents,
47
52
  http: context.use(httpHostToken),
48
53
  config,
49
54
  });
@@ -51,6 +56,7 @@ export default defineAdapter<KookAdapterConfig>({
51
56
  return new KookWebsocketEndpoint({
52
57
  id: context.id,
53
58
  gateway,
59
+ sideEvents,
54
60
  config,
55
61
  });
56
62
  },
package/lib/endpoint.d.ts CHANGED
@@ -1,5 +1,5 @@
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 KookInboundMessage, type ResolvedKookWebhookConfig, type ResolvedKookWebsocketConfig } from './protocol.js';
@@ -7,12 +7,14 @@ import { type CreateKookClient, type KookClientTransport } from './ws.js';
7
7
  export interface KookEndpointOptions {
8
8
  readonly id: CapabilityId;
9
9
  readonly gateway: MessageGateway;
10
+ readonly sideEvents?: SideEventGateway;
10
11
  readonly config: ResolvedKookWebsocketConfig;
11
12
  readonly createClient?: CreateKookClient;
12
13
  }
13
14
  export declare class KookWebsocketEndpoint implements EndpointInstance {
14
15
  #private;
15
16
  readonly management: EndpointManagement;
17
+ readonly control: EndpointControl;
16
18
  constructor(options: KookEndpointOptions);
17
19
  start(): Promise<void>;
18
20
  open(): void;
@@ -42,6 +44,7 @@ export declare class KookWebsocketEndpoint implements EndpointInstance {
42
44
  export interface KookWebhookEndpointOptions {
43
45
  readonly id: CapabilityId;
44
46
  readonly gateway: MessageGateway;
47
+ readonly sideEvents?: SideEventGateway;
45
48
  readonly http: HttpHost;
46
49
  readonly config: ResolvedKookWebhookConfig;
47
50
  readonly createClient?: CreateKookClient;
@@ -49,6 +52,7 @@ export interface KookWebhookEndpointOptions {
49
52
  export declare class KookWebhookEndpoint implements EndpointInstance {
50
53
  #private;
51
54
  readonly management: EndpointManagement;
55
+ readonly control: EndpointControl;
52
56
  constructor(options: KookWebhookEndpointOptions);
53
57
  /** Used by webhook handler. */
54
58
  get isOpen(): boolean;
package/lib/endpoint.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { createRecallEndpointControl, } from 'zhin.js/adapter';
1
2
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
3
  import { registerKookAgentEndpoint } from './kook-agent-deps.js';
3
4
  import { formatInboundContent, formatOutboundKmarkdown, isKookBotMentioned, kookInboundConversation, senderDisplayName, } from './protocol.js';
4
5
  import { registerKookWebhookRoutes } from './webhook.js';
5
6
  import { defaultCreateClient, defaultCreateWebhookClient, normalizeKookMessage, } from './ws.js';
7
+ import { receiveKookSideEvent } from './side-event-dispatch.js';
6
8
  export class KookWebsocketEndpoint {
7
9
  #logger;
8
10
  #options;
@@ -12,6 +14,7 @@ export class KookWebsocketEndpoint {
12
14
  #started = false;
13
15
  #unregisterAgent;
14
16
  management = createKookEndpointManagement(() => this.#requireClient());
17
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
15
18
  constructor(options) {
16
19
  this.#logger = getAdapterLogger('kook', options.config.id);
17
20
  this.#options = options;
@@ -143,6 +146,10 @@ export class KookWebsocketEndpoint {
143
146
  if (msg)
144
147
  this.admit(msg);
145
148
  });
149
+ const receiver = client.receiver;
150
+ receiver?.on('event', (raw) => {
151
+ receiveKookSideEvent(this.#options.sideEvents, this.#options.config.id, raw, this.#logger);
152
+ });
146
153
  }
147
154
  #requireClient() {
148
155
  if (!this.#client)
@@ -161,6 +168,7 @@ export class KookWebhookEndpoint {
161
168
  #started = false;
162
169
  #unregisterAgent;
163
170
  management = createKookEndpointManagement(() => this.#requireClient());
171
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
164
172
  constructor(options) {
165
173
  this.#logger = getAdapterLogger('kook', options.config.id);
166
174
  this.#options = options;
package/lib/protocol.js CHANGED
@@ -146,6 +146,8 @@ export function formatOutboundKmarkdown(payload) {
146
146
  switch (item.type) {
147
147
  case 'text':
148
148
  return String(data.text ?? data.content ?? '');
149
+ case 'markdown':
150
+ return String(data.content ?? data.text ?? '');
149
151
  case 'at': {
150
152
  const id = String(data.user_id ?? data.qq ?? data.id ?? '');
151
153
  return id === 'all' ? '(met)all(met)' : `(met)${id}(met)`;
@@ -0,0 +1,3 @@
1
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import { type getAdapterLogger } from '@zhin.js/logger';
3
+ export declare function receiveKookSideEvent(sideEvents: SideEventGateway | undefined, configId: string, raw: unknown, logger: ReturnType<typeof getAdapterLogger>): boolean;
@@ -0,0 +1,51 @@
1
+ import { buildNotice, mapNoticeParts, senderFromId, KOOK_NOTICE_PARTS_MAP } from '@zhin.js/core';
2
+ import { formatCompact } from '@zhin.js/logger';
3
+ function extractKookNoticeType(raw) {
4
+ if (!raw || typeof raw !== 'object')
5
+ return null;
6
+ const event = raw;
7
+ const extraType = event.extra?.type;
8
+ if (typeof extraType === 'string' && extraType)
9
+ return extraType;
10
+ if (event.channel_type === 'NOTICE' && event.type != null) {
11
+ return String(event.type);
12
+ }
13
+ return null;
14
+ }
15
+ export function receiveKookSideEvent(sideEvents, configId, raw, logger) {
16
+ if (!sideEvents)
17
+ return false;
18
+ const noticeType = extractKookNoticeType(raw);
19
+ if (!noticeType)
20
+ return false;
21
+ if (!Object.prototype.hasOwnProperty.call(KOOK_NOTICE_PARTS_MAP, noticeType))
22
+ return false;
23
+ const event = raw;
24
+ const parts = mapNoticeParts('kook', noticeType);
25
+ const guildId = event.extra?.guild_id;
26
+ const userId = event.author_id ?? event.extra?.author?.id;
27
+ const sceneId = guildId != null
28
+ ? String(guildId)
29
+ : userId != null
30
+ ? String(userId)
31
+ : configId;
32
+ void sideEvents.receiveNotice(buildNotice(event, {
33
+ $id: `kook:${noticeType}:${event.msg_timestamp ?? Date.now()}:${sceneId}:${String(userId ?? '')}`,
34
+ $adapter: 'kook',
35
+ $endpoint: configId,
36
+ $type: 'notice',
37
+ $scene_id: sceneId,
38
+ $scene_type: parts.scene_type,
39
+ $sub_type: parts.sub_type,
40
+ $actor: senderFromId(userId, event.extra?.author?.username),
41
+ $timestamp: event.msg_timestamp ?? Date.now(),
42
+ })).catch((err) => {
43
+ logger.warn(formatCompact({
44
+ op: 'kook_side_event_failed',
45
+ endpoint: configId,
46
+ event: noticeType,
47
+ error: err instanceof Error ? err.message : String(err),
48
+ }));
49
+ });
50
+ return true;
51
+ }
package/lib/webhook.d.ts CHANGED
@@ -3,11 +3,13 @@
3
3
  */
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
6
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
6
7
  import { type KookInboundMessage, type ResolvedKookWebhookConfig } from './protocol.js';
7
8
  export interface KookWebhookHandler {
8
9
  readonly config: ResolvedKookWebhookConfig;
9
10
  readonly isOpen: boolean;
10
11
  readonly selfId?: string;
12
+ readonly sideEvents?: SideEventGateway;
11
13
  admit(msg: KookInboundMessage): void;
12
14
  checkAndRememberSn(sn: number): boolean;
13
15
  }
package/lib/webhook.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { isKookWebhookChallenge, normalizeKookWebhookEvent, parseKookWebhookBody, readRequestBody, verifyKookWebhookToken, } from './protocol.js';
3
+ import { receiveKookSideEvent } from './side-event-dispatch.js';
3
4
  const logger = getLogger('kook');
4
5
  export function registerKookWebhookRoutes(http, handler) {
5
6
  const path = handler.config.webhookPath;
@@ -67,6 +68,9 @@ export async function handleKookWebhookRequest(request, response, handler) {
67
68
  }
68
69
  }
69
70
  function handleKookWebhookEvent(event, handler) {
71
+ if (receiveKookSideEvent(handler.sideEvents, handler.config.id, event, logger)) {
72
+ return;
73
+ }
70
74
  const msg = normalizeKookWebhookEvent(event, {
71
75
  ignore: handler.config.ignore,
72
76
  selfId: handler.selfId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-kook",
3
- "version": "7.0.13",
3
+ "version": "8.0.0",
4
4
  "description": "Zhin.js KOOK adapter for Plugin Runtime (WebSocket gateway or Webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,20 +34,20 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "kook-client": "^1.0.4",
37
- "@zhin.js/adapter": "1.1.10",
38
- "@zhin.js/core": "1.5.11",
39
- "@zhin.js/host-http": "1.0.10",
40
- "@zhin.js/im-contract": "1.0.3",
37
+ "@zhin.js/adapter": "1.2.0",
38
+ "@zhin.js/core": "1.5.13",
39
+ "@zhin.js/host-http": "1.0.12",
40
+ "@zhin.js/im-contract": "1.0.4",
41
41
  "@zhin.js/logger": "1.0.76",
42
- "@zhin.js/permission": "1.0.2"
42
+ "@zhin.js/permission": "1.0.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "1.1.10",
47
- "@zhin.js/agent": "1.1.13",
48
- "@zhin.js/command": "1.0.14",
49
- "@zhin.js/core": "1.5.11",
50
- "zhin.js": "6.0.11"
46
+ "@zhin.js/adapter": "1.2.0",
47
+ "@zhin.js/agent": "1.1.15",
48
+ "@zhin.js/command": "1.0.15",
49
+ "@zhin.js/core": "1.5.13",
50
+ "zhin.js": "6.0.13"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "@zhin.js/agent": {
@@ -68,9 +68,9 @@
68
68
  "typescript": "^6.0.3",
69
69
  "vitest": "^4.1.10",
70
70
  "zod": "^4.4.3",
71
- "@zhin.js/agent": "1.1.13",
72
- "@zhin.js/host-http": "1.0.10",
73
- "zhin.js": "6.0.11"
71
+ "@zhin.js/agent": "1.1.15",
72
+ "@zhin.js/host-http": "1.0.12",
73
+ "zhin.js": "6.0.13"
74
74
  },
75
75
  "files": [
76
76
  "adapters",
package/src/endpoint.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  * KookEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
4
  import { Client } from 'kook-client';
5
- import type {
6
- EndpointChannel,
7
- EndpointGroup,
8
- EndpointInstance,
9
- EndpointManagement,
10
- EndpointSendRequest,
5
+ import {
6
+ createRecallEndpointControl,
7
+ type EndpointControl,
8
+ type EndpointChannel,
9
+ type EndpointGroup,
10
+ type EndpointInstance,
11
+ type EndpointManagement,
12
+ type EndpointSendRequest,
11
13
  } from 'zhin.js/adapter';
12
- import type { MessageGateway } from '@zhin.js/core/runtime';
14
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
13
15
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
14
16
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
17
  import type { CapabilityId } from 'zhin.js';
@@ -32,10 +34,12 @@ import {
32
34
  type CreateKookClient,
33
35
  type KookClientTransport,
34
36
  } from './ws.js';
37
+ import { receiveKookSideEvent } from './side-event-dispatch.js';
35
38
 
36
39
  export interface KookEndpointOptions {
37
40
  readonly id: CapabilityId;
38
41
  readonly gateway: MessageGateway;
42
+ readonly sideEvents?: SideEventGateway;
39
43
  readonly config: ResolvedKookWebsocketConfig;
40
44
  readonly createClient?: CreateKookClient;
41
45
  }
@@ -50,6 +54,7 @@ export class KookWebsocketEndpoint implements EndpointInstance {
50
54
  #started = false;
51
55
  #unregisterAgent?: () => void;
52
56
  readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
57
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
53
58
 
54
59
  constructor(options: KookEndpointOptions) {
55
60
  this.#logger = getAdapterLogger('kook', options.config.id);
@@ -191,6 +196,10 @@ export class KookWebsocketEndpoint implements EndpointInstance {
191
196
  const msg = normalizeKookMessage(raw);
192
197
  if (msg) this.admit(msg);
193
198
  });
199
+ const receiver = (client as { receiver?: { on(event: string, listener: (...args: unknown[]) => void): void } }).receiver;
200
+ receiver?.on('event', (raw) => {
201
+ receiveKookSideEvent(this.#options.sideEvents, this.#options.config.id, raw, this.#logger);
202
+ });
194
203
  }
195
204
 
196
205
  #requireClient(): KookClientTransport {
@@ -202,6 +211,7 @@ export class KookWebsocketEndpoint implements EndpointInstance {
202
211
  export interface KookWebhookEndpointOptions {
203
212
  readonly id: CapabilityId;
204
213
  readonly gateway: MessageGateway;
214
+ readonly sideEvents?: SideEventGateway;
205
215
  readonly http: HttpHost;
206
216
  readonly config: ResolvedKookWebhookConfig;
207
217
  readonly createClient?: CreateKookClient;
@@ -219,6 +229,7 @@ export class KookWebhookEndpoint implements EndpointInstance {
219
229
  #started = false;
220
230
  #unregisterAgent?: () => void;
221
231
  readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
232
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
222
233
 
223
234
  constructor(options: KookWebhookEndpointOptions) {
224
235
  this.#logger = getAdapterLogger('kook', options.config.id);
package/src/protocol.ts CHANGED
@@ -279,6 +279,8 @@ export function formatOutboundKmarkdown(payload: unknown): string {
279
279
  switch (item.type) {
280
280
  case 'text':
281
281
  return String(data.text ?? data.content ?? '');
282
+ case 'markdown':
283
+ return String(data.content ?? data.text ?? '');
282
284
  case 'at': {
283
285
  const id = String(data.user_id ?? data.qq ?? data.id ?? '');
284
286
  return id === 'all' ? '(met)all(met)' : `(met)${id}(met)`;
@@ -0,0 +1,55 @@
1
+ import { buildNotice, mapNoticeParts, senderFromId, KOOK_NOTICE_PARTS_MAP } 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 { KookWebhookEventData } from './protocol.js';
5
+
6
+ function extractKookNoticeType(raw: unknown): string | null {
7
+ if (!raw || typeof raw !== 'object') return null;
8
+ const event = raw as KookWebhookEventData & { extra?: { type?: string } };
9
+ const extraType = event.extra?.type;
10
+ if (typeof extraType === 'string' && extraType) return extraType;
11
+ if (event.channel_type === 'NOTICE' && event.type != null) {
12
+ return String(event.type);
13
+ }
14
+ return null;
15
+ }
16
+
17
+ export function receiveKookSideEvent(
18
+ sideEvents: SideEventGateway | undefined,
19
+ configId: string,
20
+ raw: unknown,
21
+ logger: ReturnType<typeof getAdapterLogger>,
22
+ ): boolean {
23
+ if (!sideEvents) return false;
24
+ const noticeType = extractKookNoticeType(raw);
25
+ if (!noticeType) return false;
26
+ if (!Object.prototype.hasOwnProperty.call(KOOK_NOTICE_PARTS_MAP, noticeType)) return false;
27
+ const event = raw as KookWebhookEventData;
28
+ const parts = mapNoticeParts('kook', noticeType);
29
+ const guildId = event.extra?.guild_id;
30
+ const userId = event.author_id ?? event.extra?.author?.id;
31
+ const sceneId = guildId != null
32
+ ? String(guildId)
33
+ : userId != null
34
+ ? String(userId)
35
+ : configId;
36
+ void sideEvents.receiveNotice(buildNotice(event, {
37
+ $id: `kook:${noticeType}:${event.msg_timestamp ?? Date.now()}:${sceneId}:${String(userId ?? '')}`,
38
+ $adapter: 'kook' as never,
39
+ $endpoint: configId,
40
+ $type: 'notice',
41
+ $scene_id: sceneId,
42
+ $scene_type: parts.scene_type,
43
+ $sub_type: parts.sub_type,
44
+ $actor: senderFromId(userId, event.extra?.author?.username),
45
+ $timestamp: event.msg_timestamp ?? Date.now(),
46
+ })).catch((err) => {
47
+ logger.warn(formatCompact({
48
+ op: 'kook_side_event_failed',
49
+ endpoint: configId,
50
+ event: noticeType,
51
+ error: err instanceof Error ? err.message : String(err),
52
+ }));
53
+ });
54
+ return true;
55
+ }
package/src/webhook.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
6
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
6
7
  import { formatCompact, getLogger } from '@zhin.js/logger';
7
8
  import {
8
9
  isKookWebhookChallenge,
@@ -15,6 +16,7 @@ import {
15
16
  type KookWebhookFrame,
16
17
  type ResolvedKookWebhookConfig,
17
18
  } from './protocol.js';
19
+ import { receiveKookSideEvent } from './side-event-dispatch.js';
18
20
 
19
21
  const logger = getLogger('kook');
20
22
 
@@ -22,6 +24,7 @@ export interface KookWebhookHandler {
22
24
  readonly config: ResolvedKookWebhookConfig;
23
25
  readonly isOpen: boolean;
24
26
  readonly selfId?: string;
27
+ readonly sideEvents?: SideEventGateway;
25
28
  admit(msg: KookInboundMessage): void;
26
29
  checkAndRememberSn(sn: number): boolean;
27
30
  }
@@ -108,6 +111,9 @@ function handleKookWebhookEvent(
108
111
  event: KookWebhookEventData,
109
112
  handler: KookWebhookHandler,
110
113
  ): void {
114
+ if (receiveKookSideEvent(handler.sideEvents, handler.config.id, event, logger)) {
115
+ return;
116
+ }
111
117
  const msg = normalizeKookWebhookEvent(event, {
112
118
  ignore: handler.config.ignore,
113
119
  selfId: handler.selfId,