diff --git a/docs/dev/creating-a-service.md b/docs/dev/creating-a-service.md index 04fa6163..da867629 100644 --- a/docs/dev/creating-a-service.md +++ b/docs/dev/creating-a-service.md @@ -242,7 +242,7 @@ use `self.request_input(prompt)`, never a bare `input()`. Under `serve` mode there is no local terminal, so a bare `input()` would hang the server waiting on stdin that never arrives; `request_input` instead relays the prompt to the remote client through the attached `InputBridge`. Locally it routes through the -shared Rich console (`console.input`) so the prompt renders correctly alongside +shared Rich console (`prompt_user`) so the prompt renders correctly alongside progress and log output. !!! warning "Key token caches by whatever varies per session" diff --git a/docs/dev/rest-api/remote-sessions.md b/docs/dev/rest-api/remote-sessions.md index 56a89ecf..0923e38a 100644 --- a/docs/dev/rest-api/remote-sessions.md +++ b/docs/dev/rest-api/remote-sessions.md @@ -398,7 +398,8 @@ session API and a good template for any client. - **Retries.** The download-side HTTP session mounts an adapter with `Retry(total=5, backoff_factor=0.2, status_forcelist=[429, 500, 502, 503, 504])`. - **Flow.** `authenticate()` → `create` (+ poll `prompt` every 2s up to a 600s - deadline, answering `pending_input` prompts) → `get_titles()` → `get_tracks()` + deadline, answering `pending_input` prompts; with stdin closed the client logs an + error and raises `SystemExit(1)` instead of posting an empty answer) → `get_titles()` → `get_tracks()` (merging returned `session_headers`/`session_cookies`, re-parsing `manifests`) → license via proxy or `server_cdm` → `close()` (`DELETE`, saving any returned `cache`). diff --git a/unshackle/core/console.py b/unshackle/core/console.py index bbf58ba4..1015b389 100644 --- a/unshackle/core/console.py +++ b/unshackle/core/console.py @@ -427,4 +427,20 @@ console = ComfyConsole( ) -__all__ = ("ComfyLogRenderer", "ComfyRichHandler", "ComfyConsole", "GradientPulseBarColumn", "SyncLive", "console") +def prompt_user(prompt: str) -> str: + """Ask the user for input on the shared console, themed and indented like the rest of the output.""" + indent = " " * 5 + body = Text(indent + prompt.rstrip("\n ").replace("\n", "\n" + indent), style="text") + body.append("\n" + indent + "> ", style="rule.text") + return console.input(body) + + +__all__ = ( + "ComfyLogRenderer", + "ComfyRichHandler", + "ComfyConsole", + "GradientPulseBarColumn", + "SyncLive", + "console", + "prompt_user", +) diff --git a/unshackle/core/proxies/proton.py b/unshackle/core/proxies/proton.py index 72388b57..c5e9d11a 100644 --- a/unshackle/core/proxies/proton.py +++ b/unshackle/core/proxies/proton.py @@ -11,10 +11,9 @@ from pathlib import Path from typing import Optional import requests -from rich.text import Text from unshackle.core.config import config -from unshackle.core.console import console +from unshackle.core.console import prompt_user from unshackle.core.proxies.proxy import Proxy log = logging.getLogger("proxies.proton") @@ -326,13 +325,12 @@ class ProtonVPN(Proxy): log.error("Proton: unexpected TV login response") return False - indent = " " * 5 prompt = ( "Proton: open https://account.proton.me/vpn/tv/code\n" f"Enter code {user_code}, then press Enter to continue..." ) try: - console.input(Text(indent + prompt.replace("\n", "\n" + indent), style="text")) + prompt_user(prompt) except EOFError: return False diff --git a/unshackle/core/remote_service.py b/unshackle/core/remote_service.py index dd7bfd4f..38a2566d 100644 --- a/unshackle/core/remote_service.py +++ b/unshackle/core/remote_service.py @@ -22,7 +22,7 @@ from rich.padding import Padding from rich.rule import Rule from unshackle.core.config import config -from unshackle.core.console import console +from unshackle.core.console import console, prompt_user from unshackle.core.constants import AnyTrack from unshackle.core.credential import Credential from unshackle.core.titles import Title_T, Titles_T, remap_titles @@ -616,7 +616,11 @@ class RemoteService: if status == "pending_input": prompt = resp.get("prompt", "Enter input: ") - user_response = click.prompt(prompt.rstrip("\n "), default="", show_default=False) + try: + user_response = prompt_user(prompt) + except EOFError: + log.error("Remote auth needs interactive input but stdin is closed") + raise SystemExit(1) from None self.client.post( f"/api/session/{self._session_id}/prompt", {"response": user_response}, diff --git a/unshackle/core/service.py b/unshackle/core/service.py index 7331e919..c3b6f0a5 100644 --- a/unshackle/core/service.py +++ b/unshackle/core/service.py @@ -16,11 +16,10 @@ import requests from requests.adapters import HTTPAdapter, Retry from rich.padding import Padding from rich.rule import Rule -from rich.text import Text from unshackle.core.cacher import Cacher from unshackle.core.config import config -from unshackle.core.console import console +from unshackle.core.console import console, prompt_user from unshackle.core.constants import AnyTrack from unshackle.core.credential import Credential from unshackle.core.drm import DRM_T @@ -369,9 +368,7 @@ class Service(metaclass=ABCMeta): """ if self._input_bridge is not None: return self._input_bridge.request_input(prompt) - indent = " " * 5 - padded = indent + prompt.replace("\n", "\n" + indent) - return console.input(Text(padded, style="text")) + return prompt_user(prompt) def search(self) -> Generator[SearchResult, None, None]: """