11# hoyo-codes — agent guide
22
3- A small Go service that watches for new ** HoYoverse redemption codes** (Genshin
4- Impact, Honkai: Star Rail, Honkai Impact 3rd) and posts them to ** Discord
5- webhooks** on a cron schedule. Docker-first, no database, no framework.
3+ A small Go service that watches for new gacha ** redemption codes** (Genshin
4+ Impact, Honkai: Star Rail, Zenless Zone Zero, Honkai Impact 3rd, and Wuthering
5+ Waves) and posts them to ** Discord webhooks** on a cron schedule. Docker-first,
6+ no database, no framework.
67
7- Codes are pulled from two community APIs and merged/de-duplicated, so a hiccup on
8- one source doesn't cause a miss:
8+ Codes are pulled from up to three APIs and merged/de-duplicated, so a hiccup on
9+ one source doesn't cause a miss. A source is skipped for any game it doesn't
10+ serve (empty slug), and it only errors when * every* attempted source fails:
911
10- - [ ennead.cc] ( https://api.ennead.cc/mihoyo ) — ` /<game>/codes `
11- - [ torikushiii/hoyoverse-api] ( https://github.com/torikushiii/hoyoverse-api ) — ` hoyo-codes.seria.moe `
12+ - [ ennead.cc] ( https://api.ennead.cc/mihoyo ) — ` /<game>/codes ` (HoYoverse only)
13+ - [ torikushiii/hoyoverse-api] ( https://github.com/torikushiii/hoyoverse-api ) — ` hoyo-codes.seria.moe ` (HoYoverse only)
14+ - ** OpenGachaCodes** — self-hosted read-only API (` opengachaBaseUrl ` config).
15+ Supplements the HoYo games it also serves and is the ** sole** source for
16+ non-HoYo games like Wuthering Waves. Disabled when ` opengachaBaseUrl ` is unset.
1217
1318## Layout
1419
@@ -123,8 +128,9 @@ Advanced → **Developer Mode**, then right-click the role → **Copy ID**).
123128
124129## How a check works (` runCheck`)
125130
126- 1. For each watched game, `fetchCodes` queries **both** APIs and merges by
127- upper-cased code. One source failing is tolerated; both failing skips the game.
131+ 1. For each watched game, `fetchCodes` queries **every source that serves it**
132+ (empty-slug sources are skipped) and merges by upper-cased code. One source
133+ failing is tolerated; only *all* attempted sources failing skips the game.
1281342. New codes = fetched codes not in `data/sent.json`. They're marked sent
129135 immediately.
1301363. First time a game is seen (no state), codes are **marked sent silently** when
@@ -169,7 +175,8 @@ are a fatal error, catching typos). `loadConfig` resolves the file into `Config`
169175- Each game is self-contained (`GameNotify{Webhook, Message, Username, AvatarURL}`;
170176 ` Username` defaults to `defaultUsername`, `AvatarURL` is optional). `loadConfig`
171177 collects validation problems and returns them together : fatal if no games are
172- configured, or any listed game is missing `webhook` or `message`.
178+ configured, any listed game is missing `webhook` or `message`, or a listed game
179+ has no reachable source (OpenGachaCodes-only with no `opengachaBaseUrl`).
173180
174181When adding a config knob : add it to `fileConf`, resolve/validate it in
175182` loadConfig` , and document it in `config.example.yaml` and this file.
@@ -181,11 +188,22 @@ When adding a config knob: add it to `fileConf`, resolve/validate it in
181188- **torikushiii** — `https://hoyo-codes.seria.moe/codes?game=<slug>`, slugs
182189 `genshin`, `hkrpg`, `nap`, `honkai3rd`. Shape : ` {"codes":[{"code","rewards":"A*60;B*5"}]}`
183190 (active only). Rewards get normalized (`A*60;B*5` → `A ×60, B ×5`).
191+ - **OpenGachaCodes** — `<opengachaBaseUrl>/games/<slug>/codes`, slugs `genshin`,
192+ ` starrail` , `zenless`, `wuwa` (also endfield/nte, not yet in the registry).
193+ Shape : a flat array `[{"code","rewards":["Astrite x50", ...]}]` (active only).
194+ Self-hosted, no auth/CORS, GET-only, strict paths (no trailing slash). A `404`
195+ is treated as "nothing to contribute" (not a source failure). Reward quantities
196+ use ASCII `x`, not `×`; they're joined with `, ` as-is. `fetchOpengacha` lives
197+ in `sources.go`; the base URL is `opengachaBaseUrl` in config.
184198
185199The internal key differs from the torikushiii slug for Star Rail (`hsr`/`hkrpg`)
186- and Zenless (`zzz`/`nap`). Both slugs and reward-normalization live in `games.go` /
187- ` sources.go` . To add another game (e.g. Tears of Themis — ennead `tot` / tori `tot`),
188- add one entry to the `games` map **and** to `gameOrder`; nothing else needs to change.
200+ and Zenless (`zzz`/`nap`). Per-source slugs live in `games.go`; a `Game` sets a
201+ slug to `""` for any source that doesn't serve it (e.g. `wuwa` has empty
202+ ` Ennead` /`Tori` and is OpenGachaCodes-only), and `fetchCodes` skips empty-slug
203+ sources. To add another game, add one entry to the `games` map **and** to
204+ ` gameOrder` ; nothing else needs to change. A game whose only source is
205+ OpenGachaCodes **fails startup** if `opengachaBaseUrl` is unset (it could never
206+ produce codes) — a validation problem in `loadConfig`, like a missing webhook.
189207
190208# # Conventions
191209
0 commit comments