telegram-bot-build
Skill1 starsChanged 3 months agoMITView on GitHub
Use when the user has (or needs) a BotFather token and wants a runnable Telegram bot with slash commands and/or inline keyboards, in Python (python-telegram-bot) or Node (telegraf) — not for MTProto client/account automation or large-scale group moderation tooling.
- Reads credentials
- Installs packages
In this repository58 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- api-test-suiteSkill
- ascii-artSkill
- caddy-reverse-proxySkill
- changelog-generatorSkill
- color-palette-generatorSkill
- cron-taskSkill
- csv-toolkitSkill
- discord-bot-buildSkill
- docker-umbrellaSkill
- dotfiles-manageSkill
- email-sendSkill
- env-config-managerSkill
- excalidraw-diagramSkill
- file-organizerSkill
- forgejo-self-hostSkill
- frontend-design-toolkitSkill
- generate-dockerfileSkill
- gif-searchSkill
- git-backupSkill
- github-actions-ciSkill
- hallmark-readmeSkill
- http-api-testerSkill
- invoice-generatorSkill
- json-formatterSkill
- log-analyzerSkill
- markdown-linterSkill
- markdown-to-pdfSkill
- markdown-to-slidesSkill
- mcp-server-buildSkill
- media-id-mappingSkill
- movie-catalogue-siteSkill
- movie-night-calendarSkill
- ntfy-notifierSkill
- ocr-documentsSkill
- ollama-localSkill
- openapi-generatorSkill
- password-generatorSkill
- pdf-extractSkill
- portfolio-upkeepSkill
- qr-code-generatorSkill
- regex-testerSkill
- resume-builderSkill
- rss-monitorSkill
- searxng-self-hostSkill
- skill-publishSkill
- skill-registry-catalogSkill
- skills-portfolio-scaffoldSkill
- snippet-managerSkill
- sqlite-dashboardSkill
- streaming-provider-embedsSkill
- tailscale-deploySkill
- tmdb-metadataSkill
- uptime-kuma-self-hostSkill
- watchlist-syncSkill
- webhook-receiverSkill
- youtube-transcriptSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 4 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 26
Do not use it for:
- Very specific: line 102
Run it: python bot.py or node bot.js.
- Very specific: line 144
Use context.args (Python) / parse ctx.message.text (Node) for parameters.
- Very specific: line 197
Always call query.answer() / ctx.answerCbQuery() — without it the button spins forever on the user's screen.
- Very specific: line 198
Keep callback_data short and stable.
- Distinctive: line 212
Start with polling. Move to webhook only when you have a stable public HTTPS URL and need push (lower latency, fewer requests, required by …
- Very specific: line 213
If deploying serverless (Cloud Functions, Workers, Lambda), webhook is the natural fit — app.post("/webhook", ...) + setWebhook.
- Distinctive: line 214
Never hardcode the webhook URL.
- Distinctive: line 227
Testing with the real token in CI: Use a throwaway test bot or mock the update objects.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
telegram-bot-build
Overview
Build a runnable Telegram bot that responds to slash commands and presents inline keyboard menus. The agent wires up the library, registers handlers, defines the keyboard markup, and runs the bot so the user can open a chat and see it work. Reference libraries: python-telegram-bot (v20+, async) and telegraf (Node).
When to Use
- The user says "build a Telegram bot", "make a bot that does X", or "I have a BotFather token, now what".
- A project needs chat-based interaction: alerts, a menu-driven tool, a support front-end, a command wrapper around an API.
- You are extending an existing bot with new commands or interactive menus.
- The user wants inline keyboards (buttons under a message) rather than free-text replies.
Do not use it for:
- Long-polling over a web framework you already own for a different purpose — still valid, but the bot library handles this natively.
- Anything that needs raw MTProto (telethon/Pyrogram) — that is client/account automation, a different skill.
- Group moderation at scale — possible, but confirm the user wants admin tooling before adding it.
Prerequisites
The bot needs a token from BotFather. If the user does not have one, the first step is to get it:
- Open a chat with @BotFather in Telegram.
- Send
/newbot, pick a display name, then a username ending inbot(e.g.weather_poller_bot). - BotFather replies with a token like
123456789:AAE...longstring. Treat it as a secret. - Send
/setcommandsto BotFather to register the command list users see in the autocomplete menu.
Store the token in an environment variable, never in source:
# .env (gitignored)
TELEGRAM_BOT_TOKEN=123456789:AAE...Then load it in code from the environment. If it is missing, the bot must fail with a clear message, not a cryptic stack trace.
Bot Setup
Python (python-telegram-bot v20+):
pip install python-telegram-bot python-dotenv# bot.py
import os
from dotenv import load_dotenv
from telegram.ext import Application, CommandHandler
load_dotenv()
TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
if not TOKEN:
raise SystemExit("TELEGRAM_BOT_TOKEN is not set. Add it to .env")
async def start(update, context):
await update.message.reply_text("Bot is alive. Send /help.")
def main():
app = Application.builder().token(TOKEN).build()
app.add_handler(CommandHandler("start", start))
# run_polling blocks; the bot is live after this returns nothing
app.run_polling()
if __name__ == "__main__":
main()Node (telegraf):
npm init -y && npm install telegraf dotenv// bot.js
import { Telegraf } from "telegraf";
import "dotenv/config";
const token = process.env.TELEGRAM_BOT_TOKEN;
if (!token) throw new Error("TELEGRAM_BOT_TOKEN is not set. Add it to .env");
const bot = new Telegraf(token);
bot.command("start", (ctx) => ctx.reply("Bot is alive. Send /help."));
bot.launch();
process.once("SIGINT", () => bot.stop("SIGINT"));
process.once("SIGTERM", () => bot.stop("SIGTERM"));Run it: python bot.py or node bot.js. Open the chat with your bot and send /start.
Command Handling
Register one handler per command. Keep each handler small and pure where possible so logic is testable without Telegram.
Python:
from telegram.ext import CommandHandler
async def help_command(update, context):
await update.message.reply_text(
"Commands:\n/start - wake up\n/echo <text> - repeat\n/menu - show buttons"
)
async def echo(update, context):
# context.args holds everything after the command
text = " ".join(context.args) or "nothing to echo"
await update.message.reply_text(text)
app.add_handler(CommandHandler("help", help_command))
app.add_handler(CommandHandler("echo", echo))Node:
bot.command("help", (ctx) =>
ctx.reply("Commands:\n/start - wake up\n/echo <text> - repeat\n/menu - show buttons")
);
bot.command("echo", (ctx) => {
const text = ctx.message.text.replace(/^\/echo(@\S+)?\s*/, "") || "nothing to echo";
return ctx.reply(text);
});Rules:
- Command names are lowercase, letters/numbers/underscore only, max 32 chars.
- Register them with
/setcommandsin BotFather so they autocomplete. - Unknown commands should get a friendly fallback, not silence.
- Use
context.args(Python) / parsectx.message.text(Node) for parameters; do not assume order.
Inline Keyboards
Inline keyboards put buttons under a message. Each button carries a callback_data string (1–64 bytes) that comes back to a callback handler — no text is typed by the user.
Python:
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import CallbackQueryHandler
async def menu(update, context):
keyboard = [
[InlineKeyboardButton("Get weather", callback_data="weather")],
[InlineKeyboardButton("Set city", callback_data="set_city")],
[InlineKeyboardButton("Open site", url="https://example.com")],
]
await update.message.reply_text(
"Choose:", reply_markup=InlineKeyboardMarkup(keyboard)
)
async def button(update, context):
query = update.callback_query
await query.answer() # REQUIRED: clears the loading state on the button
await query.edit_message_text(f"You picked: {query.data}")
app.add_handler(CommandHandler("menu", menu))
app.add_handler(CallbackQueryHandler(button))Node:
bot.command("menu", (ctx) =>
ctx.reply("Choose:", {
reply_markup: {
inline_keyboard: [
[{ text: "Get weather", callback_data: "weather" }],
[{ text: "Set city", callback_data: "set_city" }],
[{ text: "Open site", url: "https://example.com" }],
],
},
})
);
bot.on("callback_query", async (ctx) => {
await ctx.answerCbQuery(); // REQUIRED
await ctx.editMessageText(`You picked: ${ctx.callbackQuery.data}`);
});Rules:
- Always call
query.answer()/ctx.answerCbQuery()— without it the button spins forever on the user's screen. - Keep
callback_datashort and stable; it is what your router switches on. Encode intent, not state. urlbuttons open a link and do not fire a callback.- You can edit the message text on click (
edit_message_text) to reflect the choice instead of sending a new bubble.
Webhook vs Polling
Polling (default, run_polling / bot.launch() in long-poll mode) is simplest: the bot opens HTTPS long-poll requests to Telegram. No public server, no certificates, no port forwarding. Use it for development and for bots behind a home network or behind a tunnel.
Webhook is for production behind a public HTTPS endpoint. Telegram pushes updates to your URL. You must:
- Serve HTTPS on port 443 (or use a reverse proxy / serverless function that terminates TLS).
- Set the webhook once:
curl https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://your.domain/bot<TOKEN>. - Match the path to the token, and delete the old webhook (
deleteWebhook) before switching back to polling — a bot cannot poll and webhook at once.
Guidance:
- Start with polling. Move to webhook only when you have a stable public HTTPS URL and need push (lower latency, fewer requests, required by some hosts).
- If deploying serverless (Cloud Functions, Workers, Lambda), webhook is the natural fit —
app.post("/webhook", ...)+setWebhook. - Never hardcode the webhook URL; read it from env so staging vs prod differs by config.
Common Pitfalls
- Token in source / committed to git. Load from env. A leaked token lets anyone drive your bot; revoke via BotFather
/revoke. - Forgetting
query.answer()/ctx.answerCbQuery(). The button shows a perpetual spinner. Every callback handler must answer the query. callback_dataover 64 bytes. Telegram rejects it silently with an API error. Encode an opaque id, look up the payload server-side.- Polling and webhook simultaneously. You cannot do both. Switching modes requires deleting the webhook first or updates will never arrive on the other.
- Not handling
/setcommands. Without it, commands don't autocomplete and the bot feels broken even though it works. - Replying to callback queries with
reply_textinstead ofedit_message_text. That sends a new message bubble, not an update to the button's message. Use edit for in-place changes. - Crashing on every update. A single unhandled exception in a handler can kill polling in older setups; wrap external calls (API/DB) in try/except and send a graceful error. python-telegram-bot v20 uses async error handlers (
app.add_error_handler). - Localized command names. Commands must be ASCII; don't localize the
/commanditself, localize the reply text. - Long-running work blocking the loop. Heavy jobs (image gen, API calls) should run in a task/queue, not inline in the handler, or updates back up.
- Testing with the real token in CI. Use a throwaway test bot or mock the update objects; never poll the production bot from a test run.
Verification Checklist
TELEGRAM_BOT_TOKENis read from env/.env, absent from source and git history./start(and every registered command) gets a reply when sent from a real Telegram chat.- Every inline-keyboard callback handler calls
answer()/answerCbQuery()— no button spins indefinitely. /setcommandswas sent to BotFather so commands autocomplete in the client.- Only one of polling or webhook is active —
getWebhookInfo(or equivalent) confirms no stale webhook is set if the bot is running in polling mode. - An unhandled exception in one handler does not crash the whole bot process.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

