From 9b56d5a45a20a2f96531d9bee91d183bb4b1490a Mon Sep 17 00:00:00 2001 From: Verum Date: Wed, 25 Feb 2026 17:48:18 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=B0=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=D1=80=D0=B0=20(=D0=92=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B5!)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/handlers/commands/settings/set_avatar.py | 223 +++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 bot/handlers/commands/settings/set_avatar.py diff --git a/bot/handlers/commands/settings/set_avatar.py b/bot/handlers/commands/settings/set_avatar.py new file mode 100644 index 0000000..16f7ae7 --- /dev/null +++ b/bot/handlers/commands/settings/set_avatar.py @@ -0,0 +1,223 @@ +""" +Модуль смены аватарки бота. +Совместим с aiogram 3.22.0 и Bot API 9.4+ + +Использует: +- SetMyProfilePhoto +- InputProfilePhotoStatic +""" + +from __future__ import annotations + +import os +from typing import Union + +from aiogram import Router, Bot, F +from aiogram.filters import Command +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import StatesGroup, State +from aiogram.types import ( + Message, + CallbackQuery, + FSInputFile, + InputProfilePhotoStatic, +) +from aiogram.methods.set_my_profile_photo import SetMyProfilePhoto +from aiogram.exceptions import TelegramAPIError, TelegramRetryAfter +from aiogram.utils.i18n import gettext as _ + +from bot.filters import IsSuperAdmin +from bot.templates import msg +from bot.utils import format_retry_time, status_clear +from bot.core.bots import BotInfo +from bot.handlers.commands.settings.settings_cmd import settings_keyboard +from configs import COMMANDS +from middleware.loggers import logger + +__all__ = ("router",) + +CMD: str = "set_avatar".lower() +router: Router = Router(name=f"{CMD}_router") + + +# ================= FSM ================= + +class SetBotAvatarForm(StatesGroup): + """ + FSM состояния для смены аватарки. + """ + waiting_for_photo: State = State() + + +# ================= CORE ================= + +async def handle_set_avatar( + update: Union[Message, CallbackQuery], + state: FSMContext, + bot: Bot +) -> None: + """ + Устанавливает новую аватарку бота. + + Args: + update: Message или CallbackQuery + state: FSM контекст + bot: Экземпляр бота + """ + + message: Message = update.message if isinstance(update, CallbackQuery) else update + + if not message.photo: + return + + largest_photo = message.photo[-1] + + # Получаем файл от Telegram + tg_file = await bot.get_file(largest_photo.file_id) + + temp_path: str = f"/tmp/{largest_photo.file_unique_id}.jpg" + + await bot.download_file(tg_file.file_path, destination=temp_path) + + try: + method = SetMyProfilePhoto( + photo=InputProfilePhotoStatic( + photo=FSInputFile(temp_path) + ) + ) + + result: bool = await bot(method) + + if result: + logger.info("Аватарка бота успешно обновлена", log_type="BOT_SETUP") + + await state.clear() + + await msg( + update=update, + text=_("✅ Аватарка бота успешно обновлена."), + markup=settings_keyboard(), + state=state + ) + + except TelegramRetryAfter as e: + retry_text: str = format_retry_time(e.retry_after) + + logger.warning( + f"Rate limit при смене аватарки. Повтор через {retry_text}", + log_type="BOT_SETUP" + ) + + await msg( + update=update, + text=_( + "⚠️ Слишком частая смена аватарки.\n" + "Попробуйте снова через: {retry}" + ).format(retry=retry_text), + markup=settings_keyboard(), + state=state + ) + + except TelegramAPIError as e: + logger.error( + f"Ошибка Telegram API при смене аватарки: {e}", + log_type="BOT_SETUP" + ) + + await msg( + update=update, + text=_( + "❌ Ошибка Telegram API:\n" + "
{error}
" + ).format(error=str(e)), + markup=settings_keyboard(), + state=state + ) + + except Exception as e: + logger.error( + f"Непредвиденная ошибка при смене аватарки: {e}", + log_type="BOT_SETUP" + ) + + await msg( + update=update, + text=_( + "❌ Непредвиденная ошибка:\n" + "
{error}
" + ).format(error=str(e)), + markup=settings_keyboard(), + state=state + ) + + finally: + if os.path.exists(temp_path): + os.remove(temp_path) + + +# ================= COMMAND ================= + +@router.callback_query(F.data.lower() == CMD, IsSuperAdmin()) +@router.message( + Command(*COMMANDS.get(CMD, [CMD]), prefix=BotInfo.prefix, ignore_case=True), + IsSuperAdmin() +) +async def set_avatar_cmd( + message: Message | CallbackQuery, + state: FSMContext, + bot: Bot +) -> None: + """ + Команда /set_avatar + + Поддерживает: + - Фото вместе с командой + - Ответ на фото + - FSM режим + """ + + await status_clear(update=message, state=state) + + msg_obj: Message = message.message if isinstance(message, CallbackQuery) else message + + if msg_obj.photo: + await handle_set_avatar(message, state, bot) + return + + if msg_obj.reply_to_message and msg_obj.reply_to_message.photo: + await handle_set_avatar(msg_obj.reply_to_message, state, bot) + return + + await msg( + update=message, + text=_( + "🖼 Смена аватарки бота\n\n" + "Отправьте фотографию." + ), + markup=settings_keyboard(), + state=state + ) + + await state.set_state(SetBotAvatarForm.waiting_for_photo) + + +# ================= FSM ================= + +@router.message(SetBotAvatarForm.waiting_for_photo, IsSuperAdmin(), F.photo) +async def process_avatar_photo( + message: Message, + state: FSMContext, + bot: Bot +) -> None: + """ + Обработка фото через FSM. + """ + await handle_set_avatar(message, state, bot) + + +@router.message(SetBotAvatarForm.waiting_for_photo, IsSuperAdmin()) +async def invalid_input(message: Message) -> None: + """ + Обработка некорректного ввода. + """ + await message.answer(_("❌ Пожалуйста, отправьте фотографию."))