@leoqlin/openclaw-qqbot 1.6.7-alpha1
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/LICENSE +22 -0
- package/README.md +484 -0
- package/README.zh.md +479 -0
- package/bin/qqbot-cli.js +243 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +26 -0
- package/dist/src/admin-resolver.d.ts +33 -0
- package/dist/src/admin-resolver.js +157 -0
- package/dist/src/api.d.ts +301 -0
- package/dist/src/api.js +890 -0
- package/dist/src/channel.d.ts +29 -0
- package/dist/src/channel.js +452 -0
- package/dist/src/config.d.ts +56 -0
- package/dist/src/config.js +278 -0
- package/dist/src/credential-backup.d.ts +31 -0
- package/dist/src/credential-backup.js +66 -0
- package/dist/src/deliver-debounce.d.ts +74 -0
- package/dist/src/deliver-debounce.js +174 -0
- package/dist/src/gateway.d.ts +18 -0
- package/dist/src/gateway.js +2005 -0
- package/dist/src/group-history.d.ts +136 -0
- package/dist/src/group-history.js +226 -0
- package/dist/src/image-server.d.ts +87 -0
- package/dist/src/image-server.js +570 -0
- package/dist/src/inbound-attachments.d.ts +60 -0
- package/dist/src/inbound-attachments.js +248 -0
- package/dist/src/known-users.d.ts +100 -0
- package/dist/src/known-users.js +263 -0
- package/dist/src/message-gating.d.ts +53 -0
- package/dist/src/message-gating.js +107 -0
- package/dist/src/message-queue.d.ts +89 -0
- package/dist/src/message-queue.js +257 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +203 -0
- package/dist/src/outbound-deliver.d.ts +48 -0
- package/dist/src/outbound-deliver.js +392 -0
- package/dist/src/outbound.d.ts +205 -0
- package/dist/src/outbound.js +938 -0
- package/dist/src/proactive.d.ts +170 -0
- package/dist/src/proactive.js +399 -0
- package/dist/src/ref-index-store.d.ts +101 -0
- package/dist/src/ref-index-store.js +298 -0
- package/dist/src/reply-dispatcher.d.ts +35 -0
- package/dist/src/reply-dispatcher.js +311 -0
- package/dist/src/request-context.d.ts +25 -0
- package/dist/src/request-context.js +37 -0
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/session-store.d.ts +52 -0
- package/dist/src/session-store.js +254 -0
- package/dist/src/slash-commands.d.ts +77 -0
- package/dist/src/slash-commands.js +1866 -0
- package/dist/src/startup-greeting.d.ts +30 -0
- package/dist/src/startup-greeting.js +97 -0
- package/dist/src/streaming.d.ts +247 -0
- package/dist/src/streaming.js +899 -0
- package/dist/src/stt.d.ts +21 -0
- package/dist/src/stt.js +70 -0
- package/dist/src/tools/channel.d.ts +16 -0
- package/dist/src/tools/channel.js +234 -0
- package/dist/src/tools/remind.d.ts +2 -0
- package/dist/src/tools/remind.js +256 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.js +17 -0
- package/dist/src/typing-keepalive.d.ts +27 -0
- package/dist/src/typing-keepalive.js +64 -0
- package/dist/src/update-checker.d.ts +36 -0
- package/dist/src/update-checker.js +171 -0
- package/dist/src/utils/audio-convert.d.ts +98 -0
- package/dist/src/utils/audio-convert.js +755 -0
- package/dist/src/utils/chunked-upload.d.ts +68 -0
- package/dist/src/utils/chunked-upload.js +341 -0
- package/dist/src/utils/file-utils.d.ts +61 -0
- package/dist/src/utils/file-utils.js +172 -0
- package/dist/src/utils/image-size.d.ts +51 -0
- package/dist/src/utils/image-size.js +234 -0
- package/dist/src/utils/media-send.d.ts +158 -0
- package/dist/src/utils/media-send.js +499 -0
- package/dist/src/utils/media-tags.d.ts +14 -0
- package/dist/src/utils/media-tags.js +165 -0
- package/dist/src/utils/payload.d.ts +112 -0
- package/dist/src/utils/payload.js +186 -0
- package/dist/src/utils/pkg-version.d.ts +5 -0
- package/dist/src/utils/pkg-version.js +61 -0
- package/dist/src/utils/platform.d.ts +137 -0
- package/dist/src/utils/platform.js +390 -0
- package/dist/src/utils/ssrf-guard.d.ts +25 -0
- package/dist/src/utils/ssrf-guard.js +91 -0
- package/dist/src/utils/text-parsing.d.ts +36 -0
- package/dist/src/utils/text-parsing.js +75 -0
- package/dist/src/utils/upload-cache.d.ts +34 -0
- package/dist/src/utils/upload-cache.js +93 -0
- package/index.ts +31 -0
- package/node_modules/@eshaz/web-worker/LICENSE +201 -0
- package/node_modules/@eshaz/web-worker/README.md +134 -0
- package/node_modules/@eshaz/web-worker/browser.js +17 -0
- package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
- package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
- package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
- package/node_modules/@eshaz/web-worker/node.js +223 -0
- package/node_modules/@eshaz/web-worker/package.json +54 -0
- package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
- package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
- package/node_modules/mpg123-decoder/README.md +265 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
- package/node_modules/mpg123-decoder/index.js +8 -0
- package/node_modules/mpg123-decoder/package.json +58 -0
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
- package/node_modules/mpg123-decoder/types.d.ts +30 -0
- package/node_modules/silk-wasm/LICENSE +21 -0
- package/node_modules/silk-wasm/README.md +85 -0
- package/node_modules/silk-wasm/lib/index.cjs +16 -0
- package/node_modules/silk-wasm/lib/index.d.ts +70 -0
- package/node_modules/silk-wasm/lib/index.mjs +16 -0
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
- package/node_modules/silk-wasm/package.json +39 -0
- package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
- package/node_modules/simple-yenc/.prettierignore +1 -0
- package/node_modules/simple-yenc/LICENSE +7 -0
- package/node_modules/simple-yenc/README.md +163 -0
- package/node_modules/simple-yenc/dist/esm.js +1 -0
- package/node_modules/simple-yenc/dist/index.js +1 -0
- package/node_modules/simple-yenc/package.json +50 -0
- package/node_modules/simple-yenc/rollup.config.js +27 -0
- package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +706 -0
- package/node_modules/ws/lib/sender.js +602 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +554 -0
- package/node_modules/ws/lib/websocket.js +1393 -0
- package/node_modules/ws/package.json +70 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +70 -0
- package/preload.cjs +33 -0
- package/scripts/cleanup-legacy-plugins.sh +124 -0
- package/scripts/link-sdk-core.cjs +185 -0
- package/scripts/postinstall-link-sdk.js +126 -0
- package/scripts/proactive-api-server.ts +369 -0
- package/scripts/send-proactive.ts +293 -0
- package/scripts/set-markdown.sh +156 -0
- package/scripts/test-sendmedia.ts +116 -0
- package/scripts/upgrade-via-npm.ps1 +460 -0
- package/scripts/upgrade-via-npm.sh +652 -0
- package/scripts/upgrade-via-source.sh +1026 -0
- package/skills/qqbot-channel/SKILL.md +263 -0
- package/skills/qqbot-channel/references/api_references.md +521 -0
- package/skills/qqbot-media/SKILL.md +60 -0
- package/skills/qqbot-remind/SKILL.md +159 -0
- package/src/admin-resolver.ts +181 -0
- package/src/api.ts +1284 -0
- package/src/channel.ts +477 -0
- package/src/config.ts +347 -0
- package/src/credential-backup.ts +72 -0
- package/src/deliver-debounce.ts +229 -0
- package/src/gateway.ts +2245 -0
- package/src/group-history.ts +328 -0
- package/src/image-server.ts +675 -0
- package/src/inbound-attachments.ts +321 -0
- package/src/known-users.ts +353 -0
- package/src/message-gating.ts +190 -0
- package/src/message-queue.ts +352 -0
- package/src/onboarding.ts +274 -0
- package/src/openclaw-plugin-sdk.d.ts +587 -0
- package/src/outbound-deliver.ts +473 -0
- package/src/outbound.ts +1131 -0
- package/src/proactive.ts +530 -0
- package/src/ref-index-store.ts +412 -0
- package/src/reply-dispatcher.ts +334 -0
- package/src/request-context.ts +49 -0
- package/src/runtime.ts +14 -0
- package/src/session-store.ts +303 -0
- package/src/slash-commands.ts +2030 -0
- package/src/startup-greeting.ts +120 -0
- package/src/streaming.ts +1077 -0
- package/src/stt.ts +86 -0
- package/src/tools/channel.ts +281 -0
- package/src/tools/remind.ts +308 -0
- package/src/types.ts +391 -0
- package/src/typing-keepalive.ts +59 -0
- package/src/update-checker.ts +186 -0
- package/src/utils/audio-convert.ts +859 -0
- package/src/utils/chunked-upload.ts +483 -0
- package/src/utils/file-utils.ts +193 -0
- package/src/utils/image-size.ts +266 -0
- package/src/utils/media-send.ts +631 -0
- package/src/utils/media-tags.ts +183 -0
- package/src/utils/payload.ts +265 -0
- package/src/utils/pkg-version.ts +64 -0
- package/src/utils/platform.ts +435 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/src/utils/text-parsing.ts +85 -0
- package/src/utils/upload-cache.ts +128 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群历史消息缓存
|
|
3
|
+
*
|
|
4
|
+
* 非@消息写入内存 Map,被@时一次性注入上下文后清空。
|
|
5
|
+
* 自包含实现,不依赖 openclaw/plugin-sdk。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 附件摘要(统一格式)
|
|
9
|
+
*
|
|
10
|
+
* 兼容 ref-index-store 的 RefAttachmentSummary、群历史缓存、以及 gateway 当前消息动态上下文。
|
|
11
|
+
* 所有场景的附件描述都通过 formatAttachmentTags() 统一格式化,确保标签风格一致。
|
|
12
|
+
*/
|
|
13
|
+
export interface AttachmentSummary {
|
|
14
|
+
/** 附件类型 */
|
|
15
|
+
type: "image" | "voice" | "video" | "file" | "unknown";
|
|
16
|
+
/** 文件名(如有) */
|
|
17
|
+
filename?: string;
|
|
18
|
+
/** 语音转录文本(入站:STT/ASR识别结果;出站:TTS原文本) */
|
|
19
|
+
transcript?: string;
|
|
20
|
+
/** 语音转录来源:stt=本地STT、asr=平台ASR、tts=TTS原文本、fallback=兜底文案 */
|
|
21
|
+
transcriptSource?: "stt" | "asr" | "tts" | "fallback";
|
|
22
|
+
/** 已下载到本地的文件路径 */
|
|
23
|
+
localPath?: string;
|
|
24
|
+
/** 在线来源 URL(公网图片/文件等) */
|
|
25
|
+
url?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* QQ 事件原始附件(来自 gateway 事件的通用字段子集)
|
|
29
|
+
*
|
|
30
|
+
* 多处需要将原始附件转换为 AttachmentSummary,统一此类型避免内联重复定义。
|
|
31
|
+
*/
|
|
32
|
+
export interface RawAttachment {
|
|
33
|
+
content_type: string;
|
|
34
|
+
filename?: string;
|
|
35
|
+
/** 语音 ASR 识别文本(QQ 事件内置) */
|
|
36
|
+
asr_refer_text?: string;
|
|
37
|
+
/** 附件 URL */
|
|
38
|
+
url?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 根据 content_type 推断附件类型(统一判断逻辑,避免多处重复)
|
|
42
|
+
*/
|
|
43
|
+
export declare function inferAttachmentType(contentType?: string): AttachmentSummary["type"];
|
|
44
|
+
/**
|
|
45
|
+
* 将原始附件数组转换为 AttachmentSummary 数组。
|
|
46
|
+
*
|
|
47
|
+
* 统一"原始附件 → 摘要"的映射逻辑,供历史记录缓存、合并消息格式化等场景复用。
|
|
48
|
+
* 无附件时返回 undefined(而非空数组),与 HistoryEntry.attachments 的可选语义一致。
|
|
49
|
+
*/
|
|
50
|
+
export declare function toAttachmentSummaries(attachments?: RawAttachment[]): AttachmentSummary[] | undefined;
|
|
51
|
+
/** @deprecated 使用 AttachmentSummary 代替 */
|
|
52
|
+
export type HistoryAttachment = AttachmentSummary;
|
|
53
|
+
export interface HistoryEntry {
|
|
54
|
+
sender: string;
|
|
55
|
+
body: string;
|
|
56
|
+
timestamp?: number;
|
|
57
|
+
messageId?: string;
|
|
58
|
+
/** 富媒体附件摘要(图片/语音/视频/文件) */
|
|
59
|
+
attachments?: AttachmentSummary[];
|
|
60
|
+
}
|
|
61
|
+
/** formatMessageContent 入参 */
|
|
62
|
+
export interface FormatMessageContentParams {
|
|
63
|
+
content: string;
|
|
64
|
+
/** 消息类型(group 时才做 mention 清理) */
|
|
65
|
+
chatType?: string;
|
|
66
|
+
mentions?: unknown[];
|
|
67
|
+
attachments?: RawAttachment[];
|
|
68
|
+
/** QQ 表情标签解析(<faceType=...> → 【表情: 中文名】) */
|
|
69
|
+
parseFaceTags: (text: string) => string;
|
|
70
|
+
/** mention @ 清理(移除 <@member_openid> 标记) */
|
|
71
|
+
stripMentionText?: (text: string, mentions: unknown[]) => string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 格式化单条消息内容:表情标签解析 → mention 清理 → 附件标签拼接。
|
|
75
|
+
*
|
|
76
|
+
* 用于合并消息的逐条子消息格式化,将外部依赖(parseFaceTags / stripMentionText)
|
|
77
|
+
* 通过参数注入,保持本模块自包含。
|
|
78
|
+
*/
|
|
79
|
+
export declare function formatMessageContent(params: FormatMessageContentParams): string;
|
|
80
|
+
/**
|
|
81
|
+
* 将附件摘要格式化为统一的人类可读标签描述(供 AI 上下文注入)。
|
|
82
|
+
*
|
|
83
|
+
* 标签风格沿用框架的 MEDIA: 标签格式:
|
|
84
|
+
* 有路径的附件 → MEDIA:path
|
|
85
|
+
* 语音+转录 → MEDIA:path (内容: "transcript")
|
|
86
|
+
* 无路径的语音 → [语音消息(内容: "transcript")]
|
|
87
|
+
* 无路径无转录 → [图片] / [语音消息] / [视频] / [文件]
|
|
88
|
+
*
|
|
89
|
+
* 此函数是所有附件描述的 **唯一格式化入口**,确保引用消息、群历史缓存、
|
|
90
|
+
* 当前消息动态上下文三处标签风格完全一致。
|
|
91
|
+
*/
|
|
92
|
+
export declare function formatAttachmentTags(attachments?: AttachmentSummary[]): string;
|
|
93
|
+
/** @deprecated 使用 formatAttachmentTags 代替 */
|
|
94
|
+
export declare const formatHistoryAttachments: typeof formatAttachmentTags;
|
|
95
|
+
/**
|
|
96
|
+
* 记录一条待注入的历史消息(非@消息调用此函数)。
|
|
97
|
+
* limit <= 0 或 entry 为空时不记录。
|
|
98
|
+
*/
|
|
99
|
+
export declare function recordPendingHistoryEntry(params: {
|
|
100
|
+
historyMap: Map<string, HistoryEntry[]>;
|
|
101
|
+
historyKey: string;
|
|
102
|
+
entry?: HistoryEntry | null;
|
|
103
|
+
limit: number;
|
|
104
|
+
}): HistoryEntry[];
|
|
105
|
+
/**
|
|
106
|
+
* 构建包含历史上下文的完整消息体(被@时调用)。
|
|
107
|
+
* 如果没有累积的历史消息,直接返回 currentMessage 原文。
|
|
108
|
+
*/
|
|
109
|
+
export declare function buildPendingHistoryContext(params: {
|
|
110
|
+
historyMap: Map<string, HistoryEntry[]>;
|
|
111
|
+
historyKey: string;
|
|
112
|
+
limit: number;
|
|
113
|
+
currentMessage: string;
|
|
114
|
+
formatEntry: (entry: HistoryEntry) => string;
|
|
115
|
+
lineBreak?: string;
|
|
116
|
+
}): string;
|
|
117
|
+
/**
|
|
118
|
+
* 构建合并消息上下文(多条排队消息被合并时调用)。
|
|
119
|
+
* 前置消息用 [合并消息开始]...[合并消息结束] 段落标签包裹,
|
|
120
|
+
* 最后一条作为当前消息紧跟其后。
|
|
121
|
+
* 如果只有一条消息,直接返回 currentMessage 原文。
|
|
122
|
+
*/
|
|
123
|
+
export declare function buildMergedMessageContext(params: {
|
|
124
|
+
precedingParts: string[];
|
|
125
|
+
currentMessage: string;
|
|
126
|
+
lineBreak?: string;
|
|
127
|
+
}): string;
|
|
128
|
+
/**
|
|
129
|
+
* 清空指定群的历史缓存(回复完成后调用)。
|
|
130
|
+
* limit <= 0 表示功能已禁用,不做操作。
|
|
131
|
+
*/
|
|
132
|
+
export declare function clearPendingHistory(params: {
|
|
133
|
+
historyMap: Map<string, HistoryEntry[]>;
|
|
134
|
+
historyKey: string;
|
|
135
|
+
limit: number;
|
|
136
|
+
}): void;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群历史消息缓存
|
|
3
|
+
*
|
|
4
|
+
* 非@消息写入内存 Map,被@时一次性注入上下文后清空。
|
|
5
|
+
* 自包含实现,不依赖 openclaw/plugin-sdk。
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// 常量
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/** 历史上下文段落标签 */
|
|
11
|
+
const HISTORY_CTX_START = "[上次回复后的聊天消息 - 作为上下文]";
|
|
12
|
+
const HISTORY_CTX_END = "[当前消息 - 请回复此条]";
|
|
13
|
+
/** 合并消息段落标签 */
|
|
14
|
+
const MERGED_CTX_START = "[以下是合并消息 - 作为上下文]";
|
|
15
|
+
const MERGED_CTX_END = "[当前消息 - 结合上下文回复]";
|
|
16
|
+
/** 历史 Map 最大 key 数量(LRU 淘汰,防止无限增长) */
|
|
17
|
+
const MAX_HISTORY_KEYS = 1000;
|
|
18
|
+
/**
|
|
19
|
+
* 根据 content_type 推断附件类型(统一判断逻辑,避免多处重复)
|
|
20
|
+
*/
|
|
21
|
+
export function inferAttachmentType(contentType) {
|
|
22
|
+
const ct = (contentType ?? "").toLowerCase();
|
|
23
|
+
if (ct.startsWith("image/"))
|
|
24
|
+
return "image";
|
|
25
|
+
if (ct === "voice" || ct.startsWith("audio/") || ct.includes("silk") || ct.includes("amr"))
|
|
26
|
+
return "voice";
|
|
27
|
+
if (ct.startsWith("video/"))
|
|
28
|
+
return "video";
|
|
29
|
+
if (ct.startsWith("application/") || ct.startsWith("text/"))
|
|
30
|
+
return "file";
|
|
31
|
+
return "unknown";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 将原始附件数组转换为 AttachmentSummary 数组。
|
|
35
|
+
*
|
|
36
|
+
* 统一"原始附件 → 摘要"的映射逻辑,供历史记录缓存、合并消息格式化等场景复用。
|
|
37
|
+
* 无附件时返回 undefined(而非空数组),与 HistoryEntry.attachments 的可选语义一致。
|
|
38
|
+
*/
|
|
39
|
+
export function toAttachmentSummaries(attachments) {
|
|
40
|
+
if (!attachments?.length)
|
|
41
|
+
return undefined;
|
|
42
|
+
return attachments.map((att) => ({
|
|
43
|
+
type: inferAttachmentType(att.content_type),
|
|
44
|
+
filename: att.filename,
|
|
45
|
+
transcript: att.asr_refer_text || undefined,
|
|
46
|
+
url: att.url || undefined,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 格式化单条消息内容:表情标签解析 → mention 清理 → 附件标签拼接。
|
|
51
|
+
*
|
|
52
|
+
* 用于合并消息的逐条子消息格式化,将外部依赖(parseFaceTags / stripMentionText)
|
|
53
|
+
* 通过参数注入,保持本模块自包含。
|
|
54
|
+
*/
|
|
55
|
+
export function formatMessageContent(params) {
|
|
56
|
+
let msgContent = params.parseFaceTags(params.content);
|
|
57
|
+
if (params.chatType === "group" && params.mentions?.length && params.stripMentionText) {
|
|
58
|
+
msgContent = params.stripMentionText(msgContent, params.mentions);
|
|
59
|
+
}
|
|
60
|
+
if (params.attachments?.length) {
|
|
61
|
+
const attachmentDesc = formatAttachmentTags(toAttachmentSummaries(params.attachments));
|
|
62
|
+
if (attachmentDesc) {
|
|
63
|
+
msgContent = `${msgContent} ${attachmentDesc}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return msgContent;
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// 附件标签格式化(全局统一)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* 将附件摘要格式化为统一的人类可读标签描述(供 AI 上下文注入)。
|
|
73
|
+
*
|
|
74
|
+
* 标签风格沿用框架的 MEDIA: 标签格式:
|
|
75
|
+
* 有路径的附件 → MEDIA:path
|
|
76
|
+
* 语音+转录 → MEDIA:path (内容: "transcript")
|
|
77
|
+
* 无路径的语音 → [语音消息(内容: "transcript")]
|
|
78
|
+
* 无路径无转录 → [图片] / [语音消息] / [视频] / [文件]
|
|
79
|
+
*
|
|
80
|
+
* 此函数是所有附件描述的 **唯一格式化入口**,确保引用消息、群历史缓存、
|
|
81
|
+
* 当前消息动态上下文三处标签风格完全一致。
|
|
82
|
+
*/
|
|
83
|
+
export function formatAttachmentTags(attachments) {
|
|
84
|
+
if (!attachments?.length)
|
|
85
|
+
return "";
|
|
86
|
+
const parts = [];
|
|
87
|
+
for (const att of attachments) {
|
|
88
|
+
const source = att.localPath || att.url;
|
|
89
|
+
if (source) {
|
|
90
|
+
// 有路径:使用 MEDIA: 标签
|
|
91
|
+
if (att.type === "voice" && att.transcript) {
|
|
92
|
+
parts.push(`MEDIA:${source}(内容: "${att.transcript}")`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
parts.push(`MEDIA:${source}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// 无路径:使用描述性标签
|
|
100
|
+
switch (att.type) {
|
|
101
|
+
case "image":
|
|
102
|
+
parts.push(`[图片${att.filename ? `: ${att.filename}` : ""}]`);
|
|
103
|
+
break;
|
|
104
|
+
case "voice":
|
|
105
|
+
parts.push(att.transcript
|
|
106
|
+
? `[语音消息(内容: "${att.transcript}")]`
|
|
107
|
+
: "[语音消息]");
|
|
108
|
+
break;
|
|
109
|
+
case "video":
|
|
110
|
+
parts.push(`[视频${att.filename ? `: ${att.filename}` : ""}]`);
|
|
111
|
+
break;
|
|
112
|
+
case "file":
|
|
113
|
+
parts.push(`[文件${att.filename ? `: ${att.filename}` : ""}]`);
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
parts.push(`[附件${att.filename ? `: ${att.filename}` : ""}]`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return parts.join("\n");
|
|
121
|
+
}
|
|
122
|
+
/** @deprecated 使用 formatAttachmentTags 代替 */
|
|
123
|
+
export const formatHistoryAttachments = formatAttachmentTags;
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// 内部工具
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* LRU 淘汰:当 historyMap 的 key 数量超过阈值时,删除最早插入的 key。
|
|
129
|
+
*/
|
|
130
|
+
function evictOldHistoryKeys(historyMap, maxKeys = MAX_HISTORY_KEYS) {
|
|
131
|
+
if (historyMap.size <= maxKeys)
|
|
132
|
+
return;
|
|
133
|
+
const keysToDelete = historyMap.size - maxKeys;
|
|
134
|
+
const iterator = historyMap.keys();
|
|
135
|
+
for (let i = 0; i < keysToDelete; i++) {
|
|
136
|
+
const key = iterator.next().value;
|
|
137
|
+
if (key !== undefined) {
|
|
138
|
+
historyMap.delete(key);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 向指定 key 的历史列表追加一条记录,超出 limit 时从头部淘汰。
|
|
144
|
+
* 同时刷新 key 的插入顺序(Map 迭代顺序 = 插入顺序),实现 LRU 语义。
|
|
145
|
+
*/
|
|
146
|
+
function appendHistoryEntry(params) {
|
|
147
|
+
const { historyMap, historyKey, entry } = params;
|
|
148
|
+
if (params.limit <= 0)
|
|
149
|
+
return [];
|
|
150
|
+
const history = historyMap.get(historyKey) ?? [];
|
|
151
|
+
history.push(entry);
|
|
152
|
+
while (history.length > params.limit) {
|
|
153
|
+
history.shift();
|
|
154
|
+
}
|
|
155
|
+
// 刷新插入顺序
|
|
156
|
+
if (historyMap.has(historyKey)) {
|
|
157
|
+
historyMap.delete(historyKey);
|
|
158
|
+
}
|
|
159
|
+
historyMap.set(historyKey, history);
|
|
160
|
+
evictOldHistoryKeys(historyMap);
|
|
161
|
+
return history;
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// 公开 API
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
/**
|
|
167
|
+
* 记录一条待注入的历史消息(非@消息调用此函数)。
|
|
168
|
+
* limit <= 0 或 entry 为空时不记录。
|
|
169
|
+
*/
|
|
170
|
+
export function recordPendingHistoryEntry(params) {
|
|
171
|
+
if (!params.entry || params.limit <= 0)
|
|
172
|
+
return [];
|
|
173
|
+
return appendHistoryEntry({
|
|
174
|
+
historyMap: params.historyMap,
|
|
175
|
+
historyKey: params.historyKey,
|
|
176
|
+
entry: params.entry,
|
|
177
|
+
limit: params.limit,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 构建包含历史上下文的完整消息体(被@时调用)。
|
|
182
|
+
* 如果没有累积的历史消息,直接返回 currentMessage 原文。
|
|
183
|
+
*/
|
|
184
|
+
export function buildPendingHistoryContext(params) {
|
|
185
|
+
if (params.limit <= 0)
|
|
186
|
+
return params.currentMessage;
|
|
187
|
+
const entries = params.historyMap.get(params.historyKey) ?? [];
|
|
188
|
+
if (entries.length === 0)
|
|
189
|
+
return params.currentMessage;
|
|
190
|
+
const lineBreak = params.lineBreak ?? "\n";
|
|
191
|
+
const historyText = entries.map(params.formatEntry).join(lineBreak);
|
|
192
|
+
return [
|
|
193
|
+
HISTORY_CTX_START,
|
|
194
|
+
historyText,
|
|
195
|
+
"",
|
|
196
|
+
HISTORY_CTX_END,
|
|
197
|
+
params.currentMessage,
|
|
198
|
+
].join(lineBreak);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 构建合并消息上下文(多条排队消息被合并时调用)。
|
|
202
|
+
* 前置消息用 [合并消息开始]...[合并消息结束] 段落标签包裹,
|
|
203
|
+
* 最后一条作为当前消息紧跟其后。
|
|
204
|
+
* 如果只有一条消息,直接返回 currentMessage 原文。
|
|
205
|
+
*/
|
|
206
|
+
export function buildMergedMessageContext(params) {
|
|
207
|
+
const { precedingParts, currentMessage } = params;
|
|
208
|
+
if (precedingParts.length === 0)
|
|
209
|
+
return currentMessage;
|
|
210
|
+
const lineBreak = params.lineBreak ?? "\n";
|
|
211
|
+
return [
|
|
212
|
+
MERGED_CTX_START,
|
|
213
|
+
precedingParts.join(lineBreak),
|
|
214
|
+
MERGED_CTX_END,
|
|
215
|
+
currentMessage,
|
|
216
|
+
].join(lineBreak);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 清空指定群的历史缓存(回复完成后调用)。
|
|
220
|
+
* limit <= 0 表示功能已禁用,不做操作。
|
|
221
|
+
*/
|
|
222
|
+
export function clearPendingHistory(params) {
|
|
223
|
+
if (params.limit <= 0)
|
|
224
|
+
return;
|
|
225
|
+
params.historyMap.set(params.historyKey, []);
|
|
226
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地图床服务器
|
|
3
|
+
* 提供安全的图片存储和访问服务
|
|
4
|
+
*/
|
|
5
|
+
export interface ImageServerConfig {
|
|
6
|
+
/** 监听端口 */
|
|
7
|
+
port: number;
|
|
8
|
+
/** 图片存储目录 */
|
|
9
|
+
storageDir: string;
|
|
10
|
+
/** 外部访问的基础 URL(如 http://your-server:port),留空则自动生成 */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** 图片过期时间(秒),0 表示不过期 */
|
|
13
|
+
ttlSeconds?: number;
|
|
14
|
+
/** 允许的图片格式 */
|
|
15
|
+
allowedFormats?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 启动图床服务器
|
|
19
|
+
*/
|
|
20
|
+
export declare function startImageServer(config?: Partial<ImageServerConfig>): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* 停止图床服务器
|
|
23
|
+
*/
|
|
24
|
+
export declare function stopImageServer(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 保存图片并返回访问 URL
|
|
27
|
+
* @param imageData 图片数据(Buffer 或 base64 字符串)
|
|
28
|
+
* @param mimeType 图片 MIME 类型
|
|
29
|
+
* @param ttlSeconds 过期时间(秒),默认使用配置值
|
|
30
|
+
* @returns 图片访问 URL
|
|
31
|
+
*/
|
|
32
|
+
export declare function saveImage(imageData: Buffer | string, mimeType?: string, ttlSeconds?: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* 从本地文件路径保存图片到图床
|
|
35
|
+
* @param filePath 本地文件路径
|
|
36
|
+
* @param ttlSeconds 过期时间(秒),默认使用配置值
|
|
37
|
+
* @returns 图片访问 URL,如果文件不存在或不是图片则返回 null
|
|
38
|
+
*/
|
|
39
|
+
export declare function saveImageFromPath(filePath: string, ttlSeconds?: number): string | null;
|
|
40
|
+
/**
|
|
41
|
+
* 检查图床服务器是否运行中
|
|
42
|
+
*/
|
|
43
|
+
export declare function isImageServerRunning(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* 确保图床服务器正在运行
|
|
46
|
+
* 如果未运行,则自动启动
|
|
47
|
+
* @param publicBaseUrl 公网访问的基础 URL(如 http://your-server:18765)
|
|
48
|
+
* @returns 基础 URL,启动失败返回 null
|
|
49
|
+
*/
|
|
50
|
+
export declare function ensureImageServer(publicBaseUrl?: string): Promise<string | null>;
|
|
51
|
+
/** downloadFile 的返回结果 */
|
|
52
|
+
export interface DownloadResult {
|
|
53
|
+
/** 下载成功时的本地文件路径(位于系统临时目录,调用方用完后应删除) */
|
|
54
|
+
filePath: string | null;
|
|
55
|
+
/** 下载失败时的错误信息(用于兜底消息展示) */
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 下载远程文件到系统临时目录。
|
|
60
|
+
*
|
|
61
|
+
* 文件名采用 UUID 保证不重名不覆盖,调用方用完后应自行删除。
|
|
62
|
+
*
|
|
63
|
+
* 安全措施:
|
|
64
|
+
* 1. SSRF 防护 — DNS 解析后校验 IP,拒绝私有/保留网段
|
|
65
|
+
* 2. Content-Type 黑名单 — 拦截 text/html(登录页/错误页/人机验证页)
|
|
66
|
+
* 3. 超时控制 — 默认 30 秒,传 0 表示不限时
|
|
67
|
+
* 4. 大小限制 — 可选,通过 Content-Length 预检 + 流式字节计数双重保护
|
|
68
|
+
*
|
|
69
|
+
* @param url 远程文件 URL
|
|
70
|
+
* @param originalFilename 原始文件名(可选,仅用于推导扩展名)
|
|
71
|
+
* @param options 下载选项
|
|
72
|
+
* @returns DownloadResult,filePath 为 null 表示失败,error 包含失败原因
|
|
73
|
+
*/
|
|
74
|
+
export declare function downloadFile(url: string, originalFilename?: string, options?: {
|
|
75
|
+
/** 超时时间(毫秒),默认 30000(30 秒)。传 0 表示不限时 */
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
/** 指定下载目标目录。不传则使用系统临时目录(调用方用完后应删除) */
|
|
78
|
+
destDir?: string;
|
|
79
|
+
/** 下载大小上限(字节)。超过此值中断下载并返回错误。不传则不限制 */
|
|
80
|
+
maxSizeBytes?: number;
|
|
81
|
+
/** 网络错误时的最大重试次数,默认 2(即最多尝试 3 次) */
|
|
82
|
+
maxRetries?: number;
|
|
83
|
+
}): Promise<DownloadResult>;
|
|
84
|
+
/**
|
|
85
|
+
* 获取图床服务器配置
|
|
86
|
+
*/
|
|
87
|
+
export declare function getImageServerConfig(): Required<ImageServerConfig>;
|