惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
G
Google Developers Blog
博客园 - 三生石上(FineUI控件)
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
C
Check Point Blog
V
V2EX
Vercel News
Vercel News
U
Unit 42
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
J
Java Code Geeks
WordPress大学
WordPress大学
罗磊的独立博客
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
M
MIT News - Artificial intelligence
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
云风的 BLOG
云风的 BLOG

竹林里有冰的博客

Nuxt SSG 博客的尾斜杠到底怎么加? | 竹林里有冰的博客 小米 Xiaomi Book Pro 14 (Ultra X7) Linux 兼容性实测 | 竹林里有冰的博客 国内(大陆)版小米 FCM 熄屏断连:Rootless 环境下的尝试与可能的解决方案 | 竹林里有冰的博客 我没法访问 dl.google.com —— 记一次 TUN 下的网络 debug | 竹林里有冰的博客 Vercel 的缓存控制,你注意过吗? | 竹林里有冰的博客 小记 —— Caddy 在 Layer 4 上的流量代理实践 | 竹林里有冰的博客 你的域名后缀拖慢你的网站速度了嘛?——再谈 DNS 冷启动 | 竹林里有冰的博客 DNS 冷启动:小型站点的“西西弗斯之石” | 竹林里有冰的博客 HTTP/2 Server Push 已事实性“死亡”,我很怀念它 | 竹林里有冰的博客 Nuxt Content v3 中数组字段的筛选困境与性能优化 | 竹林里有冰的博客 后 OCSP 时代,浏览器如何应对证书吊销新挑战 | 竹林里有冰的博客 初试 Github Action Self-hosted Runner,想说爱你不容易 | 竹林里有冰的博客 DNS 解析延迟毁了我的图床优化 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(下):告别 DOM 操作,拥抱 AST 与函数式渲染 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(上):从暴力刷新、分块更新到 Morphdom 的华丽变身 | 竹林里有冰的博客 node-sass 迁移至 dart-sass 踩坑实录 | 竹林里有冰的博客 前端中的量子力学——一打开 F12 就消失的 Bug | 竹林里有冰的博客 2025 年,如何为 web 页面上展示的视频选择合适的压缩算法? | 竹林里有冰的博客 el-image 和 el-table 怎么就打架了?Stacking Context 是什么? | 竹林里有冰的博客 2025年,前端如何使用 JS 将文本复制到剪切板? | 竹林里有冰的博客 ssh 拯救世界——通过 ssh 隧道在内网服务器执行 APT 更新 | 竹林里有冰的博客 Cudy TR3000 吃鹅(daed)记 | 竹林里有冰的博客 使用 Cloudflare Workers 监控 Fedora Copr 构建状态 | 竹林里有冰的博客 基于 Cloudflare Workers 实现的在线服务状态检测告警系统 | 竹林里有冰的博客 2024年,Firefox 是唯一还在坚持执行在线的 SSL 证书吊销状态检查的主流浏览器 | 竹林里有冰的博客 小爱课程表适配不完全指北——以 ZJUT 本科正方教务系统为例 | 竹林里有冰的博客 将博客从 waline v2 更新到 waline v3 | 竹林里有冰的博客 给家里云装上 Fedora 41 KDE 后,我是如何配置的 | 竹林里有冰的博客 为 Hexo 添加 follow 认证 | 竹林里有冰的博客 使用 GPT 对 waline 的评论进行审查 | 竹林里有冰的博客
构建部署在 Cloudflare Workers 上的 TG Bot | 竹林里有冰的博客
竹林里有冰 · 2024-12-30 · via 竹林里有冰的博客

起因#

早在去年 10 月,我就写过一篇《创建 b23.tv 追踪参数移除 bot》。记录了部署 b23.tv 的追踪参数移除 Bot 的部署方案。其中提到的 TG Bot 随着服务器到期不再续费也一同落灰了——公益服务总是这样,开始时满腔热血,随着时间散去没有多少人能坚持投入成本,徒留下一地鸡毛。

大概半个月前,我在群里看见 Asuka Minato 开发的群消息总结 Bot,整体部署在 Cloudflare Workers 上,在保证零运营成本的情况下有着相当非常不错的在线率保证,因此便考虑将这个 Bot 迁移到 Cloudflare Workers 上。选用免费的 serverless 能够有效延长服务的可持续性,它不需要额外投入时间精力和财力进行维护,通常可以活很久。

