- TypeScript 98.2%
- JavaScript 1.3%
- Shell 0.4%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| .githooks | ||
| dist | ||
| docs | ||
| release | ||
| .gitattributes | ||
| .gitignore | ||
| _service-test-stubs.ts | ||
| auto-bump.js | ||
| CLAUDE.md | ||
| configuration.ts | ||
| dry-run.ts | ||
| gamevault.d.ts | ||
| HANDOFF.md | ||
| helpers.ts | ||
| igdb-age-ratings.ts | ||
| igdb-client.test.ts | ||
| igdb-client.ts | ||
| igdb-matching.test.ts | ||
| igdb-matching.ts | ||
| igdb-strict.plugin.module.ts | ||
| igdb.metadata-provider.service.ts | ||
| igdb.service.test.ts | ||
| LICENSE | ||
| models.ts | ||
| name-normalizer.ts | ||
| package-lock.json | ||
| package.json | ||
| plugin-version.ts | ||
| postbuild.js | ||
| README.md | ||
| release.ts | ||
| self-updater.ts | ||
| ship.js | ||
| sync-version.js | ||
| tsconfig.json | ||
| update-crypto.ts | ||
| update-keys.ts | ||
| update-state.ts | ||
| update-swap.ts | ||
| update-tar.ts | ||
| updater.test.ts | ||
| websearch-client.test.ts | ||
| websearch-client.ts | ||
ludonexus-plugin-igdb — IGDB (Strict)
An IGDB metadata-provider plugin for the LudoCodex backend, done right — a
polite, low-priority gap-fill provider that replaces the weak built-in
igdb provider.
- Slug:
igdb-strict(the built-in ownsigdb) - Default priority:
15(low on purpose — gap-fill; must stay UNIQUE across registered providers or registration throws) - Marker file (frozen ABI):
igdb-strict.plugin.module.js - Env prefix:
PLUGIN_LORDANDRADUS_IGDB_STRICT_
The two pillars
- Respect what is already mapped. No single-source-of-truth takeover, no
unmapping of other providers — ever. The plugin registers at a low, unique
priority, so in the platform's ascending-priority merge its fields apply
early and any higher-priority provider's (and always the user's) data wins.
Best of all, when a game already has a Steam mapping, Tier 0 keys off
that appid via IGDB's exact
external_gamesjoin instead of competing with it. - Never emit a false-positive mapping (miss > mis-map).
getBestMatchoverrides the platform's always-commit fuzzy matcher with an evidence-tiered ladder; when in doubt it misses — a miss is silently recoverable, a mis-map poisons the library.
The matching ladder
| Tier | Evidence | Notes |
|---|---|---|
| pin | [igdbid=119171] in the title/filename |
explicit user instruction; an unresolvable pin misses rather than falling through |
| 0 | existing Steam appid → external_games exact join |
zero title matching, zero mis-map risk; also honors [steamid=] pins |
| 1 | alpha-exact title identity (name or any alternative_names[]), screened on game_type, unique survivor required |
same-name reboots (two exact rows) are ambiguity, not a coin flip |
| 2 | loose shapes (edition/prefix/subtitle/acronym — the steamstore predicates) only with an independent corroborator and a uniqueness census | corroborators: (a) web-search → Steam appid → external_games agrees; (b) release-year and PC-platform agreement; (c) alternative-name exact agreement |
| 3 | — | NotFoundException: a clean miss. No sentinel, no placeholder. |
Non-MAIN_GAME rows (DLC, remasters, bundles, ports…) are rejected unless
the query itself names the derivative — exactly (Tier 1) or via an explicit
edition/remaster marker token (Tier 2).
Requirements
A Twitch developer app (IGDB rides on Twitch OAuth): create one at dev.twitch.tv/console/apps, then set:
PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_ID=...
PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_SECRET=...
Each operator supplies their own credentials — Twitch's Developer Services Agreement forbids shipping a shared secret. Without credentials the provider disables itself with a loud log line (same contract as the built-in). It is the same credential the built-in demands, so migrating costs nothing new.
Disable the built-in provider so the ecosystem posture is one IGDB voice:
METADATA_IGDB_ENABLED=false
Configuration
Variable (prefix PLUGIN_LORDANDRADUS_IGDB_STRICT_) |
Default | Meaning |
|---|---|---|
ENABLED |
true |
register the provider |
PRIORITY |
15 |
merge priority — LOW by design; must be unique |
CLIENT_ID / CLIENT_SECRET |
— | Twitch app credentials (required) |
REQUEST_INTERVAL_MS |
300 |
spacing between IGDB request starts (limit: 4/s) |
RETRY_INTERVAL_MS |
2500 |
delay between retries of transient failures |
MATCH_CONFIDENCE |
0.75 |
coverage floor a loose shape needs before it is even eligible for corroboration |
EXCLUDE_EROTIC |
false |
filter erotic-themed rows (IGDB theme 42) out of candidate searches. Caveat: IGDB's themes != (42) filter also drops rows with no themes at all |
WEBSEARCH_ENABLED |
false |
enable the Tier-2 web corroborator (title → Steam appid → external_games agreement) |
WEBSEARCH_PROVIDER etc. |
auto |
same engine chain as the steamstore plugin: SearXNG → Google PSE → Brave → Serper → DuckDuckGo |
AUTO_UPDATE, UPDATE_URL, UPDATE_INTERVAL_MS, UPDATE_PIN, … |
off | signed self-update (shared LudoNexus pipeline; staged, applied on restart) |
Honest caveats
- Adult-niche coverage is thin. IGDB permits adult content (no API-level
filter — good), but it is crowd-curated with no DLsite/doujin sync, so the
eroge/doujin long tail will mostly miss here (never mis-map). The
steamstore plugin's
[steamid=]+ web-search recovery remain the workhorse for that tail; this plugin's biggest wins are mainstream and non-Steam titles. - IGDB removes dead
image_ids after ~30 days — media URLs are never treated as permanent cache keys, and a CDN blip fails a refresh pass (keeping previous media) rather than blanking covers. - Tier-0 timing: in the very first update loop the in-memory game may not yet carry a Steam mapping created moments earlier; any re-cache picks it up. With the steamstore plugin in EXCLUSIVE mode, its claim blocks every other provider (including this one) for games it owns — by that plugin's design.
Install (no clone, no build — the release channel is the installer)
From the directory containing your LudoCodex stack's plugins/:
BASE=https://git.lordandradus.dev/api/packages/LordAndradus/generic/igdb-strict/stable
V=$(curl -fsS "$BASE/manifest.json" | grep -o '"version": *"[^"]*"' | cut -d'"' -f4)
mkdir -p ./plugins/gamevault-plugin-igdb-strict
curl -fsSL "$BASE/igdb-strict-$V.tar.gz" | tar -xz -C ./plugins/gamevault-plugin-igdb-strict
After extracting, set the required environment variables: PLUGIN_LORDANDRADUS_IGDB_STRICT_ENABLED=true, plus the Twitch credentials PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_ID and PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_SECRET (register at dev.twitch.tv/console/apps). Disable the weak built-in provider with METADATA_IGDB_ENABLED=false. If using the self-update feature (opt-in via PLUGIN_LORDANDRADUS_IGDB_STRICT_AUTO_UPDATE=true), mount ./plugins writable so updates can be applied. Restart the backend. Opt into self-updates and this is the last manual install that server ever needs. Paranoia check on first install: compare sha256sum of the tarball against the manifest — every LATER update is signature-verified automatically against the key pinned inside the bundle.
Production (docker-compose)
Paste this env block into your gamevault service's environment: section:
PLUGIN_LORDANDRADUS_IGDB_STRICT_ENABLED: "true"
PLUGIN_LORDANDRADUS_IGDB_STRICT_PRIORITY: "15"
PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_ID: "${IGDB_STRICT_CLIENT_ID}"
PLUGIN_LORDANDRADUS_IGDB_STRICT_CLIENT_SECRET: "${IGDB_STRICT_CLIENT_SECRET}"
PLUGIN_LORDANDRADUS_IGDB_STRICT_WEBSEARCH_ENABLED: "true"
# PLUGIN_LORDANDRADUS_IGDB_STRICT_WEBSEARCH_URL: "http://searxng:8080" # if running SearXNG
PLUGIN_LORDANDRADUS_IGDB_STRICT_AUTO_UPDATE: "true"
PLUGIN_LORDANDRADUS_IGDB_STRICT_UPDATE_URL: "https://git.lordandradus.dev/api/packages/LordAndradus/generic/igdb-strict/stable"
METADATA_IGDB_ENABLED: "false" # Disable the weak built-in provider
Development
npm install # also activates the pre-push hook
npm run build # tsc + postbuild (rewrites src/* imports for the backend)
npm test # matching + client + service + updater + websearch suites
npx tsx release.ts pack --out /tmp/det-a # deterministic pack (see HANDOFF)
npm run ship is the maintainer's act (signs + tags + publishes) — agents
prepare, the human releases.