diff --git a/src/vk_parser_app/admin.py b/src/vk_parser_app/admin.py index 4610112..8f04be9 100644 --- a/src/vk_parser_app/admin.py +++ b/src/vk_parser_app/admin.py @@ -9,14 +9,18 @@ import re import secrets import time from datetime import date, datetime, timedelta, timezone +from io import BytesIO from pathlib import Path from typing import Any from urllib.parse import urlencode from zoneinfo import ZoneInfo import aiohttp +from aiogram import Bot +from aiogram.client.session.aiohttp import AiohttpSession +from aiogram.client.telegram import TelegramAPIServer from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile, status -from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse +from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse, Response from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from loguru import logger @@ -502,6 +506,40 @@ async def get_current_user(request: Request) -> dict | None: return dict(row) if row else None +@app.get("/raw/media/{media_id}") +async def raw_media_preview(request: Request, media_id: int) -> Response: + if not await get_current_user(request): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + pool = await get_pool() + media = await pool.fetchrow( + "SELECT media_type, tg_file_id FROM raw_post_media WHERE id=$1", + media_id, + ) + if not media or media["media_type"] != "photo" or not media["tg_file_id"]: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + local_bot_api_url = str(await fetch_setting("local_bot_api_url", settings.local_bot_api_url) or "").strip() + session = ( + AiohttpSession(api=TelegramAPIServer.from_base(local_bot_api_url.rstrip("/"), is_local=True)) + if local_bot_api_url + else AiohttpSession() + ) + bot = Bot(token=settings.tg_bot_token, session=session) + output = BytesIO() + try: + await bot.download(str(media["tg_file_id"]), destination=output) + except Exception as exc: + logger.warning("Telegram media preview failed for media {}: {}", media_id, exc) + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Media preview unavailable") from exc + finally: + await bot.session.close() + return Response( + content=output.getvalue(), + media_type="image/jpeg", + headers={"Cache-Control": "private, max-age=3600"}, + ) + + def require_csrf(user: dict, csrf_token: str) -> None: if not user or csrf_token != user["csrf_token"]: raise PermissionError("bad csrf") @@ -2680,7 +2718,11 @@ async def raw_posts( jsonb_build_object( 'id', rpm.id, 'type', rpm.media_type, - 'url', rpm.original_url, + 'url', CASE + WHEN rpm.media_type='photo' AND rpm.tg_file_id IS NOT NULL + THEN '/raw/media/' || rpm.id::text + ELSE rpm.original_url + END, 'status', rpm.status, 'error', rpm.error, 'duration_sec', rpm.duration_sec @@ -2846,7 +2888,11 @@ async def fetch_single_editor_row(pool, post_id: int): jsonb_build_object( 'id', rpm.id, 'type', rpm.media_type, - 'url', rpm.original_url, + 'url', CASE + WHEN rpm.media_type='photo' AND rpm.tg_file_id IS NOT NULL + THEN '/raw/media/' || rpm.id::text + ELSE rpm.original_url + END, 'status', rpm.status, 'error', rpm.error, 'duration_sec', rpm.duration_sec @@ -2987,7 +3033,11 @@ async def editor_feed( jsonb_build_object( 'id', rpm.id, 'type', rpm.media_type, - 'url', rpm.original_url, + 'url', CASE + WHEN rpm.media_type='photo' AND rpm.tg_file_id IS NOT NULL + THEN '/raw/media/' || rpm.id::text + ELSE rpm.original_url + END, 'status', rpm.status, 'error', rpm.error, 'duration_sec', rpm.duration_sec @@ -3327,7 +3377,11 @@ async def raw_post_detail(request: Request, post_id: int): jsonb_build_object( 'id', rpm.id, 'type', rpm.media_type, - 'url', rpm.original_url, + 'url', CASE + WHEN rpm.media_type='photo' AND rpm.tg_file_id IS NOT NULL + THEN '/raw/media/' || rpm.id::text + ELSE rpm.original_url + END, 'status', rpm.status, 'error', rpm.error, 'duration_sec', rpm.duration_sec