Add middleware and refactor admin handlers for improved functionality

- Introduced `DependenciesMiddleware` and `BlacklistMiddleware` for enhanced request handling across all routers.
- Refactored admin handlers to utilize new middleware, improving access control and error handling.
- Updated the `admin_router` to include middleware for access checks and streamlined the process of banning users.
- Enhanced the structure of admin handler imports for better organization and maintainability.
- Improved error handling in various admin functions to ensure robust user interactions.
This commit is contained in:
2025-08-28 23:54:17 +03:00
parent f75e7f82c9
commit 8cee629e28
32 changed files with 1922 additions and 1574 deletions

View File

@@ -1 +1,47 @@
from .group_handlers import group_router
"""Group handlers package for Telegram bot"""
# Local imports - main components
from .group_handlers import (
group_router,
create_group_handlers,
GroupHandlers
)
# Local imports - services
from .services import (
AdminReplyService,
DatabaseProtocol
)
# Local imports - constants and utilities
from .constants import (
FSM_STATES,
ERROR_MESSAGES
)
from .exceptions import (
NoReplyToMessageError,
UserNotFoundError
)
from .decorators import error_handler
__all__ = [
# Main components
'group_router',
'create_group_handlers',
'GroupHandlers',
# Services
'AdminReplyService',
'DatabaseProtocol',
# Constants
'FSM_STATES',
'ERROR_MESSAGES',
# Exceptions
'NoReplyToMessageError',
'UserNotFoundError',
# Utilities
'error_handler'
]

View File

@@ -0,0 +1,14 @@
"""Constants for group handlers"""
from typing import Final
# FSM States
FSM_STATES: Final[dict[str, str]] = {
"CHAT": "CHAT"
}
# Error messages
ERROR_MESSAGES: Final[dict[str, str]] = {
"NO_REPLY_TO_MESSAGE": "Блять, выдели сообщение!",
"USER_NOT_FOUND": "Не могу найти кому ответить в базе, проебали сообщение."
}

View File

@@ -0,0 +1,36 @@
"""Decorators and utility functions for group handlers"""
# Standard library imports
import traceback
from typing import Any, Callable
# Third-party imports
from aiogram import types
# Local imports
from logs.custom_logger import logger
def error_handler(func: Callable[..., Any]) -> Callable[..., Any]:
"""Decorator for centralized error handling"""
async def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return await func(*args, **kwargs)
except Exception as e:
logger.error(f"Error in {func.__name__}: {str(e)}")
# Try to send error to logs if possible
try:
message = next((arg for arg in args if isinstance(arg, types.Message)), None)
if message and hasattr(message, 'bot'):
from helper_bot.utils.base_dependency_factory import get_global_instance
bdf = get_global_instance()
important_logs = bdf.settings['Telegram']['important_logs']
await message.bot.send_message(
chat_id=important_logs,
text=f"Произошла ошибка в {func.__name__}: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
)
except Exception:
# If we can't log the error, at least it was logged to logger
pass
raise
return wrapper

View File

@@ -0,0 +1,11 @@
"""Custom exceptions for group handlers"""
class NoReplyToMessageError(Exception):
"""Raised when admin tries to reply without selecting a message"""
pass
class UserNotFoundError(Exception):
"""Raised when user is not found in database for the given message_id"""
pass

View File

