From 71bd40a2e8174853b80dddbc35ad38052e4477a9 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Wed, 27 Aug 2025 16:28:19 +0300 Subject: [PATCH] added new endpoint to get urls archive ids --- API.md | 55 ++++++++++++++++++++++++++++++++++++++++ app/library/Utils.py | 24 +++++++++++------- app/routes/api/yt_dlp.py | 37 ++++++++++++++++++++++++--- 3 files changed, 104 insertions(+), 12 deletions(-) diff --git a/API.md b/API.md index 84588a36..8d647c6c 100644 --- a/API.md +++ b/API.md @@ -44,7 +44,9 @@ This document describes the available endpoints and their usage. All endpoints r - [GET /api/logs](#get-apilogs) - [GET /api/notifications](#get-apinotifications) - [PUT /api/notifications](#put-apinotifications) + - [POST /api/yt-dlp/archive\_id/](#post-apiyt-dlparchive_id) - [POST /api/notifications/test](#post-apinotificationstest) + - [GET /api/yt-dlp/options](#get-apiyt-dlpoptions) - [Error Responses](#error-responses) --- @@ -824,6 +826,41 @@ Binary image data with appropriate `Content-Type` header. "allowedTypes": ["added", "completed", "error", "cancelled", "cleared", "log_info", "log_success", ...] } ``` +--- + +### POST /api/yt-dlp/archive_id/ +**Purpose**: Get the archive ID for a given URLs. +**Body**: Array of URLs. +```json +[ + "https://youtube.com/...", + "https://..." +} +``` + +**Response**: +```json +[ + { + "index": "index_of_the_url_in_request_array", + "url": "the_url", + "id": "the_video_id_or_null_if_not_found", + "ie_key": "the_extractor_key_or_null_if_not_found", + "archive_id": "the_archive_id_or_null_if_not_found", + "error": "error_message_if_any_or_null" + }, + ... +] +``` + +or an error: +```json +{ + "error": "text" +} +``` + +- If the body is not a valid JSON array, returns `400 Bad Request`. --- @@ -840,6 +877,24 @@ Binary image data with appropriate `Content-Type` header. --- +### GET /api/yt-dlp/options +**Purpose**: Get the current yt-dlp CLI options as a JSON object. + +**Response**: +```json +[ + { + "description": "Description of the option", + "flags":[ "--option", "-o" ], + "group": "Option Group", + "ignored": false, // true if this option is ignored by ytptube. + }, + ... +] +``` + +--- + ## Error Responses Most endpoints return standard error codes (`400`, `403`, `404`, `500`, etc.) and a JSON body on failure. For example: diff --git a/app/library/Utils.py b/app/library/Utils.py index b1f74ad8..4a54145f 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -405,13 +405,9 @@ def check_id(file: Path) -> bool | str: @lru_cache(maxsize=512) def is_private_address(hostname: str) -> bool: - try: - ip = socket.gethostbyname(hostname) - ip_obj = ipaddress.ip_address(ip) - return ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_reserved or ip_obj.is_link_local - except socket.gaierror: - # Could not resolve - treat as invalid or restricted - return True + ip = socket.gethostbyname(hostname) + ip_obj = ipaddress.ip_address(ip) + return ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_reserved or ip_obj.is_link_local def validate_url(url: str, allow_internal: bool = False) -> bool: @@ -447,10 +443,20 @@ def validate_url(url: str, allow_internal: bool = False) -> bool: raise ValueError(msg) hostname: str | None = parsed_url.host - if allow_internal is False and (not hostname or is_private_address(hostname)): - msg = "Access to internal urls or private networks is not allowed." + if not hostname: + msg = "Invalid hostname." raise ValueError(msg) + if allow_internal is False: + try: + if is_private_address(hostname): + msg = "Access to internal urls or private networks is not allowed." + raise ValueError(msg) + except socket.gaierror as e: + LOG.error(f"Error resolving hostname '{hostname}': {e!s}") + msg = "Invalid hostname." + raise ValueError(msg) from e + return True diff --git a/app/routes/api/yt_dlp.py b/app/routes/api/yt_dlp.py index 49291c20..5ca2e459 100644 --- a/app/routes/api/yt_dlp.py +++ b/app/routes/api/yt_dlp.py @@ -309,6 +309,37 @@ async def get_options() -> Response: """ from app.library.ytdlp import ytdlp_options - return web.json_response( - body=json.dumps(ytdlp_options(), indent=4, default=str), status=web.HTTPOk.status_code - ) + return web.json_response(body=json.dumps(ytdlp_options(), indent=4, default=str), status=web.HTTPOk.status_code) + + +@route("POST", "api/yt-dlp/archive_id/", "get_archive_ids") +async def get_archive_ids(request: Request) -> Response: + """ + Get the yt-dlp CLI options. + + Returns: + Response: The response object with the yt-dlp CLI options. + + """ + from app.library.Utils import get_archive_id + + data = (await request.json()) if request.body_exists else None + if not data or not isinstance(data, list): + return web.json_response( + data={"error": "Invalid request. expecting list with URLs."}, + status=web.HTTPBadRequest.status_code, + ) + + response = [] + + for i, url in enumerate(data): + dct = {"index": i, "url": url} + try: + validate_url(url) + dct.update(get_archive_id(url)) + except ValueError as e: + dct.update({"id": None, "ie_key": None, "archive_id": None, "error": str(e)}) + + response.append(dct) + + return web.json_response(data=response, status=web.HTTPOk.status_code)