aboutsummaryrefslogtreecommitdiffstats
path: root/config.py
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-10-29 19:58:05 -0700
committerPinapelz <yukais@pinapelz.com>2026-10-29 19:58:05 -0700
commit96bfe2e1505b4ae250f2d5201f9541ceb01cec84 (patch)
treeb2164a76dc3aa5b69e160d1135d3385bcbe6108d /config.py
parent150858067059ba615c548f826bb343589993b4bb (diff)
ts web slopify code
Diffstat (limited to 'config.py')
-rw-r--r--config.py170
1 files changed, 108 insertions, 62 deletions
diff --git a/config.py b/config.py
index 4d774a2..96ef08a 100644
--- a/config.py
+++ b/config.py
@@ -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"))
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage