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,20 +1,45 @@
"""Private handlers package for Telegram bot"""
from .private_handlers import private_router, create_private_handlers, PrivateHandlers
from .services import BotSettings, UserService, PostService, StickerService
from .constants import FSM_STATES, BUTTON_TEXTS, ERROR_MESSAGES
# Local imports - main components
from .private_handlers import (
private_router,
create_private_handlers,
PrivateHandlers
)
# Local imports - services
from .services import (
BotSettings,
UserService,
PostService,
StickerService
)
# Local imports - constants and utilities
from .constants import (
FSM_STATES,
BUTTON_TEXTS,
ERROR_MESSAGES
)
from .decorators import error_handler
__all__ = [
# Main components
'private_router',
'create_private_handlers',
'PrivateHandlers',
# Services
'BotSettings',
'UserService',
'PostService',
'StickerService',
# Constants
'FSM_STATES',
'BUTTON_TEXTS',
'ERROR_MESSAGES',
# Utilities
'error_handler'
]

View File

@@ -1,7 +1,9 @@
"""Constants for private handlers"""
from typing import Final
# FSM States
FSM_STATES = {
FSM_STATES: Final[dict[str, str]] = {
"START": "START",
"SUGGEST": "SUGGEST",
"PRE_CHAT": "PRE_CHAT",
@@ -9,7 +11,7 @@ FSM_STATES = {
}
# Button texts
BUTTON_TEXTS = {
BUTTON_TEXTS: Final[dict[str, str]] = {
"SUGGEST_POST": "📢Предложить свой пост",
"SAY_GOODBYE": "👋🏼Сказать пока!",
"LEAVE_CHAT": "Выйти из чата",
@@ -19,7 +21,7 @@ BUTTON_TEXTS = {
}
# Error messages
ERROR_MESSAGES = {
ERROR_MESSAGES: Final[dict[str, str]] = {
"UNSUPPORTED_CONTENT": (
'Я пока не умею работать с таким сообщением. '
'Пришли текст и фото/фоты(ы). А лучше перешли это сообщение админу @kerrad1\n'

View File

@@ -1,13 +1,19 @@
"""Decorators and utility functions for private 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):
def error_handler(func: Callable[..., Any]) -> Callable[..., Any]:
"""Decorator for centralized error handling"""
async def wrapper(*args, **kwargs):
async def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return await func(*args, **kwargs)
except Exception as e:
@@ -23,7 +29,8 @@ def error_handler(func):
chat_id=important_logs,
text=f"Произошла ошибка в {func.__name__}: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
)
except:
pass # If we can't log the error, at least it was logged to logger
except Exception:
# If we can't log the error, at least it was logged to logger
pass
raise
return wrapper

View File

@@ -1,28 +1,30 @@
import random
import traceback
import asyncio
import html
from datetime import datetime
from pathlib import Path
"""Main private handlers module for Telegram bot"""
# Standard library imports
import asyncio
from datetime import datetime
# Third-party imports
from aiogram import types, Router, F
from aiogram.filters import Command, StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.types import FSInputFile
# Local imports - filters and middlewares
from helper_bot.filters.main import ChatTypeFilter
from helper_bot.keyboards import get_reply_keyboard, get_reply_keyboard_for_post
from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat
from helper_bot.middlewares.album_middleware import AlbumMiddleware
from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware
from helper_bot.utils import messages
from helper_bot.utils.helper_func import get_first_name, get_text_message, send_text_message, send_photo_message, \
send_media_group_message_to_private_chat, prepare_media_group_from_middlewares, send_video_message, \
send_video_note_message, send_audio_message, send_voice_message, add_in_db_media, \
check_user_emoji, check_username_and_full_name, update_user_info
from logs.custom_logger import logger
# Import new modular components
# Local imports - utilities
from helper_bot.keyboards import get_reply_keyboard, get_reply_keyboard_for_post
from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat
from helper_bot.utils import messages
from helper_bot.utils.helper_func import (
get_first_name,
update_user_info,
check_user_emoji
)
# Local imports - modular components
from .constants import FSM_STATES, BUTTON_TEXTS, ERROR_MESSAGES
from .services import BotSettings, UserService, PostService, StickerService
from .decorators import error_handler
@@ -112,10 +114,7 @@ class PrivateHandlers:
markup = types.ReplyKeyboardRemove()
suggest_news = messages.get_message(get_first_name(message), 'SUGGEST_NEWS')
await message.answer(suggest_news)
await asyncio.sleep(0.3)
suggest_news_2 = messages.get_message(get_first_name(message), 'SUGGEST_NEWS_2')
await message.answer(suggest_news_2, reply_markup=markup)
await message.answer(suggest_news, reply_markup=markup)
@error_handler
async def end_message(self, message: types.Message, state: FSMContext, **kwargs):

View File

@@ -1,25 +1,50 @@
"""Service classes for private handlers"""
# Standard library imports
import random
import asyncio
import html
from datetime import datetime
from pathlib import Path
from typing import Dict, Callable
from typing import Dict, Callable, Any, Protocol, Union
from dataclasses import dataclass
# Third-party imports
from aiogram import types
from aiogram.types import FSInputFile
# Local imports - utilities
from helper_bot.utils.helper_func import (
get_first_name, get_text_message, send_text_message, send_photo_message,
send_media_group_message_to_private_chat, prepare_media_group_from_middlewares,
send_video_message, send_video_note_message, send_audio_message, send_voice_message,
add_in_db_media, check_username_and_full_name
get_first_name,
get_text_message,
send_text_message,
send_photo_message,
send_media_group_message_to_private_chat,
prepare_media_group_from_middlewares,
send_video_message,
send_video_note_message,
send_audio_message,
send_voice_message,
add_in_db_media,
check_username_and_full_name
)
from helper_bot.keyboards import get_reply_keyboard_for_post
class DatabaseProtocol(Protocol):
"""Protocol for database operations"""
def user_exists(self, user_id: int) -> bool: ...
def add_new_user_in_db(self, user_id: int, first_name: str, full_name: str,
username: str, is_bot: bool, language_code: str,
emoji: str, created_date: str, updated_date: str) -> None: ...
def update_username_and_full_name(self, user_id: int, username: str, full_name: str) -> None: ...
def update_date_for_user(self, date: str, user_id: int) -> None: ...
def add_post_in_db(self, message_id: int, text: str, user_id: int) -> None: ...
def update_info_about_stickers(self, user_id: int) -> None: ...
def add_new_message_in_db(self, text: str, user_id: int, message_id: int, date: str) -> None: ...
def update_helper_message_in_db(self, message_id: int, helper_message_id: int) -> None: ...
@dataclass
class BotSettings:
"""Bot configuration settings"""
@@ -36,7 +61,7 @@ class BotSettings:
class UserService:
"""Service for user-related operations"""
def __init__(self, db, settings: BotSettings):
def __init__(self, db: DatabaseProtocol, settings: BotSettings) -> None:
self.db = db
self.settings = settings
@@ -90,7 +115,7 @@ class UserService:
class PostService:
"""Service for post-related operations"""
def __init__(self, db, settings: BotSettings):
def __init__(self, db: DatabaseProtocol, settings: BotSettings) -> None:
self.db = db
self.settings = settings
@@ -168,7 +193,7 @@ class PostService:
"""Handle media group post submission"""
post_caption = " "
if album[0].caption:
if album and album[0].caption:
post_caption = get_text_message(album[0].caption.lower(), first_name, message.from_user.username)
media_group = await prepare_media_group_from_middlewares(album, post_caption)
@@ -185,7 +210,7 @@ class PostService:
message_id=media_group_message_id, helper_message_id=help_message_id
)
async def process_post(self, message: types.Message, album: list = None) -> None:
async def process_post(self, message: types.Message, album: Union[list[types.Message], None] = None) -> None:
"""Process post based on content type"""
first_name = get_first_name(message)
@@ -220,12 +245,14 @@ class PostService:
class StickerService:
"""Service for sticker-related operations"""
def __init__(self, settings: BotSettings):
def __init__(self, settings: BotSettings) -> None:
self.settings = settings
async def send_random_hello_sticker(self, message: types.Message) -> None:
"""Send random hello sticker"""
name_stick_hello = list(Path('Stick').rglob('Hello_*'))
if not name_stick_hello:
return
random_stick_hello = random.choice(name_stick_hello)
random_stick_hello = FSInputFile(path=random_stick_hello)
await message.answer_sticker(random_stick_hello)
@@ -234,6 +261,8 @@ class StickerService:
async def send_random_goodbye_sticker(self, message: types.Message) -> None:
"""Send random goodbye sticker"""
name_stick_bye = list(Path('Stick').rglob('Universal_*'))
if not name_stick_bye:
return
random_stick_bye = random.choice(name_stick_bye)
random_stick_bye = FSInputFile(path=random_stick_bye)
await message.answer_sticker(random_stick_bye)