From 7b5357c050f608beb25bcb0360183d9534947058 Mon Sep 17 00:00:00 2001 From: ewin Date: Tue, 5 Aug 2025 11:45:21 -0400 Subject: [PATCH] Add one-off script for patch 7.3 stellar mission name changes --- bin/7-3-stellar-mission-renames | 136 ++++++++++++++++++++++++++++++++ lib/api/mediawiki.js | 34 ++++++++ 2 files changed, 170 insertions(+) create mode 100755 bin/7-3-stellar-mission-renames diff --git a/bin/7-3-stellar-mission-renames b/bin/7-3-stellar-mission-renames new file mode 100755 index 0000000..8594295 --- /dev/null +++ b/bin/7-3-stellar-mission-renames @@ -0,0 +1,136 @@ +#!/usr/bin/env node +// Renames A-class Stellar Missions to replace A-1, A-2, A-3 difficulty +// indicators with the new empty/EX/EX+ naming used in patch 7.3. +// https://na.finalfantasyxiv.com/lodestone/topics/detail/907c6d2aa020c0e4458ed1477668521fb4a117ec#:~:text=The%20notation,adjusted%2E + +import {getMediawikiClient} from '../lib/config.js'; +import {diff} from '../lib/util/diff.js'; +import {readExistingParameter, setExistingParameter} from '../lib/util/template-parameters.js'; + +const mw = await getMediawikiClient(); +const missions = await mw.listCategoryPages('Category:Stellar Missions', [0], 1000); + +const updateMissionTitle = title => title + // "A-1: Aetherochemical Samples I" -> "Aetherochemical Samples I" + .replace(/^A-1: /, '') + // "A-2: Aetherochemical Samples II" -> "EX: Aetherochemical Samples II" + .replace(/^A-2/, 'EX') + // "A-3: Aetherochemical Samples III" -> "EX+: Aetherochemical Samples III" + .replace(/^A-3/, 'EX+'); + +// Move all the mission pages we need to move +console.log('Moving pages'); +await Promise.allSettled(missions.map(async ({title}) => { + // Rename this mission page if necessary + let newTitle = updateMissionTitle(title); + if (title === newTitle) return; // not an A-class mission, nothing to change + + try { + await mw.movePage(title, newTitle, { + reason: 'A-rank stellar mission difficulties were renamed in patch 7.3', + redirect: true, + moveTalk: true, + moveSubpages: true, + }); + console.log(title, '->', newTitle); + } catch (error) { + if (error.message.includes('[articleexists]')) { + try { + await mw.movePage(title, newTitle + ' (Stellar Mission)', { + reason: 'A-rank stellar mission difficulties were renamed in patch 7.3', + redirect: true, + moveTalk: true, + moveSubpages: true, + }); + } catch (err2) { + console.error('Failed to move', title, 'to', newTitle + '(Stellar Mission)', ':', err2); + } + } else { + console.error('Failed to move', title, 'to', newTitle, ':', error); + } + } +})); + +console.log('Done renaming'); + +// Fetch the category listing again now that a bunch of titles have changed +const movedMissions = await mw.listCategoryPages('Category:Stellar Missions', [0], 1000); + +// Fix `previous`/`next` infobox parameters for sequential missions and use +// `rank = A` consistently for all A-class missions +for (const {title} of movedMissions) { + let pageContent = await mw.readPage(title); + const originalPageContent = pageContent; + + for (const parameter of ['previous', 'next']) { + const oldValue = readExistingParameter(pageContent, parameter); + const newValue = updateMissionTitle(oldValue); + + if (oldValue === newValue) continue; // mission title didn't change + + // Update the value on the page + let updatedPageContent = setExistingParameter(pageContent, parameter, newValue); + if (updatedPageContent == null) { + // something very weird has happened, this shouldn't be able to fail + // if we already got the value of the parameter earlier + console.error('weird shit happened with', title); + continue; + } + + pageContent = updatedPageContent; + } + + if (readExistingParameter(pageContent, 'rank').match(/A-[123]/)) { + pageContent = setExistingParameter(pageContent, 'rank', 'A'); + } + + console.log('Diff for', title); + diff(originalPageContent, pageContent); + try { + if (originalPageContent === pageContent) { + console.log('No changes'); + } else { + await mw.editPage(title, pageContent, 'Update links to mission(s) renamed in 7.3'); + console.log('Written.'); + } + } catch (error) { + console.error('Error writing page:', error); + console.error('writes should not fail, this seems bad, dying now'); + process.exit(1); + } +}; + +// Also update mission names for all CE item recipes +const items = await mw.listCategoryPages('Category:Cosmic Exploration Items', [0], 10000); +for (const {title} of items) { + let pageContent = await mw.readPage(title); + const originalPageContent = pageContent; + + const infoboxes = pageContent.split('{{Recipe'); + infoboxes.forEach((infobox, i) => { + if (i === 0) return; // ignore stuff before first recipe infobox + // console.log(infobox); + + const mission = readExistingParameter(infobox, 'mission'); + const updatedMission = updateMissionTitle(mission); + infoboxes[i] = setExistingParameter(infobox, 'mission', updatedMission); + }); + pageContent = infoboxes.join('{{Recipe'); + + console.log('Diff for', title); + diff(originalPageContent, pageContent); + try { + if (originalPageContent === pageContent) { + console.log('No changes'); + } else { + await mw.editPage(title, pageContent, 'Update links to mission(s) renamed in 7.3'); + console.log('Written.'); + } + } catch (error) { + console.error('Error writing page:', error); + console.error('writes should not fail, this seems bad, dying now'); + process.exit(1); + } +} + +console.log('Done with content updates'); diff --git a/lib/api/mediawiki.js b/lib/api/mediawiki.js index 8128878..b01788c 100644 --- a/lib/api/mediawiki.js +++ b/lib/api/mediawiki.js @@ -162,6 +162,40 @@ export class MediaWikiClient { return body; } + /** + * + * @param {string} from The page's current name + * @param {string} to The page's new name + * @param {object} options + * @param {string} options.reason Move reason + * @param {boolean} options.redirect Whether to create a redirect from the + * old name to the new name + * @param {boolean} options.moveTalk Whether to move the page's talk page + * from the old name to the new name + * @param {boolean} options.moveSubpages Whether to move the page's subpages + * from the old name to the new name + * @returns {Promise} + */ + async movePage (from, to, { + reason, + redirect = true, + moveTalk = true, + moveSubpages = true, + } = {}) { + const csrfToken = await this.getCSRFToken(); + return this.fetchApiPost({ + action: 'move', + from, + to, + reason, + movetalk: moveTalk, + movesubpages: moveSubpages, + noredirect: !redirect, + token: csrfToken, + format: 'json', + }); + } + /** * Gets the list of wiki pages that belong to the given category. * @param {string} name Category name including the `Category:` namespace.