feat(qqbot): 添加图片发送功能及优化定时任务载荷格式
新增功能: - 新增 qqbot-media 技能,支持 <qqimg> 标签发送本地图片 - 添加图片尺寸检测工具 (image-size.ts),自动识别常见图片格式 - 支持将本地图片上传至 QQ 富媒体服务器 优化改进: - 定时任务支持结构化 JSON 载荷格式 - 优化 <qqimg> 标签正则表达式,避免误匹配反引号内的说明文字 - 完善消息处理流程和错误处理 文件变更: - src/gateway.ts: 添加图片处理、上传逻辑 - src/outbound.ts: 增强外发消息能力 - src/utils/image-size.ts: 新增图片尺寸解析工具 - skills/qqbot-media/SKILL.md: 新增图片功能说明文档 - skills/qqbot-cron/SKILL.md: 补充结构化载荷说明
This commit is contained in:
@@ -695,28 +695,61 @@ openclaw cron add \\
|
||||
if (qqimgMatches.length > 0) {
|
||||
log?.info(`[qqbot:${account.accountId}] Detected ${qqimgMatches.length} <qqimg> tag(s)`);
|
||||
|
||||
// 提取标签外的文本(作为描述发送)
|
||||
let textWithoutTags = replyText;
|
||||
const imagePaths: string[] = [];
|
||||
// 构建发送队列:根据内容在原文中的实际位置顺序发送
|
||||
// type: 'text' | 'image', content: 文本内容或图片路径
|
||||
const sendQueue: Array<{ type: "text" | "image"; content: string }> = [];
|
||||
|
||||
for (const match of qqimgMatches) {
|
||||
const fullMatch = match[0];
|
||||
let lastIndex = 0;
|
||||
// 使用新的正则来获取带索引的匹配结果
|
||||
const qqimgRegexWithIndex = /<qqimg>([^<>]+)<\/qqimg>/gi;
|
||||
let match;
|
||||
|
||||
while ((match = qqimgRegexWithIndex.exec(replyText)) !== null) {
|
||||
// 添加标签前的文本
|
||||
const textBefore = replyText.slice(lastIndex, match.index).replace(/\n{3,}/g, "\n\n").trim();
|
||||
if (textBefore) {
|
||||
sendQueue.push({ type: "text", content: filterInternalMarkers(textBefore) });
|
||||
}
|
||||
|
||||
// 添加图片
|
||||
const imagePath = match[1]?.trim();
|
||||
|
||||
if (imagePath) {
|
||||
imagePaths.push(imagePath);
|
||||
sendQueue.push({ type: "image", content: imagePath });
|
||||
log?.info(`[qqbot:${account.accountId}] Found image path in <qqimg>: ${imagePath}`);
|
||||
}
|
||||
|
||||
// 从文本中移除标签
|
||||
textWithoutTags = textWithoutTags.replace(fullMatch, "");
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
// 清理多余空行
|
||||
textWithoutTags = textWithoutTags.replace(/\n{3,}/g, "\n\n").trim();
|
||||
// 添加最后一个标签后的文本
|
||||
const textAfter = replyText.slice(lastIndex).replace(/\n{3,}/g, "\n\n").trim();
|
||||
if (textAfter) {
|
||||
sendQueue.push({ type: "text", content: filterInternalMarkers(textAfter) });
|
||||
}
|
||||
|
||||
log?.info(`[qqbot:${account.accountId}] Send queue: ${sendQueue.map(item => item.type).join(" -> ")}`);
|
||||
|
||||
// 按顺序发送
|
||||
for (const item of sendQueue) {
|
||||
if (item.type === "text") {
|
||||
// 发送文本
|
||||
try {
|
||||
await sendWithTokenRetry(async (token) => {
|
||||
if (event.type === "c2c") {
|
||||
await sendC2CMessage(token, event.senderId, item.content, event.messageId);
|
||||
} else if (event.type === "group" && event.groupOpenid) {
|
||||
await sendGroupMessage(token, event.groupOpenid, item.content, event.messageId);
|
||||
} else if (event.channelId) {
|
||||
await sendChannelMessage(token, event.channelId, item.content, event.messageId);
|
||||
}
|
||||
});
|
||||
log?.info(`[qqbot:${account.accountId}] Sent text: ${item.content.slice(0, 50)}...`);
|
||||
} catch (err) {
|
||||
log?.error(`[qqbot:${account.accountId}] Failed to send text: ${err}`);
|
||||
}
|
||||
} else if (item.type === "image") {
|
||||
// 发送图片
|
||||
for (const imagePath of imagePaths) {
|
||||
const imagePath = item.content;
|
||||
try {
|
||||
let imageUrl = imagePath;
|
||||
|
||||
@@ -778,24 +811,6 @@ openclaw cron add \\
|
||||
await sendErrorMessage(`发送图片失败: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 发送剩余的文本(如果有)
|
||||
if (textWithoutTags) {
|
||||
textWithoutTags = filterInternalMarkers(textWithoutTags);
|
||||
try {
|
||||
await sendWithTokenRetry(async (token) => {
|
||||
if (event.type === "c2c") {
|
||||
await sendC2CMessage(token, event.senderId, textWithoutTags, event.messageId);
|
||||
} else if (event.type === "group" && event.groupOpenid) {
|
||||
await sendGroupMessage(token, event.groupOpenid, textWithoutTags, event.messageId);
|
||||
} else if (event.channelId) {
|
||||
await sendChannelMessage(token, event.channelId, textWithoutTags, event.messageId);
|
||||
}
|
||||
});
|
||||
log?.info(`[qqbot:${account.accountId}] Sent caption text: ${textWithoutTags.slice(0, 50)}...`);
|
||||
} catch (err) {
|
||||
log?.error(`[qqbot:${account.accountId}] Failed to send caption text: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录活动并返回
|
||||
|
||||
Reference in New Issue
Block a user