Advertisement
Sp3l

GW2W - STDT to ObjectivesTable

Apr 3rd, 2022 (edited)
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. await (async function(oneLine) {
  2.     function findColumn(columns, fn) {
  3.         const i = columns.findIndex(x => fn(x.name));
  4.         if (i >= 0) {
  5.             return columns.splice(i, 1)[0];
  6.         }
  7.         return null;
  8.     }
  9.  
  10.     const oth = {
  11.         api: true,
  12.         map: false,
  13.         location: false,
  14.         notes: true,
  15.         customBit: false
  16.     };
  17.  
  18.     oneLine = oneLine ?? false;
  19.  
  20.     // ================
  21.     //  (0) Check page
  22.     // ================
  23.  
  24.     const pageTitle = window.location.href.match(/^https:\/\/wiki.guildwars2.com\/wiki\/(.*?)(?:#.*)?$/)[1];
  25.     if (!pageTitle) {
  26.         throw new Error(`Couldn't get GW2W's window.location.href: ${window.location.href}`);
  27.     }
  28.  
  29.     const $achievementbox = $('h2 ~ table.achievementbox')
  30.     if ($achievementbox.length === 0) {
  31.         throw new Error("Couldn't find .achievementbox");
  32.     }
  33.    
  34.     const $table = $('h2:has("#Walkthrough") ~ table');
  35.     if ($table.length === 0) {
  36.         throw new Error("Couldn't find walkthrough table");
  37.     }
  38.  
  39.     // ====================
  40.     //  (1) Fetch wikitext
  41.     // ====================
  42.     const pageText = await fetch(`https://wiki.guildwars2.com/index.php?title=${pageTitle}&action=edit`).then(x => x.text());
  43.     //const wikitext = new DOMParser().parseFromString(pageText, 'text/html').getElementById('wpTextbox1').value;
  44.     const wikitext = new DOMParser().parseFromString(pageText, 'text/html').getElementsByTagName('textarea')[0].value;
  45.     if (!wikitext) {
  46.         throw new Error("Couldn't fetch wikitext");
  47.     }
  48.  
  49.     // ====================
  50.     //  (2) Parse wikitext
  51.     // ====================
  52.  
  53.     const table = wikitext.match(/== *Walkthrough *==\n(?:.*\n)*\{\| \{\{STDT.*\n((?:.*\n)+)\|\}(?:.*\n?)+/im)[1];
  54.     const rr = table.match(/(?:.*?\n)+?(\|-(?:.*\n?)+)/m)[1];
  55.     const rows = rr.match(/(?<=\|-.*\n)(?:(?:!|\|).*\n?)+?(?:[^!|].*\n?)*(?:(?=\|-)|$)/gm)
  56.                    .map(x => x.split(/\|\||\n\|/).flat().map(x => x.replace(/^(?:!|\|) *(.*?) *$/g, '$1').trim()));
  57.     const rowClasses = rr.match(/(?<=\|-).*/gm).map(x => x.trim().match(/class="(.*?)"/)?.[1] ?? '');
  58.     const columnNames = table.match(/^! *.*(?:(?=\n!)|(?=\n\|-))$/gm)
  59.                              .map(x => x.split(/!!|\|\|/)).flat().map(x => x.replace(/^! *(.*?) *$/g, '$1').replace(/^.*?\|(.*)$/, '$1').trim())
  60.                              .map((x, i) => ({ i, name: x }));
  61.     const customColumns = columnNames.slice();
  62.  
  63.     const indexColumn = findColumn(customColumns, s => (/^(#|number|step|)$/gi).test(s));
  64.     const mapColumn = findColumn(customColumns, s => (/^map$/gi).test(s));
  65.     const locationColumn = findColumn(customColumns, s => (/^(location|image)$/gi).test(s));
  66.     let hintColumn = findColumn(customColumns, s => (/\bhints?\b/gi).test(s));
  67.     let notesColumn = findColumn(customColumns, s => (/^(notes?|directions?|acquisition)$/gi).test(s)) ?? findColumn(customColumns, s => (/^(description|hints)$/gi).test(s));
  68.     if (!notesColumn) {
  69.         notesColumn = hintColumn;
  70.         hintColumn = null;
  71.     }
  72.     const landmarkColumn = findColumn(customColumns, s => (/(^closest landmark$)|(\bpoi\b)|(\bpoint of interest\b)|(\bwaypoint\b)|(\b\{\{map icon\|waypoint}}\b)/gi).test(s));
  73.     let objectivesColumn = customColumns.splice(0, 1)[0];
  74.     if (!objectivesColumn) {
  75.         objectivesColumn = hintColumn;
  76.         hintColumn = null;
  77.     }
  78.  
  79.     oth.map = !!mapColumn;
  80.     oth.location = !!locationColumn;
  81.     oth.notes = !!notesColumn;
  82.  
  83.     const oRows = rows.map(row => {
  84.         const o = {};
  85.  
  86.         let col = 1; let k = 0;
  87.  
  88.         if (objectivesColumn) o.objective = row[objectivesColumn.i];
  89.         if (indexColumn) o.index = row[indexColumn.i];
  90.         if (notesColumn) o.notes = row[notesColumn.i];
  91.         if (mapColumn) o.map = row[mapColumn.i] === '' || /^(<!--.*-->)|( *([\x{2013}\x{2014}])|(&mdash;)|(&ndash;) *)$/.test(row[mapColumn.i]) ? null : row[mapColumn.i];
  92.         if (locationColumn) o.location = row[locationColumn.i] === '' || /^(<!--.*-->)|( *([\x{2013}\x{2014}])|(&mdash;)|(&ndash;) *)$/.test(row[locationColumn.i]) ? null : row[locationColumn.i];
  93.         if (landmarkColumn) {
  94.             o[`col${col}`] = row[landmarkColumn.i];
  95.             col++;
  96.             k++;
  97.         }
  98.         if (hintColumn) {
  99.             o[`col${col}`] = row[hintColumn.i];
  100.             col++;
  101.             k++;
  102.         }
  103.  
  104.         // Custom columns
  105.         if (customColumns.length > 3 - k) {
  106.             throw new Error(`The table has too many custum columns (${customColumns.length + k}): ${[landmarkColumn, ...customColumns].filter(x => !!x).map(x => `"${x}"`).join(', ')}`);
  107.         }
  108.  
  109.         for (let i = 0; i < customColumns.length; ++i) {
  110.             o[`col${i+1+k}`] = row[customColumns[i].i] === '' || /^<!--.*-->$/.test(row[customColumns[i].i]) ? '' : row[customColumns[i].i];
  111.         }
  112.  
  113.         return o;
  114.     });
  115.  
  116.     // ========================================================
  117.     //  (3) Check if the objectives list matches the API order
  118.     // ========================================================
  119.     const achievementId = $achievementbox.find('tr[data-id]').first().attr('data-id');
  120.     const wikiObjectives = $achievementbox.find('table:has(tbody > tr:contains("Objectives:")) > tbody > tr:not(.expandable):not(.collapse-reverse):not(:contains("Objectives:")) li').toArray().map(x => x.innerText);
  121.  
  122.     const apiAchievement = await fetch(`https://api.guildwars2.com/v2/achievements/${achievementId}`).then(x => x.json());
  123.     if (apiAchievement && apiAchievement.bits) {
  124.         apiAchievement.bits = apiAchievement.bits.map((x, i) => ({ i, ...x }));
  125.  
  126.         let b = true;
  127.         let i = 0;
  128.         while (b && i < wikiObjectives.length) {
  129.             b &&= wikiObjectives[i] === apiAchievement.bits[i].text;
  130.             i++;
  131.         }
  132.         if (!b) {
  133.             console.warn(`BITS[${i-1}]: "${wikiObjectives[i-1]}" !== "${apiAchievement.bits[i-1].text}"`);
  134.             oth.customBit = true;
  135.         }
  136.     } else {
  137.         oth.api = false;
  138.     }
  139.  
  140.     // ==========================================
  141.     //  (4) Fix the objectives bits if necessary
  142.     // ==========================================
  143.     let bitsManualReview = false;
  144.     if (oth.customBit) {
  145.         const bits = wikiObjectives.map((s, i) => {
  146.             const o = apiAchievement.bits.filter(x => x.text === s);
  147.             if (!o) throw new Error(`BITS: Couldn't find objective: "${s}"`);
  148.            bitsManualReview ||= o.length > 1;
  149.            return o.map(x => x.i);
  150.        });
  151.        for (let i = 0; i < oRows.length; ++i) {
  152.            oRows[i].customBit = bits[i].join(',');
  153.        }
  154.    }
  155.  
  156.    // =============================
  157.    //  (5) Create Objectives table
  158.    // =============================
  159.  
  160.    const N = oneLine ? '' : '\n';
  161.  
  162.    // {{Objectives table header}}
  163.    let col = 1;
  164.    let out = '{{Objectives table header';
  165.    if (!oth.api) out += '|api=false';
  166.    if (oth.map) out += '|map=true';
  167.    if (oth.location) out += '|location=true';
  168.    if (!oth.notes) out += '|notes=false';
  169.    if (landmarkColumn) out += `|col${col++}=Closest landmark`;
  170.    if (hintColumn) out += `|col${col++}=Hint`;
  171.    for (let i = 0; i < customColumns.length; ++i, ++col) {
  172.        out += `|col${col}=${customColumns[i].name}`;
  173.    }
  174.    if (oth.customBit) out += '|custom bit=true';
  175.    out += '}}\n';
  176.  
  177.    // {{Objectives table row}}
  178.    for (let i = 0; i < oRows.length; ++i) {
  179.        out += `{{Objectives table row|${oRows[i].objective ?? wikiObjectives[i]}${N}` +
  180.            (oth.notes ? `| ${oRows[i].notes}${N}` : '') +
  181.            (
  182.                (oth.map ? `| map = ${oRows[i].map?.replace(/^\[\[File:(.*?)(?:\|.*px)?]]$/, '$1') ?? 'none'} ` : '') +
  183.                (oth.location ? `| location = ${oRows[i].location?.replace(/^\[\[File:(.*?)(?:\|.*px)?]]$/, '$1') ?? 'none'} ` : '') +
  184.                (oRows[i].col1 !== undefined ? `| col1 = ${oRows[i].col1} ` : '') +
  185.                (oRows[i].col2 !== undefined ? `| col2 = ${oRows[i].col2} ` : '') +
  186.                (oRows[i].col3 !== undefined ? `| col3 = ${oRows[i].col3} `: '') +
  187.                (oth.customBit ? `| custom bit=${oRows[i].customBit} ` : '') +
  188.                (rowClasses[i] ? `| class=${rowClasses[i]}` : '')
  189.            ).trim() + `${N}` +
  190.            '}}\n';
  191.    }
  192.    out += '|}';
  193.    if (oneLine) {
  194.        out = out.replaceAll('| map = ', '|map=').replaceAll('| location = ', '|location=').replace(/\| col([1-3]) = /g, '|col$1=').replaceAll('| custom bit = ', '|custom bit=').replaceAll('| class = ', '|class=').replaceAll('| ', '|');
  195.    }
  196.  
  197.    console.log(out);
  198.    
  199.    if (oth.customBit) {
  200.        console.warn(`BITS: id=${achievementId}`);
  201.    }
  202.    if (bitsManualReview) {
  203.        console.warn('BITS: Some objectives have multiple bits. Manual review required!');
  204.    }
  205.    return {
  206.        oth,
  207.        rows,
  208.        rowClasses,
  209.        columnNames,
  210.        oRows,
  211.        wikiObjectives,
  212.        apiAchievement,
  213.    };
  214. })()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement