optimization of the start command

This commit is contained in:
Vladless
2025-12-07 00:32:43 +03:00
parent ccacc98b0e
commit 8183c2b678
8 changed files with 289 additions and 470 deletions
Binary file not shown.
+30 -13
View File
@@ -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
+32 -8
View File
@@ -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"))
+1
View File
@@ -0,0 +1 @@
DEFAULT_HOOK_TIMEOUT = 2.0
+20 -5
View File
@@ -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
+205 -365
View File
@@ -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 []
+1 -79
View File
@@ -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)
Binary file not shown.