Add new api endpoint to generate yt-dlp commands

This commit is contained in:
arabcoders 2025-10-27 19:55:19 +03:00
parent b5683a3e77
commit 444dc72e8f
2 changed files with 62 additions and 79 deletions

80
API.md
View file

@ -18,6 +18,7 @@ This document describes the available endpoints and their usage. All endpoints r
- [Endpoints](#endpoints) - [Endpoints](#endpoints)
- [GET /api/ping](#get-apiping) - [GET /api/ping](#get-apiping)
- [POST /api/yt-dlp/convert](#post-apiyt-dlpconvert) - [POST /api/yt-dlp/convert](#post-apiyt-dlpconvert)
- [POST /api/yt-dlp/command/](#post-apiyt-dlpcommand)
- [GET /api/yt-dlp/url/info](#get-apiyt-dlpurlinfo) - [GET /api/yt-dlp/url/info](#get-apiyt-dlpurlinfo)
- [GET /api/history/add](#get-apihistoryadd) - [GET /api/history/add](#get-apihistoryadd)
- [POST /api/history](#post-apihistory) - [POST /api/history](#post-apihistory)
@ -64,7 +65,6 @@ This document describes the available endpoints and their usage. All endpoints r
- [GET /api/notifications](#get-apinotifications) - [GET /api/notifications](#get-apinotifications)
- [PUT /api/notifications](#put-apinotifications) - [PUT /api/notifications](#put-apinotifications)
- [POST /api/yt-dlp/archive\_id/](#post-apiyt-dlparchive_id) - [POST /api/yt-dlp/archive\_id/](#post-apiyt-dlparchive_id)
- [POST /api/yt-dlp/save\_cookies/](#post-apiyt-dlpsave_cookies)
- [POST /api/notifications/test](#post-apinotificationstest) - [POST /api/notifications/test](#post-apinotificationstest)
- [GET /api/yt-dlp/options](#get-apiyt-dlpoptions) - [GET /api/yt-dlp/options](#get-apiyt-dlpoptions)
- [POST /api/system/pause](#post-apisystempause) - [POST /api/system/pause](#post-apisystempause)
@ -210,6 +210,45 @@ or an error:
--- ---
### POST /api/yt-dlp/command/
**Purpose**: Build a complete yt-dlp CLI command string with priority-based argument merging from user input, presets, and defaults.
**Requires**: Console must be enabled (`YTP_CONSOLE_ENABLED=true` env).
**Body**: JSON object:
```json
{
"url": "https://example.com/video",// required - item url
"preset": "preset_name", // optional - preset name to apply
"folder": "subfolder", // optional - output folder (relative to download_path)
"template": "%(title)s.%(ext)s", // optional - output filename template
"cli": "--write-sub --embed-subs", // optional - additional yt-dlp CLI arguments
"cookies": "cookie_string" // optional - authentication cookies as string
}
```
If cookies are given, they will be stored in a temporary file and the appropriate `--cookies <file>` argument
will be added to the command.
**Priority System** (User > Preset > Default):
1. **User fields** take highest priority (from request body)
2. **Preset fields** used only if user didn't provide them
3. **Default fields** used as final fallback (from configuration)
**Response**:
```json
{
"command": "--output-path /downloads/subfolder --output %(title)s.%(ext)s --write-sub --embed-subs https://example.com/video"
}
```
**Error Responses**:
- `403 Forbidden` if console is disabled
- `400 Bad Request` if body is invalid JSON or Item format validation fails
- `400 Bad Request` if CLI command building fails
---
### GET /api/yt-dlp/url/info ### GET /api/yt-dlp/url/info
**Purpose**: Retrieves metadata (info) for a provided URL without adding it to the download queue. **Purpose**: Retrieves metadata (info) for a provided URL without adding it to the download queue.
@ -1450,45 +1489,6 @@ or an error:
--- ---
### POST /api/yt-dlp/save_cookies/
**Purpose**: Save cookies to a file for use with yt-dlp CLI operations. Requires console to be enabled (`console_enabled` in configuration).
**Body**: JSON object with `cookies` field containing the cookie string.
```json
{
"cookies": "cookie_string_or_netscape_format"
}
```
**Response on Success**:
```json
{
"status": true,
"cookie_file": "/path/to/temp/c_uuid.txt"
}
```
**Response on Error**:
```json
{
"error": "error_message"
}
```
**Status Codes**:
- `200 OK` if cookies were successfully saved.
- `400 Bad Request` if the request body is invalid or missing the `cookies` field.
- `403 Forbidden` if console is disabled.
- `413 Payload Too Large` if cookies exceed 1MB.
- `500 Internal Server Error` if cookie file creation fails.
**Notes**:
- Console must be enabled in configuration (`console_enabled: true`).
- Cookies must be a valid string (≤ 1MB).
- Cookies are stored in the temporary directory with a UUID-based filename.
- Cookie files can be used with subsequent yt-dlp operations.
---
### POST /api/notifications/test ### POST /api/notifications/test
**Purpose**: Triggers a test notification event to all configured targets. **Purpose**: Triggers a test notification event to all configured targets.

View file

@ -2,7 +2,6 @@ import json
import logging import logging
import time import time
from collections import OrderedDict from collections import OrderedDict
from pathlib import Path
from typing import Any from typing import Any
from aiohttp import web from aiohttp import web
@ -10,6 +9,7 @@ from aiohttp.web import Request, Response
from app.library.cache import Cache from app.library.cache import Cache
from app.library.config import Config from app.library.config import Config
from app.library.ItemDTO import Item
from app.library.Presets import Presets from app.library.Presets import Presets
from app.library.router import route from app.library.router import route
from app.library.Utils import ( from app.library.Utils import (
@ -20,7 +20,7 @@ from app.library.Utils import (
get_archive_id, get_archive_id,
validate_url, validate_url,
) )
from app.library.YTDLPOpts import YTDLPOpts from app.library.YTDLPOpts import YTDLPCli, YTDLPOpts
LOG: logging.Logger = logging.getLogger(__name__) LOG: logging.Logger = logging.getLogger(__name__)
@ -275,58 +275,41 @@ async def get_archive_ids(request: Request, config: Config) -> Response:
return web.json_response(data=response, status=web.HTTPOk.status_code) return web.json_response(data=response, status=web.HTTPOk.status_code)
@route("POST", "api/yt-dlp/save_cookies/", "save_cookies") @route("POST", "api/yt-dlp/command/", "make_command")
async def save_cookies(request: Request, config: Config) -> Response: async def make_command(request: Request, config: Config) -> Response:
""" """
Save cookies for use with CLI. Build yt-dlp CLI command.
Args:
request (Request): The request object.
config (Config): The config instance.
Returns: Returns:
Response: The response object with the yt-dlp CLI options. Response: The response object with the merged fields and final yt-dlp CLI command string.
""" """
if not config.console_enabled: if not config.console_enabled:
return web.json_response( return web.json_response(data={"error": "Console is disabled."}, status=web.HTTPForbidden.status_code)
data={"error": "Console is disabled."},
status=web.HTTPForbidden.status_code,
)
data = (await request.json()) if request.body_exists else None data = (await request.json()) if request.body_exists else None
if not data or not isinstance(data, dict): if not data or not isinstance(data, dict):
return web.json_response( return web.json_response(
data={"error": "Invalid request. expecting dict with 'cookies' field."}, data={"error": "Invalid request. expecting JSON body."},
status=web.HTTPBadRequest.status_code, status=web.HTTPBadRequest.status_code,
) )
cookies = data.get("cookies")
if not cookies or not isinstance(cookies, str):
return web.json_response(
data={"error": "Invalid request. 'cookies' field is required and must be a string."},
status=web.HTTPBadRequest.status_code,
)
if len(cookies) > 1_000_000:
return web.json_response(
data={"error": "Cookie size exceeds the maximum limit of 1MB."},
status=web.HTTPBadRequest.status_code,
)
import uuid
from app.library.Utils import load_cookies
cookie_file: Path = Path(config.temp_path) / f"c_{uuid.uuid4()!s}.txt"
try: try:
cookie_file.write_text(cookies) it = Item.format(data)
file_path = str(cookie_file) except ValueError as e:
load_cookies(cookie_file) return web.json_response(data={"error": str(e), "data": data}, status=web.HTTPBadRequest.status_code)
try:
command, _ = YTDLPCli(item=it, config=config).build()
except Exception as e: except Exception as e:
LOG.exception(e)
return web.json_response( return web.json_response(
data={"error": f"Failed to create cookie file. '{e!s}'."}, data={"error": "Failed to build CLI command"},
status=web.HTTPInternalServerError.status_code, status=web.HTTPBadRequest.status_code,
) )
return web.json_response( return web.json_response(data={"command": command}, status=web.HTTPOk.status_code)
data={"status": True, "cookie_file": file_path},
status=web.HTTPOk.status_code,
)