@zhin.js/adapter-kook 5.0.1 → 5.0.3
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 +60 -0
- package/README.md +58 -451
- package/adapters/kook.ts +46 -0
- package/agent/tools/blacklist.ts +33 -0
- package/agent/tools/create_role.ts +25 -0
- package/agent/tools/delete_role.ts +21 -0
- package/agent/tools/grant_role.ts +22 -0
- package/agent/tools/list_roles.ts +27 -0
- package/agent/tools/revoke_role.ts +22 -0
- package/lib/endpoint.d.ts +80 -151
- package/lib/endpoint.js +260 -736
- package/lib/index.d.ts +6 -18
- package/lib/index.js +6 -350
- package/lib/kook-agent-deps.d.ts +29 -0
- package/lib/kook-agent-deps.js +30 -0
- package/lib/platform-permit.d.ts +12 -3
- package/lib/platform-permit.js +5 -3
- package/lib/protocol.d.ts +147 -0
- package/lib/protocol.js +279 -0
- package/lib/webhook.d.ts +15 -0
- package/lib/webhook.js +76 -0
- package/lib/ws.d.ts +42 -0
- package/lib/ws.js +63 -0
- package/package.json +47 -33
- package/plugin.ts +13 -0
- package/schema.json +55 -0
- package/src/endpoint.ts +287 -866
- package/src/index.ts +55 -331
- package/src/kook-agent-deps.ts +55 -0
- package/src/platform-permit.ts +13 -4
- package/src/protocol.ts +429 -0
- package/src/webhook.ts +116 -0
- package/src/ws.ts +120 -0
- package/client/Dashboard.tsx +0 -255
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -31
- package/lib/adapter.d.ts +0 -30
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -67
- package/lib/adapter.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/kook-asset-upload.d.ts +0 -12
- package/lib/kook-asset-upload.d.ts.map +0 -1
- package/lib/kook-asset-upload.js +0 -46
- package/lib/kook-asset-upload.js.map +0 -1
- package/lib/kook-inbound.d.ts +0 -6
- package/lib/kook-inbound.d.ts.map +0 -1
- package/lib/kook-inbound.js +0 -22
- package/lib/kook-inbound.js.map +0 -1
- package/lib/kook-msg-route.d.ts +0 -34
- package/lib/kook-msg-route.d.ts.map +0 -1
- package/lib/kook-msg-route.js +0 -106
- package/lib/kook-msg-route.js.map +0 -1
- package/lib/kook-side-events.d.ts +0 -46
- package/lib/kook-side-events.d.ts.map +0 -1
- package/lib/kook-side-events.js +0 -189
- package/lib/kook-side-events.js.map +0 -1
- package/lib/outbound-keyboard.d.ts +0 -13
- package/lib/outbound-keyboard.d.ts.map +0 -1
- package/lib/outbound-keyboard.js +0 -63
- package/lib/outbound-keyboard.js.map +0 -1
- package/lib/outbound-media.d.ts +0 -10
- package/lib/outbound-media.d.ts.map +0 -1
- package/lib/outbound-media.js +0 -42
- package/lib/outbound-media.js.map +0 -1
- package/lib/outbound-sendable.d.ts +0 -12
- package/lib/outbound-sendable.d.ts.map +0 -1
- package/lib/outbound-sendable.js +0 -64
- package/lib/outbound-sendable.js.map +0 -1
- package/lib/platform-permit.d.ts.map +0 -1
- package/lib/platform-permit.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -54
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -8
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -76
- package/src/kook-asset-upload.ts +0 -55
- package/src/kook-inbound.ts +0 -22
- package/src/kook-msg-route.ts +0 -122
- package/src/kook-side-events.ts +0 -250
- package/src/outbound-keyboard.ts +0 -78
- package/src/outbound-media.ts +0 -67
- package/src/outbound-sendable.ts +0 -73
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -59
- /package/{skills/kook → agent}/PERMITS.md +0 -0
- /package/{skills/kook/SKILL.md → agent/skills/kook.md} +0 -0
package/src/kook-side-events.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KOOK Gateway 非聊天入站:系统消息 (type=255) / BROADCAST → Notice
|
|
3
|
-
* @see https://developer.kookapp.cn/doc/event/event-introduction
|
|
4
|
-
*/
|
|
5
|
-
import { Notice, formatCompact, Message, type NoticeType } from "zhin.js";
|
|
6
|
-
|
|
7
|
-
/** KOOK WebSocket s=0 时 d 字段(kook-client Receiver 入参) */
|
|
8
|
-
export interface KookGatewayEvent {
|
|
9
|
-
channel_type?: string;
|
|
10
|
-
type?: number;
|
|
11
|
-
target_id?: string;
|
|
12
|
-
author_id?: string;
|
|
13
|
-
content?: string;
|
|
14
|
-
msg_id?: string;
|
|
15
|
-
msg_timestamp?: number;
|
|
16
|
-
nonce?: string;
|
|
17
|
-
extra?: KookGatewayExtra;
|
|
18
|
-
[key: string]: unknown;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface KookGatewayExtra {
|
|
22
|
-
type?: string;
|
|
23
|
-
body?: Record<string, unknown>;
|
|
24
|
-
guild_id?: string;
|
|
25
|
-
channel_id?: string;
|
|
26
|
-
channel_name?: string;
|
|
27
|
-
author?: { id?: string; username?: string; nickname?: string };
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const SYSTEM_MESSAGE_TYPE = 255;
|
|
32
|
-
|
|
33
|
-
/** 是否应由适配器作为 notice 处理(kook-client 对 notice 的 transform 为空实现) */
|
|
34
|
-
export function isKookNoticeGatewayEvent(data: unknown): data is KookGatewayEvent {
|
|
35
|
-
if (!data || typeof data !== "object") return false;
|
|
36
|
-
const ev = data as KookGatewayEvent;
|
|
37
|
-
if (ev.type === SYSTEM_MESSAGE_TYPE) return true;
|
|
38
|
-
return ev.channel_type === "BROADCAST";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function resolveKookSideEventDedupeKey(
|
|
42
|
-
event: KookGatewayEvent,
|
|
43
|
-
kind: "notice" | "gateway",
|
|
44
|
-
): string {
|
|
45
|
-
if (event.msg_id) return `${kind}:${event.msg_id}`;
|
|
46
|
-
const extra = event.extra ?? {};
|
|
47
|
-
const noticeType = extra.type ?? String(event.type ?? "");
|
|
48
|
-
const scope = event.target_id ?? "";
|
|
49
|
-
const ts = event.msg_timestamp ?? 0;
|
|
50
|
-
return `${kind}:${ts}_${noticeType}_${scope}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
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
|
-
function readString(obj: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
75
|
-
const v = obj?.[key];
|
|
76
|
-
if (v == null || v === "") return undefined;
|
|
77
|
-
return String(v);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** 解析 notice 发生的频道 / 群 / 私聊 */
|
|
81
|
-
export function resolveKookNoticeChannel(
|
|
82
|
-
event: KookGatewayEvent,
|
|
83
|
-
): { id: string; type: "group" | "private" | "channel" } {
|
|
84
|
-
const extra = event.extra ?? {};
|
|
85
|
-
const body = extra.body ?? {};
|
|
86
|
-
const rawType = String(extra.type ?? "");
|
|
87
|
-
|
|
88
|
-
if (event.channel_type === "BROADCAST") {
|
|
89
|
-
const guildId =
|
|
90
|
-
readString(body, "guild_id")
|
|
91
|
-
?? extra.guild_id
|
|
92
|
-
?? event.target_id
|
|
93
|
-
?? "";
|
|
94
|
-
return { id: guildId, type: "group" };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (event.type === SYSTEM_MESSAGE_TYPE && event.channel_type === "GROUP") {
|
|
98
|
-
const guildId =
|
|
99
|
-
readString(body, "guild_id")
|
|
100
|
-
?? extra.guild_id
|
|
101
|
-
?? event.target_id
|
|
102
|
-
?? "";
|
|
103
|
-
if (
|
|
104
|
-
rawType === "joined_guild"
|
|
105
|
-
|| rawType === "exited_guild"
|
|
106
|
-
|| rawType === "updated_guild"
|
|
107
|
-
|| rawType === "deleted_guild"
|
|
108
|
-
|| rawType.startsWith("added_role")
|
|
109
|
-
|| rawType.startsWith("deleted_role")
|
|
110
|
-
|| rawType.startsWith("updated_role")
|
|
111
|
-
|| rawType.startsWith("added_block")
|
|
112
|
-
|| rawType.startsWith("deleted_block")
|
|
113
|
-
|| rawType === "self_joined_guild"
|
|
114
|
-
|| rawType === "self_exited_guild"
|
|
115
|
-
) {
|
|
116
|
-
return { id: guildId, type: "group" };
|
|
117
|
-
}
|
|
118
|
-
const channelId =
|
|
119
|
-
readString(body, "channel_id")
|
|
120
|
-
?? extra.channel_id
|
|
121
|
-
?? event.target_id
|
|
122
|
-
?? "";
|
|
123
|
-
return { id: channelId, type: "channel" };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
rawType.startsWith("private_")
|
|
128
|
-
|| rawType.includes("private_message")
|
|
129
|
-
|| event.channel_type === "PERSON"
|
|
130
|
-
) {
|
|
131
|
-
const userId =
|
|
132
|
-
readString(body, "user_id")
|
|
133
|
-
?? readString(body, "target_id")
|
|
134
|
-
?? event.target_id
|
|
135
|
-
?? event.author_id
|
|
136
|
-
?? "";
|
|
137
|
-
return { id: userId, type: "private" };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const channelId =
|
|
141
|
-
readString(body, "channel_id")
|
|
142
|
-
?? extra.channel_id
|
|
143
|
-
?? event.target_id
|
|
144
|
-
?? "";
|
|
145
|
-
return { id: channelId, type: "channel" };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function enrichKookGatewayForPlugins(event: KookGatewayEvent): KookGatewayEvent {
|
|
149
|
-
const extra = event.extra ?? {};
|
|
150
|
-
const noticeType = extra.type;
|
|
151
|
-
if (!isKookNoticeGatewayEvent(event)) {
|
|
152
|
-
return { ...(event as object), post_type: "message" } as KookGatewayEvent;
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
...(event as object),
|
|
156
|
-
post_type: "notice",
|
|
157
|
-
notice_type: noticeType ?? "system",
|
|
158
|
-
} as KookGatewayEvent;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function formatKookNotice(
|
|
162
|
-
event: KookGatewayEvent,
|
|
163
|
-
endpointName: string,
|
|
164
|
-
): ReturnType<typeof Notice.from<KookGatewayEvent>> {
|
|
165
|
-
const extra = event.extra ?? {};
|
|
166
|
-
const body = extra.body ?? {};
|
|
167
|
-
const rawType = String(extra.type ?? "unknown");
|
|
168
|
-
const $type = resolveNoticeZhinType(rawType);
|
|
169
|
-
const channel = resolveKookNoticeChannel(event);
|
|
170
|
-
const ts = event.msg_timestamp ?? Date.now();
|
|
171
|
-
|
|
172
|
-
const targetId =
|
|
173
|
-
readString(body, "user_id")
|
|
174
|
-
?? readString(body, "msg_id")
|
|
175
|
-
?? readString(body, "target_id");
|
|
176
|
-
const operatorId =
|
|
177
|
-
readString(body, "operator_id")
|
|
178
|
-
?? (event.author_id !== "1" ? event.author_id : undefined);
|
|
179
|
-
|
|
180
|
-
const mapped = NOTICE_TYPE_MAP[rawType] != null;
|
|
181
|
-
|
|
182
|
-
return Notice.from(event, {
|
|
183
|
-
$id: resolveKookSideEventDedupeKey(event, "notice"),
|
|
184
|
-
$adapter: "kook",
|
|
185
|
-
$endpoint: endpointName,
|
|
186
|
-
$type,
|
|
187
|
-
$subType: mapped ? rawType : undefined,
|
|
188
|
-
$channel: channel,
|
|
189
|
-
$operator: senderFromId(operatorId),
|
|
190
|
-
$target: senderFromId(targetId),
|
|
191
|
-
$timestamp: ts,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function formatKookNoticeLog(notice: ReturnType<typeof formatKookNotice>): string {
|
|
196
|
-
return formatCompact({
|
|
197
|
-
notice: notice.$type,
|
|
198
|
-
kook_type: notice.$subType,
|
|
199
|
-
channel: `${notice.$channel.type}(${notice.$channel.id})`,
|
|
200
|
-
endpoint: notice.$endpoint,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** KOOK 卡片按钮点击(type=255, extra.type=message_btn_click) */
|
|
205
|
-
export function isKookButtonClickEvent(event: KookGatewayEvent): boolean {
|
|
206
|
-
return event.type === SYSTEM_MESSAGE_TYPE && event.extra?.type === 'message_btn_click';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function formatKookButtonClickMessage(
|
|
210
|
-
event: KookGatewayEvent,
|
|
211
|
-
endpointName: string,
|
|
212
|
-
): ReturnType<typeof Message.from<KookGatewayEvent>> {
|
|
213
|
-
const extra = event.extra ?? {};
|
|
214
|
-
const body = extra.body ?? {};
|
|
215
|
-
const payload = String(body.value ?? '');
|
|
216
|
-
const userInfo = body.user_info as { id?: string; username?: string; nickname?: string } | undefined;
|
|
217
|
-
const userId = String(body.user_id ?? userInfo?.id ?? event.author_id ?? '');
|
|
218
|
-
const userName = userInfo?.nickname ?? userInfo?.username ?? userId;
|
|
219
|
-
const sourceMessageId = String(body.msg_id ?? '');
|
|
220
|
-
|
|
221
|
-
let channelType: 'private' | 'channel' = 'private';
|
|
222
|
-
let channelId = String(body.target_id ?? event.target_id ?? userId);
|
|
223
|
-
if (body.channel_type === 'GROUP' || event.channel_type === 'GROUP') {
|
|
224
|
-
channelType = 'channel';
|
|
225
|
-
channelId = String(body.target_id ?? event.target_id ?? channelId);
|
|
226
|
-
} else if (body.channel_type === 'PERSON' || event.channel_type === 'PERSON') {
|
|
227
|
-
channelType = 'private';
|
|
228
|
-
channelId = String(body.target_id ?? event.target_id ?? userId);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return Message.from(event, {
|
|
232
|
-
$id: event.msg_id ?? `btn:${Date.now()}`,
|
|
233
|
-
$adapter: 'kook',
|
|
234
|
-
$endpoint: endpointName,
|
|
235
|
-
$sender: { id: userId, name: userName },
|
|
236
|
-
$channel: { id: channelId, type: channelType },
|
|
237
|
-
$content: [{
|
|
238
|
-
type: 'action',
|
|
239
|
-
data: {
|
|
240
|
-
id: payload,
|
|
241
|
-
payload,
|
|
242
|
-
sourceMessageId,
|
|
243
|
-
},
|
|
244
|
-
}],
|
|
245
|
-
$raw: payload,
|
|
246
|
-
$timestamp: event.msg_timestamp ?? Date.now(),
|
|
247
|
-
$recall: async () => {},
|
|
248
|
-
$reply: async () => '',
|
|
249
|
-
});
|
|
250
|
-
}
|
package/src/outbound-keyboard.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* core keyboard 段 → KOOK 卡片消息(action-group + button)
|
|
3
|
-
*/
|
|
4
|
-
import type { KeyboardSegmentData, MessageElement } from "zhin.js";
|
|
5
|
-
import { KeyboardSegment, isKeyboardSegment } from "zhin.js";
|
|
6
|
-
|
|
7
|
-
function asKeyboardElement(
|
|
8
|
-
el: MessageElement,
|
|
9
|
-
): (MessageElement & { data: KeyboardSegmentData }) | null {
|
|
10
|
-
if (el instanceof KeyboardSegment) {
|
|
11
|
-
return { type: "keyboard", data: el.data };
|
|
12
|
-
}
|
|
13
|
-
if (isKeyboardSegment(el)) return el;
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function textFromElements(elements: MessageElement[]): string {
|
|
18
|
-
return elements
|
|
19
|
-
.filter((el) => el.type === "text" && typeof el.data.text === "string")
|
|
20
|
-
.map((el) => el.data.text as string)
|
|
21
|
-
.join("\n")
|
|
22
|
-
.trim();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** core keyboard 布局 → KOOK card[](可直接传给 sendChannelMsg / sendPrivateMsg) */
|
|
26
|
-
export function coreKeyboardToKookCard(
|
|
27
|
-
data: KeyboardSegmentData,
|
|
28
|
-
prefixElements: MessageElement[] = [],
|
|
29
|
-
): Array<Record<string, unknown>> {
|
|
30
|
-
const modules: Array<Record<string, unknown>> = [];
|
|
31
|
-
const prefixText = textFromElements(prefixElements);
|
|
32
|
-
if (prefixText) {
|
|
33
|
-
modules.push({
|
|
34
|
-
type: "section",
|
|
35
|
-
text: { type: "plain-text", content: prefixText },
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
for (const row of data.rows ?? []) {
|
|
40
|
-
modules.push({
|
|
41
|
-
type: "action-group",
|
|
42
|
-
elements: row.map((btn) => ({
|
|
43
|
-
type: "button",
|
|
44
|
-
theme: btn.disabled ? "secondary" : "primary",
|
|
45
|
-
value: btn.payload,
|
|
46
|
-
click: "return-val",
|
|
47
|
-
text: { type: "plain-text", content: btn.label },
|
|
48
|
-
})),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return [{
|
|
53
|
-
type: "card",
|
|
54
|
-
theme: "secondary",
|
|
55
|
-
size: "sm",
|
|
56
|
-
modules,
|
|
57
|
-
}];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function findKeyboardSegment(
|
|
61
|
-
elements: MessageElement[],
|
|
62
|
-
): { keyboard: MessageElement & { data: KeyboardSegmentData }; rest: MessageElement[] } | null {
|
|
63
|
-
let keyboard: (MessageElement & { data: KeyboardSegmentData }) | undefined;
|
|
64
|
-
const rest: MessageElement[] = [];
|
|
65
|
-
|
|
66
|
-
for (const el of elements) {
|
|
67
|
-
const kb = asKeyboardElement(el);
|
|
68
|
-
if (kb) {
|
|
69
|
-
if (keyboard) return null;
|
|
70
|
-
keyboard = kb;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
rest.push(el);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!keyboard) return null;
|
|
77
|
-
return { keyboard, rest };
|
|
78
|
-
}
|
package/src/outbound-media.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KOOK 出站 media:base64 / 本地文件须先走 /v3/asset/create 上传,再嵌入 KMarkdown。
|
|
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";
|
|
12
|
-
|
|
13
|
-
export interface KookMediaUploader {
|
|
14
|
-
uploadMedia(data: string | Buffer): Promise<string>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function isMessageSegment(seg: MessageElement): seg is MessageSegment {
|
|
18
|
-
return typeof seg.type === "string";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function withUploadedUrl(seg: MessageSegment, uploadedUrl: string): MessageSegment {
|
|
22
|
-
const data = { ...seg.data } as Record<string, unknown>;
|
|
23
|
-
data.url = uploadedUrl;
|
|
24
|
-
data.file = uploadedUrl;
|
|
25
|
-
delete data.base64;
|
|
26
|
-
return { type: seg.type, data } as MessageSegment;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function resolveMediaSegment(
|
|
30
|
-
uploader: KookMediaUploader,
|
|
31
|
-
seg: MessageSegment,
|
|
32
|
-
): Promise<MessageSegment> {
|
|
33
|
-
if (!isMediaSegmentType(seg.type)) return seg;
|
|
34
|
-
|
|
35
|
-
const data = seg.data as Record<string, unknown>;
|
|
36
|
-
const url = typeof data.url === "string" ? data.url : undefined;
|
|
37
|
-
const file = typeof data.file === "string" ? data.file : undefined;
|
|
38
|
-
if ((url && isRemoteUrl(url)) || (file && isRemoteUrl(file))) {
|
|
39
|
-
return seg;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const buffer = extractBase64Buffer(data);
|
|
43
|
-
if (buffer) {
|
|
44
|
-
const uploaded = await uploader.uploadMedia(buffer);
|
|
45
|
-
return withUploadedUrl(seg, uploaded);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const local = resolveLocalMediaPath(data);
|
|
49
|
-
if (local) {
|
|
50
|
-
const uploaded = await uploader.uploadMedia(local);
|
|
51
|
-
return withUploadedUrl(seg, uploaded);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return seg;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 将 base64 / 本地媒体上传为 KOOK 可访问 URL */
|
|
58
|
-
export async function materializeOutboundMedia(
|
|
59
|
-
uploader: KookMediaUploader,
|
|
60
|
-
content: SendContent,
|
|
61
|
-
): Promise<MessageElement[]> {
|
|
62
|
-
const segments = asMessageElements(content);
|
|
63
|
-
return Promise.all(segments.map(async (item) => {
|
|
64
|
-
if (!isMessageSegment(item)) return item;
|
|
65
|
-
return resolveMediaSegment(uploader, item);
|
|
66
|
-
}));
|
|
67
|
-
}
|
package/src/outbound-sendable.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zhin 消息段 → kook-client Sendable。
|
|
3
|
-
* 图片/音视频须用 segment 发送对应 msg_type,不能写成 Markdown 。
|
|
4
|
-
*/
|
|
5
|
-
import { segment, type MessageSegment as KookMessageSegment } from "kook-client";
|
|
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>>;
|
|
10
|
-
|
|
11
|
-
function mediaUrl(data: Record<string, unknown>): string {
|
|
12
|
-
return String(data.url ?? data.file ?? "");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 单张图片(可带 reply)→ kook image 消息段(type=2)
|
|
17
|
-
*/
|
|
18
|
-
export function convertToKookSendable(
|
|
19
|
-
elements: MessageElement[],
|
|
20
|
-
formatText: (content: MessageElement[]) => string,
|
|
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
|
-
|
|
31
|
-
const replies: string[] = [];
|
|
32
|
-
const images: string[] = [];
|
|
33
|
-
const videos: string[] = [];
|
|
34
|
-
const audios: string[] = [];
|
|
35
|
-
const rest: MessageElement[] = [];
|
|
36
|
-
|
|
37
|
-
for (const el of elements) {
|
|
38
|
-
if (el.type === "reply" && el.data.id) {
|
|
39
|
-
replies.push(String(el.data.id));
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (el.type === "image") {
|
|
43
|
-
const url = mediaUrl(el.data as Record<string, unknown>);
|
|
44
|
-
if (url) images.push(url);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (el.type === "video") {
|
|
48
|
-
const url = mediaUrl(el.data as Record<string, unknown>);
|
|
49
|
-
if (url) videos.push(url);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (el.type === "audio") {
|
|
53
|
-
const url = mediaUrl(el.data as Record<string, unknown>);
|
|
54
|
-
if (url) audios.push(url);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
rest.push(el);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const replySegs = replies.map((id) => segment.reply(id));
|
|
61
|
-
|
|
62
|
-
if (images.length === 1 && videos.length === 0 && audios.length === 0 && rest.length === 0) {
|
|
63
|
-
return [...replySegs, segment.image(images[0])];
|
|
64
|
-
}
|
|
65
|
-
if (videos.length === 1 && images.length === 0 && audios.length === 0 && rest.length === 0) {
|
|
66
|
-
return [...replySegs, segment.video(videos[0])];
|
|
67
|
-
}
|
|
68
|
-
if (audios.length === 1 && images.length === 0 && videos.length === 0 && rest.length === 0) {
|
|
69
|
-
return [...replySegs, segment.audio(audios[0])];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return formatText(elements);
|
|
73
|
-
}
|
package/src/segment-mapper.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
|
package/src/types.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KOOK 适配器类型与配置
|
|
3
|
-
*/
|
|
4
|
-
import type { PrivateMessageEvent, ChannelMessageEvent } from "kook-client";
|
|
5
|
-
import type { LogLevel } from "kook-client";
|
|
6
|
-
|
|
7
|
-
export type { LogLevel };
|
|
8
|
-
|
|
9
|
-
export enum KookPermission {
|
|
10
|
-
Normal = 1,
|
|
11
|
-
Admin = 2,
|
|
12
|
-
Owner = 4,
|
|
13
|
-
ChannelAdmin = 5,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface KookSenderInfo {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
permission?: KookPermission;
|
|
20
|
-
roles?: number[];
|
|
21
|
-
isGuildOwner?: boolean;
|
|
22
|
-
isAdmin?: boolean;
|
|
23
|
-
/** 归一化平台身份(供 platform checker) */
|
|
24
|
-
role?: string;
|
|
25
|
-
permissions?: string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface KookTypingIndicatorConfig {
|
|
29
|
-
enabled?: boolean;
|
|
30
|
-
defaultEmoji?: string;
|
|
31
|
-
autoRemove?: boolean;
|
|
32
|
-
removeDelay?: number;
|
|
33
|
-
privateConfig?: {
|
|
34
|
-
type?: 'reaction' | 'message' | 'typing' | 'none';
|
|
35
|
-
emoji?: string;
|
|
36
|
-
message?: string;
|
|
37
|
-
};
|
|
38
|
-
/** 频道/群聊(KOOK channel 走 groupConfig 合并逻辑) */
|
|
39
|
-
groupConfig?: {
|
|
40
|
-
type?: 'reaction' | 'message' | 'typing' | 'none';
|
|
41
|
-
emoji?: string;
|
|
42
|
-
message?: string;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface KookEndpointConfig {
|
|
47
|
-
context: "kook";
|
|
48
|
-
name: string;
|
|
49
|
-
token: string;
|
|
50
|
-
data_dir?: string;
|
|
51
|
-
timeout?: number;
|
|
52
|
-
max_retry?: number;
|
|
53
|
-
ignore?: "bot" | "self";
|
|
54
|
-
logLevel?: LogLevel;
|
|
55
|
-
/** AI 处理中提示(reaction 推荐:不打断会话) */
|
|
56
|
-
typingIndicator?: KookTypingIndicatorConfig;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type KookRawMessage = PrivateMessageEvent | ChannelMessageEvent;
|
|
File without changes
|
|
File without changes
|