/** * QQ Bot Message Sending */ import type { MoltbotConfig } from "clawdbot/plugin-sdk"; import { getAccessToken, sendC2CMessage, sendGroupMessage, sendChannelMessage, sendDmsMessage, type SendMessageResult, } from "./api.js"; import { resolveQQAccount } from "./accounts.js"; const QQ_TEXT_LIMIT = 2000; export interface SendQQMessageOptions { accountId?: string; cfg?: MoltbotConfig; appId?: string; appSecret?: string; msgId?: string; msgSeq?: number; mediaUrl?: string; } export type ChatType = "c2c" | "group" | "channel" | "dms"; /** * Send a message via QQ Bot API */ export async function sendMessageQQ( chatType: ChatType, targetId: string, text: string | undefined, options: SendQQMessageOptions, ): Promise { const { accountId, cfg, msgId, msgSeq } = options; let appId = options.appId; let appSecret = options.appSecret; // Resolve from config if not provided directly if ((!appId || !appSecret) && cfg) { const account = resolveQQAccount({ cfg, accountId }); appId = account.appId; appSecret = account.appSecret; } if (!appId || !appSecret) { return { ok: false, error: "QQ appId or appSecret not configured" }; } try { const token = await getAccessToken(appId, appSecret); const request = { content: text, msg_type: 0, // text message msg_id: msgId, msg_seq: msgSeq, }; switch (chatType) { case "c2c": return sendC2CMessage(token, targetId, request); case "group": return sendGroupMessage(token, targetId, request); case "channel": return sendChannelMessage(token, targetId, request); case "dms": return sendDmsMessage(token, targetId, request); default: return { ok: false, error: `Unknown chat type: ${chatType}` }; } } catch (err) { return { ok: false, error: err instanceof Error ? err.message : String(err), }; } } /** * Chunk text for QQ message limit */ export function chunkQQText(text: string, limit = QQ_TEXT_LIMIT): string[] { if (!text) return []; if (text.length <= limit) return [text]; const chunks: string[] = []; let remaining = text; while (remaining.length > limit) { const window = remaining.slice(0, limit); const lastNewline = window.lastIndexOf("\n"); const lastSpace = window.lastIndexOf(" "); let breakIdx = lastNewline > 0 ? lastNewline : lastSpace; if (breakIdx <= 0) breakIdx = limit; const rawChunk = remaining.slice(0, breakIdx); const chunk = rawChunk.trimEnd(); if (chunk.length > 0) chunks.push(chunk); const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]); const nextStart = Math.min( remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0), ); remaining = remaining.slice(nextStart).trimStart(); } if (remaining.length) chunks.push(remaining); return chunks; }