github P-Adamiec/Free-Games-Claimer-Remaster v1.5

latest releases: v1.9, v1.9d, v1.8...
one month ago

Added

  • Release update check (NOTIFY_UPDATES) – nothing signaled when a newer release was published, so a container started once could run an outdated image indefinitely. The bot now queries GitHub's latest release at startup and at most once per day, notifying once per new version (state kept in data/update_check.json) with the release-notes link and the update command (docker compose pull && docker compose up -d). Silent when already current or running ahead of the latest release (e.g. the :dev image); a failed request never blocks the run. Enabled by default; disable with NOTIFY_UPDATES=false.
  • NOTIFY_ALREADY_CLAIMED – the run summary reports only status changes, so a store with nothing new to report disappeared from the notification entirely (e.g. AliExpress, on days the daily check-in was already collected). The new setting, off by default like NOTIFY_CLAIM_FAILS, restores already claimed / already in library / skipped entries to the summary for every store.
  • Unit test suite (tests/) – covers logic with no browser or network dependency: AliExpress coin/check-in payload parsing, Epic mobile offer detection, redirect host checks, the notification summary filter, and .env parsing. Two regressions in this category were caught by these tests during development. Runs via python -m pytest tests/ -q in under a second.
  • Epic mobile free-game claiming (src/stores/epic_mobile.py, #23) – Epic's weekly Android/iOS giveaway does not appear in the freeGamesPromotions API the bot polls, so it was never claimed. A new module queries Epic's mobile storefront listing (egs-platform-service…/discover/home?platform=android|ios) and passes the URLs to the existing Epic claimer, which claims them on the same store pages used for PC titles, requiring no additional login, browser profile, or captcha exposure. Only offers with an active free Claim entry are taken (the same catalog item also carries a full-price entry for after the promotion ends). Android and iOS are distinct SKUs and both are claimed by default; controlled by EG_MOBILE and EG_MOBILE_PLATFORMS. Since both platforms share a title, claims are labelled Foretales (Android) / Foretales (iOS) in logs, the summary notification, and the database.

Fixed

  • latest on the GitHub container registry served v1.1 for four releasesdocker-ghcr.yml only published on a release event, and releases here are created by github-actions[bot], which GitHub does not let trigger another workflow. v1.2 to v1.4 never reached GHCR, so the default docker-compose.yml kept pulling the June image. The tag push now triggers the build directly: v1.5 publishes v1.5, latest and main, v1.5d publishes v1.5d and dev. Three related fixes in the same workflow: a push to main no longer rewrites the dev tag, GHCR images are built for linux/arm64 as well (Raspberry Pi), and a per-commit concurrency group stops the same code being built twice.
  • DRYRUN=true did not prevent GamerPower from claiming giveawaysgamerpower.py had no dry-run check, unlike every other store: Fanatical, Itch.io, and IndieGala giveaways were claimed for real, and Alienware ones were silently marked "already notified" and never surfaced again. All four claim paths now stop before the irreversible action, report available (dry run), and leave the database untouched.
  • SCHEDULER_HOURS=24 or higher crashed the bot on startup (#22) – documented as fixed in 1.4, but main.py still built the interval job as a cron expression (*/24), which APScheduler rejects (ValueError: the step value is higher than the total range). Replaced with IntervalTrigger(hours=…), matching the behavior already documented in the README and .env.example: the timer counts N hours from startup for any positive value. Fixed daily times (SCHEDULER_FIXED_TIMES) are unaffected and continue to support exact minutes, e.g. 14:15,21:30.
  • Epic continued running after a failed loginrun() invoked the login step and proceeded regardless of the result, so a session that never established still visited pages and clicked into empty forms, producing failed entries. With NOTIFY_CLAIM_FAILS now off by default, this made a broken login indistinguishable from a normal run. _ensure_logged_in() now returns a boolean and run() aborts with a logged error and notification on failure. It returns false only when sign-in is confirmed to have failed (automation exhausted and the manual VNC window elapsed), never on an inconclusive check. Pre-release :dev images briefly had the opposite symptom (#29): the successful automated sign-in returned nothing instead of true, so 🔓 Signed in as: … was followed by Aborting Epic Games flow due to login failure, on the first run of a fresh profile only. A new tests/test_style.py catches that class of mistake.
  • Chrome processes leaked during GamerPower's Steam/Epic delegation – both paths closed the borrowed browser with browser.stop(), which terminates the parent Chrome process but leaves its children running, the same leak 1.4 fixed elsewhere for "Failed to connect to browser" (#19). Both now use close_browser(), which terminates the full process tree. The Epic delegation also aborts if login fails, instead of proceeding logged out.
  • Prime Gaming reported "Signed in as: unknown" while logged out (#13)1.4 widened prime.py's _is_signed_in() to accept Amazon's account-header dropdown (amazon-dropdown-header-interactable / amazon-dropdown-header-message) as proof of a session, but that header renders on Luna in the logged-out state as well. Login was skipped entirely, 🔓 Signed in as: unknown was logged, "no games available" was reported, and the bot then attempted to claim without a session. _is_signed_in() now checks only the one signal that exists solely for an authenticated account, the non-empty first name in user-dropdown-first-name-text, so a logged-out page correctly triggers the normal login flow (verified live: the logged-out page with the header dropdown returns not-signed-in; a page with a first name returns signed-in with the name).
  • AliExpress check-in notification omitted the day streak and tomorrow's reward – the notification reported only claimed 50 🪙 (985 total). aliexpress.py parsed a single response shape (the {name, value} list used by mtop.aliexpress.coin.execute, which is why the wallet balance worked), so the actual check-in responses, coin.channel.sign.list and coin.channel.sign.execute, which carry the streak and next-day reward, were captured but logged as fields={} and discarded. A new _flatten_payload() reads both response shapes (including JSON-encoded string values); _extract_checkin_info_from_api() extracts the streak and tomorrow values, preferring the response from the collect action itself. Page-scraping is now a fallback for whatever the API does not provide, with additional Polish-language patterns added since the page renders these numbers as animated digits. Every captured check-in response is also written to data/ae_coin_api.json for diagnostics.
  • SteamDB "Play For Free" games (e.g. ICARUS) were treated as free-to-keepsteam.py's _parse_steamdb_html() classified a game as keepable by searching each card's HTML for the text Free to Keep. The last card on the page also captures the page's trailing FAQ ("What is Free to Keep on Steam?") in its slice, so free-weekend/free-to-play titles matched the text, were "claimed", and reported as failures. The parser now matches SteamDB's actual green badge class cat-free-to-keep instead of matching free text (verified against a live page dump: keep games are retained; ICARUS and every cat-play-for-free card are excluded).

Changed

  • Log output overhauled: quieter by default, DEBUG=true limited to the bot, library internals moved to DEBUG_LIBS – a normal run logged detail that matters only while debugging: AliExpress alone emitted roughly 40 🔬 Coin API lines plus 🔎 Anti-bot diagnostics, 🚧 Blocked app schemes, and a running account of each login keystroke; Epic narrated every checkout click; GOG logged its login wait loop; GamerPower logged a processing and a routing line per giveaway; Prime logged internal session-recovery steps; Discord logged every message chunk sent. All of this moved to debug level, so the default log now reports only which store is running, sign-in status, games found, claim/skip outcomes, and warnings/errors. DEBUG=true itself was further diluted by library internals, every WebSocket frame exchanged with Chrome, HTTP handshakes with full response headers, SQLite calls, and SQLAlchemy printing each BEGIN/PRAGMA/COMMIT twice (its own echo handler ran alongside the app's), a single run could reach tens of thousands of lines. main.py now keeps those loggers at warning level unless the new DEBUG_LIBS=true is set, so DEBUG=true (on by default) reports only the bot's own actions: browser launch parameters, database inserts/hits, which SteamDB cards were skipped and why, Prime's offer-container diagnostics (previously dead code in a comment), GamerPower's resolved redirect target, AliExpress' check-in widget polling, Epic's mobile detection, and a credential-free settings dump at startup. Also filtered: a spurious Unknown child process pid …, will report returncode 255 warning, raised by Python's asyncio child watcher noticing a Chrome process already reaped by close_browser()'s cleanup (added in 1.4).
  • AliExpress check-in notification format – all values were packed into a single bracket, so claimed 50 🪙 (+12 tomorrow · 1,040 total) read as tomorrow bringing 12 additional coins rather than a total. Each value is now labelled individually: claimed 50 🪙, streak 5 days, tomorrow 12 🪙, balance 1,040 🪙; values the page does not report are omitted rather than estimated.
  • AliExpress streak and next-day reward now read from the check-in calendar – both values were guessed by matching field names in the captured responses, which reported the wrong number: a live capture showed the notification claiming "tomorrow 13 🪙" when the actual next-day reward was 50, the 13 having come from a day counter picked up by the page-text fallback. coin.channel.sign.list in fact returns a dailySignNodeList with one entry per day, keyed by calendarDayDistance (0 = today, 1 = tomorrow), each carrying its own prize and a sequenceNumber counting the streak day. _extract_checkin_calendar() reads those exact fields, so the notification now reports streak 15 days, tomorrow 50 🪙 for the same run. The streak counter is discarded if any visible past day was not signed (it is then not a streak), name-based matching is kept only as a fallback with stricter patterns, and the page-text fallback no longer accepts a bare number near the word "tomorrow" unless it is explicitly labelled as coins.
  • Shared title-matching helper; SteamDB dump path fixed – the loose title-comparison helper used for ownership detection existed as identical copies in steam.py and gamerpower.py; it now lives once in BaseClaimer. The SteamDB page dump moved from a hardcoded Linux path (/fgc/data/steamdb_dump.html, a silent no-op outside Docker) to the configured data directory, and is written only when DEBUG=true.
  • Dependency updates - bumped psutil to >=7.2.2, apprise to >=1.12.0, and tzdata to >=2026.3 (Dependabot #26, #27, #25), verified with a --no-cache container rebuild and a full claiming run.
  • GitHub Actions updates - upgraded actions/setup-python usage to v7 in the dependency-check workflow (Dependabot #24).
  • NOTIFY_CLAIM_FAILS now covers every store and defaults to false – previously guarded only two ad-hoc alerts (AliExpress, Epic), while unclaimable games (e.g. a free Steam DLC requiring an unowned base game, such as "The Mound: Omen of Cthulhu, Lost Explorers' Swords Pack") always appeared in the run summary regardless of store. The summary filter in main.py now hides every failed… entry from any store unless NOTIFY_CLAIM_FAILS=true is set.

Don't miss a new Free-Games-Claimer-Remaster release

NewReleases is sending notifications on new releases.