Free Games Tracker

A to Z Guide

How this project works, from data collection to GitHub Pages.

This page documents the project architecture, scripts, workflows, data files, testing helpers, and deployment path so anyone opening the repository can understand the full system quickly.

Overview

What this project does

The project tracks free game promotions from Epic Games and Steam, stores structured JSON snapshots, sends notifications through email, Telegram, Discord, and WhatsApp when new offers appear, updates the README automatically, and exposes the latest offers through this GitHub Pages site.

System Flow

End-to-end flow

1. Fetch

`epic.PY` calls Epic's promotion endpoint and `steam.py` gathers Steam deals from web/API sources.

2. Normalize

Each script converts the results into predictable JSON records with title, link, image, type, and timing fields.

3. Detect changes

A signature is built from the offer list, then the scripts check whether any fetched offer is new compared with the saved JSON state.

4. Persist

The latest state is saved into `free.json` and `free-steam.json`; expired or removed offers update state silently without sending notifications.

5. Notify

The scripts deliver channel-specific messages for email, Telegram, Discord, and WhatsApp, including image-based WhatsApp updates when artwork is available.

6. Publish

`generate_readme.py` refreshes the README while GitHub Pages serves the JSON-backed website, `start.py` runs normal local checks, and `test_runner.py` runs a clean-state test.

Repository

Main files

`epic.PY`Epic fetcher, multi-channel notifier, JSON state writer.
`steam.py`Steam fetcher, weekend title resolver, multi-channel notifier.
`start.py`Runs `epic.PY` and `steam.py` one after another.
`test_runner.py`Runs `delete_state_json.py`, then `epic.PY`, then `steam.py` for clean-state testing.
`delete_state_json.py`Deletes only `free.json` and `free-steam.json` for test resets.
`generate_readme.py`README auto-section generator.
`whatsapp.py`Shared WhatsApp Cloud API helper for text and image messages.
`config.json`Notification toggles and secrets-file settings.
`secrets.json`Optional local JSON secrets source.
`free.json`Epic current and upcoming free game snapshot.
`free-steam.json`Steam free-offer snapshot.
`index.html`GitHub Pages homepage for the games dashboard.
`SITE/docs.html`Full documentation page.
`.github/workflows/main.yml`Scheduled and manual production notifier workflow.
`.github/workflows/tester.yml`Manual test workflow that clears JSON state before running notifiers.

Data

JSON structure

The site expects these repository files:

free.json
{
  "signature": "...",
  "updated_at": "...",
  "current_games": [{ "title": "", "link": "", "image": "", "start": "", "end": "" }],
  "upcoming_games": [{ "title": "", "link": "", "image": "", "start": "", "end": "" }]
}

free-steam.json
{
  "signature": "...",
  "updated_at": "...",
  "games": [{ "type": "", "title": "", "link": "", "image": "", "time": "" }]
}

If an offer expires or disappears, the notifier saves the new JSON state without sending alerts. Alerts are sent only when at least one fetched offer is new compared with the saved snapshot.

Delivery

Notification channels

The project can notify through email, Telegram, Discord, and WhatsApp. Each channel can be enabled or disabled independently inside config.json.

{
  "secrets": {
    "use_hardcoded_secrets": true,
    "secrets_file": "secrets.json"
  },
  "notifications": {
    "email": false,
    "telegram": false,
    "discord": false,
    "whatsapp": true
  }
}

Email, Telegram, Discord, and WhatsApp all support multiple targets separated by commas. Email supports up to 30 recipients per run, for example first@example.com,second@example.com. Telegram and Discord support comma-separated chat or channel IDs like -1003918180986,1845799450 and 123456789012345678,987654321098765432. WhatsApp recipients use international numbers such as 919989148967,14155552671.

{
  "email": {
    "email": "you@gmail.com",
    "password": "your-app-password",
    "to_email": "first@example.com,second@example.com,third@example.com"
  },
  "telegram": {
    "bot_token": "123456:telegram-bot-token",
    "chat_id": "-1003918180986,1845799450"
  },
  "discord": {
    "bot_token": "YOUR_DISCORD_BOT_TOKEN",
    "channel_id": "123456789012345678,987654321098765432"
  },
  "whatsapp": {
    "access_token": "EAAG...",
    "phone_number_id": "9123242999999",
    "to": "919999999999,919999999999",
    "api_version": "v25.0"
  }
}

WhatsApp currently sends one summary text followed by one message per game. If a game includes artwork, the notifier sends an image message with a caption and store link. If no image is available, it falls back to plain text with a tappable URL preview.

GitHub Actions may block or interfere with WhatsApp API requests in some environments, so WhatsApp notifications are best run locally or from a VPS.

Automation

GitHub Actions

The main workflow runs every 6 hours and can also be triggered manually. It installs dependencies, runs both fetcher scripts, regenerates the README, and commits updated JSON snapshots.

The tester workflow is manual only. It runs delete_state_json.py first, then runs the Epic and Steam notifiers. Use it when you intentionally want to test behavior from a clean JSON state.

GitHub Actions remains useful for scraping, state updates, and publishing docs, but WhatsApp delivery is more reliable when run locally or on a VPS.

Deployment

GitHub Pages setup

The public entry lives at the repository root in index.html, while shared site assets and the documentation page live in the SITE/ folder. This keeps GitHub Pages happy while still grouping the website files cleanly.

Setup

How to run locally

pip install requests beautifulsoup4

$env:WHATSAPP_ACCESS_TOKEN="EAAG..."
$env:WHATSAPP_PHONE_NUMBER_ID="1126873913833051"
$env:WHATSAPP_TO="919999999999"
$env:WHATSAPP_API_VERSION="v25.0"

python start.py
python test_runner.py
python generate_readme.py

For local runs, you can either set environment variables like the example above or keep use_hardcoded_secrets enabled and place the same values in secrets.json. Use start.py for a normal run and test_runner.py only when you want to delete saved JSON state first.

Extend

How to grow the project

You can add more storefronts by following the same pattern: fetch data, normalize it into JSON, generate a signature, save a state file, and teach the site how to render the new data source.