#!/usr/bin/env python3 """tdarrctl — liten temporär CLI mot Tdarrs API (som npmctl fast för Tdarr). Auth: x-api-key. Nyckeln läses från $TDARR_API_KEY, annars ur en .env-fil (rad `apiKey=...`), t.ex. ~/tdarr-node/.env. Nyckeln skrivs aldrig ut. Bas-URL: $TDARR_URL (standard http://192.168.0.19:8266). """ import argparse, json, os, sys, urllib.request, urllib.error, glob URL = os.environ.get("TDARR_URL", "http://192.168.0.19:8266").rstrip("/") def load_key(): k = os.environ.get("TDARR_API_KEY") if k: return k.strip() for p in [os.path.expanduser("~/tdarr-node/.env"), os.path.expanduser("~/fileflows-node/.env")]: if os.path.exists(p): for line in open(p): line = line.strip() if line.startswith("apiKey="): return line.split("=", 1)[1].strip() sys.exit("fel: ingen API-nyckel (sätt TDARR_API_KEY eller apiKey=... i ~/tdarr-node/.env)") KEY = None def req(method, path, body=None): url = path if path.startswith("http") else URL + path data = json.dumps(body).encode() if body is not None else None r = urllib.request.Request(url, data=data, method=method, headers={"x-api-key": KEY, "Content-Type": "application/json"}) try: with urllib.request.urlopen(r, timeout=30) as resp: raw = resp.read().decode() code = resp.status except urllib.error.HTTPError as e: raw = e.read().decode(); code = e.code try: return code, json.loads(raw) except Exception: return code, raw def cruddb(collection, mode, obj=None, docID=""): return req("POST", "/api/v2/cruddb", {"data": {"collection": collection, "mode": mode, "docID": docID, "obj": obj or {}}}) def pp(x): print(json.dumps(x, indent=2, ensure_ascii=False) if not isinstance(x, str) else x) # ---- kommandon ---- def cmd_status(a): pp(req("GET", "/api/v2/status")[1]) def cmd_nodes(a): code, d = req("GET", "/api/v2/get-nodes") if not isinstance(d, dict): return pp(d) for k, n in d.items(): wl = n.get("workerLimits") or {} print(f"{n.get('nodeName'):24} id={k} paused={n.get('nodePaused')} " f"cpu={wl.get('transcodecpu')} gpu={wl.get('transcodegpu')}") def cmd_worker(a): # hitta nod-id via namn-delsträng _, d = req("GET", "/api/v2/get-nodes") nid = next((k for k, n in d.items() if a.node.lower() in (n.get("nodeName") or "").lower()), None) if not nid: sys.exit(f"ingen nod matchar '{a.node}'") for _ in range(a.count): code, r = req("POST", "/api/v2/alter-worker-limit", {"data": {"nodeID": nid, "workerType": a.type, "process": a.direction}}) print(code, r) def cmd_cruddb(a): obj = None if a.obj: obj = json.load(open(a.obj)) if a.obj != "-" else json.load(sys.stdin) pp(cruddb(a.collection, a.mode, obj, a.docID)[1]) def cmd_api(a): body = None if a.data: body = json.load(open(a.data)) if a.data != "-" else json.load(sys.stdin) code, r = req(a.method.upper(), a.path, body) print("HTTP", code) pp(r) def cmd_libraries(a): pp(cruddb("LibrarySettingsJSONDB", "getAll")[1]) def cmd_insert(a): # generisk insert/update i en collection från en JSON-fil obj = json.load(open(a.file)) if a.file != "-" else json.load(sys.stdin) pp(cruddb(a.collection, a.mode, obj, obj.get("_id", ""))[1]) def main(): global KEY KEY = load_key() p = argparse.ArgumentParser(prog="tdarrctl", description="Temporär Tdarr-API-CLI") sub = p.add_subparsers(dest="cmd", required=True) sub.add_parser("status").set_defaults(f=cmd_status) sub.add_parser("nodes").set_defaults(f=cmd_nodes) w = sub.add_parser("worker", help="ändra worker-gräns") w.add_argument("node"); w.add_argument("type", choices=["transcodecpu", "transcodegpu", "healthcheckcpu", "healthcheckgpu"]) w.add_argument("direction", choices=["increase", "decrease"]) w.add_argument("count", nargs="?", type=int, default=1) w.set_defaults(f=cmd_worker) c = sub.add_parser("cruddb", help="generisk cruddb") c.add_argument("collection"); c.add_argument("mode") c.add_argument("--obj"); c.add_argument("--docID", default="") c.set_defaults(f=cmd_cruddb) ap = sub.add_parser("api", help="generisk HTTP") ap.add_argument("method"); ap.add_argument("path"); ap.add_argument("--data") ap.set_defaults(f=cmd_api) sub.add_parser("libraries").set_defaults(f=cmd_libraries) ins = sub.add_parser("insert", help="insert/update obj (JSON-fil) i collection") ins.add_argument("collection"); ins.add_argument("file") ins.add_argument("--mode", default="insert") ins.set_defaults(f=cmd_insert) a = p.parse_args() a.f(a) if __name__ == "__main__": main()