Telegram Bot on Cloudflare Workers

Mohamed Almadih

Building telegram bots is like a passion of mine, but always had a problem with the hosting, you either pay for a server so it's running all the time or use some kind of serverless solution like google cloud functions, check my previous post about telegram bot on google cloud functions. But to be honest wasn't smooth experience at all.
Cloudflare Workers
Cloudflare workers is a serverless platform, it's really easy to use, you can deploy your code in a few minutes and the free tier is very generous, so i decided to try it. The problem was all available telegram packages are not compatible with cloudflare workers, luckily i found this great package which is compatible with workers and it's really easy to use, so i didn't have to make my own solution. The first thing i did is rebuilding my old X/Twitter downloader bot, the old was written in python and the new one is written in typescript.
Code
The structure is fairly simple, cloudflare workers has fetch function as entry point, inside it i call a function which handles everything from initializing and handling various events.
1import initBot from './bot';2export default {3async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {4await initBot(request, env);5return new Response('Hello World!');6},7};
and here what initBot
function looks like
1import TelegramBot, { TelegramExecutionContext } from '@codebam/cf-workers-telegram-bot';2import { welcome, handleVideoLink, isChatMember, sendSubscriptionMessage, handleCallbackQuery } from './commands';34const initBot = async (request: Request, env: Env) => {5const bot = new TelegramBot(env.BOT_TOKEN);6await bot7.on(':message', async (bot: TelegramExecutionContext) => {8await bot.sendTyping();9const isMember = await isChatMember(bot.update.message?.from?.id.toString()!, env);10if (!isMember) {11await sendSubscriptionMessage(bot, env);12return new Response('ok');13}14if (bot.update_type === 'message') {15await handleVideoLink(bot, env);16}17return new Response('ok');18})19.on(':callback', async (bot: TelegramExecutionContext) => {20await handleCallbackQuery(bot, env);21return new Response('ok');22})23.on('start', async (bot: TelegramExecutionContext) => {24await welcome(bot, env);25return new Response('ok');26})27.handle(request.clone());28};2930export default initBot;
the biggest change is in how the bot fetches the video from X, the old method was tedious and has many steps and was inconsistent, new method is much simpler and more readable, here is the code
1import { load } from 'cheerio';23export async function downloadVideo(url: string) {4//make sure it's valid url5try {6new URL(url);7} catch (error) {8throw new Error('Please send valid URL');9}1011const newUrl = url.replace('x.com', 'twitter.com');1213const parsedUrl = new URL(newUrl);1415if (parsedUrl.hostname !== 'twitter.com') {16throw new Error('invalid Twitter URL');17}1819return fetch(newUrl.replace('twitter.com', 'vxtwitter.com'), {20headers: {21'User-Agent': 'TelegramBot (like TwitterBot)',22},23})24.then(async (response) => {25if (!response.ok) {26throw new Error(`Failed to fetch tweet: ${response.status}`);27}2829return response.text();30})31.then((html) => {32const $ = load(html);3334const getMetaContent = (name: string) => {35const value = $(`meta[name="twitter:${name}"]`).attr('content') ?? $(`meta[property="og:${name}"]`).attr('content');36return value;37};3839const videoUrl = getMetaContent('video');40if (!videoUrl) {41throw new Error('Video not found');42}43return videoUrl;44});45}
Conclusion
The bot has been running smoothly for a while now, it has considerable amount of active users and it's been very useful for me, i'm planning to add more features to it, like downloading multiple videos from a single message, adding more commands and more.
number of requests for the past 30 days.
If you want to try the bot, you can find it here.