Automated summary
This skill provides patterns and structure for developing Telegram bots using aiogram 3.x, python-telegram-bot, Telethon, and pyrogram frameworks.
Strengths
- Covers four major frameworks with specific use cases
- Provides a clear modular project structure for aiogram 3.x
- Includes concrete code examples (routers, inline keyboards, FSM)
- Guides framework selection based on needs (production, prototype, userbot)
Limitations
- Does not detail webhook configuration or deployment
- Does not cover advanced error handling or logging
- Examples are aiogram-specific; less detail on other frameworks
Use this skill when building or modifying Telegram bots that need robust architecture, interactive keyboards, or state management (FSM).
Avoid this skill for very simple Telegram scripts (single handler) lacking modular structure, or if using incompatible older versions of these frameworks.
Security analysis
SafeThe skill provides educational code snippets for Telegram bot development. No destructive, exfiltrating, or obfuscated actions are present. It recommends using environment variables for secrets, aligning with security best practices.
No concerns found
How this analysis is madeExamples
Create a Telegram bot using aiogram 3.x with a /start command that shows an inline keyboard with two buttons: Status and Settings. When clicked, each button sends a callback query that updates the message to show the selected action.Implement a finite state machine (FSM) in aiogram 3.x for a bot that collects user name, age, and email through a series of prompts. Use StatesGroup and handle state transitions.Refactor an existing Telegram bot built with python-telegram-bot to use aiogram 3.x. Show the equivalent code for handlers, inline keyboards, and command registration.name: telegram-bot description: Telegram bot development with aiogram 3.x, python-telegram-bot, Telethon, and pyrogram. Covers bot architecture, handlers, keyboards, webhooks, and deployment.
Telegram Bot Development Skill
When to Activate
- Creating or modifying Telegram bots
- Working with Telegram Bot API
- Setting up webhooks or long polling
- Building inline keyboards, callback handlers
- Telegram userbot or channel management
Framework Selection
| Framework | Use Case | Style | |-----------|----------|-------| | aiogram 3.x | Production bots, complex logic, FSM | Async, router-based | | python-telegram-bot 21.x | Simple bots, quick prototypes | Sync/async, handler-based | | Telethon | Userbots, account automation, MTProto | Async, low-level | | pyrogram | Userbots + bots, media handling | Async, modern API |
Default choice: aiogram 3.x - best for production bots.
aiogram 3.x Project Structure
bot/
__init__.py
main.py # Entry point, bot startup
config.py # Settings from env
handlers/
__init__.py
start.py # /start, /help commands
admin.py # Admin commands
callbacks.py # Callback query handlers
errors.py # Error handler
keyboards/
__init__.py
inline.py # InlineKeyboardMarkup builders
reply.py # ReplyKeyboardMarkup builders
middlewares/
__init__.py
auth.py # User authentication
throttling.py # Rate limiting
services/
__init__.py
database.py # DB operations
api_client.py # External API calls
models/
__init__.py
user.py # User model
filters/
__init__.py
admin.py # Admin check filter
states/
__init__.py
forms.py # FSM states
utils/
__init__.py
helpers.py
aiogram 3.x Core Patterns
Bot Entry Point
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.enums import ParseMode
from aiogram.client.default import DefaultBotProperties
from bot.config import settings
from bot.handlers import start, admin, callbacks
async def main():
bot = Bot(
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)
dp = Dispatcher()
# Register routers
dp.include_routers(
start.router,
admin.router,
callbacks.router,
)
# Start polling
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Router-Based Handlers
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import Command, CommandStart
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message):
await message.answer(
"Welcome! Choose an option:",
reply_markup=get_main_keyboard()
)
@router.message(Command("help"))
async def cmd_help(message: Message):
await message.answer("Available commands:\n/start - Main menu\n/help - This message")
@router.callback_query(F.data.startswith("action:"))
async def handle_action(callback: CallbackQuery):
action = callback.data.split(":")[1]
await callback.answer(f"Processing: {action}")
await callback.message.edit_text(f"Action {action} completed")
Inline Keyboards
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
def get_main_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Status", callback_data="action:status"),
InlineKeyboardButton(text="Settings", callback_data="action:settings"),
)
builder.row(
InlineKeyboardButton(text="Help", callback_data="action:help"),
)
return builder.as_markup()
FSM (Finite State Machine)
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
class AddChannel(StatesGroup):
waiting_for_name = State()
waiting_for_url = State()
confirmation = State()
@router.message(Command("add_channel"))
async def start_add(message: Message, state: FSMContext):
await state.set_state(AddChannel.waiting_for_name)
await message.answer("Enter channel name:")
@router.message(AddChannel.waiting_for_name)
async def process_name(message: Message, state: FSMContext):
await state.update_data(name=message.text)
await state.set_state(AddChannel.waiting_for_url)
await message.answer("Enter channel URL:")
@router.message(AddChannel.waiting_for_url)
async def process_url(message: Message, state: FSMContext):
data = await state.get_data()
await state.clear()
await message.answer(f"Channel '{data['name']}' with URL {message.text} added!")
Middleware
from aiogram import BaseMiddleware
from typing import Callable, Awaitable, Any
class AuthMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Message, dict[str, Any]], Awaitable[Any]],
event: Message,
data: dict[str, Any],
) -> Any:
user = await db.get_user(event.from_user.id)
if not user:
await event.answer("Access denied. Contact admin.")
return
data["user"] = user
return await handler(event, data)
Webhook Setup (Production)
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from aiohttp import web
WEBHOOK_PATH = f"/webhook/{settings.BOT_TOKEN}"
WEBHOOK_URL = f"https://{settings.DOMAIN}{WEBHOOK_PATH}"
async def on_startup(bot: Bot):
await bot.set_webhook(WEBHOOK_URL)
app = web.Application()
webhook_handler = SimpleRequestHandler(dispatcher=dp, bot=bot)
webhook_handler.register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)
web.run_app(app, host="0.0.0.0", port=8080)
Telethon (Userbots / Channel Management)
Basic Setup
from telethon import TelegramClient, events
from telethon.sessions import StringSession
# File session (persists on disk)
client = TelegramClient("session_name", api_id, api_hash)
# String session (for Docker / serverless - store in env var)
client = TelegramClient(StringSession(SESSION_STRING), api_id, api_hash)
Channel Parsing & Forwarding
@client.on(events.NewMessage(chats=["source_channel"]))
async def forward_handler(event):
# Forward with original formatting
await client.send_message("target_channel", event.message)
# Parse channel history
async def parse_channel_history(channel: str, limit: int = 100) -> list[dict]:
messages = []
async for msg in client.iter_messages(channel, limit=limit):
messages.append({
"id": msg.id,
"text": msg.text,
"date": msg.date,
"views": msg.views,
"media": bool(msg.media),
"forwards": msg.forwards,
})
return messages
# Download media from channel
async def download_channel_media(channel: str, dest_dir: str, limit: int = 50):
async for msg in client.iter_messages(channel, limit=limit):
if msg.media:
await client.download_media(msg, file=dest_dir)
Session Management
# Generate string session (run once interactively)
async def generate_session():
async with TelegramClient(StringSession(), api_id, api_hash) as client:
print("Session string:", client.session.save())
# Multi-account management
# WARNING: Telegram actively bans automated accounts.
# Using multiple userbots may violate Telegram ToS. Use at your own risk.
class AccountPool:
def __init__(self, sessions: list[str]):
self.clients = [
TelegramClient(StringSession(s), api_id, api_hash)
for s in sessions
]
self.current = 0
async def get_client(self) -> TelegramClient:
"""Round-robin client selection to avoid rate limits."""
client = self.clients[self.current]
self.current = (self.current + 1) % len(self.clients)
if not client.is_connected():
await client.connect()
return client
Pyrogram (Modern MTProto)
from pyrogram import Client, filters
from pyrogram.types import Message
app = Client("my_account", api_id=api_id, api_hash=api_hash)
# Channel message handler
@app.on_message(filters.channel & filters.chat("source_channel"))
async def channel_handler(client: Client, message: Message):
# Process and forward
text = message.text or message.caption or ""
if should_forward(text):
await client.send_message("target_channel", text)
# Batch operations
async def get_channel_members(channel: str) -> list:
members = []
async for member in app.get_chat_members(channel):
members.append({"id": member.user.id, "name": member.user.first_name})
return members
# Media group handling
@app.on_message(filters.media_group)
async def media_group_handler(client: Client, message: Message):
media_group = await client.get_media_group(message.chat.id, message.id)
for msg in media_group:
await client.download_media(msg, file_name=f"media/{msg.id}")
Telegram Payments & Mini Apps
Telegram Stars Payment
from aiogram.types import LabeledPrice
@router.message(Command("buy"))
async def cmd_buy(message: Message):
await message.answer_invoice(
title="Premium Access",
description="30 days of premium features",
payload="premium_30d",
currency="XTR", # Telegram Stars
prices=[LabeledPrice(label="Premium", amount=100)], # 100 Stars
)
@router.pre_checkout_query()
async def pre_checkout(query: PreCheckoutQuery):
await query.answer(ok=True)
@router.message(F.successful_payment)
async def payment_success(message: Message):
payment = message.successful_payment
await activate_premium(message.from_user.id, days=30)
await message.answer("Premium activated!")
Telegram Mini App (Web App)
from aiogram.types import WebAppInfo, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
@router.message(Command("app"))
async def open_app(message: Message):
builder = InlineKeyboardBuilder()
builder.add(InlineKeyboardButton(
text="Open Dashboard",
web_app=WebAppInfo(url="https://your-app.com/miniapp")
))
await message.answer("Open the dashboard:", reply_markup=builder.as_markup())
# Validate data from Mini App
from aiogram.utils.web_app import check_webapp_signature, parse_webapp_init_data
def validate_webapp_data(init_data: str, bot_token: str) -> dict:
if check_webapp_signature(bot_token, init_data):
return parse_webapp_init_data(init_data)
raise ValueError("Invalid webapp data")
Bot + Telethon Hybrid Architecture
Use aiogram for bot commands and Telethon for userbot features in the same project.
import asyncio
from aiogram import Bot, Dispatcher
from telethon import TelegramClient
async def main():
# Bot (via Bot API)
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
dp.include_router(bot_handlers.router)
# Userbot (via MTProto)
userbot = TelegramClient(StringSession(SESSION), api_id, api_hash)
await userbot.start()
# Share data via Redis
redis = await aioredis.from_url("redis://localhost")
# Run both concurrently
await asyncio.gather(
dp.start_polling(bot),
userbot.run_until_disconnected(),
)
Docker Deployment
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-m", "bot.main"]
# docker-compose.yml
services:
bot:
build: .
env_file: .env
restart: always
depends_on:
db: { condition: service_healthy }
redis: { condition: service_started }
See Also
docker-devopsskill for production Docker deploymentdatabase-patternsskill for bot data persistenceprompt-engineeringskill for LLM-powered bot features
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/telegram-bot-development-mmh8qqb4' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/telegram-bot-development-mmh8qqb4/download' --output '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md.part' && mv '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md.part' '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md' && test -s '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/telegram-bot' && cp -R '.skill-downloads/telegram-bot-development-mmh8qqb4' '.claude/skills/telegram-bot'Codex
test -f '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md' && test -s '.skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/telegram-bot' && cp -R '.skill-downloads/telegram-bot-development-mmh8qqb4' '.agents/skills/telegram-bot'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/telegram-bot-development-mmh8qqb4'openskills read 'telegram-bot-development-mmh8qqb4'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/telegram-bot-development-mmh8qqb4/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
React Hook Form Best Practices
Development
Master React Hook Form: avoid anti-patterns, manage subscriptions, and handle reset, dirty state, and controlled inputs correctly.
Emotion to Mantine Migration
Development
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports.
Scope Creep Detector
Development
Analyzes git diffs against a stated intent to detect scope creep, unrelated files, broad PRs, and oversized changes, then suggests keep, split, or justify actions.