""" Monochrome - shared utilities for Tidal/Qobuz music downloading via Monochrome API instances. """ import json import ssl import sys import urllib.request import urllib.error # Hardcoded fallback API instances FALLBACK_INSTANCES = [ "https://monochrome.tf", "https://triton.squid.wtf", "https://qqdl.site", "https://monochrome.samidy.com", "https://api.monochrome.tf", ] QOBUZ_API = "https://qobuz.squid.wtf/api" UPTIME_URL = "https://tidal-uptime.jiffy-puffs-1j.workers.dev/" # SSL context that doesn't verify certs (some instances have bad certs) SSL_CTX = ssl.create_default_context() SSL_CTX.check_hostname = False SSL_CTX.verify_mode = ssl.CERT_NONE def fetch(url, timeout=15, use_ssl_ctx=True): """Fetch a URL, returning the response object.""" req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) ctx = SSL_CTX if use_ssl_ctx else None return urllib.request.urlopen(req, timeout=timeout, context=ctx) def fetch_json(url, timeout=15, use_ssl_ctx=True): """Fetch a URL and parse JSON response.""" with fetch(url, timeout, use_ssl_ctx) as resp: return json.loads(resp.read().decode()) def discover_instances(log=None): """Get live API instances from uptime monitor, fall back to hardcoded list.""" if log is None: log = print try: data = fetch_json(UPTIME_URL, timeout=10) if isinstance(data, dict): urls = [] for key, val in data.items(): if isinstance(val, dict) and val.get("url"): urls.append(val["url"].rstrip("/")) elif isinstance(val, str) and val.startswith("http"): urls.append(val.rstrip("/")) if urls: log(f"[*] Discovered {len(urls)} instances from uptime monitor") return urls elif isinstance(data, list): urls = [] for item in data: if isinstance(item, str) and item.startswith("http"): urls.append(item.rstrip("/")) elif isinstance(item, dict): u = item.get("url") or item.get("uri") or "" if u.startswith("http"): urls.append(u.rstrip("/")) if urls: log(f"[*] Discovered {len(urls)} instances from uptime monitor") return urls except Exception as e: log(f"[!] Uptime discovery failed: {e}") log(f"[*] Using {len(FALLBACK_INSTANCES)} fallback instances") return list(FALLBACK_INSTANCES)