diff --git a/core/app.cpython-312-x86_64-linux-gnu.so b/core/app.cpython-312-x86_64-linux-gnu.so index c1e0a88b..ad106863 100644 Binary files a/core/app.cpython-312-x86_64-linux-gnu.so and b/core/app.cpython-312-x86_64-linux-gnu.so differ diff --git a/database/payments.py b/database/payments.py index c32ec5e9..07c4b44a 100644 --- a/database/payments.py +++ b/database/payments.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -from typing import Optional from pytz import timezone from sqlalchemy import and_, insert, select, update @@ -91,11 +90,20 @@ async def get_payment_by_id(session: AsyncSession, internal_id: int) -> dict | N try: result = await session.execute(select(Payment).where(Payment.id == internal_id).limit(1)) payment = result.scalar_one_or_none() - if payment: - logger.info(f"Найден платёж id={internal_id}") - return dict(payment.__dict__) - logger.info(f"Платёж id={internal_id} не найден") - return None + if not payment: + return None + return { + "id": payment.id, + "tg_id": payment.tg_id, + "amount": payment.amount, + "currency": payment.currency, + "status": payment.status, + "payment_system": payment.payment_system, + "payment_id": payment.payment_id, + "created_at": payment.created_at, + "metadata": payment.metadata_, + "original_amount": payment.original_amount, + } except SQLAlchemyError as e: logger.error(f"Ошибка при поиске платежа id={internal_id}: {e}") return None @@ -137,11 +145,20 @@ async def get_payment_by_payment_id(session: AsyncSession, pid: str) -> dict | N try: result = await session.execute(select(Payment).where(Payment.payment_id == pid).limit(1)) payment = result.scalar_one_or_none() - if payment: - logger.info(f"Найден платёж payment_id={pid}") - return dict(payment.__dict__) - logger.info(f"Платёж payment_id={pid} не найден") - return None + if not payment: + return None + return { + "id": payment.id, + "tg_id": payment.tg_id, + "amount": payment.amount, + "currency": payment.currency, + "status": payment.status, + "payment_system": payment.payment_system, + "payment_id": payment.payment_id, + "created_at": payment.created_at, + "metadata": payment.metadata_, + "original_amount": payment.original_amount, + } except SQLAlchemyError as e: logger.error(f"Ошибка при поиске платежа payment_id={pid}: {e}") return None @@ -158,8 +175,8 @@ async def cancel_expired_pending_payments(session: AsyncSession) -> int: ) ) .values(status="cancelled") - .returning(Payment.id) ) res = await session.execute(stmt) await session.commit() - return len(res.fetchall()) + affected = res.rowcount or 0 + return affected diff --git a/handlers/start.py b/handlers/start.py index d8b02139..2dab4bec 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,4 +1,3 @@ -import asyncio import os from typing import Any @@ -70,6 +69,17 @@ router = Router() processing_gifts = set() +async def get_or_load_user_snapshot( + session: AsyncSession, + cached_snapshot: tuple[int, int] | None, + tg_id: int, +) -> tuple[int, int] | None: + """Возвращает снапшот пользователя, используя кеш если есть.""" + if cached_snapshot is not None: + return cached_snapshot + return await get_user_snapshot(session, tg_id) + + @router.message(Command("start")) @router.callback_query(F.data == "start") async def start_entry( @@ -81,10 +91,12 @@ async def start_entry( ): message = event.message if isinstance(event, CallbackQuery) else event + user_snapshot = None + captcha_enabled = bool(MODES_CONFIG.get("CAPTCHA_ENABLED", CAPTCHA_ENABLE)) if captcha_enabled and captcha: - exists = await get_user_snapshot(session, message.chat.id) - if exists is None: + user_snapshot = await get_user_snapshot(session, message.chat.id) + if user_snapshot is None: captcha_data = await generate_captcha(message, state) await edit_or_send_message(message, captcha_data["text"], reply_markup=captcha_data["markup"]) return @@ -95,7 +107,7 @@ async def start_entry( if isinstance(event, CallbackQuery): user_data = extract_user_data(event.from_user) - await process_start_logic(message, state, session, admin, text, user_data) + await process_start_logic(message, state, session, admin, text, user_data, user_snapshot=user_snapshot) @router.callback_query(F.data == "check_subscription") @@ -122,13 +134,25 @@ async def process_start_logic( state: FSMContext, session: Any, admin: bool, - text_to_process: str = None, + text_to_process: str | None = None, user_data: dict | None = None, + user_snapshot: tuple[int, int] | None = None, ): user_data = user_data or extract_user_data(message.from_user or message.chat) text = text_to_process or message.text or message.caption + + if text == "/start": + await add_user(session=session, **user_data) + trial_key = await get_or_load_user_snapshot(session, user_snapshot, user_data["tg_id"]) + trial = 0 + key_count = 0 + if trial_key is not None: + trial, key_count = trial_key + await show_start_menu(message, admin, session, trial=trial, key_count=key_count) + return + if not text: - trial_key = await get_user_snapshot(session, user_data["tg_id"]) + trial_key = await get_or_load_user_snapshot(session, user_snapshot, user_data["tg_id"]) trial = 0 key_count = 0 if trial_key is not None: @@ -182,7 +206,7 @@ async def process_start_logic( await send_instructions(message) return - trial_key = await get_user_snapshot(session, user_data["tg_id"]) + trial_key = await get_or_load_user_snapshot(session, user_snapshot, user_data["tg_id"]) trial = 0 key_count = 0 if trial_key is not None: @@ -308,7 +332,7 @@ async def show_start_menu( module_buttons = await run_hooks("start_menu", chat_id=message.chat.id, session=session) kb = insert_hook_buttons(kb, module_buttons) except Exception as e: - logger.error(f"[Hooks:start_menu] Ошибка вставки кнопок: {e}") + logger.error(f"[Hooks:start_menu] Ошибка вставки кнопок: {e}", exc_info=True) kb.row(InlineKeyboardButton(text=ABOUT_VPN, callback_data="about_vpn")) diff --git a/hooks/constants.py b/hooks/constants.py new file mode 100644 index 00000000..c59d449e --- /dev/null +++ b/hooks/constants.py @@ -0,0 +1 @@ +DEFAULT_HOOK_TIMEOUT = 2.0 \ No newline at end of file diff --git a/hooks/hooks.py b/hooks/hooks.py index 6668fe59..40a7cbb9 100644 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -1,3 +1,4 @@ +import asyncio import inspect from collections.abc import Callable @@ -5,6 +6,8 @@ from typing import Any from logger import logger +from .constants import DEFAULT_HOOK_TIMEOUT + _hooks: dict[str, list[tuple[Callable[..., Any], str | None]]] = {} @@ -40,23 +43,35 @@ def unregister_module_hooks(module_name: str): async def run_hooks(name: str, require_enabled: bool = True, **kwargs) -> list[Any]: + """Вызывает зарегистрированные хуки и собирает результаты.""" results: list[Any] = [] for func, owner in _hooks.get(name, []): if require_enabled and owner: try: from utils.modules_manager import manager - if not manager.is_enabled(owner): continue except Exception: pass try: if inspect.iscoroutinefunction(func): - result = await func(**kwargs) + coro = func(**kwargs) else: - result = func(**kwargs) + async def _run_sync(): + return func(**kwargs) + coro = _run_sync() + + result = await asyncio.wait_for(coro, timeout=DEFAULT_HOOK_TIMEOUT) if result: results.append(result) + except asyncio.TimeoutError: + logger.error( + f"[HOOK:{name}] Таймаут в {getattr(func, '__name__', func)} при timeout={DEFAULT_HOOK_TIMEOUT}", + exc_info=True, + ) except Exception as e: - logger.error(f"[HOOK:{name}] Ошибка в {getattr(func, '__name__', func)}: {e}") - return results + logger.error( + f"[HOOK:{name}] Ошибка в {getattr(func, '__name__', func)}: {e}", + exc_info=True, + ) + return results \ No newline at end of file diff --git a/hooks/processors.py b/hooks/processors.py index 69cf1fa4..3b7a19c1 100644 --- a/hooks/processors.py +++ b/hooks/processors.py @@ -12,24 +12,16 @@ async def process_cluster_override( plan: int | None = None, **kwargs, ) -> str | None: - """ - Обрабатывает хук cluster_override. - - Возвращает название кластера для принудительного выбора или None. - """ - try: - results = await run_hooks( - "cluster_override", - tg_id=tg_id, - state_data=state_data, - session=session, - plan=plan, - **kwargs, - ) - return results[0] if results and results[0] else None - except Exception as e: - logger.warning(f"[CLUSTER_OVERRIDE] Ошибка при обработке хука: {e}") - return None + """Обрабатывает хук cluster_override и возвращает название кластера.""" + results = await run_hooks( + "cluster_override", + tg_id=tg_id, + state_data=state_data, + session=session, + plan=plan, + **kwargs, + ) + return results[0] if results and results[0] else None async def process_cluster_balancer( @@ -37,22 +29,14 @@ async def process_cluster_balancer( session: Any, **kwargs, ) -> dict | None: - """ - Обрабатывает хук cluster_balancer. - - Возвращает отфильтрованный словарь кластеров или None (использовать исходный). - """ - try: - results = await run_hooks( - "cluster_balancer", - available_clusters=available_clusters, - session=session, - **kwargs, - ) - return results[0] if results and results[0] else None - except Exception as e: - logger.warning(f"[CLUSTER_BALANCER] Ошибка при обработке хука: {e}") - return None + """Обрабатывает хук cluster_balancer и возвращает отфильтрованные кластеры.""" + results = await run_hooks( + "cluster_balancer", + available_clusters=available_clusters, + session=session, + **kwargs, + ) + return results[0] if results and results[0] else None async def process_remnawave_webapp_override( @@ -61,36 +45,28 @@ async def process_remnawave_webapp_override( session: Any, **kwargs, ) -> bool: - """ - Обрабатывает хук remnawave_webapp_override. - - Возвращает bool - использовать ли webapp для подключения устройства. - """ + """Обрабатывает хук remnawave_webapp_override и решает, использовать ли webapp.""" if not remnawave_webapp or not final_link: return remnawave_webapp - try: - results = await run_hooks( - "remnawave_webapp_override", - remnawave_webapp=remnawave_webapp, - final_link=final_link, - session=session, - **kwargs, - ) - if not results: - return remnawave_webapp - - for result in results: - if result is True or result is False: - return result - elif isinstance(result, dict) and "override" in result: - return result["override"] - - return remnawave_webapp - except Exception as e: - logger.warning(f"[REMNAWAVE_WEBAPP_OVERRIDE] Ошибка при обработке хука: {e}") + results = await run_hooks( + "remnawave_webapp_override", + remnawave_webapp=remnawave_webapp, + final_link=final_link, + session=session, + **kwargs, + ) + if not results: return remnawave_webapp + for result in results: + if result is True or result is False: + return result + if isinstance(result, dict) and "override" in result: + return result["override"] + + return remnawave_webapp + async def process_happ_cryptolink_override( cluster_id: str | None, @@ -101,34 +77,26 @@ async def process_happ_cryptolink_override( happ_cryptolink: bool = False, **kwargs, ) -> bool: - """ - Обрабатывает хук happ_cryptolink_override. - - Возвращает bool - использовать ли криптоссылку для подписки. - """ - try: - results = await run_hooks( - "happ_cryptolink_override", - cluster_id=cluster_id, - plan=plan, - session=session, - email=email, - tg_id=tg_id, - happ_cryptolink=happ_cryptolink, - **kwargs, - ) - if not results: - return happ_cryptolink - - for result in results: - if result is True or result is False: - return result - - return happ_cryptolink - except Exception as e: - logger.warning(f"[HAPP_CRYPTOLINK_OVERRIDE] Ошибка при обработке хука: {e}") + """Обрабатывает хук happ_cryptolink_override и решает, использовать ли криптоссылку.""" + results = await run_hooks( + "happ_cryptolink_override", + cluster_id=cluster_id, + plan=plan, + session=session, + email=email, + tg_id=tg_id, + happ_cryptolink=happ_cryptolink, + **kwargs, + ) + if not results: return happ_cryptolink + for result in results: + if result is True or result is False: + return result + + return happ_cryptolink + async def process_extract_cryptolink_from_result( result: dict, @@ -140,11 +108,7 @@ async def process_extract_cryptolink_from_result( need_vless_key: bool = False, **kwargs, ) -> str | None: - """ - Обрабатывает хук happ_cryptolink_override и извлекает криптоссылку из результата API. - - Возвращает криптоссылку если нужно использовать, иначе None. - """ + """Извлекает криптоссылку из результата API с учётом хуков.""" if need_vless_key: return None @@ -187,11 +151,7 @@ async def process_get_cryptolink_after_renewal( remnawave_nodes: list | None = None, **kwargs, ) -> str | None: - """ - Получает свежие данные подписки после продления и извлекает криптоссылку если нужно. - - Возвращает криптоссылку если хук требует её использования, иначе None. - """ + """Получает криптоссылку из Remnawave после продления подписки.""" if not remnawave_nodes: return None @@ -236,23 +196,15 @@ async def process_intercept_key_creation_message( target_message: Any, **kwargs, ) -> bool: - """ - Обрабатывает хук intercept_key_creation_message. - - Возвращает True если нужно прервать выполнение (перехватить сообщение). - """ - try: - results = await run_hooks( - "intercept_key_creation_message", - chat_id=chat_id, - session=session, - target_message=target_message, - **kwargs, - ) - return bool(results and results[0]) - except Exception as e: - logger.warning(f"[INTERCEPT_KEY_CREATION] Ошибка при обработке хука: {e}") - return False + """Обрабатывает хук intercept_key_creation_message и решает, перехватывать ли сообщение.""" + results = await run_hooks( + "intercept_key_creation_message", + chat_id=chat_id, + session=session, + target_message=target_message, + **kwargs, + ) + return bool(results and results[0]) async def process_key_creation_complete( @@ -263,25 +215,17 @@ async def process_key_creation_complete( admin: bool = False, **kwargs, ) -> list: - """ - Обрабатывает хук key_creation_complete. - - Возвращает список кнопок для добавления в меню после создания ключа. - """ - try: - results = await run_hooks( - "key_creation_complete", - chat_id=chat_id, - admin=admin, - session=session, - email=email, - key_name=key_name, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[KEY_CREATION_COMPLETE] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук key_creation_complete и возвращает кнопки после создания ключа.""" + results = await run_hooks( + "key_creation_complete", + chat_id=chat_id, + admin=admin, + session=session, + email=email, + key_name=key_name, + **kwargs, + ) + return results if results else [] async def process_process_callback_renew_key( @@ -290,23 +234,15 @@ async def process_process_callback_renew_key( session: Any, **kwargs, ) -> list: - """ - Обрабатывает хук process_callback_renew_key. - - Возвращает список кнопок для добавления в меню продления. - """ - try: - results = await run_hooks( - "process_callback_renew_key", - callback_query=callback_query, - state=state, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[PROCESS_CALLBACK_RENEW_KEY] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук process_callback_renew_key и возвращает кнопки для продления.""" + results = await run_hooks( + "process_callback_renew_key", + callback_query=callback_query, + state=state, + session=session, + **kwargs, + ) + return results if results else [] async def process_renewal_forbidden_groups( @@ -315,29 +251,21 @@ async def process_renewal_forbidden_groups( admin: bool = False, **kwargs, ) -> list[str]: - """ - Обрабатывает хук renewal_forbidden_groups. - - Возвращает список дополнительных запрещенных групп для продления. - """ - try: - results = await run_hooks( - "renewal_forbidden_groups", - chat_id=chat_id, - admin=admin, - session=session, - **kwargs, - ) - forbidden_groups = [] - for result in results: - if isinstance(result, dict): - additional_groups = result.get("additional_groups", []) - if isinstance(additional_groups, list): - forbidden_groups.extend(additional_groups) - return forbidden_groups - except Exception as e: - logger.warning(f"[RENEWAL_FORBIDDEN_GROUPS] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук renewal_forbidden_groups и возвращает дополнительные запреты.""" + results = await run_hooks( + "renewal_forbidden_groups", + chat_id=chat_id, + admin=admin, + session=session, + **kwargs, + ) + forbidden_groups: list[str] = [] + for result in results: + if isinstance(result, dict): + additional_groups = result.get("additional_groups", []) + if isinstance(additional_groups, list): + forbidden_groups.extend(additional_groups) + return forbidden_groups async def process_purchase_tariff_group_override( @@ -347,34 +275,22 @@ async def process_purchase_tariff_group_override( admin: bool = False, **kwargs, ) -> dict | None: - """ - Обрабатывает хук purchase_tariff_group_override. - - Возвращает dict с ключами: - - override_group: str - новая группа тарифов - - discount_info: dict | None - информация о скидке (опционально) - - Или None если переопределение не требуется. - """ - try: - results = await run_hooks( - "purchase_tariff_group_override", - chat_id=chat_id, - admin=admin, - session=session, - original_group=original_group, - **kwargs, - ) - for result in results: - if isinstance(result, dict) and result.get("override_group"): - return { - "override_group": result["override_group"], - "discount_info": result.get("discount_info"), - } - return None - except Exception as e: - logger.warning(f"[PURCHASE_TARIFF_GROUP_OVERRIDE] Ошибка при обработке хука: {e}") - return None + """Обрабатывает хук purchase_tariff_group_override и может изменить группу тарифов.""" + results = await run_hooks( + "purchase_tariff_group_override", + chat_id=chat_id, + admin=admin, + session=session, + original_group=original_group, + **kwargs, + ) + for result in results: + if isinstance(result, dict) and result.get("override_group"): + return { + "override_group": result["override_group"], + "discount_info": result.get("discount_info"), + } + return None async def process_renew_tariffs( @@ -383,23 +299,15 @@ async def process_renew_tariffs( admin: bool = False, **kwargs, ) -> list: - """ - Обрабатывает хук renew_tariffs. - - Возвращает список кнопок для добавления в меню выбора тарифов для продления. - """ - try: - results = await run_hooks( - "renew_tariffs", - chat_id=chat_id, - admin=admin, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[RENEW_TARIFFS] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук renew_tariffs и возвращает кнопки для выбора тарифов продления.""" + results = await run_hooks( + "renew_tariffs", + chat_id=chat_id, + admin=admin, + session=session, + **kwargs, + ) + return results if results else [] async def process_renewal_complete( @@ -410,25 +318,17 @@ async def process_renewal_complete( admin: bool = False, **kwargs, ) -> list: - """ - Обрабатывает хук renewal_complete. - - Возвращает список кнопок для добавления в меню после продления подписки. - """ - try: - results = await run_hooks( - "renewal_complete", - chat_id=chat_id, - admin=admin, - session=session, - email=email, - client_id=client_id, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[RENEWAL_COMPLETE] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук renewal_complete и возвращает кнопки после продления.""" + results = await run_hooks( + "renewal_complete", + chat_id=chat_id, + admin=admin, + session=session, + email=email, + client_id=client_id, + **kwargs, + ) + return results if results else [] async def process_view_key_menu( @@ -436,22 +336,14 @@ async def process_view_key_menu( session: Any, **kwargs, ) -> list: - """ - Обрабатывает хук view_key_menu. - - Возвращает список кнопок для добавления в меню просмотра ключа. - """ - try: - results = await run_hooks( - "view_key_menu", - key_name=key_name, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[VIEW_KEY_MENU] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук view_key_menu и возвращает кнопки для меню ключа.""" + results = await run_hooks( + "view_key_menu", + key_name=key_name, + session=session, + **kwargs, + ) + return results if results else [] async def process_admin_key_edit_menu( @@ -459,22 +351,14 @@ async def process_admin_key_edit_menu( session: Any, **kwargs, ) -> list: - """ - Обрабатывает хук admin_key_edit_menu. - - Возвращает список кнопок для добавления в меню редактирования ключа в админке. - """ - try: - results = await run_hooks( - "admin_key_edit_menu", - email=email, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[ADMIN_KEY_EDIT_MENU] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук admin_key_edit_menu и возвращает кнопки редактирования ключа.""" + results = await run_hooks( + "admin_key_edit_menu", + email=email, + session=session, + **kwargs, + ) + return results if results else [] async def process_after_hwid_reset( @@ -484,26 +368,18 @@ async def process_after_hwid_reset( admin: bool = False, **kwargs, ) -> bool: - """ - Обрабатывает хук after_hwid_reset. - - Возвращает True если нужно перенаправить пользователя в профиль после сброса устройств. - """ - try: - results = await run_hooks( - "after_hwid_reset", - chat_id=chat_id, - admin=admin, - session=session, - key_name=key_name, - **kwargs, - ) - if not results: - return False - return any("redirect_to_profile" in str(result) for result in results) - except Exception as e: - logger.warning(f"[AFTER_HWID_RESET] Ошибка при обработке хука: {e}") + """Обрабатывает хук after_hwid_reset и решает, вести ли в профиль.""" + results = await run_hooks( + "after_hwid_reset", + chat_id=chat_id, + admin=admin, + session=session, + key_name=key_name, + **kwargs, + ) + if not results: return False + return any("redirect_to_profile" in str(result) for result in results) async def process_tariff_menu( @@ -513,24 +389,16 @@ async def process_tariff_menu( session: Any, **kwargs, ) -> list: - """ - Обрабатывает хук tariff_menu. - - Возвращает список кнопок для добавления в меню выбора тарифов. - """ - try: - results = await run_hooks( - "tariff_menu", - group_code=group_code, - cluster_name=cluster_name, - tg_id=tg_id, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[TARIFF_MENU] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук tariff_menu и возвращает кнопки для меню тарифов.""" + results = await run_hooks( + "tariff_menu", + group_code=group_code, + cluster_name=cluster_name, + tg_id=tg_id, + session=session, + **kwargs, + ) + return results if results else [] async def process_check_discount_validity( @@ -540,34 +408,22 @@ async def process_check_discount_validity( admin: bool = False, **kwargs, ) -> dict | None: - """ - Обрабатывает хук check_discount_validity. - - Возвращает dict с ключами: - - valid: bool - валидна ли скидка - - message: str - сообщение об ошибке (если valid=False) - - Или None если скидка валидна. - """ - try: - results = await run_hooks( - "check_discount_validity", - chat_id=chat_id, - admin=admin, - session=session, - tariff_group=tariff_group, - **kwargs, - ) - for result in results: - if isinstance(result, dict) and not result.get("valid", True): - return { - "valid": False, - "message": result.get("message", "❌ Скидка недоступна. Пожалуйста, выберите тариф заново."), - } - return None - except Exception as e: - logger.warning(f"[CHECK_DISCOUNT_VALIDITY] Ошибка при обработке хука: {e}") - return None + """Обрабатывает хук check_discount_validity и проверяет валидность скидки.""" + results = await run_hooks( + "check_discount_validity", + chat_id=chat_id, + admin=admin, + session=session, + tariff_group=tariff_group, + **kwargs, + ) + for result in results: + if isinstance(result, dict) and not result.get("valid", True): + return { + "valid": False, + "message": result.get("message", "❌ Скидка недоступна. Пожалуйста, выберите тариф заново."), + } + return None async def process_addons_menu( @@ -575,22 +431,14 @@ async def process_addons_menu( session: Any, **kwargs, ) -> list: - """ - Обрабатывает хук addons_menu. - - Возвращает список операций для модификации кнопок в меню конфигуратора тарифов. - """ - try: - results = await run_hooks( - "addons_menu", - email=email, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[ADDONS_MENU] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук addons_menu и возвращает операции для меню конфигуратора.""" + results = await run_hooks( + "addons_menu", + email=email, + session=session, + **kwargs, + ) + return results if results else [] async def process_connect_device_menu( @@ -599,20 +447,12 @@ async def process_connect_device_menu( admin: bool = False, **kwargs, ) -> list: - """ - Обрабатывает хук connect_device_menu. - - Возвращает список кнопок для добавления в меню подключения устройства. - """ - try: - results = await run_hooks( - "connect_device_menu", - chat_id=chat_id, - admin=admin, - session=session, - **kwargs, - ) - return results if results else [] - except Exception as e: - logger.warning(f"[CONNECT_DEVICE_MENU] Ошибка при обработке хука: {e}") - return [] + """Обрабатывает хук connect_device_menu и возвращает кнопки подключения устройства.""" + results = await run_hooks( + "connect_device_menu", + chat_id=chat_id, + admin=admin, + session=session, + **kwargs, + ) + return results if results else [] diff --git a/middlewares/direct_start_blocker.py b/middlewares/direct_start_blocker.py index 09b0ed7c..e843d4b9 100644 --- a/middlewares/direct_start_blocker.py +++ b/middlewares/direct_start_blocker.py @@ -1,26 +1,19 @@ import time from collections.abc import Awaitable, Callable -from datetime import datetime from typing import Any from aiogram import BaseMiddleware from aiogram.types import Message, Update -from sqlalchemy import select from config import DISABLE_DIRECT_START from core.bootstrap import MODES_CONFIG from database import async_session_maker, check_user_exists -from database.models import Coupon, Gift, TrackingSource, User from logger import logger _TTL = 20 _cache_user_exists: dict[int, tuple[float, bool]] = {} -_cache_coupon: dict[str, tuple[float, bool]] = {} -_cache_gift: dict[str, tuple[float, bool]] = {} -_cache_ref: dict[int, tuple[float, bool]] = {} -_cache_utm: dict[str, tuple[float, bool]] = {} class DirectStartBlockerMiddleware(BaseMiddleware): @@ -87,76 +80,5 @@ class DirectStartBlockerMiddleware(BaseMiddleware): logger.info(f"[DirectStartBlocker] Отклонена неизвестная ссылка от {tg_id}: {start_param!r}") return - async with async_session_maker() as session: - if start_param.startswith("coupons_"): - code = start_param.removeprefix("coupons_") - cached = _cache_coupon.get(code) - if cached and cached[0] > now: - ok = cached[1] - else: - ok = ( - await session.execute(select(Coupon.id).where(Coupon.code == code).limit(1)) - ).first() is not None - _cache_coupon[code] = (now + _TTL, ok) - if not ok: - logger.info(f"[DirectStartBlocker] Купон не найден: {code!r}") - return - - elif start_param.startswith("gift_"): - gift_id = start_param.removeprefix("gift_") - cached = _cache_gift.get(gift_id) - if cached and cached[0] > now: - ok = cached[1] - else: - ok = ( - await session.execute( - select(Gift.gift_id) - .where( - Gift.gift_id == gift_id, - Gift.is_used.is_(False), - (Gift.expiry_time.is_(None)) | (Gift.expiry_time > datetime.utcnow()), - ) - .limit(1) - ) - ).first() is not None - _cache_gift[gift_id] = (now + _TTL, ok) - if not ok: - logger.info(f"[DirectStartBlocker] Подарок неактивен или не найден: {gift_id!r}") - return - - elif start_param.startswith("referral_"): - try: - ref_id = int(start_param.removeprefix("referral_")) - except ValueError: - logger.info(f"[DirectStartBlocker] Неверный формат referral-ссылки: {start_param!r}") - return - cached = _cache_ref.get(ref_id) - if cached and cached[0] > now: - ok = cached[1] - else: - ok = ( - await session.execute(select(User.tg_id).where(User.tg_id == ref_id).limit(1)) - ).first() is not None - _cache_ref[ref_id] = (now + _TTL, ok) - if not ok: - logger.info(f"[DirectStartBlocker] Реферал не найден: {ref_id!r}") - return - - elif start_param.startswith("utm"): - utm_code = start_param - cached = _cache_utm.get(utm_code) - if cached and cached[0] > now: - ok = cached[1] - else: - ok = ( - await session.execute( - select(TrackingSource.code).where(TrackingSource.code == utm_code).limit(1) - ) - ).first() is not None - _cache_utm[utm_code] = (now + _TTL, ok) - if not ok: - logger.info(f"[DirectStartBlocker] UTM не найден: {utm_code!r}") - return - - logger.info(f"[DirectStartBlocker] Разрешённая и валидная ссылка от {tg_id}: {start_param!r}") + logger.info(f"[DirectStartBlocker] Разрешённая ссылка от {tg_id}: {start_param!r}") return await handler(event, data) diff --git a/panels/remnawave.cpython-312-x86_64-linux-gnu.so b/panels/remnawave.cpython-312-x86_64-linux-gnu.so index e73816f8..ae408ea1 100644 Binary files a/panels/remnawave.cpython-312-x86_64-linux-gnu.so and b/panels/remnawave.cpython-312-x86_64-linux-gnu.so differ