mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-17 09:30:35 +00:00
159 lines
5.0 KiB
Python
159 lines
5.0 KiB
Python
from __future__ import annotations
|
||
|
||
import logging
|
||
import mimetypes
|
||
from typing import Any
|
||
|
||
from aiogram import Bot
|
||
from aiogram.client.default import DefaultBotProperties
|
||
from aiogram.enums import ParseMode
|
||
from aiogram.types import BufferedInputFile
|
||
from fastapi import (
|
||
APIRouter,
|
||
File,
|
||
Form,
|
||
HTTPException,
|
||
Request,
|
||
Response,
|
||
Security,
|
||
UploadFile,
|
||
status,
|
||
)
|
||
|
||
from app.config import settings
|
||
|
||
from ..dependencies import require_api_token
|
||
from ..schemas.media import MediaUploadResponse
|
||
|
||
|
||
router = APIRouter()
|
||
logger = logging.getLogger(__name__)
|
||
|
||
ALLOWED_MEDIA_TYPES = {"photo", "video", "document"}
|
||
|
||
|
||
def _resolve_target_chat_id() -> int:
|
||
"""Выбирает чат для загрузки файлов (канал уведомлений или первый админ)."""
|
||
|
||
chat_id = settings.get_admin_notifications_chat_id()
|
||
if chat_id is not None:
|
||
return chat_id
|
||
|
||
admin_ids = settings.get_admin_ids()
|
||
if admin_ids:
|
||
return admin_ids[0]
|
||
|
||
raise HTTPException(
|
||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
"Не настроен чат для загрузки файлов (ADMIN_NOTIFICATIONS_CHAT_ID или ADMIN_IDS)",
|
||
)
|
||
|
||
|
||
def _build_media_url(request: Request, file_id: str) -> str:
|
||
return str(request.url_for("download_media", file_id=file_id))
|
||
|
||
|
||
@router.post("/upload", response_model=MediaUploadResponse, tags=["media"], status_code=status.HTTP_201_CREATED)
|
||
async def upload_media(
|
||
request: Request,
|
||
_: Any = Security(require_api_token),
|
||
file: UploadFile = File(...),
|
||
media_type: str = Form("document", description="Тип файла: photo, video или document"),
|
||
caption: str | None = Form(None, description="Необязательная подпись к файлу"),
|
||
) -> MediaUploadResponse:
|
||
media_type_normalized = (media_type or "").strip().lower()
|
||
if media_type_normalized not in ALLOWED_MEDIA_TYPES:
|
||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Unsupported media type")
|
||
|
||
file_bytes = await file.read()
|
||
if not file_bytes:
|
||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "File is empty")
|
||
|
||
target_chat_id = _resolve_target_chat_id()
|
||
upload = BufferedInputFile(file_bytes, filename=file.filename or "upload")
|
||
|
||
bot = Bot(
|
||
token=settings.BOT_TOKEN,
|
||
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
||
)
|
||
|
||
try:
|
||
if media_type_normalized == "photo":
|
||
message = await bot.send_photo(
|
||
chat_id=target_chat_id,
|
||
photo=upload,
|
||
caption=caption,
|
||
)
|
||
media = message.photo[-1]
|
||
elif media_type_normalized == "video":
|
||
message = await bot.send_video(
|
||
chat_id=target_chat_id,
|
||
video=upload,
|
||
caption=caption,
|
||
)
|
||
media = message.video
|
||
else:
|
||
message = await bot.send_document(
|
||
chat_id=target_chat_id,
|
||
document=upload,
|
||
caption=caption,
|
||
)
|
||
media = message.document
|
||
|
||
media_url = _build_media_url(request, media.file_id)
|
||
return MediaUploadResponse(
|
||
media_type=media_type_normalized,
|
||
file_id=media.file_id,
|
||
file_unique_id=getattr(media, "file_unique_id", None),
|
||
media_url=media_url,
|
||
)
|
||
except HTTPException:
|
||
raise
|
||
except Exception as error:
|
||
logger.error("Failed to upload media: %s", error)
|
||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to upload media") from error
|
||
finally:
|
||
await bot.session.close()
|
||
|
||
|
||
@router.get("/media/{file_id}", name="download_media", tags=["media"])
|
||
async def download_media(
|
||
file_id: str,
|
||
_: Any = Security(require_api_token),
|
||
) -> Response:
|
||
bot = Bot(
|
||
token=settings.BOT_TOKEN,
|
||
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
||
)
|
||
|
||
try:
|
||
file = await bot.get_file(file_id)
|
||
if not file.file_path:
|
||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Media file not found")
|
||
|
||
buffer = await bot.download_file(file.file_path)
|
||
|
||
if hasattr(buffer, "seek"):
|
||
buffer.seek(0)
|
||
|
||
content = buffer.read() if hasattr(buffer, "read") else bytes(buffer)
|
||
filename = file.file_path.split("/")[-1]
|
||
|
||
media_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
||
|
||
return Response(
|
||
content=content,
|
||
media_type=media_type,
|
||
headers={
|
||
"Content-Disposition": f"inline; filename={filename}",
|
||
},
|
||
)
|
||
except HTTPException:
|
||
raise
|
||
except Exception as error: # pragma: no cover - неожиданные ошибки загрузки файла
|
||
logger.error("Failed to download media %s: %s", file_id, error)
|
||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to download media") from error
|
||
finally:
|
||
await bot.session.close()
|
||
|