Skip to content
binhexPublic

About

Metadata game downloader — browses Metacritic for newly released games that pass configured score thresholds, matches them against the FitGirl repacks sitemap, and sends qualifying games to qBittorrent.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

gamarr

gamarr finds great games and puts them in your library — no browsing, no manual searches, no cleanup. A configurable pipeline scrapes Metacritic for critically-acclaimed releases, filters out unwanted genres and keywords, matches titles against FitGirl repacks and FreeGOG, and delivers torrents to qBittorrent. Once downloads complete, a post-processing thread SHA-256-verifies and copies each game to your library with customizable path templates, then cleans up source torrents. Runs as a daemon so your library stays fresh without lifting a finger.

Features

  • 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 filtering — reject games whose titles or FitGirl page description contain specific keywords.
  • Date-based backlog cutoff — set the oldest month to review with max_date: 202401 and how much of the backlog one cycle covers with max_cycle_months: 2. Pages remain available as max_pages / max_cycle_pages for criticscore/userscore, whose browse lists are score-ordered and have no date dimension.
  • Verify before queueing — a game's real Metacritic detail-page scores are checked before it enters the pending queue, so the queue only ever holds games that already pass the thresholds. Games that fail, or whose scores are not published yet, are not queued: they are re-checked when a browse pass next reaches their pages (immediately in latest mode, when the sweep wraps in backlog mode) instead of occupying the queue for days.
  • Source-matching expiry — a queued (already verified) game waits download_sites.fitgirl.max_queue_days for a matching repack (default 60, or 0 for indefinite; the FitGirl entry supplies the window for every queued game). Every game is queued already verified, so it takes that window at insert, and later re-checks do not push the window forward, so a verified game with no match still leaves the queue when the window lapses. Without a FitGirl source, the source-matching window falls back to the Metacritic-side max_queue_days.
  • Multiple download sources — supports FreeGOGPCGames (A-Z list-based) and 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 with separate intervals for acquisition and post-processing, or in foreground mode for single-pass execution.
  • Automatic config migration — upgrades the YAML config schema automatically on startup when new fields are added.
  • One-time queue cleanup — on first start after upgrading, pending games that never passed detail verification are deleted. They are re-discovered when a browse pass next reaches their pages (immediately in latest mode, when the sweep wraps in backlog mode). Verified games awaiting a source match are kept.
  • Post-processing — a background thread polls qBittorrent for completed downloads, copies them to a configurable library path with SHA-256 verification, and cleans up source torrents after seeding goals are met. Torrents whose metadata has not been fetched yet (a paused magnet holds no files) are not complete: they are skipped quietly and reported once per cycle in the summary line (N downloading, M awaiting metadata), never as a copy failure.

Prerequisites

Quick start

Installation

git clone https://github.com/binhex/gamarr
cd gamarr
uv venv --quiet
uv sync

Usage

# Validate configuration
gamarr --test

# Run a single acquisition cycle
gamarr

CLI Options

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 <dir> Override the log file directory from config (logs are written to <dir>/gamarr.log).
--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: freegog, fitgirl, metacritic, or all
--test Validate configuration and exit
--version Show version and exit
--help Show help message and exit

Configuration

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.

general

Key Description Default
config_version Schema version — managed automatically; do not edit. (current)
daemon_mode foreground or background. Scheduling is controlled by schedule.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. []

schedule

Key Description Default
enabled Enable or disable scheduled background mode. false
schedule_time_mins Interval in minutes between scheduled cycles. 60
run_on_start Run immediately on start, before the first interval. true
acquisition_timeout_mins Hard watchdog budget (minutes) for one acquisition cycle. A cycle that exceeds this is aborted so the next scheduled run can proceed. Bump for very large fresh source indexes. 25

download_sites

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:
  - freegog:
      enabled: true
      platform: pc
      cache_hours: 6
      reject_keywords: []
      max_queue_days: 60
  - fitgirl:
      enabled: true
      platform: pc
      cache_hours: 6
      reject_keywords: []
      max_queue_days: 60

Per-source fields

Field Description Default
enabled Enable or disable this source. true
platform Target platform for matching. pc
cache_hours How long to cache the source index before re-fetching. 6
reject_keywords Reject games whose FitGirl page title or "Repack Features" block contains any of these keywords (case-insensitive). Matching is whole-word and negation-aware against the page title and the "Repack Features" block only, so "non-HV repack" does not match hv. []
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

review_sites.metacritic

review_sites.metacritic.platform_overrides.<platform>

