diff --git a/README.md b/README.md index b4075d2..5de7f4b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# ffxiv-wiki-edb-id-script +# ffxiv-wiki-scripts -Wiki bot for https://ffxiv.consolegameswiki.com that attempts to fix items in [Category:Missing EDB ID](https://ffxiv.consolegameswiki.com/wiki/Category:Missing_EDB_ID) by looking them up in Eorzea Database and adding the appropriate `id-edb` infobox parameter. +Wiki bot scripts for https://ffxiv.consolegameswiki.com. + +- `bin/add-edb-ids`: Attempts to fix items in [Category:Missing EDB ID](https://ffxiv.consolegameswiki.com/wiki/Category:Missing_EDB_ID) by looking them up in Eorzea Database and adding the appropriate `id-edb` infobox parameter. +- `bin/add-gt-ids`: The same for [Category:Missing internal ID](https://ffxiv.consolegameswiki.com/wiki/Category:Missing_EDB_ID), looking up internal IDs via [XIVAPI](https://v2.xivapi.com) and populating the `id-gt` parameter. ## Usage -You'll need a recent version of Node.js (22+). Install dependencies via `npm ci`, set the `MW_USERNAME` and `MW_PASSWORD` environment variables to your account username and [bot password](https://www.mediawiki.org/wiki/Manual:Bot_passwords) respectively, then run `bin/add-edb-ids`. +You'll need a recent version of Node.js (22+). Install dependencies via `npm ci`, set the `MW_USERNAME` and `MW_PASSWORD` environment variables to your account username and [bot password](https://www.mediawiki.org/wiki/Manual:Bot_passwords) respectively, then run the scripts in the `bin` folder. diff --git a/bin/add-gt-ids b/bin/add-gt-ids new file mode 100755 index 0000000..0df8cf1 --- /dev/null +++ b/bin/add-gt-ids @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +import {findItemGTID} from '../lib/api/xivapi.js'; +import {getMediawikiClient} from '../lib/config.js'; +import {diff} from '../lib/util/diff.js'; +import { + addParameterBesideExistingParameter, + setEmptyParameter, +} from '../lib/util/template-parameters.js'; + +/** + * Inserts the `id-gt` item infobox parameter into the given page contents. + * @param {string} pageContent + * @param {string} gtID + * @returns {string} + */ +function insertInfoboxGTBID (pageContent, gtID) { + if (pageContent.includes(`id-gt = ${gtID}`)) { + console.log('what????', pageContent); + throw new Error('Page seems to already contain its own GT ID??'); + } + + let result = setEmptyParameter(pageContent, 'id-gt', gtID) + ?? addParameterBesideExistingParameter(pageContent, 'id-gt', gtID, 'after', 'id-edb') + ?? addParameterBesideExistingParameter(pageContent, 'id-gt', gtID, 'after', 'release') + ?? addParameterBesideExistingParameter(pageContent, 'id-gt', gtID, 'after', 'patch'); + + if (result == null) { + throw new Error('Dunno how to insert the parameter into this page'); + } + return result; +} + +const mw = await getMediawikiClient(); + +// Get pages in the "Missing internal ID" category from the main article namespace +const itemPagesWithoutGTIDs = await mw.listCategoryPages('Category:Missing internal ID', [0], +process.env.LIMIT || 500); +console.log('Processing', itemPagesWithoutGTIDs.length, 'item pages from [[Category:Missing internal ID]]\n'); + +for (const {title} of itemPagesWithoutGTIDs) { + console.log('Page:', title); + // look up on XIVAPI + let gtID; + try { + gtID = await findItemGTID(title); + } catch (error) { + console.log('Error looking up item ID:', error); + } + if (!gtID) { + console.log('No ID found for this item, skipping'); + continue; + } + console.log('GT ID:', gtID, `(https://garlandtools.org/db/#item/${encodeURIComponent(gtID)})`); + + let originalText; + try { + originalText = await mw.readPage(title); + } catch (error) { + console.log('Error reading page:', error); + continue; + } + + // rewrite wiki page to include id-edb infobox parameter + let updatedText; + try { + updatedText = insertInfoboxGTBID(originalText, gtID); + diff(originalText, updatedText); + } catch (error) { + console.log('Error inserting parameter:', error); + console.group('Bad page content:'); + console.log(originalText); + console.groupEnd(); + console.log(); + continue; + } + + // write the new stuff back to the wiki + try { + await mw.editPage(title, updatedText, "Add GT item ID", true); + } catch (error) { + console.error(error); + console.error('writes should not fail, this seems bad, dying now'); + process.exit(1); + } + console.log('Written.'); + console.log(); +} + +console.log('done!'); diff --git a/lib/api/xivapi.js b/lib/api/xivapi.js new file mode 100644 index 0000000..012794e --- /dev/null +++ b/lib/api/xivapi.js @@ -0,0 +1,25 @@ +// Escapes any quotation marks in the given string with backslashes. +const backslashEscapeQuotes = (str) => str + .replace(/\\/g, '\\\\') // replace all \ with \\ + .replace(/"/g, '\\"'); // replace all " with \" + +/** + * Gets the internal ID of the named item from XIVAPI. + * @param {string} name + * @returns {Promise} + */ +export async function findItemGTID (name) { + const query = `Name~"${backslashEscapeQuotes(name)}"`; + const apiURL = `https://v2.xivapi.com/api/search?${new URLSearchParams({ + sheets: 'Item', + query, + fields: 'Name', + })}`; + const response = await fetch(apiURL); + const data = await response.json(); + if (!response.ok) { + throw new Error(`XIVAPI request failed: ${data.code} ${data.message}`); + } + const item = data.results.find(item => item.fields.Name.toLowerCase() === name.toLowerCase()); + return item?.row_id ?? null; +}