Source code for nyora.config
"""Configuration helpers for local Nyora helper discovery.
Defines the environment-variable names the SDK honors and resolves the
platform-specific path of the helper port file. These helpers let
:mod:`nyora.client` and :mod:`nyora.helper` locate a running Nyora helper
without explicit configuration.
Environment variables:
NYORA_BASE_URL: Explicit helper base URL, overriding port-file discovery.
NYORA_HELPER_PORT_FILE: Override path for the helper port file.
NYORA_HELPER_JAR: Path to a helper jar for managed launches.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from platformdirs import user_config_dir
BASE_URL_ENV = "NYORA_BASE_URL"
HELPER_PORT_FILE_ENV = "NYORA_HELPER_PORT_FILE"
HELPER_JAR_ENV = "NYORA_HELPER_JAR"
CONFIG_FILE_ENV = "NYORA_CONFIG_FILE"
[docs]
def config_file() -> Path:
"""Path of the persisted SDK config (``NYORA_CONFIG_FILE`` override, else the
platform user-config dir). Stores the preferred server URL and blocklist cache
location so ``nyora`` uses *your* server, not the cloud, across runs."""
configured = os.getenv(CONFIG_FILE_ENV)
if configured:
return Path(configured).expanduser()
return Path(user_config_dir("nyora", appauthor=False)) / "config.json"
[docs]
def read_config() -> dict:
"""Return the persisted config dict (``{}`` if absent/corrupt)."""
path = config_file()
if not path.exists():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
except Exception:
return {}
[docs]
def write_config(data: dict) -> None:
"""Persist the config dict to :func:`config_file`."""
path = config_file()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
[docs]
def read_base_url_from_config() -> str | None:
"""Return the user's persisted preferred server URL, or ``None``."""
url = read_config().get("base_url")
return url.rstrip("/") if isinstance(url, str) and url.strip() else None
[docs]
def set_config_base_url(url: str | None) -> None:
"""Persist (or clear, when ``url`` is falsy) the preferred server URL."""
data = read_config()
if url:
data["base_url"] = url.rstrip("/")
else:
data.pop("base_url", None)
write_config(data)
[docs]
def read_theme_from_config() -> str | None:
"""Return the user's persisted TUI colour-scheme id, or ``None``."""
theme = read_config().get("theme")
return theme if isinstance(theme, str) and theme.strip() else None
[docs]
def set_config_theme(theme_id: str | None) -> None:
"""Persist (or clear, when ``theme_id`` is falsy) the TUI colour scheme."""
data = read_config()
if theme_id:
data["theme"] = theme_id
else:
data.pop("theme", None)
write_config(data)
[docs]
def read_ui_lang() -> str | None:
"""Return the user's persisted TUI interface language code, or ``None``."""
lang = read_config().get("ui_lang")
return lang if isinstance(lang, str) and lang.strip() else None
[docs]
def set_ui_lang(code: str | None) -> None:
"""Persist (or clear, when ``code`` is falsy) the TUI interface language."""
data = read_config()
if code:
data["ui_lang"] = code
else:
data.pop("ui_lang", None)
write_config(data)
[docs]
def read_onboarded() -> bool:
"""Return whether the user has passed the welcome screen at least once."""
return bool(read_config().get("onboarded"))
[docs]
def set_onboarded(value: bool = True) -> None:
"""Mark (or clear) the one-time welcome screen as seen."""
data = read_config()
if value:
data["onboarded"] = True
else:
data.pop("onboarded", None)
write_config(data)
[docs]
def read_show_nsfw() -> bool:
"""Return whether adult (18+) sources should be shown."""
return bool(read_config().get("show_nsfw"))
[docs]
def set_show_nsfw(value: bool) -> None:
"""Persist the 18+ content preference."""
data = read_config()
data["show_nsfw"] = bool(value)
write_config(data)
[docs]
def read_languages() -> list[str]:
"""Return the user's chosen language filter (empty = all languages)."""
langs = read_config().get("languages")
return [str(x) for x in langs] if isinstance(langs, list) else []
[docs]
def set_languages(langs: list[str]) -> None:
"""Persist the language filter (a list of locale codes; empty = all)."""
data = read_config()
data["languages"] = [str(x) for x in langs]
write_config(data)
[docs]
def read_reader_prefs() -> dict:
"""Return persisted reader preferences (``mode``, ``fit``), or ``{}``."""
prefs = read_config().get("reader")
return prefs if isinstance(prefs, dict) else {}
[docs]
def set_reader_pref(key: str, value: str) -> None:
"""Persist a single reader preference (e.g. ``mode`` or ``fit``)."""
data = read_config()
prefs = data.get("reader")
if not isinstance(prefs, dict):
prefs = {}
prefs[key] = value
data["reader"] = prefs
write_config(data)
[docs]
def default_port_file() -> Path:
"""Return the path of the helper port file for this platform.
Honors ``NYORA_HELPER_PORT_FILE`` when set; otherwise uses the
platform-conventional application-data location (macOS Application Support,
Windows ``%APPDATA%``, or the XDG config dir on Linux).
Returns:
The resolved port-file path (which may not yet exist).
"""
configured = os.getenv(HELPER_PORT_FILE_ENV)
if configured:
return Path(configured).expanduser()
if sys.platform == "darwin":
return Path.home() / "Library" / "Application Support" / "Nyora" / "helper.port"
if sys.platform.startswith("win"):
appdata = os.getenv("APPDATA")
base = Path(appdata) if appdata else Path.home() / "AppData" / "Roaming"
return base / "Nyora" / "helper.port"
return Path(user_config_dir("nyora", appauthor=False)) / "helper.port"
[docs]
def read_base_url_from_port_file(port_file: Path | None = None) -> str | None:
"""Derive a helper base URL from a port file, if present.
Args:
port_file: Path to read. Defaults to :func:`default_port_file`.
Returns:
``http://127.0.0.1:<port>`` when the file exists and holds a port, else
``None``.
"""
path = port_file or default_port_file()
if not path.exists():
return None
port = path.read_text(encoding="utf-8").strip()
if not port:
return None
return f"http://127.0.0.1:{port}"