Skip to main content
Steam Deals AI is a Telegram bot that runs a daily curation pipeline. Raw deals from CheapShark pass through a deterministic rules filter (Layer 1), then through GPT-4o-mini (Layer 2), before being broadcast to subscribers or served on demand.

System diagram

┌─────────────────────────────────────────────────────────┐
│                     Telegram Client                     │
└───────────────────┬─────────────────────────────────────┘
                    │  commands / webhooks

┌─────────────────────────────────────────────────────────┐
│                     Bot Core (Telegraf)                 │
│  ┌──────────────────┐    ┌──────────────────────────┐   │
│  │  CronJob Scheduler│    │    Commands Handler       │   │
│  │  (node-cron)      │    │  /start  /deals  /help   │   │
│  └────────┬─────────┘    └───────────┬──────────────┘   │
│           │                          │                   │
│           ▼                          ▼                   │
│  ┌─────────────────────────────────────────────────┐    │
│  │           DealsService  (Orchestrator)           │    │
│  │                                                  │    │
│  │  fetchSteamDeals()  ──►  applyHardFilters()      │    │
│  │       (CheapShark)          (Layer 1 Rules)      │    │
│  │                                  │               │    │
│  │                                  ▼               │    │
│  │                       hashCandidates()           │    │
│  │                          │            │          │    │
│  │                    hash miss      hash hit       │    │
│  │                          │            │          │    │
│  │                          ▼            ▼          │    │
│  │                  filterDealsWithAI() snapshot    │    │
│  │                    (Layer 2 GPT)   reused        │    │
│  │                          │                       │    │
│  │                          ▼                       │    │
│  │                  buildFilteredDeals()            │    │
│  │                          │                       │    │
│  │                          ▼                       │    │
│  │                   saveSnapshot()                 │    │
│  └─────────────────────────────────────────────────┘    │
│           │                          │                   │
│           ▼                          ▼                   │
│  ┌─────────────────┐      ┌─────────────────────────┐   │
│  │ Notifier Service│      │     /deals response      │   │
│  │ markAsNotified()│      │   (read-only, no dedup)  │   │
│  └─────────────────┘      └─────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
│                                                         │
▼                                                         ▼
┌───────────────────┐              ┌──────────────────────┐
│   data/snapshot   │              │ data/notified_games  │
│      .json        │              │        .json         │
└───────────────────┘              └──────────────────────┘

Components

Bot Core (Telegraf)

The entry point. Initializes the Telegraf instance, registers command handlers, starts the cron job, and calls clearStaleSnapshot() at startup to prevent serving deals from a previous day.

CronJob Scheduler

Runs on a configurable cron expression (default: 0 9 * * * — 9 AM Colombia time). Calls fetchAndMarkDeals(), which runs the full pipeline and records notified game IDs to prevent re-broadcasting the same game within DEDUP_DAYS days.

Commands Handler

Handles /start, /deals, and /help. The /deals command calls fetchDeals(), which is read-only — it never writes to the deduplication log. A per-chat-ID rate limit (default 45 s) prevents spam.

Notifier Service

Broadcasts FilteredDeal[] to all registered Telegram chat IDs. Introduces a configurable delay between sends to respect Telegram’s rate limits.

DealsService (Orchestrator)

The central coordinator (dealsService.ts). Owns the in-memory pipeline lock that prevents concurrent executions from racing on the JSON files. Exposes two public functions:
FunctionUsed bySide effects
fetchDeals()/deals commandNone — read-only
fetchAndMarkDeals()Cron broadcastWrites to notified_games.json

Layer 1 — Rules Filter

Pure, synchronous function. No I/O. Applies hard thresholds for discount, price, and quality scores, then removes already-notified games. See Filter pipeline.

Layer 2 — AI Filter (GPT-4o-mini)

Receives candidates that passed Layer 1. GPT selects up to 10 games and returns one short reason per selection. All price and URL data comes from CheapShark, not from the model. See AI curation.

Cache System

Two JSON files under data/:
  • snapshot.json — the last successful FilteredDeal[] plus a candidate hash and timestamp.
  • notified_games.json — deduplication log with steamAppID + notifiedAt per game.
See Caching and deduplication.

Data flows

1

Scheduler fires

The cron job triggers fetchAndMarkDeals() at the configured schedule (default 9 AM Bogotá).
2

Snapshot check

If a fresh snapshot already exists for today (same calendar day in America/Bogota), it is returned immediately without re-running the pipeline.
3

Fetch raw deals

fetchSteamDeals() calls CheapShark with maxPrice and pageSize filters. The Metacritic threshold is intentionally not passed upstream — see Filter pipeline.
4

Layer 1: rules filter

applyHardFilters() removes deals that are under-discounted, over-priced, or already notified.
5

Hash check

hashCandidates() produces a 16-character SHA-256 digest of the candidate set. If it matches the stored snapshot hash, GPT is skipped and the cached selection is reused.
6

Layer 2: AI curation

filterDealsWithAI() sends candidates to GPT-4o-mini. Only IDs, titles, and rating scores are sent — not prices or URLs.
7

Reconstruct and persist

buildFilteredDeals() assembles FilteredDeal[] from the original CheapShark data using GPT’s selected IDs. The snapshot is saved to disk.
8

Format and broadcast

getExchangeRate() fetches the current USD → COP rate (12-hour in-memory cache, falls back to 4,000 COP/USD). formatDealsMessage() renders each deal in HTML with COP and USD prices. The Notifier Service broadcasts the message to all subscribers. markAsNotified() records the game IDs to prevent future re-broadcasts.

When is GPT called?

ScenarioGPT called?
First run of the day, no snapshotYes
Candidates changed since last snapshotYes
/deals requested, fresh snapshot existsNo — snapshot served
Cron fires, same candidates as last snapshotNo — hash matched
GPT call fails, fresh snapshot existsNo — snapshot used as fallback
GPT call fails, no fresh snapshotError returned (ai_error)

Filter pipeline

How Layer 1 deterministic rules work, including the OR quality logic and rejection criteria.

AI curation

How GPT-4o-mini selects from candidates and what it contributes to the final deal.

Caching and deduplication

Snapshot freshness, candidate hash caching, and the deduplication log.

Build docs developers (and LLMs) love