Telegram Bot on Cloudflare Workers

Mohamed Almadih

Mohamed Almadih

8/6/2025
3 min read
Telegram Bot on Cloudflare Workers

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.

1
import initBot from './bot';
2
export default {
3
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
4
await initBot(request, env);
5
return new Response('Hello World!');
6
},
7
};

and here what initBot function looks like

1
import TelegramBot, { TelegramExecutionContext } from '@codebam/cf-workers-telegram-bot';
2
import { welcome, handleVideoLink, isChatMember, sendSubscriptionMessage, handleCallbackQuery } from './commands';
3
4
const initBot = async (request: Request, env: Env) => {
5
const bot = new TelegramBot(env.BOT_TOKEN);
6
await bot
7
.on(':message', async (bot: TelegramExecutionContext) => {
8
await bot.sendTyping();
9
const isMember = await isChatMember(bot.update.message?.from?.id.toString()!, env);
10
if (!isMember) {
11
await sendSubscriptionMessage(bot, env);
12
return new Response('ok');
13
}
14
if (bot.update_type === 'message') {
15
await handleVideoLink(bot, env);
16
}
17
return new Response('ok');
18
})
19
.on(':callback', async (bot: TelegramExecutionContext) => {
20
await handleCallbackQuery(bot, env);
21
return new Response('ok');
22
})
23
.on('start', async (bot: TelegramExecutionContext) => {
24
await welcome(bot, env);
25
return new Response('ok');
26
})
27
.handle(request.clone());
28
};
29
30
export 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

1
import { load } from 'cheerio';
2
3
export async function downloadVideo(url: string) {
4
//make sure it's valid url
5
try {
6
new URL(url);
7
} catch (error) {
8
throw new Error('Please send valid URL');
9
}
10
11
const newUrl = url.replace('x.com', 'twitter.com');
12
13
const parsedUrl = new URL(newUrl);
14
15
if (parsedUrl.hostname !== 'twitter.com') {
16
throw new Error('invalid Twitter URL');
17
}
18
19
return fetch(newUrl.replace('twitter.com', 'vxtwitter.com'), {
20
headers: {
21
'User-Agent': 'TelegramBot (like TwitterBot)',
22
},
23
})
24
.then(async (response) => {
25
if (!response.ok) {
26
throw new Error(`Failed to fetch tweet: ${response.status}`);
27
}
28
29
return response.text();
30
})
31
.then((html) => {
32
const $ = load(html);
33
34
const getMetaContent = (name: string) => {
35
const value = $(`meta[name="twitter:${name}"]`).attr('content') ?? $(`meta[property="og:${name}"]`).attr('content');
36
return value;
37
};
38
39
const videoUrl = getMetaContent('video');
40
if (!videoUrl) {
41
throw new Error('Video not found');
42
}
43
return 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.