optimization of the start command
This commit is contained in:
Binary file not shown.
+30
-13
@@ -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
@@ -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"))
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DEFAULT_HOOK_TIMEOUT = 2.0
|
||||
+20
-5
@@ -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
@@ -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,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.
Reference in New Issue
Block a user