async function dbConfigGet(key, env) {
const row = await env.TG_BOT_DB.prepare("SELECT value FROM config WHERE key = ?").bind(key).first();
return row ? row.value : null;
}
async function dbConfigPut(key, value, env) {
await env.TG_BOT_DB.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").bind(key, value).run();
}
async function dbUserGetOrCreate(userId, env) {
let user = await env.TG_BOT_DB.prepare("SELECT * FROM users WHERE user_id = ?").bind(userId).first();
if (!user) {
await env.TG_BOT_DB.prepare(
"INSERT INTO users (user_id, user_state, is_blocked, is_muted, block_count) VALUES (?, 'new', 0, 0, 0)"
).bind(userId).run();
user = await env.TG_BOT_DB.prepare("SELECT * FROM users WHERE user_id = ?").bind(userId).first();
}
if (user) {
user.is_blocked = user.is_blocked === 1;
user.is_muted = user.is_muted === 1;
user.user_info = user.user_info_json ? JSON.parse(user.user_info_json) : null;
}
return user;
}
async function dbUserUpdate(userId, data, env) {
if (data.user_info) {
data.user_info_json = JSON.stringify(data.user_info);
delete data.user_info;
}
const fields = Object.keys(data).map(key => {
if ((key === 'is_blocked' || key === 'is_muted') && typeof data[key] === 'boolean') {
return `${key} = ?`;
}
return `${key} = ?`;
}).join(', ');
const values = Object.keys(data).map(key => {
if ((key === 'is_blocked' || key === 'is_muted') && typeof data[key] === 'boolean') {
return data[key] ? 1 : 0;
}
return data[key];
});
await env.TG_BOT_DB.prepare(`UPDATE users SET ${fields} WHERE user_id = ?`).bind(...values, userId).run();
}
async function dbTopicUserGet(topicId, env) {
const row = await env.TG_BOT_DB.prepare("SELECT user_id FROM users WHERE topic_id = ?").bind(topicId).first();
return row ? row.user_id : null;
}
async function dbMessageDataPut(userId, messageId, data, env) {
await env.TG_BOT_DB.prepare(
"INSERT OR REPLACE INTO messages (user_id, message_id, text, date) VALUES (?, ?, ?, ?)"
).bind(userId, messageId, data.text, data.date).run();
}
async function dbMessageDataGet(userId, messageId, env) {
const row = await env.TG_BOT_DB.prepare(
"SELECT text, date FROM messages WHERE user_id = ? AND message_id = ?"
).bind(userId, messageId).first();
return row || null;
}
async function dbAdminStateDelete(userId, env) {
await env.TG_BOT_DB.prepare("DELETE FROM config WHERE key = ?").bind(`admin_state:${userId}`).run();
}
async function dbAdminStateGet(userId, env) {
const stateJson = await dbConfigGet(`admin_state:${userId}`, env);
return stateJson || null;
}
async function dbAdminStatePut(userId, stateJson, env) {
await dbConfigPut(`admin_state:${userId}`, stateJson, env);
}
async function dbMigrate(env) {
if (!env.TG_BOT_DB) {
throw new Error("D1 database binding 'TG_BOT_DB' is missing.");
}
const configTableQuery = `
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT
);
`;
const usersTableQuery = `
CREATE TABLE IF NOT EXISTS users (
user_id TEXT PRIMARY KEY NOT NULL,
user_state TEXT NOT NULL DEFAULT 'new',
is_blocked INTEGER NOT NULL DEFAULT 0,
is_muted INTEGER NOT NULL DEFAULT 0,
block_count INTEGER NOT NULL DEFAULT 0,
topic_id TEXT,
info_card_message_id TEXT,
user_info_json TEXT
);
`;
const messagesTableQuery = `
CREATE TABLE IF NOT EXISTS messages (
user_id TEXT NOT NULL,
message_id TEXT NOT NULL,
text TEXT,
date INTEGER,
PRIMARY KEY (user_id, message_id)
);
`;
try {
await env.TG_BOT_DB.batch([
env.TG_BOT_DB.prepare(configTableQuery),
env.TG_BOT_DB.prepare(usersTableQuery),
env.TG_BOT_DB.prepare(messagesTableQuery),
]);
const addColumns = [
"ALTER TABLE users ADD COLUMN is_muted INTEGER DEFAULT 0",
"ALTER TABLE users ADD COLUMN info_card_message_id TEXT",
"ALTER TABLE users ADD COLUMN block_log_message_id TEXT",
"ALTER TABLE users ADD COLUMN profile_log_message_id TEXT",
"ALTER TABLE users ADD COLUMN verification_code TEXT"
];
for (const query of addColumns) {
try {
await env.TG_BOT_DB.prepare(query).run();
} catch (e) {
}
}
} catch (e) {
console.error("D1 Migration Failed:", e);
throw new Error(`D1 Initialization Failed: ${e.message}`);
}
}
function escapeHtml(text) {
if (!text) return '';
return text.toString()
.replace(/&/g, '&')
.replace(//g, '>');
}
function generateRandomCode(length = 4) {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function formatTimestamp(timestamp) {
if (!timestamp) return '时间未知';
return new Date(timestamp * 1000).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
}
function getUserInfo(user, initialTimestamp = null) {
const userId = user.id.toString();
const rawName = (user.first_name || "") + (user.last_name ? ` ${user.last_name}` : "");
const rawUsername = user.username ? `@${user.username}` : "无";
const safeName = escapeHtml(rawName);
const safeUsername = escapeHtml(rawUsername);
const safeUserId = escapeHtml(userId);
const topicName = `${rawName.trim()} | ${userId}`.substring(0, 128);
const infoCard = `
👤 用户资料卡
• 用户名: ${safeUsername}
• ID: ${safeUserId}
`.trim();
return { userId, name: rawName, username: rawUsername, topicName, infoCard };
}
function getInfoCardButtons(userId, isBlocked, isMuted) {
const blockAction = isBlocked ? "unblock" : "block";
const blockText = isBlocked ? "✅ 解除屏蔽" : "🚫 屏蔽此人";
const muteAction = isMuted ? "unmute" : "mute";
const muteText = isMuted ? "🔔 解除静音" : "🔕 静音通知";
return {
inline_keyboard: [
[{
text: blockText,
callback_data: `${blockAction}:${userId}`
}, {
text: muteText,
callback_data: `${muteAction}:${userId}`
}],
[{
text: "👤 查看用户资料",
url: `tg://user?id=${userId}`
}],
[{
text: "📌 置顶此消息",
callback_data: `pin_card:${userId}`
}]
]
};
}
async function ensureLogTopicExists(env) {
const logTopicKey = 'user_profile_log_topic_id';
let logTopicId = await dbConfigGet(logTopicKey, env);
if (!logTopicId) {
try {
const topic = await telegramApi(env.BOT_TOKEN, "createForumTopic", {
chat_id: env.ADMIN_GROUP_ID,
name: "📋 用户资料卡汇总 (User Logs)",
icon_custom_emoji_id: null
});
logTopicId = topic.message_thread_id.toString();
await dbConfigPut(logTopicKey, logTopicId, env);
} catch (e) {
console.error("创建汇总话题失败:", e);
return null;
}
}
return logTopicId;
}
async function ensureBlockLogTopicExists(env) {
const logTopicKey = 'user_block_log_topic_id';
let logTopicId = await dbConfigGet(logTopicKey, env);
if (!logTopicId) {
try {
const topic = await telegramApi(env.BOT_TOKEN, "createForumTopic", {
chat_id: env.ADMIN_GROUP_ID,
name: "🚫 屏蔽与静音名单 (Block/Mute Log)",
icon_custom_emoji_id: null
});
logTopicId = topic.message_thread_id.toString();
await dbConfigPut(logTopicKey, logTopicId, env);
} catch (e) {
console.error("创建屏蔽名单话题失败:", e);
return null;
}
}
return logTopicId;
}
async function getConfig(key, env, defaultValue) {
const configValue = await dbConfigGet(key, env);
if (configValue !== null) {
return configValue;
}
const envKey = key.toUpperCase()
.replace('WELCOME_MSG', 'WELCOME_MESSAGE')
.replace('VERIF_Q', 'VERIFICATION_QUESTION')
.replace('VERIF_A', 'VERIFICATION_ANSWER')
.replace(/_FORWARDING/g, '_FORWARDING');
const envValue = env[envKey];
if (envValue !== undefined && envValue !== null) {
return envValue;
}
return defaultValue;
}
function isPrimaryAdmin(userId, env) {
if (!env.ADMIN_IDS) return false;
const adminIds = env.ADMIN_IDS.split(',').map(id => id.trim());
return adminIds.includes(userId.toString());
}
async function getAuthorizedAdmins(env) {
const jsonString = await getConfig('authorized_admins', env, '[]');
try {
const adminList = JSON.parse(jsonString);
return Array.isArray(adminList) ?
adminList.map(id => id.toString().trim()).filter(id => id !== "") : [];
} catch (e) {
console.error("Failed to parse authorized_admins from D1:", e);
return [];
}
}
async function isAdminUser(userId, env) {
if (isPrimaryAdmin(userId, env)) {
return true;
}
const authorizedAdmins = await getAuthorizedAdmins(env);
return authorizedAdmins.includes(userId.toString());
}
async function getAutoReplyRules(env) {
const jsonString = await getConfig('keyword_responses', env, '[]');
try {
const rules = JSON.parse(jsonString);
return Array.isArray(rules) ? rules : [];
} catch (e) {
console.error("Failed to parse keyword_responses from D1:", e);
return [];
}
}
async function getBlockKeywords(env) {
const jsonString = await getConfig('block_keywords', env, '[]');
try {
const keywords = JSON.parse(jsonString);
return Array.isArray(keywords) ? keywords : [];
} catch (e) {
console.error("Failed to parse block_keywords from D1:", e);
return [];
}
}
async function telegramApi(token, methodName, params = {}) {
const url = `https://api.telegram.org/bot${token}/${methodName}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
let data;
try {
data = await response.json();
} catch (e) {
throw new Error(`Telegram API ${methodName} returned non-JSON response`);
}
if (!data.ok) {
throw new Error(`${methodName} failed: ${data.description || JSON.stringify(data)}`);
}
return data.result;
}
export default {
async fetch(request, env, ctx) {
try {
await dbMigrate(env);
} catch (e) {
return new Response(`D1 Database Initialization Error: ${e.message}`, { status: 500 });
}
if (request.method === "POST") {
try {
const update = await request.json();
ctx.waitUntil(handleUpdate(update, env));
} catch (e) {
console.error("处理更新时出错:", e);
}
}
return new Response("OK");
},
};
async function handleUpdate(update, env) {
if (update.message) {
if (update.message.chat.type === "private") {
await handlePrivateMessage(update.message, env);
}
else if (update.message.chat.id.toString() === env.ADMIN_GROUP_ID) {
await handleAdminReply(update.message, env);
}
} else if (update.edited_message) {
if (update.edited_message.chat.type === "private") {
await handleRelayEditedMessage(update.edited_message, env);
}
else if (update.edited_message.chat.id.toString() === env.ADMIN_GROUP_ID) {
await handleAdminEditedReply(update.edited_message, env);
}
} else if (update.callback_query) {
await handleCallbackQuery(update.callback_query, env);
}
}
async function handlePrivateMessage(message, env) {
const chatId = message.chat.id.toString();
const text = message.text || "";
const userId = chatId;
const isPrimary = isPrimaryAdmin(userId, env);
const isAdmin = await isAdminUser(userId, env);
if (text === "/start" || text === "/help") {
if (isPrimary) {
await handleAdminConfigStart(chatId, env);
} else {
await handleStart(chatId, env);
}
return;
}
const user = await dbUserGetOrCreate(userId, env);
const isBlocked = user.is_blocked;
if (isBlocked) {
return;
}
if (isPrimary) {
const adminStateJson = await dbAdminStateGet(userId, env);
if (adminStateJson) {
await handleAdminConfigInput(userId, text, adminStateJson, env);
return;
}
if (user.user_state !== "verified") {
user.user_state = "verified";
await dbUserUpdate(userId, { user_state: "verified" }, env);
}
}
if (isAdmin && user.user_state !== "verified") {
user.user_state = "verified";
await dbUserUpdate(userId, { user_state: "verified" }, env);
}
const userState = user.user_state;
if (userState === "pending_verification" ||
(userState === "new" && text && !text.startsWith('/'))) {
await handleVerification(chatId, text, env);
} else if (userState === "verified") {
const blockKeywords = await getBlockKeywords(env);
const blockThreshold = parseInt(await getConfig('block_threshold', env, "5"), 10) || 5;
if (blockKeywords.length > 0 && text) {
let currentCount = user.block_count;
for (const keyword of blockKeywords) {
try {
const regex = new RegExp(keyword, 'gi');
if (regex.test(text)) {
currentCount += 1;
await dbUserUpdate(userId, { block_count: currentCount }, env);
const blockNotification = `⚠️ 您的消息触发了屏蔽关键词过滤器 (${currentCount}/${blockThreshold}次),此消息已被丢弃,不会转发给对方。`;
if (currentCount >= blockThreshold) {
await dbUserUpdate(userId, { is_blocked: true }, env);
const autoBlockMessage = `❌ 您已多次触发屏蔽关键词,根据设置,您已被自动屏蔽。机器人将不再接收您的任何消息。`;
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: chatId, text: blockNotification });
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: chatId, text: autoBlockMessage });
return;
}
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: blockNotification,
});
return;
}
} catch (e) {
console.error("Invalid keyword block regex:", keyword, e);
}
}
}
const filters = {
media: (await getConfig('enable_image_forwarding', env, 'true')).toLowerCase() === 'true',
link: (await getConfig('enable_link_forwarding', env, 'true')).toLowerCase() === 'true',
text: (await getConfig('enable_text_forwarding', env, 'true')).toLowerCase() === 'true',
audio_voice: (await getConfig('enable_audio_forwarding', env, 'true')).toLowerCase() === 'true',
sticker_gif: (await getConfig('enable_sticker_forwarding', env, 'true')).toLowerCase() === 'true',
user_forward: (await getConfig('enable_user_forwarding', env, 'true')).toLowerCase() === 'true',
group_forward: (await getConfig('enable_group_forwarding', env, 'true')).toLowerCase() === 'true',
channel_forward: (await getConfig('enable_channel_forwarding', env, 'true')).toLowerCase() === 'true',
};
let isForwardable = true;
let filterReason = '';
const hasLinks = (msg) => {
const entities = msg.entities || msg.caption_entities || [];
return entities.some(entity => entity.type === 'url' || entity.type === 'text_link');
};
if (message.forward_from) {
if (!filters.user_forward) {
isForwardable = false;
filterReason = '用户转发消息';
}
} else if (message.forward_from_chat) {
const type = message.forward_from_chat.type;
if (type === 'channel') {
if (!filters.channel_forward) {
isForwardable = false;
filterReason = '频道转发消息';
}
} else if (type === 'group' || type === 'supergroup') {
if (!filters.group_forward) {
isForwardable = false;
filterReason = '群组转发消息';
}
}
}
else if (message.audio || message.voice) {
if (!filters.audio_voice) {
isForwardable = false;
filterReason = '音频或语音消息';
}
}
else if (message.sticker || message.animation) {
if (!filters.sticker_gif) {
isForwardable = false;
filterReason = '贴纸或GIF';
}
}
else if (message.photo || message.video || message.document) {
if (!filters.media) {
isForwardable = false;
filterReason = '媒体内容(图片/视频/文件)';
}
}
if (isForwardable && hasLinks(message)) {
if (!filters.link) {
isForwardable = false;
filterReason = filterReason ? `${filterReason} (并包含链接)` : '包含链接的内容';
}
}
const isPureText = message.text &&
!message.photo && !message.video && !message.document &&
!message.sticker && !message.audio && !message.voice &&
!message.forward_from_chat && !message.forward_from && !message.animation;
if (isForwardable && isPureText) {
if (!filters.text) {
isForwardable = false;
filterReason = '纯文本内容';
}
}
if (!isForwardable) {
const filterNotification = `此消息已被过滤:${filterReason}。根据设置,此类内容不会转发给对方。`;
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: filterNotification,
});
return;
}
const autoResponseRules = await getAutoReplyRules(env);
if (autoResponseRules.length > 0 && text) {
for (const rule of autoResponseRules) {
try {
const regex = new RegExp(rule.keywords, 'gi');
if (regex.test(text)) {
const autoReplyPrefix = "此消息为自动回复\n\n";
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: autoReplyPrefix + rule.response,
});
return;
}
} catch (e) {
console.error("Invalid auto-reply regex:", rule.keywords, e);
}
}
}
await handleRelayToTopic(message, user, env);
} else {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: "请使用 /start 命令开始。",
});
}
}
async function handleStart(chatId, env) {
const verifyMode = await getConfig('verification_mode', env, 'button');
const defaultWelcome = "为了防止垃圾广告骚扰,首次使用需要完成身份验证。";
const welcomeMessage = await getConfig('welcome_msg', env, defaultWelcome);
const user = await dbUserGetOrCreate(chatId, env);
const userInfo = getUserInfo({
id: chatId,
first_name: user.user_info?.name || '用户',
username: user.user_info?.username
});
if (verifyMode === 'button') {
const text = `
🔐 身份验证
欢迎 ${userInfo.username !== '无' ? userInfo.username : userInfo.name}!
${escapeHtml(welcomeMessage)}
👇 请点击下方按钮完成验证:
`.trim();
const keyboard = {
inline_keyboard: [[
{ text: "✅ 点击这里验证身份", callback_data: "action:verify_user" }
]]
};
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: text,
parse_mode: "HTML",
reply_markup: keyboard
});
} else {
const code = generateRandomCode(4);
await dbUserUpdate(chatId, {
user_state: "pending_verification",
verification_code: code
}, env);
const text = `
🔐 身份验证
欢迎 ${userInfo.username !== '无' ? userInfo.username : userInfo.name}!
${escapeHtml(welcomeMessage)}
🤖 请在对话框中发送以下验证码:
${code}
`.trim();
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: text,
parse_mode: "HTML"
});
}
await dbUserUpdate(chatId, { user_state: "pending_verification" }, env);
}
async function handleVerification(chatId, answer, env) {
const user = await dbUserGetOrCreate(chatId, env);
const verifyMode = await getConfig('verification_mode', env, 'button');
if (verifyMode === 'button') {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: "👇 请点击上方的按钮进行验证,无需发送文本。",
});
return;
}
const expectedCode = user.verification_code;
if (!expectedCode) {
await handleStart(chatId, env);
return;
}
if (answer.trim().toUpperCase() === expectedCode.toUpperCase()) {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: "🎉 验证通过!可以开始聊天咯!",
});
await dbUserUpdate(chatId, { user_state: "verified", verification_code: null }, env);
} else {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: "❌ 验证码错误,请检查后重新发送,或输入 /start 获取新验证码。",
});
}
}
async function handleAdminEditedReply(editedMessage, env) {
if (!editedMessage.is_topic_message || !editedMessage.message_thread_id) return;
const adminGroupIdStr = env.ADMIN_GROUP_ID.toString();
if (editedMessage.chat.id.toString() !== adminGroupIdStr) return;
if (editedMessage.from && editedMessage.from.is_bot) return;
const senderId = editedMessage.from.id.toString();
const isAuthorizedAdmin = await isAdminUser(senderId, env);
if (!isAuthorizedAdmin) {
return;
}
const topicId = editedMessage.message_thread_id.toString();
const userId = await dbTopicUserGet(topicId, env);
if (!userId) return;
const messageId = editedMessage.message_id.toString();
const storedMessage = await dbMessageDataGet(userId, messageId, env);
if (!storedMessage) return;
const newText = editedMessage.text || editedMessage.caption || "[媒体内容]";
const originalTime = formatTimestamp(storedMessage.date);
const editTime = formatTimestamp(editedMessage.edit_date || editedMessage.date);
const notificationText = `
⚠️ 管理员编辑了回复
---
原发送/上次编辑时间: ${originalTime}
本次编辑时间: ${editTime}
原消息内容:
${escapeHtml(storedMessage.text)}
新消息内容:
${escapeHtml(newText)}
`.trim();
try {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: userId,
text: notificationText,
parse_mode: "HTML",
});
await dbMessageDataPut(userId, messageId, { text: newText, date: editedMessage.edit_date || editedMessage.date }, env);
} catch (e) {
console.error("handleAdminEditedReply: Failed to send edited message to user:", e?.message || e);
}
}
async function handleAdminConfigStart(chatId, env, messageId = 0) {
const isPrimary = isPrimaryAdmin(chatId, env);
if (!isPrimary) {
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: chatId, text: "您是授权协管员,已绕过验证。此菜单仅供主管理员使用。", });
return;
}
const menuText = `
⚙️ 机器人主配置菜单
请选择要管理的配置类别:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "📝 基础配置 (验证模式)", callback_data: "config:menu:base" }],
[{ text: "🤖 自动回复管理", callback_data: "config:menu:autoreply" }],
[{ text: "🚫 关键词屏蔽管理", callback_data: "config:menu:keyword" }],
[{ text: "🔗 按类型过滤管理", callback_data: "config:menu:filter" }],
[{ text: "🧑💻 协管员授权设置", callback_data: "config:menu:authorized" }],
[{ text: "💾 备份群组设置", callback_data: "config:menu:backup" }],
[{ text: "🔄 刷新主菜单", callback_data: "config:menu" }],
]
};
await dbAdminStateDelete(chatId, env);
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params).catch(e => {
if (apiMethod === "editMessageText") {
delete params.message_id;
telegramApi(env.BOT_TOKEN, "sendMessage", params).catch(e2 => console.error("Fallback sendMessage also failed:", e2.message));
} else {
console.error("Error sending main menu:", e.message);
}
});
}
async function handleAdminBaseConfigMenu(chatId, messageId, env) {
const welcomeMsg = await getConfig('welcome_msg', env, "为了防止...");
const currentMode = await getConfig('verification_mode', env, 'button');
const modeText = currentMode === 'button' ? "🖱️ 点击按钮验证" : "🔠 4位验证码验证";
const menuText = `
⚙️ 基础配置 (验证设置)
当前验证模式: ${modeText}
当前欢迎/提示语:
${escapeHtml(welcomeMsg).substring(0, 50)}...
请选择要修改的配置项:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "🔄 切换验证模式", callback_data: "config:toggle_mode:verification" }],
[{ text: "📝 编辑欢迎/提示语", callback_data: "config:edit:welcome_msg" }],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminAuthorizedConfigMenu(chatId, messageId, env) {
const primaryAdmins = env.ADMIN_IDS ? env.ADMIN_IDS.split(',').map(id => id.trim()).filter(id => id !== "") : [];
const authorizedAdmins = await getAuthorizedAdmins(env);
const allAdmins = [...new Set([...primaryAdmins, ...authorizedAdmins])];
const authorizedCount = authorizedAdmins.length;
const menuText = `
🧑💻 协管员授权设置
主管理员 (来自 ENV): ${primaryAdmins.join(', ')}
已授权协管员 (来自 D1): ${authorizedAdmins.join(', ') || '无'}
总管理员/协管员数量: ${allAdmins.length} 人
注意:
1. 协管员 ID 或用户名必须与群组话题中的回复者一致。
2. 协管员的私聊会自动绕过验证。
3. 输入格式:ID 或用户名,多个用逗号分隔。
请选择要修改的配置项:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "✏️ 设置/修改协管员列表", callback_data: "config:edit:authorized_admins" }],
[{ text: `🗑️ 清空协管员列表 (${authorizedCount}人)`, callback_data: "config:edit:authorized_admins_clear" }],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminAutoReplyMenu(chatId, messageId, env) {
const rules = await getAutoReplyRules(env);
const ruleCount = rules.length;
const menuText = `
🤖 自动回复管理
当前规则总数:${ruleCount} 条。
请选择操作:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "➕ 新增自动回复规则", callback_data: "config:add:keyword_responses" }],
[{ text: `🗑️ 管理/删除现有规则 (${ruleCount}条)`, callback_data: "config:list:keyword_responses" }],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminKeywordBlockMenu(chatId, messageId, env) {
const blockKeywords = await getBlockKeywords(env);
const keywordCount = blockKeywords.length;
const blockThreshold = await getConfig('block_threshold', env, "5");
const menuText = `
🚫 关键词屏蔽管理
当前屏蔽关键词总数:${keywordCount} 个。
屏蔽次数阈值:${escapeHtml(blockThreshold)} 次。
请选择操作:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "➕ 新增屏蔽关键词", callback_data: "config:add:block_keywords" }],
[{ text: `🗑️ 管理/删除现有关键词 (${keywordCount}个)`, callback_data: "config:list:block_keywords" }],
[{ text: `✏️ 修改屏蔽次数阈值 (${blockThreshold}次)`, callback_data: "config:edit:block_threshold" }],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminBackupConfigMenu(chatId, messageId, env) {
const backupGroupId = await getConfig('backup_group_id', env, "");
const statusText = backupGroupId ? `✅ 已设置: ${escapeHtml(backupGroupId)}` : "❌ 未设置";
const menuText = `
💾 消息备份群组设置
当前群组 ID: ${statusText}
注意:
1. 群组必须是超级群组,且 Bot 必须是管理员。
2. 设置后,所有用户消息的副本都会转发到此群组。
请选择操作:
`.trim();
const menuKeyboard = {
inline_keyboard: [
[{ text: "✏️ 设置/修改备份群组 ID", callback_data: "config:edit:backup_group_id" }],
[{ text: "🗑️ 清除备份群组 ID", callback_data: "config:edit:backup_group_id_clear" }],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminRuleList(chatId, messageId, env, key) {
let rules = [];
let menuText = "";
let backCallback = "";
if (key === 'keyword_responses') {
rules = await getAutoReplyRules(env);
menuText = `
🤖 自动回复规则列表 (${rules.length}条)
请点击右侧按钮删除对应规则。
因为数据库限制,点击删除后界面不会刷新实际已经执行
请点击返回上一级菜单后重新进入就可以看到了
规则格式:关键词表达式 ➡️ 回复内容
---
`.trim();
backCallback = "config:menu:autoreply";
} else if (key === 'block_keywords') {
rules = await getBlockKeywords(env);
menuText = `
🚫 屏蔽关键词列表 (${rules.length}个)
请点击右侧按钮删除对应关键词。
因为数据库限制,点击删除后界面不会刷新实际已经执行
请点击返回上一级菜单后重新进入就可以看到了
关键词格式:关键词表达式
---
`.trim();
backCallback = "config:menu:keyword";
} else {
return;
}
const ruleButtons = [];
if (rules.length === 0) {
menuText += "\n\n(列表为空)";
} else {
rules.forEach((rule, index) => {
let label = "";
let deleteId = "";
if (key === 'keyword_responses') {
const keywordsSnippet = rule.keywords.substring(0, 15);
const responseSnippet = rule.response.substring(0, 20);
label = `${index + 1}. ${escapeHtml(keywordsSnippet)}... ➡️ ${escapeHtml(responseSnippet)}...`;
deleteId = rule.id;
} else if (key === 'block_keywords') {
const keywordSnippet = rule.substring(0, 25);
label = `${index + 1}. ${escapeHtml(keywordSnippet)}...`;
deleteId = rule;
}
menuText += `\n${label}`;
ruleButtons.push([
{
text: `🗑️ 删除 ${index + 1}`,
callback_data: `config:delete:${key}:${deleteId}`
}
]);
});
}
const finalKeyboard = {
inline_keyboard: [
...ruleButtons,
[{ text: "⬅️ 返回", callback_data: backCallback }]
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: finalKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminRuleDelete(chatId, messageId, env, key, deleteValue) {
let rules = [];
let typeName = "";
if (key === 'keyword_responses') {
rules = await getAutoReplyRules(env);
typeName = "自动回复规则";
const newRules = rules.filter(rule => rule.id.toString() !== deleteValue.toString());
await dbConfigPut(key, JSON.stringify(newRules), env);
} else if (key === 'block_keywords') {
rules = await getBlockKeywords(env);
typeName = "屏蔽关键词";
const newRules = rules.filter(keyword => keyword !== deleteValue);
await dbConfigPut(key, JSON.stringify(newRules), env);
} else {
return;
}
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", {
callback_query_id: chatId,
text: `✅ ${typeName}已删除并更新。`,
show_alert: false
});
await handleAdminRuleList(chatId, messageId, env, key);
}
async function handleAdminTypeBlockMenu(chatId, messageId, env) {
const mediaStatus = (await getConfig('enable_image_forwarding', env, 'true')).toLowerCase() === 'true';
const linkStatus = (await getConfig('enable_link_forwarding', env, 'true')).toLowerCase() === 'true';
const textStatus = (await getConfig('enable_text_forwarding', env, 'true')).toLowerCase() === 'true';
const audioVoiceStatus = (await getConfig('enable_audio_forwarding', env, 'true')).toLowerCase() === 'true';
const stickerGifStatus = (await getConfig('enable_sticker_forwarding', env, 'true')).toLowerCase() === 'true';
const userForwardStatus = (await getConfig('enable_user_forwarding', env, 'true')).toLowerCase() === 'true';
const groupForwardStatus = (await getConfig('enable_group_forwarding', env, 'true')).toLowerCase() === 'true';
const channelForwardStatus = (await getConfig('enable_channel_forwarding', env, 'true')).toLowerCase() === 'true';
const s = (status) => status ? "✅ 允许" : "❌ 屏蔽";
const cb = (key, status) => `config:toggle:${key}:${status ? 'false' : 'true'}`;
const btnText = (status) => status ? "✅ 允许" : "❌ 屏蔽";
const menuText = `
🔗 按类型过滤管理
点击下方按钮切换状态。
--- 转发来源控制 ---
1. ${s(userForwardStatus)} | 转发消息 (用户)
2. ${s(groupForwardStatus)} | 转发消息 (群组)
3. ${s(channelForwardStatus)} | 转发消息 (频道)
--- 媒体类型控制 ---
4. ${s(audioVoiceStatus)} | 音频/语音消息
5. ${s(stickerGifStatus)} | 贴纸/GIF (动画)
6. ${s(mediaStatus)} | 图片/视频/文件
--- 基础内容控制 ---
7. ${s(linkStatus)} | 链接消息
8. ${s(textStatus)} | 纯文本消息
`.trim();
const menuKeyboard = {
inline_keyboard: [
[
{ text: `1. ${btnText(userForwardStatus)}`, callback_data: cb('enable_user_forwarding', userForwardStatus) },
{ text: `2. ${btnText(groupForwardStatus)}`, callback_data: cb('enable_group_forwarding', groupForwardStatus) }
],
[
{ text: `3. ${btnText(channelForwardStatus)}`, callback_data: cb('enable_channel_forwarding', channelForwardStatus) },
{ text: `4. ${btnText(audioVoiceStatus)}`, callback_data: cb('enable_audio_forwarding', audioVoiceStatus) }
],
[
{ text: `5. ${btnText(stickerGifStatus)}`, callback_data: cb('enable_sticker_forwarding', stickerGifStatus) },
{ text: `6. ${btnText(mediaStatus)}`, callback_data: cb('enable_image_forwarding', mediaStatus) }
],
[
{ text: `7. ${btnText(linkStatus)}`, callback_data: cb('enable_link_forwarding', linkStatus) },
{ text: `8. ${btnText(textStatus)}`, callback_data: cb('enable_text_forwarding', textStatus) }
],
[{ text: "⬅️ 返回主菜单", callback_data: "config:menu" }],
]
};
const apiMethod = (messageId && messageId !== 0) ? "editMessageText" : "sendMessage";
const params = {
chat_id: chatId,
text: menuText,
parse_mode: "HTML",
reply_markup: menuKeyboard,
};
if (apiMethod === "editMessageText") {
params.message_id = messageId;
}
await telegramApi(env.BOT_TOKEN, apiMethod, params);
}
async function handleAdminConfigInput(userId, text, adminStateJson, env) {
let adminState;
try {
adminState = JSON.parse(adminStateJson);
} catch (e) {
await dbAdminStateDelete(userId, env);
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "⚠️ 状态错误,已重置。请重新使用 /start 访问菜单。", });
return;
}
if (adminState.action === 'awaiting_input') {
let successMsg = "";
let finalValue = text;
if (text.toLowerCase() === '/cancel') {
await dbAdminStateDelete(userId, env);
let cancelBack = "config:menu";
if (adminState.key === 'block_keywords_add') {
cancelBack = "config:menu:keyword";
}
else if (adminState.key === 'keyword_responses_add') {
cancelBack = "config:menu:autoreply";
}
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "❌ 已取消输入。", });
if (cancelBack === 'config:menu:keyword') {
await handleAdminKeywordBlockMenu(userId, 0, env);
}
else if (cancelBack === 'config:menu:autoreply') {
await handleAdminAutoReplyMenu(userId, 0, env);
}
else {
await handleAdminConfigStart(userId, env);
}
return;
}
if (adminState.key === 'verif_a' || adminState.key === 'block_threshold') {
finalValue = text.trim();
} else if (adminState.key === 'backup_group_id') {
finalValue = text.trim();
} else if (adminState.key === 'authorized_admins') {
const adminList = text.split(',').map(id => id.trim()).filter(id => id !== "");
finalValue = JSON.stringify(adminList);
}
if (adminState.key === 'block_keywords_add') {
const blockKeywords = await getBlockKeywords(env);
const newKeyword = finalValue.trim();
if (newKeyword && !blockKeywords.includes(newKeyword)) {
blockKeywords.push(newKeyword);
await dbConfigPut('block_keywords', JSON.stringify(blockKeywords), env);
successMsg = `✅ 屏蔽关键词 ${escapeHtml(newKeyword)} 已添加。`;
} else {
successMsg = `⚠️ 屏蔽关键词未添加,内容为空或已存在。`;
}
await dbAdminStateDelete(userId, env);
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: successMsg, parse_mode: "HTML" });
await handleAdminKeywordBlockMenu(userId, 0, env);
return;
} else if (adminState.key === 'keyword_responses_add') {
const rules = await getAutoReplyRules(env);
const separatorIndex = finalValue.indexOf('===');
if (separatorIndex > -1) {
const keywords = finalValue.substring(0, separatorIndex).trim();
const response = finalValue.substring(separatorIndex + 3).trim();
if (keywords && response) {
const newRule = {
keywords: keywords,
response: response,
id: Date.now(),
};
rules.push(newRule);
await dbConfigPut('keyword_responses', JSON.stringify(rules), env);
successMsg = `✅ 自动回复规则已添加。关键词: ${escapeHtml(newRule.keywords)}`;
} else {
successMsg = `⚠️ 自动回复规则未添加,内容不能为空。`;
}
} else {
successMsg = `⚠️ 自动回复规则未添加。请确保格式正确:关键词表达式===回复内容`;
}
await dbAdminStateDelete(userId, env);
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: successMsg, parse_mode: "HTML" });
await handleAdminAutoReplyMenu(userId, 0, env);
return;
}
if (finalValue.length === 0 && adminState.key !== 'backup_group_id') {
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "⚠️ 输入内容不能为空,请重新发送。", });
return;
}
await dbConfigPut(adminState.key, finalValue, env);
await dbAdminStateDelete(userId, env);
successMsg = `✅ 配置项 ${adminState.key} 已更新。新值:${escapeHtml(finalValue).substring(0, 50)}...`;
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: successMsg, parse_mode: "HTML" });
let nextMenuAction = '';
if (adminState.key === 'welcome_msg' || adminState.key === 'verif_q' || adminState.key === 'verif_a') {
nextMenuAction = 'config:menu:base';
} else if (adminState.key === 'block_threshold') {
nextMenuAction = 'config:menu:keyword';
} else if (adminState.key === 'backup_group_id') {
nextMenuAction = 'config:menu:backup';
} else if (adminState.key === 'authorized_admins') {
nextMenuAction = 'config:menu:authorized';
}
if (nextMenuAction === 'config:menu:base') {
await handleAdminBaseConfigMenu(userId, 0, env);
} else if (nextMenuAction === 'config:menu:autoreply') {
await handleAdminAutoReplyMenu(userId, 0, env);
} else if (nextMenuAction === 'config:menu:keyword') {
await handleAdminKeywordBlockMenu(userId, 0, env);
} else if (nextMenuAction === 'config:menu:backup') {
await handleAdminBackupConfigMenu(userId, 0, env);
} else if (nextMenuAction === 'config:menu:authorized') {
await handleAdminAuthorizedConfigMenu(userId, 0, env);
} else {
await handleAdminConfigStart(userId, env);
}
} else {
await dbAdminStateDelete(userId, env);
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "⚠️ 状态错误,已重置。请重新使用 /start 访问菜单。", });
}
}
async function handleRelayToTopic(message, user, env) {
const { from: userDetails, date } = message;
const { userId, topicName, infoCard } = getUserInfo(userDetails, date);
let topicId = user.topic_id;
const isBlocked = user.is_blocked;
const isMuted = user.is_muted || false;
const createTopicForUser = async () => {
try {
const newTopic = await telegramApi(env.BOT_TOKEN, "createForumTopic", {
chat_id: env.ADMIN_GROUP_ID,
name: topicName,
});
const newTopicId = newTopic.message_thread_id.toString();
const { name, username } = getUserInfo(userDetails, date);
const newInfo = { name, username, first_message_date: date };
const cardMarkup = getInfoCardButtons(userId, isBlocked, isMuted);
const sentMsg = await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: env.ADMIN_GROUP_ID,
message_thread_id: newTopicId,
text: infoCard,
parse_mode: "HTML",
reply_markup: cardMarkup,
});
await dbUserUpdate(userId, {
topic_id: newTopicId,
user_info_json: JSON.stringify(newInfo),
block_count: 0,
info_card_message_id: sentMsg.message_id.toString()
}, env);
try {
let logTopicId = await ensureLogTopicExists(env);
if (logTopicId) {
const cleanGroupId = env.ADMIN_GROUP_ID.toString().replace(/^-100/, '');
const jumpUrl = `https://t.me/c/${cleanGroupId}/${newTopicId}`;
const logMarkup = JSON.parse(JSON.stringify(cardMarkup));
logMarkup.inline_keyboard.push([{ text: "💬 跳转到会话窗口", url: jumpUrl }]);
const logText = `#新用户连接\n话题ID: ${newTopicId}\n\n${infoCard}`;
const sendParams = { chat_id: env.ADMIN_GROUP_ID, message_thread_id: logTopicId, text: logText, parse_mode: "HTML", reply_markup: logMarkup };
try {
const logMsg = await telegramApi(env.BOT_TOKEN, "sendMessage", sendParams);
await dbUserUpdate(userId, { profile_log_message_id: logMsg.message_id.toString() }, env);
} catch (sendErr) {
const errStr = sendErr.message || sendErr.toString();
if (errStr.includes("thread not found") || errStr.includes("TOPIC_DELETED")) {
await env.TG_BOT_DB.prepare("DELETE FROM config WHERE key = ?").bind('user_profile_log_topic_id').run();
logTopicId = await ensureLogTopicExists(env);
if (logTopicId) {
sendParams.message_thread_id = logTopicId;
const retryMsg = await telegramApi(env.BOT_TOKEN, "sendMessage", sendParams);
await dbUserUpdate(userId, { profile_log_message_id: retryMsg.message_id.toString() }, env);
}
}
}
}
} catch (logErr) {
console.error("发送资料卡到汇总话题失败:", logErr);
}
return newTopicId;
} catch (e) {
console.error("创建话题失败:", e?.message || e);
throw e;
}
};
const tryCopyToTopic = async (targetTopicId) => {
const copyResult = await telegramApi(env.BOT_TOKEN, "copyMessage", {
chat_id: env.ADMIN_GROUP_ID,
message_thread_id: targetTopicId,
from_chat_id: userId,
message_id: message.message_id,
disable_notification: isBlocked || isMuted,
});
return copyResult.message_id.toString();
};
if (!topicId) {
try {
topicId = await createTopicForUser();
} catch (e) {
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "抱歉,无法创建客服话题(请稍后再试)。", });
return;
}
}
try {
const adminMessageId = await tryCopyToTopic(topicId);
if (message.text || message.caption) {
const messageData = {
text: message.text || message.caption || '', date: message.date
};
await dbMessageDataPut(userId, message.message_id.toString(), messageData, env);
}
} catch (e) {
try {
await dbUserUpdate(userId, { topic_id: null }, env);
const newTopicId = await createTopicForUser();
try {
await tryCopyToTopic(newTopicId);
if (message.text || message.caption) {
const messageData = {
text: message.text || message.caption || '', date: message.date
};
await dbMessageDataPut(userId, message.message_id.toString(), messageData, env);
}
} catch (e2) {
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "抱歉,消息转发失败(请稍后再试或联系管理员)。", });
return;
}
} catch (createErr) {
await telegramApi(env.BOT_TOKEN, "sendMessage", { chat_id: userId, text: "抱歉,无法创建新的客服话题(请稍后再试)。", });
return;
}
}
const backupGroupId = await getConfig('backup_group_id', env, "");
if (backupGroupId) {
const userInfo = getUserInfo(message.from, user.date);
const fromUserHeader = `
--- 备份消息 ---
👤 来自用户: ${userInfo.name || '无昵称'} • ID: ${userInfo.userId} • 用户名: ${userInfo.username}
------------------
`.trim() + '\n\n';
const backupParams = { chat_id: backupGroupId, disable_notification: true, parse_mode: "HTML", };
try {
if (message.text) {
const combinedText = fromUserHeader + message.text;
await telegramApi(env.BOT_TOKEN, "sendMessage", { ...backupParams, text: combinedText, });
} else if (message.caption || message.photo || message.video || message.document || message.audio || message.voice || message.sticker || message.animation) {
await telegramApi(env.BOT_TOKEN, "sendMessage", { ...backupParams, text: fromUserHeader.trim(), parse_mode: "HTML", });
await telegramApi(env.BOT_TOKEN, "copyMessage", { chat_id: backupGroupId, from_chat_id: userId, message_id: message.message_id, });
}
} catch (e) {
console.error("消息备份转发失败:", e?.message || e);
}
}
}
async function handleRelayEditedMessage(editedMessage, env) {
const { from: user } = editedMessage;
const userId = user.id.toString();
const userData = await dbUserGetOrCreate(userId, env);
const topicId = userData.topic_id;
if (!topicId) {
return;
}
const storedData = await dbMessageDataGet(userId, editedMessage.message_id.toString(), env);
let originalText = "[原始内容无法获取/非文本内容]";
let originalDate = "[发送时间无法获取]";
if (storedData) {
originalText = storedData.text || originalText;
originalDate = formatTimestamp(storedData.date);
const updatedData = {
text: editedMessage.text || editedMessage.caption || '',
date: editedMessage.date
};
await dbMessageDataPut(userId, editedMessage.message_id.toString(), updatedData, env);
}
const newContent = editedMessage.text || editedMessage.caption || "[非文本/媒体说明内容]";
const notificationText = `
⚠️ 用户消息已修改
原消息发送时间: ${originalDate}
原始信息: ${originalText}
修改后的新内容:
${escapeHtml(newContent)}
`.trim();
try {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: env.ADMIN_GROUP_ID,
text: notificationText,
message_thread_id: topicId,
parse_mode: "HTML",
});
} catch (e) {
console.error("handleRelayEditedMessage failed:", e?.message || e);
}
}
async function handleAdminReply(message, env) {
if (!message.is_topic_message || !message.message_thread_id) return;
const adminGroupIdStr = env.ADMIN_GROUP_ID.toString();
if (message.chat.id.toString() !== adminGroupIdStr) return;
if (message.from && message.from.is_bot) return;
const senderId = message.from.id.toString();
const isAuthorizedAdmin = await isAdminUser(senderId, env);
if (!isAuthorizedAdmin) {
return;
}
const topicId = message.message_thread_id.toString();
const userId = await dbTopicUserGet(topicId, env);
if (!userId) {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: adminGroupIdStr,
message_thread_id: topicId,
text: "❌ 找不到该话题对应的用户 ID,无法转发消息。",
});
return;
}
try {
if (message.text) {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: userId,
text: message.text,
});
} else if (message.photo) {
await telegramApi(env.BOT_TOKEN, "sendPhoto", {
chat_id: userId,
photo: message.photo[message.photo.length - 1].file_id,
caption: message.caption || "",
});
} else if (message.video) {
await telegramApi(env.BOT_TOKEN, "sendVideo", {
chat_id: userId,
video: message.video.file_id,
caption: message.caption || "",
});
} else if (message.audio) {
await telegramApi(env.BOT_TOKEN, "sendAudio", {
chat_id: userId,
audio: message.audio.file_id,
caption: message.caption || "",
});
} else if (message.voice) {
await telegramApi(env.BOT_TOKEN, "sendVoice", {
chat_id: userId,
voice: message.voice.file_id,
caption: message.caption || "",
});
} else if (message.sticker) {
await telegramApi(env.BOT_TOKEN, "sendSticker", {
chat_id: userId,
sticker: message.sticker.file_id,
});
} else if (message.animation) {
await telegramApi(env.BOT_TOKEN, "sendAnimation", {
chat_id: userId,
animation: message.animation.file_id,
caption: message.caption || "",
});
}
else if (message.video_note) {
await telegramApi(env.BOT_TOKEN, "sendVideoNote", {
chat_id: userId,
video_note: message.video_note.file_id,
});
}
else if (message.document) {
await telegramApi(env.BOT_TOKEN, "sendDocument", {
chat_id: userId,
document: message.document.file_id,
caption: message.caption || "",
});
}
else {
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: userId,
text: "管理员发送了机器人无法直接转发的内容(例如投票或某些特殊媒体)。",
});
}
} catch (e2) {
console.error("handleAdminReply fallback also failed:", e2?.message || e2);
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: adminGroupIdStr,
message_thread_id: topicId,
text: `❌ 转发消息给用户 ${userId} 失败: ${e2.message || e2}`,
});
}
try {
if (message.text || message.caption) {
const messageData = {
text: message.text || message.caption || '',
date: message.date
};
await dbMessageDataPut(userId, message.message_id.toString(), messageData, env);
}
} catch (e) {
console.error("Failed to store admin message data for edit tracking:", e?.message || e);
}
}
async function syncToBlockLog(userId, user, isBlocked, isMuted, env) {
const blockLogTopicId = await ensureBlockLogTopicExists(env);
if (!blockLogTopicId) return;
const userName = user.user_info?.name || userId;
const jumpUrl = `https://t.me/c/${env.ADMIN_GROUP_ID.toString().replace(/^-100/, '')}/${user.topic_id}`;
let statusText = "";
if (isBlocked) statusText += "🚫 用户被屏蔽";
else if (isMuted) statusText += "🔕 用户被静音";
else statusText += "✅ 用户正常 (无屏蔽/无静音)";
const logText = `${statusText}\n` +
`用户: ${escapeHtml(userName)}\n` +
`ID: ${userId}`;
const buttons = getInfoCardButtons(userId, isBlocked, isMuted);
const logMarkup = JSON.parse(JSON.stringify(buttons));
if (user.topic_id) {
logMarkup.inline_keyboard.push([{ text: "💬 跳转到会话窗口", url: jumpUrl }]);
}
const storedLogMsgId = user.block_log_message_id;
const sendNewLog = async (targetTopicId) => {
const sentMsg = await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: env.ADMIN_GROUP_ID,
message_thread_id: targetTopicId,
text: logText,
parse_mode: "HTML",
reply_markup: logMarkup
});
await dbUserUpdate(userId, { block_log_message_id: sentMsg.message_id.toString() }, env);
};
if (storedLogMsgId) {
try {
await telegramApi(env.BOT_TOKEN, "editMessageText", {
chat_id: env.ADMIN_GROUP_ID,
message_id: storedLogMsgId,
text: logText,
parse_mode: "HTML",
reply_markup: logMarkup
});
return;
} catch (e) {
if (e.description && e.description.includes("message is not modified")) {
return;
}
console.warn("编辑屏蔽日志失败 (可能是消息已删),转为发送新消息:", e.message);
await dbUserUpdate(userId, { block_log_message_id: null }, env);
}
}
try {
await sendNewLog(blockLogTopicId);
} catch (e) {
const errStr = e.message || e.toString();
if (errStr.includes("thread not found") || errStr.includes("TOPIC_DELETED")) {
console.warn("屏蔽名单话题失效,尝试重建...");
await env.TG_BOT_DB.prepare("DELETE FROM config WHERE key = ?").bind('user_block_log_topic_id').run();
const newLogId = await ensureBlockLogTopicExists(env);
if (newLogId) {
await sendNewLog(newLogId);
}
}
}
}
async function handleCallbackQuery(callbackQuery, env) {
const chatId = callbackQuery.from.id.toString();
const data = callbackQuery.data;
const message = callbackQuery.message;
const isAdmin = await isAdminUser(chatId, env);
if (data === 'action:verify_user') {
let user = await dbUserGetOrCreate(chatId, env);
if (user.user_state === 'verified') {
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "您已经通过验证啦!", show_alert: true });
return;
}
await dbUserUpdate(chatId, { user_state: "verified" }, env);
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "✅ 验证成功!", show_alert: false });
try {
await telegramApi(env.BOT_TOKEN, "editMessageText", {
chat_id: chatId,
message_id: message.message_id,
text: message.text + "\n\n✅ 已通过验证",
parse_mode: "HTML",
reply_markup: { inline_keyboard: [] }
});
} catch (e) { }
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: chatId,
text: "🎉 验证通过!您可以开始发送消息了。",
});
return;
}
if (!isAdmin) {
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "您无权操作此菜单。", show_alert: true });
return;
}
if (data.startsWith('config:')) {
const parts = data.split(':');
const actionType = parts[1];
const keyOrAction = parts[2];
const value = parts[3];
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "处理中...", show_alert: false });
if (actionType === 'menu') {
if (keyOrAction === 'base') {
await handleAdminBaseConfigMenu(chatId, message.message_id, env);
}
else if (keyOrAction === 'autoreply') {
await handleAdminAutoReplyMenu(chatId, message.message_id, env);
}
else if (keyOrAction === 'keyword') {
await handleAdminKeywordBlockMenu(chatId, message.message_id, env);
}
else if (keyOrAction === 'filter') {
await handleAdminTypeBlockMenu(chatId, message.message_id, env);
}
else if (keyOrAction === 'backup') {
await handleAdminBackupConfigMenu(chatId, message.message_id, env);
}
else if (keyOrAction === 'authorized') {
await handleAdminAuthorizedConfigMenu(chatId, message.message_id, env);
}
else {
await handleAdminConfigStart(chatId, env, message.message_id);
}
}
else if (actionType === 'toggle_mode' && keyOrAction === 'verification') {
const currentMode = await getConfig('verification_mode', env, 'button');
const newMode = currentMode === 'button' ? 'code' : 'button';
await dbConfigPut('verification_mode', newMode, env);
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", {
callback_query_id: callbackQuery.id,
text: `✅ 模式已切换为: ${newMode === 'button' ? '点击验证' : '验证码验证'}`,
show_alert: false
});
await handleAdminBaseConfigMenu(chatId, message.message_id, env);
return;
}
else if (actionType === 'toggle' && keyOrAction && value) {
await dbConfigPut(keyOrAction, value, env);
await handleAdminTypeBlockMenu(chatId, message.message_id, env);
} else if (actionType === 'edit' && keyOrAction) {
if (keyOrAction === 'backup_group_id_clear') {
await dbConfigPut('backup_group_id', '', env);
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "✅ 备份群组 ID 已清除。", show_alert: false });
await handleAdminBackupConfigMenu(chatId, message.message_id, env);
return;
}
if (keyOrAction === 'authorized_admins_clear') {
await dbConfigPut('authorized_admins', '[]', env);
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "✅ 协管员列表已清除。", show_alert: false });
await handleAdminAuthorizedConfigMenu(chatId, message.message_id, env);
return;
}
await dbAdminStatePut(chatId, JSON.stringify({ action: 'awaiting_input', key: keyOrAction }), env);
let prompt = `请发送**新的** ${keyOrAction} **值**:`;
let cancelBack = "config:menu";
if (keyOrAction === 'welcome_msg') {
prompt = "请发送**新的欢迎消息**:";
cancelBack = "config:menu:base";
}
else if (keyOrAction === 'verif_q') {
prompt = "请发送**新的验证问题**:";
cancelBack = "config:menu:base";
}
else if (keyOrAction === 'verif_a') {
prompt = "请发送你需要设置的答案...";
cancelBack = "config:menu:base";
}
else if (keyOrAction === 'block_threshold') {
prompt = "请发送**新的屏蔽次数阈值 (数字)**:";
cancelBack = "config:menu:keyword";
}
else if (keyOrAction === 'backup_group_id') {
prompt = "请发送**新的备份群组 ID**...";
cancelBack = "config:menu:backup";
}
else if (keyOrAction === 'authorized_admins') {
prompt = "请发送**新的协管员 ID 列表**...";
cancelBack = "config:menu:authorized";
}
const cancelBtn = { inline_keyboard: [[{ text: "❌ 取消编辑", callback_data: cancelBack }]] };
await telegramApi(env.BOT_TOKEN, "editMessageText", { chat_id: chatId, message_id: message.message_id, text: `${prompt}\n\n发送 \`/cancel\` 或点击下方按钮取消。`, parse_mode: "HTML", reply_markup: cancelBtn, });
} else if (actionType === 'add' && keyOrAction) {
const newKey = keyOrAction + '_add';
await dbAdminStatePut(chatId, JSON.stringify({ action: 'awaiting_input', key: newKey }), env);
let prompt = "";
let cancelBack = "";
if (keyOrAction === 'keyword_responses') {
prompt = "请发送**新的自动回复规则**..."; cancelBack = "config:menu:autoreply";
}
else if (keyOrAction === 'block_keywords') {
prompt = "请发送**新的屏蔽关键词表达式**...";
cancelBack = "config:menu:keyword";
}
const cancelBtn = { inline_keyboard: [[{ text: "❌ 取消添加", callback_data: cancelBack }]] };
await telegramApi(env.BOT_TOKEN, "editMessageText", { chat_id: chatId, message_id: message.message_id, text: `${prompt}\n\n发送 \`/cancel\` 或点击下方按钮取消。`, parse_mode: "HTML", reply_markup: cancelBtn, });
} else if (actionType === 'list' && keyOrAction) {
await handleAdminRuleList(chatId, message.message_id, env, keyOrAction);
} else if (actionType === 'delete' && keyOrAction && value) {
await handleAdminRuleDelete(chatId, message.message_id, env, keyOrAction, value);
}
return;
}
if (message.chat.id.toString() !== env.ADMIN_GROUP_ID) {
return;
}
const [action, targetUserId] = data.split(':');
const currentTopicId = message.message_thread_id ? message.message_thread_id.toString() : null;
let user = await dbUserGetOrCreate(targetUserId, env);
if (user.topic_id === currentTopicId && !user.info_card_message_id) {
await dbUserUpdate(targetUserId, { info_card_message_id: message.message_id.toString() }, env);
user.info_card_message_id = message.message_id.toString();
}
const blockLogTopicId = await dbConfigGet('user_block_log_topic_id', env);
if (blockLogTopicId === currentTopicId && !user.block_log_message_id) {
await dbUserUpdate(targetUserId, { block_log_message_id: message.message_id.toString() }, env);
user.block_log_message_id = message.message_id.toString();
}
const profileLogTopicId = await dbConfigGet('user_profile_log_topic_id', env);
if (profileLogTopicId === currentTopicId && !user.profile_log_message_id) {
await dbUserUpdate(targetUserId, { profile_log_message_id: message.message_id.toString() }, env);
user.profile_log_message_id = message.message_id.toString();
}
if (['block', 'unblock', 'mute', 'unmute'].includes(action)) {
const isBlockAction = action === 'block' || action === 'unblock';
const isMuteAction = action === 'mute' || action === 'unmute';
const newState = (action === 'block' || action === 'mute');
try {
const updateData = isBlockAction ? { is_blocked: newState } : { is_muted: newState };
await dbUserUpdate(targetUserId, updateData, env);
user = await dbUserGetOrCreate(targetUserId, env);
const userName = user.user_info?.name || targetUserId;
const newMarkup = getInfoCardButtons(targetUserId, user.is_blocked, user.is_muted);
const preserveJumpLink = (originalMarkup) => {
let updated = JSON.parse(JSON.stringify(newMarkup));
if (originalMarkup && originalMarkup.inline_keyboard) {
const lastRow = originalMarkup.inline_keyboard[originalMarkup.inline_keyboard.length - 1];
if (lastRow && lastRow[0] && lastRow[0].url && lastRow[0].url.includes('t.me/c/')) {
updated.inline_keyboard.push(lastRow);
}
}
return updated;
};
await telegramApi(env.BOT_TOKEN, "editMessageReplyMarkup", {
chat_id: message.chat.id,
message_id: message.message_id,
reply_markup: preserveJumpLink(message.reply_markup),
});
let toastText = "";
if (isBlockAction) toastText = newState ? "🚫 已屏蔽该用户" : "✅ 已解除屏蔽";
else if (isMuteAction) toastText = newState ? "🔕 已静音通知" : "🔔 已恢复通知";
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", {
callback_query_id: callbackQuery.id,
text: toastText,
show_alert: false
});
await syncToBlockLog(targetUserId, user, user.is_blocked, user.is_muted, env);
if (user.info_card_message_id && message.message_id.toString() !== user.info_card_message_id) {
try {
await telegramApi(env.BOT_TOKEN, "editMessageReplyMarkup", {
chat_id: env.ADMIN_GROUP_ID,
message_id: user.info_card_message_id,
reply_markup: newMarkup,
});
} catch (e) { console.warn("同步私聊资料卡失败:", e.message); }
}
if (user.profile_log_message_id && message.message_id.toString() !== user.profile_log_message_id) {
try {
const cleanGroupId = env.ADMIN_GROUP_ID.toString().replace(/^-100/, '');
const jumpUrl = `https://t.me/c/${cleanGroupId}/${user.topic_id}`;
const logMarkup = JSON.parse(JSON.stringify(newMarkup));
logMarkup.inline_keyboard.push([{ text: "💬 跳转到会话窗口", url: jumpUrl }]);
await telegramApi(env.BOT_TOKEN, "editMessageReplyMarkup", {
chat_id: env.ADMIN_GROUP_ID,
message_id: user.profile_log_message_id,
reply_markup: logMarkup,
});
} catch (e) { console.warn("同步资料卡汇总失败:", e.message); }
}
if (isBlockAction && currentTopicId && currentTopicId === user.topic_id) {
const confirmation = newState
? `❌ **用户 [${userName}] 已被屏蔽。**`
: `✅ **用户 [${userName}] 已解除屏蔽。**`;
await telegramApi(env.BOT_TOKEN, "sendMessage", {
chat_id: message.chat.id,
text: confirmation,
message_thread_id: currentTopicId,
parse_mode: "Markdown",
});
}
} catch (e) {
console.error(`处理 ${action} 操作失败:`, e.message);
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", { callback_query_id: callbackQuery.id, text: "❌ 操作失败,请重试。", show_alert: true });
}
}
else if (action === 'pin_card') {
try {
await telegramApi(env.BOT_TOKEN, "pinChatMessage", {
chat_id: message.chat.id,
message_id: message.message_id,
message_thread_id: currentTopicId,
disable_notification: true,
});
if (currentTopicId === user.topic_id) {
await dbUserUpdate(targetUserId, { info_card_message_id: message.message_id.toString() }, env);
} else if (currentTopicId === await dbConfigGet('user_profile_log_topic_id', env)) {
await dbUserUpdate(targetUserId, { profile_log_message_id: message.message_id.toString() }, env);
}
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", {
callback_query_id: callbackQuery.id,
text: "✅ 已置顶该资料卡。",
show_alert: false
});
} catch (e) {
await telegramApi(env.BOT_TOKEN, "answerCallbackQuery", {
callback_query_id: callbackQuery.id,
text: `❌ 置顶失败: ${e.message}`,
show_alert: true
});
}
}
}