okay
This commit is contained in:
62
backend/thirdparty/downloader/views.py
vendored
62
backend/thirdparty/downloader/views.py
vendored
@@ -9,6 +9,7 @@ from django.http import StreamingHttpResponse, JsonResponse
|
||||
from django.utils.text import slugify
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
|
||||
# docs + schema helpers
|
||||
from rest_framework import serializers
|
||||
@@ -26,6 +27,7 @@ import math
|
||||
import json
|
||||
import tempfile
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from urllib.parse import quote as urlquote
|
||||
|
||||
from .models import DownloaderModel
|
||||
from .serializers import DownloaderLogSerializer
|
||||
@@ -141,9 +143,30 @@ def _client_meta(request) -> Tuple[Optional[Any], Optional[str], Optional[str]]:
|
||||
user = getattr(request, "user", None)
|
||||
return user, ip, ua
|
||||
|
||||
# Safe logger: swallow DB errors if table is missing/not migrated yet
|
||||
def _log_safely(*, info, requested_format, status: str, url: str, user, ip_address: str, user_agent: str, error_message: str | None = None):
|
||||
try:
|
||||
DownloaderModel.from_ydl_info(
|
||||
info=info,
|
||||
requested_format=requested_format,
|
||||
status=status,
|
||||
url=url,
|
||||
user=user,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
error_message=error_message,
|
||||
)
|
||||
except (OperationalError, ProgrammingError):
|
||||
# migrations not applied or table missing – ignore
|
||||
pass
|
||||
except Exception:
|
||||
# never break the request on logging failures
|
||||
pass
|
||||
|
||||
class DownloaderFormatsView(APIView):
|
||||
"""Probe media URL and return available formats with estimated sizes and limit flags."""
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
|
||||
@extend_schema(
|
||||
tags=["downloader"],
|
||||
@@ -224,7 +247,7 @@ class DownloaderFormatsView(APIView):
|
||||
except Exception as e:
|
||||
# log probe error
|
||||
user, ip, ua = _client_meta(request)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info={"webpage_url": url},
|
||||
requested_format=None,
|
||||
status="probe_error",
|
||||
@@ -258,7 +281,7 @@ class DownloaderFormatsView(APIView):
|
||||
|
||||
# Log probe
|
||||
user, ip, ua = _client_meta(request)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info=info,
|
||||
requested_format=None,
|
||||
status="probe_ok",
|
||||
@@ -280,6 +303,7 @@ class DownloaderFormatsView(APIView):
|
||||
class DownloaderFileView(APIView):
|
||||
"""Download selected format if under max size, then stream the file back."""
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
|
||||
@extend_schema(
|
||||
tags=["downloader"],
|
||||
@@ -288,7 +312,7 @@ class DownloaderFileView(APIView):
|
||||
description="Downloads with a strict max filesize guard and streams as application/octet-stream.",
|
||||
request=DownloadRequestSchema,
|
||||
responses={
|
||||
200: OpenApiResponse(response=OpenApiTypes.BINARY, media_type="application/octet-stream"),
|
||||
200: OpenApiTypes.BINARY, # was OpenApiResponse(..., media_type="application/octet-stream")
|
||||
400: OpenApiResponse(response=ErrorResponseSchema),
|
||||
413: OpenApiResponse(response=ErrorResponseSchema),
|
||||
500: OpenApiResponse(response=ErrorResponseSchema),
|
||||
@@ -334,7 +358,7 @@ class DownloaderFileView(APIView):
|
||||
info = ydl.extract_info(url, download=False)
|
||||
except Exception as e:
|
||||
user, ip, ua = _client_meta(request)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info={"webpage_url": url},
|
||||
requested_format=fmt_id,
|
||||
status="precheck_error",
|
||||
@@ -358,7 +382,7 @@ class DownloaderFileView(APIView):
|
||||
est_size = _estimate_size_bytes(selected, duration)
|
||||
if est_size is not None and est_size > max_bytes:
|
||||
user, ip, ua = _client_meta(request)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info=selected,
|
||||
requested_format=fmt_id,
|
||||
status="blocked_by_size",
|
||||
@@ -400,7 +424,7 @@ class DownloaderFileView(APIView):
|
||||
filepath = result.get("requested_downloads", [{}])[0].get("filepath") or result.get("_filename")
|
||||
except Exception as e:
|
||||
user, ip, ua = _client_meta(request)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info=selected,
|
||||
requested_format=fmt_id,
|
||||
status="download_error",
|
||||
@@ -425,7 +449,7 @@ class DownloaderFileView(APIView):
|
||||
try:
|
||||
selected_info = dict(selected)
|
||||
selected_info["filesize"] = os.path.getsize(filepath)
|
||||
DownloaderModel.from_ydl_info(
|
||||
_log_safely(
|
||||
info=selected_info,
|
||||
requested_format=fmt_id,
|
||||
status="success",
|
||||
@@ -451,7 +475,13 @@ class DownloaderFileView(APIView):
|
||||
pass
|
||||
|
||||
resp = StreamingHttpResponse(file_generator(filepath), content_type="application/octet-stream")
|
||||
resp["Content-Disposition"] = f'attachment; filename="{safe_name}"'
|
||||
# Include both plain and RFC 5987 encoded filename
|
||||
resp["Content-Disposition"] = (
|
||||
f'attachment; filename="{safe_name}"; filename*=UTF-8\'\'{urlquote(safe_name)}'
|
||||
)
|
||||
# Expose headers so the browser can read them via XHR/fetch
|
||||
resp["X-Filename"] = safe_name
|
||||
resp["Access-Control-Expose-Headers"] = "Content-Disposition, X-Filename, Content-Length, Content-Type"
|
||||
try:
|
||||
resp["Content-Length"] = str(os.path.getsize(filepath))
|
||||
except Exception:
|
||||
@@ -521,19 +551,3 @@ class DownloaderStatsView(APIView):
|
||||
"top_acodec": top_acodec,
|
||||
"audio_vs_video": audio_vs_video,
|
||||
})
|
||||
|
||||
|
||||
# Minimal placeholder so existing URL doesn't break; prefer using automatic logs above.
|
||||
class DownloaderLogView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@extend_schema(
|
||||
tags=["downloader"],
|
||||
operation_id="downloader_log_helper",
|
||||
summary="Deprecated helper",
|
||||
description="Use /api/downloader/formats/ then /api/downloader/download/.",
|
||||
responses={200: inline_serializer(name="LogHelper", fields={"detail": serializers.CharField()})},
|
||||
)
|
||||
def post(self, request):
|
||||
"""POST to the deprecated log helper endpoint."""
|
||||
return Response({"detail": "Use /api/downloader/formats/ then /api/downloader/download/."}, status=200)
|
||||
|
||||
Reference in New Issue
Block a user