### Running a Guest Bot with uv Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/guest-bots.md This example shows how to add aiogram as a dependency and run a simple guest bot example using `uv`. Ensure aiogram version 3.28.0 or higher is installed. ```Bash uv add "aiogram>=3.28.0" BOT_TOKEN=1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo uv run simple_example.py ``` -------------------------------- ### Install and Run Advanced Bot Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/guest-bots.md Installs necessary libraries and runs the bot using uv. Ensure settings.toml is configured. ```bash uv add structlog pydantic-settings openai uv run -m bot ``` -------------------------------- ### TOML Configuration Example Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This snippet demonstrates how to define bot configuration, such as the token, in a TOML file. TOML is preferred for its readability and lack of indentation issues. ```toml [bot]\ntoken = "1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo" ``` -------------------------------- ### Basic Bot Entrypoint with Dispatcher Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/routers.md Sets up the main asynchronous function to initialize the bot and dispatcher, and starts polling for updates. Ensure to replace 'TOKEN' with your actual bot token. ```python import asyncio from aiogram import Bot, Dispatcher # Запуск бота async def main(): bot = Bot(token="TOKEN") dp = Dispatcher() # Запускаем бота и пропускаем все накопленные входящие # Да, этот метод можно вызвать даже если у вас поллинг await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main()) ``` -------------------------------- ### Basic Router with Message Handler Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This snippet shows how to set up a basic router and a handler that responds to any incoming message. It's a starting point for handling general user input. ```Python from aiogram import Router from aiogram.types import Message router = Router(name="start") @router.message() async def any_message( message: Message, ) -> None: await message.answer("Я тебя не понимаю") ``` -------------------------------- ### Example TOML Configuration Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This TOML file defines bot token and logging settings. Ensure your actual settings.toml file contains your real bot token. ```toml [bot] token = "1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo" [logs] project_name = "hello_world" show_datetime = true datetime_format = "%Y-%m-%d %H:%M:%S" show_debug_logs = false time_in_utc = false use_colors_in_console = true renderer = "console" # "console" or "json" allow_third_party_logs = true ``` -------------------------------- ### Get Routers for Bot Configuration Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md Returns a list of configured routers for the bot. This is a basic setup function. ```python def get_routers() -> list[Router]: return [ general_topic_router, in_topic_router, ] ``` -------------------------------- ### Environment Variable Configuration Example Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This snippet shows how to set the bot token using environment variables as an alternative to a TOML file. This provides flexibility for deployment and CI/CD pipelines. ```bash BOT_TOKEN="1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo" ``` -------------------------------- ### Router with CommandStart and General Message Handlers Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This snippet demonstrates a more advanced router setup with two handlers. The first handler specifically catches the /start command using CommandStart filter, while the second handles all other messages. This illustrates prioritized handler execution. ```Python from aiogram import Router from aiogram.filters import CommandStart from aiogram.types import Message router = Router(name="start") @router.message(CommandStart()) async def cmd_start( message: Message, ) -> None: await message.answer("Привет!") @router.message() async def any_message( message: Message, ) -> None: await message.answer("Я тебя не понимаю") ``` -------------------------------- ### Callback Data Factory Example Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/buttons.md Demonstrates the creation of a CallbackData factory for structured callback data. This factory defines a prefix and fields for actions and values, simplifying data parsing. ```python from aiogram.filters.callback_data import CallbackData class NumbersCallbackFactory(CallbackData, prefix="fabnum"): action: str value: int | None = None ``` -------------------------------- ### Basic Bot Entrypoint (bot.py) Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md This is the main entry point for the aiogram bot application. It initializes the Bot and Dispatcher, sets up webhook deletion, and starts polling. ```python import asyncio from aiogram import Bot, Dispatcher async def main(): bot = Bot(token="TOKEN") dp = Dispatcher() # Запускаем бота и пропускаем все накопленные входящие # Да, этот метод можно вызвать даже если у вас поллинг await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main()) ``` -------------------------------- ### Weekend Callback Middleware Example Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md This example implements a middleware that prevents callback queries from being processed on weekends. It checks the current day of the week and either proceeds with the handler or responds with an alert. ```python import asyncio import logging import sys from datetime import datetime from typing import Any, Callable, Dict, Awaitable from aiogram import Bot, Dispatcher, Router, BaseMiddleware, F from aiogram.filters import Command from aiogram.types import Message, CallbackQuery, TelegramObject from aiogram.utils.keyboard import InlineKeyboardBuilder router = Router() # Это будет outer-мидлварь на любые колбэки class WeekendCallbackMiddleware(BaseMiddleware): def is_weekend(self) -> bool: # 5 - суббота, 6 - воскресенье return datetime.utcnow().weekday() in (5, 6) async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, data: Dict[str, Any] ) -> Any: # Можно подстраховаться и игнорировать мидлварь, # если она установлена по ошибке НЕ на колбэки if not isinstance(event, CallbackQuery): # тут как-нибудь залогировать return await handler(event, data) # Если сегодня не суббота и не воскресенье, # то продолжаем обработку. if not self.is_weekend(): return await handler(event, data) # В противном случае отвечаем на колбэк самостоятельно # и прекращаем дальнейшую обработку await event.answer( "Какая работа? Завод остановлен до понедельника!", show_alert=True ) return @router.message(Command("checkin")) async def cmd_checkin(message: Message): builder = InlineKeyboardBuilder() builder.button(text="Я на работе!", callback_data="checkin") await message.answer( text="Нажимайте эту кнопку только по будним дням!", reply_markup=builder.as_markup() ) @router.callback_query(F.data == "checkin") async def callback_checkin(callback: CallbackQuery): # Тут много сложного кода await callback.answer( text="Спасибо, что подтвердили своё присутствие!", show_alert=True ) async def main() -> None: bot = Bot('1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo') dp = Dispatcher() dp.callback_query.outer_middleware(WeekendCallbackMiddleware()) dp.include_router(router) await dp.start_polling(bot) if __name__ == "__main__": logging.basicConfig(level=logging.INFO, stream=sys.stdout) asyncio.run(main()) ``` -------------------------------- ### Main Bot Initialization and Client Setup Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md Initialize the Aiogram bot, dispatcher, HTTP client, and LLM client. The HTTP and LLM clients are added to the dispatcher's workflow data. Includes shutdown hook for closing the HTTP client. ```python import asyncio import httpx import structlog from aiogram import Bot, Dispatcher from aiogram.fsm.storage.memory import MemoryStorage from structlog.typing import FilteringBoundLogger from .config_reader import Settings from .handlers import get_routers from .logs import get_structlog_config from .llm import LLMClient logger: FilteringBoundLogger = structlog.get_logger() async def shutdown(dispatcher: Dispatcher) -> None: await dispatcher["http_client"].aclose() async def main(): settings = Settings() structlog.configure(**get_structlog_config(settings.logs)) bot = Bot(token=settings.bot.token.get_secret_value()) dp = Dispatcher(storage=MemoryStorage()) dp.include_routers(*get_routers()) dp.shutdown.register(shutdown) http_client = httpx.AsyncClient(timeout=None) llm_client = LLMClient(http_client, settings.llm.url) dp.workflow_data.update( http_client=http_client, llm_client=llm_client, ) await logger.ainfo("Starting bot...") await dp.start_polling(bot) asyncio.run(main()) ``` -------------------------------- ### Basic Router Setup for Group Events Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/special-updates.md Sets up a router for handling events within a specific group chat. It includes a comment suggesting a custom filter for checking administrator status. ```python from aiogram import Router, F from aiogram.filters.command import Command from aiogram.types import Message router = Router() # Вообще говоря, можно на роутер навесить кастомный фильтр # с проверкой, лежит ли айди вызывающего во множестве admins. # Тогда все хэндлеры в роутере автоматически будут вызываться # только для людей из admins, это сократит код и избавит от лишнего if ``` -------------------------------- ### Handle Deep Link for Adding Data Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/inline-mode.md Register a handler for `CommandStart` with a specific deep link parameter (`add`). This handler will be triggered when a user clicks the `switch_pm_parameter` from an inline query, allowing them to directly start the data addition process. ```python # Учтите, что хэндлер на просто /start должен идти ПОЗЖЕ @router.message(Command(commands=["start"])) async def cmd_start(message: Message, state: FSMContext): ... ``` -------------------------------- ### Telegram Bot Command Handler with Streaming Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md Handles the /start command, initializing messages for an LLM and setting up a random draft ID for Telegram's streaming API. This is the initial setup before sending streamed messages. ```python from random import randint import structlog from aiogram import Bot, Router from aiogram.exceptions import TelegramRetryAfter from aiogram.filters import CommandStart from aiogram.types import Message from structlog.typing import FilteringBoundLogger from ..llm import LLMClient router = Router() logger: FilteringBoundLogger = structlog.get_logger() @router.message(CommandStart()) async def cmd_start( message: Message, llm_client: LLMClient, bot: Bot, ): messages = [ { "role": "system", "content": "Ты — умный ассистент, помогаешь пользователям с разными задачами." }, { "role": "user", "content": "Ты кто?" } ] # Начальное состояние текста text = "" # Случайный идентификатор черновика draft_id = randint(1, 100_000_000) ``` -------------------------------- ### Start Handler for New Chat Creation Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md This handler is triggered by the /start command and prompts the user to create a new chat. It's the entry point for initiating a new thread-based conversation. ```python import structlog from aiogram import Router from aiogram.filters import CommandStart from aiogram.types import Message from structlog.typing import FilteringBoundLogger router = Router() logger: FilteringBoundLogger = structlog.get_logger() @router.message(CommandStart()) async def cmd_start( message: Message, ): await message.answer("Добро пожаловать! Нажмите /new для создания нового чата") ``` -------------------------------- ### Advanced Message Formatting Example Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/messages.md Demonstrates complex message construction using various formatting utilities like lists, marked sections, key-value pairs, and hashtags. This approach is useful for creating structured and informative messages. ```python from aiogram.filters import Command from aiogram.utils.formatting import ( Bold, as_list, as_marked_section, as_key_value, HashTag ) @dp.message(Command("advanced_example")) async def cmd_advanced_example(message: Message): content = as_list( as_marked_section( Bold("Success:"), "Test 1", "Test 3", "Test 4", marker="✅ ", ), as_marked_section( Bold("Failed:"), "Test 2", marker="❌ ", ), as_marked_section( Bold("Summary:"), as_key_value("Total", 4), as_key_value("Success", 3), as_key_value("Failed", 1), marker=" ", ), HashTag("#test"), sep="\n\n", ) await message.answer(**content.as_kwargs()) ``` -------------------------------- ### Main Bot File with Router Registration and Admin Loading Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/special-updates.md This is the main bot file that sets up the dispatcher, registers routers, and loads the administrator list upon startup. It uses aiogram's default properties for HTML parsing and starts the polling process. ```python import asyncio import logging from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode from config_reader import config from handlers import in_pm, bot_in_group, admin_changes_in_group, events_in_group async def main(): logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", ) dp = Dispatcher() bot = Bot( config.bot_token.get_secret_value(), default=DefaultBotProperties( parse_mode=ParseMode.HTML ) ) dp.include_routers( in_pm.router, events_in_group.router, bot_in_group.router, admin_changes_in_group.router ) # Подгрузка списка админов admins = await bot.get_chat_administrators(config.main_chat_id) admin_ids = {admin.user.id for admin in admins} await dp.start_polling(bot, admins=admin_ids) if __name__ == '__main__': asyncio.run(main()) ``` -------------------------------- ### Guest Message Handler Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/guest-bots.md Handles incoming user messages in guest mode. It prepares the system prompt by injecting message context and then calls the LLM to get a response, handling potential errors. ```python @router.guest_message(F.text) async def guest_message( message: Message, llm_client: AsyncOpenAI, llm_model: str, system_prompt: str, ) -> None: # Проверка, является ли вызывающее сообщение ответом на какое-то другое. if (replied_message := message.reply_to_message) is None: # Это пойдет в системный промпт replied_message = "(none provided)" else: replied_message = ( replied_message.text or f"(some mediafile, contents unknown, " f"but there is a caption: {replied_message.caption})") # Считаем, что не умеем "читать" медиафайлы or "(some mediafile, contents unknown)" ) # Подготовка системного промта prompt = ( system_prompt .replace("{{replied_message}}", replied_message) .replace("{{current_message}}", message.text) # noqa .replace("{{date_today}}", datetime.now().strftime("%d.%m.%Y")) ) response_text = await get_llm_response( client=llm_client, prompt=prompt, model=llm_model, ) parse_mode = None # Бывает, что ответа от модели нет. В таком случае, пусть будет заглушка. if response_text is None: response_text = "К сожалению, не удалось получить ответ от модели." parse_mode = ParseMode.HTML # Отвечаем на исходный запрос await message.answer_guest_query( result=InlineQueryResultArticle( id="1", title=".", input_message_content=InputTextMessageContent( message_text=response_text, parse_mode=parse_mode, ), ) ) ``` -------------------------------- ### Handle Bot Added as Admin or Member in Group/Supergroup Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/special-updates.md This example demonstrates setting up a router and handlers to react to a bot being added to a group or supergroup, distinguishing between administrator and member roles. It filters for group and supergroup chat types. ```python from aiogram import F, Router from aiogram.filters.chat_member_updated import \ ChatMemberUpdatedFilter, IS_NOT_MEMBER, MEMBER, ADMINISTRATOR from aiogram.types import ChatMemberUpdated router = Router() router.my_chat_member.filter(F.chat.type.in_({"group", "supergroup"})) chats_variants = { "group": "группу", "supergroup": "супергруппу" } # Не удалось воспроизвести случай добавления бота как Restricted, ``` -------------------------------- ### Include Routers for General Topic Handling Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md This code snippet shows how to initialize and include routers for handling messages within the general topic. It filters messages to only include those not in a specific thread and then includes the start, new chat creation, and general topic handlers. ```python from aiogram import Router, F from . import ( chat_in_general_topic, new_chat_creation, start, ) general_topic_router = Router() general_topic_router.message.filter(F.message_thread_id.is_(None)) general_topic_router.include_routers( start.router, new_chat_creation.router, chat_in_general_topic.router, ) in_topic_router = Router() ``` -------------------------------- ### Create URL Inline Buttons Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/buttons.md Demonstrates how to create inline buttons that link to external URLs or Telegram user profiles using InlineKeyboardBuilder. It includes examples for GitHub, the official Telegram channel, and a specific user ID, with a check for user privacy settings. ```Python # новый импорт from aiogram.utils.keyboard import InlineKeyboardBuilder @dp.message(Command("inline_url")) async def cmd_inline_url(message: types.Message, bot: Bot): builder = InlineKeyboardBuilder() builder.row(types.InlineKeyboardButton( text="GitHub", url="https://github.com") ) builder.row(types.InlineKeyboardButton( text="Оф. канал Telegram", url="tg://resolve?domain=telegram") ) # Чтобы иметь возможность показать ID-кнопку, # У юзера должен быть False флаг has_private_forwards user_id = 1234567890 chat_info = await bot.get_chat(user_id) if not chat_info.has_private_forwards: builder.row(types.InlineKeyboardButton( text="Какой-то пользователь", url=f"tg://user?id={user_id}") ) await message.answer( 'Выберите ссылку', reply_markup=builder.as_markup(), ) ``` -------------------------------- ### Initialize Bot and Dispatcher in bot/__main__.py Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md This script sets up the bot, dispatcher, and configures logging. It includes reading settings, creating the Bot and Dispatcher instances, and including the routers. ```python import asyncio import structlog from structlog.typing import FilteringBoundLogger from aiogram import Bot, Dispatcher from bot.config import Settings from bot.handlers import get_routers from bot.logging_config import get_structlog_config logger: FilteringBoundLogger = structlog.get_logger() async def main() -> None: # Чтение конфигурации (toml-файл или env vars – не важно) settings = Settings() # Конфигурирование логгера structlog.configure(**get_structlog_config(settings.logs)) # Создание объекта бота. Обязательный аргумент token – читаем токен # из конфигурации. Поскольку токен помечен как SecretStr, то необходимо # дополнительно вызывать get_secret_value(). bot = Bot( token=settings.bot.token.get_secret_value(), ) # Создание объекта диспетчера и привязка роутеров dp = Dispatcher() # Небольшой лайфхак: include_routers() принимает на вход # произвольное количество аргументов # get_routers() возвращает список: [A, B, C,...] # и этот список будет передан как набор аргументов: # include_routers(A, B, C,...) dp.include_routers(*get_routers()) # Запуск бота в режиме поллинга await logger.ainfo("Starting polling...") try: await dp.start_polling(bot) finally: await logger.ainfo("Bot stopped") asyncio.run(main()) ``` -------------------------------- ### Basic Message and Callback Query Handlers Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md This snippet demonstrates a basic message handler for the /start command and a callback query handler for a button press. It shows how to create inline keyboards and respond to user interactions. ```python import asyncio import logging import sys from aiogram.filters import CommandStart, F from aiogram.types import Message, CallbackQuery from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram import Bot, Dispatcher # Assume maintenance_router and regular_router are defined elsewhere # For demonstration purposes, let's define dummy routers class DummyRouter: def message(self, *args, **kwargs): def decorator(handler): return handler return decorator def callback_query(self, *args, **kwargs): def decorator(handler): return handler return decorator def include_routers(self, *routers): pass maintenance_router = DummyRouter() regular_router = DummyRouter() @regular_router.message(CommandStart()) async def cmd_start(message: Message): builder = InlineKeyboardBuilder() builder.button(text="Нажми меня", callback_data="anything") await message.answer( text="Какой-то текст с кнопкой", reply_markup=builder.as_markup() ) @regular_router.callback_query(F.data == "anything") async def callback_anything(callback: CallbackQuery): await callback.answer( text="Это какое-то обычное действие", show_alert=True ) async def main() -> None: bot = Bot('1234567890:AaBbCcDdEeFfGrOoShAHhIiJjKkLlMmNnOo') # В реальной жизни значение maintenance_mode # будет взято из стороннего источника (например, конфиг или через API) # Помните, что т.к. bool тип является иммутабельным, # его смена в рантайме ни на что не повлияет dp = Dispatcher(maintenance_mode=True) # Maintenance-роутер должен быть первый dp.include_routers(maintenance_router, regular_router) await dp.start_polling(bot) if __name__ == "__main__": logging.basicConfig(level=logging.INFO, stream=sys.stdout) asyncio.run(main()) ``` -------------------------------- ### Set 'long_operation' flag for a handler Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md Example of how to apply a flag to a handler using the dispatcher. This flag can be read by middlewares. ```python @dp.message(<тут ваши фильтры>, flags={"long_operation": "upload_video_note"}) ``` -------------------------------- ### OpenRouter Client and Prompt Initialization Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/guest-bots.md Initializes the OpenRouter client and loads the system prompt from a file during bot startup. These are then passed to the Dispatcher. ```python async def main() -> None: settings = Settings() ... openrouter_client = AsyncOpenAI( base_url=settings.llm.base_url, api_key=settings.llm.api_key.get_secret_value(), ) with open("bot/system_prompt.txt", "r", encoding="utf-8") as f: system_prompt: str = f.read() dp = Dispatcher( llm_client=openrouter_client, llm_model=settings.llm.model_name, system_prompt=system_prompt, ) ... ``` -------------------------------- ### Payment Successful Localization Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/payments.md Example of a Fluent localization string for a successful payment message. It includes placeholders for dynamic data like the transaction ID. ```fluent payment-successful = Огромное спасибо! Ваш айди транзакции: {$id} Сохраните его, если вдруг сделать рефанд в будущем 😢 ``` -------------------------------- ### Get Largest Photo Size using as_() Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md Utilize the as_() method with MagicFilter to extract and pass the largest photo object directly to the handler. ```python from aiogram.types import Message, PhotoSize from aiogram import F @router.message(F.photo[-1].as_("largest_photo")) async def forward_from_channel_handler(message: Message, largest_photo: PhotoSize) -> None: print(largest_photo.width, largest_photo.height) ``` -------------------------------- ### Start Polling with Specific Allowed Updates Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/special-updates.md Initiate bot polling with a defined list of updates to receive. If `allowed_updates` is not specified, aiogram will automatically collect them from all routers. ```python async def main(): dp = Dispatcher() bot = Bot("токен") await dp.start_polling( bot, allowed_updates=["message", "inline_query", "chat_member"] ) ``` -------------------------------- ### Handle Deep Links with /start Command Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/messages.md Use CommandStart with deep_link=True to process parameters passed via t.me/bot?start=xxx links. The magic parameter can filter based on arguments, and regexp can extract specific patterns. ```python import re from aiogram import F from aiogram.types import Message from aiogram.filters import Command, CommandObject, CommandStart @dp.message(Command("help")) @dp.message(CommandStart( deep_link=True, magic=F.args == "help" )) async def cmd_start_help(message: Message): await message.answer("Это сообщение со справкой") @dp.message(CommandStart( deep_link=True, magic=F.args.regexp(re.compile(r'book_(\d+)')) )) async def cmd_start_book( message: Message, command: CommandObject ): book_number = command.args.split("_")[1] await message.answer(f"Sending book №{book_number}") ``` -------------------------------- ### Implement /start and /cancel Commands in Aiogram Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/fsm.md Handles the /start command to display a welcome message and remove the reply keyboard. Implements /cancel command handlers for scenarios with and without an active state, clearing data or the entire state and removing the reply keyboard. ```python from aiogram import F, Router from aiogram.filters import Command from aiogram.filters import StateFilter from aiogram.fsm.context import FSMContext from aiogram.fsm.state import default_state from aiogram.types import Message, ReplyKeyboardRemove router = Router() @router.message(Command(commands=["start"])) async def cmd_start(message: Message, state: FSMContext): await state.clear() await message.answer( text="Выберите, что хотите заказать: " "блюда (/food) или напитки (/drinks).", reply_markup=ReplyKeyboardRemove() ) # Нетрудно догадаться, что следующие два хэндлера можно # спокойно объединить в один, но для полноты картины оставим так # default_state - это то же самое, что и StateFilter(None) @router.message(StateFilter(None), Command(commands=["cancel"])) @router.message(default_state, F.text.lower() == "отмена") async def cmd_cancel_no_state(message: Message, state: FSMContext): # Стейт сбрасывать не нужно, удалим только данные await state.set_data({}) await message.answer( text="Нечего отменять", reply_markup=ReplyKeyboardRemove() ) @router.message(Command(commands=["cancel"])) @router.message(F.text.lower() == "отмена") async def cmd_cancel(message: Message, state: FSMContext): await state.clear() await message.answer( text="Действие отменено", reply_markup=ReplyKeyboardRemove() ) ``` -------------------------------- ### Aiogram Bot Entrypoint Configuration Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/fsm.md Sets up the main bot entry point, including logging configuration, initializing the Dispatcher with MemoryStorage, and registering routers for common commands and food ordering. ```python import asyncio import logging from aiogram import Bot, Dispatcher from aiogram.fsm.storage.memory import MemoryStorage # файл config_reader.py можно взять из репозитория # пример — в первой главе from config_reader import config from handlers import common, ordering_food async def main(): logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", ) # Если не указать storage, то по умолчанию всё равно будет MemoryStorage # Но явное лучше неявного =] dp = Dispatcher(storage=MemoryStorage()) bot = Bot(config.bot_token.get_secret_value()) dp.include_router(common.router) dp.include_router(ordering_food.router) # сюда импортируйте ваш собственный роутер для напитков await dp.start_polling(bot) if __name__ == '__main__': asyncio.run(main()) ``` -------------------------------- ### Get LLM Response Function Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/guest-bots.md Asynchronously fetches a response from an LLM provider like OpenRouter. Ensure streaming is disabled for guest bots and handle cases where no completion is returned. ```python async def get_llm_response( client: AsyncOpenAI, prompt: str, model: str, ) -> str | None: completion = await client.chat.completions.create( model=model, messages=[ { "role": "system", "content": prompt, }, ], stream=False, extra_body={"reasoning": {"enabled": True}}, ) if not completion.choices: return None return completion.choices[0].message.content ``` -------------------------------- ### LLM Handler for /start Command Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md Implement a message handler for the /start command that interacts with the LLM client. It sends a system prompt and a user question to the LLM and answers with the response. ```python from aiogram import Router from aiogram.filters import CommandStart from aiogram.types import Message from ..llm import LLMClient router = Router() @router.message(CommandStart()) async def cmd_start( message: Message, llm_client: LLMClient, ): messages = [ { "role": "system", "content": "Ты — умный ассистент, помогаешь пользователям с разными задачами." }, { "role": "user", "content": "Ты кто?" } ] response_text: str = await llm_client.generate_response( messages=messages, temperature=0.7, ) await message.answer(response_text) ``` -------------------------------- ### Define Handlers for /start and Yes/No Answers Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/routers.md This module defines a router and handlers for the /start command, which displays a question with Yes/No buttons, and for the 'Да' and 'Нет' answers, providing appropriate responses and removing the keyboard. ```python from aiogram import Router, F from aiogram.filters import Command from aiogram.types import Message, ReplyKeyboardRemove from keyboards.for_questions import get_yes_no_kb router = Router() @router.message(Command("start")) async def cmd_start(message: Message): await message.answer( "Вы довольны своей работой?", reply_markup=get_yes_no_kb() ) @router.message(F.text.lower() == "да") async def answer_yes(message: Message): await message.answer( "Это здорово!", reply_markup=ReplyKeyboardRemove() ) @router.message(F.text.lower() == "нет") async def answer_no(message: Message): await message.answer( "Жаль...", reply_markup=ReplyKeyboardRemove() ) ``` -------------------------------- ### Applying Slowpoke Middleware to Routers Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md Apply the custom middleware to specific routers with different configurations. This example shows how to attach the 'SlowpokeMiddleware' with varying delay times to two different routers. ```Python from aiogram import Router from <...> import SlowpokeMiddleware # Где-то в другом месте router1 = Router() router2 = Router() router1.message.middleware(SlowpokeMiddleware(sleep_sec=5)) router2.message.middleware(SlowpokeMiddleware(sleep_sec=10)) ``` -------------------------------- ### Команда /start в Telegram-боте Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/payments.md Обработчик команды /start для отображения приветственного сообщения пользователю. Использует локализацию через FluentLocalization и устанавливает parse_mode в None для корректного отображения подстрок. ```fluent cmd-start = Здравствуйте! Спасибо, что решили воспользоваться ботом. Доступны следующие команды: • /donate_1: подарить 1 звезду. • /donate_25: подарить 25 звёзд. • /donate_50: подарить 50 звёзд. • /donate <число>: подарить <число> звёзд. • /paysupport: помощь с покупками. • /refund: возврат платежа (рефанд). ``` ```python @router.message(CommandStart()) async def cmd_start( message: Message, l10n: FluentLocalization, ): await message.answer( l10n.format_value("cmd-start"), parse_mode=None, ) ``` -------------------------------- ### Dynamic Handler Registration Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/quickstart.md Register handlers dynamically using the router's register() method when filter parameters are dynamic. This approach is used when filter parameters are not known until the bot starts. ```Python from aiogram import Router from aiogram.filters import CommandStart from aiogram.types import Message router = Router(name="start") async def cmd_start( message: Message, ) -> None: await message.answer("Привет!") async def any_message( message: Message, ) -> None: await message.answer("Я тебя не понимаю") ``` -------------------------------- ### Handle Initial State Transition with Command Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/fsm.md This handler reacts to the /food command when no state is set for the user. It prompts the user to choose a food item and sets the state to OrderFood.choosing_food_name. ```python from aiogram.filters import Command, StateFilter from aiogram.fsm.context import FSMContext @router.message(StateFilter(None), Command("food")) async def cmd_food(message: Message, state: FSMContext): await message.answer( text="Выберите блюдо:", reply_markup=make_row_keyboard(available_food_names) ) # Устанавливаем пользователю состояние "выбирает название" await state.set_state(OrderFood.choosing_food_name) ``` -------------------------------- ### Basic Text Message Handler with Conditional Logic Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/filters-and-middlewares.md This handler processes any text message and responds differently based on the user's ID. It's an initial example before introducing more specific filters. ```python from random import choice @router.message(F.text) async def my_text_handler(message: Message): phrases = [ "Привет! Отлично выглядишь :)", "Хэллоу, сегодня будет отличный день!", "Здравствуй)) улыбнись :)" ] if message.from_user.id in (111, 777): await message.answer(choice(phrases)) else: await message.answer("Я с тобой не разговариваю!") ``` -------------------------------- ### Create and Send a Reply Keyboard with ReplyKeyboardBuilder Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/buttons.md This snippet shows how to initialize ReplyKeyboardBuilder, add multiple buttons, adjust their layout, and send the keyboard to the user. It's useful for creating custom sets of buttons that users can tap to send predefined messages. ```Python from aiogram.utils.keyboard import ReplyKeyboardBuilder from aiogram import types from aiogram.filters import Command # Assuming 'dp' is your Dispatcher instance # dp = Dispatcher() @dp.message(Command("reply_builder")) async def reply_builder(message: types.Message): builder = ReplyKeyboardBuilder() for i in range(1, 17): builder.add(types.KeyboardButton(text=str(i))) builder.adjust(4) await message.answer( "Выберите число:", reply_markup=builder.as_markup(resize_keyboard=True), ) ``` -------------------------------- ### Callback Query Handler for Number Actions Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/buttons.md Processes callback queries starting with 'num_'. It increments, decrements, or finishes the number selection based on the callback data, updating the message text accordingly. ```python @dp.callback_query(F.data.startswith("num_")) async def callbacks_num(callback: types.CallbackQuery): user_value = user_data.get(callback.from_user.id, 0) action = callback.data.split("_")[1] if action == "incr": user_data[callback.from_user.id] = user_value+1 await update_num_text(callback.message, user_value+1) elif action == "decr": user_data[callback.from_user.id] = user_value-1 await update_num_text(callback.message, user_value-1) elif action == "finish": await callback.message.edit_text(f"Итого: {user_value}") await callback.answer() ``` -------------------------------- ### Handle New Chat Creation Command Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/blog/posts/project_threads_llm.md Handles the '/new' command to create a new forum topic, set up chat storage, and prompt the user to choose a persona. It correctly manages FSM context within the new topic. ```python import structlog from aiogram import Router, Bot from aiogram.enums.topic_icon_color import TopicIconColor from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.fsm.storage.base import StorageKey from aiogram.types import Message from structlog.typing import FilteringBoundLogger from bot.keyboards import make_prompt_keyboard from bot.states import NewChatFlow from bot.storage import memory_chat_storage router = Router() logger: FilteringBoundLogger = structlog.get_logger() @router.message(Command("new")) async def cmd_start( message: Message, bot: Bot, state: FSMContext, ): try: new_topic = await bot.create_forum_topic( chat_id=message.chat.id, name="Новый чат", icon_color=TopicIconColor.YELLOW, ) except: # noqa await logger.aexception("Failed to create new topic") await message.answer("Что-то пошло не так. Пожалуйста, попробуйте ещё раз.") return new_topic_id = new_topic.message_thread_id await memory_chat_storage.create( user_id=message.from_user.id, thread_id=new_topic_id, ) # Важный момент: исходное сообщение пользователя (/new) # принадлежит топику General, но стейт надо поменять уже внутри # только что созданного топика. Поэтому отдельно создаём # ключ FSM и по нему меняем стейт юзеру. key = StorageKey( bot_id=bot.id, chat_id=message.chat.id, user_id=message.from_user.id, thread_id=new_topic_id, ) fsm = FSMContext( storage=state.storage, key=key, ) await bot.send_message( chat_id=message.chat.id, text="Пожалуйста, выберите стиль для этого чата одной из кнопок ниже.", message_thread_id=new_topic_id, reply_markup=make_prompt_keyboard(), ) await fsm.set_state(NewChatFlow.choosing_persona) ``` -------------------------------- ### Send Hidden Link with aiogram Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/messages.md This example shows how to use the `hide_link` utility from `aiogram.utils.markdown` to embed a URL in a message that will generate a link preview without being directly visible in the text. This is useful for attaching media with long captions. ```python from aiogram.types import Message from aiogram.utils.markdown import hide_link from aiogram import Command # Assuming 'dp' is your Dispatcher instance @dp.message(Command("hidden_link")) async def cmd_hidden_link(message: Message): await message.answer( f"{hide_link('https://telegra.ph/file/562a512448876923e28c3.png')}" f"Документация Telegram: *существует*\n" f"Пользователи: *не читают документацию*\n" f"Груша:" ) ``` -------------------------------- ### Configure CallbackAnswerMiddleware with custom settings Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/buttons.md Customize the default behavior of the CallbackAnswerMiddleware by specifying parameters like `pre`, `text`, and `show_alert` during its initialization. ```python dp.callback_query.middleware( CallbackAnswerMiddleware( pre=True, text="Готово!", show_alert=True ) ) ``` -------------------------------- ### Build and Send Media Group (Album) Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/messages.md Construct a media group (album) with an optional general caption and individual media items. Media can be added from local files, URLs, or file IDs. Remember to call `.build()` on the `MediaGroupBuilder` before sending. ```python from aiogram.filters import Command from aiogram.types import FSInputFile, Message from aiogram.utils.media_group import MediaGroupBuilder @dp.message(Command("album")) async def cmd_album(message: Message): album_builder = MediaGroupBuilder( caption="Общая подпись для будущего альбома" ) album_builder.add( type="photo", media=FSInputFile("image_from_pc.jpg") # caption="Подпись к конкретному медиа" ) # Если мы сразу знаем тип, то вместо общего add # можно сразу вызывать add_<тип> album_builder.add_photo( # Для ссылок или file_id достаточно сразу указать значение media="https://picsum.photos/seed/groosha/400/300" ) album_builder.add_photo( media="<ваш file_id>" ) await message.answer_media_group( # Не забудьте вызвать build() media=album_builder.build() ) ``` -------------------------------- ### Create Paysupport Command Redirecting to Refund Source: https://github.com/mastergroosha/aiogram-3-guide/blob/master/book_src/payments.md A simple command handler for `/paysupport` that redirects users to the `/refund` command by providing a predefined message. This is useful for offering a more user-friendly entry point for refund requests. ```python ```python @router.message(Command("paysupport")) async def cmd_paysupport( message: Message, l10n: FluentLocalization ): await message.answer(l10n.format_value("cmd-paysupport")) ``` ```