Files
balance_bot/bot/middlewares/subscription_mdw.py
2026-01-23 04:45:55 +07:00

111 lines
4.8 KiB
Python

from typing import Callable, Awaitable, Any, Dict
from aiogram import BaseMiddleware, Bot
from aiogram.exceptions import TelegramBadRequest
from aiogram.types import TelegramObject, Message, CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from middleware.loggers import logger
class SubscriptionMiddleware(BaseMiddleware):
"""
Middleware для проверки подписки пользователя на необходимые каналы.
Блокирует обработку команд, если пользователь не подписан.
Зачем нужен:
- Автоматическая проверка подписки для всех входящих сообщений
- Единая точка управления подписками
- Предотвращение доступа к функционалу без подписки
"""
def __init__(self, bot: Bot, channel_ids: list[int | str]):
"""
Инициализация middleware проверки подписки.
Args:
bot: Экземпляр бота
channel_ids: Список ID каналов/чатов для проверки подписки
"""
self.bot = bot
self.channel_ids = channel_ids
super().__init__()
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any]
) -> Any:
"""
Проверяет подписку пользователя перед обработкой команды.
"""
# Пропускаем не-сообщения и не-колбэки
if not isinstance(event, (Message, CallbackQuery)):
return await handler(event, data)
user_id: int = event.from_user.id
user_str: str = f"@{event.from_user.username}" if event.from_user.username else f"id{user_id}"
# Логируем начало проверки подписки
logger.info(
text=f"Проверка подписки для пользователя",
log_type="SUBSCRIPTION_CHECK",
user=user_str
)
# Проверяем подписку на все required каналы
not_subscribed_channels: list[str] = []
for channel_id in self.channel_ids:
try:
member = await self.bot.get_chat_member(
chat_id=channel_id,
user_id=user_id
)
# Проверяем, что пользователь является участником
if member.status not in ['member', 'administrator', 'creator']:
not_subscribed_channels.append(str(channel_id))
except TelegramBadRequest as e:
logger.error(
text=f"Ошибка проверки подписки на канал {channel_id}: {e}",
log_type="SUBSCRIPTION_ERROR",
user=user_str
)
# Если пользователь не подписан на некоторые каналы
if not_subscribed_channels:
logger.warning(
text=f"Пользователь не подписан на каналы: {', '.join(not_subscribed_channels)}",
log_type="SUBSCRIPTION_FAILED",
user=user_str
)
warning_text: str = (
"📢 Для использования бота необходимо подписаться на наши каналы!\n\n"
"После подписки нажмите /start для продолжения."
)
# Создаем кнопку "Проверить подписку"
ikb: InlineKeyboardBuilder = InlineKeyboardBuilder()
ikb.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
if isinstance(event, Message):
await event.answer(warning_text, reply_markup=ikb.as_markup())
elif isinstance(event, CallbackQuery):
await event.message.answer(warning_text, reply_markup=ikb.as_markup())
await event.answer()
return None
# Логируем успешную проверку подписки
logger.info(
text="Пользователь подписан на все required каналы",
log_type="SUBSCRIPTION_SUCCESS",
user=user_str
)
# Если подписка есть, продолжаем обработку
return await handler(event, data)