Telegram Боты на Aiogram 3.x: Доступ после подписки на каналы, получение Telegram ID и админка с рассылкой сообщений

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Приветствую, друзья!

В рамках цикла публикаций о разработке телеграм-ботов с использованием aiogram 3 я давно хотел осветить несколько ключевых тем: получение Telegram ID пользователей, групп и каналов, рассылка сообщений всем пользователям бота, вход в бота только по подписке на определенный канал или каналы, интеграция базы данных SQLite с помощью aiosqlite и деплой (удаленный запуск бота на сервере или хостинге). Сегодня мы закроем все эти вопросы.

Мы создадим бота с использованием вебхуков (о необходимости и способах их настройки я уже рассказывал в одной из предыдущих статей) и в конце статьи осуществим деплой на сервисе Amvera Cloud. Я выбрал этот сервис из-за бесплатного доменного имени с HTTPS-протоколом, которое автоматически выделяется и привязывается к созданному проекту, а также за простоту деплоя: достаточно загрузить файлы через GIT или внутренний интерфейс, сгенерировать простой файл с настройками прямо на сайте Amvera, и проект автоматически соберется и запустится. Подробности далее.

Зачем нам нужен ID пользователя, группы или канала?

Идентфификаторы Telegram-сущностей часто полезны в разработке ботов и user-ботов (подробнее об этом читайте ТУТ). Вот несколько сценариев, когда это может пригодиться:

  1. Персонализированные уведомления: Отправка сообщений конкретным пользователям или группам на основе их уникальных ID позволяет делать уведомления более персонализированными и целенаправленными.

  2. Целевая рассылка: Использование ID для целевой рассылки сообщений в определенные группы или каналы может быть полезным для продвижения контента или проведения опросов.

  3. Интеграция с внешними системами: Идентификаторы могут быть использованы для интеграции бота с внешними системами, например, CRM или системами аналитики, для отслеживания взаимодействий и результатов.

  4. Управление доступом: Если ваш бот предоставляет доступ к различным функциям или контенту, вы можете использовать ID для контроля доступа и управления правами пользователей.

  5. Аналитика и отчеты: ID позволяют собирать данные о том, как пользователи взаимодействуют с вашим ботом, что важно для анализа и оптимизации его работы.

Эти примеры показывают, насколько важны идентификаторы для эффективного управления и настройки функциональности ботов.

О преимуществах рассылки в боте, думаю, отдельно говорить не стоит, так как это и так очевидно.

В коде, который мы сегодня напишем, вы узнаете, как в Aiogram 3 можно легко создать одну функцию, которая закроет рассылку всех типов сообщений (фото, текст, видео и т.д.). Мы создадим универсальный модуль, который сможете использовать в любом телеграм-боте.

Также мы сделаем универсальным блок, который проверяет, подписан ли пользователь на определенные каналы, и только после проверки предоставляет доступ к функционалу бота.

Так как бот получится достаточно объемным и функциональным, сегодня я планирую сосредоточиться только на написании кода с подробными комментариями. Если вам будут непонятны какие-то части кода, пишите в комментариях здесь или в сообществе моего телеграм-канала «Легкий путь в Python». Там мы вместе с участниками разбираем трудные вопросы и просто общаемся.

Для лучшего понимания темы статьи рекомендую ознакомиться с другимив моими статьями по разработке телеграм-ботов на Python с использованием Aiogram 3. Статьи вы найдете в моем профиле на Хабре.

План на сегодня

Теперь кратко подведем итоги плана на сегодня и приступим к написанию кода:

  1. Создать бота на вебхуках, который будет выполнять следующие функции:

    • Проверка подписки на каналы с последующим открытием доступа к боту.

    • Получение своего ID, ID‑группы, ID‑канала, ID‑любого бота и т. д. через удобную клавиатуру.

    • Внутренняя админка с возможностью рассылки любого типа сообщений всем пользователям и отчетом по доставке.

    • Просмотр списка всех пользователей в админ-панели

  2. Интегрировать в бота базу данных SQLite

  3. Выполнить удаленный запуск бота (на деплой мы потратим около 5 минут, так что обязательно дочитайте до конца).

Подготовка

Для написания кода предлагаю открыть IDE и там создать отдельный проект под нового бота. Я буду использовать Pycharm.

