Files
remnawave-bedolaga-telegram…/app/webapi/routes/media.py
T
Fringg 0a53b85b8a refactor: centralize Bot instantiation via create_bot() factory
Replace all ~45 direct Bot() calls across the codebase with a centralized
create_bot() factory function that automatically configures SOCKS5 proxy
session when PROXY_URL is set. This ensures proxy support applies uniformly
to all Telegram API traffic.

Key changes:
- Add app/bot_factory.py with create_bot() factory
- Replace direct Bot() instantiation in 33 files
- Fix session leaks in cloudpayments.py and auth.py (async with)
- Replace 2 direct httpx calls to api.telegram.org with
  bot.create_invoice_link() (balance.py, wheel.py)
- Remove now-unused imports (Bot, DefaultBotProperties, ParseMode, httpx)
2026-03-21 02:49:37 +03:00

150 lines
4.7 KiB
Python

from __future__ import annotations
import mimetypes
from typing import Any
import structlog
from aiogram.types import BufferedInputFile
from fastapi import (
APIRouter,
File,
Form,
HTTPException,
Request,
Response,
Security,
UploadFile,
status,
)
from app.bot_factory import create_bot
from app.config import settings
from ..dependencies import require_api_token
from ..schemas.media import MediaUploadResponse
router = APIRouter()
logger = structlog.get_logger(__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 = create_bot()
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', error=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 = create_bot()
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', file_id=file_id, error=error)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to download media') from error
finally:
await bot.session.close()