safe callback
This commit is contained in:
@@ -24,7 +24,7 @@ from handlers.texts import (
|
||||
UNLIMITED_DEVICES_LABEL,
|
||||
UNLIMITED_TRAFFIC_LABEL,
|
||||
)
|
||||
from handlers.utils import edit_or_send_message
|
||||
from handlers.utils import edit_or_send_message, safe_answer_callback
|
||||
from hooks.processors import process_check_discount_validity
|
||||
from logger import logger
|
||||
|
||||
@@ -160,7 +160,7 @@ async def start_tariff_config(
|
||||
target_message=callback_query.message,
|
||||
text="❌ Указанный тариф не найден.",
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.warning(f"[TARIFF_CFG] start_tariff_config tariff_not_found: tariff_id={tariff_id}")
|
||||
return
|
||||
|
||||
@@ -169,7 +169,7 @@ async def start_tariff_config(
|
||||
target_message=callback_query.message,
|
||||
text="❌ Этот тариф нельзя настроить.",
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.info(f"[TARIFF_CFG] start_tariff_config not_configurable: tariff_id={tariff_id}")
|
||||
return
|
||||
|
||||
@@ -253,7 +253,7 @@ async def proceed_purchase_with_values(
|
||||
text=CREATING_CONNECTION_MSG,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
|
||||
expiry_time = datetime.now(moscow_tz) + timedelta(days=duration_days)
|
||||
|
||||
@@ -306,6 +306,7 @@ async def render_user_config_screen(
|
||||
text="Подождите, создаём подписку…",
|
||||
reply_markup=None,
|
||||
)
|
||||
await safe_answer_callback(callback_query)
|
||||
await state.clear()
|
||||
logger.warning("[TARIFF_CFG] render_user_config_screen: config_tariff_id missing in state")
|
||||
return
|
||||
@@ -317,6 +318,7 @@ async def render_user_config_screen(
|
||||
text="❌ Тариф не найден.",
|
||||
reply_markup=None,
|
||||
)
|
||||
await safe_answer_callback(callback_query)
|
||||
await state.clear()
|
||||
logger.warning(f"[TARIFF_CFG] render_user_config_screen tariff_not_found: tariff_id={tariff_id}")
|
||||
return
|
||||
@@ -547,7 +549,7 @@ async def render_user_config_screen(
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
|
||||
|
||||
async def start_user_tariff_configurator(
|
||||
@@ -587,7 +589,7 @@ async def start_user_tariff_configurator(
|
||||
text="❌ Конфигуратор для этого тарифа не настроен. Попробуйте выбрать другой тариф.",
|
||||
reply_markup=None,
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.warning(f"[TARIFF_CFG] configurator_not_configured: tariff_id={tariff.get('id')}")
|
||||
return
|
||||
|
||||
@@ -774,7 +776,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
target_message=callback_query.message,
|
||||
text="❌ Указанный тариф не найден.",
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.warning(f"[TARIFF_CFG] select_tariff_plan tariff_not_found: tariff_id={tariff_id}")
|
||||
return
|
||||
|
||||
@@ -788,7 +790,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
text="❌ Скидка недоступна или истекла. Пожалуйста, выберите тариф заново.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.info(
|
||||
"[TARIFF_CFG] select_tariff_plan discount_invalid: "
|
||||
f"tg_id={tg_id} tariff_id={tariff_id} info={discount_info}"
|
||||
@@ -809,7 +811,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
text=validity_result["message"],
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
await safe_answer_callback(callback_query)
|
||||
logger.info(
|
||||
"[TARIFF_CFG] select_tariff_plan discount_validity_failed: "
|
||||
f"tg_id={tg_id} tariff_id={tariff_id} result={validity_result}"
|
||||
@@ -867,6 +869,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
)
|
||||
async def handle_user_devices_choice(callback: CallbackQuery, state: FSMContext, session: Any):
|
||||
"""Обрабатывает выбор лимита устройств в конфигураторе."""
|
||||
await safe_answer_callback(callback)
|
||||
_, _tariff_id_str, devices_str = callback.data.split("|", 2)
|
||||
devices = int(devices_str)
|
||||
|
||||
@@ -875,7 +878,6 @@ async def handle_user_devices_choice(callback: CallbackQuery, state: FSMContext,
|
||||
await state.update_data(config_tariff_id=int(_tariff_id_str))
|
||||
current = data.get("config_selected_device_limit")
|
||||
if current is not None and int(current) == devices:
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
await state.update_data(config_selected_device_limit=devices)
|
||||
@@ -888,6 +890,7 @@ async def handle_user_devices_choice(callback: CallbackQuery, state: FSMContext,
|
||||
)
|
||||
async def handle_user_traffic_choice(callback: CallbackQuery, state: FSMContext, session: Any):
|
||||
"""Обрабатывает выбор лимита трафика в конфигураторе."""
|
||||
await safe_answer_callback(callback)
|
||||
_, _tariff_id_str, traffic_str = callback.data.split("|", 2)
|
||||
traffic = int(traffic_str)
|
||||
|
||||
@@ -896,7 +899,6 @@ async def handle_user_traffic_choice(callback: CallbackQuery, state: FSMContext,
|
||||
await state.update_data(config_tariff_id=int(_tariff_id_str))
|
||||
current = data.get("config_selected_traffic_gb")
|
||||
if current is not None and int(current) == traffic:
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
await state.update_data(config_selected_traffic_gb=traffic)
|
||||
|
||||
@@ -8,8 +8,10 @@ from datetime import datetime, timedelta
|
||||
|
||||
import aiofiles
|
||||
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.types import (
|
||||
BufferedInputFile,
|
||||
CallbackQuery,
|
||||
InlineKeyboardMarkup,
|
||||
InputMediaAnimation,
|
||||
InputMediaPhoto,
|
||||
@@ -30,6 +32,26 @@ from logger import logger
|
||||
ALLOWED_GROUP_CODES = ["trial", "discounts", "discounts_max", "gifts"]
|
||||
|
||||
|
||||
_CALLBACK_ANSWER_IGNORE = (
|
||||
"query is too old",
|
||||
"response timeout expired",
|
||||
"query id is invalid",
|
||||
)
|
||||
|
||||
|
||||
async def safe_answer_callback(callback_query: CallbackQuery, text: str | None = None, show_alert: bool = False, **kwargs) -> None:
|
||||
"""
|
||||
Вызывает callback_query.answer(), не поднимая исключение при устаревшем/уже отвеченном callback.
|
||||
Использовать в хендлерах после долгой обработки или при наплыве пользователей.
|
||||
"""
|
||||
try:
|
||||
await callback_query.answer(text=text, show_alert=show_alert, **kwargs)
|
||||
except TelegramBadRequest as e:
|
||||
msg = str(e).lower()
|
||||
if not any(phrase in msg for phrase in _CALLBACK_ANSWER_IGNORE):
|
||||
raise
|
||||
|
||||
|
||||
async def generate_random_email(
|
||||
length: int = 8,
|
||||
session: AsyncSession | None = None,
|
||||
|
||||
+15
-22
@@ -109,27 +109,20 @@ def _run_with_api_in_thread(
|
||||
loop.close()
|
||||
|
||||
|
||||
def invalidate_remnawave_profile_cache(*, api_url: str | None = None, client_id: str | None = None) -> None:
|
||||
"""Invalidate cached Remnawave profiles by api_url/client_id (or both)."""
|
||||
import asyncio
|
||||
|
||||
async def _invalidate_async() -> None:
|
||||
if api_url is None and client_id is None:
|
||||
await cache_delete_pattern("remna_profile:*")
|
||||
return
|
||||
if api_url is not None and client_id is not None:
|
||||
await cache_delete_pattern(f"remna_profile:{api_url}:{client_id}")
|
||||
return
|
||||
if api_url is not None:
|
||||
await cache_delete_pattern(f"remna_profile:{api_url}:*")
|
||||
return
|
||||
await cache_delete_pattern(f"remna_profile:*:{client_id}")
|
||||
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.create_task(_invalidate_async())
|
||||
except RuntimeError:
|
||||
async def invalidate_remnawave_profile_cache(
|
||||
*, api_url: str | None = None, client_id: str | None = None
|
||||
) -> None:
|
||||
"""Invalidate cached Remnawave profiles by api_url/client_id (or both). Await to avoid pending task on shutdown."""
|
||||
if api_url is None and client_id is None:
|
||||
await cache_delete_pattern("remna_profile:*")
|
||||
return
|
||||
if api_url is not None and client_id is not None:
|
||||
await cache_delete_pattern(f"remna_profile:{api_url}:{client_id}")
|
||||
return
|
||||
if api_url is not None:
|
||||
await cache_delete_pattern(f"remna_profile:{api_url}:*")
|
||||
return
|
||||
await cache_delete_pattern(f"remna_profile:*:{client_id}")
|
||||
|
||||
|
||||
async def resolve_remnawave_api_url(
|
||||
@@ -204,9 +197,9 @@ async def invalidate_remnawave_profile(
|
||||
) -> None:
|
||||
api_url = await resolve_remnawave_api_url(session, server_ref, fallback_any=fallback_any)
|
||||
if api_url:
|
||||
invalidate_remnawave_profile_cache(api_url=api_url, client_id=client_id)
|
||||
await invalidate_remnawave_profile_cache(api_url=api_url, client_id=client_id)
|
||||
else:
|
||||
invalidate_remnawave_profile_cache(client_id=client_id)
|
||||
await invalidate_remnawave_profile_cache(client_id=client_id)
|
||||
|
||||
|
||||
async def with_remnawave_api(
|
||||
|
||||
Reference in New Issue
Block a user