Modern and fully asynchronous framework for Telegram Bot API
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive utility functions for deep linking, keyboard building, text formatting, token validation, and complete enumeration system for API constants. These utilities simplify common bot development tasks.
Functions for creating and managing Telegram bot deep links.
def create_start_link(bot_username: str, payload: str | None = None, encode: bool = False) -> str:
"""
Create a start link for the bot.
Parameters:
- bot_username: Bot username (without @)
- payload: Optional payload to include in the link
- encode: Whether to base64 encode the payload
Returns:
Deep link URL (t.me/bot_username?start=payload)
"""
def create_telegram_link(username: str, **params: str) -> str:
"""
Create telegram:// protocol links.
Parameters:
- username: Username or entity
- params: Additional URL parameters
Returns:
Telegram protocol link
"""
def encode_payload(payload: str) -> str:
"""
Encode payload for deep links using base64url encoding.
Parameters:
- payload: Raw payload string
Returns:
Base64url encoded payload
"""
def decode_payload(encoded_payload: str) -> str:
"""
Decode base64url encoded payload.
Parameters:
- encoded_payload: Base64url encoded payload
Returns:
Decoded payload string
"""Utility classes for building inline and reply keyboards.
class KeyboardBuilder:
"""Generic keyboard builder base class"""
def __init__(self, markup: list[list[Any]] | None = None):
"""
Initialize keyboard builder.
Parameters:
- markup: Optional initial markup
"""
def add(self, *buttons: Any) -> KeyboardBuilder:
"""Add buttons to the current row"""
def row(self, *buttons: Any) -> KeyboardBuilder:
"""Add buttons as a new row"""
def adjust(self, *sizes: int, repeat: bool = True) -> KeyboardBuilder:
"""
Adjust button layout by specifying row sizes.
Parameters:
- sizes: Number of buttons per row
- repeat: Whether to repeat the pattern
"""
def as_markup(self) -> Any:
"""Generate the final keyboard markup"""
class InlineKeyboardBuilder(KeyboardBuilder):
"""Builder for inline keyboards"""
def button(
self,
text: str,
url: str | None = None,
login_url: LoginUrl | None = None,
callback_data: str | None = None,
web_app: WebAppInfo | None = None,
switch_inline_query: str | None = None,
switch_inline_query_current_chat: str | None = None,
callback_game: CallbackGame | None = None,
pay: bool | None = None,
**kwargs: Any
) -> InlineKeyboardBuilder:
"""
Add an inline keyboard button.
Parameters:
- text: Button text
- url: HTTP or tg:// URL
- login_url: Login URL for Telegram Login
- callback_data: Data for callback query
- web_app: Web App info
- switch_inline_query: Switch to inline mode
- switch_inline_query_current_chat: Switch to inline mode in current chat
- callback_game: Callback game
- pay: Payment button
Returns:
Self for method chaining
"""
def as_markup(self) -> InlineKeyboardMarkup:
"""Generate InlineKeyboardMarkup"""
class ReplyKeyboardBuilder(KeyboardBuilder):
"""Builder for reply keyboards"""
def button(
self,
text: str,
request_user: KeyboardButtonRequestUser | None = None,
request_chat: KeyboardButtonRequestChat | None = None,
request_contact: bool | None = None,
request_location: bool | None = None,
request_poll: KeyboardButtonPollType | None = None,
web_app: WebAppInfo | None = None,
**kwargs: Any
) -> ReplyKeyboardBuilder:
"""
Add a reply keyboard button.
Parameters:
- text: Button text
- request_user: Request user sharing
- request_chat: Request chat sharing
- request_contact: Request contact sharing
- request_location: Request location sharing
- request_poll: Request poll creation
- web_app: Web App info
Returns:
Self for method chaining
"""
def as_markup(
self,
resize_keyboard: bool | None = None,
one_time_keyboard: bool | None = None,
input_field_placeholder: str | None = None,
selective: bool | None = None,
is_persistent: bool | None = None
) -> ReplyKeyboardMarkup:
"""
Generate ReplyKeyboardMarkup.
Parameters:
- resize_keyboard: Resize keyboard to fit
- one_time_keyboard: Hide keyboard after use
- input_field_placeholder: Placeholder text
- selective: Show keyboard only to specific users
- is_persistent: Keep keyboard visible
Returns:
ReplyKeyboardMarkup object
"""Functions and classes for formatting text with HTML and Markdown.
class TextDecoration:
"""Base class for text decorations"""
def bold(self, text: str) -> str:
"""Make text bold"""
def italic(self, text: str) -> str:
"""Make text italic"""
def code(self, text: str) -> str:
"""Format text as inline code"""
def pre(self, text: str, language: str | None = None) -> str:
"""Format text as code block"""
def link(self, text: str, url: str) -> str:
"""Create a link"""
def spoiler(self, text: str) -> str:
"""Create spoiler text"""
def strikethrough(self, text: str) -> str:
"""Strike through text"""
def underline(self, text: str) -> str:
"""Underline text"""
class HtmlDecoration(TextDecoration):
"""HTML text decoration"""
def bold(self, text: str) -> str:
"""Format: <b>text</b>"""
def italic(self, text: str) -> str:
"""Format: <i>text</i>"""
def code(self, text: str) -> str:
"""Format: <code>text</code>"""
def pre(self, text: str, language: str | None = None) -> str:
"""Format: <pre><code class="language">text</code></pre>"""
def link(self, text: str, url: str) -> str:
"""Format: <a href="url">text</a>"""
def spoiler(self, text: str) -> str:
"""Format: <tg-spoiler>text</tg-spoiler>"""
class MarkdownDecoration(TextDecoration):
"""Markdown text decoration"""
def bold(self, text: str) -> str:
"""Format: **text**"""
def italic(self, text: str) -> str:
"""Format: *text*"""
def code(self, text: str) -> str:
"""Format: `text`"""
def pre(self, text: str, language: str | None = None) -> str:
"""Format: ```language\ntext\n```"""
# Global decoration instances
html: HtmlDecoration
md: MarkdownDecorationFunctions for validating and parsing bot tokens.
def validate_token(token: str) -> bool:
"""
Validate bot token format.
Parameters:
- token: Bot token to validate
Returns:
True if token format is valid
"""
def extract_bot_id(token: str) -> int:
"""
Extract bot ID from token.
Parameters:
- token: Bot token
Returns:
Bot ID as integer
Raises:
ValueError if token format is invalid
"""Utilities for building and managing media groups.
class MediaGroupBuilder:
"""Builder for media groups (albums)"""
def __init__(self, caption: str | None = None, parse_mode: str | None = None):
"""
Initialize media group builder.
Parameters:
- caption: Overall caption for the media group
- parse_mode: Parse mode for caption
"""
def add_photo(
self,
media: str | InputFile,
caption: str | None = None,
parse_mode: str | None = None,
caption_entities: list[MessageEntity] | None = None,
has_spoiler: bool | None = None
) -> MediaGroupBuilder:
"""Add photo to media group"""
def add_video(
self,
media: str | InputFile,
width: int | None = None,
height: int | None = None,
duration: int | None = None,
supports_streaming: bool | None = None,
thumbnail: str | InputFile | None = None,
caption: str | None = None,
parse_mode: str | None = None,
caption_entities: list[MessageEntity] | None = None,
has_spoiler: bool | None = None
) -> MediaGroupBuilder:
"""Add video to media group"""
def add_audio(
self,
media: str | InputFile,
thumbnail: str | InputFile | None = None,
caption: str | None = None,
parse_mode: str | None = None,
caption_entities: list[MessageEntity] | None = None,
duration: int | None = None,
performer: str | None = None,
title: str | None = None
) -> MediaGroupBuilder:
"""Add audio to media group"""
def add_document(
self,
media: str | InputFile,
thumbnail: str | InputFile | None = None,
caption: str | None = None,
parse_mode: str | None = None,
caption_entities: list[MessageEntity] | None = None,
disable_content_type_detection: bool | None = None
) -> MediaGroupBuilder:
"""Add document to media group"""
def build(self) -> list[InputMedia]:
"""Build the media group"""Utilities for managing chat actions (typing indicators).
class ChatActionSender:
"""Automatic chat action sender"""
def __init__(
self,
bot: Bot,
chat_id: int | str,
action: str = "typing",
interval: float = 5.0
):
"""
Initialize chat action sender.
Parameters:
- bot: Bot instance
- chat_id: Target chat ID
- action: Chat action to send
- interval: Interval between action sends
"""
async def __aenter__(self) -> ChatActionSender:
"""Start sending chat actions"""
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
"""Stop sending chat actions"""Additional utilities for generating various types of links.
def create_tg_link(entity: str, **params: str) -> str:
"""
Create tg:// protocol links.
Parameters:
- entity: Target entity (username, chat_id, etc.)
- params: Additional parameters
Returns:
Telegram protocol link
"""Fundamental enumerations for Telegram API constants.
class ChatType(str, Enum):
"""Chat types"""
PRIVATE = "private"
GROUP = "group"
SUPERGROUP = "supergroup"
CHANNEL = "channel"
class ChatAction(str, Enum):
"""Chat actions (typing indicators)"""
TYPING = "typing"
UPLOAD_PHOTO = "upload_photo"
RECORD_VIDEO = "record_video"
UPLOAD_VIDEO = "upload_video"
RECORD_VOICE = "record_voice"
UPLOAD_VOICE = "upload_voice"
UPLOAD_DOCUMENT = "upload_document"
CHOOSE_STICKER = "choose_sticker"
FIND_LOCATION = "find_location"
RECORD_VIDEO_NOTE = "record_video_note"
UPLOAD_VIDEO_NOTE = "upload_video_note"
class ParseMode(str, Enum):
"""Text parsing modes"""
HTML = "HTML"
MARKDOWN = "Markdown"
MARKDOWN_V2 = "MarkdownV2"
class ContentType(str, Enum):
"""Message content types"""
TEXT = "text"
AUDIO = "audio"
DOCUMENT = "document"
ANIMATION = "animation"
GAME = "game"
PHOTO = "photo"
STICKER = "sticker"
VIDEO = "video"
VIDEO_NOTE = "video_note"
VOICE = "voice"
CONTACT = "contact"
DICE = "dice"
POLL = "poll"
VENUE = "venue"
LOCATION = "location"
NEW_CHAT_MEMBERS = "new_chat_members"
LEFT_CHAT_MEMBER = "left_chat_member"
INVOICE = "invoice"
SUCCESSFUL_PAYMENT = "successful_payment"
CONNECTED_WEBSITE = "connected_website"
MIGRATE_TO_CHAT_ID = "migrate_to_chat_id"
MIGRATE_FROM_CHAT_ID = "migrate_from_chat_id"
PINNED_MESSAGE = "pinned_message"
NEW_CHAT_TITLE = "new_chat_title"
NEW_CHAT_PHOTO = "new_chat_photo"
DELETE_CHAT_PHOTO = "delete_chat_photo"
GROUP_CHAT_CREATED = "group_chat_created"
SUPERGROUP_CHAT_CREATED = "supergroup_chat_created"
CHANNEL_CHAT_CREATED = "channel_chat_created"
MESSAGE_AUTO_DELETE_TIMER_CHANGED = "message_auto_delete_timer_changed"
WEB_APP_DATA = "web_app_data"
class UpdateType(str, Enum):
"""Update types"""
MESSAGE = "message"
EDITED_MESSAGE = "edited_message"
CHANNEL_POST = "channel_post"
EDITED_CHANNEL_POST = "edited_channel_post"
INLINE_QUERY = "inline_query"
CHOSEN_INLINE_RESULT = "chosen_inline_result"
CALLBACK_QUERY = "callback_query"
SHIPPING_QUERY = "shipping_query"
PRE_CHECKOUT_QUERY = "pre_checkout_query"
POLL = "poll"
POLL_ANSWER = "poll_answer"
MY_CHAT_MEMBER = "my_chat_member"
CHAT_MEMBER = "chat_member"
CHAT_JOIN_REQUEST = "chat_join_request"Enumerations for message formatting and entities.
class MessageEntityType(str, Enum):
"""Message entity types"""
MENTION = "mention" # @username
HASHTAG = "hashtag" # #hashtag
CASHTAG = "cashtag" # $USD
BOT_COMMAND = "bot_command" # /start
URL = "url" # https://telegram.org
EMAIL = "email" # user@example.com
PHONE_NUMBER = "phone_number" # +1-123-456-7890
BOLD = "bold" # Bold text
ITALIC = "italic" # Italic text
UNDERLINE = "underline" # Underlined text
STRIKETHROUGH = "strikethrough" # Strikethrough text
SPOILER = "spoiler" # Spoiler text
CODE = "code" # Inline code
PRE = "pre" # Code block
TEXT_LINK = "text_link" # Clickable text URLs
TEXT_MENTION = "text_mention" # Users without usernames
CUSTOM_EMOJI = "custom_emoji" # Custom emoji
class PollType(str, Enum):
"""Poll types"""
REGULAR = "regular"
QUIZ = "quiz"
class DiceEmoji(str, Enum):
"""Dice emoji types"""
DICE = "🎲" # 1-6
DARTS = "🎯" # 1-6
BASKETBALL = "🏀" # 1-5
FOOTBALL = "⚽" # 1-5
BOWLING = "🎳" # 1-6
SLOT_MACHINE = "🎰" # 1-64Enumerations for user and chat management.
class ChatMemberStatus(str, Enum):
"""Chat member statuses"""
CREATOR = "creator"
ADMINISTRATOR = "administrator"
MEMBER = "member"
RESTRICTED = "restricted"
LEFT = "left"
KICKED = "kicked"
class BotCommandScopeType(str, Enum):
"""Bot command scope types"""
DEFAULT = "default"
ALL_PRIVATE_CHATS = "all_private_chats"
ALL_GROUP_CHATS = "all_group_chats"
ALL_CHAT_ADMINISTRATORS = "all_chat_administrators"
CHAT = "chat"
CHAT_ADMINISTRATORS = "chat_administrators"
CHAT_MEMBER = "chat_member"Enumerations for media and sticker handling.
class StickerFormat(str, Enum):
"""Sticker formats"""
STATIC = "static"
ANIMATED = "animated"
VIDEO = "video"
class StickerType(str, Enum):
"""Sticker types"""
REGULAR = "regular"
MASK = "mask"
CUSTOM_EMOJI = "custom_emoji"
class InputMediaType(str, Enum):
"""Input media types"""
PHOTO = "photo"
VIDEO = "video"
ANIMATION = "animation"
AUDIO = "audio"
DOCUMENT = "document"Enumerations for inline query handling.
class InlineQueryResultType(str, Enum):
"""Inline query result types"""
ARTICLE = "article"
PHOTO = "photo"
GIF = "gif"
MPEG4_GIF = "mpeg4_gif"
VIDEO = "video"
AUDIO = "audio"
VOICE = "voice"
DOCUMENT = "document"
LOCATION = "location"
VENUE = "venue"
CONTACT = "contact"
GAME = "game"
STICKER = "sticker"from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
# Inline keyboard with multiple buttons
builder = InlineKeyboardBuilder()
builder.button(text="Button 1", callback_data="btn1")
builder.button(text="Button 2", callback_data="btn2")
builder.button(text="Button 3", callback_data="btn3")
builder.button(text="Button 4", callback_data="btn4")
# Adjust layout: 2 buttons per row, then 1, then repeat pattern
builder.adjust(2, 1)
keyboard = builder.as_markup()
# Reply keyboard with special buttons
reply_builder = ReplyKeyboardBuilder()
reply_builder.button(text="📞 Share Contact", request_contact=True)
reply_builder.button(text="📍 Share Location", request_location=True)
reply_builder.button(text="Regular Button")
reply_builder.adjust(2, 1)
reply_keyboard = reply_builder.as_markup(
resize_keyboard=True,
one_time_keyboard=True
)from aiogram.utils.text_decorations import html_decoration as html
from aiogram.utils.text_decorations import markdown_decoration as md
# HTML formatting
text = (
f"{html.bold('Welcome!')} to our bot.\n"
f"Visit our {html.link('website', 'https://example.com')}\n"
f"Use {html.code('/start')} to begin\n"
f"{html.spoiler('Secret message')}"
)
# Markdown formatting
text = (
f"{md.bold('Welcome!')} to our bot.\n"
f"Visit our {md.link('website', 'https://example.com')}\n"
f"Use {md.code('/start')} to begin"
)
await message.answer(text, parse_mode="HTML")from aiogram.utils.deep_linking import create_start_link, encode_payload, decode_payload
# Create simple start link
link = create_start_link("mybot", "welcome")
# Result: https://t.me/mybot?start=welcome
# Create encoded start link
payload = "user_id=123&source=website"
encoded = encode_payload(payload)
link = create_start_link("mybot", encoded)
# In handler, decode the payload
@router.message(CommandStart(deep_link=True))
async def handle_start_link(message: Message, command: CommandObject):
if command.args:
try:
decoded = decode_payload(command.args)
# Parse the decoded payload
params = dict(param.split('=') for param in decoded.split('&'))
user_id = params.get('user_id')
source = params.get('source')
await message.answer(f"Welcome! Referred by user {user_id} from {source}")
except Exception:
await message.answer("Invalid referral link")from aiogram.utils.media_group import MediaGroupBuilder
from aiogram.types import FSInputFile
# Build media group
media_builder = MediaGroupBuilder(caption="Photo album")
media_builder.add_photo(FSInputFile("photo1.jpg"), caption="First photo")
media_builder.add_photo(FSInputFile("photo2.jpg"))
media_builder.add_video(FSInputFile("video.mp4"), caption="Video")
media_group = media_builder.build()
await bot.send_media_group(chat_id, media_group)from aiogram.utils.chat_action import ChatActionSender
# Automatic typing indicator
async with ChatActionSender(bot, chat_id, "typing"):
# Simulate long operation
await asyncio.sleep(3)
await bot.send_message(chat_id, "Operation completed!")
# Custom action and interval
async with ChatActionSender(bot, chat_id, "upload_photo", interval=3.0):
# Process and upload photo
await process_image()
await bot.send_photo(chat_id, photo)from aiogram.enums import ChatType, ContentType, ParseMode
@router.message(F.chat.type == ChatType.PRIVATE)
async def private_only(message: Message):
await message.answer("This works only in private chats")
@router.message(F.content_type == ContentType.PHOTO)
async def handle_photo(message: Message):
await message.answer("Nice photo!")
# Send message with specific parse mode
await bot.send_message(
chat_id,
"<b>Bold text</b>",
parse_mode=ParseMode.HTML
)
# Check update type
if update.message:
update_type = UpdateType.MESSAGE
elif update.callback_query:
update_type = UpdateType.CALLBACK_QUERYfrom aiogram.utils.token import validate_token, extract_bot_id
token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
if validate_token(token):
bot_id = extract_bot_id(token)
print(f"Valid token for bot ID: {bot_id}")
bot = Bot(token=token)
else:
print("Invalid token format")Install with Tessl CLI
npx tessl i tessl/pypi-aiogram