使用 Cloudflare Workers、Durable Objects 和 Grammy 构建 Telegram 机器人
Building a Telegram Bot with Cloudflare Workers, Durable Objects and Grammy

原始链接: https://flashblaze.xyz/posts/cloudflare-workers-durable-objects-telegram-bot/

## Drinky:一个用于水分追踪的 Telegram 机器人 出于改善饮水量的个人需求,作者构建了“Drinky”,一个用于追踪和提醒用户喝水的 Telegram 机器人。为了避免 Web 或原生应用程序的复杂性,该机器人利用 Telegram API 实现轻松的分享和访问。 该项目使用了部署在 Cloudflare 上的现代技术栈:Durable Objects (DO) 作为快速的无服务器数据库,grammY 用于与 Telegram API 交互,以及 Vitest 和 oxc 等工具用于测试和代码质量。 开发过程中也遇到了一些挑战。最初的障碍包括配置 Webhooks 以接收消息以及启用机器人响应——这需要在无状态的无服务器环境中编写特定代码。作者还发现了通过 API 进行动态命令注册,从而无需更新 BotFather。一个计划中的 AI 驱动的时区解决方案被巧妙地替换为 Telegram 的位置 API 和一个专门的时区查找库。 最终,Drinky 证明是一个宝贵的学习经验,展示了基于机器人方法的强大力量以及现代无服务器技术的有效利用。该项目的 GitHub 仓库地址是[这里](link to repo)。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 使用 Cloudflare Workers、Durable Objects 和 Grammy 构建 Telegram 机器人 (flashblaze.xyz) 6 分,由 flashblaze 发表于 2 小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文
alt

Last month I visited my friend for a week or so who mentioned I drink way less water and he showed me the reading on his water purifier. On average, I drank like 1L of water.

I then started keeping track of how much water I drank, but then I used to miss a couple of days. So I thought, why not create an app to track this and also remind myself to drink water.

I present to you Drinky which does just that. If you’re interested, the GitHub repo is here. This blog post will go into why I went via the bot route, the tech stack I chose and the things I learned.

I didn’t want to develop another web app in which I had to log in to. I thought of creating an Android app and to store the data locally but it would have been overkill and it would also be a hassle if I wanted to share it with somebody. I had already built a Telegram bot to notify when your Blender render is completed (Render Notifier) and I knew the Telegram API was also pretty nice to work with. This would also allow me to share the bot with anyone. So there it was. Telegram bot.

I was tired of using D1 due to its cold start time and drizzle not supporting its sessions API. Since I wanted to deploy everything to Cloudflare, I thought why not use Durable Objects? I had used it a while back to write a blog post, but not in a product as such and each user gets their own DB and since there won’t be any external DB calls as such, the latency between data fetching, updating, etc would be close to 0ms. So I chose DO for my database.

The last remaining part was which TS library to use to interact with Telegram API. I had read about telegraf.js somewhere so I checked it out, but it was not actively maintained. After searching for a bit, I went with grammY due to extensive documentation and it being actively maintained.

Some more new libs which I have used are:

  • oxc - for linting and formatting
  • Vitest - for testing
  • AGENTS.md - for Gemini CLI
  • drizzle (beta version) - for ORM

Seemed pretty basic in hindsight, but no matter what, I was unable to listen to the messages sent to the bot. Added to the fact that I was using wrangler to develop locally (since I would be deploying to Cloudflare Workers), I was not sure what I was doing wrong. I eventually found out I had to set the webhook like so https://api.telegram.org/bot/<BOT_ID>setWebhook?url=<WEBHOOK_URL>. This would tell the bot to forward the messages to the webhook URL. To expose my localhost to the public internet, I used Cloudflare Tunnel.

I was now able to see the messages, but I was not able to respond to them. I regrettably had to ask Cursor to fix this to which it spat out below code.

app.post("/webhook", async (c) => {
    const update = await c.req.json();

    const bot = await setupBot({
        token: c.env.BOT_TOKEN,
        env: c.env,
        commands,
        callbacks,
    });

    await bot.handleUpdate(update);

    return c.json({ ok: true });
});

await bot.handleUpdate(update); is used to pass the updates to the bot instance. Apparently, this is needed in a serverless env where everything is stateless. Adding this fixed the issue

Initially I was under the impression that BotFather has to be used to update any commands which the bot can show to the user. But turns out you can set them via the API as well.

app.post("/webhook", async (c) => {
    const update = await c.req.json();

    const bot = await setupBot({
      token: c.env.BOT_TOKEN,
      env: c.env,
      commands,
      callbacks,
    });

    await bot.api.setMyCommands(
      commands.map((command) => ({
        command: command.name,
        description: command.description,
      })),
    );

    // This is required for grammy to work in Cloudflare Workers. Also, this has to go after the commands are registered.
    await bot.handleUpdate(update);

    return c.json({ ok: true });
});

As you can see, the handleUpdate method needs to be called after the commands are set. Otherwise, the data is not passed along.

There came a point where I had to have timezone identification in the bot. My first thought was to ask user where they’re located, send it to an LLM and then get the timezone in IANA format. Instead, I now ask the user for their location via Telegram’s API, send their lat and long to tz-lookup and get the timezone. I’m glad I did not go via the AI route and I’m ashamed I did not think of the tz-lookup solution first.

All in all, it was a lot of fun building this bot. I learned a lot and also solved my problem rather elegantly if I say so myself.

联系我们 contact @ memedata.com