Писать бота мы будем на вебхуках. Так что для удобства его локальной установки установите ngrok-v3. Подробную инструкцию как это сделать и зачем я давал в статье «Telegram Боты на Aiogram 3.x: Простой бот на вебхуках с локальным запуском и деплоем».

Кроме того, вам необходимо будет получить токен бота. Для этого воспользуйтесь телеграмм-ботом BotFather. Там выполните следующие действия:

  1. /newbot – команда для создания нового бота

  2. Пишите имя бота

  3. Придумываете ему логин (обязательно на английском языке и в конце bot в любом регистре).

Если все прошло корректно вы должны получить такой результат:

Кроме того, предлагаю сразу следовать моей структуре бота, которую я предложу далее. Она позволит сделать код структурированным и удобным для дальнейшей поддержки и понимания написанного в нем.

Структура проекта

tg_bot_project/: Корневая директория проекта.

  • keyboards/: Содержит файлы для создания клавиатур бота.

    • __init__.py: Инициализирует пакет.

    • kb.py: Определяет клавиатуры.

  • utils/: Утилитарные файлы и функции.

    • __init__.py: Инициализирует пакет.

    • db.py: Работа с базой данных.

    • utils.py: Вспомогательные функции.

  • handlers/: Обработчики различных команд и событий.

    • __init__.py: Инициализирует пакет.

    • start.py: Обработчик команды пользователя

    • admin_panel.py: Обработчик административных команд.

  • requirements.txt: Список зависимостей проекта.

  • env: Файл с конфиденциальными переменными окружения.

  • aiogram_run.py: Основной скрипт для запуска бота.

  • create_bot.py: Скрипт для создания и конфигурации бота.

Файл requirements.txt

aiogram==3.11.0
python-decouple==3.8
aiosqlite==0.20.0

Как вы видите, в файле зависимостей всего 3 библиотеки:

  • Aiogram 3.11.0 – модуль при помощи которого мы будем писать нашего асинхронного бота на вебхуках

  • Python-decouple – мой любимый модуль для удобно работы с конфиденциальными переменными окружения.

  • Aiosqlite – Библиотека для асинхронной работы с базой данных Aiosqlite  (для нашего бота ее будет более чем достаточно).

Для установки в терминале, с корня проекта, пишем:

pip install -r requirements.txt

Файл с конфиденциальными переменными окружения .env

BOT_TOKEN=00000:VVVZtSQ9_ToIKD8PmD83JHMnRuXJc3s
ADMIN_ID=5321351707
PORT=5000
HOST=0.0.0.0
BASE_URL=https://grok-url.gr

В примере выше я показал какие переменные окружения необходимы в текущем проекте.

  • BOT_TOKEN – это токен бота, который вы получили через BotFather.

  • ADMIN_ID – это ваш телеграмм айди, который будет использоваться далее для открытия доступа к админ-панели бота.

  • PORT, HOST и BASE_URL – это данные для подключения веб-хука бота. Для этапа разработки ссылку получим через NGROK.

О том как работать с NGROK и веб-хуками в телеграмм боте на aiogram 3 я подробно описывал в статье «Telegram Боты на Aiogram 3.x: Простой бот на вебхуках с локальным запуском и деплоем». На эту статью я сегодня часто буду ссылаться, так что настоятельно рекомендую ее прочитать.

Выглядит работающий NGROK на Windows так:

На примере выше вы видите, что порт использовался 5000-й. Порт в файле .env должен соответствовать порту, который вы указывали при запуске NGROK (подробно в статье про вебхуки, на которую уже ссылался ранее).

Опишем логику базы данных

Для работы бота нам будет необходима следующая логика:

  • Добавления пользователя в базу данных

  • Получение пользователя / пользователей с базы данных

  • Изменение статуса пользователя на Открыт доступ к боту (не подписан на каналы) / Закрыт доступ к боту (подписан на каналы)

Конечно, можно было бы создать отдельную логику для добавления каналов в базу данных, изменение и удаление их, но я решил сегодня не перегружать и каналы по подписке захардкодить.

Возможно, если будет запрос от подписчиков моего телеграмм канала «Легкий путь в Python», в качестве эксклюзива, опишу отдельно логику по работе с каналами через базу  данных.