Key Description Default
min_criticscore Minimum Metacritic critic score (0–100). 75
min_criticscore_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
mode Discovery mode. "latest" reads up to max_pages pages of the window every cycle (no cursors, no pacing) so new releases are always seen, spending the depth newest year first and keeping one page for the immediately lower year; "backlog" sweeps back through the window with per-year cursors and pacing. Latest requires sort_order: "new" — a score sort is warned about and coerced to "new" for that cycle. backlog
max_pages Page budget. In backlog mode: total browse page depth budget; the scanner advances across cycles and resets to page 1 when exhausted. In latest mode: depth — the first N pages of the window are read every cycle. 0 = no page budget in backlog (the sweep is still bounded by the 10-year year window) and is clamped to a depth of 10 in latest. 500
max_cycle_pages Maximum pages per cycle (pacing). 0 = unlimited. 0
max_date Date floor for the new sort order: sweep back to and including this month (YYYYMM with month 01-12 and year 1958 or later, e.g. 202401). 0 = disabled. It bounds the window and is ignored for criticscore/userscore; in backlog mode it replaces the max_pages budget, while in latest mode max_pages still sets the depth inside that window. A month later than the current one is clamped to the current month. 0
max_cycle_months Pace in date mode: how far back one cycle may sweep, counted in months from the cycle's newest release month (a month is included when a page holds any game at or after its first day, so a cycle can cover N+1 calendar months at a boundary). The floor set by max_date always wins — this value can only shorten a cycle, never extend past it. 0 = no month pacing; the maximum is 1200 (100 years), and a larger value is rejected at config load. 0
sort_order Discovery and pending processing order: "new" (newest release date first), "criticscore" (verified games by highest critic score), or "userscore" (verified games by highest user score). Queued games are already verified (see "Verify before queueing"). Changing this value reprioritizes the entire pending queue, including games from previous cycles. "new"
max_queue_days Fallback expiry window for queued games, used only when no FitGirl source is configured (its max_queue_days normally governs the source-matching window). 0 = indefinite pending (no expiry). 30
enabled Enable or disable the Metacritic browse step. Disabling skips game discovery entirely. true
cache_details_days Days to cache Metacritic detail-page results. 7
cache_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". []

Games are verified against their Metacritic detail page before they enter the pending queue, so the queue only ever holds games that already pass the score thresholds. Games whose scores are not published yet are re-checked on the next pass rather than queued.

torrent_client

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

notification

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

database

Key Description Default
processed_expiry_days Delete processed history records older than this many days (currently unused — no automatic history pruning exists yet). 365

library

Key Description Default
paths List of library root paths to scan when checking whether a game already exists. []

post_process

Controls automatic copying of completed downloads to a game library. Mirrors movarr's post-processing pattern.

Key Description Default
post_process_enabled Master toggle for post-processing. true
schedule_time_mins Polling interval in minutes. 5
run_on_start Run immediately on daemon start. true
library_path Destination path template. Supports {site}, {platform}, {genre}, {title}. Empty string disables copying. ""
copy_completed Copy files to library after download completes. true
remove_completed Delete torrent + data after seeding goals are met. true
max_seed_wait_hours Fallback: delete after this many hours even if still seeding. 0 = never force-delete. 168
exclude_file_min_kb Skip files smaller than this (0 = no minimum). 0
exclude_file_regex_list Case-insensitive regex patterns to exclude files. []
exclude_folder_regex_list Case-insensitive regex patterns to exclude folders. []
path_case Controls casing of template variables. "pretty" uses display-friendly names for source (FitGirl, FreeGOG); "lowercase" downcases everything. Platform, genre, and title pass through unchanged (already correctly capitalized from Metacritic/user config). "pretty"

Template variables in library_path:

Variable Source Example
{site} Download source name fitgirl (lowercase) or FitGirl (pretty)
{platform} Target platform pc or PC
{genre} First Metacritic genre Action

Casing of all variables is controlled by path_case above. | {title} | Metacritic game title | Elden Ring |

Example: "/data/library/{site}/{platform}/{genre}/{title}" produces "/data/library/FitGirl/pc/Action/Elden Ring" (with default path_case: "pretty").

How It Works

Acquisition pipeline

flowchart TD
    A([Start cycle]) --> B{Platform\nenabled?}
    B -- No --> END([End])
    B -- Yes --> C[Browse Metacritic\nconfigured sort_order]
    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 max_date\nfloor / max_pages\nwindow?}
    H -- No --> SKIP3([⛔ Skip])
    H -- Yes --> K[Look up real scores\non Metacritic\ndetail page]

    K --> M{Real scores\npass thresholds?}
    M -- No --> SKIP4([⛔ Skip -\nre-checked next pass])
    M -- Yes --> I[Add to pending queue,\nmarked verified]

    I --> J[For each verified\npending game]
    J --> L{Genre matches\nreject_genre?}
    L -- Yes --> FAIL1([❌ Remove -\nrejected genre])
    L -- No --> 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])
Loading

