Automated torrent search and download service for UNIT3D-based private trackers. Polls Radarr/Sonarr for wanted media, searches configured trackers, downloads the best match, injects into qBittorrent, and auto-thanks uploaders.
Works with any UNIT3D tracker, with no tracker-specific code — HD-Olimpo and Torrentland are two examples of compatible sites. Point TRACKER_N_URL at one, or at several, and drop in a cookie file.
S03E01, 3x01) are ignored, and a season is left alone until its last episode has aired429 Too Many Requests, on a challenge page, or on a tracker that has started failing every request, so a dead session can never turn into a floodtracker_cli.py reads account state, seeding obligations, deletion notices and requests as JSON, and refuses to send any write until you pass --confirmdocker run -d \
--name requester-torrent \
--restart unless-stopped \
-v ./cookies:/cookies \
-v ./data:/data \
--env-file .env \
drumsergio/requester-torrent:1.5.2
Tags are semver and published from git tags; pick the version you want rather than tracking a floating tag.
Copy .env.example to .env and configure:
cp .env.example .env
| Variable | Description |
|---|---|
RADARR_URL | Radarr instance URL |
RADARR_API_KEY | Radarr API key |
SONARR_URL | Sonarr instance URL |
SONARR_API_KEY | Sonarr API key |
QBITTORRENT_URL | qBittorrent Web UI URL |
QBITTORRENT_USER | qBittorrent username |
QBITTORRENT_PASS | qBittorrent password |
Add up to 9 trackers using numbered environment variables (TRACKER_1 through TRACKER_9):
| Variable | Description |
|---|---|
TRACKER_N_NAME | Display name for the tracker |
TRACKER_N_URL | Tracker base URL |
TRACKER_N_COOKIE_PATH | Path to Netscape-format cookie file |
TRACKER_N_USERNAME | Account username on this site. Used for auto-login, and by tracker_cli.py for every per-user page. Accounts on different sites do not share a username, so set it per tracker |
TRACKER_N_PASSWORD | Password for auto-login (optional) |
TRACKER_N_PROFILE | UNIT3D generation: unit3d-v9 (default) or unit3d-v6. Only tracker_cli.py reads it |
TRACKER_N_MAP | Optional JSON file of category/type/resolution ids for tracker_cli.py request-create, in the format of addons/example_tracker.json |
| Variable | Default | Description |
|---|---|---|
POLL_INTERVAL | 3600 | Seconds between search cycles |
MAX_SIZE_BYTES | 16106127360 | Maximum torrent size (default ~15 GiB) |
MOVIE_CATEGORY | Movies | qBittorrent category for movies |
TV_CATEGORY | TV | qBittorrent category for TV shows |
TRACKER_REQUEST_DELAY | 2.0 | Minimum seconds between two requests to the same tracker |
SHOUTRRR_URL | Shoutrrr Telegram URL for notifications | |
LOG_LEVEL | INFO | Logging level |
DATA_DIR | /data | Directory for persistence data |
JELLYSEERR_URL | Jellyseerr URL — enables the already-available check (see below) | |
JELLYSEERR_KEY | Jellyseerr API key — required together with JELLYSEERR_URL |
Radarr reports a movie as missing whenever it holds no imported file. In
setups where the download client writes straight into the library folder and
Radarr never imports anything, that stays true forever — so a film you already
own sits in wanted/missing permanently and gets grabbed again on the next
cycle, leaving two copies in your media server.
Set JELLYSEERR_URL and JELLYSEERR_KEY and each wanted movie is checked
against Jellyseerr first; anything already available is logged and skipped
instead of being downloaded again:
[INFO] requester-torrent: Already in library, skipping: Chungking Express (1994)
The check fails open — if Jellyseerr is unreachable, downloads proceed as before. Leave both unset to keep the previous behaviour.
A cycle runs one search per wanted item per tracker. A series season is searched
under its primary title, under each alternate title, and under both the S01 and
Temporada 1 spellings. On a large Radarr/Sonarr library that is easily a
thousand searches per tracker per cycle. The rules below keep that traffic
steady, and stop a broken session from turning it into a flood of logins.
429 Too Many Requests is final. The first 429 stops every request to that
tracker for the rest of the cycle. A Retry-After header is honoured, outlives
the cycle, and is capped at six hours.403 challenge page from an expired Cloudflare
clearance, or a 503, counts as no session, not as a healthy one.404 or 500 on one search is ordinary noise and changes nothing.Retry-After hold is left
alone: new cookies do not undo a rate limit.TRACKER_REQUEST_DELAY, 2 seconds by default, is
the minimum gap between two requests to the same tracker.Availability, back-off and pacing are all per tracker, so one tracker going quiet never slows down the others.
TV is fetched one complete season at a time. Two rules enforce it:
S03E01, 3x01, Capítulo 4) is
never grabbed, even when it is the only result. A loose episode would satisfy
Sonarr's "missing" list without giving you the season.Both checks run before anything is downloaded, so a still-airing season is simply skipped and picked up on a later cycle once it has finished.
tracker_cli.py ships in the image next to the service. It
talks to one configured tracker at a time, in one shot, and answers the
questions the polling loop does not. What does this account still owe? What did
the tracker delete? What is being requested?
docker run --rm -i --entrypoint python3 \
-v ./cookies:/cookies --env-file .env -w /app \
drumsergio/requester-torrent:1.5.2 tracker_cli.py "My Tracker" hnr
The site is picked by TRACKER_N_NAME or by its number. Reads print one JSON
object on stdout and progress on stderr.
| Command | What it answers |
|---|---|
whoami | ratio, buffer, bonus balance, seeding count, active warnings, hit-and-run count, thanks ratio |
hnr | every unsatisfied, pre-warned and hit-run torrent with the tracker's own seed time, the time still owed, the deadline, and whether qBittorrent still holds it. Rows you can still fix by seeding are marked actionable and sorted first |
requests --mine | this account's own requests, through the site's own page or filter |
deletions [--since 30d] | the mailbox's deletion notices, parsed into date, release name, reason and the replacement link |
requests [--query X] [--unfilled] [--mine] [--page N] | browse or search requests |
request <id> | one request |
bounty <request-id> <amount> | add bounty to a request |
request-create --title ... --category ... --type ... --resolution ... | create a request |
thank <torrent-id> | thank an uploader |
Writes print the exact method, path and fields they would send, with the CSRF
token blanked, and exit 2 without sending anything. Passing --confirm sends
it. Exit codes are 0 done, 2 needs --confirm, 3 blocked because the session is
dead or a cooldown is open, 4 refused.
Two rules are in the tool rather than in the caller's hands. No write takes more than one id, so there is no batch mode to reach for. And nothing here schedules or repeats. A site whose rules forbid automation must not be handed an unattended mode, so there is none to turn on.
UNIT3D generations differ in the bounty route and its field name, the request
form's route and fields, the mailbox path and the Livewire version. All of that
lives in one table in the file. TRACKER_N_PROFILE says which generation a site
runs, and the same entry carries the seeding rule that site publishes, which is
what hnr counts down against.
A read walks at most three listing pages, and --page N reads that page
alone. Nothing here pages through a list to emulate a filter the site does not
offer: --mine uses the site's own page or its own query parameter, and is
refused with exit 4 where there is neither.
The qBittorrent join tries the info hash, then the name, then the name with the file extension the client adds to a single-file torrent, then a loose comparison that ignores case, accents and repeated spaces. No rule matches on a prefix, because two editions of one film share everything up to the part that says which edition it is.
Seed time comes from the tracker, never from qBittorrent. The two are different
numbers and only one of them decides whether an obligation is served. Sites
write it differently: 1 day 1 hour on one, 1M 2S on another, where the
capitals are Spanish for a month and two weeks. A unit the parser does not
recognise makes the whole cell unknown, counted under seed_time_unknown, and
an unknown seed time is never reported as an obligation served.
A standalone CLI tool to submit torrent requests on any UNIT3D tracker from a Jellyseerr request ID. Each tracker's dropdown mappings (categories, resolutions, types) are defined in a JSON config file.
# Set Jellyseerr env vars
export JELLYSEERR_URL=https://your-jellyseerr.example.com
export JELLYSEERR_KEY=your-api-key
# Submit a request using a tracker config
python3 addons/request_submitter.py --config addons/example_tracker.json 2834
# With options
python3 addons/request_submitter.py --config addons/example_tracker.json 2834 \
--bounty 50000 --resolution 2160p --type web-dl --anon
# Preview without submitting
python3 addons/request_submitter.py --config addons/example_tracker.json 2834 --dry-run
See addons/example_tracker.json for the config file format.
Content type
Image
Digest
sha256:7fa6bbb25…
Size
45.8 MB
Last updated
4 months ago
docker pull drumsergio/requester-torrent