Привет. Бот работает, это круто. Но писать всё в одном файле — как-то не то. В хороших проектах так не принято, а наш бот будет лучшим! Поэтому сегодня мы вынесем команды и другие handlerы в отдельную директорию, где сможем удобно их разделить не только на отдельные файлики, но и папки для каналов, чатов, личных сообщений. А потом рассмотрим фильтры AIOGram, которые помогут боту правильно это всё обработать.
Данная статья — прямое продолжение этой: [AIOGram | Python] Урок 1. Вступление. Простой бот
Новые папки и __init__.py
Добавляем новую папку и называем её handlers.
Автоматически должен был быть создан и файлик __init__.py
Для чего он нужен вы можете прочитать подробнее тут (https://docs.python.org/3/tutorial/modules.html#packages). А я скажу так: этот файлик помечает каталог как пакет (package), а также в нём можно прописать код инициализации чего-либо и глобавльные переменные пакета, которые будут доступны из вне.
Внутри handlers создадим ещё три папки: channels, groups, personal. Само собой в каждой будет ещё по __init__.py. В будущем не запутайтесь!
Теперь в папку personal добавим файлик echo.py. Как вы догадались, он заменит собой функцию эхо, которую мы писали в прошлый раз. Обязательно удалите её из main.py.
Содержимое файла будет простым:
1 2 3 4 5 6 7 |
from aiogram import types from main import dp @dp.message_handler() async def bot_echo(message: types.Message): await message.answer(message.text) |
Импортируем dp (Dispatcher) из main файла и переносим функцию эхо сюда. Заодно подтягиваем туда типы aiogram. Всё верно, но работать не будет. Мы же его нигде не вызываем и не импортируем. Так что вполне ожидаемо.
Вспоминаем про __init__.py, который находится в папке personal.
Туда пишем:
1 2 3 |
from .echo import dp __all__ = ["dp"] |
Возможно, не самая понятная штука. Разберём. Из файла echo мы импортируем dp, который импортировали из main. Так происходит вызов Диспетчера (Dispatcher) и связанного с ним контента в файле (регистрация handler эхо). Чтобы получить этот dp в handlers нам нужно сделать его публичным. В этом нам помогает __all__. По сути, __all__ определяет список публичных объектов данного модуля. И теперь мы сможем продолжить импорт dp из personal в папке handlers (файл init):
1 2 3 |
from .personal import dp __all__ = ["dp"] |
И в main.py заменяем:
1 2 |
if __name__ == '__main__': executor.start_polling(dp, skip_updates=True) |
На:
1 2 3 4 |
if __name__ == '__main__': from handlers import dp executor.start_polling(dp, skip_updates=True) |
Выходит, что после создания dp в main мы импортируем handlers, где этот же dp собирает прописанные инструкции.
Хух. Надеюсь понятно.
Можно запускать и тестировать.
Теперь бот будет спамить эхо в любом чате. Но папка personal намекает на ЛС. И в реализации этого нам помогут фильтра!
Фильтры в AIOGram!
По сути, мы уже их использовали. Так мы определяли, когда написали /start, а когда /help.
1 |
@dp.message_handler(commands=['start']) |
В aiogram есть уже встроенные полноценные фильтры для этих команд. Для первой статьи я их опустил, чтобы не грузить лишним, но вот настал их час. Рядом с echo.py добавим новый файлик: personal_start.py. И пишем:
1 2 3 4 5 6 7 8 9 |
from aiogram import types from aiogram.dispatcher.filters.builtin import CommandStart from main import dp @dp.message_handler(CommandStart()) async def bot_start(message: types.Message): await message.answer(f'Привет, {message.from_user.full_name}!') |
Импортируем фильтр CommandStart и передаём его как параметр для handler. Фильтров может быть сразу несколько.
Обязательно добавляем personal_start в init!
1 2 3 4 |
from .personal_start import dp from .echo import dp __all__ = ["dp"] |
И проверяем. Только не забудьте удалить аналогичный handler в main!
Кстати. Порядок имеет значение. Если вы сначала импортируете echo, то он перекроет personal_start. Работает логика порядка функций из первого гайда.
Но в группе команда старт всё ещё срабатывает, как и echo. А нам нужно только в ЛС. Но и для этого есть готовый фильтр:
1 |
ChatTypeFilter(chat_type=types.ChatType.PRIVATE) |
Теперь наши команды будут иметь такой вид:
1 2 3 4 5 6 7 8 9 |
from aiogram import types from aiogram.dispatcher.filters import ChatTypeFilter from main import dp @dp.message_handler(ChatTypeFilter(chat_type=types.ChatType.PRIVATE)) async def bot_echo(message: types.Message): await message.answer(message.text) |
1 2 3 4 5 6 7 8 9 |
from aiogram import types from aiogram.dispatcher.filters.builtin import CommandStart, ChatTypeFilter from main import dp @dp.message_handler(CommandStart(), ChatTypeFilter(chat_type=types.ChatType.PRIVATE)) async def bot_start(message: types.Message): await message.answer(f'Привет, {message.from_user.full_name}!') |
Добавляем start для группы
Для теста напишем отдельный start для групп. В папке groups создадим файлик group_start.py. Код там аналогичен старту в ЛС, но поправим текст:
1 2 3 4 5 6 7 8 9 |
from aiogram import types from aiogram.dispatcher.filters.builtin import CommandStart, ChatTypeFilter from main import dp @dp.message_handler(CommandStart(), ChatTypeFilter(chat_type=types.ChatType.SUPERGROUP)) async def bot_start(message: types.Message): await message.answer(f'Приветствую в нашей группе, {message.from_user.full_name}!') |
Потом не забываем прописать импорты из модулей в groups:
1 2 3 |
from .group_start import dp __all__ = ["dp"] |
И в handlers:
1 2 3 4 |
from .groups import dp from .personal import dp __all__ = ["dp"] |
Можно запускать!
В группе с друзьями (наш второй):
А в ЛС:
ВАЖНОЕ! Если вы добавили бота в группу, но у него нет доступа к сообщениям, то в BotFather отключите режим приватности. После его отключения бот должен начать реагировать. И само собой боту нужна админка.
На этом пока всё. В следующий раз предлагаю добавить свои фильтры, новые типы команд и что-то ещё…
Спасибо за внимание!