#!/usr/bin/env python3 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry import requests import re import time # ============================================================================ # CONFIG # ============================================================================ ip_address = "192.168.0.4" INSTANCES = [ { "name": "radarr-de", "url": f"http://{ip_address}:7879", "api_key": "29f67f0ac8894357b22ea3f7fc1abfdf", }, { "name": "radarr-en", "url": f"http://{ip_address}:7878", "api_key": "4332aee075c748f2acee41aa26f06007", }, { "name": "sonarr", "url": f"http://{ip_address}:8990", "api_key": "707d4fcbb3d74822ba990c6a8b7278e9", }, { "name": "lidarr", "url": f"http://{ip_address}:8686", "api_key": "bb41cbf2b17e439290f90ecfaeb24dff", }, # { # "name": "listenarr", # "url": f"http://{ip_address}:7878", # "api_key": "READARR_API_KEY", # }, ] PRIVATE_CLIENT = "qbittorrent-private" PUBLIC_CLIENT = "qbittorrent-public" PRIVATE_INDEXER_REGEX = re.compile( r"(Bittorrentfiles|NordicQuality|funzone|SeedCore|The Brothers)", re.IGNORECASE ) DRY_RUN = False # ============================================================================ session = requests.Session() retry_strategy = Retry( total=3, connect=3, read=3, backoff_factor=2, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET", "PUT", "POST"], ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("http://", adapter) session.mount("https://", adapter) def get_api_version(base_url, api_key): r = requests.get( f"{base_url}/api", headers={"X-Api-Key": api_key}, timeout=30, ) r.raise_for_status() return r.json()["current"] def api_get(base_url, api_key, endpoint, retries=3): last_error = None for attempt in range(1, retries + 1): try: r = requests.get( f"{base_url}{endpoint}", headers={"X-Api-Key": api_key}, timeout=30, ) r.raise_for_status() return r.json() except requests.HTTPError as e: print("Status:", e.response.status_code) print("Response:", e.response.text) raise except Exception as e: last_error = e print(f"GET failed ({attempt}/{retries}): {e}") if attempt < retries: time.sleep(2 ** attempt) raise last_error def api_put(base_url, api_key, endpoint, payload, retries=3): last_error = None for attempt in range(1, retries + 1): try: r = requests.put( f"{base_url}{endpoint}", headers={ "X-Api-Key": api_key, "Content-Type": "application/json", }, json=payload, timeout=30, ) r.raise_for_status() return r.json() if r.content else None except requests.HTTPError as e: print("Status:", e.response.status_code) print("Response:", e.response.text) raise except Exception as e: last_error = e print(f"PUT failed ({attempt}/{retries}): {e}") if attempt < retries: time.sleep(2 ** attempt) raise last_error def get_download_client_ids(base_url, endpoint,api_key): clients = api_get(base_url, api_key, endpoint) private_id = None public_id = None for client in clients: if client["name"] == PRIVATE_CLIENT: private_id = client["id"] if client["name"] == PUBLIC_CLIENT: public_id = client["id"] return private_id, public_id def process_instance(instance): name = instance["name"] url = instance["url"] api_key = instance["api_key"] print(f"\n{'=' * 80}") print(f"Processing: {name}") print(f"{'=' * 80}") api_version = get_api_version(url, api_key) endpoint_download = f"/api/{api_version}/downloadclient" private_id, public_id = get_download_client_ids(url, endpoint_download, api_key) if not private_id: print(f"ERROR: '{PRIVATE_CLIENT}' not found") return if not public_id: print(f"ERROR: '{PUBLIC_CLIENT}' not found") return print(f"{PRIVATE_CLIENT} id: {private_id}") print(f"{PUBLIC_CLIENT} id: {public_id}") indexers = api_get(url,api_key,f"/api/{api_version}/indexer") for indexer in indexers: idx_id = indexer["id"] idx_name = indexer["name"] print(idx_name) if PRIVATE_INDEXER_REGEX.search(idx_name): target_id = private_id target_name = PRIVATE_CLIENT else: target_id = public_id target_name = PUBLIC_CLIENT current_id = indexer.get("downloadClientId") if current_id == target_id: print(f"SKIP {idx_name} -> {target_name}") continue indexer["downloadClientId"] = target_id if DRY_RUN: print(f"DRY {idx_name} -> {target_name}") continue endpoint_indexer = f"/api/{api_version}/indexer/{idx_id}?forceSave=true" try: api_put( url, api_key, endpoint_indexer, indexer, ) print(f"SET {idx_name} -> {target_name}") except Exception as e: print(f"FAIL {idx_name}: {e}") def main(): for instance in INSTANCES: process_instance(instance) if __name__ == "__main__": main()