@zhin.js/adapter-kook 5.0.0 → 5.0.2

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -0
  3. package/{skills/kook → agent}/PERMITS.md +1 -1
  4. package/agent/tools/blacklist.ts +33 -0
  5. package/agent/tools/create_role.ts +25 -0
  6. package/agent/tools/delete_role.ts +21 -0
  7. package/agent/tools/grant_role.ts +22 -0
  8. package/agent/tools/list_roles.ts +27 -0
  9. package/agent/tools/revoke_role.ts +22 -0
  10. package/dist/index.js +2 -2
  11. package/lib/agent/tools/blacklist.js +33 -0
  12. package/lib/agent/tools/blacklist.js.map +1 -0
  13. package/lib/agent/tools/create_role.js +25 -0
  14. package/lib/agent/tools/create_role.js.map +1 -0
  15. package/lib/agent/tools/delete_role.js +21 -0
  16. package/lib/agent/tools/delete_role.js.map +1 -0
  17. package/lib/agent/tools/grant_role.js +22 -0
  18. package/lib/agent/tools/grant_role.js.map +1 -0
  19. package/lib/agent/tools/list_roles.js +27 -0
  20. package/lib/agent/tools/list_roles.js.map +1 -0
  21. package/lib/agent/tools/revoke_role.js +22 -0
  22. package/lib/agent/tools/revoke_role.js.map +1 -0
  23. package/lib/{adapter.js → src/adapter.js} +4 -3
  24. package/lib/src/adapter.js.map +1 -0
  25. package/lib/{endpoint.js → src/endpoint.js} +93 -59
  26. package/lib/src/endpoint.js.map +1 -0
  27. package/lib/src/index.js +197 -0
  28. package/lib/src/index.js.map +1 -0
  29. package/lib/src/kook-agent-deps.js +10 -0
  30. package/lib/src/kook-agent-deps.js.map +1 -0
  31. package/lib/src/kook-asset-upload.js.map +1 -0
  32. package/lib/src/kook-inbound.js.map +1 -0
  33. package/lib/src/kook-msg-route.js.map +1 -0
  34. package/lib/{kook-side-events.js → src/kook-side-events.js} +52 -33
  35. package/lib/src/kook-side-events.js.map +1 -0
  36. package/lib/src/outbound-keyboard.js +66 -0
  37. package/lib/src/outbound-keyboard.js.map +1 -0
  38. package/lib/{outbound-media.js → src/outbound-media.js} +4 -1
  39. package/lib/src/outbound-media.js.map +1 -0
  40. package/lib/{outbound-sendable.js → src/outbound-sendable.js} +9 -0
  41. package/lib/src/outbound-sendable.js.map +1 -0
  42. package/lib/{platform-permit.js → src/platform-permit.js} +5 -2
  43. package/lib/src/platform-permit.js.map +1 -0
  44. package/lib/src/segment-mapper.js +2 -0
  45. package/lib/src/segment-mapper.js.map +1 -0
  46. package/lib/src/types.js.map +1 -0
  47. package/package.json +26 -16
  48. package/src/adapter.ts +5 -5
  49. package/src/endpoint.ts +97 -66
  50. package/src/index.ts +14 -168
  51. package/src/kook-agent-deps.ts +22 -0
  52. package/src/kook-side-events.ts +66 -38
  53. package/src/outbound-keyboard.ts +77 -0
  54. package/src/outbound-media.ts +1 -8
  55. package/src/outbound-sendable.ts +13 -1
  56. package/src/platform-permit.ts +5 -6
  57. package/src/segment-mapper.ts +1 -0
  58. package/src/types.ts +1 -2
  59. package/lib/adapter.d.ts +0 -29
  60. package/lib/adapter.d.ts.map +0 -1
  61. package/lib/adapter.js.map +0 -1
  62. package/lib/endpoint.d.ts +0 -155
  63. package/lib/endpoint.d.ts.map +0 -1
  64. package/lib/endpoint.js.map +0 -1
  65. package/lib/index.d.ts +0 -18
  66. package/lib/index.d.ts.map +0 -1
  67. package/lib/index.js +0 -350
  68. package/lib/index.js.map +0 -1
  69. package/lib/kook-asset-upload.d.ts +0 -12
  70. package/lib/kook-asset-upload.d.ts.map +0 -1
  71. package/lib/kook-asset-upload.js.map +0 -1
  72. package/lib/kook-inbound.d.ts +0 -6
  73. package/lib/kook-inbound.d.ts.map +0 -1
  74. package/lib/kook-inbound.js.map +0 -1
  75. package/lib/kook-msg-route.d.ts +0 -34
  76. package/lib/kook-msg-route.d.ts.map +0 -1
  77. package/lib/kook-msg-route.js.map +0 -1
  78. package/lib/kook-side-events.d.ts +0 -43
  79. package/lib/kook-side-events.d.ts.map +0 -1
  80. package/lib/kook-side-events.js.map +0 -1
  81. package/lib/outbound-media.d.ts +0 -10
  82. package/lib/outbound-media.d.ts.map +0 -1
  83. package/lib/outbound-media.js.map +0 -1
  84. package/lib/outbound-sendable.d.ts +0 -11
  85. package/lib/outbound-sendable.d.ts.map +0 -1
  86. package/lib/outbound-sendable.js.map +0 -1
  87. package/lib/platform-permit.d.ts +0 -14
  88. package/lib/platform-permit.d.ts.map +0 -1
  89. package/lib/platform-permit.js.map +0 -1
  90. package/lib/types.d.ts +0 -54
  91. package/lib/types.d.ts.map +0 -1
  92. package/lib/types.js.map +0 -1
  93. /package/{skills/kook/SKILL.md → agent/skills/kook.md} +0 -0
  94. /package/lib/{kook-asset-upload.js → src/kook-asset-upload.js} +0 -0
  95. /package/lib/{kook-inbound.js → src/kook-inbound.js} +0 -0
  96. /package/lib/{kook-msg-route.js → src/kook-msg-route.js} +0 -0
  97. /package/lib/{types.js → src/types.js} +0 -0
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@
2
2
  * KOOK 适配器入口:类型扩展、导出、注册
