fix quality code

This commit is contained in:
2026-02-01 23:03:23 +03:00
parent 731e68a597
commit f8962225ee
106 changed files with 8456 additions and 5810 deletions

View File

@@ -7,19 +7,21 @@ from aiogram.types import Message
class AlbumGetter:
"""Вспомогательный класс для получения полной медиагруппы из middleware"""
def __init__(self, album_data: Dict[str, Any], media_group_id: str, event: asyncio.Event):
def __init__(
self, album_data: Dict[str, Any], media_group_id: str, event: asyncio.Event
):
self.album_data = album_data
self.media_group_id = media_group_id
self.event = event
async def get_album(self, timeout: float = 10.0) -> Optional[List[Message]]:
"""
Ждет полную медиагруппу и возвращает ее.
Args:
timeout: Максимальное время ожидания в секундах
Returns:
Список сообщений медиагруппы или None при таймауте
"""
@@ -38,11 +40,11 @@ class AlbumMiddleware(BaseMiddleware):
Собирает все сообщения одной медиа группы и передает их как album в data.
Не блокирует handler - сразу вызывает его, а полную медиагруппу передает через Event.
"""
def __init__(self, latency: Union[int, float] = 5.0):
"""
Инициализация middleware.
Args:
latency: Задержка в секундах для сбора всех сообщений медиа группы
"""
@@ -54,43 +56,43 @@ class AlbumMiddleware(BaseMiddleware):
def collect_album_messages(self, event: Message) -> int:
"""
Собирает сообщения одной медиа группы.
Args:
event: Сообщение для обработки
Returns:
Количество сообщений в текущей медиа группе
"""
if not event.media_group_id:
return 0
if event.media_group_id not in self.album_data:
self.album_data[event.media_group_id] = {"messages": []}
self.album_data[event.media_group_id]["messages"].append(event)
return len(self.album_data[event.media_group_id]["messages"])
async def _collect_album_background(self, media_group_id: str) -> None:
"""
Фоновая задача для сбора всех сообщений медиагруппы.
Args:
media_group_id: ID медиагруппы для сбора
"""
try:
await asyncio.sleep(self.latency)
if media_group_id not in self.album_data:
return
# Получаем текущий список сообщений
album_messages = self.album_data[media_group_id]["messages"].copy()
album_messages.sort(key=lambda x: x.message_id)
# Сохраняем собранную медиагруппу и уведомляем через Event
self.album_data[media_group_id]["collected_album"] = album_messages
self.album_data[media_group_id]["event"].set()
# Очищаем данные после небольшой задержки (чтобы handler успел получить album)
await asyncio.sleep(1.0)
if media_group_id in self.album_data:
@@ -114,24 +116,24 @@ class AlbumMiddleware(BaseMiddleware):
async def __call__(self, handler, event: Message, data: Dict[str, Any]) -> Any:
"""
Основная логика middleware.
Для медиагрупп: сразу вызывает handler, передавая Event для получения полной медиагруппы.
Для обычных сообщений: сразу вызывает handler.
Args:
handler: Обработчик события
event: Событие (сообщение)
data: Данные для передачи в обработчик
Returns:
Результат выполнения обработчика
"""
if not event.media_group_id:
return await handler(event, data)
media_group_id = event.media_group_id
message_id = event.message_id
# Если это первое сообщение медиагруппы - создаем структуру данных
is_first_message = False
if media_group_id not in self.album_data:
@@ -141,27 +143,25 @@ class AlbumMiddleware(BaseMiddleware):
"messages": [],
"event": album_event,
"task": None,
"first_message_id": message_id
"first_message_id": message_id,
}
# Запускаем фоновую задачу для сбора медиагруппы
task = asyncio.create_task(self._collect_album_background(media_group_id))
self.album_data[media_group_id]["task"] = task
# Добавляем сообщение в медиагруппу
self.album_data[media_group_id]["messages"].append(event)
# Обрабатываем только первое сообщение медиагруппы
if not is_first_message:
# Для остальных сообщений просто возвращаемся, не вызывая handler
return
# Передаем объект-геттер в data, чтобы handler мог получить полную медиагруппу
album_getter = AlbumGetter(
self.album_data,
media_group_id,
self.album_data[media_group_id]["event"]
self.album_data, media_group_id, self.album_data[media_group_id]["event"]
)
data["album_getter"] = album_getter
# Сразу вызываем handler только для первого сообщения (не блокируем)
return await handler(event, data)

View File

@@ -4,6 +4,7 @@ from typing import Any, Dict
from aiogram import BaseMiddleware, types
from aiogram.types import CallbackQuery, Message, TelegramObject
from helper_bot.utils.base_dependency_factory import get_global_instance
from logs.custom_logger import logger
@@ -12,47 +13,61 @@ BotDB = bdf.get_db()
class BlacklistMiddleware(BaseMiddleware):
async def __call__(self, handler, event: TelegramObject, data: Dict[str, Any]) -> Any:
async def __call__(
self, handler, event: TelegramObject, data: Dict[str, Any]
) -> Any:
# Проверяем тип события и получаем пользователя
user = None
if isinstance(event, Message):
user = event.from_user
elif isinstance(event, CallbackQuery):
user = event.from_user
# Если это не сообщение или callback, пропускаем проверку
if not user:
return await handler(event, data)
logger.info(f'Вызов BlacklistMiddleware для пользователя {user.username}')
logger.info(f"Вызов BlacklistMiddleware для пользователя {user.username}")
# Используем асинхронную версию для предотвращения блокировки
if await BotDB.check_user_in_blacklist(user.id):
logger.info(f'BlacklistMiddleware результат для пользователя: {user.username} заблокирован!')
logger.info(
f"BlacklistMiddleware результат для пользователя: {user.username} заблокирован!"
)
user_info = await BotDB.get_blacklist_users_by_id(user.id)
# Экранируем потенциально проблемные символы
reason = html.escape(str(user_info[1])) if user_info and user_info[1] else "Не указана"
reason = (
html.escape(str(user_info[1]))
if user_info and user_info[1]
else "Не указана"
)
# Преобразуем timestamp в человекочитаемый формат
if user_info and user_info[2]:
try:
timestamp = int(user_info[2])
date_unban = datetime.fromtimestamp(timestamp).strftime("%d-%m-%Y %H:%M")
date_unban = datetime.fromtimestamp(timestamp).strftime(
"%d-%m-%Y %H:%M"
)
except (ValueError, TypeError):
date_unban = "Не указана"
else:
date_unban = "Не указана"
# Отправляем сообщение в зависимости от типа события
if isinstance(event, Message):
await event.answer(
f"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {date_unban}")
f"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {date_unban}"
)
elif isinstance(event, CallbackQuery):
await event.answer(
f"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {date_unban}",
show_alert=True)
show_alert=True,
)
return False
logger.info(f'BlacklistMiddleware результат для пользователя: {user.username} доступ разрешен')
logger.info(
f"BlacklistMiddleware результат для пользователя: {user.username} доступ разрешен"
)
return await handler(event, data)

View File

@@ -2,30 +2,35 @@ from typing import Any, Dict
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from helper_bot.utils.base_dependency_factory import get_global_instance
from logs.custom_logger import logger
class DependenciesMiddleware(BaseMiddleware):
"""Универсальная middleware для внедрения зависимостей во все хендлеры"""
async def __call__(self, handler, event: TelegramObject, data: Dict[str, Any]) -> Any:
async def __call__(
self, handler, event: TelegramObject, data: Dict[str, Any]
) -> Any:
try:
# Получаем глобальные зависимости
bdf = get_global_instance()
# Внедряем зависимости в data для MagicData
if 'bot_db' not in data:
data['bot_db'] = bdf.get_db()
if 'settings' not in data:
data['settings'] = bdf.settings
data['bot'] = data.get('bot')
data['dp'] = data.get('dp')
logger.debug(f"DependenciesMiddleware: внедрены зависимости для {type(event).__name__}")
if "bot_db" not in data:
data["bot_db"] = bdf.get_db()
if "settings" not in data:
data["settings"] = bdf.settings
data["bot"] = data.get("bot")
data["dp"] = data.get("dp")
logger.debug(
f"DependenciesMiddleware: внедрены зависимости для {type(event).__name__}"
)
except Exception as e:
logger.error(f"Ошибка в DependenciesMiddleware: {e}")
# Не прерываем выполнение, продолжаем без зависимостей
return await handler(event, data)

View File

@@ -16,16 +16,16 @@ from ..utils.metrics import metrics
# Import button command mapping
try:
from ..handlers.admin.constants import (ADMIN_BUTTON_COMMAND_MAPPING,
ADMIN_COMMANDS)
from ..handlers.admin.constants import ADMIN_BUTTON_COMMAND_MAPPING, ADMIN_COMMANDS
from ..handlers.callback.constants import CALLBACK_COMMAND_MAPPING
from ..handlers.private.constants import BUTTON_COMMAND_MAPPING
from ..handlers.voice.constants import \
BUTTON_COMMAND_MAPPING as VOICE_BUTTON_COMMAND_MAPPING
from ..handlers.voice.constants import \
CALLBACK_COMMAND_MAPPING as VOICE_CALLBACK_COMMAND_MAPPING
from ..handlers.voice.constants import \
COMMAND_MAPPING as VOICE_COMMAND_MAPPING
from ..handlers.voice.constants import (
BUTTON_COMMAND_MAPPING as VOICE_BUTTON_COMMAND_MAPPING,
)
from ..handlers.voice.constants import (
CALLBACK_COMMAND_MAPPING as VOICE_CALLBACK_COMMAND_MAPPING,
)
from ..handlers.voice.constants import COMMAND_MAPPING as VOICE_COMMAND_MAPPING
except ImportError:
# Fallback if constants not available
BUTTON_COMMAND_MAPPING = {}
@@ -39,40 +39,49 @@ except ImportError:
class MetricsMiddleware(BaseMiddleware):
"""Enhanced middleware for automatic collection of ALL available metrics."""
def __init__(self):
super().__init__()
self.logger = logging.getLogger(__name__)
# Metrics update intervals
self.last_active_users_update = 0
self.active_users_update_interval = 300 # 5 minutes
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any]
data: Dict[str, Any],
) -> Any:
"""Process event and collect comprehensive metrics."""
# Update active users periodically
current_time = time.time()
if current_time - self.last_active_users_update > self.active_users_update_interval:
if (
current_time - self.last_active_users_update
> self.active_users_update_interval
):
await self._update_active_users_metric()
self.last_active_users_update = current_time
# Extract command and event info
command_info = None
event_metrics = {}
# Process event based on type
if hasattr(event, 'message') and event.message:
event_metrics = await self._record_comprehensive_message_metrics(event.message)
if hasattr(event, "message") and event.message:
event_metrics = await self._record_comprehensive_message_metrics(
event.message
)
command_info = self._extract_command_info_with_fallback(event.message)
elif hasattr(event, 'callback_query') and event.callback_query:
event_metrics = await self._record_comprehensive_callback_metrics(event.callback_query)
command_info = self._extract_callback_command_info_with_fallback(event.callback_query)
elif hasattr(event, "callback_query") and event.callback_query:
event_metrics = await self._record_comprehensive_callback_metrics(
event.callback_query
)
command_info = self._extract_callback_command_info_with_fallback(
event.callback_query
)
elif isinstance(event, Message):
event_metrics = await self._record_comprehensive_message_metrics(event)
command_info = self._extract_command_info_with_fallback(event)
@@ -81,107 +90,106 @@ class MetricsMiddleware(BaseMiddleware):
command_info = self._extract_callback_command_info_with_fallback(event)
else:
event_metrics = await self._record_unknown_event_metrics(event)
if command_info:
self.logger.info(f"📊 Command info extracted: {command_info}")
else:
self.logger.warning(f"📊 No command info extracted for event type: {type(event).__name__}")
self.logger.warning(
f"📊 No command info extracted for event type: {type(event).__name__}"
)
# Execute handler with comprehensive timing and metrics
start_time = time.time()
try:
result = await handler(event, data)
duration = time.time() - start_time
# Record successful execution metrics
handler_name = self._get_handler_name(handler)
metrics.record_method_duration(
handler_name,
duration,
"handler",
"success"
)
metrics.record_method_duration(handler_name, duration, "handler", "success")
if command_info:
metrics.record_command(
command_info['command'],
command_info['handler_type'],
command_info['user_type'],
"success"
command_info["command"],
command_info["handler_type"],
command_info["user_type"],
"success",
)
await self._record_additional_success_metrics(event, event_metrics, handler_name)
await self._record_additional_success_metrics(
event, event_metrics, handler_name
)
return result
except Exception as e:
duration = time.time() - start_time
# Record error metrics
handler_name = self._get_handler_name(handler)
error_type = type(e).__name__
metrics.record_method_duration(
handler_name,
duration,
"handler",
"error"
)
metrics.record_error(
error_type,
"handler",
handler_name
)
metrics.record_method_duration(handler_name, duration, "handler", "error")
metrics.record_error(error_type, "handler", handler_name)
if command_info:
metrics.record_command(
command_info['command'],
command_info['handler_type'],
command_info['user_type'],
"error"
command_info["command"],
command_info["handler_type"],
command_info["user_type"],
"error",
)
await self._record_additional_error_metrics(event, event_metrics, handler_name, error_type)
await self._record_additional_error_metrics(
event, event_metrics, handler_name, error_type
)
raise
finally:
# Record middleware execution time
middleware_duration = time.time() - start_time
metrics.record_middleware("MetricsMiddleware", middleware_duration, "success")
metrics.record_middleware(
"MetricsMiddleware", middleware_duration, "success"
)
async def _update_active_users_metric(self):
"""Periodically update active users metric from database."""
try:
#TODO: Должна подключаться к базе данных, а не к глобальному экземпляру
# TODO: Должна подключаться к базе данных, а не к глобальному экземпляру
from ..utils.base_dependency_factory import get_global_instance
bdf = get_global_instance()
bot_db = bdf.get_db()
# Используем правильные методы AsyncBotDB для выполнения запросов
# Простой подсчет всех пользователей в базе
total_users_query = "SELECT COUNT(DISTINCT user_id) as total FROM our_users"
total_users_result = await bot_db.fetch_one(total_users_query)
total_users = total_users_result['total'] if total_users_result else 1
total_users = total_users_result["total"] if total_users_result else 1
# Подсчет активных за день пользователей (date_changed - это Unix timestamp)
daily_users_query = "SELECT COUNT(DISTINCT user_id) as daily FROM our_users WHERE date_changed > (strftime('%s', 'now', '-1 day'))"
daily_users_result = await bot_db.fetch_one(daily_users_query)
daily_users = daily_users_result['daily'] if daily_users_result else 1
daily_users = daily_users_result["daily"] if daily_users_result else 1
# Устанавливаем метрики с правильными лейблами
metrics.set_active_users(daily_users, "daily")
metrics.set_total_users(total_users)
self.logger.info(f"📊 Active users metric updated: {daily_users} (daily), {total_users} (total)")
self.logger.info(
f"📊 Active users metric updated: {daily_users} (daily), {total_users} (total)"
)
except Exception as e:
self.logger.error(f"❌ Failed to update users metric: {e}")
# Устанавливаем 1 как fallback
metrics.set_active_users(1, "daily")
metrics.set_total_users(1)
async def _record_comprehensive_message_metrics(self, message: Message) -> Dict[str, Any]:
async def _record_comprehensive_message_metrics(
self, message: Message
) -> Dict[str, Any]:
"""Record comprehensive message metrics."""
# Determine message type
message_type = "text"
@@ -199,7 +207,7 @@ class MetricsMiddleware(BaseMiddleware):
message_type = "sticker"
elif message.animation:
message_type = "animation"
# Determine chat type
chat_type = "private"
if message.chat.type == ChatType.GROUP:
@@ -208,129 +216,139 @@ class MetricsMiddleware(BaseMiddleware):
chat_type = "supergroup"
elif message.chat.type == ChatType.CHANNEL:
chat_type = "channel"
# Record message processing
metrics.record_message(message_type, chat_type, "message_handler")
return {
'message_type': message_type,
'chat_type': chat_type,
'user_id': message.from_user.id if message.from_user else None,
'is_bot': message.from_user.is_bot if message.from_user else False
"message_type": message_type,
"chat_type": chat_type,
"user_id": message.from_user.id if message.from_user else None,
"is_bot": message.from_user.is_bot if message.from_user else False,
}
async def _record_comprehensive_callback_metrics(self, callback: CallbackQuery) -> Dict[str, Any]:
async def _record_comprehensive_callback_metrics(
self, callback: CallbackQuery
) -> Dict[str, Any]:
"""Record comprehensive callback metrics."""
# Record callback message
metrics.record_message("callback_query", "callback", "callback_handler")
return {
'callback_data': callback.data,
'user_id': callback.from_user.id if callback.from_user else None,
'is_bot': callback.from_user.is_bot if callback.from_user else False
"callback_data": callback.data,
"user_id": callback.from_user.id if callback.from_user else None,
"is_bot": callback.from_user.is_bot if callback.from_user else False,
}
async def _record_unknown_event_metrics(self, event: TelegramObject) -> Dict[str, Any]:
async def _record_unknown_event_metrics(
self, event: TelegramObject
) -> Dict[str, Any]:
"""Record metrics for unknown event types."""
# Record unknown event
metrics.record_message("unknown", "unknown", "unknown_handler")
return {
'event_type': type(event).__name__,
'event_data': str(event)[:100] if hasattr(event, '__str__') else "unknown"
"event_type": type(event).__name__,
"event_data": str(event)[:100] if hasattr(event, "__str__") else "unknown",
}
def _extract_command_info_with_fallback(self, message: Message) -> Optional[Dict[str, str]]:
def _extract_command_info_with_fallback(
self, message: Message
) -> Optional[Dict[str, str]]:
"""Extract command information with fallback for unknown commands."""
if not message.text:
return None
# Check if it's a slash command
if message.text.startswith('/'):
command_name = message.text.split()[0][1:] # Remove '/' and get command name
if message.text.startswith("/"):
command_name = message.text.split()[0][
1:
] # Remove '/' and get command name
# Check if it's an admin command
if command_name in ADMIN_COMMANDS:
return {
'command': ADMIN_COMMANDS[command_name],
'user_type': "admin" if message.from_user else "unknown",
'handler_type': "admin_handler"
"command": ADMIN_COMMANDS[command_name],
"user_type": "admin" if message.from_user else "unknown",
"handler_type": "admin_handler",
}
# Check if it's a voice bot command
elif command_name in VOICE_COMMAND_MAPPING:
return {
'command': VOICE_COMMAND_MAPPING[command_name],
'user_type': "user" if message.from_user else "unknown",
'handler_type': "voice_command_handler"
"command": VOICE_COMMAND_MAPPING[command_name],
"user_type": "user" if message.from_user else "unknown",
"handler_type": "voice_command_handler",
}
else:
# FALLBACK: Record unknown command
return {
'command': command_name,
'user_type': "user" if message.from_user else "unknown",
'handler_type': "unknown_command_handler"
"command": command_name,
"user_type": "user" if message.from_user else "unknown",
"handler_type": "unknown_command_handler",
}
# Check if it's an admin button click
if message.text in ADMIN_BUTTON_COMMAND_MAPPING:
return {
'command': ADMIN_BUTTON_COMMAND_MAPPING[message.text],
'user_type': "admin" if message.from_user else "unknown",
'handler_type': "admin_button_handler"
"command": ADMIN_BUTTON_COMMAND_MAPPING[message.text],
"user_type": "admin" if message.from_user else "unknown",
"handler_type": "admin_button_handler",
}
# Check if it's a regular button click (text button)
if message.text in BUTTON_COMMAND_MAPPING:
return {
'command': BUTTON_COMMAND_MAPPING[message.text],
'user_type': "user" if message.from_user else "unknown",
'handler_type': "button_handler"
"command": BUTTON_COMMAND_MAPPING[message.text],
"user_type": "user" if message.from_user else "unknown",
"handler_type": "button_handler",
}
# Check if it's a voice bot button click
if message.text in VOICE_BUTTON_COMMAND_MAPPING:
return {
'command': VOICE_BUTTON_COMMAND_MAPPING[message.text],
'user_type': "user" if message.from_user else "unknown",
'handler_type': "voice_button_handler"
"command": VOICE_BUTTON_COMMAND_MAPPING[message.text],
"user_type": "user" if message.from_user else "unknown",
"handler_type": "voice_button_handler",
}
# FALLBACK: Record ANY text message as a command for metrics
if message.text and len(message.text.strip()) > 0:
return {
'command': f"text",
'user_type': "user" if message.from_user else "unknown",
'handler_type': "text_message_handler"
"command": f"text",
"user_type": "user" if message.from_user else "unknown",
"handler_type": "text_message_handler",
}
return None
def _extract_callback_command_info_with_fallback(self, callback: CallbackQuery) -> Optional[Dict[str, str]]:
def _extract_callback_command_info_with_fallback(
self, callback: CallbackQuery
) -> Optional[Dict[str, str]]:
"""Extract callback command information with fallback."""
if not callback.data:
return None
# Extract command from callback data
parts = callback.data.split(':', 1)
parts = callback.data.split(":", 1)
if parts and parts[0] in CALLBACK_COMMAND_MAPPING:
return {
'command': CALLBACK_COMMAND_MAPPING[parts[0]],
'user_type': "user" if callback.from_user else "unknown",
'handler_type': "callback_handler"
"command": CALLBACK_COMMAND_MAPPING[parts[0]],
"user_type": "user" if callback.from_user else "unknown",
"handler_type": "callback_handler",
}
# Check if it's a voice bot callback
if parts and parts[0] in VOICE_CALLBACK_COMMAND_MAPPING:
return {
'command': VOICE_CALLBACK_COMMAND_MAPPING[parts[0]],
'user_type': "user" if callback.from_user else "unknown",
'handler_type': "voice_callback_handler"
"command": VOICE_CALLBACK_COMMAND_MAPPING[parts[0]],
"user_type": "user" if callback.from_user else "unknown",
"handler_type": "voice_callback_handler",
}
# FALLBACK: Record unknown callback
if parts:
callback_data = parts[0]
# Группируем похожие callback'и по паттернам
if callback_data.startswith("ban_") and callback_data[4:].isdigit():
# callback_ban_123456 -> callback_ban
@@ -341,60 +359,69 @@ class MetricsMiddleware(BaseMiddleware):
else:
# Для остальных неизвестных callback'ов оставляем как есть
command = f"callback_{callback_data[:20]}"
return {
'command': command,
'user_type': "user" if callback.from_user else "unknown",
'handler_type': "unknown_callback_handler"
"command": command,
"user_type": "user" if callback.from_user else "unknown",
"handler_type": "unknown_callback_handler",
}
return None
async def _record_additional_success_metrics(self, event: TelegramObject, event_metrics: Dict[str, Any], handler_name: str):
async def _record_additional_success_metrics(
self, event: TelegramObject, event_metrics: Dict[str, Any], handler_name: str
):
"""Record additional success metrics."""
try:
# Record rate limiting metrics (if applicable)
if hasattr(event, 'from_user') and event.from_user:
if hasattr(event, "from_user") and event.from_user:
# You can add rate limiting logic here
pass
# Record user activity metrics
if event_metrics.get('user_id'):
if event_metrics.get("user_id"):
# This could trigger additional user activity tracking
pass
except Exception as e:
self.logger.error(f"❌ Error recording additional success metrics: {e}")
async def _record_additional_error_metrics(self, event: TelegramObject, event_metrics: Dict[str, Any], handler_name: str, error_type: str):
async def _record_additional_error_metrics(
self,
event: TelegramObject,
event_metrics: Dict[str, Any],
handler_name: str,
error_type: str,
):
"""Record additional error metrics."""
try:
# Record specific error context
if event_metrics.get('user_id'):
if event_metrics.get("user_id"):
# You can add user-specific error tracking here
pass
except Exception as e:
self.logger.error(f"❌ Error recording additional error metrics: {e}")
def _get_handler_name(self, handler: Callable) -> str:
"""Extract handler name efficiently."""
# Check various ways to get handler name
if hasattr(handler, '__name__') and handler.__name__ != '<lambda>':
if hasattr(handler, "__name__") and handler.__name__ != "<lambda>":
return handler.__name__
elif hasattr(handler, '__qualname__') and handler.__qualname__ != '<lambda>':
elif hasattr(handler, "__qualname__") and handler.__qualname__ != "<lambda>":
return handler.__qualname__
elif hasattr(handler, 'callback') and hasattr(handler.callback, '__name__'):
elif hasattr(handler, "callback") and hasattr(handler.callback, "__name__"):
return handler.callback.__name__
elif hasattr(handler, 'view') and hasattr(handler.view, '__name__'):
elif hasattr(handler, "view") and hasattr(handler.view, "__name__"):
return handler.view.__name__
else:
# Пытаемся получить имя из строкового представления
handler_str = str(handler)
if 'function' in handler_str:
if "function" in handler_str:
# Извлекаем имя функции из строки
import re
match = re.search(r'function\s+(\w+)', handler_str)
match = re.search(r"function\s+(\w+)", handler_str)
if match:
return match.group(1)
return "unknown"
@@ -402,83 +429,77 @@ class MetricsMiddleware(BaseMiddleware):
class DatabaseMetricsMiddleware(BaseMiddleware):
"""Enhanced middleware for database operation metrics."""
def __init__(self):
super().__init__()
self.logger = logging.getLogger(__name__)
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any]
data: Dict[str, Any],
) -> Any:
"""Process event and collect database metrics."""
# Check if this handler involves database operations
handler_name = handler.__name__ if hasattr(handler, '__name__') else "unknown"
handler_name = handler.__name__ if hasattr(handler, "__name__") else "unknown"
# Record middleware start
start_time = time.time()
try:
result = await handler(event, data)
# Record successful database operation
duration = time.time() - start_time
metrics.record_middleware("DatabaseMetricsMiddleware", duration, "success")
return result
except Exception as e:
# Record failed database operation
duration = time.time() - start_time
metrics.record_middleware("DatabaseMetricsMiddleware", duration, "error")
metrics.record_error(
type(e).__name__,
"database_middleware",
handler_name
)
metrics.record_error(type(e).__name__, "database_middleware", handler_name)
raise
class ErrorMetricsMiddleware(BaseMiddleware):
"""Enhanced middleware for error tracking and metrics."""
def __init__(self):
super().__init__()
self.logger = logging.getLogger(__name__)
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any]
data: Dict[str, Any],
) -> Any:
"""Process event and collect error metrics."""
# Record middleware start
start_time = time.time()
try:
result = await handler(event, data)
# Record successful error handling
duration = time.time() - start_time
metrics.record_middleware("ErrorMetricsMiddleware", duration, "success")
return result
except Exception as e:
# Record error metrics
duration = time.time() - start_time
handler_name = handler.__name__ if hasattr(handler, '__name__') else "unknown"
metrics.record_middleware("ErrorMetricsMiddleware", duration, "error")
metrics.record_error(
type(e).__name__,
"error_middleware",
handler_name
handler_name = (
handler.__name__ if hasattr(handler, "__name__") else "unknown"
)
metrics.record_middleware("ErrorMetricsMiddleware", duration, "error")
metrics.record_error(type(e).__name__, "error_middleware", handler_name)
raise

View File

@@ -1,42 +1,43 @@
"""
Middleware для автоматического применения rate limiting ко всем входящим сообщениям
"""
from typing import Any, Awaitable, Callable, Dict, Union
from aiogram import BaseMiddleware
from aiogram.exceptions import TelegramAPIError, TelegramRetryAfter
from aiogram.types import (CallbackQuery, ChatMemberUpdated, InlineQuery,
Message, Update)
from aiogram.types import CallbackQuery, ChatMemberUpdated, InlineQuery, Message, Update
from helper_bot.utils.rate_limiter import telegram_rate_limiter
from logs.custom_logger import logger
class RateLimitMiddleware(BaseMiddleware):
"""Middleware для автоматического rate limiting входящих сообщений"""
def __init__(self):
super().__init__()
self.rate_limiter = telegram_rate_limiter
async def __call__(
self,
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Union[Update, Message, CallbackQuery, InlineQuery, ChatMemberUpdated],
data: Dict[str, Any]
data: Dict[str, Any],
) -> Any:
"""Обрабатывает событие с rate limiting"""
# Извлекаем сообщение из Update
message = None
if isinstance(event, Update):
message = event.message
elif isinstance(event, Message):
message = event
# Применяем rate limiting только к сообщениям
if message is not None:
chat_id = message.chat.id
# Обертываем handler в rate limiting
async def rate_limited_handler():
try:
@@ -46,13 +47,11 @@ class RateLimitMiddleware(BaseMiddleware):
# Middleware не должен перехватывать эти ошибки,
# пусть их обрабатывает rate_limiter в функциях отправки
raise
# Применяем rate limiting к handler
return await self.rate_limiter.send_with_rate_limit(
rate_limited_handler,
chat_id
rate_limited_handler, chat_id
)
else:
# Для других типов событий просто вызываем handler
return await handler(event, data)

View File

@@ -12,7 +12,6 @@ class BulkTextMiddleware(BaseMiddleware):
self.latency = latency
self.texts = defaultdict(list)
async def __call__(self, handler, event: Message, data: Dict[str, Any]) -> Any:
"""
Main middleware logic.
@@ -37,10 +36,9 @@ class BulkTextMiddleware(BaseMiddleware):
# # Sort the album messages by message_id and add to data
msg_texts = self.texts[key]
msg_texts.sort(key=lambda x: x.message_id)
data["texts"] = ''.join([msg.text for msg in msg_texts])
data["texts"] = "".join([msg.text for msg in msg_texts])
#
# Remove the media group from tracking to free up memory
del self.texts[key]
# # Call the original event handler
return await handler(event, data)