"""Общий каталог картинок для бота и админки (Docker volume).""" from __future__ import annotations import logging import shutil from pathlib import Path from starlette.datastructures import UploadFile from common.config import AdminSettings logger = logging.getLogger(__name__) #: Встроенная картинка приветствия (лежит в образе бота и админки). BUILTIN_WELCOME = Path("images/welcome_image.jpg") #: Имя файла в volume, которое ждёт сид последовательности `welcome`. WELCOME_IMAGE_NAME = "welcome_image.jpg" #: Допустимые типы и максимальный размер загружаемых картинок. ALLOWED_IMAGE_TYPES = {"image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp"} MAX_IMAGE_BYTES = 5 * 1024 * 1024 async def read_image(upload: UploadFile) -> tuple[bytes, str, str]: """Прочитать картинку из формы. Возвращает (данные, mime, расширение).""" content_type = (upload.content_type or "").lower() extension = ALLOWED_IMAGE_TYPES.get(content_type) if extension is None: raise ValueError("Картинка должна быть JPEG, PNG или WebP") data = await upload.read() if len(data) > MAX_IMAGE_BYTES: raise ValueError("Картинка больше 5 МБ") return data, content_type, extension def ensure_media(settings: AdminSettings) -> Path: """Создать каталог и положить туда встроенное приветствие, если его нет.""" directory = Path(settings.media_dir) directory.mkdir(parents=True, exist_ok=True) target = directory / WELCOME_IMAGE_NAME if not target.exists() and BUILTIN_WELCOME.is_file(): try: shutil.copyfile(BUILTIN_WELCOME, target) logger.info("Приветственная картинка скопирована в %s", target) except OSError: logger.warning("Не удалось скопировать приветственную картинку в %s", target, exc_info=True) return directory def media_path(settings: AdminSettings, name: str) -> Path: """Безопасный путь к файлу в media (без выхода из каталога).""" return Path(settings.media_dir) / Path(name).name