Skip to content

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tracker-thanks-bot

Auto-thanks bot for private trackers, driven by Radarr/Sonarr webhooks and a daily scan of qBittorrent. When a new torrent is grabbed, the bot logs into the tracker site and calls its "thanks" button over HTTP, without a browser.

Features

  • Webhook-driven: receives Grab events from Radarr and Sonarr, looks up the torrent's comment in qBittorrent, parses the tracker URL, and thanks the upload.
  • Daily scan: at a configurable hour, walks every torrent in qBittorrent and thanks any that haven't been thanked yet.
  • Per-site session reuse: one cookie jar per tracker, so login happens once and stays cached on disk.
  • Per-site serial queue: concurrent webhook bursts are serialized per tracker so two grabs never log in at once.
  • Prometheus metrics on /metrics.
  • Graceful shutdown: in-flight thank tasks finish before exit (30 s timeout).

Requirements

  • Node.js ≥ 26.8.1 (or Docker).
  • A running qBittorrent instance (WebUI enabled).
  • Radarr and/or Sonarr to send webhooks (optional — the daily scan works on its own).
  • An account on each tracker you want to thank.

Configuration

All configuration is via environment variables. See .env.example.

Variable Required Default Description
WEBHOOK_PORT no 3000 HTTP port for the webhook server.
WEBHOOK_SECRET recommended — If set, POST /webhook/* requires header X-Webhook-Secret: <value>. If unset, the endpoints are unauthenticated and a warning is logged at startup.
QBIT_URL yes — qBittorrent WebUI base URL (e.g. http://qbit:8080).
QBIT_API_KEY preferred (qBit ≥ 5.2) — API key. If set, takes precedence over username/password.
QBIT_USERNAME if no API key — qBittorrent WebUI username.
QBIT_PASSWORD if no API key — qBittorrent WebUI password.
<ID>_USERNAME per site — Tracker username, where <ID> is the Site id from sites.json uppercased with - replaced by _ (see Sites).
<ID>_PASSWORD per site — Tracker password (same convention as above).
SITES_CONFIG_PATH no ./config/sites.json (source) / /app/config/sites.json (Docker) Path to the Sites config file.
CACHE_DIR no ./.cache Where each Site's session cookies are stored.
SCAN_ENABLED no true Run the daily scan. Set to false to disable.
SCAN_HOUR no 3 Hour (0–23) at which the daily scan runs. An out-of-range value aborts startup.
SCAN_ON_START no false Run a scan immediately on startup.
SCAN_DELAY_MS no 1000 Pause between consecutive calls to a Site during a scan. Keeps a full scan from arriving as one burst, which a private tracker may read as abuse.

Sites

The Sites the bot operates on are defined in an operator-supplied sites.json. Source code does not ship with any Site identifier.

Schema

{
  "sites": [
    {
      "id": "example",
      "base_url": "https://tracker.example.com"
    }
  ]
}
Field Required Default Notes
id yes — Must match ^[a-z][a-z0-9-]{0,31}$. Cannot be a reserved word (serve, scan, help, version, init, list, add, remove, login, test). Used as cache directory name, metrics label, log prefix, and credential env var prefix.
base_url yes — Full URL of the Site. Normalized at load (host lowercased, trailing slash stripped). Two Sites cannot share the same normalized base_url.

File location

Mode Path
Docker (default) /app/config/sites.json (mount your file or volume here)
From source (default) <repo>/config/sites.json
Override (any mode) Set SITES_CONFIG_PATH

A starter file is provided at config/sites.example.json.

Credentials

For each Site id, set <ID>_USERNAME and <ID>_PASSWORD environment variables, where <ID> is the id uppercased with - replaced by _. For example, id: my-tracker requires MY_TRACKER_USERNAME and MY_TRACKER_PASSWORD. Missing credentials abort startup.

Running

Docker (recommended)

docker run -d --name tracker-thanks-bot \
  -p 3000:3000 \
  -v tracker-thanks-cache:/app/.cache \
  -v /path/to/your/sites.json:/app/config/sites.json:ro \
  -e WEBHOOK_SECRET=your-shared-secret \
  -e QBIT_URL=http://qbittorrent:8080 \
  -e QBIT_API_KEY=your-qbit-api-key \
  -e <ID>_USERNAME=... -e <ID>_PASSWORD=... \
  ghcr.io/alorle/tracker-thanks-bot:latest

Replace <ID> with whatever you named each Site in sites.json (uppercased, - → _). Repeat the username/password pair for every Site.

Images are published to GHCR on every push to main.

From source

nvm use                  # uses .node-version (26.8.1)
npm ci
npm run build
node dist/index.js serve

CLI mode

Thank a specific torrent without running the server:

node dist/index.js <site> <torrentId> [<torrentId> ...]

Where <site> is a Site id defined in your sites.json.

Run a one-shot scan and exit:

node dist/index.js scan

In Docker, run these one-off commands with docker run --init: only serve handles SIGTERM and SIGINT itself.

Radarr / Sonarr setup

In each app, go to Settings → Connect → Add → Webhook:

  • URL: http://<host>:3000/webhook/radarr (or /webhook/sonarr).
  • Method: POST.
  • Triggers: enable On Grab only.
  • Headers: add X-Webhook-Secret = <your WEBHOOK_SECRET> if you set one (strongly recommended if the server is reachable beyond your LAN).

Endpoints

Method Path Description
POST /webhook/radarr Radarr Grab webhook. Requires X-Webhook-Secret if WEBHOOK_SECRET is set.
POST /webhook/sonarr Sonarr Grab webhook. Same auth as above.
GET /health Liveness probe. Returns 200 {"status":"healthy"}.
GET /metrics Prometheus metrics.

Metrics

Key metrics exposed (all prefixed with tracker_):

  • webhooks_received_total{source,event_type}
  • webhook_processing_duration_seconds{source,site}
  • torrents_thanked_total{site}, torrents_skipped_total{site,reason}, torrents_errored_total{site} — reason is one of no_button, already_thanked, quota_exhausted, not_eligible, rejected
  • thank_duration_seconds{site}
  • scans_completed_total{status}, scan_duration_seconds, scan_last_torrents_processed{result}
  • qbittorrent_api_duration_seconds{endpoint}, qbittorrent_api_errors_total{endpoint}
  • logins_total{site,status}
  • Plus the default Node.js process metrics.

Refusals

A Site can take the thanks, or turn it down with the button still on offer. It says so in its own prose, in whatever language it is installed in, and the bot has to decide what that prose meant before it can act on it.

Two of those refusals come from Livewire rather than from the Site — Component payload was altered! and Wrong component! — and mean the bot built a bad request. Those are matched literally, with no network call, and counted as errors rather than skips.

The rest are the Site's own words, and are recorded as rejected.

License

MIT

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages