Skills telegram-bot-builder
git clone https://github.com/TerminalSkills/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/telegram-bot-builder" ~/.claude/skills/terminalskills-skills-telegram-bot-builder && rm -rf "$T"
skills/telegram-bot-builder/SKILL.md- pip install
- references .env files
Telegram Bot Builder
Overview
Builds production-ready Telegram bots covering the full Bot API surface: commands, inline keyboards, callback queries, conversations with state machines, media handling, group management, payments, and Mini Apps. Supports both long polling (development) and webhooks (production).
Instructions
1. Bot Creation
- Message @BotFather on Telegram
- Send
, choose name and username/newbot - Save the bot token (format:
)123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 - Configure with
,/setcommands
,/setdescription/setabouttext
2. Project Scaffolding
Recommended frameworks by language:
Node.js — grammY (recommended):
npm init -y && npm install grammy dotenv
Node.js — Telegraf:
npm install telegraf dotenv
Python — python-telegram-bot:
pip install python-telegram-bot python-dotenv
Standard project structure:
telegram-bot/ ├── bot.js # Entry point, bot initialization ├── handlers/ │ ├── commands.js # /start, /help, custom commands │ ├── callbacks.js # Inline keyboard callback handlers │ ├── conversations.js # Multi-step conversation flows │ └── middleware.js # Auth, logging, rate limiting ├── keyboards/ # Inline and reply keyboard builders ├── services/ # Business logic ├── .env # BOT_TOKEN, WEBHOOK_URL, ADMIN_ID └── package.json
3. Core Patterns (grammY)
Basic command handler:
bot.command('start', async (ctx) => { await ctx.reply('Welcome!', { reply_markup: { inline_keyboard: [[ { text: '📊 Dashboard', callback_data: 'dashboard' }, { text: '⚙️ Settings', callback_data: 'settings' } ]] } }); });
Callback query handler:
bot.callbackQuery('dashboard', async (ctx) => { await ctx.answerCallbackQuery(); await ctx.editMessageText('Here is your dashboard...', { reply_markup: backButton }); });
Conversation with sessions:
bot.use(session({ initial: () => ({ step: 'idle' }) })); bot.use(conversations()); bot.use(createConversation('onboarding', onboardingFlow)); async function onboardingFlow(conversation, ctx) { await ctx.reply('What is your name?'); const name = await conversation.wait(); await ctx.reply('What is your email?'); const email = await conversation.wait(); await ctx.reply(`Thanks ${name.message.text}! Registered with ${email.message.text}`); }
Middleware for auth:
function adminOnly(ctx, next) { if (ctx.from?.id !== Number(process.env.ADMIN_ID)) { return ctx.reply('⛔ Not authorized'); } return next(); } bot.command('admin', adminOnly, handleAdmin);
4. Keyboard Types
Inline Keyboard (attached to message):
- Callback buttons (
) — triggers callbackQuery handlercallback_data - URL buttons (
) — opens a linkurl - Web App buttons (
) — opens a Mini Appweb_app - Switch Inline buttons (
) — triggers inline modeswitch_inline_query
Reply Keyboard (replaces phone keyboard):
- Custom keyboard with predefined responses
to auto-hide after selectionone_time_keyboard: true
for compact displayresize_keyboard: true
Remove Keyboard:
{ reply_markup: { remove_keyboard: true } }
5. Polling vs Webhooks
Long Polling (development):
bot.start(); // Calls getUpdates in a loop
- No public URL needed
- Slightly higher latency
- Good for development and low-traffic bots
Webhooks (production):
// With express const app = express(); app.use(express.json()); app.use(`/bot${token}`, webhookCallback(bot, 'express')); app.listen(3000); // Set webhook await bot.api.setWebhook(`https://yourdomain.com/bot${token}`);
- Requires HTTPS public URL
- Lower latency, better for high traffic
- Self-signed certificates supported with
parametercertificate
6. Media Handling
// Send photo await ctx.replyWithPhoto(new InputFile('./image.png'), { caption: 'Check this out' }); // Send document await ctx.replyWithDocument(new InputFile(buffer, 'report.pdf')); // Handle received photos bot.on('message:photo', async (ctx) => { const file = await ctx.getFile(); const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`; });
7. Bot API Limits
- Messages: 30 messages/sec globally, 1 message/sec per chat in groups
- Inline results: 50 results per query
- File upload: 50 MB max (20 MB for photos)
- File download: 20 MB via getFile
- Message length: 4096 characters
- Caption length: 1024 characters
- Inline keyboard: 100 buttons per message
- Webhook: 40M updates/hour
8. Deployment
- Simple: Railway, Render, Fly.io (webhook mode)
- Serverless: Vercel/AWS Lambda with webhook adapter
- VPS: systemd service with auto-restart
- Docker: Lightweight Node.js container with health checks
Examples
Example 1: Task Management Bot
Input: "Build a Telegram bot for my team to manage tasks. Users should be able to create tasks, assign them, set deadlines, and get reminders."
Output: A grammY bot with:
command opening a conversation flow (title → description → assignee → deadline)/newtask- Inline keyboard for task status updates (To Do → In Progress → Done)
- Daily reminder messages for overdue tasks using node-cron
showing personal task list with inline navigation/mytasks- SQLite database for persistence via better-sqlite3
Example 2: Content Publishing Bot
Input: "Create a Telegram bot that lets me draft posts, preview them, schedule them to a channel, and track engagement."
Output: A grammY bot with:
- Multi-step drafting flow with text, photos, and formatting preview
- Schedule picker with inline calendar keyboard
- Auto-publishing to target channel via
bot.api.sendMessage(channelId, ...) - Engagement tracking by checking message views via
and forwarded countsgetChat
command listing scheduled posts with edit/delete options/drafts
Guidelines
- Always handle errors in callbacks — unhandled errors kill the bot process
- Use
to dismiss the loading indicator on buttonsctx.answerCallbackQuery() - Store bot token in env vars, never hardcode
- Use
orparse_mode: 'HTML'
for rich text (MarkdownV2 requires escaping special chars)'MarkdownV2' - Implement graceful shutdown:
on SIGINT/SIGTERMbot.stop() - For groups: handle
updates to track when bot is added/removedmy_chat_member - Set commands list via
for autocompletebot.api.setMyCommands() - Use
for long operationsctx.chatAction = 'typing' - Rate limit user interactions to prevent abuse
- For conversations: always handle the case where the user sends an unexpected message type