Metadata game downloader — browses Metacritic for newly released games that pass configured score thresholds, matches them against download sources (FitGirl repacks), and sends qualifying games to qBittorrent. Sources are checked in config-defined priority order.
- Two-phase score verification — browse pages use internal Metacritic metrics (not real 0–100 scores) to build a candidate pool quickly. Browse scores are on a completely different scale (e.g. 1478, 1931) and always pass the configured thresholds — the real 0–100 score filtering only happens during the detail-page verification step. This avoids hundreds of slow HTTP requests while keeping score accuracy.
- Genre-based rejection — exclude games by genre using case-insensitive
substring matching (e.g.
["RPG"]rejects "Action RPG", "JRPG", etc.). - Keyword-based title filtering — reject games whose titles contain specific keywords.
- Relative date cutoff — set a look-back window in weeks instead of
maintaining a static date (e.g.
max_weeks: 52for roughly one year). - Re-verification with expiry — games whose real Metacritic detail
page scores don't match the browse page scores are kept in a pending queue
for re-verification until they expire (
max_queue_days). Once scores pass thresholds, a fresh expiry window begins for the FitGirl-matching phase. - Two-phase pending expiry — games awaiting score review expire after
max_queue_days(under metacritic thresholds). Once scores pass, a fresh expiry window (download_sites.fitgirl.max_queue_days) starts for the FitGirl-matching phase. Set either value to0for indefinite pending. - Multiple download sources — supports FitGirl repacks (sitemap-based). Sources are configured as an ordered list — position determines priority, and the first source with a match delivers the torrent.
- Source priority — if a game is available on multiple sources, the highest-priority source in the config list is used. Games that don't match any source remain in the pending queue.
- Database deduplication — every processed game is recorded in SQLite; previously processed titles are never re-processed.
- Configurable cache TTLs — Metacritic scores and browse pages are cached with independent TTLs to reduce HTTP requests.
- Notifications — sends alerts via any apprise-compatible service (ntfy, Discord, Telegram, email, and more).
- Schedule mode — runs as a scheduled background process, or in foreground mode for single-pass execution.
- Automatic config migration — upgrades the YAML config schema automatically on startup when new fields are added.
- Python 3.12+
- Astral uv
- qBittorrent with WebUI enabled
git clone https://github.com/binhex/gamarr
cd gamarr
uv venv --quiet
uv sync# Validate configuration
gamarr --test
# Run a single acquisition cycle
gamarr| Option | Description |
|---|---|
--config-path <dir> |
Directory containing gamarr.yml (default: configs) |
--log-level <level> |
Override console log level (DEBUG, INFO, SUCCESS, WARNING, ERROR) |
--log-path <path> |
Override log file path |
--db-path <dir> |
Override the database directory from config |
--pid-path <dir> |
Override the PID file directory from config |
--library-path <paths> |
Override library paths (pipe-separated: --library-path /a|/b) |
--qbt-host <host> |
Override qBittorrent host from config |
--qbt-port <port> |
Override qBittorrent WebUI port from config |
--qbt-username <user> |
Override qBittorrent username from config |
--qbt-password <pass> |
Override qBittorrent password from config |
--clear-cache <sources> |
Clear cached data. Comma-separated: fitgirl, metacritic, or all |
--test |
Validate configuration and exit |
--version |
Show version and exit |
--help |
Show help message and exit |
All behaviour is controlled by a YAML file inside the config directory
(configs/gamarr.yml by default). A default config is created automatically
on first run. The file is divided into the sections below.
| Key | Description | Default |
|---|---|---|
config_version |
Schema version — managed automatically; do not edit. | (current) |
daemon_mode |
foreground or background. Scheduling is controlled by schedule.acquisition.enabled. This field is deprecated. |
foreground |
log_level_console |
Console logging level. | INFO |
log_level_file |
File logging level. | INFO |
log_path |
Directory for log files (gamarr.log is created inside). |
logs |
db_path |
Directory for the SQLite history database (gamarr.db is created inside). |
db |
pid_path |
Directory for the PID file (gamarr.pid is created inside). |
pids |
library_path_list |
Override library paths from CLI (--library-path). Mapped to library.paths at runtime. |
[] |
| Key | Description | Default |
|---|---|---|
acquisition.enabled |
Enable or disable the acquisition task (scheduled daemon mode). | false |
acquisition.schedule_time_mins |
Interval in minutes between acquisition cycles. | 60 |
acquisition.run_on_start |
Run acquisition immediately on start, before the first interval. | true |
download_sites is an ordered list of source configurations. Each
entry is a single-key dict where the key IS the source name.
Position in the list determines priority — the first source that has
a match delivers the torrent.
download_sites:
- fitgirl:
enabled: true
feed_url: https://fitgirl-repacks.site/feed/
platform: pc
cache_pages_hours: 6
reject_keywords: []
max_queue_days: 60| Field | Description | Default |
|---|---|---|
enabled |
Enable or disable this source. | true |
feed_url |
Source URL (RSS feed, user page, etc.). | None |
platform |
Target platform for matching. | pc |
cache_pages_hours |
How long to cache the source index before re-fetching. | 6 |
reject_keywords |
Reject titles containing any of these keywords (case-insensitive). | [] |
max_queue_days |
How many days a game stays in the pending queue after its scores are verified (the source-matching phase). 0 = indefinite pending (no expiry). |
60 |
| Key | Description | Default |
|---|---|---|
min_metascore |
Minimum Metacritic critic score (0–100). | 75 |
min_metascore_reviews |
Minimum number of critic reviews required. | 10 |
min_user_score |
Minimum Metacritic user score (0–10). | 7.5 |
min_user_reviews |
Minimum number of user reviews required. | 10 |
max_weeks |
Look-back window in weeks from today. Games released before this are skipped. null or 0 = no cutoff. |
13 |
max_cycle_weeks |
Size of the rolling scan window in weeks. Each cycle scans this many weeks of recent releases. Staged backlog catch-up: each cycle retreats up to max_weeks, then locks into steady-state scanning only the most recent max_cycle_weeks. Reduces HTTP load by limiting browse depth each cycle. 0 or null = unlimited (retreats through the full max_weeks window each time). |
4 |
age_recheck_weeks |
Games older than this (weeks since release) are permanently processed once their Metacritic scores pass thresholds. null or 0 = disabled. |
null |
enabled |
Enable or disable the Metacritic browse step. Disabling skips game discovery entirely. | true |
max_queue_days |
Days a game stays in the pending queue before expiring. 0 = indefinite pending (no expiry). |
30 |
cache_details_days |
Days to cache Metacritic detail-page results. | 7 |
cache_pages_hours |
Hours to cache Metacritic browse-page results. | 6 |
reject_genre |
Reject games whose Metacritic genre contains any of these substrings (case-insensitive). E.g. ["RPG"] matches "Action RPG", "JRPG", "Western RPG". |
[] |
reject_title |
Reject games whose title contains any of these substrings (case-insensitive). E.g. ["Remake"] matches "Resident Evil 4 Remake", "Remake Collection". |
[] |
| Key | Description | Default |
|---|---|---|
selected |
Torrent client to use. Currently only qbittorrent is supported. |
qbittorrent |
qbittorrent.host |
qBittorrent Web UI hostname or IP address. | localhost |
qbittorrent.port |
qBittorrent Web UI port. | 8080 |
qbittorrent.username |
Web UI username. | admin |
qbittorrent.password |
Web UI password. | adminadmin |
qbittorrent.add_paused |
Add torrents in paused state. | false |
qbittorrent.category |
Category tag applied to all gamarr-managed torrents. | games-gamarr |
| Key | Description | Default |
|---|---|---|
apprise_urls |
List of apprise service URLs. Leave empty to disable. | [] |
on_download |
Send a notification when a game is successfully added to qBittorrent. | true |
on_failure |
Send a notification when a game fails verification after max attempts. | false |
on_error |
Send a notification when an error occurs during the acquisition cycle. | false |
on_scrape_failure |
Send a notification when Metacritic scraping appears to be broken. | true |
| Key | Description | Default |
|---|---|---|
processed_expiry_days |
Delete processed history records older than this many days. | 365 |
| Key | Description | Default |
|---|---|---|
paths |
List of library root paths to scan when checking whether a game already exists. | [] |
flowchart TD
A([Start cycle]) --> B{Platform\nenabled?}
B -- No --> END([End])
B -- Yes --> C[Browse Metacritic\nnewest-first]
C --> D[Parse browse-page\nscores & titles]
D --> E[For each game]
E --> F{Exclude\nkeyword\nin title?}
F -- Yes --> SKIP1([⛔ Skip])
F -- No --> G{Meets score\nthresholds on\nbrowse page?}
G -- No --> SKIP2([⛔ Skip])
G -- Yes --> H{Within cutoff\nweeks?}
H -- No --> SKIP3([⛔ Skip])
H -- Yes --> I[Add to pending\nqueue]
I --> J[For each pending game]
J --> K[Look up real scores\non Metacritic\ndetail page]
K --> L{Genre matches\nreject_genre?}
L -- Yes --> FAIL1([❌ Remove -\nrejected genre])
L -- No --> M{Real scores\npass thresholds?}
M -- Yes --> N[Mark as verified\nwith real scores]
M -- No --> O{Attempts <\nmax_verify\n_attempts?}
O -- Yes --> P([Keep in queue\nfor re-check])
O -- No --> FAIL2([❌ Remove -\nmax attempts])
N --> Q[Match verified\ngames against\nFitGirl sitemap]
Q --> R{FitGirl match\nfound?}
R -- No --> STAY([Stay in queue])
R -- Yes --> S[Fetch magnet link\nfrom FitGirl page]
S --> T[Add to qBittorrent\nwith gamarr-* tag]
T --> U([Record in\nhistory])
END --> V{Daemon\nmode?}
V -- Yes --> A
V -- No --> W([Exit])
- Metacritic browse — Scans Metacritic browse pages (newest-first) for
games matching the target platform. Important: browse pages show
internal browse-only metrics, not the real 0–100 critic scores or
0–10 user scores. These rough scores are used only to build a candidate
pool — the real filtering happens in step 4. Games are collected within the
max_weekswindow. are collected. - Browse-page filtering — Games whose titles match
reject_titleare skipped. Games outside themax_weekswindow are skipped.max_cycle_weekscontrols the per-cycle scan window size (default 4 weeks). Each cycle retreats up tomax_weeks, then switches to steady-state — always scanning the most recentmax_cycle_weeks. Note: browse scores are on a different scale and always exceed the configured thresholds — score filtering effectively starts at the verification step (phase 4), not here. - Pending queue — Surviving games enter a
pending_gamesqueue with a configurable expiry (review_sites.metacritic.platform_overrides.*.max_queue_days, default 30, or0for indefinite). They wait for a detail-page verification pass. - Score verification — Each pending game's real Metacritic detail page
is fetched. The real 0–100 critic score and 0–10 user score are compared
against configured thresholds. Games whose real scores fail the checks
are kept for re-verification.
When scores pass, the game's expiry is recalculated to
now + <source>.max_queue_days(default 60 per source, or0for indefinite), giving it a fresh window for the source-matching phase. - Genre rejection — Before score verification, the game's genres
(extracted from the detail page) are checked against
reject_genre. Matching games are removed from pending immediately — no score evaluation or re-verification (genres never change). - FitGirl sitemap fetch — Only when there are verified games in the queue. The FitGirl repacks sitemap is fetched and indexed.
- Source matching — Verified games are matched against the FitGirl sitemap by title. The best-matching repack title gets its magnet link fetched.
- Delivery — Matched games are added to qBittorrent with a
gamarr-*tag. The result is recorded in the history database. - Notifications — Optional Apprise notifications on download, failure, or error.
The codebase is structured as a Metacritic-first pipeline:
metacritic.py → pipeline.py → sources/fitgirl.py → qbittorrent.py
_(Note: `sources/` is the Python package name for download site implementations — distinct from the config key.)
↓ ↓ ↓ ↓
Browse new Score filter + Sitemap match Add torrent
releases pending queue + magnet fetch
All configuration is driven by a YAML file (configs/gamarr.yml) validated
with Pydantic. The scheduler (scheduler.py) runs in single-pass mode by
default, or in continuous scheduled mode when
schedule.acquisition.enabled is true.
git clone https://github.com/binhex/gamarr
cd gamarr
uv venv --quiet
uv sync --extra devBefore committing, run the full lint suite:
pre-commit run --all-filesuv run pytestQ: What download sites are supported?
A: Currently FitGirl repacks.
Q: Can I add Nintendo Switch games?
A: Planned for a future release via Jackett/Prowlarr integration.
Q: What if qBittorrent is not running?
A: gamarr will log a warning and skip the acquisition cycle.
Q: How do I reject specific game genres?
A: Use the reject_genre option under review_sites.metacritic.platform_overrides.pc.
It uses case-insensitive substring matching. For example:
["RPG"]matches "Action RPG", "Western RPG", "JRPG", "RPG"["Western RPG"]only matches genres containing "Western RPG"["Action"]matches "Action", "Action RPG", "Open-World Action"
See the genres section for details.
Q: Why do I see "X from previous cycles" in the log — why aren't those games leaving the queue?
A: Games leave the pending queue in only two ways:
- Downloaded — scores pass thresholds AND a matching torrent is found on FitGirl
- Aged out —
age_recheck_weeksis set and the game's release date is old enough
Games that are verified but fail score thresholds, or pass but have no FitGirl match, stay in the queue. They get re-verified each cycle because their scores could change or a new FitGirl repack could appear. If you find the queue growing indefinitely:
- Set
age_recheck_weeksto a reasonable value (e.g.52for one year) to automatically retire old games - Games with no
release_datecannot be aged out — this is expected for unreleased or obscure titles - The message
0 new + X from previous cyclesmeans browsing didn't find anything new, and all X are carryovers from earlier cycles
Q: Why does max_queue_days have two separate settings — one for Metacritic and one for FitGirl?
A: There are two waiting rooms. When a game is first discovered, it sits
in the first waiting room while gamarr checks its Metacritic scores. The
review_sites.metacritic max_queue_days is the time limit for this phase — if scores
can't be verified in time, the game is removed.
Once scores pass, the game moves to a second waiting room where it waits for
a source match (e.g. FitGirl repack). The per-source max_queue_days
(e.g. fitgirl.max_queue_days) starts a
fresh countdown from this point — it doesn't matter how long the game
spent in the first room. This gives the game a full window to find a match,
regardless of how long the score-checking took.
So even if both values are the same (say 30 days each), a game that took 25 days to get scores verified still gets a full 30 days to find a source match — not just 5.
If you appreciate my work, then please consider buying me a beer :D