Detailed phases

  1. Metacritic browse — Scans Metacritic browse pages using the configured sort_order; new traverses the newest year first. Source matching and delivery use the same ordering, including games carried over from previous cycles. Important: browse pages show internal browse-only metrics, not the real 0–100 critic scores or 0–10 user scores. These rough scores only order and pre-filter candidates — the real threshold check happens before a game is queued (phase 3). mode: latest reads up to max_pages pages of the window every cycle, newest year first with one page held back for the years below; mode: backlog sweeps back through it. Games are collected within the max_pages browse window. In backlog date mode (max_date set with sort_order: new) the window is a month floor instead: the sweep stops inside the boundary year as soon as a page's games are all older than max_date, and max_cycle_months limits how many months of backlog a single cycle covers — max_pages and max_cycle_pages are then unused. mode: latest still reads max_pages pages per cycle whatever the date keys say. For criticscore/userscore the date keys are ignored, because those browse lists mix years and no page corresponds to a date.
  2. Browse-page filtering — Games whose titles match reject_title are skipped. In page mode, games outside the max_pages window are skipped, max_cycle_pages controls the per-cycle page limit (default 0 = unlimited), and the scanner advances through that window progressively — each cycle resumes where the last one left off, with auto-reset to page 1 when the max_pages budget is exhausted. In date mode the window and the pace come from max_date and max_cycle_months instead, both page keys are unused, and each cycle resumes from the per-year page cursors, restarting at the newest year once the floor has been swept. Note: browse scores are on a different scale and always exceed the configured thresholds — they cannot filter, so the threshold check happens on the detail page instead (phase 3).
  3. Detail verification, then queueing — Each surviving game's real Metacritic detail page is fetched and its 0–100 critic score and 0–10 user score are compared against the configured thresholds. Only games that pass are queued, and they enter pending_games already marked verified with their real scores, with the source-matching expiry window (download_sites.fitgirl.max_queue_days, default 60, or 0 for indefinite; review_sites.metacritic.platform_overrides.*.max_queue_days is used only when no FitGirl source is configured). Games that fail — or whose scores are not published yet — are not queued; they are re-checked on the next browse pass.
  4. Re-verification — Verified queue entries are re-checked against the detail page each cycle. A game whose scores drop is not re-queued on the old numbers: it stays in the queue only until a match attempt re-verifies it and rejects it, or until it expires, and it is never delivered on stale data. When scores first pass, the game's expiry is set to now + <source>.max_queue_days (the FitGirl entry's value, default 60, or 0 for indefinite; another source's own value is used when no FitGirl source is configured). Later passing re-checks do not extend it, so a game that keeps passing but never matches still leaves the queue when the window lapses.
  5. 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).
  6. Source indexing — Only when there are verified games in the queue. Each enabled source's index is fetched (FreeGOG A-Z list, FitGirl sitemap) and new titles are stored for matching. The FreeGOG A-Z index stores each game as it is fetched, so a cycle aborted by the acquisition watchdog resumes where it left off next cycle instead of starting over — the progress line reports the true number of pages left.
  7. Source matching — Verified games are matched against the source index by title, best match first. A DLC/expansion title can match its base game's repack when the repack page confirms the included content; otherwise a fuzzy candidate that merely shares title words (e.g. "2", "master", "collection") is accepted only when the repack page actually names the game, so an unrelated repack is no longer picked on URL order alone. The matched title gets its magnet link fetched (pre-stored for FreeGOG, on-demand for FitGirl).
  8. Delivery — Matched games are added to qBittorrent with a gamarr-* tag. A magnet qBittorrent already holds is never re-uploaded: one info line reports it. A torrent gamarr owns (one it tagged earlier, or one sitting in gamarr's category — that is the opt-in, even for a torrent you added) is adopted so post-processing still copies it, while a torrent outside gamarr's category is recorded as skipped, since there is nothing for gamarr to copy. If the torrent cannot be confirmed or adopted at all, the game stays pending and is retried next cycle. The result is recorded in the history database.
  9. Notifications — Optional Apprise notifications on download, failure, or error.

Architecture

The codebase is structured as a Metacritic-first pipeline:

metacritic.py  →  pipeline.py  →  sources/freegog.py  →  qbittorrent.py
                sources/fitgirl.py                 →  post_processor.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     post_processor.py
                                                        ↓
                                              Copy to library
                                              + SHA-256 verify
                                              + cleanup

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.enabled is true.

Development

git clone https://github.com/binhex/gamarr
cd gamarr
uv venv --quiet
uv sync --extra dev

Before committing, run the full lint suite:

pre-commit run --all-files

Running tests

uv run pytest

FAQ

Q: What download sites are supported?

A: FreeGOGPCGames and 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:

  1. Downloaded — scores pass thresholds AND a matching torrent is found on a source
  2. Expired — the game has been in the queue longer than max_queue_days

Games that pass score thresholds but have no source match stay in the queue — they get re-verified each cycle because a new FitGirl/FreeGOG repack could appear. If you find the queue growing indefinitely:

  • Increase max_pages to catch more recently released games
  • Or set max_date with sort_order: new to state the oldest month you want reviewed, so the cutoff is a date instead of a page count
  • Games that fail score thresholds are not queued at all — they are re-checked when the next browse pass reaches their pages again
  • The message 0 new + X from previous cycles means browsing found nothing new

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

PayPal donation

About

Metadata game downloader — browses Metacritic for newly released games that pass configured score thresholds, matches them against the FitGirl repacks sitemap, and sends qualifying games to qBittorrent.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages