Files
Richard Macias e539043aad spring cleaning
2026-03-07 22:34:36 -06:00

119 lines
3.9 KiB
Python

"""Shared utility functions for Obtainium Emulation Pack scripts."""
import copy
import json
import os
import urllib.parse
from pathlib import Path
from typing import Any
from urllib.parse import urlparse
from constants import OBTAINIUM_SCHEME, REDIRECT_URL, SETTINGS_SCHEMA, SOURCE_HOST_MAP
def load_dotenv() -> None:
"""Load .env into os.environ. Real env vars take precedence. Blank values skipped."""
env_path = Path(__file__).resolve().parent.parent / ".env"
if not env_path.exists():
return
with open(env_path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
value = value.strip().strip("\"'")
if key and value and key not in os.environ:
os.environ[key] = value
def detect_source_from_url(url: str) -> str | None:
"""Match a URL's host against SOURCE_HOST_MAP, including subdomains."""
try:
host = urlparse(url).netloc.lower().lstrip("www.")
except Exception:
return None
for domain, source in SOURCE_HOST_MAP.items():
if host == domain or host.endswith(f".{domain}"):
return source
return None
def should_include_app(app: dict[str, Any], variant: str) -> bool:
meta = app.get("meta", {})
if meta.get("excludeFromExport", False):
return False
if variant == "standard":
return meta.get("includeInStandard", True)
elif variant == "dual-screen":
return meta.get("includeInDualScreen", True)
return True
def get_display_name(app: dict[str, Any]) -> str:
return app.get("meta", {}).get("nameOverride") or app.get("name", "")
def get_application_url(app: dict[str, Any]) -> str:
return app.get("meta", {}).get("urlOverride") or app.get("url", "")
def get_additional_settings(app: dict[str, Any]) -> dict[str, Any]:
"""Return additionalSettings as a dict, whether stored as object or JSON string."""
raw = app.get("additionalSettings", {})
if isinstance(raw, str):
return json.loads(raw) if raw else {}
if isinstance(raw, dict):
return raw
return {}
def get_defaults_for_source(source: str | None) -> dict[str, Any]:
"""Return the full default settings dict for a given source type."""
defaults: dict[str, Any] = {}
for key, defn in SETTINGS_SCHEMA.items():
if source is None or source in defn.sources:
defaults[key] = copy.deepcopy(defn.default)
return defaults
def hydrate_settings(sparse: dict[str, Any], source: str | None) -> dict[str, Any]:
"""Merge sparse settings with schema defaults to produce a full settings dict.
Keys are ordered according to SETTINGS_SCHEMA insertion order.
"""
defaults = get_defaults_for_source(source)
defaults.update(sparse)
return defaults
def stringify_additional_settings(
settings: dict[str, Any],
source: str | None = None,
) -> str:
"""Hydrate and stringify settings for Obtainium consumption."""
hydrated = hydrate_settings(settings, source)
return json.dumps(hydrated, separators=(",", ":"))
def make_obtainium_link(app: dict[str, Any]) -> str:
settings = get_additional_settings(app)
source = app.get("overrideSource")
payload = {
"id": app["id"],
"url": app["url"],
"author": app["author"],
"name": app["name"],
"otherAssetUrls": app.get("otherAssetUrls"),
"apkUrls": app.get("apkUrls"),
"preferredApkIndex": app.get("preferredApkIndex"),
"additionalSettings": stringify_additional_settings(settings, source),
"categories": app.get("categories"),
"overrideSource": app.get("overrideSource"),
"allowIdChange": app.get("allowIdChange"),
}
encoded = urllib.parse.quote(json.dumps(payload, separators=(",", ":")), safe="")
return f"{REDIRECT_URL}?r={OBTAINIUM_SCHEME}{encoded}"