/**
* QQBot 结构化消息载荷工具
*
* 用于处理 AI 输出的结构化消息载荷,包括:
* - 定时提醒载荷 (cron_reminder)
* - 媒体消息载荷 (media)
*/
// ============================================
// 类型定义
// ============================================
/**
* 定时提醒载荷
*/
export interface CronReminderPayload {
type: 'cron_reminder';
/** 提醒内容 */
content: string;
/** 目标类型:c2c (私聊) 或 group (群聊) */
targetType: 'c2c' | 'group';
/** 目标地址:user_openid 或 group_openid */
targetAddress: string;
/** 原始消息 ID(可选) */
originalMessageId?: string;
}
/**
* 媒体消息载荷
*/
export interface MediaPayload {
type: 'media';
/** 媒体类型:image, audio, video */
mediaType: 'image' | 'audio' | 'video';
/** 来源类型:url 或 file */
source: 'url' | 'file';
/** 媒体路径或 URL */
path: string;
/** 媒体描述(可选) */
caption?: string;
}
/**
* QQBot 载荷联合类型
*/
export type QQBotPayload = CronReminderPayload | MediaPayload;
/**
* 解析结果
*/
export interface ParseResult {
/** 是否为结构化载荷 */
isPayload: boolean;
/** 解析后的载荷对象(如果是结构化载荷) */
payload?: QQBotPayload;
/** 原始文本(如果不是结构化载荷) */
text?: string;
/** 解析错误信息(如果解析失败) */
error?: string;
}
// ============================================
// 常量定义
// ============================================
/** AI 输出的结构化载荷前缀 */
const PAYLOAD_PREFIX = 'QQBOT_PAYLOAD:';
/** Cron 消息存储的前缀 */
const CRON_PREFIX = 'QQBOT_CRON:';
// ============================================
// 解析函数
// ============================================
/**
* 解析 AI 输出的结构化载荷
*
* 检测消息是否以 QQBOT_PAYLOAD: 前缀开头,如果是则提取并解析 JSON
*
* @param text AI 输出的原始文本
* @returns 解析结果
*
* @example
* const result = parseQQBotPayload('QQBOT_PAYLOAD:\n{"type": "media", "mediaType": "image", ...}');
* if (result.isPayload && result.payload) {
* // 处理结构化载荷
* }
*/
export function parseQQBotPayload(text: string): ParseResult {
const trimmedText = text.trim();
// 检查是否以 QQBOT_PAYLOAD: 开头
if (!trimmedText.startsWith(PAYLOAD_PREFIX)) {
return {
isPayload: false,
text: text
};
}
// 提取 JSON 内容(去掉前缀)
const jsonContent = trimmedText.slice(PAYLOAD_PREFIX.length).trim();
if (!jsonContent) {
return {
isPayload: true,
error: '载荷内容为空'
};
}
try {
const payload = JSON.parse(jsonContent) as QQBotPayload;
// 验证必要字段
if (!payload.type) {
return {
isPayload: true,
error: '载荷缺少 type 字段'
};
}
// 根据 type 进行额外验证
if (payload.type === 'cron_reminder') {
if (!payload.content || !payload.targetType || !payload.targetAddress) {
return {
isPayload: true,
error: 'cron_reminder 载荷缺少必要字段 (content, targetType, targetAddress)'
};
}
} else if (payload.type === 'media') {
if (!payload.mediaType || !payload.source || !payload.path) {
return {
isPayload: true,
error: 'media 载荷缺少必要字段 (mediaType, source, path)'
};
}
}
return {
isPayload: true,
payload
};
} catch (e) {
return {
isPayload: true,
error: `JSON 解析失败: ${e instanceof Error ? e.message : String(e)}`
};
}
}
// ============================================
// Cron 编码/解码函数
// ============================================
/**
* 将定时提醒载荷编码为 Cron 消息格式
*
* 将 JSON 编码为 Base64,并添加 QQBOT_CRON: 前缀
*
* @param payload 定时提醒载荷
* @returns 编码后的消息字符串,格式为 QQBOT_CRON:{base64}
*
* @example
* const message = encodePayloadForCron({
* type: 'cron_reminder',
* content: '喝水时间到!',
* targetType: 'c2c',
* targetAddress: 'user_openid_xxx'
* });
* // 返回: QQBOT_CRON:eyJ0eXBlIjoiY3Jvbl9yZW1pbmRlciIs...
*/
export function encodePayloadForCron(payload: CronReminderPayload): string {
const jsonString = JSON.stringify(payload);
const base64 = Buffer.from(jsonString, 'utf-8').toString('base64');
return `${CRON_PREFIX}${base64}`;
}
/**
* 解码 Cron 消息中的载荷
*
* 检测 QQBOT_CRON: 前缀,解码 Base64 并解析 JSON
*
* @param message Cron 触发时收到的消息
* @returns 解码结果,包含是否为 Cron 载荷、解析后的载荷对象或错误信息
*
* @example
* const result = decodeCronPayload('QQBOT_CRON:eyJ0eXBlIjoiY3Jvbl9yZW1pbmRlciIs...');
* if (result.isCronPayload && result.payload) {
* // 处理定时提醒
* }
*/
export function decodeCronPayload(message: string): {
isCronPayload: boolean;
payload?: CronReminderPayload;
error?: string;
} {
const trimmedMessage = message.trim();
// 检查是否以 QQBOT_CRON: 开头
if (!trimmedMessage.startsWith(CRON_PREFIX)) {
return {
isCronPayload: false
};
}
// 提取 Base64 内容
const base64Content = trimmedMessage.slice(CRON_PREFIX.length);
if (!base64Content) {
return {
isCronPayload: true,
error: 'Cron 载荷内容为空'
};
}
try {
// Base64 解码
const jsonString = Buffer.from(base64Content, 'base64').toString('utf-8');
const payload = JSON.parse(jsonString) as CronReminderPayload;
// 验证类型
if (payload.type !== 'cron_reminder') {
return {
isCronPayload: true,
error: `期望 type 为 cron_reminder,实际为 ${payload.type}`
};
}
// 验证必要字段
if (!payload.content || !payload.targetType || !payload.targetAddress) {
return {
isCronPayload: true,
error: 'Cron 载荷缺少必要字段'
};
}
return {
isCronPayload: true,
payload
};
} catch (e) {
return {
isCronPayload: true,
error: `Cron 载荷解码失败: ${e instanceof Error ? e.message : String(e)}`
};
}
}
// ============================================
// 辅助函数
// ============================================
/**
* 判断载荷是否为定时提醒类型
*/
export function isCronReminderPayload(payload: QQBotPayload): payload is CronReminderPayload {
return payload.type === 'cron_reminder';
}
/**
* 判断载荷是否为媒体消息类型
*/
export function isMediaPayload(payload: QQBotPayload): payload is MediaPayload {
return payload.type === 'media';
}