Кроме того нам нужно будет создать базу данных SQLITE и создать в ней таблицу под пользователей (в логике сегодняшнего бота будет всего одна таблица).

Писать код я буду в файле utils/db.py

Напишем первую функцию.

import aiosqlite


async def initialize_database():
    # Подключаемся к базе данных (если база данных не существует, она будет создана)
    async with aiosqlite.connect("bot.db") as db:
        # Создаем таблицу users, если она не существует
        await db.execute("""
            CREATE TABLE IF NOT EXISTS users (
                telegram_id INTEGER PRIMARY KEY,
                username TEXT,
                first_name TEXT,
                bot_open BOOLEAN DEFAULT FALSE
            )
        """)
        # Сохраняем изменения
        await db.commit()

Как вы поняли по комментариям, которые я оставил в коде, данная функция при вызове выполняет 2 действия:

  1. Создает базу данных SQLITE если она еще не была создана

  2. Создает таблицу с пользователями если таблица не существовала

Для самого создания таблицы использовался простой SQL-запрос.

На этом примере, так же, вы можете увидеть как работает библиотека aiosqlite.

Во-первых, использует она асинхронный менеджер async with, создавая клиента для работы с базой данных. Клиента я назвал db.

Далее выполняется SQL запрос и для сохранения изменений вызывается db.commit()

В SQL-запросе для создания таблицы я использовал «IF NOT EXIST», чтоб избежать проблем при создании таблицы, если она существовала.

Такая конструкция кода позволит нам привязать функцию к запуску бота и, тем самым, первый запуск бота – поднимет нашу базу данных. Удобно.

В таблице будет всего четыре колонки:

  • telegram_id: целое уникальное число, колонка в которой будем хранить телеграмм айди пользователя

  • username: телеграм логин пользователя если есть (если нет то будет залетать None)

  • first_name: имя пользователя, просто для удобства будущего обращения

  • bot_open: булево значение. Если False, то доступ к боту закрыт (не подписан пользователь на канал / каналы), если True – доступ открыт.

Надеюсь, что понятно объяснил.

Теперь опишем необходимые нам методы.

Первый метод для добавления пользователя в базу данных:

async def add_user(telegram_id: int, username: str, first_name: str):
    async with aiosqlite.connect("bot.db") as db:
        await db.execute("""
            INSERT INTO users (telegram_id, username, first_name)
            VALUES (?, ?, ?)
            ON CONFLICT(telegram_id) DO NOTHING
        """, (telegram_id, username, first_name))
        await db.commit()

На вход получаем telegram_id, username и first_name.

Логику описал таким образом, чтоб если пользователь уже существовал в базе, то при конликте ничего не происходило «ON CONFLICT (telegram_id) DO NOTHING».

Обрати внимание на синтаксис. В отличие от psycong / asyncpg (работа с PostgreSQL), тут используется не $, а ?.

В остальном логика как в первом случае: открыли соединение, выполнили запрос и сохранили изменения.

Функция для получения всех пользователей с базы данных

async def get_all_users():
    async with aiosqlite.connect("bot.db") as db:
        cursor = await db.execute("SELECT * FROM users")
        rows = await cursor.fetchall()

        # Преобразуем результаты в список словарей
        users = [
            {
                "telegram_id": row[0],
                "username": row[1],
                "first_name": row[2],
                "bot_open": bool(row[3])
            }
            for row in rows
        ]
        return users

В данном случае мы сохранили результат запроса на получение всех пользователей с таблицы users в переменной cursor.

После мы получили все строки, а затем строки преобразовали в список питоновских словарей для удобства в дальнейшей работе с данными. Особо ничего примечательного.

Напишем похожую функцию, но для получения одного пользователя по его ID:

async def get_user_by_id(telegram_id: int):
    async with aiosqlite.connect("bot.db") as db:
        cursor = await db.execute("SELECT * FROM users WHERE telegram_id = ?", (telegram_id,))
        row = await cursor.fetchone()

        if row is None:
            return None

        # Преобразуем результат в словарь
        user = {
            "telegram_id": row[0],
            "username": row[1],
            "first_name": row[2],
            "bot_open": bool(row[3])
        }
        return user

Тут я немного изменил SQL запрос под получение конкретного пользователя по его telegram_id – "SELECT * FROM users WHERE telegram_id = ?".

