fix: tune webapp visual and fix some errors
This commit is contained in:
@@ -17,6 +17,7 @@ docker-compose.yml
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
.docker
|
.docker
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
tmp/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ WEBAPP_ENABLED=True #
|
|||||||
WEBAPP_SERVER_HOST=0.0.0.0 # Internal listen host
|
WEBAPP_SERVER_HOST=0.0.0.0 # Internal listen host
|
||||||
WEBAPP_SERVER_PORT=8081 # Internal/published Mini App port
|
WEBAPP_SERVER_PORT=8081 # Internal/published Mini App port
|
||||||
WEBAPP_TITLE="Моя подписка" # Mini App title
|
WEBAPP_TITLE="Моя подписка" # Mini App title
|
||||||
WEBAPP_PRIMARY_COLOR="#10b981" # Main UI color
|
WEBAPP_PRIMARY_COLOR="#00fe7a" # Main UI color
|
||||||
WEBAPP_LOGO_URL= # Optional logo URL
|
WEBAPP_LOGO_URL= # Optional logo URL
|
||||||
WEBAPP_SESSION_TTL_SECONDS=2592000 # Web App session lifetime
|
WEBAPP_SESSION_TTL_SECONDS=2592000 # Web App session lifetime
|
||||||
WEBAPP_AUTH_MAX_AGE_SECONDS=86400 # Max Telegram initData age
|
WEBAPP_AUTH_MAX_AGE_SECONDS=86400 # Max Telegram initData age
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
| `WEBAPP_ENABLED` | Включить Web App в том же контейнере, но на отдельном порту. | `true` |
|
| `WEBAPP_ENABLED` | Включить Web App в том же контейнере, но на отдельном порту. | `true` |
|
||||||
| `WEBAPP_SERVER_PORT` | Внутренний порт Web App. | `8081` |
|
| `WEBAPP_SERVER_PORT` | Внутренний порт Web App. | `8081` |
|
||||||
| `WEBAPP_TITLE` | Заголовок Web App. | `Моя подписка` |
|
| `WEBAPP_TITLE` | Заголовок Web App. | `Моя подписка` |
|
||||||
| `WEBAPP_PRIMARY_COLOR` | Основной цвет Web App. | `#10b981` |
|
| `WEBAPP_PRIMARY_COLOR` | Основной цвет Web App. | `#00fe7a` |
|
||||||
| `WEBAPP_LOGO_URL` | (Опционально) URL логотипа Web App. | `https://domain.com/logo.png` |
|
| `WEBAPP_LOGO_URL` | (Опционально) URL логотипа Web App. | `https://domain.com/logo.png` |
|
||||||
| `MY_DEVICES_SECTION_ENABLED` | Включить раздел «Мои устройства» в меню подписки (`true`/`false`). | `false` |
|
| `MY_DEVICES_SECTION_ENABLED` | Включить раздел «Мои устройства» в меню подписки (`true`/`false`). | `false` |
|
||||||
| `REQUIRED_CHANNEL_ID` | (Опционально) ID канала, на который пользователь должен подписаться перед использованием. Оставьте пустым, если проверка не нужна. | `-1001234567890` |
|
| `REQUIRED_CHANNEL_ID` | (Опционально) ID канала, на который пользователь должен подписаться перед использованием. Оставьте пустым, если проверка не нужна. | `-1001234567890` |
|
||||||
@@ -202,7 +202,7 @@ Web App запускается в том же контейнере, что и б
|
|||||||
WEBAPP_SERVER_PORT=8081
|
WEBAPP_SERVER_PORT=8081
|
||||||
SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
|
SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/
|
||||||
WEBAPP_TITLE="Моя подписка"
|
WEBAPP_TITLE="Моя подписка"
|
||||||
WEBAPP_PRIMARY_COLOR="#10b981"
|
WEBAPP_PRIMARY_COLOR="#00fe7a"
|
||||||
WEBAPP_LOGO_URL=
|
WEBAPP_LOGO_URL=
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ Web App запускается в том же контейнере, что и б
|
|||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
После этого кнопка «Моя подписка» в меню бота откроет Web App. В MVP Web App показывает текущую ссылку подключения, остаток времени, трафик, тарифы и создает платежные ссылки через уже настроенные платежные системы. Успешные оплаты обрабатываются существующими webhook-обработчиками.
|
После этого кнопка «Моя подписка» в меню бота откроет Web App. В MVP Web App показывает текущую ссылку подключения, остаток времени и трафик. Оплата открывается отдельной кнопкой «Продлить подписку/Добавить дни»: пользователь пошагово выбирает срок, способ оплаты и создает платежную ссылку через уже настроенные платежные системы. Успешные оплаты обрабатываются существующими webhook-обработчиками.
|
||||||
|
|
||||||
## Подробная инструкция для развертывания на сервере с панелью Remnawave
|
## Подробная инструкция для развертывания на сервере с панелью Remnawave
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ from bot.keyboards.inline.user_keyboards import (
|
|||||||
)
|
)
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from bot.middlewares.i18n import JsonI18n
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.utils.callback_answer import safe_answer_callback
|
||||||
|
|
||||||
from .start import send_main_menu
|
from .start import send_main_menu
|
||||||
|
|
||||||
@@ -35,15 +36,18 @@ async def prompt_promo_code_input(callback: types.CallbackQuery,
|
|||||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
if not i18n:
|
if not i18n:
|
||||||
await callback.answer("Language service error.", show_alert=True)
|
await safe_answer_callback(callback, "Language service error.", show_alert=True)
|
||||||
return
|
return
|
||||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
|
|
||||||
if not callback.message:
|
if not callback.message:
|
||||||
logging.error(
|
logging.error(
|
||||||
"CallbackQuery has no message in prompt_promo_code_input")
|
"CallbackQuery has no message in prompt_promo_code_input")
|
||||||
await callback.answer(_("error_occurred_processing_request"),
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
_("error_occurred_processing_request"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -58,7 +62,7 @@ async def prompt_promo_code_input(callback: types.CallbackQuery,
|
|||||||
text=_(key="promo_code_prompt"),
|
text=_(key="promo_code_prompt"),
|
||||||
reply_markup=get_back_to_main_menu_markup(current_lang, i18n))
|
reply_markup=get_back_to_main_menu_markup(current_lang, i18n))
|
||||||
|
|
||||||
await callback.answer()
|
await safe_answer_callback(callback)
|
||||||
await state.set_state(UserPromoStates.waiting_for_promo_code)
|
await state.set_state(UserPromoStates.waiting_for_promo_code)
|
||||||
logging.info(
|
logging.info(
|
||||||
f"User {callback.from_user.id} entered state UserPromoStates.waiting_for_promo_code. "
|
f"User {callback.from_user.id} entered state UserPromoStates.waiting_for_promo_code. "
|
||||||
@@ -181,7 +185,7 @@ async def cancel_promo_input_via_button(
|
|||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
if not i18n:
|
if not i18n:
|
||||||
logging.error("i18n missing in cancel_promo_input_via_button")
|
logging.error("i18n missing in cancel_promo_input_via_button")
|
||||||
await callback.answer("Language error", show_alert=True)
|
await safe_answer_callback(callback, "Language error", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
@@ -200,5 +204,8 @@ async def cancel_promo_input_via_button(
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
await callback.answer(_("promo_input_cancelled_short"),
|
await safe_answer_callback(
|
||||||
show_alert=False)
|
callback,
|
||||||
|
_("promo_input_cancelled_short"),
|
||||||
|
show_alert=False,
|
||||||
|
)
|
||||||
|
|||||||
+61
-30
@@ -25,6 +25,7 @@ from bot.services.promo_code_service import PromoCodeService
|
|||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from bot.middlewares.i18n import JsonI18n
|
from bot.middlewares.i18n import JsonI18n
|
||||||
from bot.utils.text_sanitizer import sanitize_username, sanitize_display_name
|
from bot.utils.text_sanitizer import sanitize_username, sanitize_display_name
|
||||||
|
from bot.utils.callback_answer import safe_answer_callback
|
||||||
from bot.app.web.webapp_auth import authorize_pending_webapp_auth_token
|
from bot.app.web.webapp_auth import authorize_pending_webapp_auth_token
|
||||||
|
|
||||||
router = Router(name="user_start_router")
|
router = Router(name="user_start_router")
|
||||||
@@ -91,8 +92,11 @@ async def send_main_menu(target_event: Union[types.Message,
|
|||||||
f"send_main_menu: target_message_obj is None for event from user {user_id}."
|
f"send_main_menu: target_message_obj is None for event from user {user_id}."
|
||||||
)
|
)
|
||||||
if isinstance(target_event, types.CallbackQuery):
|
if isinstance(target_event, types.CallbackQuery):
|
||||||
await target_event.answer(_("error_displaying_menu"),
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
target_event,
|
||||||
|
_("error_displaying_menu"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -102,10 +106,7 @@ async def send_main_menu(target_event: Union[types.Message,
|
|||||||
await target_message_obj.answer(text, reply_markup=reply_markup)
|
await target_message_obj.answer(text, reply_markup=reply_markup)
|
||||||
|
|
||||||
if isinstance(target_event, types.CallbackQuery):
|
if isinstance(target_event, types.CallbackQuery):
|
||||||
try:
|
await safe_answer_callback(target_event)
|
||||||
await target_event.answer()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except Exception as e_send_edit:
|
except Exception as e_send_edit:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Failed to send/edit main menu (user: {user_id}, is_edit: {is_edit}): {type(e_send_edit).__name__} - {e_send_edit}."
|
f"Failed to send/edit main menu (user: {user_id}, is_edit: {is_edit}): {type(e_send_edit).__name__} - {e_send_edit}."
|
||||||
@@ -118,11 +119,10 @@ async def send_main_menu(target_event: Union[types.Message,
|
|||||||
f"Also failed to send new main menu message for user {user_id}: {e_send_new}"
|
f"Also failed to send new main menu message for user {user_id}: {e_send_new}"
|
||||||
)
|
)
|
||||||
if isinstance(target_event, types.CallbackQuery):
|
if isinstance(target_event, types.CallbackQuery):
|
||||||
try:
|
await safe_answer_callback(
|
||||||
await target_event.answer(
|
target_event,
|
||||||
_("error_occurred_try_again") if is_edit else None)
|
_("error_occurred_try_again") if is_edit else None,
|
||||||
except Exception:
|
)
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def ensure_required_channel_subscription(
|
async def ensure_required_channel_subscription(
|
||||||
@@ -693,8 +693,11 @@ async def verify_channel_subscription_callback(
|
|||||||
welcome_text)
|
welcome_text)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await callback.answer(_(key="channel_subscription_verified_success"),
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
_(key="channel_subscription_verified_success"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -725,7 +728,11 @@ async def language_command_handler(
|
|||||||
event, types.CallbackQuery) else event
|
event, types.CallbackQuery) else event
|
||||||
if not target_message_obj:
|
if not target_message_obj:
|
||||||
if isinstance(event, types.CallbackQuery):
|
if isinstance(event, types.CallbackQuery):
|
||||||
await event.answer(_("error_occurred_try_again"), show_alert=True)
|
await safe_answer_callback(
|
||||||
|
event,
|
||||||
|
_("error_occurred_try_again"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(event, types.CallbackQuery):
|
if isinstance(event, types.CallbackQuery):
|
||||||
@@ -736,7 +743,7 @@ async def language_command_handler(
|
|||||||
except Exception:
|
except Exception:
|
||||||
await target_message_obj.answer(text_to_send,
|
await target_message_obj.answer(text_to_send,
|
||||||
reply_markup=reply_markup)
|
reply_markup=reply_markup)
|
||||||
await event.answer()
|
await safe_answer_callback(event)
|
||||||
else:
|
else:
|
||||||
await target_message_obj.answer(text_to_send,
|
await target_message_obj.answer(text_to_send,
|
||||||
reply_markup=reply_markup)
|
reply_markup=reply_markup)
|
||||||
@@ -748,15 +755,21 @@ async def select_language_callback_handler(
|
|||||||
subscription_service: SubscriptionService, session: AsyncSession):
|
subscription_service: SubscriptionService, session: AsyncSession):
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
if not i18n or not callback.message:
|
if not i18n or not callback.message:
|
||||||
await callback.answer("Service error or message context lost.",
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
"Service error or message context lost.",
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lang_code = callback.data.split("_")[2]
|
lang_code = callback.data.split("_")[2]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
await callback.answer("Error processing language selection.",
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
"Error processing language selection.",
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
user_id = callback.from_user.id
|
user_id = callback.from_user.id
|
||||||
@@ -767,18 +780,22 @@ async def select_language_callback_handler(
|
|||||||
|
|
||||||
i18n_data["current_language"] = lang_code
|
i18n_data["current_language"] = lang_code
|
||||||
_ = lambda key, **kwargs: i18n.gettext(lang_code, key, **kwargs)
|
_ = lambda key, **kwargs: i18n.gettext(lang_code, key, **kwargs)
|
||||||
await callback.answer(_(key="language_set_alert"))
|
await safe_answer_callback(callback, _(key="language_set_alert"))
|
||||||
logging.info(
|
logging.info(
|
||||||
f"User {user_id} language updated to {lang_code} in session.")
|
f"User {user_id} language updated to {lang_code} in session.")
|
||||||
else:
|
else:
|
||||||
await callback.answer("Could not set language.", show_alert=True)
|
await safe_answer_callback(
|
||||||
|
callback,
|
||||||
|
"Could not set language.",
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except Exception as e_lang_update:
|
except Exception as e_lang_update:
|
||||||
|
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Error updating lang for user {user_id}: {e_lang_update}",
|
f"Error updating lang for user {user_id}: {e_lang_update}",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
await callback.answer("Error setting language.", show_alert=True)
|
await safe_answer_callback(callback, "Error setting language.", show_alert=True)
|
||||||
return
|
return
|
||||||
await send_main_menu(callback,
|
await send_main_menu(callback,
|
||||||
settings,
|
settings,
|
||||||
@@ -803,7 +820,11 @@ async def main_action_callback_handler(
|
|||||||
from . import trial_handler as user_trial_handlers
|
from . import trial_handler as user_trial_handlers
|
||||||
|
|
||||||
if not callback.message:
|
if not callback.message:
|
||||||
await callback.answer("Error: message context lost.", show_alert=True)
|
await safe_answer_callback(
|
||||||
|
callback,
|
||||||
|
"Error: message context lost.",
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if action == "subscribe":
|
if action == "subscribe":
|
||||||
@@ -834,8 +855,11 @@ async def main_action_callback_handler(
|
|||||||
current_lang = i18n_data.get("current_language",
|
current_lang = i18n_data.get("current_language",
|
||||||
settings.DEFAULT_LANGUAGE)
|
settings.DEFAULT_LANGUAGE)
|
||||||
if not i18n:
|
if not i18n:
|
||||||
await callback.answer("Language service error.",
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
"Language service error.",
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
_ = lambda key, **kwargs: i18n.gettext(
|
_ = lambda key, **kwargs: i18n.gettext(
|
||||||
current_lang, key, **kwargs) if i18n else key
|
current_lang, key, **kwargs) if i18n else key
|
||||||
@@ -844,8 +868,11 @@ async def main_action_callback_handler(
|
|||||||
user_agreement_url = settings.USER_AGREEMENT_URL or settings.TERMS_OF_SERVICE_URL
|
user_agreement_url = settings.USER_AGREEMENT_URL or settings.TERMS_OF_SERVICE_URL
|
||||||
|
|
||||||
if not privacy_url and not user_agreement_url:
|
if not privacy_url and not user_agreement_url:
|
||||||
await callback.answer(_("error_occurred_try_again"),
|
await safe_answer_callback(
|
||||||
show_alert=True)
|
callback,
|
||||||
|
_("error_occurred_try_again"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
reply_markup = get_information_links_keyboard(
|
reply_markup = get_information_links_keyboard(
|
||||||
@@ -860,7 +887,7 @@ async def main_action_callback_handler(
|
|||||||
except Exception:
|
except Exception:
|
||||||
await callback.message.answer(_(key="info_links_message"),
|
await callback.message.answer(_(key="info_links_message"),
|
||||||
reply_markup=reply_markup)
|
reply_markup=reply_markup)
|
||||||
await callback.answer()
|
await safe_answer_callback(callback)
|
||||||
elif action == "back_to_main":
|
elif action == "back_to_main":
|
||||||
await send_main_menu(callback,
|
await send_main_menu(callback,
|
||||||
settings,
|
settings,
|
||||||
@@ -878,5 +905,9 @@ async def main_action_callback_handler(
|
|||||||
else:
|
else:
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
_ = lambda key, **kwargs: i18n.gettext(
|
_ = lambda key, **kwargs: i18n.gettext(
|
||||||
i18n_data.get("current_language"), key, **kw) if i18n else key
|
i18n_data.get("current_language"), key, **kwargs) if i18n else key
|
||||||
await callback.answer(_("main_menu_unknown_action"), show_alert=True)
|
await safe_answer_callback(
|
||||||
|
callback,
|
||||||
|
_("main_menu_unknown_action"),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import logging
|
|||||||
from typing import Callable, Dict, Any, Awaitable
|
from typing import Callable, Dict, Any, Awaitable
|
||||||
|
|
||||||
from aiogram import BaseMiddleware
|
from aiogram import BaseMiddleware
|
||||||
|
from aiogram.exceptions import TelegramBadRequest
|
||||||
from aiogram.types import Update
|
from aiogram.types import Update
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from bot.utils.callback_answer import is_expired_callback_answer_error
|
||||||
|
|
||||||
|
|
||||||
class DBSessionMiddleware(BaseMiddleware):
|
class DBSessionMiddleware(BaseMiddleware):
|
||||||
|
|
||||||
@@ -31,6 +34,18 @@ class DBSessionMiddleware(BaseMiddleware):
|
|||||||
|
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return result
|
return result
|
||||||
|
except TelegramBadRequest as error:
|
||||||
|
await session.rollback()
|
||||||
|
if is_expired_callback_answer_error(error):
|
||||||
|
logging.info(
|
||||||
|
"DBSessionMiddleware: expired callback answer ignored; "
|
||||||
|
"session rolled back for the interrupted update."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
logging.error(
|
||||||
|
"DBSessionMiddleware: Exception caused rollback.", exc_info=True
|
||||||
|
)
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
logging.error(
|
logging.error(
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiogram.exceptions import TelegramAPIError, TelegramBadRequest
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
|
|
||||||
|
_EXPIRED_CALLBACK_MARKERS = (
|
||||||
|
"query is too old",
|
||||||
|
"response timeout expired",
|
||||||
|
"query id is invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_expired_callback_answer_error(error: BaseException) -> bool:
|
||||||
|
if not isinstance(error, TelegramBadRequest):
|
||||||
|
return False
|
||||||
|
message = str(error).lower()
|
||||||
|
return any(marker in message for marker in _EXPIRED_CALLBACK_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_answer_callback(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> bool:
|
||||||
|
try:
|
||||||
|
await callback.answer(*args, **kwargs)
|
||||||
|
return True
|
||||||
|
except TelegramBadRequest as error:
|
||||||
|
user_id = getattr(getattr(callback, "from_user", None), "id", "unknown")
|
||||||
|
if is_expired_callback_answer_error(error):
|
||||||
|
logging.info(
|
||||||
|
"Ignored expired callback answer for user %s: %s",
|
||||||
|
user_id,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
logging.warning(
|
||||||
|
"Failed to answer callback query for user %s: %s",
|
||||||
|
user_id,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except TelegramAPIError as error:
|
||||||
|
user_id = getattr(getattr(callback, "from_user", None), "id", "unknown")
|
||||||
|
logging.warning(
|
||||||
|
"Telegram API error while answering callback query for user %s: %s",
|
||||||
|
user_id,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return False
|
||||||
+1
-1
@@ -209,7 +209,7 @@ class Settings(BaseSettings):
|
|||||||
WEBAPP_SERVER_HOST: str = Field(default="0.0.0.0")
|
WEBAPP_SERVER_HOST: str = Field(default="0.0.0.0")
|
||||||
WEBAPP_SERVER_PORT: int = Field(default=8081)
|
WEBAPP_SERVER_PORT: int = Field(default=8081)
|
||||||
WEBAPP_TITLE: str = Field(default="Моя подписка")
|
WEBAPP_TITLE: str = Field(default="Моя подписка")
|
||||||
WEBAPP_PRIMARY_COLOR: str = Field(default="#10b981")
|
WEBAPP_PRIMARY_COLOR: str = Field(default="#00fe7a")
|
||||||
WEBAPP_LOGO_URL: Optional[str] = Field(default=None)
|
WEBAPP_LOGO_URL: Optional[str] = Field(default=None)
|
||||||
WEBAPP_SESSION_TTL_SECONDS: int = Field(default=30 * 24 * 60 * 60)
|
WEBAPP_SESSION_TTL_SECONDS: int = Field(default=30 * 24 * 60 * 60)
|
||||||
WEBAPP_AUTH_MAX_AGE_SECONDS: int = Field(default=24 * 60 * 60)
|
WEBAPP_AUTH_MAX_AGE_SECONDS: int = Field(default=24 * 60 * 60)
|
||||||
|
|||||||
Reference in New Issue
Block a user