cheapo — CLI in Go (Cobra) to fetch and observe current free game offers. Later planned: exposing the tools as an MCP server (see README.md, section "Roadmap / MCP").
free-games-claimer — https://github.com/DoSpamu/free-games-claimer (fork of https://github.com/vogler/free-games-claimer). How it gets the free-games info:
run.js, Discord notify,
logger) on the upstream Docker image. The actual logic lives upstream in
vogler/free-games-claimer (epic-games.js, prime-gaming.js, gog.js,
steam-games.js, aliexpress.js).a:has(span:text-is("Free Now")), follows the links and claims them in
the browser (login per cookie session, 2FA via OTP key, captcha handling).epic-games.js, ~line 151): the page is built
from the JSON of the public backend:
https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions
(filter data.Catalog.searchStore.elements on active promotions.promotionalOffers
with 100 % discount; slug preferably from catalogNs.mappings[0].pageSlug, since
urlSlug is sometimes wrong).free-games-notifier — https://github.com/jaixmario/free-games-notifier
(Python, GitHub-Actions cron every 6 h; notifications via e-mail, Telegram,
Discord, WhatsApp; state in free.json/free-steam.json).
How it gets the info:
epic.py): same public endpoint
store-site-backend-static.ak.epicgames.com/freeGamesPromotions?locale=en-US
(HTTP GET, no login) — confirms our Go approach. Filter:
discountSetting.discountPercentage == 0; distinguishes current
(promotionalOffers) and upcoming (upcomingPromotionalOffers — preview
window for future free games). Change detection via signature
(CURRENT:t1,t2|UPCOMING:...) + detail comparison per (title, link) keys;
only notify on new offers, removals are silently written to state.steam.py), two sources:
https://store.steampowered.com/search/?maxprice=free&specials=1
(BeautifulSoup; .search_result_row, price "Free"/100 %).https://store.steampowered.com/api/featuredcategories/ — items with
"Free Weekend"/"play for free" in name/body; title fallback via
store.steampowered.com/api/appdetails?appids=<id>.go version).gofmt -w . (mandatory before every commit; CI checks gofmt -l . = empty).gopls (install: go install golang.org/x/tools/gopls@latest).
Use in editors as LSP; additionally go vet ./... before every commit.bash
go build ./... && go vet ./... && go test ./...
gofmt -l . # must be empty
## Conventions
- Module path: gogs.netdome.biz/paul/cheapo.
- CLI framework: Cobra (github.com/spf13/cobra), commands in cmd/cheapo/main.go.
- Internal code under internal/ (epic: store client, watch: polling/eventing).
- Wrap errors with context: fmt.Errorf("epic: ...: %w", err).
- User-facing strings in German (CLI output, help texts, error messages),
code comments in English.
- Provider pattern: every store lives in internal/<store>/ and implements
game.Provider (Name() + GetFreeGames(ctx) ([]game.Game, error)); the
shared type is game.Game (internal/game). New stores (e.g. GOG)
follow this pattern and are registered in newProviders() (cmd/cheapo/main.go).
- Chatto client: internal/chatto is a hand-built Connect-JSON client
(Chatto's generated code is internal/ and not importable). Credentials ONLY via
CHATTO_BASE_URL/CHATTO_TOKEN env from the operator — the tool never
fetches credentials itself, and the agent does not use its own plugin
credentials for it.
- New store platforms (Prime, GOG, …) follow the provider pattern (above).