diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-10-29 19:58:05 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-10-29 19:58:05 -0700 |
| commit | 96bfe2e1505b4ae250f2d5201f9541ceb01cec84 (patch) | |
| tree | b2164a76dc3aa5b69e160d1135d3385bcbe6108d /config.py | |
| parent | 150858067059ba615c548f826bb343589993b4bb (diff) | |
ts web slopify code
Diffstat (limited to 'config.py')
| -rw-r--r-- | config.py | 170 |
1 files changed, 108 insertions, 62 deletions
@@ -1,78 +1,124 @@ -"""TeamSpeak Web Explorer configuration.""" +"""TeamSpeak Web Explorer configuration. +Settings live in ``config.toml`` (shared with the Node voice gateway in +``gateway/``) and secrets live in ``.env``. Environment variables override the +TOML values, using the same names the gateway reads (see ``ENV_OVERRIDES``). +""" + +import os +import tomllib from pathlib import Path + from dotenv import load_dotenv -import os -load_dotenv() BASE_DIR = Path(__file__).resolve().parent +CONFIG_FILE = Path(os.environ.get("TS6_CONFIG_FILE") or BASE_DIR / "config.toml") + +load_dotenv(BASE_DIR / ".env") + +# (section, key, ENV_VAR, type). Keep in sync with gateway/src/config.js. +ENV_OVERRIDES = [ + ("web", "host", "WEB_HOST", str), + ("web", "port", "WEB_PORT", int), + ("web", "dashboard_port", "DASHBOARD_PORT", int), + ("server", "host", "TS_HOST", str), + ("server", "webquery_port", "TS_WEBQUERY_PORT", int), + ("server", "server_id", "TS_SERVER_ID", str), + ("server", "voice_port", "TS_VOICE_PORT", int), + ("server", "voice_port", "TS_PORT", int), # legacy name from the old .env.template + ("server", "ts_domain", "TS_DOMAIN", str), + ("voice", "enabled", "VOICE_ENABLED", bool), + ("voice", "trust_proxy", "VOICE_TRUST_PROXY", bool), +] + + +def _env_value(raw, kind): + if kind is bool: + return raw.strip().lower() in {"1", "true", "yes", "on"} + return kind(raw.strip()) + + +def _load_toml(): + try: + with CONFIG_FILE.open("rb") as fh: + return tomllib.load(fh) + except FileNotFoundError: + print(f"[config] {CONFIG_FILE} not found, using defaults") + return {} + + +def _build_config(): + raw = _load_toml() + sections = { + name: dict(raw.get(name) or {}) + for name in ( + "web", "server", "voice", "ui", "buttons", "external_links", "footer", + "channel_banners", "channel_icons", "generic_images", "role_icons", + ) + } + + applied = set() + for section, key, env_name, kind in ENV_OVERRIDES: + value = os.environ.get(env_name) + if value is None or value.strip() == "" or (section, key) in applied: + continue + try: + sections[section][key] = _env_value(value, kind) + applied.add((section, key)) + except ValueError: + print(f"[config] ignoring invalid {env_name}={value!r}") + + web = sections["web"] + web.setdefault("host", "0.0.0.0") + web.setdefault("port", 5000) + web.setdefault("dashboard_port", 5001) + + server = sections["server"] + server.setdefault("host", "") + server["port"] = server.get("webquery_port", 8443) # name used by the WebQuery fetcher + server["server_id"] = str(server.get("server_id", 1)) + server.setdefault("voice_port", 9987) + server["ts_domain"] = server.get("ts_domain") or server["host"] + server.setdefault("cache_time", 30) + server["cache_file"] = BASE_DIR / server.get("cache_file", "cache.json") + server.setdefault("afk_status_max_clients", 3) + server["api_key"] = os.environ.get("TS_API_KEY", "") -CONFIG = { - # Server connection settings - "server": { - "host": os.environ.get("TS_HOST", ""), # Server IP or hostname, e.g. 192.168.1.50 - "port": os.environ.get("TS_WEBQUERY_PORT", "8443"), # HTTP WebQuery port (TS6) - "api_key": os.environ.get("TS_API_KEY", ""), - "server_id": os.environ.get("TS_SERVER_ID", "1"), - "ts_domain": os.environ.get("TS_HOST", ""), # Used by the connect button - "cache_time": 30, # Refresh interval in seconds (anti-spam) - "cache_file": BASE_DIR / "cache.json", - "afk_status_max_clients": 3, # Skip per-client clientinfo (AFK/streaming) calls when client count exceeds this (due to potential rate limits) - }, + voice = sections["voice"] + voice.setdefault("enabled", True) - # Main design and copy - "ui": { - "title": "", - "subtitle": "", - "background_color": "#07111f", # Main background color - "panel_color": "#101b2d", # Main panel and channel block color - "accent_color": "#2589ff", # Buttons, highlights, and refresh timer - "text_color": "#e8f2ff", - "banner_url": "banner.jpg", # Leave empty ('') to hide the header banner - "enable_stars": True, # Set to True to enable a subtle animated space background - }, + ui = sections["ui"] + for key, default in { + "title": "", "subtitle": "", "background_color": "#07111f", + "panel_color": "#101b2d", "accent_color": "#2589ff", "text_color": "#e8f2ff", + "banner_url": "", "enable_stars": False, + }.items(): + ui.setdefault(key, default) - # Interface buttons and links - "buttons": { - "join": "Connect to TeamSpeak", + buttons = sections["buttons"] + for key, default in { + "join": "Connect in TeamSpeak", "web_join": "Join from browser", "download_text": "Teamspeak is not installed yet?", "download_link": "Download Teamspeak", - "download_url": "https://www.teamspeak.com/en/downloads/", # Official download link - }, + "download_url": "https://www.teamspeak.com/en/downloads/", + }.items(): + buttons.setdefault(key, default) - # Useful links. Add or remove as many rows as you want. - "external_links": { - }, + footer = sections["footer"] + for key in ("text", "author", "author_url"): + footer.setdefault(key, "") - # Footer - "footer": { - "text": "another moekyun service", - "author": "", - "author_url": "", - }, + generic = sections["generic_images"] + generic.setdefault("background", "") + generic.setdefault("icon", "") - # Channel customization. Exact channel name => image filename. - "channel_banners": { - "INFORMATION": "info.png", - "GAME SERVERS": "game.jpeg", - "General": "general.jpg", - "CRASHING OUT": "crash.png", - "Wonhee HQ": "illit.jpg", - }, + sections["role_icons"] = {str(k): v for k, v in sections["role_icons"].items()} + return sections - "channel_icons": { - # "Lobby": "lobby_icon.png", - # "General": "general_icon.png", - }, - "generic_images": { - "background": "", # Default image applied to unknown channels - "icon": "", # Default icon applied to unknown channels - }, +CONFIG = _build_config() - # Server groups / roles: 'GROUP_ID' => 'Image' - "role_icons": { - # "6": "admin.png", - # "9": "vip.png", - }, -} +# Web voice is only offered when enabled AND a server password is configured: +# the gateway refuses every join without one, so nobody can join without it. +# The password itself is intentionally kept out of CONFIG (which templates see). +VOICE_AVAILABLE = bool(CONFIG["voice"].get("enabled")) and bool(os.environ.get("TS_SERVER_PASSWORD")) |
