"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.checkMessageReplyLimit = checkMessageReplyLimit;exports.getMessageReplyConfig = getMessageReplyConfig;exports.getMessageReplyStats = getMessageReplyStats;exports.recordMessageReply = recordMessageReply;exports.sendCronMessage = sendCronMessage;exports.sendMedia = sendMedia;exports.sendProactiveMessage = sendProactiveMessage;exports.sendText = sendText; var fs = _interopRequireWildcard(require("fs")); var path = _interopRequireWildcard(require("path")); var _payload = require("./utils/payload.js"); var _api = require("./api.js");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** * QQ Bot 消息发送模块 */ // ============ 消息回复限流器 ============ // 同一 message_id 1小时内最多回复 4 次,超过 1 小时无法被动回复(需改为主动消息) const MESSAGE_REPLY_LIMIT = 4; const MESSAGE_REPLY_TTL = 60 * 60 * 1000; // 1小时 const messageReplyTracker = new Map(); /** 限流检查结果 */ /** * 检查是否可以回复该消息(限流检查) * @param messageId 消息ID * @returns ReplyLimitResult 限流检查结果 */ function checkMessageReplyLimit(messageId) { const now = Date.now(); const record = messageReplyTracker.get(messageId); // 清理过期记录(定期清理,避免内存泄漏) if (messageReplyTracker.size > 10000) { for (const [id, rec] of messageReplyTracker) { if (now - rec.firstReplyAt > MESSAGE_REPLY_TTL) { messageReplyTracker.delete(id); } } } // 新消息,首次回复 if (!record) { return { allowed: true, remaining: MESSAGE_REPLY_LIMIT, shouldFallbackToProactive: false }; } // 检查是否超过1小时(message_id 过期) if (now - record.firstReplyAt > MESSAGE_REPLY_TTL) { // 超过1小时,被动回复不可用,需要降级为主动消息 return { allowed: false, remaining: 0, shouldFallbackToProactive: true, fallbackReason: "expired", message: `消息已超过1小时有效期,将使用主动消息发送` }; } // 检查是否超过回复次数限制 const remaining = MESSAGE_REPLY_LIMIT - record.count; if (remaining <= 0) { return { allowed: false, remaining: 0, shouldFallbackToProactive: true, fallbackReason: "limit_exceeded", message: `该消息已达到1小时内最大回复次数(${MESSAGE_REPLY_LIMIT}次),将使用主动消息发送` }; } return { allowed: true, remaining, shouldFallbackToProactive: false }; } /** * 记录一次消息回复 * @param messageId 消息ID */ function recordMessageReply(messageId) { const now = Date.now(); const record = messageReplyTracker.get(messageId); if (!record) { messageReplyTracker.set(messageId, { count: 1, firstReplyAt: now }); } else { // 检查是否过期,过期则重新计数 if (now - record.firstReplyAt > MESSAGE_REPLY_TTL) { messageReplyTracker.set(messageId, { count: 1, firstReplyAt: now }); } else { record.count++; } } console.log(`[qqbot] recordMessageReply: ${messageId}, count=${messageReplyTracker.get(messageId)?.count}`); } /** * 获取消息回复统计信息 */ function getMessageReplyStats() { let totalReplies = 0; for (const record of messageReplyTracker.values()) { totalReplies += record.count; } return { trackedMessages: messageReplyTracker.size, totalReplies }; } /** * 获取消息回复限制配置(供外部查询) */ function getMessageReplyConfig() { return { limit: MESSAGE_REPLY_LIMIT, ttlMs: MESSAGE_REPLY_TTL, ttlHours: MESSAGE_REPLY_TTL / (60 * 60 * 1000) }; } /** * 解析目标地址 * 格式: * - openid (32位十六进制) -> C2C 单聊 * - group:xxx -> 群聊 * - channel:xxx -> 频道 * - 纯数字 -> 频道 */ function parseTarget(to) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [qqbot] parseTarget: input=${to}`); // 去掉 qqbot: 前缀 let id = to.replace(/^qqbot:/i, ""); if (id.startsWith("c2c:")) { const userId = id.slice(4); if (!userId || userId.length === 0) { const error = `Invalid c2c target format: ${to} - missing user ID`; console.error(`[${timestamp}] [qqbot] parseTarget: ${error}`); throw new Error(error); } console.log(`[${timestamp}] [qqbot] parseTarget: c2c target, user ID=${userId}`); return { type: "c2c", id: userId }; } if (id.startsWith("group:")) { const groupId = id.slice(6); if (!groupId || groupId.length === 0) { const error = `Invalid group target format: ${to} - missing group ID`; console.error(`[${timestamp}] [qqbot] parseTarget: ${error}`); throw new Error(error); } console.log(`[${timestamp}] [qqbot] parseTarget: group target, group ID=${groupId}`); return { type: "group", id: groupId }; } if (id.startsWith("channel:")) { const channelId = id.slice(8); if (!channelId || channelId.length === 0) { const error = `Invalid channel target format: ${to} - missing channel ID`; console.error(`[${timestamp}] [qqbot] parseTarget: ${error}`); throw new Error(error); } console.log(`[${timestamp}] [qqbot] parseTarget: channel target, channel ID=${channelId}`); return { type: "channel", id: channelId }; } // 默认当作 c2c(私聊) if (!id || id.length === 0) { const error = `Invalid target format: ${to} - empty ID after removing qqbot: prefix`; console.error(`[${timestamp}] [qqbot] parseTarget: ${error}`); throw new Error(error); } console.log(`[${timestamp}] [qqbot] parseTarget: default c2c target, ID=${id}`); return { type: "c2c", id }; } /** * 发送文本消息 * - 有 replyToId: 被动回复,1小时内最多回复4次 * - 无 replyToId: 主动发送,有配额限制(每月4条/用户/群) * * 注意: * 1. 主动消息(无 replyToId)必须有消息内容,不支持流式发送 * 2. 当被动回复不可用(超期或超过次数)时,自动降级为主动消息 * 3. 支持 路径路径 格式发送图片 */ async function sendText(ctx) { const { to, account } = ctx; let { text, replyToId } = ctx; let fallbackToProactive = false; console.log("[qqbot] sendText ctx:", JSON.stringify({ to, text: text?.slice(0, 50), replyToId, accountId: account.accountId }, null, 2)); // ============ 消息回复限流检查 ============ // 如果有 replyToId,检查是否可以被动回复 if (replyToId) { const limitCheck = checkMessageReplyLimit(replyToId); if (!limitCheck.allowed) { // 检查是否需要降级为主动消息 if (limitCheck.shouldFallbackToProactive) { console.warn(`[qqbot] sendText: 被动回复不可用,降级为主动消息 - ${limitCheck.message}`); fallbackToProactive = true; replyToId = null; // 清除 replyToId,改为主动消息 } else { // 不应该发生,但作为保底 console.error(`[qqbot] sendText: 消息回复被限流但未设置降级 - ${limitCheck.message}`); return { channel: "qqbot", error: limitCheck.message }; } } else { console.log(`[qqbot] sendText: 消息 ${replyToId} 剩余被动回复次数: ${limitCheck.remaining}/${MESSAGE_REPLY_LIMIT}`); } } // ============ 标签检测与处理 ============ // 支持 路径路径 格式发送图片 const qqimgRegex = /([^<>]+)<\/(?:qqimg|img)>/gi; const qqimgMatches = text.match(qqimgRegex); if (qqimgMatches && qqimgMatches.length > 0) { console.log(`[qqbot] sendText: Detected ${qqimgMatches.length} tag(s), processing...`); // 构建发送队列:根据内容在原文中的实际位置顺序发送 const sendQueue = []; let lastIndex = 0; const qqimgRegexWithIndex = /([^<>]+)<\/(?:qqimg|img)>/gi; let match; while ((match = qqimgRegexWithIndex.exec(text)) !== null) { // 添加标签前的文本 const textBefore = text.slice(lastIndex, match.index).replace(/\n{3,}/g, "\n\n").trim(); if (textBefore) { sendQueue.push({ type: "text", content: textBefore }); } // 添加图片 const imagePath = match[1]?.trim(); if (imagePath) { sendQueue.push({ type: "image", content: imagePath }); console.log(`[qqbot] sendText: Found image path in : ${imagePath}`); } lastIndex = match.index + match[0].length; } // 添加最后一个标签后的文本 const textAfter = text.slice(lastIndex).replace(/\n{3,}/g, "\n\n").trim(); if (textAfter) { sendQueue.push({ type: "text", content: textAfter }); } console.log(`[qqbot] sendText: Send queue: ${sendQueue.map((item) => item.type).join(" -> ")}`); // 按顺序发送 if (!account.appId || !account.clientSecret) { return { channel: "qqbot", error: "QQBot not configured (missing appId or clientSecret)" }; } const accessToken = await (0, _api.getAccessToken)(account.appId, account.clientSecret); const target = parseTarget(to); let lastResult = { channel: "qqbot" }; for (const item of sendQueue) { try { if (item.type === "text") { // 发送文本 if (replyToId) { // 被动回复 if (target.type === "c2c") { const result = await (0, _api.sendC2CMessage)(accessToken, target.id, item.content, replyToId); recordMessageReply(replyToId); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { const result = await (0, _api.sendGroupMessage)(accessToken, target.id, item.content, replyToId); recordMessageReply(replyToId); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else { const result = await (0, _api.sendChannelMessage)(accessToken, target.id, item.content, replyToId); recordMessageReply(replyToId); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } } else { // 主动消息 if (target.type === "c2c") { const result = await (0, _api.sendProactiveC2CMessage)(accessToken, target.id, item.content); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { const result = await (0, _api.sendProactiveGroupMessage)(accessToken, target.id, item.content); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else { const result = await (0, _api.sendChannelMessage)(accessToken, target.id, item.content); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } } console.log(`[qqbot] sendText: Sent text part: ${item.content.slice(0, 30)}...`); } else if (item.type === "image") { // 发送图片 const imagePath = item.content; const isHttpUrl = imagePath.startsWith("http://") || imagePath.startsWith("https://"); let imageUrl = imagePath; // 如果是本地文件路径,读取并转换为 Base64 if (!isHttpUrl && !imagePath.startsWith("data:")) { if (fs.existsSync(imagePath)) { const fileBuffer = fs.readFileSync(imagePath); const ext = path.extname(imagePath).toLowerCase(); const mimeTypes = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp" }; const mimeType = mimeTypes[ext] ?? "image/png"; imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`; console.log(`[qqbot] sendText: Converted local image to Base64 (size: ${fileBuffer.length} bytes)`); } else { console.error(`[qqbot] sendText: Image file not found: ${imagePath}`); continue; // 跳过不存在的图片 } } // 发送图片 if (target.type === "c2c") { const result = await (0, _api.sendC2CImageMessage)(accessToken, target.id, imageUrl, replyToId ?? undefined); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { const result = await (0, _api.sendGroupImageMessage)(accessToken, target.id, imageUrl, replyToId ?? undefined); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (isHttpUrl) { // 频道使用 Markdown 格式(仅支持公网 URL) const result = await (0, _api.sendChannelMessage)(accessToken, target.id, `![](${imagePath})`, replyToId ?? undefined); lastResult = { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } console.log(`[qqbot] sendText: Sent image via tag: ${imagePath.slice(0, 60)}...`); } } catch (err) { const errMsg = err instanceof Error ? err.message : String(err); console.error(`[qqbot] sendText: Failed to send ${item.type}: ${errMsg}`); // 继续发送队列中的其他内容 } } return lastResult; } // ============ 主动消息校验(参考 Telegram 机制) ============ // 如果是主动消息(无 replyToId 或降级后),必须有消息内容 if (!replyToId) { if (!text || text.trim().length === 0) { console.error("[qqbot] sendText error: 主动消息的内容不能为空 (text is empty)"); return { channel: "qqbot", error: "主动消息必须有内容 (--message 参数不能为空)" }; } if (fallbackToProactive) { console.log(`[qqbot] sendText: [降级] 发送主动消息到 ${to}, 内容长度: ${text.length}`); } else { console.log(`[qqbot] sendText: 发送主动消息到 ${to}, 内容长度: ${text.length}`); } } if (!account.appId || !account.clientSecret) { return { channel: "qqbot", error: "QQBot not configured (missing appId or clientSecret)" }; } try { const accessToken = await (0, _api.getAccessToken)(account.appId, account.clientSecret); const target = parseTarget(to); console.log("[qqbot] sendText target:", JSON.stringify(target)); // 如果没有 replyToId,使用主动发送接口 if (!replyToId) { if (target.type === "c2c") { const result = await (0, _api.sendProactiveC2CMessage)(accessToken, target.id, text); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { const result = await (0, _api.sendProactiveGroupMessage)(accessToken, target.id, text); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else { // 频道暂不支持主动消息 const result = await (0, _api.sendChannelMessage)(accessToken, target.id, text); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } } // 有 replyToId,使用被动回复接口 if (target.type === "c2c") { const result = await (0, _api.sendC2CMessage)(accessToken, target.id, text, replyToId); // 记录回复次数 recordMessageReply(replyToId); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { const result = await (0, _api.sendGroupMessage)(accessToken, target.id, text, replyToId); // 记录回复次数 recordMessageReply(replyToId); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else { const result = await (0, _api.sendChannelMessage)(accessToken, target.id, text, replyToId); // 记录回复次数 recordMessageReply(replyToId); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } } catch (err) { const message = err instanceof Error ? err.message : String(err); return { channel: "qqbot", error: message }; } } /** * 主动发送消息(不需要 replyToId,有配额限制:每月 4 条/用户/群) * * @param account - 账户配置 * @param to - 目标地址,格式:openid(单聊)或 group:xxx(群聊) * @param text - 消息内容 */ async function sendProactiveMessage( account, to, text) { const timestamp = new Date().toISOString(); if (!account.appId || !account.clientSecret) { const errorMsg = "QQBot not configured (missing appId or clientSecret)"; console.error(`[${timestamp}] [qqbot] sendProactiveMessage: ${errorMsg}`); return { channel: "qqbot", error: errorMsg }; } console.log(`[${timestamp}] [qqbot] sendProactiveMessage: starting, to=${to}, text length=${text.length}, accountId=${account.accountId}`); try { console.log(`[${timestamp}] [qqbot] sendProactiveMessage: getting access token for appId=${account.appId}`); const accessToken = await (0, _api.getAccessToken)(account.appId, account.clientSecret); console.log(`[${timestamp}] [qqbot] sendProactiveMessage: parsing target=${to}`); const target = parseTarget(to); console.log(`[${timestamp}] [qqbot] sendProactiveMessage: target parsed, type=${target.type}, id=${target.id}`); if (target.type === "c2c") { console.log(`[${timestamp}] [qqbot] sendProactiveMessage: sending proactive C2C message to user=${target.id}`); const result = await (0, _api.sendProactiveC2CMessage)(accessToken, target.id, text); console.log(`[${timestamp}] [qqbot] sendProactiveMessage: proactive C2C message sent successfully, messageId=${result.id}`); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else if (target.type === "group") { console.log(`[${timestamp}] [qqbot] sendProactiveMessage: sending proactive group message to group=${target.id}`); const result = await (0, _api.sendProactiveGroupMessage)(accessToken, target.id, text); console.log(`[${timestamp}] [qqbot] sendProactiveMessage: proactive group message sent successfully, messageId=${result.id}`); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } else { // 频道暂不支持主动消息,使用普通发送 console.log(`[${timestamp}] [qqbot] sendProactiveMessage: sending channel message to channel=${target.id}`); const result = await (0, _api.sendChannelMessage)(accessToken, target.id, text); console.log(`[${timestamp}] [qqbot] sendProactiveMessage: channel message sent successfully, messageId=${result.id}`); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); console.error(`[${timestamp}] [qqbot] sendProactiveMessage: error: ${errorMessage}`); console.error(`[${timestamp}] [qqbot] sendProactiveMessage: error stack: ${err instanceof Error ? err.stack : 'No stack trace'}`); return { channel: "qqbot", error: errorMessage }; } } /** * 发送富媒体消息(图片) * * 支持以下 mediaUrl 格式: * - 公网 URL: https://example.com/image.png * - Base64 Data URL: data:image/png;base64,xxxxx * - 本地文件路径: /path/to/image.png(自动读取并转换为 Base64) * * @param ctx - 发送上下文,包含 mediaUrl * @returns 发送结果 * * @example * ```typescript * // 发送网络图片 * const result = await sendMedia({ * to: "group:xxx", * text: "这是图片说明", * mediaUrl: "https://example.com/image.png", * account, * replyToId: msgId, * }); * * // 发送 Base64 图片 * const result = await sendMedia({ * to: "group:xxx", * text: "这是图片说明", * mediaUrl: "data:image/png;base64,iVBORw0KGgo...", * account, * replyToId: msgId, * }); * * // 发送本地文件(自动读取并转换为 Base64) * const result = await sendMedia({ * to: "group:xxx", * text: "这是图片说明", * mediaUrl: "/tmp/generated-chart.png", * account, * replyToId: msgId, * }); * ``` */ async function sendMedia(ctx) { const { to, text, replyToId, account } = ctx; const { mediaUrl } = ctx; if (!account.appId || !account.clientSecret) { return { channel: "qqbot", error: "QQBot not configured (missing appId or clientSecret)" }; } if (!mediaUrl) { return { channel: "qqbot", error: "mediaUrl is required for sendMedia" }; } // 验证 mediaUrl 格式:支持公网 URL、Base64 Data URL 或本地文件路径 const isHttpUrl = mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://"); const isDataUrl = mediaUrl.startsWith("data:"); const isLocalPath = mediaUrl.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(mediaUrl) || mediaUrl.startsWith("./") || mediaUrl.startsWith("../"); // 处理本地文件路径:读取文件并转换为 Base64 Data URL let processedMediaUrl = mediaUrl; if (isLocalPath) { console.log(`[qqbot] sendMedia: local file path detected: ${mediaUrl}`); try { // 检查文件是否存在 if (!fs.existsSync(mediaUrl)) { return { channel: "qqbot", error: `本地文件不存在: ${mediaUrl}` }; } // 读取文件内容 const fileBuffer = fs.readFileSync(mediaUrl); const base64Data = fileBuffer.toString("base64"); // 根据文件扩展名确定 MIME 类型 const ext = path.extname(mediaUrl).toLowerCase(); const mimeTypes = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp" }; const mimeType = mimeTypes[ext]; if (!mimeType) { return { channel: "qqbot", error: `不支持的图片格式: ${ext}。支持的格式: ${Object.keys(mimeTypes).join(", ")}` }; } // 构造 Data URL processedMediaUrl = `data:${mimeType};base64,${base64Data}`; console.log(`[qqbot] sendMedia: local file converted to Base64 (size: ${fileBuffer.length} bytes, type: ${mimeType})`); } catch (readErr) { const errMsg = readErr instanceof Error ? readErr.message : String(readErr); console.error(`[qqbot] sendMedia: failed to read local file: ${errMsg}`); return { channel: "qqbot", error: `读取本地文件失败: ${errMsg}` }; } } else if (!isHttpUrl && !isDataUrl) { console.log(`[qqbot] sendMedia: unsupported media format: ${mediaUrl.slice(0, 50)}`); return { channel: "qqbot", error: `不支持的图片格式: ${mediaUrl.slice(0, 50)}...。支持的格式: 公网 URL (http/https)、Base64 Data URL (data:image/...) 或本地文件路径。` }; } else if (isDataUrl) { console.log(`[qqbot] sendMedia: sending Base64 image (length: ${mediaUrl.length})`); } else { console.log(`[qqbot] sendMedia: sending image URL: ${mediaUrl.slice(0, 80)}...`); } try { const accessToken = await (0, _api.getAccessToken)(account.appId, account.clientSecret); const target = parseTarget(to); // 先发送图片(使用处理后的 URL,可能是 Base64 Data URL) let imageResult; if (target.type === "c2c") { imageResult = await (0, _api.sendC2CImageMessage)( accessToken, target.id, processedMediaUrl, replyToId ?? undefined, undefined // content 参数,图片消息不支持同时带文本 ); } else if (target.type === "group") { imageResult = await (0, _api.sendGroupImageMessage)( accessToken, target.id, processedMediaUrl, replyToId ?? undefined, undefined ); } else { // 频道暂不支持富媒体消息,只发送文本 + URL(本地文件路径无法在频道展示) const displayUrl = isLocalPath ? "[本地文件]" : mediaUrl; const textWithUrl = text ? `${text}\n${displayUrl}` : displayUrl; const result = await (0, _api.sendChannelMessage)(accessToken, target.id, textWithUrl, replyToId ?? undefined); return { channel: "qqbot", messageId: result.id, timestamp: result.timestamp }; } // 如果有文本说明,再发送一条文本消息 if (text?.trim()) { try { if (target.type === "c2c") { await (0, _api.sendC2CMessage)(accessToken, target.id, text, replyToId ?? undefined); } else if (target.type === "group") { await (0, _api.sendGroupMessage)(accessToken, target.id, text, replyToId ?? undefined); } } catch (textErr) { // 文本发送失败不影响整体结果,图片已发送成功 console.error(`[qqbot] Failed to send text after image: ${textErr}`); } } 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", * "这是一条普通的提醒消息" * ); * ``` */ async function sendCronMessage( account, to, message) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [qqbot] sendCronMessage: to=${to}, message length=${message.length}`); // 检测是否是 QQBOT_CRON: 格式的结构化载荷 const cronResult = (0, _payload.decodeCronPayload)(message); if (cronResult.isCronPayload) { if (cronResult.error) { console.error(`[${timestamp}] [qqbot] sendCronMessage: cron payload decode error: ${cronResult.error}`); return { channel: "qqbot", error: `Cron 载荷解码失败: ${cronResult.error}` }; } if (cronResult.payload) { const payload = cronResult.payload; console.log(`[${timestamp}] [qqbot] sendCronMessage: decoded cron payload, targetType=${payload.targetType}, targetAddress=${payload.targetAddress}, content length=${payload.content.length}`); // 使用载荷中的目标地址和类型发送消息 const targetTo = payload.targetType === "group" ? `group:${payload.targetAddress}` : payload.targetAddress; console.log(`[${timestamp}] [qqbot] sendCronMessage: sending proactive message to targetTo=${targetTo}`); // 发送提醒内容 const result = await sendProactiveMessage(account, targetTo, payload.content); if (result.error) { console.error(`[${timestamp}] [qqbot] sendCronMessage: proactive message failed, error=${result.error}`); } else { console.log(`[${timestamp}] [qqbot] sendCronMessage: proactive message sent successfully`); } return result; } } // 非结构化载荷,作为普通文本处理 console.log(`[${timestamp}] [qqbot] sendCronMessage: plain text message, sending to ${to}`); return await sendProactiveMessage(account, to, message); } /* v9-dadef4e5fe8005eb */