diff --git a/app/bot.py b/app/bot.py index 025551cc..428015cf 100644 --- a/app/bot.py +++ b/app/bot.py @@ -61,6 +61,7 @@ from app.handlers.admin import ( ) from app.handlers.stars_payments import register_stars_handlers from app.middlewares.auth import AuthMiddleware +from app.middlewares.blacklist import BlacklistMiddleware from app.middlewares.button_stats import ButtonStatsMiddleware from app.middlewares.display_name_restriction import DisplayNameRestrictionMiddleware from app.middlewares.global_error import GlobalErrorMiddleware @@ -119,6 +120,10 @@ async def setup_bot() -> tuple[Bot, Dispatcher]: dp.callback_query.middleware(LoggingMiddleware()) dp.message.middleware(MaintenanceMiddleware()) dp.callback_query.middleware(MaintenanceMiddleware()) + blacklist_middleware = BlacklistMiddleware() + dp.message.middleware(blacklist_middleware) + dp.callback_query.middleware(blacklist_middleware) + dp.pre_checkout_query.middleware(blacklist_middleware) display_name_middleware = DisplayNameRestrictionMiddleware() dp.message.middleware(display_name_middleware) dp.callback_query.middleware(display_name_middleware) diff --git a/app/cabinet/dependencies.py b/app/cabinet/dependencies.py index aa718529..73bdcc08 100644 --- a/app/cabinet/dependencies.py +++ b/app/cabinet/dependencies.py @@ -12,6 +12,7 @@ from app.config import settings from app.database.crud.user import get_user_by_id from app.database.database import AsyncSessionLocal from app.database.models import User +from app.services.blacklist_service import blacklist_service from app.services.maintenance_service import maintenance_service from .auth.jwt_handler import get_token_payload @@ -104,6 +105,18 @@ async def get_current_cabinet_user( detail='User account is not active', ) + # Check blacklist + if user.telegram_id is not None: + is_blacklisted, reason = await blacklist_service.is_user_blacklisted(user.telegram_id, user.username) + if is_blacklisted: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={ + 'code': 'blacklisted', + 'message': reason or 'Доступ запрещен', + }, + ) + # Check maintenance mode (allow admins to pass) if maintenance_service.is_maintenance_active(): # Проверяем админа по telegram_id ИЛИ email diff --git a/app/handlers/balance/cryptobot.py b/app/handlers/balance/cryptobot.py index 1af27f9f..6e7b4dab 100644 --- a/app/handlers/balance/cryptobot.py +++ b/app/handlers/balance/cryptobot.py @@ -8,7 +8,6 @@ from app.config import settings from app.database.models import User from app.keyboards.inline import get_back_keyboard from app.localization.texts import get_texts -from app.services.blacklist_service import blacklist_service from app.services.payment_service import PaymentService from app.states import BalanceStates from app.utils.decorators import error_handler @@ -126,23 +125,6 @@ async def process_cryptobot_payment_amount( await state.clear() return - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Оплата невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) if not settings.is_cryptobot_enabled(): diff --git a/app/handlers/balance/stars.py b/app/handlers/balance/stars.py index 4b4df7f6..6d0135f0 100644 --- a/app/handlers/balance/stars.py +++ b/app/handlers/balance/stars.py @@ -8,7 +8,6 @@ from app.database.models import User from app.external.telegram_stars import TelegramStarsService from app.keyboards.inline import get_back_keyboard from app.localization.texts import get_texts -from app.services.blacklist_service import blacklist_service from app.services.payment_service import PaymentService from app.states import BalanceStates from app.utils.decorators import error_handler @@ -94,23 +93,6 @@ async def process_stars_payment_amount(message: types.Message, db_user: User, am await state.clear() return - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Оплата невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) if not settings.TELEGRAM_STARS_ENABLED: diff --git a/app/handlers/balance/yookassa.py b/app/handlers/balance/yookassa.py index 2f6d1034..7ae59386 100644 --- a/app/handlers/balance/yookassa.py +++ b/app/handlers/balance/yookassa.py @@ -10,7 +10,6 @@ from app.config import settings from app.database.models import User from app.keyboards.inline import get_back_keyboard from app.localization.texts import get_texts -from app.services.blacklist_service import blacklist_service from app.services.payment_service import PaymentService from app.states import BalanceStates from app.utils.decorators import error_handler @@ -171,23 +170,6 @@ async def process_yookassa_payment_amount( await state.clear() return - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Оплата невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) if not settings.is_yookassa_enabled(): @@ -338,23 +320,6 @@ async def process_yookassa_sbp_payment_amount( await state.clear() return - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Оплата невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) if not settings.is_yookassa_enabled() or not settings.YOOKASSA_SBP_ENABLED: diff --git a/app/handlers/promocode.py b/app/handlers/promocode.py index 1f97de06..da6fbfbb 100644 --- a/app/handlers/promocode.py +++ b/app/handlers/promocode.py @@ -9,7 +9,6 @@ from app.database.models import User from app.keyboards.inline import get_back_keyboard from app.localization.texts import get_texts from app.services.admin_notification_service import AdminNotificationService -from app.services.blacklist_service import blacklist_service from app.services.promocode_service import PromoCodeService from app.states import PromoCodeStates from app.utils.decorators import error_handler @@ -71,23 +70,6 @@ async def activate_promocode_for_registration(db: AsyncSession, user_id: int, co @error_handler async def process_promocode(message: types.Message, db_user: User, state: FSMContext, db: AsyncSession): - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Активация промокода невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) code = message.text.strip() diff --git a/app/handlers/start.py b/app/handlers/start.py index f3189fde..27602a00 100644 --- a/app/handlers/start.py +++ b/app/handlers/start.py @@ -35,7 +35,6 @@ from app.middlewares.channel_checker import ( get_pending_payload_from_redis, ) from app.services.admin_notification_service import AdminNotificationService -from app.services.blacklist_service import blacklist_service from app.services.campaign_service import AdvertisingCampaignService from app.services.main_menu_button_service import MainMenuButtonService from app.services.pinned_message_service import ( @@ -993,25 +992,6 @@ async def process_referral_code_skip(callback: types.CallbackQuery, state: FSMCo async def complete_registration_from_callback(callback: types.CallbackQuery, state: FSMContext, db: AsyncSession): logger.info(f'🎯 COMPLETE: Завершение регистрации для пользователя {callback.from_user.id}') - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - callback.from_user.id, callback.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await callback.message.answer( - f'🚫 Регистрация невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - - await state.clear() - return - existing_user = await get_user_by_telegram_id(db, callback.from_user.id) if existing_user and existing_user.status == UserStatus.ACTIVE.value: @@ -1262,25 +1242,6 @@ async def complete_registration_from_callback(callback: types.CallbackQuery, sta async def complete_registration(message: types.Message, state: FSMContext, db: AsyncSession): logger.info(f'🎯 COMPLETE: Завершение регистрации для пользователя {message.from_user.id}') - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - message.from_user.id, message.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await message.answer( - f'🚫 Регистрация невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.' - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - - await state.clear() - return - existing_user = await get_user_by_telegram_id(db, message.from_user.id) if existing_user and existing_user.status == UserStatus.ACTIVE.value: diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index 550ce71a..8f5aa6cf 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -39,7 +39,6 @@ from app.keyboards.inline import ( ) from app.localization.texts import get_texts from app.services.admin_notification_service import AdminNotificationService -from app.services.blacklist_service import blacklist_service from app.services.remnawave_service import RemnaWaveConfigurationError from app.services.subscription_checkout_service import ( clear_subscription_checkout_draft, @@ -1724,24 +1723,6 @@ async def handle_extend_subscription(callback: types.CallbackQuery, db_user: Use async def confirm_extend_subscription(callback: types.CallbackQuery, db_user: User, db: AsyncSession): - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - callback.from_user.id, callback.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await callback.answer( - f'🚫 Продление подписки невозможно\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.', - show_alert=True, - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - days = int(callback.data.split('_')[2]) texts = get_texts(db_user.language) @@ -2228,24 +2209,6 @@ async def devices_continue(callback: types.CallbackQuery, state: FSMContext, db_ async def confirm_purchase(callback: types.CallbackQuery, state: FSMContext, db_user: User, db: AsyncSession): - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - callback.from_user.id, callback.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await callback.answer( - f'🚫 Покупка подписки невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.', - show_alert=True, - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - # Проверка ограничения на покупку/продление подписки if getattr(db_user, 'restriction_subscription', False): reason = getattr(db_user, 'restriction_reason', None) or 'Действие ограничено администратором' @@ -4134,24 +4097,6 @@ async def handle_simple_subscription_purchase( db: AsyncSession, ): """Обрабатывает простую покупку подписки.""" - # Проверяем, находится ли пользователь в черном списке - is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted( - callback.from_user.id, callback.from_user.username - ) - - if is_blacklisted: - logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}') - try: - await callback.answer( - f'🚫 Простая покупка подписки невозможна\n\n' - f'Причина: {blacklist_reason}\n\n' - f'Если вы считаете, что это ошибка, обратитесь в поддержку.', - show_alert=True, - ) - except Exception as e: - logger.error(f'Ошибка при отправке сообщения о блокировке: {e}') - return - texts = get_texts(db_user.language) if not settings.SIMPLE_SUBSCRIPTION_ENABLED: diff --git a/app/middlewares/blacklist.py b/app/middlewares/blacklist.py new file mode 100644 index 00000000..25215247 --- /dev/null +++ b/app/middlewares/blacklist.py @@ -0,0 +1,49 @@ +import logging +from collections.abc import Awaitable, Callable +from typing import Any + +from aiogram import BaseMiddleware +from aiogram.types import CallbackQuery, Message, PreCheckoutQuery, TelegramObject, User as TgUser + +from app.services.blacklist_service import blacklist_service + + +logger = logging.getLogger(__name__) + + +class BlacklistMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: dict[str, Any], + ) -> Any: + user: TgUser | None = None + if isinstance(event, (Message, CallbackQuery, PreCheckoutQuery)): + user = event.from_user + + if not user or user.is_bot: + return await handler(event, data) + + is_blacklisted, reason = await blacklist_service.is_user_blacklisted(user.id, user.username) + + if not is_blacklisted: + return await handler(event, data) + + logger.warning(f'🚫 Пользователь {user.id} (@{user.username}) из черного списка: {reason}') + + block_text = ( + f'🚫 Доступ запрещен\n\nПричина: {reason}\n\nЕсли вы считаете, что это ошибка, обратитесь в поддержку.' + ) + + try: + if isinstance(event, Message): + await event.answer(block_text) + elif isinstance(event, CallbackQuery): + await event.answer(block_text, show_alert=True) + elif isinstance(event, PreCheckoutQuery): + await event.answer(ok=False, error_message='Доступ запрещен') + except Exception as e: + logger.error(f'Ошибка отправки сообщения о блокировке пользователю {user.id}: {e}') + + return None diff --git a/app/services/blacklist_service.py b/app/services/blacklist_service.py index aec7db7d..5b6458dd 100644 --- a/app/services/blacklist_service.py +++ b/app/services/blacklist_service.py @@ -160,8 +160,9 @@ class BlacklistService: # Проверяем по username, если он передан if username: + username_lower = username.lower().lstrip('@') for bl_id, bl_username, bl_reason in self.blacklist_data: - if bl_username and (bl_username == username or bl_username == f'@{username}'): + if bl_username and bl_username.lower().lstrip('@') == username_lower: logger.info( f'Пользователь {username} ({telegram_id}) найден в черном списке по username: {bl_reason}' )