From 16eb35f095684401c10184cfa68e8710cc9f385f Mon Sep 17 00:00:00 2001 From: constantprojects Date: Tue, 17 Feb 2026 15:21:57 -0700 Subject: [PATCH] Fix seeding field overwrite bug and hardcode NocoDB field names NocoDB returns records keyed by field titles, but the code was looking up values by field IDs, always getting None. This caused every update to overwrite existing seeders instead of appending. Hardcode "Id" and "Seeding Users" field names as class constants and remove the --id-field and --seeding-field CLI args. --- xb_seed_status.py | 54 ++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/xb_seed_status.py b/xb_seed_status.py index 1865263..0864c76 100644 --- a/xb_seed_status.py +++ b/xb_seed_status.py @@ -16,12 +16,10 @@ Requirements: Usage: python seed_tracker.py --id-folder ./torrents --bt-backup /path/to/BT_backup \\ - --nocodb-url https://noco.example.com --table-id tblXXX --api-token xc-xxx \\ - --id-field cXXX --seeding-field cYYY + --nocodb-url https://noco.example.com --table-id tblXXX --api-token xc-xxx To find NocoDB IDs: - Table ID: Click ... next to table name → Copy Table ID - - Field IDs: Click field header dropdown → Copy Field ID """ import argparse @@ -209,38 +207,40 @@ def get_bt_backup_data(bt_backup_path: Path) -> tuple[set[str], dict[tuple[str, class NocoDBClient: """Simple NocoDB API client.""" - def __init__(self, base_url: str, table_id: str, api_token: str, - id_field: str, seeding_field: str, debug: bool = False): + ID_FIELD = "Id" + SEEDING_FIELD = "Seeding Users" + + def __init__(self, base_url: str, table_id: str, api_token: str, + debug: bool = False): self.base_url = base_url.rstrip('/') self.table_id = table_id self.api_token = api_token - self.id_field = id_field - self.seeding_field = seeding_field self.debug = debug self.endpoint = f"{self.base_url}/api/v2/tables/{table_id}/records" - - def _request(self, method: str, data: dict | None = None, params: dict | None = None) -> dict: + + def _request(self, method: str, url: str | None = None, data: dict | None = None, params: dict | None = None) -> dict: """Make an API request.""" - url = self.endpoint + if url is None: + url = self.endpoint if params: query = "&".join(f"{k}={urllib.request.quote(str(v))}" for k, v in params.items()) url = f"{url}?{query}" - + headers = { "xc-token": self.api_token, "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "application/json", } - + body = json.dumps(data).encode('utf-8') if data else None req = urllib.request.Request(url, data=body, headers=headers, method=method) - + if self.debug: print(f" DEBUG: {method} {url}", file=sys.stderr) if body: print(f" DEBUG: Body: {body.decode()}", file=sys.stderr) - + try: with urllib.request.urlopen(req, timeout=30) as response: result = json.loads(response.read().decode('utf-8')) @@ -254,7 +254,7 @@ class NocoDBClient: def get_record(self, record_id: int | str) -> dict | None: """Get a single record by ID.""" try: - params = {"where": f"({self.id_field},eq,{record_id})", "limit": "1"} + params = {"where": f"({self.ID_FIELD},eq,{record_id})", "limit": "1"} result = self._request("GET", params=params) records = result.get("list", []) return records[0] if records else None @@ -266,7 +266,7 @@ class NocoDBClient: def update_record(self, row_id: int, value: str) -> bool: """Update the seeding_users field on a record.""" try: - data = {"Id": row_id, self.seeding_field: value} + data = {"Id": row_id, self.SEEDING_FIELD: value} self._request("PATCH", data=data) return True except Exception as e: @@ -302,13 +302,10 @@ def main(): Examples: # API mode - update NocoDB directly: %(prog)s --id-folder ./torrents --bt-backup ~/.local/share/qBittorrent/BT_backup \\ - --nocodb-url https://noco.example.com --table-id tblXXXXX --api-token xc-xxxx \\ - --id-field cXXXXX --seeding-field cYYYYY - + --nocodb-url https://noco.example.com --table-id tblXXXXX --api-token xc-xxxx + # CSV mode - just output a file: %(prog)s --id-folder ./torrents --bt-backup /path/to/BT_backup --csv-only - -To find field IDs in NocoDB: click field header dropdown → Copy Field ID (starts with "c") """ ) @@ -324,12 +321,7 @@ To find field IDs in NocoDB: click field header dropdown → Copy Field ID (star help='NocoDB table ID (starts with "tbl")') parser.add_argument('--api-token', type=str, default=None, help='NocoDB API token (xc-token)') - parser.add_argument('--id-field', type=str, default=None, - help='Field ID for the Id column (starts with "c")') - parser.add_argument('--seeding-field', type=str, default=None, - help='Field ID for the seeding_users column (starts with "c")') - - # CSV fallback +# CSV fallback parser.add_argument('--csv-only', action='store_true', help='Skip API, just output CSV') parser.add_argument('--output', type=Path, default=Path('seeding_update.csv'), @@ -357,13 +349,13 @@ To find field IDs in NocoDB: click field header dropdown → Copy Field ID (star print(f"Mode: {'NocoDB API' if use_api else 'CSV output'}") if use_api: - if not all([args.nocodb_url, args.table_id, args.api_token, args.id_field, args.seeding_field]): - print("Error: API mode requires --nocodb-url, --table-id, --api-token, --id-field, and --seeding-field", file=sys.stderr) + if not all([args.nocodb_url, args.table_id, args.api_token]): + print("Error: API mode requires --nocodb-url, --table-id, and --api-token", file=sys.stderr) print(" Use --csv-only to skip API and just output CSV", file=sys.stderr) sys.exit(1) noco = NocoDBClient( args.nocodb_url, args.table_id, args.api_token, - args.id_field, args.seeding_field, args.debug + args.debug ) # Test connection @@ -446,7 +438,7 @@ To find field IDs in NocoDB: click field header dropdown → Copy Field ID (star continue # Parse current seeders - current_seeders = parse_multiselect(record.get(noco.seeding_field)) + current_seeders = parse_multiselect(record.get(noco.SEEDING_FIELD)) if username in current_seeders: print(f" = {torrent_id}: already listed")