Game Scraper
Game Scraper monitors Reddit communities where free game keys are shared in comments, extracts them across five storefront formats, uses an LLM to crack obfuscated hints, and pushes finished keys to your phone via Telegram.
Executive Summary & Problem Statement
Free-game giveaways land in Reddit comments — usually hugely popular, so by
the time you see them, the keys are gone. Worse, many communities encode keys
with placeholder symbols (??) and require solving a trivia hint to complete
them, which is exactly the kind of small reasoning task humans are slow at.
This project automates the entire race:
- Stream first — tail the subreddit's live comment feed rather than polling, so a key is caught within seconds of being posted.
- Parse every format — Steam, Epic, GOG, Ubisoft, and EA keys have different layouts; each needs its own recognition path.
- Solve the hint with an LLM — when a key contains placeholder symbols, a constrained Gemini call turns the hint into the missing characters.
- Deliver instantly — completed keys are sent to Telegram as one-tap register links, so any device in your pocket can redeem them.
Architecture Overview
A straight-line event pipeline with a single LLM call in the middle:
Reddit (PRAW stream) ──► comment ──► post-title married to key
│
key format detection (Steam / Epic / GOG / Uplay / EA)
│
placeholders? ──► Gemini 2.0 Flash solves hint
│
Telegram sendMessage ──► steam://registerkey link
| Component | Role |
|---|---|
reddit_script.py |
r/FreeGamesOnSteam stream + Steam key focus |
free_game_findings.py |
r/FreeGameFindings multi-platform patterns |
main.py |
Workflow menu, mode selection |
.env |
Reddit, Gemini, and Telegram credentials |
Reddit Comment Streaming
PRAW's subreddit.stream.comments(skip_existing=True) yields a live feed of new
comments. Every comment is bound to its parent submission, so the giveaway
context (title, link-flair, thread topic) travels with the key:
reddit = praw.Reddit(
client_id=os.getenv("CLIENT_ID"),
client_secret=os.getenv("CLIENT_SECRET"),
user_agent=os.getenv("USER_AGENT")
)
subreddit = reddit.subreddit("FreeGamesOnSteam")
for comment in subreddit.stream.comments(skip_existing=True):
post = comment.submission
if "+1" in (post.link_flair_text or "").lower() and comment.id not in seen_comments:
process_comment(post.title, comment.body)
Two details prevent wasted work:
- Flair filtering — free-key posts are flagged with a
+1link flair in that community; skipping the rest prunes paid-promo and discussion threads. - Deduplication — an in-memory
seen_commentsset prevents re-processing when the stream rewinds or restarts.
Two run modes are supported: a one-shot scan of recent posts (useful for a catch-up run) and live mode (long-running, intended to stay resident and push instantly).
Multi-Format Key Detection
Keys come in five storefront layouts, each with different group counts and
character widths. The Steam matcher anchors the canonical 5-5-5 triple-group
layout while tolerating the obfuscation character set communities use (?, *,
%, !, @, #, $, &, ., ,, -, _):
key_pattern = re.compile(
r'[A-Z0-9\?\!\*\%\@\#\$\&\.\,\-\_]{5}-'
r'[A-Z0-9\?\!\*\%\@\#\$\&\.\,\-\_]{5}-'
r'[A-Z0-9\?\!\*\%\@\#\$\&\.\,\-\_]{5}',
re.IGNORECASE
)
| Storefront | Format |
|---|---|
| Steam | XXXXX-XXXXX-XXXXX (5-5-5) |
| Epic | 5×5 group layout |
| GOG | FCS... prefix layout |
| Ubisoft / Uplay | XXXX-XXXX-XXXX-XXXX (4-4-4-4) |
| EA | XXXX-XXXX-XXXX-XXXX-XXXX (4-4-4-4-4) |
free_game_findings.py adds those platform-aware patterns on top. The text
immediately following a matched key is captured as the hint — the same line
the comment's author used to encode the answer.
Solving Hints with an LLM
A key like WCI??-A2BX7-X6R8I ships with a human-authored hint (e.g.
"HITMAN AGENT = ??"). The project delegates this reasoning step to Gemini 2.0
Flash with a strict single-answer contract:
def gemini_response(key, hint):
response = gemini.models.generate_content(
model="gemini-2.0-flash",
config=types.GenerateContentConfig(
system_instruction="I am giving you a key and a hint. The key has a "
"special symbol which can be found by solving the hint. Replace the "
"special symbol with the correct solution from the hint. For example "
"Key: WCI??-A2BX7-X6R8I, Hint: HITMAN AGENT = ??. The answer is: "
"WCI47-A2BX7-X6R8I. You will output ONLY the answer and nothing else."
),
contents=f"Key: {key}, Hint: {hint}. Output only the completed key"
)
return response.text.strip()
Design notes:
- Few-shot exemplar — the system prompt embeds a full worked example
(including the
HITMAN AGENT=47joke answer), which anchors expected output shape without any fine-tuning. - Output discipline — "output ONLY the answer" +
.strip()keeps the pipeline deterministic; a contaminated LLM response is still a valid key candidate for manual review. - Fail-soft — if the model returns nothing useful, the original key and hint are printed to the terminal so a human can finish it.
Telegram Delivery
Completed keys are delivered as a direct redemption link rather than raw text, so tapping the notification on a phone goes straight to the register page:
payload = {
"chat_id": telegram_chat_id,
"text": f"https://store.steampowered.com/account/registerkey?key={text}",
"parse_mode": "Markdown",
}
The bot is configured entirely via .env (CLIENT_ID, CLIENT_SECRET,
USER_AGENT, GEMINI_API, TELEGRAM_API, TELEGRAM_CHAT_ID), which keeps
credentials out of source control and makes deployment a copy-plus-env-file
operation.
Design Decisions & Tradeoffs
- Streaming over polling — a live comment stream trades a persistent connection for latency measured in seconds; the biggest win of the project.
- LLM in the loop — hint-solving is exactly the awkward, varied, low-volume reasoning task LLMs handle well without custom training data. A regex-based solver would miss the variety; a trained model would be overkill.
- Comment scraping over storefront APIs — giveaways are announced only in community comments, so Reddit is the data source; the platform abstraction is confined to PRAW.
- Push over polling UI — Telegram means the scraper never needs a server, a web UI, or even to be looked at while running.
Engineering Discipline
- Environment-driven configuration — zero secrets in code;
.envis the single source of credential truth. - Rate-limit awareness — a documented
time.sleep(1)throttle guards against Reddit/Telegram rate limits. - Graceful degradation — missing env vars, failed requests, and empty LLM responses all degrade to a readable terminal message instead of a crash.
- Live-mode hygiene — dedup sets and
skip_existingkeep long-running sessions stable across restarts.
Source availability: the repository is currently private; complete source is available on request.
Technical Competencies Demonstrated
- Streaming API integration — PRAW comment streaming with deduplication and flair-based filtering.
- Multi-platform pattern matching — distinct key formats for five stores with an obfuscation-tolerant character set.
- LLM-in-the-loop automation — constrained prompt engineering (system instruction + few-shot example + strict output format) feeding a deterministic pipeline.
- Push delivery — Telegram Bot API integration with one-tap redemption links.
- Security hygiene — secrets isolated in
.envfor rotation and portability.