# Comprehensive Architectural & Security Review: Python Remaster vs Original JS This document serves as a 100% transparent, file-by-file technical breakdown of the `fgc-remaster` (Python) project compared to the original `free-games-claimer` (Node.js). If you are inspecting this codebase for security reasons, migrating your deployment, or simply wanting to understand how the internal logic was rewritten to defeat modern bot protections, this document covers every addition, modification, and omission without any marketing fluff. --- ## 1. Executive Summary & Security Posture The original project relied on **Node.js** and **Playwright/Patchright**. While effective initially, advanced bot-protection services like Cloudflare Turnstile, hCaptcha, and Epic's in-house security have become exceptionally good at fingerprinting Playwright's Chrome DevTools Protocol (CDP) signatures. The **Python Remaster** was built from the ground up to solve two critical flaws: 1. **Automation Detection**: The core was migrated to **Python 3** using [`nodriver`](https://github.com/ultrafunkamsterdam/nodriver), an ultra-stealthy CDP-based asyncio library that commands official Chrome binaries without leaving typical automation fingerprints. 2. **Data Corruption**: Volatile, highly concurrent `.json` file-writes were replaced with an ACID-compliant asynchronous **SQLite** database via `SQLAlchemy`. On fingerprints the project deliberately does **less** than it used to. A hand-written desktop spoof (fake `Win32` platform, fake NVIDIA WebGL renderer, fake plugin list) shipped in an early release and started summoning captchas across every store, because those values did not match the real Linux container running it. Since `1.4` the base stealth script is off by default (`BaseClaimer.inject_base_stealth = False`) and stores run on the fingerprint nodriver's patched Chrome produces by itself. Exactly one module overrides it: AliExpress injects one coherent Android device (user-agent, client hints, screen, navigator, WebGL) generated by `browserforge`, because its check-in only exists on the mobile site. **There are no obfuscated binaries, no hidden tracking telemetry, and no unauthorized outbound requests.** Every request either fetches a target store (Epic, Fab, Steam and SteamDB, GOG, Amazon, Ubisoft, Unity Asset Store, AliExpress, GamerPower), asks `api.github.com` for the latest release number so the bot can tell you your image is outdated (`NOTIFY_UPDATES=false` stops even that), or dispatches a JSON payload to exactly the Discord/Apprise webhook you configured. --- ## 2. What was DROPPED? (File Omissions) The following scripts from the original `free-games-claimer-dev` were deemed out-of-scope for a core gaming claimer or misleading, and were **deleted**: - 🔄 `aliexpress.js`: Removed as a coin scraper, then reimagined and re-added as `aliexpress.py` per user request. It performs the daily check-in on the mobile coin page. As of August 2026 AliExpress serves that page to its own app only, so the module reports this once and stops instead of retrying, see [issue #33](https://github.com/P-Adamiec/Free-Games-Claimer-Remaster/issues/33). - 🔄 `unrealengine.js`: Removed at the time, and since replaced by something better. The Unreal Engine marketplace became **Fab**, and `src/stores/epic_fab.py` now claims its weekly limited-time free assets; `src/stores/unity.py` does the same for the Unity Asset Store. Both are asset stores rather than game stores, so both stay strictly opt-in about what they accept: only an offer the storefront itself flags as free is ever claimed. - ❌ `steam-games.js` (Original): The original JS project contained a file named `steam-games.js`, **but it never claimed games.** It only scraped your public Steam profile to read playtime hours and achievements. It was deleted and completely replaced with a real auto-claimer. - ❌ `package.json` / `package-lock.json` / `eslint.config.js`: Entire Node.js ecosystem files removed in favor of Python's `requirements.txt`. --- ## 3. What was ADDED & REWRITTEN? (File Additions) ### A. The Core Engine (`src/core/`) Instead of duplicating browser launch logic in every single store file (as the JS version did), the Remaster uses a strictly Object-Oriented approach. - 🟢 `claimer.py`: Contains the `BaseClaimer` class every store inherits from. It standardizes the `nodriver` launch flags (including `--restore-last-session`), maps each store to its own persistent profile under `data/browser/`, and lets one store ride another's session where that saves a login (Fab signs in as Epic). Teardown kills the whole Chrome process tree with `psutil`, because stopping only the parent left orphans behind that broke the next run. It also owns the shared human hand-over: challenge detection, the "open noVNC and finish this" notification, and the wait that follows. - 🟢 `database.py`: A new `SQLAlchemy` async ORM layout managing an `fgc.db` SQLite database. Every claim is keyed on a `(store, user, game_id)` tuple, so a game already recorded is never claimed twice, whatever the display title happens to be that week. - 🟢 `config.py`: Replaces the old `config.js`. It strictly parses your `.env` file into a typed Python `Config` class. Store modules never read the environment directly, which is why every setting is documented in one table. - 🟢 `notifier.py`: A native web-request wrapper utilizing `httpx` and `apprise` to dispatch Discord messages, both in parallel. It reads granular `.env` triggers (`NOTIFY_SUMMARY`, `NOTIFY_ERRORS`, `NOTIFY_CLAIM_FAILS`, `NOTIFY_SKIP_STORES`) to prevent the bot from spamming your server. - 🟢 `url_security.py`: One hostname check used by every redirect in the project. A giveaway link is trusted only when the resolved host really is the store, so a lookalike such as `gog.com.evil.tld` can never pass as GOG. Substring matching on URLs is banned in this codebase precisely because it accepted exactly that. - 🟢 `selection.py`: Publishes which stores the current run covers, so a giveaway found by GamerPower is only claimed when this run includes the store it belongs to. `STORES=steam` claims the Steam giveaways it finds and leaves the Epic ones alone. - 🟢 `run_state.py`: Remembers what one run learned about the person at the keyboard. A store whose VNC prompt went unanswered is not asked again in the same run, because waiting another full timeout changes nothing and re-opening its sign-in page over and over is what lowers a session's standing. - 🟢 `updates.py`: Asks GitHub once a day whether a newer release exists and notifies you once per version, with the release notes link. It sends nothing about you, and a failed request never blocks a run. ### B. The Store Modules (`src/stores/`) - 🟢 `epic.py`: Rewritten to force strict `headful` mode using `nodriver`. This flawlessly mimics a real human user opening an Epic Games tab, almost entirely bypassing the aggressive hCaptcha checkpoints that halted the Node.js version. - 🟢 `epic_mobile.py` (**New**): Epic's weekly free Android/iOS game never appears in the promotions API the bot polls, so it was silently missed. This module reads Epic's mobile storefront listing and hands the URLs to the normal Epic claimer, which claims them on the same product pages. Detection only: no extra login, no extra browser, and a network failure returns an empty list rather than breaking the Epic run. - 🟢 `epic_fab.py` (**New**): Claims Fab's limited-time free assets. Listings keep their original price and Fab's search API silently ignores unknown filters, so only the storefront's own `isLimitedFreeContent` flag authorises a claim, and ownership is read from the library API rather than from page text. Signs in on Epic's profile, so there is no second login. - 🟢 `unity.py` (**New**, opt-in): Claims the weekly free Publisher of the Week asset on the Unity Asset Store. The asset is free only with a coupon that changes every week, so the code is read off the sale page on each run and never cached. Unity's checkout starts on "exempt from consumption tax: yes", which silently blocks the coupon; the bot answers No when the account carries no tax number and refuses to confirm any order whose total is not exactly zero. - 🟢 `ubisoft.py` (**New**): Claims Ubisoft giveaways. Detection is a pure function over the JSON feed embedded in `ubisoft.com/games/free`, with no browser involved. Giveaways share their type with ordinary trials, so the filter also demands a real promotion window and the claim page has to identify itself before anything is clicked. - 🟢 `prime.py`: Fixed critical DOM selector bugs from the JS version. Amazon recently injected "Sign in with Passkey" prompts which trapped the old script in an infinite loop. The Python port explicitly targets the standard form. It also differentiates between natively claimed Amazon games and external keys (e.g. extracting GOG activation codes), and keeps writing `data/prime-gaming.json` next to the database for anyone who parsed that file in the JS days. - 🟢 `gog.py`: Streamlined the giveaway banner parsing logic. Most importantly, it binds natively to Chromium's `--restore-last-session` boot flag. This prevents Docker restarts from wiping out your ephemeral `gog-al` session cookies (which previously caused random logouts). It also redeems the GOG keys Prime Gaming hands out. - 🟢 `steam.py` (**Completely New Feature**): Replacing the useless original script, this entirely new module actively searches for free-to-keep titles by scraping `SteamDB` through the browser (which bypasses Cloudflare), then logs into Steam to finalize the 100% discount purchases. Only SteamDB's own green "Free to Keep" badge counts; matching that phrase as page text used to drag free-weekend titles in. - 🟢 `gamerpower.py`: Reads the GamerPower API once at the start of a run for giveaways the storefronts themselves do not advertise, then hands each one to the store it belongs to, which claims it in the browser session it had opened anyway. Routing goes through `url_security.py`, in-game DLC is skipped unless you ask for it (`GP_CLAIM_DLC`), and the sites with no module of their own (Fanatical, Itch.io, IndieGala, Alienware Arena) are chosen in `STORES` like any other store, because each needs an account there. - 🟢 `aliexpress.py`: The daily check-in module. It carries one coherent `browserforge` Android fingerprint, reads the coin balance by intercepting the store's own API response rather than the animated DOM, and treats a low-trust reward (1 coin instead of the full amount) as a reason to back off rather than to collect. Check-in state is read from the check-in API, whose field names do not change with the site language. ### C. Docker Infrastructure - 🟢 `Dockerfile`: Debian `bookworm-slim`, with `google-chrome-stable` on x86-64 and `chromium` on ARM (Raspberry Pi). The stack still includes `TurboVNC`, `VirtualGL` and `noVNC` on `localhost:7080` so you can watch the browser or solve a captcha exactly as before. Two Chrome-level details matter for AliExpress: `xdg-open` is neutralised, and a managed Chrome policy pre-answers the "Open xdg-open?" dialog for app schemes, which otherwise froze the session behind a system prompt nobody was there to click. - 🟢 `docker-entrypoint.sh`: Heavily upgraded the container startup protocol. If you brutally stop the container, Chromium leaves behind `SingletonLock` and TurboVNC leaves `/tmp/.X1-lock`. On next boot, these would block the display and Chromium from starting. The shell script now elegantly scrubs orphaned locks before spinning up the X-server. Useless legacy `xauth` footprint diagnostics were also silenced. ### D. Tests (`tests/`) The original had no test suite. This one covers the logic that can be checked without a browser, an account or a network: payload parsing, offer filtering, hostname checks, store selection, the notification summary filter, `.env` parsing, and whether the documentation still matches the code. It runs in about a second with `python -m pytest tests/ -q`, and it exists because several regressions in exactly these areas shipped before it did. Browser and DOM behaviour is not mocked here; it is verified by launching a real browser and reading back what it produced. --- ## 4. Verification Check If you wish to verify the integrity of the project: 1. Examine `requirements.txt`, you will find standard, highly-vetted open-source libraries (`nodriver`, `sqlalchemy`, `aiosqlite`, `httpx`, `apprise`, `apscheduler`, `psutil`, `browserforge`, `pyotp`, `tenacity`, `rich`, `python-dotenv`). 2. Search the codebase for execution tools (`os.system`, `eval`, `exec`), you will find them absent. There is exactly one use of `subprocess`, in `src/core/claimer.py`: when Chrome refuses to start, the bot runs that same Chrome binary twice, once with `--version` and once headless on `about:blank`, and puts what it printed into the log, because otherwise a failed start says nothing at all. Nothing else is executed, process cleanup goes through `psutil`, and the only thing evaluated is JavaScript sent to the browser you can watch over noVNC. 3. Review outbound requests (search for `httpx.AsyncClient` and `page.get`), you will see the target stores, `steamdb.info`, `gamerpower.com`, `api.github.com` for the release check, and exactly the webhook domain you provide in your `.env`. 4. Run `python -m pytest tests/ -q`. It touches no accounts and no network, so it is safe to run before you trust the thing with your credentials.