• 首页
  • 归档
  • 关于
  • 搜索
  • 夜间模式
    ©2026  望时代 Theme by OneBlog

    望时代

    搜索
    标签
    # 教程 # Linux # 脚本 # AI # 影音 # 游戏 # 随笔 # 科技 # 认知
  • 首页>
  • >
  • 正文
  • 『NFD3.0』 Telegram私聊机器人 拦截全部垃圾广告

    2026年06月26日 2 阅读 0 评论 17706 字

    『NFD3.0』 Telegram私聊机器人 拦截全部垃圾广告

    __2025年12月16日 __3条评论 __295次阅读 __0人点赞 __Tao_Qi

    ✨ 特性

    • Serverless 部署 :直接部署在 Cloudflare Workers,零成本。
    • 双向沟通 :用户发消息给机器人 -> 机器人转发给管理员 -> 管理员回复消息 -> 机器人发回给用户。
    • 🛡 ️ 暴躁验证:首次聊天的用户必须完成 CF人机验证 。
    • 🚫 铁血黑名单:支持永久拉黑恶意用户,被拉黑用户无法再发送消息。
    • KV 持久化 :使用 Cloudflare KV 存储验证状态和黑名单,数据不丢失。
    • 便捷指令 :支持直接回复消息进行拉黑、解封、重置验证等操作。

    🛠️ 部署教程

    1. 准备工作

    • Telegram Bot Token :在 TG 上找 @BotFather 创建机器人获取。
    • Admin UID :在 TG 上找 @userinfobot 获取你自己的 User ID。
    • Secret :生成一个随机字符串(用于 Webhook 验证)。

    2. 配置 Cloudflare Workers

    1. 登录 Cloudflare Dashboard。
    2. 进入 Workers & Pages -> Create Application -> Create Worker 。
    3. 命名为 fuck-spam-bot (或其他你喜欢的名字),点击 Deploy 。

    3. 配置 KV 数据库

    1. 在 Workers 页面,点击左侧菜单的 KV 。
    2. 点击 Create a Namespace ,命名为 TG_BOT_KV (或者其他名字)。
    3. 回到你刚才创建的 Worker,进入 Settings -> Variables 。
    4. 向下滚动到 KV Namespace Bindings 。
    5. 点击 Add binding :

      • Variable name : 必须填 nfd (代码中写死了这个名字)。
      • KV Namespace : 选择刚才创建的 TG_BOT_KV。
    6. 点击 Save and deploy 。

    4. 设置环境变量

    在 Worker 的 Settings -> Variables -> Environment Variables 中添加以下变量:

    变量名说明示例
    ENV_BOT_TOKEN你的 Bot Token123456:ABC-DEF...
    ENV_BOT_SECRETWebhook 密钥 (随机字符串)random_string_123
    ENV_ADMIN_UID管理员的 User ID123456789

    5. 部署代码

    1. 点击 Edit code 进入在线编辑器。
    2. 将本项目 worker.js 的内容完整复制粘贴进去。
    3. 点击右上角的 Deploy 。

    6. 绑定 Webhook

    部署完成后,在浏览器访问以下 URL 来激活机器人:
    https://你的worker域名.workers.dev/registerWebhook

    如果看到 Ok,说明部署成功!

    🤖 指令说明 (管理员专用)

    所有指令建议直接回复 (Reply) 用户转发过来的消息使用,机器人会自动提取目标用户 ID。

    指令作用示例
    回复消息直接回复内容给用户(直接打字发送)
    /block拉黑 该用户 (永久)回复某条消息发送 /block
    /unblock解封 该用户回复某条消息发送 /unblock
    /clear_ver重置验证 (强制重新验证)回复某条消息发送 /clear_ver
    也可以手动指定 ID,例如 /unblock 123456789,但回复消息更方便且不易出错。

    📝 验证机制说明

    • 验证有效期 :默认 30 天 。用户通过验证后,30 天内无需再次验证。
    • 黑名单 :永久有效 ,除非管理员手动解封。

    ⚠️ 注意事项

    • 请确保 KV Namespace 的变量名绑定为 nfd,否则机器人无法记忆状态。
    • Cloudflare KV 存在短暂的最终一致性延迟 (约 1 分钟)。如果你刚解封用户,可能需要等几十秒才会生效。
      • *

    License : MIT 全部代码由AI完成,我是抄的隔壁的
    [code]

    // ================================================================= 
    //                     自行修改14-15行
    // =================================================================
    
    const TOKEN = ENV_BOT_TOKEN;      // 不需要修改,在KV中配置
    const WEBHOOK = '/endpoint';
    const SECRET = ENV_BOT_SECRET;    // 在KV中配置
    const ADMIN_UID = ENV_ADMIN_UID;  // 在KV中配置
    
    // 验证通过后的有效期 (秒),默认 30 天
    const VERIFICATION_TTL = 60 * 60 * 24 * 30;
    
    // Cloudflare Turnstile Keys
    const CF_TURNSTILE_SITE_KEY = '0x4AAAAAasdasd0H5ADQjY';
    const CF_TURNSTILE_SECRET_KEY = '0x4AAAAAACG6XsdfsdfsdfsdfZbm2cph_mxgV0';
    
    // =================================================================
    //                      核心功能
    // =================================================================
    
    function apiUrl(methodName, params = null) {
      let query = '';
      if (params) {
        query = '?'   new URLSearchParams(params).toString();
      }
      return `https://api.telegram.org/bot${TOKEN}/${methodName}${query}`;
    }
    
    function requestTelegram(methodName, body, params = null) {
      return fetch(apiUrl(methodName, params), {
        method: 'POST',
        headers: {
          'content-type': 'application/json'
        },
        body: JSON.stringify(body)
      }).then(r => r.json());
    }
    
    function sendMessage(msg = {}) {
      return requestTelegram('sendMessage', msg);
    }
    
    function copyMessage(msg = {}) {
      return requestTelegram('copyMessage', msg);
    }
    
    function forwardMessage(msg) {
      return requestTelegram('forwardMessage', msg);
    }
    
    addEventListener('fetch', event => {
      const url = new URL(event.request.url);
      if (url.pathname === WEBHOOK) {
        event.respondWith(handleWebhook(event, url));
      } else if (url.pathname === '/registerWebhook') {
        event.respondWith(registerWebhook(event, url, WEBHOOK, SECRET));
      } else if (url.pathname === '/unRegisterWebhook') {
        event.respondWith(unRegisterWebhook(event));
      } else if (url.pathname === '/verify') {
        event.respondWith(handleVerifyPage(event.request));
      } else if (url.pathname === '/verify-callback') {
        event.respondWith(handleVerifyCallback(event.request));
      } else {
        event.respondWith(new Response('No handler for this request'));
      }
    });
    
    async function handleWebhook(event, url) {
      if (event.request.headers.get('X-Telegram-Bot-Api-Secret-Token') !== SECRET) {
        return new Response('Unauthorized', { status: 403 });
      }
    
      const update = await event.request.json();
      event.waitUntil(onUpdate(update, url.origin));
      return new Response('Ok');
    }
    
    async function onUpdate(update, origin) {
      if ('message' in update) {
        await onMessage(update.message, origin);
      }
    }
    
    async function onMessage(message, origin) {
      const chatId = message.chat.id.toString();
    
      // 1. 如果是管理员发消息
      if (chatId === ADMIN_UID) {
        return handleAdminMessage(message);
      }
    
      // 2. 如果是访客 (普通用户)
      else {
        // 0. 检查黑名单
        const isBlocked = await nfd.get('blocked-'   chatId);
        if (isBlocked) {
          // 被拉黑了,回复提示
          return sendMessage({
            chat_id: chatId,
            text: '🚫 您已被管理员拉黑,无法发送消息。'
          });
        }
    
        // 1. 检查是否已通过验证
        const isVerified = await nfd.get('verified-'   chatId);
    
        if (isVerified) {
          // 已验证,正常转发给管理员
          return handleGuestMessage(message);
        } else {
          // 未验证,进入验证流程
          return handleVerification(message, chatId, origin);
        }
      }
    }
    
    // 辅助函数:尝试从回复或参数中获取目标 ID
    async function getTargetId(message, commandName) {
      const text = (message.text || '').trim();
      const args = text.split(/\s /);
      const reply = message.reply_to_message;
    
      // 优先 1:从回复的消息中提取
      if (reply && (reply.forward_from || reply.forward_sender_name)) {
        const guestChatId = await nfd.get('msg-map-'   reply.message_id);
        if (guestChatId) return guestChatId;
      }
    
      // 优先 2:从指令参数中提取 (例如 /unblock 123456)
      if (args.length > 1) {
        const potentialId = args[1];
        // 简单的数字校验
        if (/^\d $/.test(potentialId)) {
          return potentialId;
        }
      }
    
      return null;
    }
    
    // 处理管理员消息
    async function handleAdminMessage(message) {
      const text = (message.text || '').trim();
      const reply = message.reply_to_message;
    
      // --- 管理指令区域 ---
    
      // 指令:/block (需回复用户消息)
      if (text === '/block') {
        if (reply && (reply.forward_from || reply.forward_sender_name)) {
          const guestChatId = await nfd.get('msg-map-'   reply.message_id);
          if (guestChatId) {
            await nfd.put('blocked-'   guestChatId, 'true'); // 永久拉黑
            return sendMessage({ chat_id: ADMIN_UID, text: `🚫 用户 ${guestChatId} 已被拉黑。` });
          } else {
            return sendMessage({ chat_id: ADMIN_UID, text: '⚠️ 无法获取用户ID,可能是旧消息。' });
          }
        } else {
          return sendMessage({ chat_id: ADMIN_UID, text: '⚠️ 请回复一条用户转发的消息来拉黑。' });
        }
      }
    
      // 指令:/unblock [ID] (支持回复或手输)
      if (text.startsWith('/unblock')) {
        const targetId = await getTargetId(message, '/unblock');
        if (targetId) {
          await nfd.delete('blocked-'   targetId);
          return sendMessage({ chat_id: ADMIN_UID, text: `✅ 用户 ${targetId} 已解封。` });
        } else {
          return sendMessage({ chat_id: ADMIN_UID, text: '⚠️ 格式错误。
    请回复用户消息发送 /unblock
    或发送 /unblock 123456 (必须是数字 ID)' });
        }
      }
    
      // 指令:/clear_ver [ID] (支持回复或手输)
      if (text.startsWith('/clear_ver')) {
        const targetId = await getTargetId(message, '/clear_ver');
        if (targetId) {
          await nfd.delete('verified-'   targetId);
          return sendMessage({ chat_id: ADMIN_UID, text: `🔄 用户 ${targetId} 验证状态已重置。` });
        } else {
          return sendMessage({ chat_id: ADMIN_UID, text: '⚠️ 格式错误。
    请回复用户消息发送 /clear_ver
    或发送 /clear_ver 123456 (必须是数字 ID)' });
        }
      }
    
      // --- 普通回复逻辑 ---
    
      // 检查是否在回复转发消息
      if (reply && (reply.forward_from || reply.forward_sender_name)) {
        const guestChatId = await nfd.get('msg-map-'   reply.message_id);
        if (guestChatId) {
          return copyMessage({
            chat_id: guestChatId,
            from_chat_id: message.chat.id,
            message_id: message.message_id,
          });
        } else {
          return sendMessage({
            chat_id: ADMIN_UID,
            text: '⚠️ 未找到原用户映射,可能消息太旧或被清理了缓存。'
          });
        }
      } else {
        // 既不是指令也不是回复
        return sendMessage({
          chat_id: ADMIN_UID,
          text: '🤖 管理面板
    
    回复消息 = 发送给用户
    回复并发送 /block = 拉黑
    回复并发送 /unblock = 解封
    回复并发送 /clear_ver = 重置验证'
        });
      }
    }
    
    // 处理验证流程
    async function handleVerification(message, chatId, origin) {
      // 生成验证链接
      const verifyUrl = `${origin}/verify?uid=${chatId}`;
    
      return sendMessage({
        chat_id: chatId,
        text: '🛡 为了防止垃圾消息,请点击下方按钮完成人机验证:',
        reply_markup: {
          inline_keyboard: [[
            { text: '🤖 点击进行人机验证', web_app: { url: verifyUrl } }
          ]]
        }
      });
    }
    
    // 渲染验证页面
    function handleVerifyPage(request) {
      const html = `
    
    
    
        
        
        人机验证
        
        
        
            body {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                height: 100vh;
                margin: 0;
                background-color: #f0f2f5;
            }
            .container {
                background: white;
                padding: 2rem;
                border-radius: 8px;
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                text-align: center;
            }
            h1 { margin-bottom: 1.5rem; color: #1a1a1a; }
            .success-msg { color: #10b981; display: none; }
            .error-msg { color: #ef4444; display: none; margin-top: 1rem; }
        
    
    
        
    
    
            
    
    # 请完成验证
    
    
            
    
    
            
    
    ## ✅ 验证成功!  
    请返回 Telegram 继续聊天。
    
    
            
    
    验证失败,请刷新重试。
    
    
        
    
    
    
        
            // 初始化 Telegram Web App
            const tg = window.Telegram.WebApp;
            tg.ready();
            tg.expand(); // 尝试展开到最大高度
    
            function onVerify(token) {
                const urlParams = new URLSearchParams(window.location.search);
                const uid = urlParams.get('uid');
                
                if (!uid) {
                    document.getElementById('error-msg').innerText = "错误:缺少用户 ID";
                    document.getElementById('error-msg').style.display = 'block';
                    return;
                }
    
                fetch('/verify-callback', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ token, uid })
                })
                .then(response => {
                    if (response.ok) {
                        document.getElementById('turnstile-widget').style.display = 'none';
                        document.getElementById('success-msg').style.display = 'block';
                        
                        // 验证成功 1.5 秒后自动关闭窗口
                        setTimeout(() => {
                            tg.close();
                        }, 1500);
                    } else {
                        throw new Error('Verification failed');
                    }
                })
                .catch(err => {
                    document.getElementById('error-msg').style.display = 'block';
                });
            }
        
    
    
      `;
      return new Response(html, {
        headers: { 'content-type': 'text/html;charset=UTF-8' }
      });
    }
    
    // 处理验证回调
    async function handleVerifyCallback(request) {
      if (request.method !== 'POST') {
        return new Response('Method Not Allowed', { status: 405 });
      }
    
      try {
        const { token, uid } = await request.json();
    
        if (!token || !uid) {
          return new Response('Missing token or uid', { status: 400 });
        }
    
        // 向 Cloudflare 验证 Token
        const formData = new FormData();
        formData.append('secret', CF_TURNSTILE_SECRET_KEY);
        formData.append('response', token);
        // formData.append('remoteip', request.headers.get('CF-Connecting-IP')); // 可选
    
        const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
          method: 'POST',
          body: formData
        }).then(r => r.json());
    
        if (result.success) {
          // 验证通过!写入 KV
          await nfd.put('verified-'   uid, 'true', { expirationTtl: VERIFICATION_TTL });
    
          // 主动通知用户验证成功
          await sendMessage({
            chat_id: uid,
            text: '✅ 验证通过!您可以直接发送消息给管理员了。'
          });
    
          return new Response(JSON.stringify({ success: true }), {
            status: 200,
            headers: { 'content-type': 'application/json' }
          });
        } else {
          return new Response(JSON.stringify({ success: false, error: result['error-codes'] }), {
            status: 400,
            headers: { 'content-type': 'application/json' }
          });
        }
      } catch (e) {
        return new Response(e.message, { status: 500 });
      }
    }
    
    // 处理访客消息 (已验证)
    async function handleGuestMessage(message) {
      const forwardReq = await forwardMessage({
        chat_id: ADMIN_UID,
        from_chat_id: message.chat.id,
        message_id: message.message_id
      });
    
      if (forwardReq.ok && forwardReq.result && forwardReq.result.message_id) {
        // 存储消息映射关系,用于管理员回复
        // 这里也可以设置一个过期时间,比如 48 小时,避免 KV 爆炸
        await nfd.put('msg-map-'   forwardReq.result.message_id, message.chat.id.toString(), { expirationTtl: 172800 });
      } else {
        await sendMessage({
          chat_id: ADMIN_UID,
          text: `❌ 转发消息失败:${JSON.stringify(forwardReq)}`
        });
      }
    }
    
    // =================================================================
    //                      Webhook 设置工具
    // =================================================================
    
    async function registerWebhook(event, requestUrl, suffix, secret) {
      const webhookUrl = `${requestUrl.protocol}//${requestUrl.hostname}${suffix}`;
      const r = await (await fetch(apiUrl('setWebhook', { url: webhookUrl, secret_token: secret }))).json();
      return new Response('ok' in r && r.ok ? 'Ok' : JSON.stringify(r, null, 2));
    }
    
    async function unRegisterWebhook(event) {
      const r = await (await fetch(apiUrl('setWebhook', { url: '' }))).json();
      return new Response('ok' in r && r.ok ? 'Ok' : JSON.stringify(r, null, 2));
    }
    
    

    [/code]

    本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可

    __点赞 __分享

    __ __ __ __ __ __ 打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮

    __No Tag

    最后编辑:2025年12月16日

    < 上一篇

    下一篇 >

    1. 阿卡迪亚说道:

    2025年12月16日 上午11:36

    先赞后看

    回复

    1. beyond说道:

    2025年12月16日 上午11:41

    收藏

    回复

    1. Femtocell说道:

    2025年12月20日 下午1:01

    能不能别复制粘贴原来的教程 cf 这 ui 面板完全改版了根本没法按你的教程操作

    比如到这个操作

    3.配置 KV 数据库

    1.在 Workers 页面,点击左侧菜单的 KV

    _只有在 Storage & databases 的下栏才能找到 worker KV_,这写的在左侧菜单,也不知道面板已经把这个设置改动到 worker 内了 。

    还有到这里

    向下滚动到 KV Namespace Bindings。

    点击 Add binding:
    Variable name: 必须填 nfd (代码中写死了这个名字)。
    KV Namespace: 选择刚才创建的 TG_BOT_KV。

    这里截图也上传不了,只有 bindings 的"add binding" 里才有"kv namespace"而且没有填入框只能添加代码,你这能不能自己亲手操作弄正确流程给大家分享下 ?

    我本来想跟你 tg 机器人私聊跟进这些小问题的,但我想你大概率不回复,所以我只能论坛说了 •••

    回复

    发表回复 取消回复

    电子邮件地址不会被公开。必填项已用 * 标注

    __

    *

    __

    *

    __

    *

    __

    本文著作权归作者 [ doudoudoubao ] 享有,未经作者书面授权,禁止转载,封面图片来源于 [ 互联网 ] ,本文仅供个人学习、研究和欣赏使用。如有异议,请联系博主及时处理。
    取消回复

    发表留言
    回复

    Copyright©2026  All Rights Reserved.  Load:0.004 s
    Theme by OneBlog V3.7.1
    夜间模式

    开源不易,请尊重作者版权,保留基本的版权信息。