diff --git a/src/games/slaythespire2/wiki/cards.ts b/src/games/slaythespire2/wiki/cards.ts new file mode 100644 index 0000000..be979df --- /dev/null +++ b/src/games/slaythespire2/wiki/cards.ts @@ -0,0 +1,100 @@ +/** + * Compares the local CARDS item data array against the wiki's card data. + * Prints matched / new / stale entries to the terminal. + * + * On slaythespire.wiki.gg the card data is split into submodules + * `Module:Cards/StS2 data/`. + * + * Run with: pnpm tsx src/games/slaythespire2/wiki/cards.ts + */ + +import { cleanWikiText } from "#/features/wiki-sync/clean-wiki-text"; +import { + CARDS, + type SlayTheSpire2CardItem, +} from "#/games/slaythespire2/core/item-data/cards.ts"; +import { + type CompareResult, + getString, + syncWikiCategory, +} from "#/games/slaythespire2/wiki/sync-category.ts"; + +const CARD_SUBMODULES = [ + "Ironclad", + "Silent", + "Regent", + "Necrobinder", + "Defect", + "Colorless", +] as const; + +const WIKI_URLS = CARD_SUBMODULES.map( + (c) => + `https://slaythespire.wiki.gg/wiki/Module:Cards/StS2%20data/${c}?action=raw`, +); + +type LocalCard = (typeof CARDS)[number]; + +type WikiCard = { + name: string; + description: string[]; + cost: number | undefined; + type: string; + rarity: string; + color: string; + image: string; +}; + +const getNumber = ( + fields: Record, + key: string, +): number | undefined => + typeof fields[key] === "number" ? (fields[key] as number) : undefined; + +const normalizeEntry = ( + name: string, + fields: Record, +): WikiCard => ({ + name, + description: cleanWikiText(getString(fields, "Text")), + cost: getNumber(fields, "Cost"), + type: getString(fields, "Type"), + rarity: getString(fields, "Rarity"), + color: getString(fields, "Color"), + image: getString(fields, "Image"), +}); + +const compareCard = (local: LocalCard, wiki: WikiCard): CompareResult => { + const localDesc = JSON.stringify(local.description); + const wikiDesc = JSON.stringify(wiki.description); + const localCost = local.cost.energy; + const wikiCost = wiki.cost; + + const differingFields: string[] = []; + if (localDesc !== wikiDesc) differingFields.push("description"); + if (wikiCost !== undefined && localCost !== wikiCost) { + differingFields.push("cost.energy"); + } + + return { + differingFields, + printDetails: () => { + if (localDesc !== wikiDesc) { + console.log(` description local: ${localDesc}`); + console.log(` description wiki: ${wikiDesc}`); + } + if (wikiCost !== undefined && localCost !== wikiCost) { + console.log(` cost.energy local: ${localCost}`); + console.log(` cost.energy wiki: ${wikiCost}`); + } + }, + }; +}; + +void syncWikiCategory({ + wikiUrls: WIKI_URLS, + label: "cards", + localItems: CARDS, + normalizeEntry, + compareItem: compareCard, +}); diff --git a/src/games/slaythespire2/wiki/potions.ts b/src/games/slaythespire2/wiki/potions.ts index b057c9d..0955ddf 100644 --- a/src/games/slaythespire2/wiki/potions.ts +++ b/src/games/slaythespire2/wiki/potions.ts @@ -75,7 +75,7 @@ const comparePotion = (local: LocalPotion, wiki: WikiPotion): CompareResult => { }; void syncWikiCategory({ - wikiUrl: WIKI_URL, + wikiUrls: [WIKI_URL], label: "potions", localItems: POTIONS, normalizeEntry, diff --git a/src/games/slaythespire2/wiki/relics.ts b/src/games/slaythespire2/wiki/relics.ts index 8d8ba5b..d951a0d 100644 --- a/src/games/slaythespire2/wiki/relics.ts +++ b/src/games/slaythespire2/wiki/relics.ts @@ -134,7 +134,7 @@ const compareRelic = (local: LocalRelic, wiki: WikiRelic): CompareResult => { }; void syncWikiCategory({ - wikiUrl: WIKI_URL, + wikiUrls: [WIKI_URL], label: "relics", localItems: RELICS, normalizeEntry, diff --git a/src/games/slaythespire2/wiki/sync-category.ts b/src/games/slaythespire2/wiki/sync-category.ts index fe1b819..bb9eeea 100644 --- a/src/games/slaythespire2/wiki/sync-category.ts +++ b/src/games/slaythespire2/wiki/sync-category.ts @@ -54,7 +54,7 @@ type SyncCategoryOptions< TLocal extends { name: string }, TWiki extends { name: string }, > = { - wikiUrl: string; + wikiUrls: string[]; label: string; localItems: readonly TLocal[]; normalizeEntry: (name: string, fields: Record) => TWiki; @@ -68,7 +68,7 @@ type WikiSyncStats = { diffCounts: Map; }; -const fetchWikiItems = async ( +const fetchModule = async ( wikiUrl: string, normalizeEntry: (name: string, fields: Record) => TWiki, ): Promise => { @@ -83,6 +83,24 @@ const fetchWikiItems = async ( ); }; +const fetchWikiItems = async ( + wikiUrls: string[], + normalizeEntry: (name: string, fields: Record) => TWiki, +): Promise => { + const seen = new Set(); + const all: TWiki[] = []; + for (const wikiUrl of wikiUrls) { + for (const item of await fetchModule(wikiUrl, normalizeEntry)) { + if (seen.has(item.name)) { + console.warn(` ! duplicate '${item.name}' across modules — last wins`); + } + seen.add(item.name); + all.push(item); + } + } + return all; +}; + const reportMatch = ( local: TLocal, wiki: TWiki, @@ -122,9 +140,9 @@ const syncWikiCategory = async < >( opts: SyncCategoryOptions, ): Promise => { - const { wikiUrl, label, localItems, normalizeEntry, compareItem } = opts; + const { wikiUrls, label, localItems, normalizeEntry, compareItem } = opts; - const wikiItems = await fetchWikiItems(wikiUrl, normalizeEntry); + const wikiItems = await fetchWikiItems(wikiUrls, normalizeEntry); console.log( `\nFetched ${wikiItems.length} ${label} from wiki; local has ${localItems.length}.\n`, );