No description
  • TypeScript 98.2%
  • JavaScript 1.3%
  • Shell 0.4%
Find a file
LordAndradus f772280f47
All checks were successful
release / release (push) Successful in 18s
release: v0.1.1
2026-07-12 05:30:55 -05:00
.forgejo/workflows fix: close the four adversarial-review-confirmed defects + one plausible vector 2026-07-12 01:43:36 -05:00
.githooks chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
dist release: v0.1.1 2026-07-12 05:30:55 -05:00
docs docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
release release: v0.1.1 2026-07-12 05:30:55 -05:00
.gitattributes docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
.gitignore docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
_service-test-stubs.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00
auto-bump.js chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
CLAUDE.md docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
configuration.ts feat: igdb-strict provider — evidence-tiered matcher over a hand-rolled IGDB client 2026-07-12 01:23:09 -05:00
dry-run.ts fix: kill the WarCraft III alias mis-map — fold guard + stem-alias demotion; background = first screenshot 2026-07-12 03:59:15 -05:00
gamevault.d.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00
HANDOFF.md docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
helpers.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00
igdb-age-ratings.ts feat: igdb-strict provider — evidence-tiered matcher over a hand-rolled IGDB client 2026-07-12 01:23:09 -05:00
igdb-client.test.ts fix: IGDB multiquery+search silently drops ALL results — split search into two plain lanes 2026-07-12 03:31:31 -05:00
igdb-client.ts fix: IGDB multiquery+search silently drops ALL results — split search into two plain lanes 2026-07-12 03:31:31 -05:00
igdb-matching.test.ts fix: kill the WarCraft III alias mis-map — fold guard + stem-alias demotion; background = first screenshot 2026-07-12 03:59:15 -05:00
igdb-matching.ts fix: kill the WarCraft III alias mis-map — fold guard + stem-alias demotion; background = first screenshot 2026-07-12 03:59:15 -05:00
igdb-strict.plugin.module.ts feat: igdb-strict provider — evidence-tiered matcher over a hand-rolled IGDB client 2026-07-12 01:23:09 -05:00
igdb.metadata-provider.service.ts fix: background priority is artwork first, first screenshot as fallback 2026-07-12 04:06:14 -05:00
igdb.service.test.ts fix: background priority is artwork first, first screenshot as fallback 2026-07-12 04:06:14 -05:00
LICENSE docs: design record, handoff, README for the igdb-strict gap-fill provider 2026-07-12 01:22:30 -05:00
models.ts fix: IGDB multiquery+search silently drops ALL results — split search into two plain lanes 2026-07-12 03:31:31 -05:00
name-normalizer.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00
package-lock.json chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
package.json release: v0.1.1 2026-07-12 05:30:55 -05:00
plugin-version.ts release: v0.1.1 2026-07-12 05:30:55 -05:00
postbuild.js chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
README.md docs: fresh-install section (release channel is the installer) + prod compose snippet 2026-07-12 04:21:55 -05:00
release.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
self-updater.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
ship.js chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
sync-version.js chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
tsconfig.json chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
update-crypto.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
update-keys.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
update-state.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
update-swap.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
update-tar.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
updater.test.ts chore(scaffold): signed self-update core + release tooling from steamstore 2026-07-12 01:22:31 -05:00
websearch-client.test.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00
websearch-client.ts chore(scaffold): shared matching/corroboration sources from steamstore 2026-07-12 01:23:08 -05:00

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 owns igdb)
  • 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

  1. 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_games join instead of competing with it.
  2. Never emit a false-positive mapping (miss > mis-map). getBestMatch overrides 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.