Далее функция возвращает или None, если пользователя нет или словарь с данными по пользователю, если он есть в базе данных.

И напишем последнюю функцию под работу с базой данных для изменения статуса bot_open (подписан пользователь на канал / каналы или нет).

async def update_bot_open_status(telegram_id: int, bot_open: bool):
    async with aiosqlite.connect("bot.db") as db:
        await db.execute("""
            UPDATE users
            SET bot_open = ?
            WHERE telegram_id = ?
        """, (bot_open, telegram_id))
        await db.commit()

Принимает функция telegram_id пользователя и статус, который необходимо установить (True или False).

Этого кода нам будет достаточно для того, чтоб закрыть всю логику внутренней базы данных бота.

Теперь мы готовы к описанию самого бота.

Файл create_bot.py

В этом файле, я, как обычно, опишу стартовые настройки бота и вынесу переменные окружения в обычные переменные.

Импорты:

from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from decouple import config
from aiogram.fsm.storage.memory import MemoryStorage

Как вы видите, я использовал в качестве FSM MemoryStorage. Честно говоря, хотел еще и RedisStorage подкинуть, но решил что и так слишком много информации.

Подробно про FSM и, в частности, его реализацию через Redis я описывал в статье «Telegram Боты на Aiogram 3.x: Все про FSM простыми словами», там я описывал и почему MemoryStorage лучше не использовать в «боевых» проектах.

Bot и Dispatcher я выношу обычно в своих проекта в файл create_bot.py и тут без исплючений.

ParseMode используем чтоб по умолчанию обучить бота «понимать» HTML теги.

Config из decouple (python-decouple) будем использовать для работы с переменными окружения, что в файле .env.

MemoryStorage, как описал выше, будем использовать в качестве машины состояний на этапе рассылки сообщений пользователям бота.

Достанем значения из переменных окружения.

# переменные для работы
ADMIN_ID = int(config('ADMIN_ID'))
BOT_TOKEN = config("BOT_TOKEN")
HOST = config("HOST")
PORT = int(config("PORT"))
WEBHOOK_PATH = f'/{BOT_TOKEN}'
BASE_URL = config("BASE_URL")

 Инициализируем бота и диспетчера для работы с ними:

bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp = Dispatcher(storage=MemoryStorage())

Тут, обратите внимание, что в диспетчер мы передали MemoryStorage() – хранилище будующих состояний админа.

Кроме того, в этот файл я добавил и список каналов (в моем случае свой канал). Конечно, нужно было это дело добавлять в базу данных и описывать дополнительную логику, но я решил сэкономить ваше и своем время.

Список каналов для подписки

kb_list = [{'label': 'Легкий путь в Python', 'url': 'https://t.me/PythonPathMaster'}]

Каналы для подписки я представил в виде списка питоновских словарей с двумя ключами:

  • label – назавние канала (значение ключа будет использовано для надписи на инлайн-кнопке)

  • url – ссылка на канал

Такая структура позволит добавить неограниченное количество каналов в будующую клавиатуру и, совсем скоро, я покажу как мы это используем в нашем коде.

На этом с файлом create_bot.py – все.

Теперь к немного более сложному файлу – aiogram_run.py (основной файл бота или файл запуска).

Он немного сложнее, так как внутри будет описана логика веб-хуков (в основном большинстве своих публикаций я описывал логику через лонг-поллинг).

Файл aiogram_run.py

import logging
from aiogram.types import BotCommand, BotCommandScopeDefault
from aiohttp import web
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from create_bot import bot, dp, BASE_URL, WEBHOOK_PATH, HOST, PORT, ADMIN_ID
from utils.db import initialize_database
from handlers.start import router as start_router
from handlers.admin_panel import router as admin_router


# Функция для установки командного меню для бота
async def set_commands():
    # Создаем список команд, которые будут доступны пользователям
    commands = [BotCommand(command='start', description='Старт')]
    # Устанавливаем эти команды как дефолтные для всех пользователей
    await bot.set_my_commands(commands, BotCommandScopeDefault())


