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.
This commit is contained in:
constantprojects
2026-02-17 15:21:57 -07:00
parent 4d6e664ba6
commit 16eb35f095

View File

@@ -16,12 +16,10 @@ Requirements:
Usage: Usage:
python seed_tracker.py --id-folder ./torrents --bt-backup /path/to/BT_backup \\ 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 \\ --nocodb-url https://noco.example.com --table-id tblXXX --api-token xc-xxx
--id-field cXXX --seeding-field cYYY
To find NocoDB IDs: To find NocoDB IDs:
- Table ID: Click ... next to table name → Copy Table ID - Table ID: Click ... next to table name → Copy Table ID
- Field IDs: Click field header dropdown → Copy Field ID
""" """
import argparse import argparse
@@ -209,18 +207,20 @@ def get_bt_backup_data(bt_backup_path: Path) -> tuple[set[str], dict[tuple[str,
class NocoDBClient: class NocoDBClient:
"""Simple NocoDB API client.""" """Simple NocoDB API client."""
ID_FIELD = "Id"
SEEDING_FIELD = "Seeding Users"
def __init__(self, base_url: str, table_id: str, api_token: str, def __init__(self, base_url: str, table_id: str, api_token: str,
id_field: str, seeding_field: str, debug: bool = False): debug: bool = False):
self.base_url = base_url.rstrip('/') self.base_url = base_url.rstrip('/')
self.table_id = table_id self.table_id = table_id
self.api_token = api_token self.api_token = api_token
self.id_field = id_field
self.seeding_field = seeding_field
self.debug = debug self.debug = debug
self.endpoint = f"{self.base_url}/api/v2/tables/{table_id}/records" 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.""" """Make an API request."""
if url is None:
url = self.endpoint url = self.endpoint
if params: if params:
query = "&".join(f"{k}={urllib.request.quote(str(v))}" for k, v in params.items()) query = "&".join(f"{k}={urllib.request.quote(str(v))}" for k, v in params.items())
@@ -254,7 +254,7 @@ class NocoDBClient:
def get_record(self, record_id: int | str) -> dict | None: def get_record(self, record_id: int | str) -> dict | None:
"""Get a single record by ID.""" """Get a single record by ID."""
try: 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) result = self._request("GET", params=params)
records = result.get("list", []) records = result.get("list", [])
return records[0] if records else None return records[0] if records else None
@@ -266,7 +266,7 @@ class NocoDBClient:
def update_record(self, row_id: int, value: str) -> bool: def update_record(self, row_id: int, value: str) -> bool:
"""Update the seeding_users field on a record.""" """Update the seeding_users field on a record."""
try: try:
data = {"Id": row_id, self.seeding_field: value} data = {"Id": row_id, self.SEEDING_FIELD: value}
self._request("PATCH", data=data) self._request("PATCH", data=data)
return True return True
except Exception as e: except Exception as e:
@@ -302,13 +302,10 @@ def main():
Examples: Examples:
# API mode - update NocoDB directly: # API mode - update NocoDB directly:
%(prog)s --id-folder ./torrents --bt-backup ~/.local/share/qBittorrent/BT_backup \\ %(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 \\ --nocodb-url https://noco.example.com --table-id tblXXXXX --api-token xc-xxxx
--id-field cXXXXX --seeding-field cYYYYY
# CSV mode - just output a file: # CSV mode - just output a file:
%(prog)s --id-folder ./torrents --bt-backup /path/to/BT_backup --csv-only %(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,11 +321,6 @@ To find field IDs in NocoDB: click field header dropdown → Copy Field ID (star
help='NocoDB table ID (starts with "tbl")') help='NocoDB table ID (starts with "tbl")')
parser.add_argument('--api-token', type=str, default=None, parser.add_argument('--api-token', type=str, default=None,
help='NocoDB API token (xc-token)') 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', parser.add_argument('--csv-only', action='store_true',
help='Skip API, just output CSV') help='Skip API, just output 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'}") print(f"Mode: {'NocoDB API' if use_api else 'CSV output'}")
if use_api: if use_api:
if not all([args.nocodb_url, args.table_id, args.api_token, args.id_field, args.seeding_field]): if not all([args.nocodb_url, args.table_id, args.api_token]):
print("Error: API mode requires --nocodb-url, --table-id, --api-token, --id-field, and --seeding-field", file=sys.stderr) 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) print(" Use --csv-only to skip API and just output CSV", file=sys.stderr)
sys.exit(1) sys.exit(1)
noco = NocoDBClient( noco = NocoDBClient(
args.nocodb_url, args.table_id, args.api_token, args.nocodb_url, args.table_id, args.api_token,
args.id_field, args.seeding_field, args.debug args.debug
) )
# Test connection # Test connection
@@ -446,7 +438,7 @@ To find field IDs in NocoDB: click field header dropdown → Copy Field ID (star
continue continue
# Parse current seeders # 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: if username in current_seeders:
print(f" = {torrent_id}: already listed") print(f" = {torrent_id}: already listed")