@@ -1,49 +1,106 @@
"""Main group handlers module for Telegram bot"""
# Third-party imports
from aiogram import Router, types
from aiogram.fsm.context import FSMContext
# Local imports - filters
from helper_bot.filters.main import ChatTypeFilter
from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat
from helper_bot.utils.base_dependency_factory import get_global_instance
from helper_bot.utils.helper_func import send_text_message
# Local imports - modular components
from .constants import FSM_STATES, ERROR_MESSAGES
from .services import AdminReplyService
from .decorators import error_handler
from .exceptions import UserNotFoundError
# Local imports - utilities
from logs.custom_logger import logger
class GroupHandlers:
"""Main handler class for group messages"""
def __init__(self, db, keyboard_markup: types.ReplyKeyboardMarkup):
self.db = db
self.keyboard_markup = keyboard_markup
self.admin_reply_service = AdminReplyService(db)
# Create router
self.router = Router()
# Register handlers
self._register_handlers()
def _register_handlers(self):
"""Register all message handlers"""
self.router.message.register(
self.handle_message,
ChatTypeFilter(chat_type=["group", "supergroup"])
)
@error_handler
async def handle_message(self, message: types.Message, state: FSMContext):
"""Handle admin reply to user through group chat"""
logger.info(
f'Получено сообщение в группе {message.chat.title} (ID: {message.chat.id}) '
f'от пользователя {message.from_user.full_name} (ID: {message.from_user.id}): "{message.text}"'
)
# Check if message is a reply
if not message.reply_to_message:
await message.answer(ERROR_MESSAGES["NO_REPLY_TO_MESSAGE"])
logger.warning(
f'В группе {message.chat.title} (ID: {message.chat.id}) '
f'админ не выделил сообщение для ответа.'
)
return
message_id = message.reply_to_message.message_id
reply_text = message.text
try:
# Get user ID for reply
chat_id = self.admin_reply_service.get_user_id_for_reply(message_id)
# Send reply to user
await self.admin_reply_service.send_reply_to_user(
chat_id, message, reply_text, self.keyboard_markup
)
# Set state
await state.set_state(FSM_STATES["CHAT"])
except UserNotFoundError:
await message.answer(ERROR_MESSAGES["USER_NOT_FOUND"])
logger.error(
f'Ошибка при поиске пользователя в базе для ответа на сообщение: {reply_text} '
f'в группе {message.chat.title} (ID сообщения: {message.message_id})'
)
# Factory function to create handlers with dependencies
def create_group_handlers(db, keyboard_markup: types.ReplyKeyboardMarkup) -> GroupHandlers:
"""Create group handlers instance with dependencies"""
return GroupHandlers(db, keyboard_markup)
# Legacy router for backward compatibility
group_router = Router()
bdf = get_global_instance()
GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts']
GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message']
MAIN_PUBLIC = bdf.settings['Telegram']['main_public']
GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs']
IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs']
PREVIEW_LINK = bdf.settings['Telegram']['preview_link']
LOGS = bdf.settings['Settings']['logs']
TEST = bdf.settings['Settings']['test']
# Initialize with global dependencies (for backward compatibility)
def init_legacy_router():
"""Initialize legacy router with global dependencies"""
global group_router
from helper_bot.utils.base_dependency_factory import get_global_instance
from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat
bdf = get_global_instance()
db = bdf.get_db()
keyboard_markup = get_reply_keyboard_leave_chat()
handlers = create_group_handlers(db, keyboard_markup)
group_router = handlers.router
BotDB = bdf.get_db()
@group_router.message(
ChatTypeFilter(chat_type=["group", "supergroup"]),
)
async def handle_message(message: types.Message, state: FSMContext):
"""Функция ответа админа пользователю через закрытый чат"""
logger.info(
f'Получено сообщение в группе {message.chat.title} (ID: {message.chat.id}) от пользователя {message.from_user.full_name} (ID: {message.from_user.id}): "{message.text}"')
markup = get_reply_keyboard_leave_chat()
message_id = 0
try:
message_id = message.reply_to_message.message_id
except AttributeError as e:
await message.answer('Блять, выдели сообщение!')
logger.warning(
f'В группе {message.chat.title} (ID: {message.chat.id}) админ не выделил сообщение для ответа. Ошибка {str(e)}')
message_from_admin = message.text
try:
chat_id = BotDB.get_user_by_message_id(message_id)
await send_text_message(chat_id, message, message_from_admin, markup)
await state.set_state("CHAT")
logger.info(f'Ответ админа "{message.text}" отправлен пользователю с ID: {chat_id} на сообщение {message_id}')
except TypeError as e:
await message.answer('Не могу найти кому ответить в базе, проебали сообщение.')
logger.error(
f'Ошибка при поиске пользователя в базе для ответа на сообщение: {message.text} в группе {message.chat.title} (ID сообщения: {message.message_id}) Ошибка: {str(e)}')
# Initialize legacy router
init_legacy_router()

View File

@@ -0,0 +1,64 @@
"""Service classes for group handlers"""
# Standard library imports
from typing import Protocol, Optional
# Third-party imports
from aiogram import types
# Local imports
from helper_bot.utils.helper_func import send_text_message
from .exceptions import NoReplyToMessageError, UserNotFoundError
from logs.custom_logger import logger
class DatabaseProtocol(Protocol):
"""Protocol for database operations"""
def get_user_by_message_id(self, message_id: int) -> Optional[int]: ...
class AdminReplyService:
"""Service for admin reply operations"""
def __init__(self, db: DatabaseProtocol) -> None:
self.db = db
def get_user_id_for_reply(self, message_id: int) -> int:
"""
Get user ID for reply by message ID.
Args:
message_id: ID of the message to reply to
Returns:
User ID for the reply
Raises:
UserNotFoundError: If user is not found in database
"""
user_id = self.db.get_user_by_message_id(message_id)
if user_id is None:
raise UserNotFoundError(f"User not found for message_id: {message_id}")
return user_id
async def send_reply_to_user(
self,
chat_id: int,
message: types.Message,
reply_text: str,
markup: types.ReplyKeyboardMarkup
) -> None:
"""
Send reply to user.
Args:
chat_id: User's chat ID
message: Original message from admin
reply_text: Text to send to user
markup: Reply keyboard markup
"""
await send_text_message(chat_id, message, reply_text, markup)
logger.info(
f'Ответ админа "{reply_text}" отправлен пользователю с ID: {chat_id} '
f'на сообщение {message.reply_to_message.message_id if message.reply_to_message else "N/A"}'
)