# Функция, которая будет вызвана при запуске бота
async def on_startup() -> None:
    # Устанавливаем командное меню
    await set_commands()

    # Создаем базу данных и таблицу с пользователями, если таблицы не было
    await initialize_database()

    # Устанавливаем вебхук для приема сообщений через заданный URL
    await bot.set_webhook(f"{BASE_URL}{WEBHOOK_PATH}")

    # Отправляем сообщение администратору о том, что бот был запущен
    await bot.send_message(chat_id=ADMIN_ID, text='Бот запущен!')


# Функция, которая будет вызвана при остановке бота
async def on_shutdown() -> None:
    # Отправляем сообщение администратору о том, что бот был остановлен
    await bot.send_message(chat_id=ADMIN_ID, text='Бот остановлен!')
    # Удаляем вебхук и, при необходимости, очищаем ожидающие обновления
    # await bot.delete_webhook(drop_pending_updates=True)
    # Закрываем сессию бота, освобождая ресурсы
    await bot.session.close()


# Основная функция, которая запускает приложение
def main() -> None:
    # Подключаем маршрутизатор (роутер) для обработки сообщений
    dp.include_router(start_router)
    dp.include_router(admin_router)

    # Регистрируем функцию, которая будет вызвана при старте бота
    dp.startup.register(on_startup)

    # Регистрируем функцию, которая будет вызвана при остановке бота
    dp.shutdown.register(on_shutdown)

    # Создаем веб-приложение на базе aiohttp
    app = web.Application()

    # Настраиваем обработчик запросов для работы с вебхуком
    webhook_requests_handler = SimpleRequestHandler(
        dispatcher=dp,  # Передаем диспетчер
        bot=bot  # Передаем объект бота
    )
    # Регистрируем обработчик запросов на определенном пути
    webhook_requests_handler.register(app, path=WEBHOOK_PATH)

    # Настраиваем приложение и связываем его с диспетчером и ботом
    setup_application(app, dp, bot=bot)

    # Запускаем веб-сервер на указанном хосте и порте
    web.run_app(app, host=HOST, port=PORT)


# Точка входа в программу
if __name__ == "__main__":
    # Настраиваем логирование (информация, предупреждения, ошибки) и выводим их в консоль
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    logger = logging.getLogger(__name__)  # Создаем логгер для использования в других частях программы
    main()  # Запускаем основную функцию

Подробно я описывал данную структуру файла в статье «Telegram Боты на Aiogram 3.x: Простой бот на вебхуках с локальным запуском и деплоем» сейчас же приведу только короткое его описание.

Тут суть в том, что запускать нашего бота будет мини-веб сервер aiohttp (встроенный модуль в aiogram 3). Для большего понимания я оставил комментарии в коде.

В остальном, отличий от поллинга нет.

Функция, которую мы будем вызывать сразу после запуска бота.

# Функция, которая будет вызвана при запуске бота
async def on_startup() -> None:
    # Устанавливаем командное меню
    await set_commands()
    # Создаем базу данных и таблицу с пользователями, если таблицы не было
    await initialize_database()
    # Устанавливаем вебхук для приема сообщений через заданный URL
    await bot.set_webhook(f"{BASE_URL}{WEBHOOK_PATH}")
    # Отправляем сообщение администратору о том, что бот был запущен
    await bot.send_message(chat_id=ADMIN_ID, text='Бот запущен!')

Вот тут началась наша магия. Данная функция вызывается сразу после запуска бота и вот что она делает:

  • Устанавливает команды в командное меню

  • Создает базу данных с таблицей если их ещё не было

  • Вешает вебхук, используя переменные из create_bot.py

  • Отправляет сообщение администратору

Удобно, так как всего парой строк, мы решили много вопросов. Сам код старался писать универсальным, чтоб его можно было переиспользовать и в других проектах.

Функция для запуска бота

def main() -> None:
    # Подключаем маршрутизатор (роутер) для обработки сообщений
    dp.include_router(start_router)
    dp.include_router(admin_router)

    # Регистрируем функцию, которая будет вызвана при старте бота
    dp.startup.register(on_startup)

    # Регистрируем функцию, которая будет вызвана при остановке бота
    dp.shutdown.register(on_shutdown)

    # Создаем веб-приложение на базе aiohttp
    app = web.Application()

    # Настраиваем обработчик запросов для работы с вебхуком
    webhook_requests_handler = SimpleRequestHandler(
        dispatcher=dp,  # Передаем диспетчер
        bot=bot  # Передаем объект бота
    )
    # Регистрируем обработчик запросов на определенном пути
    webhook_requests_handler.register(app, path=WEBHOOK_PATH)

    # Настраиваем приложение и связываем его с диспетчером и ботом
    setup_application(app, dp, bot=bot)

    # Запускаем веб-сервер на указанном хосте и порте
    web.run_app(app, host=HOST, port=PORT)

