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:
rianli
2026-02-03 13:14:22 +08:00
parent cbe51bfb82
commit 93f284891c
5 changed files with 557 additions and 239 deletions

View File

@@ -5,6 +5,7 @@
import * as fs from "fs";
import * as path from "path";
import type { ResolvedQQBotAccount } from "./types.js";
import { decodeCronPayload } from "./utils/payload.js";
import {
getAccessToken,
sendC2CMessage,
@@ -495,9 +496,76 @@ export async function sendMedia(ctx: MediaOutboundContext): Promise<OutboundResu
}
}
return { channel: "qqbot", messageId: imageResult.id, timestamp: imageResult.timestamp };
return { channel: "qqbot", messageId: imageResult.id, timestamp: imageResult.timestamp };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return { channel: "qqbot", error: message };
}
}
/**
* 发送 Cron 触发的消息
*
* 当 OpenClaw cron 任务触发时,消息内容可能是:
* 1. QQBOT_CRON:{base64} 格式的结构化载荷 - 解码后根据 targetType 和 targetAddress 发送
* 2. 普通文本 - 直接发送到指定目标
*
* @param account - 账户配置
* @param to - 目标地址(作为后备,如果载荷中没有指定)
* @param message - 消息内容(可能是 QQBOT_CRON: 格式或普通文本)
* @returns 发送结果
*
* @example
* ```typescript
* // 处理结构化载荷
* const result = await sendCronMessage(
* account,
* "user_openid", // 后备地址
* "QQBOT_CRON:eyJ0eXBlIjoiY3Jvbl9yZW1pbmRlciIs..." // Base64 编码的载荷
* );
*
* // 处理普通文本
* const result = await sendCronMessage(
* account,
* "user_openid",
* "这是一条普通的提醒消息"
* );
* ```
*/
export async function sendCronMessage(
account: ResolvedQQBotAccount,
to: string,
message: string
): Promise<OutboundResult> {
console.log(`[qqbot] sendCronMessage: to=${to}, message length=${message.length}`);
// 检测是否是 QQBOT_CRON: 格式的结构化载荷
const cronResult = decodeCronPayload(message);
if (cronResult.isCronPayload) {
if (cronResult.error) {
console.error(`[qqbot] sendCronMessage: cron payload decode error: ${cronResult.error}`);
return {
channel: "qqbot",
error: `Cron 载荷解码失败: ${cronResult.error}`
};
}
if (cronResult.payload) {
const payload = cronResult.payload;
console.log(`[qqbot] sendCronMessage: decoded cron payload, targetType=${payload.targetType}, targetAddress=${payload.targetAddress}`);
// 使用载荷中的目标地址和类型发送消息
const targetTo = payload.targetType === "group"
? `group:${payload.targetAddress}`
: payload.targetAddress;
// 发送提醒内容
return await sendProactiveMessage(account, targetTo, payload.content);
}
}
// 非结构化载荷,作为普通文本处理
console.log(`[qqbot] sendCronMessage: plain text message, sending to ${to}`);
return await sendProactiveMessage(account, to, message);
}