5 Telegram Bot Patterns Every Python Developer Should Know
After building 50+ bots, these patterns solve 90% of problems.
1. FSM for Multi-Step Flows
from aiogram.fsm.state import State, StatesGroup
class OrderFlow(StatesGroup):
product = State()
quantity = State()
address = State()
@dp.message(Command('order'))
async def start_order(message: Message, state: FSMContext):
await state.set_state(OrderFlow.product)
await message.answer('What product?')
@dp.message(OrderFlow.product)
async def get_product(message: Message, state: FSMContext):
await state.update_data(product=message.text)
await state.set_state(OrderFlow.quantity)
await message.answer('How many?')
2. Throttle Decorator
from functools import wraps
import time
_last_call = {}
def throttle(rate_limit: int = 3):
def decorator(func):
@wraps(func)
async def wrapper(message: Message, *args, **kwargs):
uid = message.from_user.id
now = time.time()
if now - _last_call.get(uid, 0) < rate_limit:
await message.answer('Please wait...')
return
_last_call[uid] = now
return await func(message, *args, **kwargs)
return wrapper
return decorator
3. Paginated Results
def paginate(items: list, page: int, per_page: int = 5):
start = page * per_page
return items[start:start + per_page], len(items) // per_page
def build_page_kb(page: int, total: int) -> InlineKeyboardMarkup:
btns = []
if page > 0:
btns.append(InlineKeyboardButton('Prev', callback_data=f'page:{page-1}'))
if page < total:
btns.append(InlineKeyboardButton('Next', callback_data=f'page:{page+1}'))
return InlineKeyboardMarkup(inline_keyboard=[btns])
4. Admin Middleware
from aiogram import BaseMiddleware
class AdminMiddleware(BaseMiddleware):
def __init__(self, admin_ids: list[int]):
self.admin_ids = admin_ids
async def __call__(self, handler, event, data):
user = data['event_from_user']
if user.id not in self.admin_ids:
await event.answer('Admin only')
return
return await handler(event, data)
dp.message.middleware(AdminMiddleware([ADMIN_ID]))
5. Retry on Network Errors
async def safe_send(bot: Bot, chat_id: int, text: str, retries: int = 3):
for attempt in range(retries):
try:
return await bot.send_message(chat_id, text)
except TelegramRetryAfter as e:
await asyncio.sleep(e.retry_after)
except TelegramForbiddenError:
db.mark_inactive(chat_id)
return None
except Exception:
if attempt == retries - 1:
raise
await asyncio.sleep(2 ** attempt)
Need a production-ready bot with these patterns? Find me on Upwork.
From $49 | 2-3 days | Full source code included
Top comments (0)