В этой функции мы включаем два роутера (админка и пользовательская часть), которые напием совсем скоро.

Затем регистрируем (по факту запускаем) функцию, которая будет вызвана при старте и завершении работы бота.

А далее создаем веб-приложение и, уже при его помощи, выполняем запуск бота. В целом, если читали мою статью про создание бота на вебхуках, проблем в понимании быть не должно.

Пользовательская часть бота

Теперь начнем писать пользовательскую часть.

Напоминаю, что в пользовательской части нам необходимо реализовать следующую логику:

  • Регистрация пользователя в базе данных, если он ещё не был зарегистрирован

  • Проверка подписки пользователя на канал / каналы с дальнейшим предоставлением доступа к боту

  • Логика получения телеграмм айди самого пользователя, других пользователей, группы, канала, любого бота и прочее.

Для осуществления этой логики мы уже выполнили некоторую подготовку, когда создали методы для работы с базой данных. Теперь нам необходимо создать специальную клавиатуру, которая будет возвращать телеграмм айди разных сущностей и метод, который будет проверять подписан ли пользователь на указанный канал. И, прежде чем мы приступим к написанию кода, давайте разберемся как это работает в телеграмм ботах, написанных на aiogram 3.

Как работает проверка подписки на канал

Для обычных ботов общий принцип сводится к тому, что бот должен быть участником того или иного канала. User-ботам не обязательно быть администратором канала, чтоб видеть аодписчиков, но это другая история.

Для того чтоб бот мог видеть всех участников канала – он должен являться администратором канала. То есть, для тестов необходимо сделать бота админом канала.

Теперь у нас появляется возможность использовать метод для проверки наличия пользователя в канале.

Для данного метода необходимо передать телеграмм айди пользователя и логин канала. Напишем код в файл utils/utils.py

from aiogram.enums import ContentType, ChatMemberStatus
from create_bot import bot


async def is_user_subscribed(channel_url: str, telegram_id: int) -> bool:
    try:
        # Получаем username канала из URL
        channel_username = channel_url.split('/')[-1]

        # Получаем информацию о пользователе в канале
        member = await bot.get_chat_member(chat_id=f"@{channel_username}", user_id=telegram_id)

        # Проверяем статус пользователя
        if member.status in [ChatMemberStatus.MEMBER, ChatMemberStatus.CREATOR, ChatMemberStatus.ADMINISTRATOR]:
            return True
        else:
            return False
    except Exception as e:
        # Если возникла ошибка (например, пользователь не найден или бот не имеет доступа к каналу)
        print(f"Ошибка при проверке подписки: {e}")
        return False

В рамках логики этого кода мы получаем логин телеграмм канала из его ссылки. В боевых проектах лучше в переменную chat_id подставлять телеграмм айди канала, тем более, сегодня мы научим нашего бота получать телеграмм айди вообще любого канала, группы и пользователя.

Для удобства проверки я импортировал из aiogram.enums объект ChatMemberStatus. Там, по сути, хранятся в строках возможные статусы пользователя (администратор, участник и так далее).

Для получения информации по пользователю в канале я использовал:

member = await bot.get_chat_member(chat_id=f"@{channel_username}", user_id=telegram_id)

Далее выполнил простую проверку, попадает ли статус пользователя в подходящие для проверки статусы: создатель канала, участник или администратор. Если попадает, то функция вернет True иначе False.

Как видите, логика достаточно простая.

Теперь в файле keyboards/kb.py напишем клавиатуру, которая будет возвращать каналы для подписки и кнопку «Проверить подписку».

def channels_kb(kb_list: list):
    inline_keyboard = []

    for channel_data in kb_list:
        label = channel_data.get('label')
        url = channel_data.get('url')

        # Проверка на наличие необходимых ключей
        if label and url:
            kb = [InlineKeyboardButton(text=label, url=url)]
            inline_keyboard.append(kb)

    # Добавление кнопки "Проверить подписку"
    inline_keyboard.append([InlineKeyboardButton(text="Проверить подписку", callback_data="check_subscription")])

    return InlineKeyboardMarkup(inline_keyboard=inline_keyboard)

