/** * Telegram Bot Worker v3.68 (No-Receipt Mod, No-Username Fix & Security Hardening) * 完整功能版:保留所有备份、配置面板、辅助函数 * * 修改 1: 修复了无用户名用户无法推送卡片的问题 * * 修改 2: 彻底移除了管理员回复的“✅ 已回复”提示 * * 修改 3: 彻底移除了用户发送消息后的“✅ 已送达”回执 * * 修改 4: 增加 Webhook secret、WebApp initData、nonce、管理员精确匹配与正则安全检查 */ // --- 1. 静态配置与常量 --- // 缓存系统,用于减少数据库读写压力,降低 Worker KV/D1 计费 const CACHE = { data: {}, ts: 0, ttl: 60000, user_locks: {}, warn_cd: {}, admin: { ts: 0, ttl: 60000, primary: new Set(), auth: new Set() } }; const DEFAULTS = { // 基础设置 welcome_msg: "欢迎 {name}!使用前请先完成验证。", // 验证设置 enable_verify: "true", enable_qa_verify: "true", captcha_mode: "turnstile", verif_q: "1+1=?\n提示:答案在简介中。", verif_a: "3", // 风控设置 block_threshold: "5", enable_admin_receipt: "false", // 默认关闭管理员回执 // 转发类型开关配置 enable_image_forwarding: "true", enable_link_forwarding: "true", enable_text_forwarding: "true", enable_channel_forwarding: "true", enable_forward_forwarding: "true", enable_audio_forwarding: "true", enable_sticker_forwarding: "true", // 话题与列表存储占位 backup_group_id: "", blocked_topic_id: "", busy_mode: "false", busy_msg: "当前是非营业时间,消息已收到,管理员稍后回复。", block_keywords: "[]", keyword_responses: "[]", authorized_admins: "[]" }; // 消息类型检查与映射字典 const MSG_TYPES = [ { check: m => m.forward_from || m.forward_from_chat, key: 'enable_forward_forwarding', name: "转发消息", extra: m => m.forward_from_chat?.type === 'channel' ? 'enable_channel_forwarding' : null }, { check: m => m.audio || m.voice, key: 'enable_audio_forwarding', name: "语音/音频" }, { check: m => m.sticker || m.animation, key: 'enable_sticker_forwarding', name: "贴纸/GIF" }, { check: m => m.photo || m.video || m.document, key: 'enable_image_forwarding', name: "媒体文件" }, { check: m => (m.entities||[]).some(e => ['url','text_link'].includes(e.type)), key: 'enable_link_forwarding', name: "链接" }, { check: m => m.text, key: 'enable_text_forwarding', name: "纯文本" } ]; const REGEX_MAX_PATTERN_LEN = 256; const REGEX_MAX_TEXT_LEN = 512; const REGEX_REJECT_PATTERNS = [ /\([^)]*\)\s*[+*{]/, /\(\s*\.\*\s*\)\s*\+/, /\(\s*\.\+\s*\)\s*\+/, /\\[1-9]/, /\(\?<=[\s\S]*\)/, /\(\? console.error("DB Init Failed:", err))); const url = new URL(req.url); try { // GET 请求处理:验证页面加载或连通性测试 if (req.method === "GET") { if (url.pathname === "/verify") return handleVerifyPage(url, env); if (url.pathname === "/") return new Response("Bot v3.68 Active", { status: 200 }); } // POST 请求处理:Telegram Webhook 核心逻辑接收端 if (req.method === "POST") { if (url.pathname === "/submit_token") return handleTokenSubmit(req, env); if (!isTelegramWebhook(req, env)) return new Response("Forbidden", { status: 403 }); try { const update = await req.json(); ctx.waitUntil(handleUpdate(update, env, ctx)); return new Response("OK"); } catch (jsonErr) { console.error("Invalid JSON Update:", jsonErr); return new Response("Bad Request", { status: 400 }); } } } catch (e) { console.error("Critical Worker Error:", e); return new Response("Internal Server Error", { status: 500 }); } return new Response("404 Not Found", { status: 404 }); } }; // --- 3. 数据库与工具函数 --- // 安全解析JSON,避免非规范格式导致脚本执行中断 const safeParse = (str, fallback = {}) => { if (!str) return fallback; try { return JSON.parse(str); } catch (e) { console.error("JSON Parse Error:", e); return fallback; } }; // SQL 执行封装,支持不同的运行类型 (run, all, first) const sql = async (env, query, args = [], type = 'run') => { try { const stmt = env.TG_BOT_DB.prepare(query).bind(...(Array.isArray(args) ? args : [args])); return type === 'run' ? await stmt.run() : await stmt[type](); } catch (e) { console.error(`SQL Error [${query}]:`, e); return null; } }; // 获取配置项:优先命中内存缓存以提升响应速度 async function getCfg(key, env) { const now = Date.now(); if (CACHE.ts && (now - CACHE.ts) < CACHE.ttl && CACHE.data[key] !== undefined) return CACHE.data[key]; const rows = await sql(env, "SELECT * FROM config", [], 'all'); if (rows && rows.results) { CACHE.data = {}; rows.results.forEach(r => CACHE.data[r.key] = r.value); CACHE.ts = now; } const envKey = key.toUpperCase().replace(/_MSG|_Q|_A/, m => ({'_MSG':'_MESSAGE','_Q':'_QUESTION','_A':'_ANSWER'}[m])); return CACHE.data[key] !== undefined ? CACHE.data[key] : (env[envKey] || DEFAULTS[key] || ""); } // 设置配置项:同步使当前内存缓存失效 async function setCfg(key, val, env) { await sql(env, "INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", [key, val]); CACHE.ts = 0; if (key === 'authorized_admins') CACHE.admin.ts = 0; } // 删除配置项:同步失效缓存,避免旧值被 getCfg 误读 async function deleteCfg(key, env) { await sql(env, "DELETE FROM config WHERE key=?", key); delete CACHE.data[key]; CACHE.ts = 0; if (key === 'authorized_admins') CACHE.admin.ts = 0; } // 获取或初始化用户信息实体 async function getUser(id, env) { let u = await sql(env, "SELECT * FROM users WHERE user_id = ?", id, 'first'); if (!u) { try { await sql(env, "INSERT OR IGNORE INTO users (user_id, user_state) VALUES (?, 'new')", id); } catch {} u = await sql(env, "SELECT * FROM users WHERE user_id = ?", id, 'first'); } if (!u) u = { user_id: id, user_state: 'new', is_blocked: 0, block_count: 0, first_message_sent: 0, topic_id: null, user_info_json: "{}" }; // 布尔状态类型转换及附属 JSON 解析 u.is_blocked = !!u.is_blocked; u.first_message_sent = !!u.first_message_sent; u.user_info = safeParse(u.user_info_json); return u; } // 增量更新用户信息记录 async function updUser(id, data, env) { if (data.user_info) { data.user_info_json = JSON.stringify(data.user_info); delete data.user_info; } const keys = Object.keys(data); if (!keys.length) return; const query = `UPDATE users SET ${keys.map(k => `${k}=?`).join(',')} WHERE user_id=?`; const values = [...keys.map(k => typeof data[k] === 'boolean' ? (data[k]?1:0) : data[k]), id]; await sql(env, query, values); } // 数据库表结构初始化与防御性兼容处理 async function dbInit(env) { if (!env.TG_BOT_DB) return; await env.TG_BOT_DB.batch([ env.TG_BOT_DB.prepare(`CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT)`), env.TG_BOT_DB.prepare(`CREATE TABLE IF NOT EXISTS users (user_id TEXT PRIMARY KEY, user_state TEXT DEFAULT 'new', is_blocked INTEGER DEFAULT 0, block_count INTEGER DEFAULT 0, first_message_sent INTEGER DEFAULT 0, topic_id TEXT, user_info_json TEXT)`), env.TG_BOT_DB.prepare(`CREATE TABLE IF NOT EXISTS messages (user_id TEXT, message_id TEXT, text TEXT, date INTEGER, PRIMARY KEY (user_id, message_id))`) ]); try { await sql(env, "ALTER TABLE messages ADD COLUMN topic_message_id TEXT"); } catch (e) {} } // --- 4. 业务逻辑 (核心流) --- // Telegram Bot API 原生请求封装 async function api(token, method, body) { try { const r = await fetch(`https://api.telegram.org/bot${token}/${method}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const d = await r.json(); if (!d.ok) { console.warn(`TG API Error [${method}]:`, d.description); throw new Error(d.description); } return d.result; } catch (e) { throw e; } } // 自动向 Telegram 注册快捷菜单命令 async function registerCommands(env) { try { await api(env.BOT_TOKEN, "deleteMyCommands", { scope: { type: "default" } }); await api(env.BOT_TOKEN, "setMyCommands", { commands: [{ command: "start", description: "开始 / Start" }], scope: { type: "default" } }); const sets = await getAdminSets(env); const admins = [...sets.auth]; for (const id of admins) await api(env.BOT_TOKEN, "setMyCommands", { commands: [{ command: "start", description: "⚙️ 管理面板" }, { command: "help", description: "📄 帮助说明" }], scope: { type: "chat", chat_id: id } }); } catch (e) { console.error("Register Commands Failed:", e); } } // 全局更新对象分发调度中心 async function handleUpdate(update, env, ctx) { const msg = update.message || update.edited_message; if (!msg) return update.callback_query ? handleCallback(update.callback_query, env) : null; // 监听管理员侧的消息变更事件 if (update.edited_message && msg.chat.id.toString() === env.ADMIN_GROUP_ID) { return handleAdminEdit(msg, env); } // 监听用户侧的消息变更事件 if (update.edited_message) return (msg.chat.type === "private") ? handleEdit(msg, env) : null; // 会话路由 if (msg.chat.type === "private") await handlePrivate(msg, env, ctx); else if (msg.chat.id.toString() === env.ADMIN_GROUP_ID) { // 检查是否是管理员的 /del 命令 if ((msg.text === "/del" || msg.caption === "/del") && msg.reply_to_message) { await handleAdminDelete(msg, env); } else { await handleAdminReply(msg, env); } } } // 管理员编辑群组内消息时的逻辑,主动通知用户变更内容 async function handleAdminEdit(msg, env) { if (!msg.message_thread_id) return; const u = await sql(env, "SELECT user_id FROM users WHERE topic_id = ?", msg.message_thread_id.toString(), 'first'); if (!u) return; const newText = msg.text || msg.caption || "[媒体消息]"; await api(env.BOT_TOKEN, "sendMessage", { chat_id: u.user_id, text: `✏️ 对方修改了消息\n内容: ${escape(newText)}`, parse_mode: "HTML" }); } // 用户侧删除消息处理 async function handleUserDelete(msg, u, env) { if (!msg.reply_to_message) { return api(env.BOT_TOKEN, "sendMessage", { chat_id: u.user_id, text: "⚠️ 请回复要删除的消息后使用 /del 命令", reply_to_message_id: msg.message_id }); } // 检查是否是 Bot 发送的消息(管理员回复) if (msg.reply_to_message.from && msg.reply_to_message.from.is_bot) { console.log(`Delete blocked: User tried to delete bot's message`); return api(env.BOT_TOKEN, "sendMessage", { chat_id: u.user_id, text: "❌ 您只能删除自己发送的消息,无法删除管理员回复的消息", reply_to_message_id: msg.message_id }); } const targetMsgIdRaw = msg.reply_to_message.message_id; const targetMsgId = targetMsgIdRaw.toString(); console.log(`Delete request: user=${u.user_id}, target_msg_raw=${targetMsgIdRaw} (type: ${typeof targetMsgIdRaw}), target_msg_str=${targetMsgId}`); // 查询对应的管理员侧消息ID - 尝试多种可能的格式 let ref = await sql(env, "SELECT topic_message_id FROM messages WHERE user_id=? AND message_id=?", [u.user_id, targetMsgId], 'first'); // 如果没找到,尝试用整数查询(以防数据库中存的是数字) if (!ref || !ref.topic_message_id) { console.log(`First query failed, trying with integer...`); ref = await sql(env, "SELECT topic_message_id FROM messages WHERE user_id=? AND message_id=?", [u.user_id, parseInt(targetMsgId)], 'first'); } if (!ref || !ref.topic_message_id) { console.log(`Delete failed: No mapping found for user=${u.user_id}, msg=${targetMsgId}`); console.log(`Tip: Check database records with: SELECT * FROM messages WHERE user_id='${u.user_id}'`); // 帮助用户排查:列出该用户的最近5条消息记录 try { const recentMsgs = await sql(env, "SELECT message_id, topic_message_id, text FROM messages WHERE user_id=? ORDER BY date DESC LIMIT 5", [u.user_id], 'all'); if (recentMsgs && recentMsgs.results) { console.log(`Recent messages for user ${u.user_id}:`, JSON.stringify(recentMsgs.results)); } } catch (e) { console.log(`Failed to fetch recent messages:`, e.message); } return api(env.BOT_TOKEN, "sendMessage", { chat_id: u.user_id, text: "❌ 未找到对应的消息记录,可能该消息未被转发或已被删除", reply_to_message_id: msg.message_id }); } console.log(`Delete success: Found mapping topic_msg=${ref.topic_message_id}`); try { // 1. 删除被回复的目标消息(先删目标,再删命令) await api(env.BOT_TOKEN, "deleteMessage", { chat_id: u.user_id, message_id: parseInt(targetMsgId) }).catch((e) => console.log("Failed to delete target msg:", e.message)); // 2. 删除用户侧的 /del 命令消息 await api(env.BOT_TOKEN, "deleteMessage", { chat_id: u.user_id, message_id: msg.message_id }).catch((e) => console.log("Failed to delete /del cmd:", e.message)); // 3. 通知管理员(引用原消息) await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: u.topic_id, text: `🗑️ 用户已删除消息`, parse_mode: "HTML", reply_to_message_id: parseInt(ref.topic_message_id) }).catch((e) => console.log("Failed to notify admin:", e.message)); // 4. 清理数据库记录 await sql(env, "DELETE FROM messages WHERE user_id=? AND message_id=?", [u.user_id, targetMsgId]); console.log(`Delete completed: Cleaned up database record`); } catch (e) { console.error("User Delete Failed:", e); await api(env.BOT_TOKEN, "sendMessage", { chat_id: u.user_id, text: "❌ 删除失败,请稍后重试", reply_to_message_id: msg.message_id }); } } // 管理员侧删除消息处理 async function handleAdminDelete(msg, env) { if (!msg.message_thread_id || !(await isAuthAdmin(msg.from.id, env))) return; if (!msg.reply_to_message) { return api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "⚠️ 请回复要删除的消息后使用 /del 命令" }); } const targetTopicMsgId = msg.reply_to_message.message_id; // 查询对应的用户侧消息ID const ref = await sql(env, "SELECT user_id, message_id FROM messages WHERE topic_message_id=?", targetTopicMsgId.toString(), 'first'); if (!ref) { return api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "❌ 未找到对应的消息记录" }); } try { // 1. 删除管理员侧消息(包括 /del 命令本身和被回复的消息) await api(env.BOT_TOKEN, "deleteMessage", { chat_id: msg.chat.id, message_id: msg.message_id }).catch(() => {}); await api(env.BOT_TOKEN, "deleteMessage", { chat_id: msg.chat.id, message_id: targetTopicMsgId }).catch(() => {}); // 2. 删除用户侧消息 await api(env.BOT_TOKEN, "deleteMessage", { chat_id: ref.user_id, message_id: parseInt(ref.message_id) }).catch(() => {}); // 3. 清理数据库记录 await sql(env, "DELETE FROM messages WHERE user_id=? AND message_id=?", [ref.user_id, ref.message_id]); } catch (e) { console.error("Admin Delete Failed:", e); await api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "❌ 删除失败,请稍后重试" }); } } // 私聊消息处理总线 (用户侧逻辑入口) async function handlePrivate(msg, env, ctx) { const id = msg.chat.id.toString(); const text = msg.text || ""; const isAdm = await isPrimaryAdmin(id, env); const u = await getUser(id, env); // 人机验证拦截器 (非管理人员未完成验证则阻断) if (text !== "/start" && u.user_state !== 'pending_verification' && !isAdm) { const isCaptchaOn = await getBool('enable_verify', env); const isQAOn = await getBool('enable_qa_verify', env); if ((isCaptchaOn || isQAOn) && u.user_state !== 'verified') { const now = Date.now(); const lastWarn = CACHE.warn_cd[id] || 0; // 设定3秒冷却限流阈值,防止恶意并发攻击刷量 if (now - lastWarn < 3000) return; CACHE.warn_cd[id] = now; return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "❗️❗️❗️请先进行验证,再发送消息", reply_to_message_id: msg.message_id }); } } // 1. 命令显式路由处理 if (text === "/start") { if (isAdm && ctx) ctx.waitUntil(registerCommands(env)); if (!isAdm) { await updUser(id, { user_state: 'new' }, env); u.user_state = 'new'; } return isAdm ? handleAdminConfig(id, null, 'menu', null, null, env) : sendStart(id, msg, env); } if (text === "/help" && isAdm) return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "ℹ️ 帮助\n• 回复消息即对话\n• /start 打开面板\n• /del 删除消息", parse_mode: "HTML" }); if (text === "/del" && !isAdm) return handleUserDelete(msg, u, env); // 2. 封禁拦截层 if (u.is_blocked) { return; } // 3. 授权状态补偿验证 if (await isAuthAdmin(id, env)) { if(u.user_state !== "verified") await updUser(id, { user_state: "verified" }, env); if(text === "/start" && ctx) ctx.waitUntil(registerCommands(env)); } // 4. 管理员配置状态机判定(面板输入模式截取) if (isAdm) { const stateStr = await getCfg(`admin_state:${id}`, env); if (stateStr) { const state = safeParse(stateStr); if (state.action === 'input') return handleAdminInput(id, msg, state, env); } } // 5. 常规状态验证路由 const isCaptchaOn = await getBool('enable_verify', env); const isQAOn = await getBool('enable_qa_verify', env); if (!isCaptchaOn && !isQAOn) { if (u.user_state !== 'verified') await updUser(id, { user_state: "verified" }, env); return handleVerifiedMsg(msg, u, env); } const state = u.user_state; if (state === 'pending_verification') return verifyAnswer(id, text, env); if (state === 'verified') return handleVerifiedMsg(msg, u, env); return sendStart(id, msg, env); } // 首次接入:渲染欢迎页面与验证环节 async function sendStart(id, msg, env) { const u = await getUser(id, env); if (u.topic_id) { try { const success = await sendInfoCardToTopic(env, u, msg.from, u.topic_id); if (!success) await updUser(id, { topic_id: null }, env); } catch (e) { await updUser(id, { topic_id: null }, env); } } let welcomeRaw = await getCfg('welcome_msg', env); const firstName = (msg.from.first_name || "用户").replace(/&/g,'&').replace(//g,'>'); const nameDisplay = escape(firstName); let mediaConfig = null; let welcomeText = welcomeRaw; try { if (welcomeRaw.trim().startsWith('{')) { mediaConfig = safeParse(welcomeRaw, null); if(mediaConfig) welcomeText = mediaConfig.caption || ""; } } catch {} welcomeText = welcomeText.replace(/{name}|{user}/g, nameDisplay); try { if (mediaConfig && mediaConfig.type) { const method = `send${mediaConfig.type.charAt(0).toUpperCase() + mediaConfig.type.slice(1)}`; let body = { chat_id: id, caption: welcomeText, parse_mode: "HTML" }; if (mediaConfig.type === 'photo') body.photo = mediaConfig.file_id; else if (mediaConfig.type === 'video') body.video = mediaConfig.file_id; else if (mediaConfig.type === 'animation') body.animation = mediaConfig.file_id; else body = { chat_id: id, text: welcomeText, parse_mode: "HTML" }; await api(env.BOT_TOKEN, method, body); } else { await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: welcomeText, parse_mode: "HTML" }); } } catch (e) { await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "Welcome!", parse_mode: "HTML" }); } const url = (env.WORKER_URL || "").replace(/\/$/, ''); const mode = await getCfg('captcha_mode', env); const hasKey = mode === 'recaptcha' ? env.RECAPTCHA_SITE_KEY : env.TURNSTILE_SITE_KEY; const isCaptchaOn = await getBool('enable_verify', env); const isQAOn = await getBool('enable_qa_verify', env); if (isCaptchaOn && url && hasKey) { const nonce = genNonce(); await updUser(id, { user_state: "pending_turnstile", user_info: { ...u.user_info, verify_nonce: nonce, verify_nonce_ts: Date.now() } }, env); return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "🛡️ 安全验证\n请点击下方按钮完成人机验证以继续。", parse_mode: "HTML", reply_markup: { inline_keyboard: [[{ text: "点击进行验证", web_app: { url: `${url}/verify?user_id=${encodeURIComponent(id)}&nonce=${encodeURIComponent(nonce)}` } }]] } }); } else if (isQAOn) { await updUser(id, { user_state: "pending_verification" }, env); return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "❓ 安全提问\n请回答:\n" + await getCfg('verif_q', env), parse_mode: "HTML" }); } else if (isCaptchaOn || isQAOn) { return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "⚠️ 验证功能暂时不可用,请联系管理员检查配置" }); } } // 正常态用户消息防线:敏感词与类型拦截器 async function handleVerifiedMsg(msg, u, env) { const id = u.user_id, text = msg.text || ""; // 敏感词屏蔽预检系统 if (text) { const kws = await getJsonCfg('block_keywords', env); if ((Array.isArray(kws) ? kws : []).some(k => safeRegexTest(k, text))) { const c = u.block_count + 1, max = parseInt(await getCfg('block_threshold', env)) || 5; const willBlock = c >= max; await updUser(id, { block_count: c, is_blocked: willBlock }, env); if (willBlock) { await manageBlacklist(env, u, msg.from, true); return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "❌ 已封禁 (发送 /start 可申请解封)" }); } return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: `⚠️ 屏蔽词 (${c}/${max})` }); } } // 负载类型过滤体系 for (const t of MSG_TYPES) { if (t.check(msg)) { const isAdmin = await isAuthAdmin(id, env); if ((t.extra && !(await getBool(t.extra(msg), env)) && !isAdmin) || (!t.extra && !(await getBool(t.key, env)) && !isAdmin)) { return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: `⚠️ 不接收 ${t.name}` }); } break; } } // 勿扰状态静默应答逻辑 if (await getBool('busy_mode', env)) { const now = Date.now(); if (now - (u.user_info.last_busy_reply || 0) > 300000) { await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "🌙 " + await getCfg('busy_msg', env) }); await updUser(id, { user_info: { ...u.user_info, last_busy_reply: now } }, env); } } // 关键词自动回复钩子 if (text) { const rules = await getJsonCfg('keyword_responses', env); const match = (Array.isArray(rules) ? rules : []).find(r => safeRegexTest(r?.keywords, text)); if (match) return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "自动回复:\n" + match.response }); } // 所有前置校验通过,放行进入转发链路 await relayToTopic(msg, u, env); } // --- 核心:消息转发与话题流控引擎 --- async function relayToTopic(msg, u, env) { const uMeta = getUMeta(msg.from, u, msg.date), uid = u.user_id; let tid = u.topic_id; // 1. 动态检测并同步用户基础标识信息变更 if (u.user_info.name !== uMeta.name || u.user_info.username !== uMeta.username) { u.user_info.name = uMeta.name; u.user_info.username = uMeta.username; await updUser(uid, { user_info: u.user_info }, env); if (u.topic_id) api(env.BOT_TOKEN, "editForumTopic", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: u.topic_id, name: uMeta.topicName }).catch(e=>{}); } // 2. 线程池资源申请:建立独立话题 if (!tid) { if (CACHE.user_locks[uid]) return; CACHE.user_locks[uid] = true; try { const freshU = await getUser(uid, env); if (freshU.topic_id) tid = freshU.topic_id; else { const t = await api(env.BOT_TOKEN, "createForumTopic", { chat_id: env.ADMIN_GROUP_ID, name: uMeta.topicName }); tid = t.message_thread_id.toString(); u.user_info.card_msg_id = null; const dummy = await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: tid, text: "✨ 正在加载用户资料...", disable_notification: true }); u.user_info.dummy_msg_id = dummy.message_id; await updUser(uid, { topic_id: tid, user_info: u.user_info }, env); } } catch (e) { console.error("Topic Creation Failed:", e); delete CACHE.user_locks[uid]; return api(env.BOT_TOKEN, "sendMessage", { chat_id: uid, text: "系统忙,请稍后再试" }); } delete CACHE.user_locks[uid]; } try { let forwardedMsg; const rawText = msg.text || ""; let topicReplyToMsgId = undefined; if (msg.reply_to_message) { const ref = await sql(env, "SELECT topic_message_id FROM messages WHERE user_id=? AND message_id=?", [uid, msg.reply_to_message.message_id.toString()], 'first'); if (ref?.topic_message_id) { topicReplyToMsgId = parseInt(ref.topic_message_id); console.log(`Found reply mapping: user_msg=${msg.reply_to_message.message_id} -> topic_msg=${topicReplyToMsgId}`); } else { console.log(`No reply mapping found for user_msg=${msg.reply_to_message.message_id}`); } } // 特殊引用语法降级渲染支持 if (rawText && (rawText.startsWith('>') || rawText.startsWith('》') || rawText.startsWith('>'))) { let cleanText = rawText.replace(/^[>》]\s?/, '').replace(/^>\s?/, ''); cleanText = escape(cleanText); const customHtml = `
${cleanText}
`; forwardedMsg = await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: tid, text: customHtml, parse_mode: "HTML", reply_to_message_id: topicReplyToMsgId }); } else { // 标准转发处理:统一使用 copyMessage 以支持引用关系 try { const copyParams = { chat_id: env.ADMIN_GROUP_ID, from_chat_id: uid, message_id: msg.message_id, message_thread_id: tid }; if (topicReplyToMsgId) { copyParams.reply_to_message_id = topicReplyToMsgId; } forwardedMsg = await api(env.BOT_TOKEN, "copyMessage", copyParams); } catch(fwdErr) { console.log("copyMessage failed, trying forwardMessage:", fwdErr.message); // forwardMessage 不支持 reply_to_message_id,只能作为备选 forwardedMsg = await api(env.BOT_TOKEN, "forwardMessage", { chat_id: env.ADMIN_GROUP_ID, from_chat_id: uid, message_id: msg.message_id, message_thread_id: tid }); } } // 推送资料卡片流程补偿机制 try { let infoDirty = false; if (!u.user_info.card_msg_id) { try { const cardId = await sendInfoCardToTopic(env, u, msg.from, tid, msg.date); if (cardId) { u.user_info.card_msg_id = cardId; u.user_info.join_date = msg.date || (Date.now()/1000); infoDirty = true; } } catch (innerErr) { if (innerErr.message && (innerErr.message.includes("thread") || innerErr.message.includes("not found"))) { throw innerErr; } } } // 回收临时占位符提升界面整洁度 if (u.user_info.dummy_msg_id) { await api(env.BOT_TOKEN, "deleteMessage", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.dummy_msg_id }).catch(() => {}); delete u.user_info.dummy_msg_id; infoDirty = true; } if (infoDirty) await updUser(uid, { user_info: u.user_info }, env); } catch (processErr) { if (processErr.message && (processErr.message.includes("thread") || processErr.message.includes("not found"))) { await updUser(uid, { topic_id: null }, env); u.topic_id = null; return relayToTopic(msg, u, env); // 进行一次自愈递归重试 } } // --------------------------------------------------------------------- // * 修改点:注释/移除了面向用户的 “✅ 已送达” 反馈回执 API 调用。 // 此动作去除了多余的底层 fetch() 开销。 // 原逻辑: api(env.BOT_TOKEN, "sendMessage", { chat_id: uid, text: "✅ 已送达", ... }).catch(()=>{}); // --------------------------------------------------------------------- // 构建映射关联池,支撑双向回复寻址 if (forwardedMsg && forwardedMsg.message_id) { const storeText = msg.text || "[Media]"; await sql(env, "INSERT OR REPLACE INTO messages (user_id, message_id, text, date, topic_message_id) VALUES (?,?,?,?,?)", [uid, msg.message_id, storeText, msg.date, forwardedMsg.message_id.toString()]); } // 下游归档链路:数据备份 await handleBackup(msg, uMeta, env); } catch (e) { console.error("Relay Failed:", e); if (e.message && (e.message.includes("thread") || e.message.includes("not found") || e.message.includes("Bad Request"))) { await updUser(uid, { topic_id: null }, env); u.topic_id = null; return relayToTopic(msg, u, env); } else { await api(env.BOT_TOKEN, "sendMessage", { chat_id: uid, text: "❌ 发送失败,系统异常" }); } } } // --- 核心:发送用户信息复合卡片 --- async function sendInfoCardToTopic(env, u, tgUser, tid, date) { const meta = getUMeta(tgUser, u, date || (Date.now()/1000)); let bestPhoto = null; // 1. [容错防护] 非阻塞尝试获取目标用户头像 try { const photos = await api(env.BOT_TOKEN, "getUserProfilePhotos", { user_id: u.user_id, limit: 1 }); if (photos && photos.photos && photos.photos.length > 0) { bestPhoto = photos.photos[0][photos.photos[0].length - 1].file_id; } } catch (e) {} try { let cardMsg; // 2. [边界防护] 校验配文长度防止超出 Telegram 1024字符图片 Caption 上限 const isCaptionTooLong = meta.card.length > 1000; // 根据上下文存在性选择装配发送方式 if (bestPhoto && !isCaptionTooLong) { try { cardMsg = await api(env.BOT_TOKEN, "sendPhoto", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: tid, photo: bestPhoto, caption: meta.card, parse_mode: "HTML", reply_markup: getBtns(u.user_id, u.is_blocked, meta.username) }); } catch (err) { if (err.message && (err.message.includes("parse") || err.message.includes("MEDIA"))) { cardMsg = await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: tid, text: meta.card, parse_mode: "HTML", reply_markup: getBtns(u.user_id, u.is_blocked, meta.username) }); } else { throw err; } } } else { cardMsg = await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: tid, text: meta.card, parse_mode: "HTML", reply_markup: getBtns(u.user_id, u.is_blocked, meta.username) }); } try { await api(env.BOT_TOKEN, "pinChatMessage", { chat_id: env.ADMIN_GROUP_ID, message_id: cardMsg.message_id, message_thread_id: tid }); } catch (pinErr) {} return cardMsg.message_id; } catch (e) { console.error("Send Info Card Failed:", e); if (e.message && (e.message.includes("thread") || e.message.includes("not found"))) { throw e; } return null; } } // --- 5. 通用/黑名单 (管理黑名单控制域) --- async function manageBlacklist(env, u, tgUser, isBlocking) { let bid = await getCfg('blocked_topic_id', env); if (!bid && isBlocking) { try { const t = await api(env.BOT_TOKEN, "createForumTopic", { chat_id: env.ADMIN_GROUP_ID, name: "🚫 黑名单" }); bid = t.message_thread_id.toString(); await setCfg('blocked_topic_id', bid, env); } catch { return; } } if (!bid) return; if (isBlocking) { const meta = getUMeta(tgUser, u, Date.now()/1000); const msg = await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: bid, text: `🚫 用户已屏蔽\n${meta.card}`, parse_mode: "HTML", reply_markup: { inline_keyboard: [[{ text: "✅ 解除屏蔽", callback_data: `unblock:${u.user_id}` }]] } }); await updUser(u.user_id, { user_info: { ...u.user_info, blacklist_msg_id: msg.message_id } }, env); } else { if (u.user_info.blacklist_msg_id) { try { await api(env.BOT_TOKEN, "deleteMessage", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.blacklist_msg_id }); } catch (e) { if(e.message && e.message.includes("thread")) await setCfg('blocked_topic_id', "", env); } await updUser(u.user_id, { user_info: { ...u.user_info, blacklist_msg_id: null } }, env); } } } // 下游归档链路:异步数据备份到冷频道 async function handleBackup(msg, meta, env) { const bid = await getCfg('backup_group_id', env); if (!bid) return; try { const header = `📨 备份 ${meta.name} (${meta.userId})`; if (msg.text) await api(env.BOT_TOKEN, "sendMessage", { chat_id: bid, text: header + "\n" + escape(msg.text), parse_mode: "HTML" }); else { await api(env.BOT_TOKEN, "sendMessage", { chat_id: bid, text: header, parse_mode: "HTML" }); await api(env.BOT_TOKEN, "copyMessage", { chat_id: bid, from_chat_id: msg.chat.id, message_id: msg.message_id }); } } catch (e) {} } // --- 6. 管理员功能模块 (双向交互枢纽) --- async function handleAdminReply(msg, env) { if (!msg.message_thread_id || msg.from.is_bot || !(await isAuthAdmin(msg.from.id, env))) return; const stateStr = await getCfg(`admin_state:${msg.from.id}`, env); if (stateStr) { const state = safeParse(stateStr); if (state.action === 'input_note') { const targetUid = state.target; const u = await getUser(targetUid, env); if (msg.text === '/clear' || msg.text === '清除') delete u.user_info.note; else u.user_info.note = msg.text; const mockTgUser = { id: targetUid, username: u.user_info.username || "", first_name: u.user_info.name || "(未获取)", last_name: "" }; const newMeta = getUMeta(mockTgUser, u, u.user_info.join_date || (Date.now()/1000)); // 更新带有新附注的资料实体卡片 if (u.topic_id && u.user_info.card_msg_id) { try { await api(env.BOT_TOKEN, "editMessageCaption", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.card_msg_id, caption: newMeta.card, parse_mode: "HTML", reply_markup: getBtns(targetUid, u.is_blocked, newMeta.username) }); } catch (e) { try { await api(env.BOT_TOKEN, "editMessageText", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.card_msg_id, text: newMeta.card, parse_mode: "HTML", reply_markup: getBtns(targetUid, u.is_blocked, newMeta.username) }); } catch(e2) {} } } await updUser(targetUid, { user_info: u.user_info }, env); await deleteCfg(`admin_state:${msg.from.id}`, env); return api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: `✅ 备注已更新` }); } } const uid = (await sql(env, "SELECT user_id FROM users WHERE topic_id = ?", msg.message_thread_id.toString(), 'first'))?.user_id; if (!uid) return; let replyToMsgId = undefined; if (msg.reply_to_message) { const ref = await sql(env, "SELECT message_id FROM messages WHERE topic_message_id = ?", msg.reply_to_message.message_id.toString(), 'first'); if (ref) replyToMsgId = ref.message_id; } try { const sent = await api(env.BOT_TOKEN, "copyMessage", { chat_id: uid, from_chat_id: msg.chat.id, message_id: msg.message_id, reply_to_message_id: replyToMsgId }); if (sent && sent.message_id) { const storeText = msg.text || msg.caption || "[Admin Message]"; await sql(env, "INSERT OR REPLACE INTO messages (user_id, message_id, text, date, topic_message_id) VALUES (?,?,?,?,?)", [uid, sent.message_id.toString(), storeText, msg.date || Math.floor(Date.now() / 1000), msg.message_id.toString()]); } // 此处为管理员端给用户下发消息的主逻辑。根据之前的版本,管理员侧发送成功后的回执代码也已经去除 } catch (e) { await api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "❌ 内部投递失败" }); } } async function handleEdit(msg, env) { const u = await getUser(msg.from.id.toString(), env); if (!u.topic_id) return; const old = await sql(env, "SELECT text FROM messages WHERE user_id=? AND message_id=?", [u.user_id, msg.message_id], 'first'); const newTxt = msg.text || msg.caption || "[非文本]"; const logText = `✏️ 消息修改\n前: ${escape(old?.text||"?")}\n后: ${escape(newTxt)}`; await api(env.BOT_TOKEN, "sendMessage", { chat_id: env.ADMIN_GROUP_ID, message_thread_id: u.topic_id, text: logText, parse_mode: "HTML" }); if (old) { await sql(env, "UPDATE messages SET text=? WHERE user_id=? AND message_id=?", [newTxt, u.user_id, msg.message_id]); } } // --- 7. Web验证外设接口组件 --- async function handleVerifyPage(url, env) { const uid = url.searchParams.get('user_id'); const nonce = url.searchParams.get('nonce') || ""; const mode = await getCfg('captcha_mode', env); const siteKey = mode === 'recaptcha' ? env.RECAPTCHA_SITE_KEY : env.TURNSTILE_SITE_KEY; if (!uid || !siteKey) return new Response("Miss Config (Check Mode/Key)", { status: 400 }); const scriptUrl = mode === 'recaptcha' ? "https://www.google.com/recaptcha/api.js" : "https://challenges.cloudflare.com/turnstile/v0/api.js"; const divClass = mode === 'recaptcha' ? "g-recaptcha" : "cf-turnstile"; const html = `

