feat(core): 添加 Homebrew 智能升级管理脚本

新增 brew-upgrade-manager.sh 脚本,实现 Homebrew 自动化升级及 Cask 更新。引入 Python PTY 伪终端处理机制,修复了自动化过程中因终端尺寸导致的 Ruby 渲染崩溃问题,并对部分项目文件进行了归类整理。
This commit is contained in:
2026-04-24 08:45:08 +08:00
parent ad9fa63853
commit 2470bdc278
3 changed files with 208 additions and 0 deletions

898
telegram/tg-bot.js Normal file
View File

@@ -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: `✏️ <b>对方修改了消息</b>\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: " <b>帮助</b>\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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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: "🛡️ <b>安全验证</b>\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: "❓ <b>安全提问</b>\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('&gt;'))) {
let cleanText = rawText.replace(/^[>》]\s?/, '').replace(/^&gt;\s?/, '');
cleanText = escape(cleanText);
const customHtml = `<blockquote>${cleanText}</blockquote>`;
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: `<b>🚫 用户已屏蔽</b>\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 = `<b>📨 备份</b> ${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 = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><script src="https://telegram.org/js/telegram-web-app.js"></script><script src="${scriptUrl}" async defer></script><style>body{display:flex;justify-content:center;align-items:center;height:100vh;background:#fff;font-family:sans-serif}#c{text-align:center;padding:20px;background:#f0f0f0;border-radius:10px}</style></head><body><div id="c"><h3>🛡️ 安全验证</h3><div class="${divClass}" data-sitekey="${siteKey}" data-callback="S"></div><div id="m"></div></div><script>const tg=window.Telegram.WebApp;tg.ready();function S(t){document.getElementById('m').innerText='验证中...';fetch('/submit_token',{method:'POST',body:JSON.stringify({token:t,userId:'${uid}'})}).then(r=>r.json()).then(d=>{if(d.success){document.getElementById('m').innerText='✅';setTimeout(()=>{tg.close();window.close();},1000)}else{document.getElementById('m').innerText='❌'}}).catch(e=>{document.getElementById('m').innerText='Error'})}</script></body></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("⚙️ <b>控制面板</b>", { 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("🛠 <b>过滤设置</b>", 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(`💾 <b>备份与通知</b>\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(`🌙 <b>营业状态</b>\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("🛠 <b>过滤设置</b>", 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<b>关键词===回复内容</b>\n\n例如:价格===请联系人工客服\n(/cancel 取消)`;
if (key === 'welcome_msg') promptText = `请发送新的欢迎语 (/cancel 取消):\n\n• 支持 <b>文字</b> 或 <b>图片/视频/GIF</b>\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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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 `<a>` 标签组装逻辑:穿透安全审查拦截,对无用户名账号建立伪链接
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 = `<a href="tg://user?id=${id}"><b>${escape(name)}</b></a>`;
const note = dbUser.user_info && dbUser.user_info.note ? `\n📝 <b>附加备注:</b> ${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: `<b>🪪 用户身份卡片</b>\n---\n👤: ${safeName}\n🏷️: ${labelDisplay}\n🆔: <code>${id}</code>${note}\n🕒: <code>${timeStr}</code>`
};
};
// 动态键盘矩阵构建:受限 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 };
};