feat(serve): relay approval messages to jobs and proxy remote downloads

A REST job shows a wait_for_approval message as its notice field. search, list-titles and list-tracks fail at once on a login that needs the user. A remote client downloads through the --proxy it sends.
This commit is contained in:
imSp4rky
2026-10-07 11:15:16 -06:00
parent 5b0369e63a
commit 09cae2ec3f
16 changed files with 233 additions and 28 deletions
+5 -1
View File
@@ -272,7 +272,11 @@ token = self.wait_for_approval(
It raises `TimeoutError` after `timeout` seconds. Under `serve` mode it also stops
when the client deletes the remote session, so the loop does not continue on the
server with no client. The message goes out as a log line, so a remote
session shows it, but a REST download job does not relay it as `input_prompt`.
session shows it. A REST download job shows it in its `notice` field until the wait ends.
The wait sees a cancel only between two `check` calls, so `check` must return within a few
seconds. A `check` that reads a socket keeps the socket open across calls and returns `None`
after each short idle wait.
!!! warning "Make each token cache entry unique"
The cache above uses `tokens_{device}_{profile}` on purpose. A token cache
+13 -1
View File
@@ -197,6 +197,13 @@ Get the list of titles behind a title ID, for example episodes or a movie, witho
You can also pass service-specific CLI options as additional keys.
#### Logins that need the user
`search`, `list-titles` and `list-tracks` have no channel to the user. A login that asks for
input or waits for an approval on another device fails at once with `AUTH_FAILED` on these
endpoints. Complete that login in a download job or a remote session, then send the saved
login with the request.
#### Client cache on a remote-only server
A `--remote-only` server keeps nothing that a client sends. When the request
@@ -556,7 +563,8 @@ Show download jobs, with optional filtering and sorting.
"segments_total": 300,
"speed": "5.2 MB/s",
"skipped_subtitles": [],
"input_prompt": null
"input_prompt": null,
"notice": null
}
]
}
@@ -721,6 +729,10 @@ example an OTP code, a PIN, or a device-code confirmation. The job then shows th
`input_prompt` field and in its `progress` events. Show the prompt to the user and send the
answer here.
A job that waits for an approval on another device, such as a pair code login, shows the
message in its `notice` field instead. A notice needs no answer: show it to the user and keep
polling.
=== "Request"
```json
+3
View File
@@ -163,6 +163,7 @@ full detail:
| `speed` | string | Transfer speed of the track in progress, e.g. `4.20 MB/s`. |
| `skipped_subtitles` | list | Subtitles that were skipped as non-fatal failures (when `skip_subtitle_errors` is set). |
| `input_prompt` | string or `null` | The prompt the service waits on. Answer it with `POST /api/download/jobs/{job_id}/input`. |
| `notice` | string or `null` | A message the service shows while it waits for an approval on another device, such as a pair code. It needs no answer. |
The full detail adds `started_time`, `completed_time`, `output_files`, the redacted
`parameters`, and, for failed jobs, `error_message`, `error_details`, and `error_code`.
@@ -368,6 +369,8 @@ Three temporary files bridge the parent and the job worker:
The job worker has no terminal. When a service asks the user for input, the job worker writes
the prompt to `progress.json` as `input_prompt` and waits. The answer from
`POST /api/download/jobs/{job_id}/input` goes to the job worker on its stdin.
When a service waits for an approval on another device, the job worker writes the message as
`notice` and sets it back to `null` when the wait ends.
Running each download out-of-process isolates the server from crashes, memory growth,
and blocking work inside the download engine, and makes hard cancellation (killing the
+3
View File
@@ -127,6 +127,9 @@ hood the client walks a remote session through its lifecycle.
URI and runs its own forwarder for it. A bare region skips Control D, because its
proxy is a forwarder on the client. See
[Control D](../../guide/proxies-and-vpn.md#control-d)
The client also downloads playlists and segments through the same proxy,
as a local run does. `--no-proxy-download` and `--proxy-download` change
that for downloads only.
- Track-selection hints (`range_`, `vcodec`, `quality`, `best_available`) so
the server fetches the right manifests
- Your language and audio codec selection (`-l`, `-vl`, `-al`, `-a`, `-fs`)
+2 -1
View File
@@ -91,7 +91,8 @@ These are abstract. A service will not load unless it defines all three:
bridge, the REST download job relay, or the rich console.
- **`wait_for_approval(message, check, timeout, interval=3.0)`**: shows a message, then
calls `check` until it returns a value. Use it for a login that the user approves on
another device, such as a pair code.
another device, such as a pair code. A remote-dl session shows the message as a log
line, and a REST download job shows it as the job's `notice`.
- **Event callbacks**: `on_segment_downloaded`, `on_track_downloaded`,
`on_track_decrypted`, `on_track_repacked`, `on_track_multiplex` let a service react to
pipeline events (for example, MonaLisa decrypts each segment in `on_segment_downloaded`).