mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-28 15:50:25 +00:00
171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
import logging
|
||
import functools
|
||
from typing import Callable, Any
|
||
from aiogram import types
|
||
from aiogram.fsm.context import FSMContext
|
||
from aiogram.exceptions import TelegramBadRequest
|
||
|
||
from app.config import settings
|
||
from app.localization.texts import get_texts
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def admin_required(func: Callable) -> Callable:
|
||
|
||
@functools.wraps(func)
|
||
async def wrapper(
|
||
event: types.Update,
|
||
*args,
|
||
**kwargs
|
||
) -> Any:
|
||
user = None
|
||
if isinstance(event, (types.Message, types.CallbackQuery)):
|
||
user = event.from_user
|
||
|
||
if not user or not settings.is_admin(user.id):
|
||
texts = get_texts()
|
||
|
||
try:
|
||
if isinstance(event, types.Message):
|
||
await event.answer(texts.ACCESS_DENIED)
|
||
elif isinstance(event, types.CallbackQuery):
|
||
await event.answer(texts.ACCESS_DENIED, show_alert=True)
|
||
except TelegramBadRequest as e:
|
||
if "query is too old" in str(e).lower():
|
||
logger.warning(f"Попытка ответить на устаревший callback query от {user.id if user else 'Unknown'}")
|
||
else:
|
||
raise
|
||
|
||
logger.warning(f"Попытка доступа к админской функции от {user.id if user else 'Unknown'}")
|
||
return
|
||
|
||
return await func(event, *args, **kwargs)
|
||
|
||
return wrapper
|
||
|
||
|
||
def error_handler(func: Callable) -> Callable:
|
||
|
||
@functools.wraps(func)
|
||
async def wrapper(*args, **kwargs) -> Any:
|
||
try:
|
||
return await func(*args, **kwargs)
|
||
except TelegramBadRequest as e:
|
||
error_message = str(e).lower()
|
||
|
||
if "query is too old" in error_message or "query id is invalid" in error_message:
|
||
event = _extract_event(args)
|
||
if event and isinstance(event, types.CallbackQuery):
|
||
user_info = f"@{event.from_user.username}" if event.from_user.username else f"ID:{event.from_user.id}"
|
||
logger.warning(f"🕐 Игнорируем устаревший callback '{event.data}' от {user_info} в {func.__name__}")
|
||
else:
|
||
logger.warning(f"🕐 Игнорируем устаревший запрос в {func.__name__}: {e}")
|
||
return None
|
||
|
||
elif "message is not modified" in error_message:
|
||
logger.debug(f"📝 Сообщение не изменено в {func.__name__}")
|
||
event = _extract_event(args)
|
||
if event and isinstance(event, types.CallbackQuery):
|
||
try:
|
||
await event.answer()
|
||
except TelegramBadRequest as answer_error:
|
||
if "query is too old" not in str(answer_error).lower():
|
||
logger.error(f"Ошибка при ответе на callback: {answer_error}")
|
||
return None
|
||
|
||
else:
|
||
logger.error(f"Telegram API error в {func.__name__}: {e}")
|
||
await _send_error_message(args, kwargs, e)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в {func.__name__}: {e}", exc_info=True)
|
||
await _send_error_message(args, kwargs, e)
|
||
|
||
return wrapper
|
||
|
||
|
||
def _extract_event(args) -> types.TelegramObject:
|
||
for arg in args:
|
||
if isinstance(arg, (types.Message, types.CallbackQuery)):
|
||
return arg
|
||
return None
|
||
|
||
|
||
async def _send_error_message(args, kwargs, original_error):
|
||
try:
|
||
event = _extract_event(args)
|
||
db_user = kwargs.get('db_user')
|
||
|
||
if not event:
|
||
return
|
||
|
||
texts = get_texts(db_user.language if db_user else 'ru')
|
||
|
||
if isinstance(event, types.Message):
|
||
await event.answer(texts.ERROR)
|
||
elif isinstance(event, types.CallbackQuery):
|
||
await event.answer(texts.ERROR, show_alert=True)
|
||
|
||
except TelegramBadRequest as e:
|
||
if "query is too old" in str(e).lower():
|
||
logger.warning("Не удалось отправить сообщение об ошибке - callback query устарел")
|
||
else:
|
||
logger.error(f"Ошибка при отправке сообщения об ошибке: {e}")
|
||
except Exception as e:
|
||
logger.error(f"Критическая ошибка при отправке сообщения об ошибке: {e}")
|
||
|
||
|
||
def state_cleanup(func: Callable) -> Callable:
|
||
|
||
@functools.wraps(func)
|
||
async def wrapper(*args, **kwargs) -> Any:
|
||
state = kwargs.get('state')
|
||
|
||
try:
|
||
return await func(*args, **kwargs)
|
||
except Exception as e:
|
||
if state and isinstance(state, FSMContext):
|
||
await state.clear()
|
||
raise e
|
||
|
||
return wrapper
|
||
|
||
|
||
def typing_action(func: Callable) -> Callable:
|
||
|
||
@functools.wraps(func)
|
||
async def wrapper(
|
||
event: types.Update,
|
||
*args,
|
||
**kwargs
|
||
) -> Any:
|
||
if isinstance(event, types.Message):
|
||
try:
|
||
await event.bot.send_chat_action(
|
||
chat_id=event.chat.id,
|
||
action="typing"
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось отправить typing action: {e}")
|
||
|
||
return await func(event, *args, **kwargs)
|
||
|
||
return wrapper
|
||
|
||
|
||
def rate_limit(rate: float = 1.0, key: str = None):
|
||
def decorator(func: Callable) -> Callable:
|
||
|
||
@functools.wraps(func)
|
||
async def wrapper(
|
||
event: types.Update,
|
||
*args,
|
||
**kwargs
|
||
) -> Any:
|
||
return await func(event, *args, **kwargs)
|
||
|
||
return wrapper
|
||
|
||
return decorator
|