From ebf3d1f1fefe9030d76d5be982f443c6fb3847fc Mon Sep 17 00:00:00 2001 From: Orion Date: Thu, 9 Apr 2026 09:41:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E2=9C=A8=20=E6=96=B0=E5=A2=9ETel?= =?UTF-8?q?egram=E6=9C=BA=E5=99=A8=E4=BA=BA=E5=AE=8C=E6=95=B4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加完整的Telegram机器人脚本tg-bot.js,包含验证、消息转发、缓存及配置面板等功能。主要修改包括:修复无用户名用户推送问题,移除管理员及用户消息回执提示。 --- tg-bot.js | 898 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 898 insertions(+) create mode 100644 tg-bot.js diff --git a/tg-bot.js b/tg-bot.js new file mode 100644 index 0000000..bbcd4cf --- /dev/null +++ b/tg-bot.js @@ -0,0 +1,898 @@ +/** + * Telegram Bot Worker v3.67 (No-Receipt Mod & No-Username Fix) + * 完整功能版:保留所有备份、配置面板、辅助函数 + * * 修改 1: 修复了无用户名用户无法推送卡片的问题 + * * 修改 2: 彻底移除了管理员回复的“✅ 已回复”提示 + * * 修改 3: 彻底移除了用户发送消息后的“✅ 已送达”回执 + */ + +// --- 1. 静态配置与常量 --- +// 缓存系统,用于减少数据库读写压力,降低 Worker KV/D1 计费 +const CACHE = { data: {}, ts: 0, ttl: 60000, user_locks: {}, warn_cd: {} }; + +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: "纯文本" } +]; + +// --- 2. 核心入口 (Entry Point) --- +export default { + async fetch(req, env, ctx) { + // 确保数据库初始化完毕,waitUntil不会阻塞主线程的即时响应 + ctx.waitUntil(dbInit(env).catch(err => 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.67 Active", { status: 200 }); + } + // POST 请求处理:Telegram Webhook 核心逻辑接收端 + if (req.method === "POST") { + if (url.pathname === "/submit_token") return handleTokenSubmit(req, env); + 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; +} + +// 获取或初始化用户信息实体 +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 list = [...(env.ADMIN_IDS||"").split(/[,,]/), ...(safeParse(await getCfg('authorized_admins', env), []))]; + const admins = [...new Set(list.map(i=>i.trim()).filter(Boolean))]; + 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) 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 handlePrivate(msg, env, ctx) { + const id = msg.chat.id.toString(); + const text = msg.text || ""; + const isAdm = (env.ADMIN_IDS || "").includes(id); + const u = await getUser(id, env); + + // 人机验证拦截器 (非管理人员未完成验证则阻断) + if (text !== "/start" && !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 打开面板", parse_mode: "HTML" }); + + // 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) { + 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=${id}` } }]] } + }); + } else if (!isCaptchaOn && 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" + }); + } +} + +// 正常态用户消息防线:敏感词与类型拦截器 +async function handleVerifiedMsg(msg, u, env) { + const id = u.user_id, text = msg.text || ""; + + // 敏感词屏蔽预检系统 + if (text) { + const kws = await getJsonCfg('block_keywords', env); + if (kws.some(k => new RegExp(k, 'gi').test(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 = rules.find(r => new RegExp(r.keywords, 'gi').test(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 || ""; + + // 特殊引用语法降级渲染支持 + 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" + }); + } else { + // 标准转发处理尝试,若触碰受限隐私配置则回退到原生复制 + try { + 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 + }); + } catch(fwdErr) { + forwardedMsg = await api(env.BOT_TOKEN, "copyMessage", { + 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 { + 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" + 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 setCfg(`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 { + 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 }); + // 此处为管理员端给用户下发消息的主逻辑。根据之前的版本,管理员侧发送成功后的回执代码也已经去除 + } catch (e) { 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 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 } = await req.json(); + 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(userId, { user_state: "pending_verification" }, env); + await api(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "✅ 验证通过!\n请回答:\n" + await getCfg('verif_q', env) }); + } else { + await updUser(userId, { user_state: "verified" }, env); + await api(env.BOT_TOKEN, "sendMessage", { chat_id: userId, 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') { + await setCfg(`admin_state:${from.id}`, JSON.stringify({ action: 'input_note', target: p2 }), env); + return api(env.BOT_TOKEN, "sendMessage", { chat_id: msg.chat.id, message_thread_id: msg.message_thread_id, text: "⌨️ 请回复备注内容 (回复 /clear 清除):" }); + } + + if (act === 'config') { + if (!(env.ADMIN_IDS||"").includes(from.id.toString())) 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.chat.id.toString() === env.ADMIN_GROUP_ID) { + await api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id }); + if (act === 'pin_card') 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) api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: cb.id, text: "✅ 已解除屏蔽" }); + else 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) { api(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: mid, text: "Data Fetch Error", show_alert: true }); } +} + +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 sql(env, "DELETE FROM config WHERE key=?", `admin_state:${id}`); 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 sql(env, "DELETE FROM config WHERE key=?", `admin_state:${id}`); + 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) { 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,'>'); +const isAuthAdmin = async (id, e) => { + const idStr = id.toString(); + if ((e.ADMIN_IDS||"").includes(idStr)) return true; + const list = await getJsonCfg('authorized_admins', e); + return list.includes(idStr); +}; + +// 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 }; +}; \ No newline at end of file