Sign inSign up

drumsergio/requester-torrent

By drumsergio

Updated about 9 hours ago

Image
0

2.8K

drumsergio/requester-torrent repository overview

requester-torrent banner

requester-torrent

Docker Pulls License: GPL-3.0

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.


Features

  • Auto-search -- polls Radarr and Sonarr for missing movies and episodes, then searches your trackers automatically
  • Full seasons only -- a show is fetched one complete season at a time: releases that name an episode (S03E01, 3x01) are ignored, and a season is left alone until its last episode has aired
  • Multi-tracker -- configure up to 9 UNIT3D trackers; the service tries each in order until a match is found
  • Auto-thank -- automatically thanks the uploader after downloading via Livewire
  • Session management -- cookie-based authentication with one automatic re-login attempt per cycle when a session expires, and an immediate retry the moment you drop in a fresh cookie file
  • Tracker-safe pacing -- a minimum gap between requests, one session check per cycle, and a hard stop on 429 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 flood
  • Telegram notifications -- get notified when torrents are grabbed or sessions expire (via Shoutrrr URL)
  • Duplicate prevention -- tracks processed media to avoid re-downloading
  • A command line for the same account -- tracker_cli.py reads account state, seeding obligations, deletion notices and requests as JSON, and refuses to send any write until you pass --confirm

Quick Start

docker 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.

Configuration

Copy .env.example to .env and configure:

cp .env.example .env
Required
VariableDescription
RADARR_URLRadarr instance URL
RADARR_API_KEYRadarr API key
SONARR_URLSonarr instance URL
SONARR_API_KEYSonarr API key
QBITTORRENT_URLqBittorrent Web UI URL
QBITTORRENT_USERqBittorrent username
QBITTORRENT_PASSqBittorrent password
Trackers

Add up to 9 trackers using numbered environment variables (TRACKER_1 through TRACKER_9):

VariableDescription
TRACKER_N_NAMEDisplay name for the tracker
TRACKER_N_URLTracker base URL
TRACKER_N_COOKIE_PATHPath to Netscape-format cookie file
TRACKER_N_USERNAMEAccount 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_PASSWORDPassword for auto-login (optional)
TRACKER_N_PROFILEUNIT3D generation: unit3d-v9 (default) or unit3d-v6. Only tracker_cli.py reads it
TRACKER_N_MAPOptional JSON file of category/type/resolution ids for tracker_cli.py request-create, in the format of addons/example_tracker.json
Optional
VariableDefaultDescription
POLL_INTERVAL3600Seconds between search cycles
MAX_SIZE_BYTES16106127360Maximum torrent size (default ~15 GiB)
MOVIE_CATEGORYMoviesqBittorrent category for movies
TV_CATEGORYTVqBittorrent category for TV shows
TRACKER_REQUEST_DELAY2.0Minimum seconds between two requests to the same tracker
SHOUTRRR_URLShoutrrr Telegram URL for notifications
LOG_LEVELINFOLogging level
DATA_DIR/dataDirectory for persistence data
JELLYSEERR_URLJellyseerr URL — enables the already-available check (see below)
JELLYSEERR_KEYJellyseerr API key — required together with JELLYSEERR_URL
Skipping titles you already have

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.

Being a good guest on a tracker

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.

  • One session check per cycle. The cycle verifies the session when it opens, not before every search.
  • One login attempt per cycle. When the login form does not let us in, say because of a captcha or wrong credentials, the tracker is marked unavailable and receives nothing else until the next cycle. The expiry alert goes out once, not once per search.
  • 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.
  • An error page is not a live session. The session check reads the status code, not just the body. A 403 challenge page from an expired Cloudflare clearance, or a 503, counts as no session, not as a healthy one.
  • A tracker that keeps failing is dropped for the cycle. Four failed requests in a row, with nothing succeeding in between, block the tracker for the rest of the cycle and count against the back-off, exactly like a failed login. A single 404 or 500 on one search is ordinary noise and changes nothing.
  • Repeated bad cycles back off. After consecutive failing cycles the tracker is skipped entirely for 1, 2, 4 then 8 cycles, capped at 8. One good cycle resets the count. Once the cap is reached that is one attempt every nine cycles — at the default hourly poll, roughly once every nine hours instead of every hour forever.
  • Fresh cookies skip the queue. At the start of each cycle the cookie file is hashed. If its contents changed since it was last loaded, it is reloaded before anything else, the back-off is cleared, and the session check runs against the new jar — so a hand refresh takes effect on the very next cycle and no password login is posted when the new cookies work. An open Retry-After hold is left alone: new cookies do not undo a rate limit.
  • A floor between requests. 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.

Full seasons only

TV is fetched one complete season at a time. Two rules enforce it:

  • A release whose name points at an episode (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.
  • A season is left alone until its last episode has aired. A season still airing can only be had as loose episodes or as a pack that stops halfway, and neither is wanted. A season with no announced air dates is not complete either.

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.

The tracker command

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.

CommandWhat it answers
whoamiratio, buffer, bonus balance, seeding count, active warnings, hit-and-run count, thanks ratio
hnrevery 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 --minethis 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.

Addons

Request Submitter

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.

License

GPL-3.0

Tag summary

Content type

Image

Digest

sha256:7fa6bbb25

Size

45.8 MB

Last updated

4 months ago

docker pull drumsergio/requester-torrent