Принимает клавиатура список из каналов (описывали этот список в файле create_bot.py).

Тут я использовал обычный цикл FOR, чтоб пробежаться по списку. Благодаря этому нам не важно сколько каналов в списке – все они попадут в финальную клавиатуру.

В конце, независимо от количества каналов, добавим кнопку «Проверить подписку». Она уже будет содержать, не ссылку на канал, а callback_data «check_subscription». Далее мы напишем обработчик этих данных.

На этом этапе с подготовкой все и мы можем начать писать логику пользовательской части бота.

Файл handlers/start.py

Тут нас будет интересовать функция, которая будет обрабатывать команду /start. Она должна будет выполнить следующую логику:

  1. Проверит есть ли пользователь в базе данных (если нет, то выполнит регистрацию)

  2. Получим статус пользователя с базы данных по подпискам (у новых пользователей статус False). Тут смысл в том, чтоб не давать доступа к основному функционалу бота, если пользователь не подписан на все каналы. У нового пользователя этот статус автоматически False

Далее мы или даем доступ к основному функционалу бота (чуть позже напишем) или отправляем клавиатуру с подпиской на каналы.

from aiogram import Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message, CallbackQuery
from create_bot import kb_list
from utils.db import get_user_by_id, add_user, update_bot_open_status
from keyboards.kb import main_contact_kb, channels_kb
from utils.utils import is_user_subscribed

router = Router()
@router.message(CommandStart())
async def start(message: Message):
    telegram_id = message.from_user.id
    user_data = await get_user_by_id(telegram_id)

    if user_data is None:
        # Если пользователь не найден, добавляем его в базу данных
        await add_user(
            telegram_id=telegram_id,
            username=message.from_user.username,
            first_name=message.from_user.first_name
        )
        bot_open = False
    else:
        # Получаем статус bot_open для пользователя
        bot_open = user_data.get('bot_open', False)  # Второй параметр по умолчанию False

    if bot_open:
        # Если пользователь подписался на все каналы
        await message.answer("Тут основная логика бота, чуть позже напишем")
    else:
        # Иначе показываем клавиатуру с каналами для подписки
        await message.answer(
            "Для пользования ботом необходимо подписаться на следующие каналы:",
            reply_markup=channels_kb(kb_list)

Тут обратите внимание на импорты. В файл start.py необходимо импортировать функции для работы с базой данных, утилиты и функции, которые генерируют наши клавиатуры.

Теперь выполним первый запуск бота.

Нас встретила инлайн клавиатура с одним каналом и кнопкой «Проверить подписку». Опишем логику проверки подписки на канал / каналы.

@router.callback_query(F.data == 'check_subscription')
async def check_subs_func(call: CallbackQuery):
    await call.answer('Запускаю проверку подписок на каналы')

    for channel in kb_list:
        label = channel.get('label')
        channel_url = channel.get('url')
        telegram_id = call.from_user.id
        check = await is_user_subscribed(channel_url, telegram_id)
        if check is False:
            await call.message.answer(f"❌ вы не подписались на канал 						
Источник: https://habr.com/ru/companies/amvera/articles/839654/


Интересные статьи

Интересные статьи

Сегодня ровно 20 лет, как я начал программировать профессионально. За эти годы я:- Получил одобрение на петицию по грин‑карте за выдающиеся способности в науке.- Стал Goo...
Сигнал — это значение, которое является «реактивным», то есть оно может уведомлять заинтересованных потребителей об изменении. Существует много разных реализаций этой концепции с разными схемами того,...
Всем привет! Да-да тема не очень актуальна и на просторах habr-a есть очень много статей про то, как написать простейшего telegram bota, но я все-таки рискну написать еще одну. Может кому-то она будет...
Решение по созданию недорогого варианта для открывания шлагбаума. После нескольких месяцев тестирования в посёлке были выявлены проблемы, исправлены ошибки. Статья повторяет первую, но основана на пра...
Если раньше удаленный формат работы был просто трендом, то сейчас стал необходимостью. И многие из-за принудительного режима самоизоляции столкнулись с проблемой доступа к файлам и приложения...