Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 855c1571f9 |
7 changed files with 49 additions and 178 deletions
|
|
@ -47,8 +47,7 @@ const pages = (await Promise.all(Object.entries(categoryTypes).map(async ([categ
|
||||||
|
|
||||||
console.log('Processing', pages.length, 'items\n');
|
console.log('Processing', pages.length, 'items\n');
|
||||||
|
|
||||||
for (const page of pages) {
|
for (const {title, type} of pages) {
|
||||||
const {title, type} = page;
|
|
||||||
// this runs serially with an artificial delay between requests to decrease
|
// this runs serially with an artificial delay between requests to decrease
|
||||||
// the chance of sqenix sending ninjas to my house
|
// the chance of sqenix sending ninjas to my house
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
@ -74,7 +73,7 @@ for (const page of pages) {
|
||||||
let updatedText;
|
let updatedText;
|
||||||
try {
|
try {
|
||||||
updatedText = insertInfoboxEDBID(originalText, edbID);
|
updatedText = insertInfoboxEDBID(originalText, edbID);
|
||||||
diff(originalText, updatedText, page);
|
diff(originalText, updatedText);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error inserting parameter:', error);
|
console.log('Error inserting parameter:', error);
|
||||||
console.group('Bad page content:');
|
console.group('Bad page content:');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env -S node --env-file-if-exists=.env
|
#!/usr/bin/env -S node --env-file-if-exists=.env
|
||||||
|
|
||||||
|
console.log(process.env);
|
||||||
|
process.exit(1);
|
||||||
|
|
||||||
import {findItemGTID} from '../lib/api/xivapi.js';
|
import {findItemGTID} from '../lib/api/xivapi.js';
|
||||||
import {getMediawikiClient} from '../lib/config.js';
|
import {getMediawikiClient} from '../lib/config.js';
|
||||||
import {diff} from '../lib/util/diff.js';
|
import {diff} from '../lib/util/diff.js';
|
||||||
|
|
@ -35,11 +38,10 @@ const mw = await getMediawikiClient();
|
||||||
|
|
||||||
// TODO: update this to work with the new maintenance category hierarchy
|
// TODO: update this to work with the new maintenance category hierarchy
|
||||||
// Get pages in the "Missing internal ID" category from the main article namespace
|
// Get pages in the "Missing internal ID" category from the main article namespace
|
||||||
const itemPagesWithoutGTIDs = await mw.listCategoryPages('Category:Items with no internal ID specified', [0], +process.env.LIMIT || 500);
|
const itemPagesWithoutGTIDs = await mw.listCategoryPages('Category:Missing internal ID', [0], +process.env.LIMIT || 500);
|
||||||
console.log('Processing', itemPagesWithoutGTIDs.length, 'item pages from [[Category:Items with no internal ID specified]]\n');
|
console.log('Processing', itemPagesWithoutGTIDs.length, 'item pages from [[Category:Missing internal ID]]\n');
|
||||||
|
|
||||||
for (const page of itemPagesWithoutGTIDs) {
|
for (const {title} of itemPagesWithoutGTIDs) {
|
||||||
const {title} = page;
|
|
||||||
console.log('Page:', title);
|
console.log('Page:', title);
|
||||||
// look up on XIVAPI
|
// look up on XIVAPI
|
||||||
let gtID;
|
let gtID;
|
||||||
|
|
@ -66,7 +68,7 @@ for (const page of itemPagesWithoutGTIDs) {
|
||||||
let updatedText;
|
let updatedText;
|
||||||
try {
|
try {
|
||||||
updatedText = insertInfoboxGTBID(originalText, gtID);
|
updatedText = insertInfoboxGTBID(originalText, gtID);
|
||||||
diff(originalText, updatedText, page);
|
diff(originalText, updatedText);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error inserting parameter:', error);
|
console.log('Error inserting parameter:', error);
|
||||||
console.group('Bad page content:');
|
console.group('Bad page content:');
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env -S node --env-file-if-exists=.env
|
|
||||||
// Adds `quest-sync` parameters to quest infoboxes that don't have them
|
|
||||||
|
|
||||||
import { findEDBQuestSync } from '../../lib/api/lodestone.js';
|
|
||||||
import {getMediawikiClient} from '../../lib/config.js';
|
|
||||||
import {diff} from '../../lib/util/diff.js';
|
|
||||||
import { addParameterBesideExistingParameter } from '../../lib/util/template-parameters.js';
|
|
||||||
|
|
||||||
const mw = await getMediawikiClient();
|
|
||||||
|
|
||||||
const allquestcats = `
|
|
||||||
Category:Main Scenario quests
|
|
||||||
Category:Side Story quests
|
|
||||||
Category:Guildleves
|
|
||||||
Category:Sidequests
|
|
||||||
Category:other quests
|
|
||||||
Category:Daily quests
|
|
||||||
Category:Feature quests
|
|
||||||
Category:Repeatable Feature quests
|
|
||||||
Category:Quasi-quests
|
|
||||||
Category:Feature quasi-quests
|
|
||||||
Category:Quests with no type specified
|
|
||||||
`.split('\n').filter(s => s);
|
|
||||||
console.log(allquestcats)
|
|
||||||
|
|
||||||
const pages = (await Promise.all(allquestcats.map(category => mw.listCategoryPages(category, [0], 'max')))).flat();
|
|
||||||
|
|
||||||
console.log(pages.length);
|
|
||||||
|
|
||||||
function insertQuestSync (pageContent) {
|
|
||||||
if (pageContent.includes('quest-sync')) {
|
|
||||||
throw new Error('Page already contains a `quest-sync` parameter');
|
|
||||||
}
|
|
||||||
let result = addParameterBesideExistingParameter(pageContent, 'quest-sync', 'true', 'after', 'level');
|
|
||||||
if (result == null) throw new Error('Failed to add parameter');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = parseInt(process.env.START ?? '0', 10);
|
|
||||||
if (start) {
|
|
||||||
console.log('Starting from item index', start);
|
|
||||||
pages.splice(0, start);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [i, {title}] of Object.entries(pages)) {
|
|
||||||
console.log(start+parseInt(i), title);
|
|
||||||
const content = await mw.readPage(title);
|
|
||||||
const hasQuestSync = await findEDBQuestSync(title.replace(' (Quest)', ''));
|
|
||||||
if (!hasQuestSync) continue;
|
|
||||||
let newContent;
|
|
||||||
try {
|
|
||||||
newContent = insertQuestSync(content);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
diff(content, newContent);
|
|
||||||
|
|
||||||
await mw.editPage(title, newContent, 'Add quest-sync parameter', true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
#!/usr/bin/env -S node --env-file-if-exists=.env
|
|
||||||
// Fixes flavor text formatting on files robogurgum created for patches 7.3/7.31
|
|
||||||
// see https://discord.com/channels/312060255469305856/1412850908668100810
|
|
||||||
|
|
||||||
import {getMediawikiClient} from '../../lib/config.js';
|
|
||||||
import {diff} from '../../lib/util/diff.js';
|
|
||||||
|
|
||||||
const mw = await getMediawikiClient();
|
|
||||||
|
|
||||||
function fixPageContent (pageContent) {
|
|
||||||
const flavorTextRegexp = /\| flavor-text = ([\s\S]*?)\n\|/;
|
|
||||||
|
|
||||||
let match = pageContent.match(flavorTextRegexp);
|
|
||||||
if (!match) return pageContent;
|
|
||||||
let flavorText = match[1];
|
|
||||||
if (!flavorText.trim()) return pageContent;
|
|
||||||
|
|
||||||
let newFlavorText = flavorText
|
|
||||||
// linebreaks with
|
|
||||||
// - no <br> or <br /> etc before or after
|
|
||||||
// - no {{action fact}} after (that renders a line break at the start)
|
|
||||||
.replace(/(?<!<br\s*\/?>)\n(?!<br\s*\/?>)(?!{{action fact|\s*$)/gi, '<br>\n')
|
|
||||||
// spans that set color via inline style instead of using {{colorize}}
|
|
||||||
.replace(/<span style="color: #00cc22;">([^<]+)<\/span>/g, '`$1`')
|
|
||||||
.replace(/<span style="color: #ffff66;">([^<]+)<\/span>/g, '``$1``')
|
|
||||||
.replace(/<span style="color: #ff7b1a;">([^<]+)<\/span>/g, '```$1```')
|
|
||||||
// convert lunar/phaenna credit bonuses to use {{action fact}}, but only
|
|
||||||
// when we can be sure it won't add extra line breaks
|
|
||||||
.replace(
|
|
||||||
/<br\s*\/?>(\n?)`([\w\d _-]+)(?<=Credit Bonus)(?::`|`:) +([^\n<]+)/g,
|
|
||||||
'$1{{action fact|$2|$3}}',
|
|
||||||
)
|
|
||||||
// flavor-text is automatically colorized, remove redundant invocations
|
|
||||||
.replace(/{{colorize\|([^|{}]+)}}/g, '$1');
|
|
||||||
|
|
||||||
return pageContent.replace(flavorTextRegexp, `| flavor-text = ${newFlavorText}\n|`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = await mw.listUserContribs('RoboGurgum', {namespaces: [0], show: 'new', limit: 1000});
|
|
||||||
for (const page of pages) {
|
|
||||||
const {title} = page;
|
|
||||||
const pageContent = await mw.readPage(title);
|
|
||||||
let fixedContent = fixPageContent(pageContent);
|
|
||||||
if (fixedContent === pageContent) {
|
|
||||||
console.log('#', title, ' No change\n');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
diff(pageContent, fixedContent, page);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
await mw.editPage(title, fixedContent, 'Fix flavor text formatting', true);
|
|
||||||
}
|
|
||||||
|
|
@ -27,26 +27,9 @@ export class MediaWikiClient {
|
||||||
constructor (wikiURL, {killPage}) {
|
constructor (wikiURL, {killPage}) {
|
||||||
this.wikiURL = wikiURL;
|
this.wikiURL = wikiURL;
|
||||||
this.killPage = killPage;
|
this.killPage = killPage;
|
||||||
this.lastKillPageCheck = 0; // first check is triggered after login
|
|
||||||
this.fetch = makeFetchCookie(fetch);
|
this.fetch = makeFetchCookie(fetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks the kill page if needed, killing the process if not empty. */
|
|
||||||
async tryKillPageCheck () {
|
|
||||||
if (!this.killPage) return;
|
|
||||||
// wait at least 10 seconds since the last check
|
|
||||||
if (Date.now() > this.lastKillPageCheck + 10 * 1000) return;
|
|
||||||
this.lastKillPageCheck = Date.now();
|
|
||||||
|
|
||||||
const response = await this.fetch(`${this.wikiURL}/index.php?action=raw&title=${encodeURIComponent(this.killPage)}`);
|
|
||||||
const content = await response.text();
|
|
||||||
if (content.trim()) {
|
|
||||||
console.error('*** Kill page is not empty; stopping ***\n');
|
|
||||||
console.error(content);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a GET request against `index.php`.
|
* Makes a GET request against `index.php`.
|
||||||
* @param {Record<string, string>} params Query string parameters
|
* @param {Record<string, string>} params Query string parameters
|
||||||
|
|
@ -88,7 +71,6 @@ export class MediaWikiClient {
|
||||||
* @returns {Promise<any>}
|
* @returns {Promise<any>}
|
||||||
*/
|
*/
|
||||||
async fetchApiPost (params, options = {}) {
|
async fetchApiPost (params, options = {}) {
|
||||||
await this.tryKillPageCheck();
|
|
||||||
const response = await this.fetch(`${this.wikiURL}/api.php`, {
|
const response = await this.fetch(`${this.wikiURL}/api.php`, {
|
||||||
...options,
|
...options,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -146,6 +128,20 @@ export class MediaWikiClient {
|
||||||
if (body.login.result === 'Failed') {
|
if (body.login.result === 'Failed') {
|
||||||
throw new Error(body.login.reason);
|
throw new Error(body.login.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.killPage) {
|
||||||
|
// start the kill page check loop as soon as we're logged in
|
||||||
|
const checkKillPage = async () => {
|
||||||
|
let content = await this.readPage(this.killPage);
|
||||||
|
if (content.trim()) {
|
||||||
|
console.error('*** Kill page is not empty; stopping ***\n');
|
||||||
|
console.error(content);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
setTimeout(checkKillPage, 30 * 1000); // every 30 seconds
|
||||||
|
};
|
||||||
|
checkKillPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -243,28 +239,4 @@ export class MediaWikiClient {
|
||||||
});
|
});
|
||||||
return body.query.categorymembers;
|
return body.query.categorymembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list of a user's contributions.
|
|
||||||
* @param {string} username Name of the user whose contribs should be fetched
|
|
||||||
* @param {object} options
|
|
||||||
* @param {number | number[] | '*'} [options.namespaces] List of namespaces from which to return results
|
|
||||||
* @param {number} [options.limit] Maximum number of items to return
|
|
||||||
* @param {string} [options.show] See the documentation of `ucshow` at https://www.mediawiki.org/wiki/API:Usercontribs
|
|
||||||
* @returns {Promise<{pageid: string; revid: string; timestamp: string; title: string}[]>}
|
|
||||||
*/
|
|
||||||
async listUserContribs (username, {namespaces = '*', limit = 50, show}) {
|
|
||||||
if (Array.isArray(namespaces)) {
|
|
||||||
namespaces = namespaces.join('|');
|
|
||||||
}
|
|
||||||
const body = await this.fetchApiGet({
|
|
||||||
action: 'query',
|
|
||||||
list: 'usercontribs',
|
|
||||||
ucuser: username,
|
|
||||||
uclimit: limit,
|
|
||||||
ucnamespace: namespaces,
|
|
||||||
ucshow: show,
|
|
||||||
});
|
|
||||||
return body.query.usercontribs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,17 @@ const toBase64 = str => Buffer.from(str, 'utf-8').toString('base64');
|
||||||
* terrible terrible terrible string diff helper for debugging
|
* terrible terrible terrible string diff helper for debugging
|
||||||
* @param {string} a
|
* @param {string} a
|
||||||
* @param {string} b
|
* @param {string} b
|
||||||
* @param {object} page Page object from the API, used to generate diff labels
|
|
||||||
*/
|
*/
|
||||||
export function diff (a, b, page) {
|
export function diff (a, b) {
|
||||||
let labels = null;
|
|
||||||
if (page) { // generate diff labels based on
|
|
||||||
labels = [
|
|
||||||
`${page.title} (${page.revid ? `revision ${page.revid}` : 'current revision'}${page.timestamp ? ` from ${page.timestamp}` : ''})`,
|
|
||||||
`${page.title} (pending edit)`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
// base64 input strings before passing to shell to avoid escaping issues
|
// base64 input strings before passing to shell to avoid escaping issues
|
||||||
// https://stackoverflow.com/a/60221847
|
// https://stackoverflow.com/a/60221847
|
||||||
// use tail to cut off file info lines and re-add with fake filenames/dates
|
// use tail to cut off useless file info lines
|
||||||
// this function is so extra now holy shit
|
execSync(String.raw`bash <<- EOF
|
||||||
execSync(String.raw`bash <<- '_EOF_'
|
|
||||||
diff --color=always -u \
|
diff --color=always -u \
|
||||||
${labels ? `--label="$(echo '${toBase64(labels[0])}' | base64 -d)" ` : ''}<(echo '${toBase64(a)}' | base64 -d) \
|
<(echo "${toBase64(a)}" | base64 -d) \
|
||||||
${labels ? `--label="$(echo '${toBase64(labels[1])}' | base64 -d)" ` : ''}<(echo '${toBase64(b)}' | base64 -d) \
|
<(echo "${toBase64(b)}" | base64 -d) \
|
||||||
${labels ? `|| true` : `| tail -n +3` /* cut off header if no labels */}
|
| tail -n +3
|
||||||
_EOF_`, {
|
EOF`, {
|
||||||
// display result directly in terminal
|
// display result directly in terminal
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
19
temp.js
Normal file
19
temp.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const flavorTextRegexp = /\| flavor-text = ([\s\S]+?)\n\|/;
|
||||||
|
const flavorTextReplacement = newFlavorText => `| flavor-text = ${newFlavorText}\n|`
|
||||||
|
|
||||||
|
function fixPageContent (pageContent) {
|
||||||
|
let match = pageContent.match(flavorTextRegexp);
|
||||||
|
if (!match) return pageContent;
|
||||||
|
|
||||||
|
let flavorText = match[1];
|
||||||
|
let newFlavorText = flavorText
|
||||||
|
// linebreaks with no preceding <br> or <br /> etc
|
||||||
|
.replace(/(?<!<br\s*\/?>)\n/g, '<br>\n')
|
||||||
|
// spans that set color via inline style instead of using {{colorize}}
|
||||||
|
.replace(/<span style="color: #00cc22;">([^<]+)<\/span>/g, '`$1`')
|
||||||
|
.replace(/<span style="color: #ffff66;">([^<]+)<\/span>/g, '``$1``')
|
||||||
|
|
||||||
|
return pageContent.replace(flavorTextRegexp, flavorTextReplacement(newFlavorText));
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = //...
|
||||||
Loading…
Add table
Add a link
Reference in a new issue