之所以可以把 TG Bot 部署到 Cloudflare Workers 上,主要是得益于 TG 平台支持 webhook 的方式让 Bot 程序提供服务。我们所需要提供的是一个公网能访问的 http 地址,在成功注册 webhook 服务后,TG 的官方服务器会将所有 Bot 所在群或者和所有对个人的对话以 http 请求的方式打到这个 url 上,而 Cloudflare Workers 平台就能提供一个 workers.dev 结尾的公网 url 作为 webhook 提供给 TG 服务器。

怎么做?#

在这个 Github 仓库中,给出了不少例子,用以讲述如何利用 Cloudflare Workers 构建 TG Bot。

bot.js 这个文件中编写了一个最简单的 TG Bot,其功能是: 将受到的所有消息在头部添加「Echo:」字符串,并发送回刚才的对话。

例: 用户发送 「Hello」,bot 回复 「Echo: Hello」


代码分析#

代码整体分为四个部分

顶部变量#

文件顶部定义了 Bot 的 token、webhook 的 url 路径以及一个简易的 webhook 密码。

TOKEN 是从 botfather 那里获得的 bot token,SECRET 是自己设置的 webhook 密码,TG 的 webhook 服务器会通过把这个字段添加到名为 X-Telegram-Bot-Api-Secret-Token 的请求头中来证明自己的官方身份。

const TOKEN = ENV_BOT_TOKEN // Get it from @BotFather https://core.telegram.org/bots#6-botfather
const WEBHOOK = '/endpoint'
const SECRET = ENV_BOT_SECRET // A-Z, a-z, 0-9, _ and -

简易路由#

紧接着定义了一个简易的路由

addEventListener('fetch', event => {
  const url = new URL(event.request.url)
  if (url.pathname === WEBHOOK) {
    event.respondWith(handleWebhook(event))
  } else if (url.pathname === '/registerWebhook') {
    event.respondWith(registerWebhook(event, url, WEBHOOK, SECRET))
  } else if (url.pathname === '/unRegisterWebhook') {
    event.respondWith(unRegisterWebhook(event))
  } else {
    event.respondWith(new Response('No handler for this request'))
  }
})

核心功能#

随后的三个函数,会先进行 SECRET 的校验,并将 message 类型消息从 TG 的所有消息类型中分离开单独处理

/**
 * Handle requests to WEBHOOK
 * https://core.telegram.org/bots/api#update
 */
async function handleWebhook (event) {
  // Check secret
  if (event.request.headers.get('X-Telegram-Bot-Api-Secret-Token') !== SECRET) {
    return new Response('Unauthorized', { status: 403 })
  }

  // Read request body synchronously
  const update = await event.request.json()
  // Deal with response asynchronously
  event.waitUntil(onUpdate(update))

  return new Response('Ok')
}

/**
 * Handle incoming Update
 * https://core.telegram.org/bots/api#update
 */
async function onUpdate (update) {
  if ('message' in update) {
    await onMessage(update.message)
  }
}

/**
 * Handle incoming Message
 * https://core.telegram.org/bots/api#message
 */
function onMessage (message) {
  return sendPlainText(message.chat.id, 'Echo:\n' + message.text)
}

/**
 * Send plain text message
 * https://core.telegram.org/bots/api#sendmessage
 */
async function sendPlainText (chatId, text) {
  return (await fetch(apiUrl('sendMessage', {
    chat_id: chatId,
    text
  }))).json()

我们需要更改的就是这个 onMessage 函数,用户输入的文本信息通过 message.text 获取,我们可以很轻易的把 b23 remover 的逻辑用 js 实现

async function onMessage(message) {
    if (!message.text) {
        return;
    }
    const b23Reg = /b23\.tv\/[a-zA-Z0-9]+/g;
    if (!b23Reg.test(message.text)) {
        console.log('No match found');
    }
    const b23Links = message.text.match(b23Reg);
    const cleanLinks = [];
    await Promise.all(
        b23Links.map(link => b23Remover('https://' + link))
    ).then(result => {
        cleanLinks.push(...result);
    });

    const text2Send = "Track ID removed:\n" + cleanLinks.join("\n");
    return sendPlainText(message.chat.id, text2Send);
}

async function b23Remover (url) {
    const r = await fetch(url)
    const v = r.url
    const u = new URL(v)
    return u.origin + u.pathname
}

webhook 注册相关#

后面的 registerWebhook、unRegisterWebhook、apiUrl 在没有特定需求的情况下不需要变动。

在 Cloudflare 部署后,就可以访问 aaa.bbb.workers.dev/registerWebhook 这个 url 注册 TG 的 webhook 服务。收到 Ok 就代表注册好了。

成果展示#