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:
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")