🛡️ 安全验证

`; return new Response(html, { headers: { "Content-Type": "text/html" } }); } async function handleTokenSubmit(req, env) { try { const { token, userId, nonce, initData } = await req.json(); const parsed = await verifyTelegramInitData(initData || "", env.BOT_TOKEN, 600); const verifiedUserId = parsed?.userId?.toString(); if (!verifiedUserId || (userId && userId.toString() !== verifiedUserId)) throw new Error("Invalid Telegram initData"); const user = await getUser(verifiedUserId, env); if (user.is_blocked && !(await isAuthAdmin(verifiedUserId, env))) throw new Error("Blocked user"); if (!nonce || user.user_info.verify_nonce !== nonce || Date.now() - (user.user_info.verify_nonce_ts || 0) > 15 * 60 * 1000) { throw new Error("Invalid verification nonce"); } const mode = await getCfg('captcha_mode', env); let success = false; const verifyUrl = mode === 'recaptcha' ? 'https://www.google.com/recaptcha/api/siteverify' : 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; const params = mode === 'recaptcha' ? new URLSearchParams({ secret: env.RECAPTCHA_SECRET_KEY, response: token }) : JSON.stringify({ secret: env.TURNSTILE_SECRET_KEY, response: token }); const headers = mode === 'recaptcha' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : { 'Content-Type': 'application/json' }; const r = await fetch(verifyUrl, { method: 'POST', headers, body: params }); const d = await r.json(); success = d.success; if (!success) throw new Error("Invalid Token"); if (await getBool('enable_qa_verify', env)) { await updUser(verifiedUserId, { user_state: "pending_verification", user_info: { ...user.user_info, verify_nonce: "", verify_nonce_ts: 0 } }, env); await api(env.BOT_TOKEN, "sendMessage", { chat_id: verifiedUserId, text: "✅ 验证通过!\n请回答:\n" + await getCfg('verif_q', env) }); } else { await updUser(verifiedUserId, { user_state: "verified", user_info: { ...user.user_info, verify_nonce: "", verify_nonce_ts: 0 } }, env); await api(env.BOT_TOKEN, "sendMessage", { chat_id: verifiedUserId, text: "✅ 验证通过!\n现在您可以直接发送消息,我会帮您转达给管理员。" }); } return new Response(JSON.stringify({ success: true })); } catch { return new Response(JSON.stringify({ success: false }), { status: 400 }); } } async function verifyAnswer(id, ans, env) { if (ans.trim() === (await getCfg('verif_a', env)).trim()) { await updUser(id, { user_state: "verified" }, env); await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "✅ 验证通过!\n现在您可以直接发送消息,我会帮您转达给管理员。" }); } else await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "❌ 错误" }); } // --- 8. 菜单回调调度控制室 --- async function handleCallback(cb, env) { const { data, message: msg, from } = cb; const [act, p1, p2, p3] = data.split(':'); if (act === 'note' && p1 === 'set') { if (!msg || msg.chat.id.toString() !== env.ADMIN_GROUP_ID || !(await isAuthAdmin(from.id, env))) { return api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "无操作权限", show_alert: true }); } try { await setCfg(`admin_state:${from.id}`, JSON.stringify({ action: 'input_note', target: p2 }), env); await api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "⌨️ 请回复备注内容 (回复 /clear 清除):" }); return api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "请直接回复备注内容" }); } catch (e) { console.error("Set Note State Failed:", e); await deleteCfg(`admin_state:${from.id}`, env).catch(() => {}); return api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "操作失败,请重试", show_alert: true }); } } if (act === 'config') { if (!(await isPrimaryAdmin(from.id, env))) return api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "无操作权限", show_alert: true }); if (p1 === 'rotate_mode') { const currentMode = await getCfg('captcha_mode', env); const isEnabled = await getBool('enable_verify', env); let nextMode = 'turnstile'; let nextEnable = 'true'; let toast = "已切换: Cloudflare"; if (isEnabled) { if (currentMode === 'turnstile') { nextMode = 'recaptcha'; toast = "已切换: Google Recaptcha"; } else { nextEnable = 'false'; nextMode = currentMode; toast = "验证码功能已关闭"; } } else { nextMode = 'turnstile'; nextEnable = 'true'; toast = "已切换: Cloudflare"; } await setCfg('captcha_mode', nextMode, env); await setCfg('enable_verify', nextEnable, env); await api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: toast }); return handleAdminConfig(msg.chat.id, msg.message_id, 'menu', 'base', null, env); } await api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id }); return handleAdminConfig(msg.chat.id, msg.message_id, p1, p2, p3, env); } if (msg && msg.chat.id.toString() === env.ADMIN_GROUP_ID) { if (!(await isAuthAdmin(from.id, env))) { return api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "无操作权限", show_alert: true }); } await api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id }); if (act === 'pin_card') await api(env.BOT_TOKEN, "pinChatMessage", { chat_id: msg.chat.id, message_id: msg.message_id, message_thread_id: msg.message_thread_id }); else if (['block','unblock'].includes(act)) { const isB = act === 'block'; const uid = p1; const u = await getUser(uid, env); const bid = await getCfg('blocked_topic_id', env); await updUser(uid, { is_blocked: isB, block_count: 0 }, env); // 响应变更,刷新目标人员资料卡片上的按钮渲染状态 if (u.user_info.card_msg_id) { api(env.BOT_TOKEN, "editMessageReplyMarkup", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.card_msg_id, reply_markup: getBtns(uid, isB, u.user_info.username) }).catch(()=>{}); } await manageBlacklist(env, u, { id: uid, username: u.user_info.username, first_name: u.user_info.name }, isB); if (!isB && msg.message_thread_id && bid && msg.message_thread_id.toString() === bid) await api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "✅ 已解除屏蔽" }); else await api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: isB ? "❌ 已屏蔽" : "✅ 已解封" }); } } } // --- 9. 管理控制后台面板渲染 --- async function handleAdminConfig(cid, mid, type, key, val, env) { const render = (txt, kb) => api(env.BOT_TOKEN, mid?"editMessageText":"sendMessage", { chat_id: cid, message_id: mid, text: txt, parse_mode: "HTML", reply_markup: kb }); const back = { text: "🔙 返回", callback_data: "config:menu" }; try { if (!type || type === 'menu') { if (!key) return render("⚙️ 控制面板", { inline_keyboard: [[{text:"📝 基础",callback_data:"config:menu:base"},{text:"🤖 自动回复",callback_data:"config:menu:ar"}], [{text:"🚫 屏蔽词",callback_data:"config:menu:kw"},{text:"🛠 过滤",callback_data:"config:menu:fl"}], [{text:"👮 协管",callback_data:"config:menu:auth"},{text:"💾 备份/通知",callback_data:"config:menu:bak"}], [{text:"🌙 营业状态",callback_data:"config:menu:busy"}]] }); if (key === 'base') { const mode = await getCfg('captcha_mode', env); const captchaOn = await getBool('enable_verify', env); const qaOn = await getBool('enable_qa_verify', env); let statusText = "❌ 已关闭"; if (captchaOn) statusText = mode === 'recaptcha' ? "Google" : "Cloudflare"; return render(`基础配置\n验证码模式: ${statusText}\n问题验证: ${qaOn?"✅":"❌"}`, { inline_keyboard: [[{text:"欢迎语",callback_data:"config:edit:welcome_msg"},{text:"问题",callback_data:"config:edit:verif_q"},{text:"答案",callback_data:"config:edit:verif_a"}], [{text: `验证码模式: ${statusText} (点击切换)`, callback_data:`config:rotate_mode`}], [{text: `问题验证: ${qaOn?"✅ 开启":"❌ 关闭"}`, callback_data:`config:toggle:enable_qa_verify:${!qaOn}`}], [back]] }); } if (key === 'fl') return render("🛠 过滤设置", await getFilterKB(env)); if (['ar','kw','auth'].includes(key)) return render(`列表: ${key}`, await getListKB(key, env)); if (key === 'bak') { const bid = await getCfg('backup_group_id', env), blk = await getCfg('blocked_topic_id', env); return render(`💾 备份与通知\n备份群: ${bid||"无"}\n黑名单话题: ${blk?`✅ (${blk})`:"⏳"}`, { inline_keyboard: [[{text:"设备份群",callback_data:"config:edit:backup_group_id"},{text:"清备份",callback_data:"config:cl:backup_group_id"}],[{text:"重置黑名单",callback_data:"config:cl:blocked_topic_id"}],[back]] }); } if (key === 'busy') { const on = await getBool('busy_mode', env), msg = await getCfg('busy_msg', env); return render(`🌙 营业状态\n当前: ${on?"🔴 休息中":"🟢 营业中"}\n回复语: ${escape(msg)}`, { inline_keyboard: [[{text:`切换为 ${on?"🟢 营业":"🔴 休息"}`,callback_data:`config:toggle:busy_mode:${!on}`}], [{text:"✏️ 修改回复语",callback_data:"config:edit:busy_msg"}], [back]] }); } } if (type === 'toggle') { await setCfg(key, val, env); return key==='busy_mode' ? handleAdminConfig(cid,mid,'menu','busy',null,env) : (key==='enable_qa_verify' ? handleAdminConfig(cid,mid,'menu','base',null,env) : render("🛠 过滤设置", await getFilterKB(env))); } if (type === 'cl') { await setCfg(key, key==='authorized_admins'?'[]':'', env); return handleAdminConfig(cid, mid, 'menu', key==='blocked_topic_id'?'bak':(key==='authorized_admins'?'auth':'bak'), null, env); } if (type === 'del') { const realK = key==='kw'?'block_keywords':(key==='auth'?'authorized_admins':'keyword_responses'); let l = await getJsonCfg(realK, env); l = l.filter(i => (i.id||i).toString() !== val); await setCfg(realK, JSON.stringify(l), env); return render(`列表: ${key}`, await getListKB(key, env)); } if (type === 'edit' || type === 'add') { await setCfg(`admin_state:${cid}`, JSON.stringify({ action: 'input', key: key + (type==='add'?'_add':'') }), env); let promptText = `请输入 ${key} 的值 (/cancel 取消):`; if (key === 'ar' && type === 'add') promptText = `请输入自动回复规则,格式:\n关键词===回复内容\n\n例如:价格===请联系人工客服\n(/cancel 取消)`; if (key === 'welcome_msg') promptText = `请发送新的欢迎语 (/cancel 取消):\n\n• 支持 文字图片/视频/GIF\n• 支持占位符: {name}\n• 直接发送媒体即可`; return api(env.BOT_TOKEN, "editMessageText", { chat_id: cid, message_id: mid, text: promptText, parse_mode: "HTML" }); } } catch (e) { console.error("Admin Config Failed:", e); await deleteCfg(`admin_state:${cid}`, env).catch(() => {}); return api(env.BOT_TOKEN, "sendMessage", { chat_id: cid, text: "❌ 面板加载失败,请稍后重试" }); } } async function getFilterKB(env) { const s = async k => (await getBool(k, env)) ? "✅" : "❌"; const b = (t, k, v) => ({ text: `${t} ${v}`, callback_data: `config:toggle:${k}:${v==="❌"}` }); const keys = ['enable_admin_receipt', 'enable_forward_forwarding', 'enable_image_forwarding', 'enable_audio_forwarding', 'enable_sticker_forwarding', 'enable_link_forwarding', 'enable_channel_forwarding', 'enable_text_forwarding']; const vals = await Promise.all(keys.map(k => s(k))); return { inline_keyboard: [[b("回执", keys[0], vals[0]), b("转发", keys[1], vals[1])], [b("媒体", keys[2], vals[2]), b("语音", keys[3], vals[3])], [b("贴纸", keys[4], vals[4]), b("链接", keys[5], vals[5])], [b("频道", keys[6], vals[6]), b("文本", keys[7], vals[7])], [{ text: "🔙 返回", callback_data: "config:menu" }]] }; } async function getListKB(type, env) { const k = type==='ar'?'keyword_responses':(type==='kw'?'block_keywords':'authorized_admins'); const l = await getJsonCfg(k, env); const btns = l.map((i, idx) => [{ text: `🗑 ${type==='ar'?i.keywords:i}`, callback_data: `config:del:${type}:${i.id||i}` }]); btns.push([{ text: "➕ 添加", callback_data: `config:add:${type}` }], [{ text: "🔙 返回", callback_data: "config:menu" }]); return { inline_keyboard: btns }; } async function handleAdminInput(id, msg, state, env) { const txt = msg.text || ""; if (txt === '/cancel') { await deleteCfg(`admin_state:${id}`, env); return handleAdminConfig(id, null, 'menu', null, null, env); } let k = state.key, val = txt; try { if (k === 'welcome_msg') { if (msg.photo || msg.video || msg.animation) { let fileId, type; if (msg.photo) { type = 'photo'; fileId = msg.photo[msg.photo.length - 1].file_id; } else if (msg.video) { type = 'video'; fileId = msg.video.file_id; } else if (msg.animation) { type = 'animation'; fileId = msg.animation.file_id; } val = JSON.stringify({ type: type, file_id: fileId, caption: msg.caption || "" }); } else { val = txt; } } else if (k.endsWith('_add')) { k = k.replace('_add', ''); const realK = k==='ar'?'keyword_responses':(k==='kw'?'block_keywords':'authorized_admins'); const list = await getJsonCfg(realK, env); if (k === 'ar') { const [kk, rr] = txt.split('==='); if(kk && rr) list.push({keywords:kk, response:rr, id:Date.now()}); else return api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: "❌ 格式识别异常,请使用:关键词===回复内容" }); } else list.push(txt); val = JSON.stringify(list); k = realK; } else if (k === 'authorized_admins') { val = JSON.stringify(txt.split(/[,,]/).map(s => s.trim()).filter(Boolean)); } await setCfg(k, val, env); await deleteCfg(`admin_state:${id}`, env); const displayVal = (val.startsWith('{') && k === 'welcome_msg') ? "[媒体配置组合]" : val.substring(0,100); await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: `✅ ${k} 规则已写入:\n${displayVal}` }); await handleAdminConfig(id, null, 'menu', null, null, env); } catch (e) { await api(env.BOT_TOKEN, "sendMessage", { chat_id: id, text: `❌ 指令提交受阻: ${e.message}` }); } } // --- 10. 工具函数池 (Pure Functions) --- const getBool = async (k, e) => (await getCfg(k, e)) === 'true'; const getJsonCfg = async (k, e) => safeParse(await getCfg(k, e), []); const escape = t => (t||"").toString().replace(/&/g,'&').replace(//g,'>'); function parseIdsToSet(str) { return new Set((str || "").toString().split(/[,,]/).map(s => s.trim()).filter(Boolean)); } async function getAdminSets(env) { const now = Date.now(); if (CACHE.admin.ts && now - CACHE.admin.ts < CACHE.admin.ttl) return CACHE.admin; const primary = parseIdsToSet(env.ADMIN_IDS || ""); const authList = await getJsonCfg('authorized_admins', env); const auth = new Set([...primary, ...((Array.isArray(authList) ? authList : []).map(x => x.toString()))]); CACHE.admin = { ts: now, ttl: CACHE.admin.ttl, primary, auth }; return CACHE.admin; } const isPrimaryAdmin = async (id, e) => (await getAdminSets(e)).primary.has(id.toString()); const isAuthAdmin = async (id, e) => (await getAdminSets(e)).auth.has(id.toString()); function safeRegexTest(pattern, text) { try { if (!pattern || typeof pattern !== "string") return false; const p = pattern.trim(); if (!p || p.length > REGEX_MAX_PATTERN_LEN) return false; if (REGEX_REJECT_PATTERNS.some(re => re.test(p))) return false; const t = (text || "").toString(); return new RegExp(p, 'gi').test(t.length > REGEX_MAX_TEXT_LEN ? t.slice(0, REGEX_MAX_TEXT_LEN) : t); } catch { return false; } } function isTelegramWebhook(req, env) { const secret = (env.TELEGRAM_WEBHOOK_SECRET || "").toString(); if (!secret) return true; const header = req.headers.get("X-Telegram-Bot-Api-Secret-Token") || ""; return timingSafeEqualStr(header, secret); } function timingSafeEqualStr(a, b) { const aa = (a || "").toString(); const bb = (b || "").toString(); let out = aa.length ^ bb.length; const len = Math.max(aa.length, bb.length); for (let i = 0; i < len; i++) out |= (aa.charCodeAt(i) || 0) ^ (bb.charCodeAt(i) || 0); return out === 0; } function genNonce(bytes = 24) { const data = new Uint8Array(bytes); crypto.getRandomValues(data); return btoa(String.fromCharCode(...data)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); } async function hmacSha256(key, data) { const enc = new TextEncoder(); const cryptoKey = await crypto.subtle.importKey("raw", key instanceof Uint8Array ? key : enc.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); return new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(data))); } function hex(bytes) { return [...bytes].map(b => b.toString(16).padStart(2, "0")).join(""); } async function verifyTelegramInitData(initData, botToken, maxAgeSec = 600) { if (!initData || !botToken) return null; const params = new URLSearchParams(initData); const receivedHash = params.get("hash") || ""; if (!receivedHash) return null; params.delete("hash"); const authDate = Number(params.get("auth_date") || 0); if (!authDate || Math.abs(Math.floor(Date.now() / 1000) - authDate) > maxAgeSec) return null; const checkString = [...params.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join("\n"); const secret = await hmacSha256("WebAppData", botToken); const calcHash = hex(await hmacSha256(secret, checkString)); if (!timingSafeEqualStr(calcHash, receivedHash)) return null; const user = safeParse(params.get("user"), null); return user?.id ? { userId: user.id.toString(), user } : null; }; // HTML `` 标签组装逻辑:穿透安全审查拦截,对无用户名账号建立伪链接 const getUMeta = (tgUser, dbUser, d) => { const id = tgUser.id.toString(); const firstName = tgUser.first_name || ""; const lastName = tgUser.last_name || ""; let name = (firstName + " " + lastName).trim(); if (!name) name = "未命名匿名用户"; // 文本级伪链接构建协议 const safeName = `${escape(name)}`; const note = dbUser.user_info && dbUser.user_info.note ? `\n📝 附加备注: ${escape(dbUser.user_info.note)}` : ""; const labelDisplay = tgUser.username ? `@${tgUser.username}` : "未设公开ID"; const timeStr = new Date(d*1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); return { userId: id, name, username: tgUser.username, topicName: name.substring(0, 128), card: `🪪 用户身份卡片\n---\n👤: ${safeName}\n🏷️: ${labelDisplay}\n🆔: ${id}${note}\n🕒: ${timeStr}` }; }; // 动态键盘矩阵构建:受限 API 规避检查点 const getBtns = (id, blk, username) => { const btns = []; // username 条件控制路由:缺少公开标识符强制屏蔽按钮注册 if (username) { btns.push([{ text: "👤 访问个人主页", url: `https://t.me/${username}` }]); } btns.push([{ text: blk ? "✅ 执行解封" : "🚫 执行屏蔽", callback_data: `${blk ? 'unblock' : 'block'}:${id}` }]); btns.push([{ text: "✏️ 录入备注", callback_data: `note:set:${id}` }, { text: "📌 提升置顶", callback_data: `pin_card:${id}` }]); return { inline_keyboard: btns }; };