mirror of
https://github.com/jikssha/telegram_private_chatbot.git
synced 2026-05-20 07:40:39 +08:00
Update bot version and enhance user checks
Updated the bot version and modified the local question bank. Added mandatory username check for private messages and adjusted the /info command to remove full name display.
This commit is contained in:
55
worker.js
55
worker.js
@@ -1,9 +1,6 @@
|
||||
// Cloudflare Worker:Telegram 双向机器人 (纯本地极速版 v4.2)
|
||||
// 修改内容:
|
||||
// 1. 私聊逻辑增加 Username 强制检查,未设置则拦截。
|
||||
// 2. /info 指令增加显示用户的完整姓名 (Full Name)。
|
||||
// Cloudflare Worker:Telegram 双向机器人 (纯本地极速版 v4.0)
|
||||
|
||||
// --- 1. 本地题库配置 (14条) ---
|
||||
// --- 本地题库 (15条) ---
|
||||
const LOCAL_QUESTIONS = [
|
||||
{"question": "冰融化后会变成什么?", "correct_answer": "水", "incorrect_answers": ["石头", "木头", "火"]},
|
||||
{"question": "正常人有几只眼睛?", "correct_answer": "2", "incorrect_answers": ["1", "3", "4"]},
|
||||
@@ -18,12 +15,13 @@ const LOCAL_QUESTIONS = [
|
||||
{"question": "鱼通常生活在哪里?", "correct_answer": "水里", "incorrect_answers": ["树上", "土里", "火里"]},
|
||||
{"question": "我们用什么器官来听声音?", "correct_answer": "耳朵", "incorrect_answers": ["眼睛", "鼻子", "嘴巴"]},
|
||||
{"question": "晴朗的天空通常是什么颜色的?", "correct_answer": "蓝色", "incorrect_answers": ["绿色", "红色", "紫色"]},
|
||||
{"question": "太阳从哪个方向升起?", "correct_answer": "东方", "incorrect_answers": ["西方", "南方", "北方"]},
|
||||
{"question": "小狗发出的叫声通常是?", "correct_answer": "汪汪", "incorrect_answers": ["喵喵", "咩咩", "呱呱"]}
|
||||
];
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
// --- 2. 环境自检 ---
|
||||
// 环境自检
|
||||
if (!env.TOPIC_MAP) return new Response("Error: KV 'TOPIC_MAP' not bound.");
|
||||
if (!env.BOT_TOKEN) return new Response("Error: BOT_TOKEN not set.");
|
||||
if (!env.SUPERGROUP_ID) return new Response("Error: SUPERGROUP_ID not set.");
|
||||
@@ -37,9 +35,6 @@ export default {
|
||||
return new Response("OK");
|
||||
}
|
||||
|
||||
// --- 3. 路由分发 ---
|
||||
|
||||
// A. 处理按钮回调
|
||||
if (update.callback_query) {
|
||||
await handleCallbackQuery(update.callback_query, env, ctx);
|
||||
return new Response("OK");
|
||||
@@ -50,7 +45,6 @@ export default {
|
||||
|
||||
ctx.waitUntil(flushExpiredMediaGroups(env, Date.now()));
|
||||
|
||||
// B. 处理私聊消息
|
||||
if (msg.chat && msg.chat.type === "private") {
|
||||
try {
|
||||
await handlePrivateMessage(msg, env, ctx);
|
||||
@@ -62,7 +56,6 @@ export default {
|
||||
return new Response("OK");
|
||||
}
|
||||
|
||||
// C. 处理群组消息
|
||||
const supergroupId = Number(env.SUPERGROUP_ID);
|
||||
if (msg.chat && Number(msg.chat.id) === supergroupId) {
|
||||
if (msg.forum_topic_closed && msg.message_thread_id) {
|
||||
@@ -89,27 +82,14 @@ async function handlePrivateMessage(msg, env, ctx) {
|
||||
const userId = msg.chat.id;
|
||||
const key = `user:${userId}`;
|
||||
|
||||
// 1. 过滤掉非 /start 的指令
|
||||
// 拦截普通用户发送的指令
|
||||
if (msg.text && msg.text.startsWith("/") && msg.text.trim() !== "/start") {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 检查黑名单
|
||||
const isBanned = await env.TOPIC_MAP.get(`banned:${userId}`);
|
||||
if (isBanned) return;
|
||||
|
||||
// [新增] 2.1 强制检查 Username 是否存在
|
||||
// 如果 msg.from.username 为空或 undefined,直接中断并提示
|
||||
if (!msg.from.username) {
|
||||
await tgCall(env, "sendMessage", {
|
||||
chat_id: userId,
|
||||
text: "⚠️ **很抱歉,你的用户名(username)未设置,无法进行人机验证流程!消息发送失败!**\n\n(请在 Telegram 设置中配置用户名后重试)",
|
||||
parse_mode: "Markdown"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 检查验证状态
|
||||
const verified = await env.TOPIC_MAP.get(`verified:${userId}`);
|
||||
|
||||
if (!verified) {
|
||||
@@ -119,7 +99,6 @@ async function handlePrivateMessage(msg, env, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 已验证用户,转发消息
|
||||
await forwardToTopic(msg, userId, key, env, ctx);
|
||||
}
|
||||
|
||||
@@ -180,6 +159,7 @@ async function handleAdminReply(msg, env, ctx) {
|
||||
const threadId = msg.message_thread_id;
|
||||
const text = (msg.text || "").trim();
|
||||
|
||||
// 反查 UserId
|
||||
let userId = null;
|
||||
const list = await env.TOPIC_MAP.list({ prefix: "user:" });
|
||||
for (const { name } of list.keys) {
|
||||
@@ -190,9 +170,10 @@ async function handleAdminReply(msg, env, ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找不到用户,说明可能是在普通话题,或者数据丢失,直接返回
|
||||
if (!userId) return;
|
||||
|
||||
// --- 管理员指令区域 ---
|
||||
// --- 指令区域 ---
|
||||
|
||||
if (text === "/close") {
|
||||
const key = `user:${userId}`;
|
||||
@@ -242,23 +223,13 @@ async function handleAdminReply(msg, env, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// [修改] /info 指令逻辑:增加显示 Full Name
|
||||
if (text === "/info") {
|
||||
const chatInfo = await tgCall(env, "getChat", { chat_id: userId });
|
||||
const r = chatInfo.result || {};
|
||||
|
||||
// 1. 获取 Username
|
||||
const username = r.username ? `@${r.username}` : "未设置";
|
||||
|
||||
// 2. [新增] 获取 Full Name (First + Last)
|
||||
const fullName = (r.first_name + " " + (r.last_name || "")).trim();
|
||||
|
||||
const info = `👤 **用户信息**\nUID: \`${userId}\`\nName: \`${fullName}\`\nUsername: \`${username}\`\nTopic ID: \`${threadId}\`\nLink: [点击私聊](tg://user?id=${userId})`;
|
||||
|
||||
const info = `👤 **用户信息**\nUID: \`${userId}\`\nTopic ID: \`${threadId}\`\nLink: [点击私聊](tg://user?id=${userId})`;
|
||||
await tgCall(env, "sendMessage", { chat_id: env.SUPERGROUP_ID, message_thread_id: threadId, text: info, parse_mode: "Markdown" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 转发管理员消息给用户
|
||||
if (msg.media_group_id) {
|
||||
await handleMediaGroup(msg, env, ctx, { direction: "t2p", targetChat: userId, threadId: null });
|
||||
return;
|
||||
@@ -266,9 +237,10 @@ async function handleAdminReply(msg, env, ctx) {
|
||||
await tgCall(env, "copyMessage", { chat_id: userId, from_chat_id: env.SUPERGROUP_ID, message_id: msg.message_id });
|
||||
}
|
||||
|
||||
// ---------------- 验证模块 ----------------
|
||||
// ---------------- 验证模块 (纯本地) ----------------
|
||||
|
||||
async function sendVerificationChallenge(userId, env, pendingMsgId) {
|
||||
// 直接从本地题库随机
|
||||
const q = LOCAL_QUESTIONS[Math.floor(Math.random() * LOCAL_QUESTIONS.length)];
|
||||
const challenge = {
|
||||
question: q.question,
|
||||
@@ -276,7 +248,9 @@ async function sendVerificationChallenge(userId, env, pendingMsgId) {
|
||||
options: shuffleArray([...q.incorrect_answers, q.correct_answer])
|
||||
};
|
||||
|
||||
// 使用 8 位短 ID 防止按钮失效
|
||||
const verifyId = Math.random().toString(36).substring(2, 10);
|
||||
|
||||
const state = { ans: challenge.correct, pending: pendingMsgId };
|
||||
await env.TOPIC_MAP.put(`chal:${verifyId}`, JSON.stringify(state), { expirationTtl: 300 });
|
||||
|
||||
@@ -325,6 +299,7 @@ async function handleCallbackQuery(query, env, ctx) {
|
||||
if (userAns === state.ans) {
|
||||
await tgCall(env, "answerCallbackQuery", { callback_query_id: query.id, text: "✅ 验证通过" });
|
||||
|
||||
// 30天有效期
|
||||
await env.TOPIC_MAP.put(`verified:${userId}`, "1", { expirationTtl: 2592000 });
|
||||
await env.TOPIC_MAP.delete(`chal:${verifyId}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user