Link to article: Site News Title Fetcher.
float: right;
hundred
good
bubble
bad
fifty
output-container
log
[[html]] <head> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script> <style type="text/css"> .log p { margin-top: 0; margin-bottom: 0; } .error { color: red; } textarea { white-space: pre-wrap; } </style> <script type="text/javascript"> "use strict"; Array.prototype.unique = function() { return this.filter(function (value, index, self) { return self.indexOf(value) === index; }); } String.prototype.toCamelCase = function() {return this.toLowerCase().replace(/[^\w\s\-]/g, '').replace(/[^a-z0-9]/g, ' ').replace(/^\s+|\s+$/g, '').replace(/\s(.)/g, function(match,group) {return group.toUpperCase()})}; var list = { original: "", lines: [], scps: [], scpTypes: [], placeholder: [], authors: [], }; var one = 0, two = 0, three = 0, four = 0, joke = 0, five = 0, six = 0, seven = 0, explained = 0, archived = 0, mystery = 0; var vlog = function(data, error) { if(typeof error === "undefined") { $("div.log").append("<p>" + data + "</p>"); console.log(data); } else { $("div.log").append("<p class='error'>" + data + "</p>"); }; }; var pagesToFetch = []; var pageData = { scpSeries: [], scpSeries2: [], scpSeries3: [], scpSeries4: [], scpSeries5: [], scpSeries6: [], scpSeries7: [], scpEx: [], archivedScps: [], jokeScps: [], authorsPages: [], }; var fetch = function(index) { // This function is called towards the end of #pull, handles grabbing and presenting the data. // must return array of objects {url: title:} for the complete series //possible pagenames: 1,2,3,4,5,6,ex,arc,joke //urls: scp-series, append -x, scp-ex, joke-scps, archived-scps //only archived-scps has a nonstandard format vlog("Fetching " + pagesToFetch[index] + "..."); $.ajax({ type: "GET", url: "https://api.codetabs.com/v1/proxy?quest=" + "http://scp-wiki.wikidot.com/" + pagesToFetch[index], timeout: 10000, error: function(jqXHR, textStatus, errorThrown) { vlog("Connection error: " + textStatus, true) }, success: function(response) { console.log("Fetched! " + typeof response); console.log(response); const contents = new DOMParser().parseFromString(response, "text/html"); console.log(contents) //code for manipulating mainlist goes here if(pagesToFetch[index] === "authors-pages") { var mainlist = $(contents).find("#page-content tr"); } else { var mainlist = $(contents).find("#page-content .content-panel:first ul li"); } console.log(mainlist); // split up "SCP-XXXX - Title" into ["SCP-XXXX","Title"] for(let i = 0; i < mainlist.length; i++) { var splits = []; if(pagesToFetch[index] === "authors-pages") { // splits[0] is the url, splits[1] is the title / author name splits[0] = mainlist[i].children[1].children[0].pathname.substring(1); splits[1] = mainlist[i].children[0].innerText; } else { splits = mainlist[i].innerHTML.split(" - "); switch(splits.length) { case 0: throw new Error("No splits - " + pagesToFetch[index] + " at index " + i); break; case 1: // Ignore this entry splits = ["Invalid","Invalid"]; break; case 2: // This is the intended value break; default: // At negative values(?) + 3 and above // Keep first value, concatenate the rest splits = [splits[0], splits.slice(1).join(" - ")]; }; // replace HTML with wikicode and format the URL into a title splits[0] = splits[0].replace(/<a href="\/(scp-\S+)">SCP-\S+<\/a>/,"$1".toUpperCase()); splits[1] = splits[1].replace("<strong>","**").replace("</strong>","**").replace("<em>","//").replace("</em>","//"); } // Log these splits to the thing pageData[pagesToFetch[index].toCamelCase()].push({url: splits[0], title: splits[1]}); // Now we check all of OUR SCPs against the mainlist // list.scpTypes has a list of the urls our SCPs need, in the right order. for(let j = 0; j < list.scpTypes.length; j++) { if(list.scpTypes[j] === pagesToFetch[index]) { // the SCP we're evaluating is of the correct type if(list.scps[j].toUpperCase() === splits[0].toUpperCase()) { vlog("Found a match for " + list.scps[j] + " (" + splits[1] + ")"); // Yay we did it! Now we need to replace %%actual-title%% list.lines[j] = list.lines[j].replace("%%actual-title%%",splits[1]); }; }; // evaluate the author thingy if($("#replaceAuthor").prop("checked") && list.authors[j] === splits[1]) { // found a match for this author console.log("Found the author page of " + splits[1] + ", " + splits[0]); list.lines[j] = list.lines[j].replace(new RegExp($("#authorSearch").val(),"gm"),$("#matchReplace").val().replace("$1",splits[1]).replace("$2",splits[0])); list.authors[j] = 0; // mark this author as already-replaced } else { // no match :'( //list.lines[j] = list.lines[j].replace(new RegExp($("#authorSearch").val()),$("#missReplace").val().replace("$1",splits[1])); }; }; }; // now let's go through and clean up unreplaced authors if($("#replaceAuthor").prop("checked")) { for(let j = 0; j < list.authors.length; j++) { if(list.authors[j] !== 0) { list.lines[j] = list.lines[j].replace(new RegExp($("#authorSearch").val()),$("#missReplace").val().replace("$1",list.authors[j])); }; }; }; if(++index < pagesToFetch.length) { fetch(index); } else { // now that all the lines have been replaced, substitute this into the textarea vlog("Outputting results..."); $("#output").val(list.lines.join("\n")); vlog("Done."); $("#copy").prop("disabled", false); }; }, }); } $(document).ready(function() { $("#clear").on("click", function() { $("div.log").empty(); }); $("#fetch").on("click", function() { list = { original: "", lines: [], scps: [], scpTypes: [], placeholder: [], authors: [], }; one = 0, two = 0, three = 0, four = 0, joke = 0, five = 0, six = 0, seven = 0, explained = 0, archived = 0, mystery = 0; try { vlog("Evaluating input..."); // Step one: take the original text and save it to the variable list.original = $("#input").val(); // Save this to the savebox $("#savebox").val(list.original); // Step two: split the list up into lines list.lines = list.original.split("\n"); if(list.lines.length === 1 && list.lines[0] == "") { throw new Error("Input is empty"); }; // Step three: search for SCPs // Go through each line and find the matching SCPs // list.scps is currently an empty array //fist, check if there are ANY scps or title placeholders if(list.original.search(/%%actual-title%%/) == -1) { throw new Error("No title placeholders present"); }; if(list.original.search(/[scp]{3}-/i) == -1) { throw new Error("No SCPs detected"); }; var missingPlaceholders = []; var missingScps = []; for(let line = 0; line < list.lines.length; line++) { // Iterating through each line // First, we need to check what lines are missing a title placeholder list.placeholder[line] = list.lines[line].search(/%%actual-title%%/); if(list.placeholder[line] == -1) { missingPlaceholders.push(line + 1); }; // Next, check which lines are missing SCP list.scps[line] = list.lines[line].search(/[scp]{3}-/i); if(list.scps[line] == -1) { missingScps.push(line + 1); }; } if(missingPlaceholders.length) { vlog("Warning: no title placeholders detected on line(s) " + missingPlaceholders.join(", "), true); } if(missingScps.length) { vlog("Warning: no SCPs detected on line(s) " + missingScps.join(", "), true); } for(let line = 0; line < list.lines.length; line++) { // still iterating through lines, but this time we know which ones to avoid if(missingPlaceholders.includes(line + 1) || missingScps.includes(line + 1)) { //do nothing, skip } else { // work out what scp we need to fetch if(list.lines[line].match(/([scp]{3}-\S+-j)\D/i)) { // match Joke SCPs list.scps[line] = list.lines[line].match(/([scp]{3}-\S+-j)\D/i)[1]; list.scpTypes[line] = "joke-scps"; } else if(list.lines[line].match(/(scp-\d{3})\D/i)) { // match series 1 SCPs list.scps[line] = list.lines[line].match(/(scp-\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series"; } else if(list.lines[line].match(/(scp-1\d{3})\D/i)) { // match series 2 SCPs list.scps[line] = list.lines[line].match(/(scp-1\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-2"; } else if(list.lines[line].match(/(scp-2\d{3})\D/i)) { // match series 3 SCPs list.scps[line] = list.lines[line].match(/(scp-2\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-3"; } else if(list.lines[line].match(/(scp-3\d{3})\D/i)) { // match series 4 SCPs list.scps[line] = list.lines[line].match(/(scp-3\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-4"; } else if(list.lines[line].match(/(scp-4\d{3})\D/i)) { // match series 5 SCPs list.scps[line] = list.lines[line].match(/(scp-4\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-5"; // TODO: add detectors for -EX and -ARC } else if(list.lines[line].match(/(scp-5\d{3})\D/i)) { // match series 6 SCPs list.scps[line] = list.lines[line].match(/(scp-5\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-6"; } else if(list.lines[line].match(/(scp-6\d{3})\D/i)) { // match series 7 SCPs list.scps[line] = list.lines[line].match(/(scp-6\d{3})\D/i)[1]; list.scpTypes[line] = "scp-series-7"; // TODO: add detectors for -EX and -ARC } else { vlog("Unsure whether or not there is an SCP on line " + (line + 1)); }; // now let's check for authors if(list.lines[line].match($("#authorSearch").val())) { list.authors[line] = list.lines[line].match($("#authorSearch").val())[1]; }; }; // squeeze this in while we're still looping if(list.scps[line] == -1) { list.scps[line] = "unknown"; }; }; vlog("Found " + list.scps.length + " entries: " + list.scps.join(", ").toUpperCase()); if($("#replaceAuthor").prop("checked")) vlog("Found " + list.authors.unique().length + " different authors"); // count up total numbers of jokes and series Xs for(let i = 0; i < list.scpTypes.length; i++) { switch(list.scpTypes[i]) { case "scp-series": one++; break; case "scp-series-2": two++; break; case "scp-series-3": three++; break; case "scp-series-4": four++; break; case "scp-series-5": five++; break; case "scp-series-6": six++; break; case "scp-series-7": seven++; break; case "joke-scps": joke++; break; case "scp-ex": explained++; break; case "archived-scps": archived++; break; default: mystery++; }; }; vlog("Found a total of " + one + " series 1s, " + two + " series 2s, " + three + " series 3s, " + four + " series 4s, " + five + " series 5s, " + six + " series 6s, " + seven + " series 7s, " + explained + " -EXs, " + archived + " -ARCs, " + (mystery ? ", " : " and ") + joke + " jokes" + (mystery ? " and " + mystery + " complete mysteries." : ".")); if(mystery) { vlog(mystery + " unknown article(s) - review these manually after fetching", true); }; if(!(one || two || three || four || five || six || seven || joke || explained || archived)) { throw new Error("Not fetching any pages"); } if(Math.random() > 0.95) { vlog("Ready to fetch, Daddy."); // for TSAT } else { vlog("Ready to fetch, Captain."); } $("#pull").prop("disabled", false); } catch(error) { vlog(error + ", terminating", true); throw error; } }); // PULL $("#pull").on("click", function() { $("#pull").prop("disabled", true); $("#fetch").prop("disabled", true); vlog("Fetching..."); try { // so what we're going to do here is define a new function that works out which pages we need to fetch. // then, using promises, we grab each one in order. pagesToFetch = []; // list of all pages to fetch if($("#replaceAuthor").prop("checked")) { pagesToFetch.push("authors-pages"); }; if(one) { pagesToFetch.push("scp-series"); }; if(two) { pagesToFetch.push("scp-series-2"); }; if(three) { pagesToFetch.push("scp-series-3"); }; if(four) { pagesToFetch.push("scp-series-4"); }; if(five) { pagesToFetch.push("scp-series-5"); }; if(six) { pagesToFetch.push("scp-series-6"); }; if(seven) { pagesToFetch.push("scp-series-7"); }; if(explained) { pagesToFetch.push("scp-ex"); }; if(archived) { pagesToFetch.push("archived-scps"); }; if(joke) { pagesToFetch.push("joke-scps"); }; // the functions that fetch this MUST return a single ARRAY of objects {url: "scp-XXX", title: string"} console.log(pagesToFetch); fetch(0); // the fetch function will handle the remainder of the operation. $("#pull").prop("disabled", false); $("#fetch").prop("disabled", false); } catch(error) { vlog(error + ", terminating", true); throw error; } }); /* AUTHOR STUFF */ $("#replaceAuthor").on("click", function() { $("#pull").prop("disabled", true); if($("#replaceAuthor").prop("checked")) { $("#authorOnly").show(); } else { $("#authorOnly").hide(); } try { } catch(error) { vlog(error + ", terminating", true); throw error; } }); /* COPY */ $("#copy").on("click", function() { console.log("Copied!"); try { $("#output").select(); document.execCommand("copy"); $(".output-container").addClass("copied"); setTimeout(function() {$(".output-container").removeClass("copied");}, 1000); } catch(error) { $(".output-container").addClass("failed"); setTimeout(function() {$(".output-container").removeClass("failed");}, 1000); console.error(error); } }); }); </script> <style> body { font-family: Nunito, sans-serif; } h1 { margin: 0.5rem 1rem 0.5em 1rem; } * { box-sizing: border-box; } body > div { border-radius: 3rem; margin: 0.2rem 0.6rem 1rem 0.6rem; padding: 1rem; box-shadow: 0 8px 17px 2px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); } html { padding: 3rem; } .hundred { width: 100%; display: flex; justify-content: center; } .fifty { width: 50%; margin: 0 0.5rem 0 0; } .fifty:nth-of-type(2) { margin: 0 0 0 0.5rem; } textarea { border-radius: 1rem; width: 100%; outline: none !important; padding: 0.3rem; height: 22rem; border-color: rgba(0,0,0,0.2); } input[type=text] { border-radius: 1rem; outline: none !important; border-color: rgba(0,0,0,0.2); } .log { margin: 0 1rem; } button { border-radius: 1rem; } .output-container { position: relative; } .output-container::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; font-size: 3rem; overflow: hidden; text-align: center; line-height: 22rem; border-radius: 1rem; opacity: 0; transition: all 0.3s ease-in-out; pointer-events: none; } .output-container.copied::after { content: "Copied!"; background-color: #2d882d; color: #004400; opacity: 0.5;} .output-container.failed::after { content: "Failed :("; background-color: #aa3939; color: #550000; opacity: 0.5;} #wrapper { padding-top: 1em; border-top: 1px solid rgba(0,0,0,0.2); } .bubble { background: rgba(25,25,100,0.15); border-radius: 1rem; margin: 0 0.5rem; padding: 0.5rem 1rem; } .bubble.good { background: rgba(0,100,0,0.15); } .bubble.bad { background: rgba(100,0,0,0.15); } .bubble p, .bubble ul { margin: 0; } </style> </head> <body> <div> <h1>SCP title-fetching tool v2.3</h1> <p>Made by Croquembouche for the SCP wiki news. This tool uses <a href="https://codetabs.com/cors-proxy/cors-proxy.html">CodeTabs CORS Proxy</a>.</p> <p>For each line, %%actual-title%% will be replaced with the title of the <b>first</b> correctly-formatted SCP mentioned on that line.</p> <p>Use SCP URLs instead of names - i.e., "scp-botnik-j" as opposed to "SCP-\̅\̅\̅\̅-J"</p> <div class="hundred"> <div class="good bubble"> <p>Supports:</p> <ul> <li>Series I - VII</li> <li>Jokes</li> <li>Author pages</li> </ul> </div> <div class="bad bubble"> <p>Does not support:</p> <ul> <li>Tales, GoIs</li> <li>Proposals</li> <li>EX, ARC, translations</li> </ul> </div> </div> <p>Usage: Paste a list of SCPs into the box from <a href=http://www.scp-wiki.net/site-news-automator>the site news automator</a>. Evaluate the list and review the log underneath to see if there are any errors. If there aren't, or if you don't care, hit Fetch.</p> <p>If the list is changed after evaluating it, make sure to re-evaluate before fetching again.</p> <div id="wrapper"> <div class="hundred"> <div class="fifty"> <textarea id="input" placeholder="Input"></textarea> </div> <div class="fifty output-container"> <textarea id="output" placeholder="Output"></textarea> </div> </div> <p><input type="checkbox" id="replaceAuthor"> Replace author names with author pages</p> <div id="authorOnly" style="display: none;"> <p>Regular expression to search for: <input id="authorSearch" value="by \[\[\*user (.*)\]\]"></p> <p>Replace matches with: <input id="matchReplace" value="by [[[$2 | $1]]]"></p> <p>Replace misses with: <input id="missReplace" value="by $1"></p> </div> <p> <button type="button" id="fetch">Evaluate list</button> <button type="button" id="clear">Clear log</button> <button type="button" id="pull" disabled>Fetch titles</button> <button type="button" id="copy" disabled style="float: right;">Copy output</button> </p> <div class="log"> <p>Waiting for input...</p> </div> </div> </div> </body> [[/html]]