3
3
  */
4
4
  import path from "node:path";
5
- import { usePlugin, type Plugin, type IGroupManagement, createGroupManagementTools, type ToolFeature } from "zhin.js";
5
+ import { usePlugin, type Plugin, type ISceneManagement, createSceneManagementTools, type ToolFeature } from "zhin.js";
6
6
  import { KookAdapter } from "./adapter.js";
7
- import { kookGroupPermitResolver, platformPermit, registerKookPlatformPermitChecker } from "./platform-permit.js";
7
+ import { kookGroupPermitResolver, registerKookPlatformPermitChecker } from "./platform-permit.js";
8
+ import { setKookAgentDeps } from "./kook-agent-deps.js";
8
9
  import { PageManager } from "@zhin.js/host-api";
9
10
 
10
11
  declare module "zhin.js" {
@@ -44,175 +45,20 @@ provide({
44
45
  useContext('tool', 'kook', (toolService: ToolFeature, kook: KookAdapter) => {
45
46
  const disposers: (() => void)[] = [];
46
47
  disposers.push(registerKookPlatformPermitChecker());
47
- const groupTools = createGroupManagementTools(
48
- kook as unknown as IGroupManagement,
48
+ setKookAgentDeps({
49
+ getEndpoint: (endpointId) => {
50
+ const endpoint = kook.endpoints.get(endpointId);
51
+ if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
52
+ return endpoint;
53
+ },
54
+ getAdapter: () => kook,
55
+ });
56
+ const sceneTools = createSceneManagementTools(
57
+ kook as unknown as ISceneManagement,
49
58
  'kook',
50
59
  { permitResolver: kookGroupPermitResolver, registerChecker: false },
51
60
  );
52
- disposers.push(...groupTools.map(t => toolService.addTool(t, plugin.name)));
53
-
54
- disposers.push(toolService.addTool({
55
- name: 'kook_grant_role',
56
- description: '给用户授予 KOOK 服务器角色',
57
- parameters: {
58
- type: 'object',
59
- properties: {
60
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
61
- guild_id: { type: 'string', description: '服务器 ID' },
62
- user_id: { type: 'string', description: '用户 ID' },
63
- role_id: { type: 'string', description: '角色 ID' },
64
- },
65
- required: ['endpoint_id', 'guild_id', 'user_id', 'role_id'],
66
- },
67
- platforms: ['kook'],
68
- tags: ['kook'],
69
- permissions: [platformPermit('manage_roles')],
70
- execute: async (args: Record<string, any>) => {
71
- const endpoint = kook.endpoints.get(args.endpoint_id);
72
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
73
- const success = await endpoint.grantRole(args.guild_id, args.user_id, args.role_id);
74
- return { success, message: success ? `已授予用户 ${args.user_id} 角色 ${args.role_id}` : '授予角色失败' };
75
- },
76
- }, plugin.name));
77
-
78
- disposers.push(toolService.addTool({
79
- name: 'kook_revoke_role',
80
- description: '撤销用户的 KOOK 服务器角色',
81
- parameters: {
82
- type: 'object',
83
- properties: {
84
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
85
- guild_id: { type: 'string', description: '服务器 ID' },
86
- user_id: { type: 'string', description: '用户 ID' },
87
- role_id: { type: 'string', description: '角色 ID' },
88
- },
89
- required: ['endpoint_id', 'guild_id', 'user_id', 'role_id'],
90
- },
91
- platforms: ['kook'],
92
- tags: ['kook'],
93
- permissions: [platformPermit('manage_roles')],
94
- execute: async (args: Record<string, any>) => {
95
- const endpoint = kook.endpoints.get(args.endpoint_id);
96
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
97
- const success = await endpoint.revokeRole(args.guild_id, args.user_id, args.role_id);
98
- return { success, message: success ? `已撤销用户 ${args.user_id} 的角色 ${args.role_id}` : '撤销角色失败' };
99
- },
100
- }, plugin.name));
101
-
102
- disposers.push(toolService.addTool({
103
- name: 'kook_list_roles',
104
- description: '获取 KOOK 服务器的角色列表',
105
- parameters: {
106
- type: 'object',
107
- properties: {
108
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
109
- guild_id: { type: 'string', description: '服务器 ID' },
110
- },
111
- required: ['endpoint_id', 'guild_id'],
112
- },
113
- platforms: ['kook'],
114
- tags: ['kook'],
115
- execute: async (args: Record<string, any>) => {
116
- const endpoint = kook.endpoints.get(args.endpoint_id);
117
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
118
- const roles = await endpoint.getRoleList(args.guild_id);
119
- return {
120
- roles: roles.map((r: any) => ({
121
- id: r.role_id,
122
- name: r.name,
123
- color: r.color,
124
- position: r.position,
125
- permissions: r.permissions,
126
- })),
127
- count: roles.length,
128
- };
129
- },
130
- }, plugin.name));
131
-
132
- disposers.push(toolService.addTool({
133
- name: 'kook_create_role',
134
- description: '在 KOOK 服务器中创建新角色',
135
- parameters: {
136
- type: 'object',
137
- properties: {
138
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
139
- guild_id: { type: 'string', description: '服务器 ID' },
140
- name: { type: 'string', description: '角色名称' },
141
- },
142
- required: ['endpoint_id', 'guild_id', 'name'],
143
- },
144
- platforms: ['kook'],
145
- tags: ['kook'],
146
- permissions: [platformPermit('guild_owner')],
147
- execute: async (args: Record<string, any>) => {
148
- const endpoint = kook.endpoints.get(args.endpoint_id);
149
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
150
- const role = await endpoint.createRole(args.guild_id, args.name);
151
- return {
152
- success: true,
153
- message: `已创建角色 "${args.name}"`,
154
- role: { id: role.role_id, name: role.name },
155
- };
156
- },
157
- }, plugin.name));
158
-
159
- disposers.push(toolService.addTool({
160
- name: 'kook_delete_role',
161
- description: '删除 KOOK 服务器中的角色',
162
- parameters: {
163
- type: 'object',
164
- properties: {
165
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
166
- guild_id: { type: 'string', description: '服务器 ID' },
167
- role_id: { type: 'string', description: '角色 ID' },
168
- },
169
- required: ['endpoint_id', 'guild_id', 'role_id'],
170
- },
171
- platforms: ['kook'],
172
- tags: ['kook'],
173
- permissions: [platformPermit('guild_owner')],
174
- execute: async (args: Record<string, any>) => {
175
- const endpoint = kook.endpoints.get(args.endpoint_id);
176
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
177
- const success = await endpoint.deleteRole(args.guild_id, args.role_id);
178
- return { success, message: success ? `已删除角色 ${args.role_id}` : '删除角色失败' };
179
- },
180
- }, plugin.name));
181
-
182
- disposers.push(toolService.addTool({
183
- name: 'kook_blacklist',
184
- description: 'KOOK 服务器黑名单管理:添加/移除',
185
- parameters: {
186
- type: 'object',
187
- properties: {
188
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
189
- guild_id: { type: 'string', description: '服务器 ID' },
190
- action: { type: 'string', description: 'add|remove', enum: ['add', 'remove'] },
191
- user_id: { type: 'string', description: '用户 ID' },
192
- remark: { type: 'string', description: '备注(add 可选)' },
193
- },
194
- required: ['endpoint_id', 'guild_id', 'action', 'user_id'],
195
- },
196
- platforms: ['kook'],
197
- tags: ['kook'],
198
- permissions: [platformPermit('guild_admin')],
199
- execute: async (args: Record<string, any>) => {
200
- const endpoint = kook.endpoints.get(args.endpoint_id);
201
- if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
202
- switch (args.action) {
203
- case 'add': {
204
- const success = await endpoint.addToBlacklist(args.guild_id, args.user_id, args.remark);
205
- return { success, message: success ? `已将 ${args.user_id} 加入黑名单` : '操作失败' };
206
- }
207
- case 'remove': {
208
- const success = await endpoint.removeFromBlacklist(args.guild_id, args.user_id);
209
- return { success, message: success ? `已将 ${args.user_id} 从黑名单移除` : '操作失败' };
210
- }
211
- default:
212
- return { success: false, message: `未知操作: ${args.action}` };
213
- }
214
- },
215
- }, plugin.name));
61
+ disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
216
62
 
217
63
  return () => disposers.forEach(d => d());
218
64
  });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared runtime deps for kook agent/ authoring tools.
3
+ * Set once from plugin index during init — never call getPlugin() from tool execute.
4
+ */
5
+ import type { KookAdapter } from './adapter.js';
6
+ import type { KookEndpoint } from './endpoint.js';
7
+
8
+ export interface KookAgentDeps {
9
+ getEndpoint: (endpointId: string) => KookEndpoint;
10
+ getAdapter: () => KookAdapter;
11
+ }
12
+
13
+ let _deps: KookAgentDeps | null = null;
14
+
15
+ export function setKookAgentDeps(deps: KookAgentDeps): void {
16
+ _deps = deps;
17
+ }
18
+
19
+ export function getKookAgentDeps(): KookAgentDeps {
20
+ if (!_deps) throw new Error('kook agent deps not initialized');
21
+ return _deps;
22
+ }
@@ -1,10 +1,15 @@
1
1
  /**
2
2
  * KOOK Gateway 非聊天入站:系统消息 (type=255) / BROADCAST → Notice
3
- * @see https://developer.kookapp.cn/doc/event/event-introduction
4
3
  */
5
- import { Notice, formatCompact, type NoticeType } from "zhin.js";
4
+ import {
5
+ Notice,
6
+ Message,
7
+ mapNoticeParts,
8
+ senderFromId,
9
+ buildNotice,
10
+ formatCompact,
11
+ } from "zhin.js";
6
12
 
7
- /** KOOK WebSocket s=0 时 d 字段(kook-client Receiver 入参) */
8
13
  export interface KookGatewayEvent {
9
14
  channel_type?: string;
10
15
  type?: number;
@@ -30,7 +35,6 @@ export interface KookGatewayExtra {
30
35
 
31
36
  const SYSTEM_MESSAGE_TYPE = 255;
32
37
 
33
- /** 是否应由适配器作为 notice 处理(kook-client 对 notice 的 transform 为空实现) */
34
38
  export function isKookNoticeGatewayEvent(data: unknown): data is KookGatewayEvent {
35
39
  if (!data || typeof data !== "object") return false;
36
40
  const ev = data as KookGatewayEvent;
@@ -50,34 +54,12 @@ export function resolveKookSideEventDedupeKey(
50
54
  return `${kind}:${ts}_${noticeType}_${scope}`;
51
55
  }
52
56
 
53
- const NOTICE_TYPE_MAP: Record<string, NoticeType> = {
54
- joined_guild: "group_member_increase",
55
- exited_guild: "group_member_decrease",
56
- deleted_message: "group_recall",
57
- deleted_private_message: "friend_recall",
58
- added_reaction: "group_emoji_reaction",
59
- deleted_reaction: "group_emoji_reaction",
60
- private_added_reaction: "group_emoji_reaction",
61
- private_deleted_reaction: "group_emoji_reaction",
62
- };
63
-
64
- function resolveNoticeZhinType(rawType: string): NoticeType {
65
- return NOTICE_TYPE_MAP[rawType] ?? rawType;
66
- }
67
-
68
- function senderFromId(id: unknown, name?: string) {
69
- if (id == null || id === "") return undefined;
70
- const s = String(id);
71
- return { id: s, name: name ?? s };
72
- }
73
-
74
57
  function readString(obj: Record<string, unknown> | undefined, key: string): string | undefined {
75
58
  const v = obj?.[key];
76
59
  if (v == null || v === "") return undefined;
77
60
  return String(v);
78
61
  }
79
62
 
80
- /** 解析 notice 发生的频道 / 群 / 私聊 */
81
63
  export function resolveKookNoticeChannel(
82
64
  event: KookGatewayEvent,
83
65
  ): { id: string; type: "group" | "private" | "channel" } {
@@ -161,11 +143,11 @@ export function enrichKookGatewayForPlugins(event: KookGatewayEvent): KookGatewa
161
143
  export function formatKookNotice(
162
144
  event: KookGatewayEvent,
163
145
  endpointName: string,
164
- ): ReturnType<typeof Notice.from<KookGatewayEvent>> {
146
+ ) {
165
147
  const extra = event.extra ?? {};
166
148
  const body = extra.body ?? {};
167
149
  const rawType = String(extra.type ?? "unknown");
168
- const $type = resolveNoticeZhinType(rawType);
150
+ const { scene_type, sub_type } = mapNoticeParts('kook', rawType);
169
151
  const channel = resolveKookNoticeChannel(event);
170
152
  const ts = event.msg_timestamp ?? Date.now();
171
153
 
@@ -177,16 +159,15 @@ export function formatKookNotice(
177
159
  readString(body, "operator_id")
178
160
  ?? (event.author_id !== "1" ? event.author_id : undefined);
179
161
 
180
- const mapped = NOTICE_TYPE_MAP[rawType] != null;
181
-
182
- return Notice.from(event, {
162
+ return buildNotice(event, {
183
163
  $id: resolveKookSideEventDedupeKey(event, "notice"),
184
164
  $adapter: "kook",
185
165
  $endpoint: endpointName,
186
- $type,
187
- $subType: mapped ? rawType : undefined,
188
- $channel: channel,
189
- $operator: senderFromId(operatorId),
166
+ $type: "notice",
167
+ $scene_id: channel.id,
168
+ $scene_type: scene_type,
169
+ $sub_type: sub_type,
170
+ $actor: senderFromId(operatorId),
190
171
  $target: senderFromId(targetId),
191
172
  $timestamp: ts,
192
173
  });
@@ -194,9 +175,56 @@ export function formatKookNotice(
194
175
 
195
176
  export function formatKookNoticeLog(notice: ReturnType<typeof formatKookNotice>): string {
196
177
  return formatCompact({
197
- notice: notice.$type,
198
- kook_type: notice.$subType,
199
- channel: `${notice.$channel.type}(${notice.$channel.id})`,
178
+ notice: `${notice.$type}.${notice.$scene_type}.${notice.$sub_type}`,
179
+ kook_type: notice.$sub_type,
180
+ scene: `${notice.$scene_type}(${notice.$scene_id})`,
200
181
  endpoint: notice.$endpoint,
201
182
  });
202
183
  }
184
+
185
+ export function isKookButtonClickEvent(event: KookGatewayEvent): boolean {
186
+ return event.type === SYSTEM_MESSAGE_TYPE && event.extra?.type === 'message_btn_click';
187
+ }
188
+
189
+ export function formatKookButtonClickMessage(
190
+ event: KookGatewayEvent,
191
+ endpointName: string,
192
+ ): ReturnType<typeof Message.from<KookGatewayEvent>> {
193
+ const extra = event.extra ?? {};
194
+ const body = extra.body ?? {};
195
+ const payload = String(body.value ?? '');
196
+ const userInfo = body.user_info as { id?: string; username?: string; nickname?: string } | undefined;
197
+ const userId = String(body.user_id ?? userInfo?.id ?? event.author_id ?? '');
198
+ const userName = userInfo?.nickname ?? userInfo?.username ?? userId;
199
+ const sourceMessageId = String(body.msg_id ?? '');
200
+
201
+ let channelType: 'private' | 'channel' = 'private';
202
+ let channelId = String(body.target_id ?? event.target_id ?? userId);
203
+ if (body.channel_type === 'GROUP' || event.channel_type === 'GROUP') {
204
+ channelType = 'channel';
205
+ channelId = String(body.target_id ?? event.target_id ?? channelId);
206
+ } else if (body.channel_type === 'PERSON' || event.channel_type === 'PERSON') {
207
+ channelType = 'private';
208
+ channelId = String(body.target_id ?? event.target_id ?? userId);
209
+ }
210
+
211
+ return Message.from(event, {
212
+ $id: event.msg_id ?? `btn:${Date.now()}`,
213
+ $adapter: 'kook',
214
+ $endpoint: endpointName,
215
+ $sender: { id: userId, name: userName },
216
+ $channel: { id: channelId, type: channelType },
217
+ $content: [{
218
+ type: 'action',
219
+ data: {
220
+ id: payload,
221
+ payload,
222
+ sourceMessageId,
223
+ },
224
+ }],
225
+ $raw: payload,
226
+ $timestamp: event.msg_timestamp ?? Date.now(),
227
+ $recall: async () => {},
228
+ $reply: async () => '',
229
+ });
230
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * core keyboard 段 → KOOK 卡片消息(action-group + button)
3
+ */
4
+ import { KeyboardSegment, isKeyboardSegment, type KeyboardSegmentData, type MessageElement } from 'zhin.js';
5
+
6
+ function asKeyboardElement(
7
+ el: MessageElement,
8
+ ): (MessageElement & { data: KeyboardSegmentData }) | null {
9
+ if (el instanceof KeyboardSegment) {
10
+ return { type: "keyboard", data: el.data };
11
+ }
12
+ if (isKeyboardSegment(el)) return el;
13
+ return null;
14
+ }
15
+
16
+ function textFromElements(elements: MessageElement[]): string {
17
+ return elements
18
+ .filter((el) => el.type === "text" && typeof el.data.text === "string")
19
+ .map((el) => el.data.text as string)
20
+ .join("\n")
21
+ .trim();
22
+ }
23
+
24
+ /** core keyboard 布局 → KOOK card[](可直接传给 sendChannelMsg / sendPrivateMsg) */
25
+ export function coreKeyboardToKookCard(
26
+ data: KeyboardSegmentData,
27
+ prefixElements: MessageElement[] = [],
28
+ ): Array<Record<string, unknown>> {
29
+ const modules: Array<Record<string, unknown>> = [];
30
+ const prefixText = textFromElements(prefixElements);
31
+ if (prefixText) {
32
+ modules.push({
33
+ type: "section",
34
+ text: { type: "plain-text", content: prefixText },
35
+ });
36
+ }
37
+
38
+ for (const row of data.rows ?? []) {
39
+ modules.push({
40
+ type: "action-group",
41
+ elements: row.map((btn) => ({
42
+ type: "button",
43
+ theme: btn.disabled ? "secondary" : "primary",
44
+ value: btn.payload,
45
+ click: "return-val",
46
+ text: { type: "plain-text", content: btn.label },
47
+ })),
48
+ });
49
+ }
50
+
51
+ return [{
52
+ type: "card",
53
+ theme: "secondary",
54
+ size: "sm",
55
+ modules,
56
+ }];
57
+ }
58
+
59
+ export function findKeyboardSegment(
60
+ elements: MessageElement[],
61
+ ): { keyboard: MessageElement & { data: KeyboardSegmentData }; rest: MessageElement[] } | null {
62
+ let keyboard: (MessageElement & { data: KeyboardSegmentData }) | undefined;
63
+ const rest: MessageElement[] = [];
64
+
65
+ for (const el of elements) {
66
+ const kb = asKeyboardElement(el);
67
+ if (kb) {
68
+ if (keyboard) return null;
69
+ keyboard = kb;
70
+ continue;
71
+ }
72
+ rest.push(el);
73
+ }
74
+
75
+ if (!keyboard) return null;
76
+ return { keyboard, rest };
77
+ }
@@ -1,14 +1,7 @@
1
1
  /**
2
2
  * KOOK 出站 media:base64 / 本地文件须先走 /v3/asset/create 上传,再嵌入 KMarkdown。
3
3
  */
4
- import type { MessageElement, MessageSegment, SendContent } from "zhin.js";
5
- import {
6
- asMessageElements,
7
- extractBase64Buffer,
8
- isMediaSegmentType,
9
- isRemoteUrl,
10
- resolveLocalMediaPath,
11
- } from "zhin.js";
4
+ import { asMessageElements, extractBase64Buffer, isMediaSegmentType, isRemoteUrl, resolveLocalMediaPath, type MessageElement, type MessageSegment, type SendContent } from 'zhin.js';
12
5
 
13
6
  export interface KookMediaUploader {
14
7
  uploadMedia(data: string | Buffer): Promise<string>;
@@ -4,6 +4,9 @@
4
4
  */
5
5
  import { segment, type MessageSegment as KookMessageSegment } from "kook-client";
6
6
  import type { MessageElement } from "zhin.js";
7
+ import { coreKeyboardToKookCard, findKeyboardSegment } from "./outbound-keyboard.js";
8
+
9
+ export type KookSendable = string | KookMessageSegment[] | Array<Record<string, unknown>>;
7
10
 
8
11
  function mediaUrl(data: Record<string, unknown>): string {
9
12
  return String(data.url ?? data.file ?? "");
@@ -15,7 +18,16 @@ function mediaUrl(data: Record<string, unknown>): string {
15
18
  export function convertToKookSendable(
16
19
  elements: MessageElement[],
17
20
  formatText: (content: MessageElement[]) => string,
18
- ): string | KookMessageSegment[] {
21
+ ): KookSendable {
22
+ const keyboardBundle = findKeyboardSegment(elements);
23
+ if (keyboardBundle) {
24
+ const { keyboard, rest } = keyboardBundle;
25
+ const nonReplyRest = rest.filter((el) => el.type !== "reply");
26
+ if (nonReplyRest.every((el) => el.type === "text" || el.type === "reply")) {
27
+ return coreKeyboardToKookCard(keyboard.data, nonReplyRest.filter((el) => el.type === "text"));
28
+ }
29
+ }
30
+
19
31
  const replies: string[] = [];
20
32
  const images: string[] = [];
21
33
  const videos: string[] = [];
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * KOOK platform permit — guild / channel 差异化门禁
3
3
  */
4
- import type { Message } from 'zhin.js';
5
- import { registerPlatformPermitChecker } from 'zhin.js';
6
- import { KookPermission } from './types.js';
7
- import type { KookSenderInfo } from './types.js';
4
+ import { registerPlatformPermitChecker, type Message } from 'zhin.js';
5
+
6
+ import { KookPermission, type KookSenderInfo } from './types.js';
8
7
 
9
8
  const ADAPTER = 'kook';
10
9
 
@@ -13,8 +12,8 @@ export function platformPermit(perm: string): string {
13
12
  }
14
13
 
15
14
  const FACTORY_PERM_MAP: Record<string, string> = {
16
- group_admin: 'guild_admin',
17
- group_owner: 'guild_owner',
15
+ scene_admin: 'guild_admin',
16
+ scene_owner: 'guild_owner',
18
17
  };
19
18
 
20
19
  export function kookGroupPermitResolver(logicalPerm: string): string {
@@ -0,0 +1 @@
1
+ export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
package/src/types.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * KOOK 适配器类型与配置
3
3
  */
4
- import type { PrivateMessageEvent, ChannelMessageEvent } from "kook-client";
5
- import type { LogLevel } from "kook-client";
4
+ import { type PrivateMessageEvent, type ChannelMessageEvent, type LogLevel } from 'kook-client';
6
5
 
7
6
  export type { LogLevel };
8
7
 
package/lib/adapter.d.ts DELETED
@@ -1,29 +0,0 @@
1
- /**
2
- * KOOK 适配器
3
- */
4
- import { Adapter, Plugin } from 'zhin.js';
5
- import { KookEndpoint } from "./endpoint.js";
6
- import type { KookEndpointConfig } from "./types.js";
7
- import type { OutboundRichSegmentPolicy } from "zhin.js";
8
- export declare class KookAdapter extends Adapter<KookEndpoint> {
9
- static readonly capabilities: readonly ["inbound", "outbound"];
10
- static outboundRichSegmentPolicy: OutboundRichSegmentPolicy;
11
- constructor(plugin: Plugin);
12
- createEndpoint(config: KookEndpointConfig): KookEndpoint;
13
- kickMember(endpointId: string, sceneId: string, userId: string): Promise<boolean>;
14
- banMember(endpointId: string, sceneId: string, userId: string, reason?: string): Promise<boolean>;
15
- unbanMember(endpointId: string, sceneId: string, userId: string): Promise<boolean>;
16
- setMemberNickname(endpointId: string, sceneId: string, userId: string, nickname: string): Promise<boolean>;
17
- listMembers(endpointId: string, sceneId: string): Promise<{
18
- members: {
19
- id: string;
20
- username: string;
21
- nickname: string;
22
- roles: number[];
23
- }[];
24
- count: number;
25
- }>;
26
- start(): Promise<void>;
27
- stop(): Promise<void>;
28
- }
29
- //# sourceMappingURL=adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAiB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAEzD,qBAAa,WAAY,SAAQ,OAAO,CAAC,YAAY,CAAC;IACpD,gBAAyB,YAAY,mCAAoC;IACzE,OAAgB,yBAAyB,EAAE,yBAAyB,CAIlE;gBAEU,MAAM,EAAE,MAAM;IAI1B,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY;IAMlD,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM9D,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAM9E,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM/D,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAMvF,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;IAe/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,OAAO,EAAU,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAI7C,MAAM,OAAO,WAAY,SAAQ,OAAqB;IACpD,MAAM,CAAmB,YAAY,GAAG,CAAC,SAAS,EAAE,UAAU,CAAU,CAAC;IACzE,MAAM,CAAU,yBAAyB,GAA8B;QACrE,MAAM,EAAE,OAAO;QACf,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,QAAQ;KACnB,CAAC;IAEF,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,cAAc,CAAC,MAA0B;QACvC,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAED,gEAAgE;IAEhE,KAAK,CAAC,UAAU,CAAC,UAAkB,EAAE,OAAe,EAAE,MAAc;QAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,OAAe,EAAE,MAAc,EAAE,MAAe;QAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,OAAe,EAAE,MAAc;QACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,UAAkB,EAAE,OAAe,EAAE,MAAc,EAAE,QAAgB;QAC3F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,OAAe;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzB,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK;aACrC,CAAC,CAAC;YACH,KAAK,EAAE,OAAO,CAAC,MAAM;SACtB,CAAC;IACJ,CAAC;IAED,kEAAkE;IAElE,KAAK,CAAC,KAAK;QACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC"}