/** * 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'; }