您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds custom features to MSPFA.
当前为
// ==UserScript== // @name MSPFA extras // @namespace http://tampermonkey.net/ // @version 1.3.2.3 // @description Adds custom features to MSPFA. // @author seymour schlong // @match https://mspfa.com/ // @match https://mspfa.com/*/ // @match https://mspfa.com/*/?* // @match https://mspfa.com/?s=* // @match https://mspfa.com/my/* // @grant none // ==/UserScript== (function() { 'use strict'; /** * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true * Github to-do completion list * * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count * * Extension to-do... maybe... * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values (might be very difficult unless i can detect when any spoiler button on the bbtoolbar is clicked. EACH ONE) * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers (may also be extremely tough, as you have to add a button to each toolbar (or add an option in the regular spoiler)) */ // A general function that allows for waiting until a certain element appears on the page. const pageLoad = (fn) => { let interval = setInterval(() => { if (fn()) clearInterval(interval); }, 500); }; // Saves the options data for the script. const saveData = (data) => { localStorage.mspfaextra = JSON.stringify(data); //console.log("Saved cookies under mspfaextra."); }; // Encases an element within a link const addLink = (elm, url) => { let link = document.createElement('a'); link.href = url; elm.parentNode.insertBefore(link, elm); link.appendChild(elm); }; let settings = {}; if (localStorage.mspfaextra) { settings = JSON.parse(localStorage.mspfaextra); } else { settings.autospoiler = false; settings.style = 0; settings.styleURL = ""; settings.night = false; settings.auto502 = true; saveData(settings); } // If any settings are undefined, re-set to their default state. (For older users when new things get stored) if (typeof settings.autospoiler === "undefined") { settings.autospoiler = false; } if (typeof settings.style === "undefined") { settings.style = 0; } if (typeof settings.styleURL === "undefined") { settings.styleURL = ""; } if (typeof settings.night === "undefined") { settings.night = false; } if (typeof settings.auto502 === "undefined") { settings.auto502 = true; } if (typeof settings.textFix === "undefined") { settings.textFix = false; } if (typeof settings.pixelFix === "undefined") { settings.pixelFix = false; } //console.log(settings); let styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"]; let styleUrls = ['', '/css/theme1.css', '/css/theme2.css', '/css/?s=36237', '/css/theme4.css', '/css/theme5.css']; let myLink = document.querySelector('nav a[href="/my/"]'); let dropDiv = document.createElement('div'); dropDiv.className = 'dropdown'; Object.assign(dropDiv.style, { position: 'relative', display: 'inline-block', backgroundColor: 'inherit' }); let dropContent = document.createElement('div'); dropContent.className = 'dropdown-content'; Object.assign(dropContent.style, { display: 'none', backgroundColor: 'inherit', position: 'absolute', textAlign: 'left', minWidth: '100px', marginLeft: '-5px', padding: '2px', zIndex: '1', borderRadius: '0 0 5px 5px' }); dropDiv.addEventListener('mouseenter', evt => { dropContent.style.display = 'block'; dropContent.style.color = getComputedStyle(myLink).color; }); dropDiv.addEventListener('mouseleave', evt => { dropContent.style.display = 'none'; }); if (myLink) { myLink.parentNode.insertBefore(dropDiv, myLink); dropDiv.appendChild(myLink); dropDiv.appendChild(dropContent); let dLinks = []; dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ]; dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ]; for (let i = 0; i < dLinks[0].length; i++) { let newLink = document.createElement('a'); newLink.textContent = dLinks[0][i]; newLink.href = dLinks[1][i]; dropContent.appendChild(newLink); } } window.addEventListener("load", () => { // Reload the page if 502 CloudFlare error page appears if (settings.auto502 && document.querySelector('.cf-error-overview')) { window.location.reload(); } // Append "My Profile" to the dropdown list if you're signed in pageLoad(() => { if (window.MSPFA) { if (window.MSPFA.me.n) { let newLink = document.createElement('a'); newLink.textContent = "My Profile"; newLink.href = `/user/?u=${window.MSPFA.me.i}`; dropContent.appendChild(newLink); return true; } return true; } }); }); let pixelFixText = 'img, .mspfalogo, .major, .arrow, #flashytitle, .heart, .fav, .notify, .edit, .rss, input, #loading { image-rendering: pixelated !important; }' let dropStyleText = `#notification { z-index: 2; } .dropdown-content a { color: inherit; padding: 2px; text-decoration: underline; display: block;}`; let dropStyle = document.createElement('style'); dropStyle.id = 'dropdown-style'; dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : ''); //dropdownStyle.textContent = '#notification { z-index: 2;}.dropdown:hover .dropdown-content { display: block;}.dropdown { position: relative; display: inline-block; background-color: inherit;}.dropdown-content { display: none; position: absolute; text-align: left; background-color: inherit; min-width: 100px; margin-left: -5px; padding: 2px; z-index: 1; border-radius: 0 0 5px 5px;}.dropdown-content a { color: #fffa36; padding: 2px 2px; text-decoration: underline; display: block;}'; let theme = document.createElement('link'); Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' }); if (!document.querySelector('#theme') && !/^\/css\/|^\/js\//.test(location.pathname)) { document.querySelector('head').appendChild(theme); } if (!document.querySelector('#dropdown-style')) { document.querySelector('head').appendChild(dropStyle); } const updateTheme = (src) => { theme.href = src; } if (settings.night) { updateTheme('/css/?s=36237'); } else { updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); } pageLoad(() => { if (window.MSPFA) { if (window.MSPFA.story && window.MSPFA.story.y && window.MSPFA.story.y.length > 0) { updateTheme(''); } return true; } }); pageLoad(() => { if (document.querySelector('footer .mspfalogo')) { document.querySelector('footer .mspfalogo').addEventListener('dblclick', evt => { if (evt.button === 0) { settings.night = !settings.night; saveData(settings); if (settings.night) { updateTheme('/css/?s=36237'); } else { updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); } dropStyle.textContent = dropStyleText + ''; dropStyle.textContent = dropStyleText + '*{transition:1s}'; setTimeout(() => { dropStyle.textContent = dropStyleText; }, 1000); console.log(`Night mode turned ${settings.night ? 'on' : 'off'}.`); } }); return true; } }); if (location.pathname === "/" || location.pathname === "/preview/") { if (settings.autospoiler) { window.MSPFA.slide.push((p) => { document.querySelectorAll('#slide .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click()); }); } if (location.search) { pageLoad(() => { if (document.querySelector('#infobox tr td:nth-child(2)')) { document.querySelector('#infobox tr td:nth-child(2)').appendChild(document.createTextNode('Creation date: ' + new Date(window.MSPFA.story.d).toString().split(' ').splice(1, 3).join(' '))); return true; } }); if (settings.textFix) { pageLoad(() => { if (window.MSPFA.story && window.MSPFA.story.p) { // russian/bulgarian is not possible =( let currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]); let library = [ ["â��", "'"], ["Ã�", "Ñ"], ["ñ", "ñ"], ["ó", "ó"], ["á", "á"], ["í", "í"], ["ú", "ú"], ["é", "é"], ["¡", "¡"], ["¿", "¿"], ["Nº", "#"] ]; // https://mspfa.com/?s=5280&p=51 -- unknown error const replaceTerms = (p) => { library.forEach(term => { if (window.MSPFA.story.p[p]) { window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]); window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]); } }); }; replaceTerms(currentPage-1); window.MSPFA.slide.push(p => { replaceTerms(p); replaceTerms(p-2); }); window.MSPFA.page(currentPage); return true; } }); } pageLoad(() => { let infoButton = document.querySelector('.edit.major'); if (infoButton) { pageLoad(() => { if (window.MSPFA.me.i) { addLink(infoButton, `/my/stories/info/${location.search.split('&p=')[0]}`); return; } }); addLink(document.querySelector('.rss.major'), `/rss/${location.search.split('&p=')[0]}`); return true; } }); /* pageLoad(() => { let infoButton = document.querySelector('.edit.major'); if (infoButton) { let editPages = document.createElement('button'); Object.assign(editPages, { className: 'editpages major edit', title: 'Edit pages'}); //infoButton.parentNode.insertBefore(editPages, infoButton); return true; } });/**/ } } else if (location.pathname === "/my/settings/") { // Custom settings let saveBtn = document.querySelector('#savesettings'); let table = document.querySelector("#editsettings tbody"); let saveTr = table.querySelectorAll("tr"); saveTr = saveTr[saveTr.length - 1]; let headerTr = document.createElement('tr'); let header = document.createElement('th'); header.textContent = "Extra Settings"; headerTr.appendChild(header); let moreTr = document.createElement('tr'); let more = document.createElement('td'); more.textContent = "* This only applies to a select few older adventures that have had their text corrupted. Some punctuation is fixed, as well as regular characters with accents. Currently only some spanish/french is fixable. Russian/Bulgarian is not possible."; moreTr.appendChild(more); let settingsTr = document.createElement('tr'); let localMsg = document.createElement('span'); let settingsTd = document.createElement('td'); localMsg.innerHTML = "Because this is an extension, any data saved is only <b>locally</b> on this device.<br>Don't forget to <b>save</b> when you've finished making changes!"; let plusTable = document.createElement('table'); let plusTbody = document.createElement('tbody'); plusTable.appendChild(plusTbody); settingsTd.appendChild(localMsg); settingsTd.appendChild(document.createElement('br')); settingsTd.appendChild(document.createElement('br')); settingsTd.appendChild(plusTable); settingsTr.appendChild(settingsTd); let spoilerTr = plusTbody.insertRow(plusTbody.childNodes.length); let spoilerTextTd = spoilerTr.insertCell(0); let spoilerInputTd = spoilerTr.insertCell(1); let spoilerInput = document.createElement('input'); spoilerInputTd.appendChild(spoilerInput); let errorTr = plusTbody.insertRow(plusTbody.childNodes.length); let errorTextTd = errorTr.insertCell(0); let errorInputTd = errorTr.insertCell(1); let errorInput = document.createElement('input'); errorInputTd.appendChild(errorInput); let pixelFixTr = plusTbody.insertRow(plusTbody.childNodes.length); let pixelFixTextTd = pixelFixTr.insertCell(0); let pixelFixInputTd = pixelFixTr.insertCell(1); let pixelFixInput = document.createElement('input'); pixelFixInputTd.appendChild(pixelFixInput); let textFixTr = plusTbody.insertRow(plusTbody.childNodes.length); let textFixTextTd = textFixTr.insertCell(0); let textFixInputTd = textFixTr.insertCell(1); let textFixInput = document.createElement('input'); textFixInputTd.appendChild(textFixInput); let cssTr = plusTbody.insertRow(plusTbody.childNodes.length); let cssTextTd = cssTr.insertCell(0); let cssSelectTd = cssTr.insertCell(1); let cssSelect = document.createElement('select'); cssSelectTd.appendChild(cssSelect); let customTr = plusTbody.insertRow(plusTbody.childNodes.length); let customTextTd = customTr.insertCell(0); let customCssTd = customTr.insertCell(1); let customCssInput = document.createElement('input'); customCssTd.appendChild(customCssInput); plusTable.style = "text-align: center;"; spoilerTextTd.textContent = "Automatically open spoilers:"; spoilerInput.name = "p1"; spoilerInput.type = "checkbox"; spoilerInput.checked = settings.autospoiler; errorTextTd.textContent = "Automatically reload Cloudflare 502 error pages:"; errorInput.name = "p2"; errorInput.type = "checkbox"; errorInput.checked = settings.auto502; pixelFixTextTd.textContent = "Change pixel scaling to nearest neighbour:"; pixelFixInput.name = "p3"; pixelFixInput.type = "checkbox"; pixelFixInput.checked = settings.pixelFix; textFixTextTd.textContent = "Attempt to fix text errors (experimental)*:"; textFixInput.name = "p4"; textFixInput.type = "checkbox"; textFixInput.checked = settings.textFix; cssTextTd.textContent = "Change style:"; customTextTd.textContent = "Custom CSS URL:"; customCssInput.style.width = "99px"; customCssInput.value = settings.styleURL; styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o))); // Enable the save button spoilerInput.addEventListener("change", () => { saveBtn.disabled = false; }); errorInput.addEventListener("change", () => { saveBtn.disabled = false; }); pixelFixInput.addEventListener("change", () => { saveBtn.disabled = false; }); textFixInput.addEventListener("change", () => { saveBtn.disabled = false; }); cssSelect.addEventListener("change", () => { saveBtn.disabled = false; }); customCssInput.addEventListener("keydown", () => { saveBtn.disabled = false; }); saveTr.parentNode.insertBefore(headerTr, saveTr); saveTr.parentNode.insertBefore(settingsTr, saveTr); saveTr.parentNode.insertBefore(moreTr, saveTr); cssSelect.selectedIndex = settings.style; saveBtn.addEventListener('mouseup', () => { settings.autospoiler = spoilerInput.checked; settings.style = cssSelect.selectedIndex; settings.styleURL = customCssInput.value; settings.auto502 = errorInput.checked; settings.textFix = textFixInput.checked; settings.pixelFix = pixelFixInput.checked; settings.night = false; console.log(settings); saveData(settings); updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]); dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : ''); dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}'; setTimeout(() => { dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : ''); }, 1000); }); } else if (location.pathname === "/my/messages/") { // New buttons let btnStyle = "margin: 10px 5px;"; // Select all read messages button. const selRead = document.createElement('input'); selRead.style = btnStyle; selRead.value = "Select Read"; selRead.id = "selectread"; selRead.classList.add("major"); selRead.type = "button"; // On click, select all messages with the style attribute indicating it as read. selRead.addEventListener('mouseup', () => { document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click()); }); // Select duplicate message (multiple update notifications). const selDupe = document.createElement('input'); selDupe.style = btnStyle; selDupe.value = "Select Same"; selDupe.id = "selectdupe"; selDupe.classList.add("major"); selDupe.type = "button"; selDupe.addEventListener('mouseup', evt => { let temp = document.querySelectorAll('#messages > tr'); let msgs = []; for (let i = temp.length - 1; i >= 0; i--) { msgs.push(temp[i]); } let titles = []; msgs.forEach((msg) => { let title = msg.querySelector('a.major').textContent; if (/^New update: /.test(title)) { // Select only adventure updates if (titles.indexOf(title) === -1) { if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") { titles.push(title); } } else { msg.querySelector('input').click(); } } }); }); // Add buttons to the page. let del = document.querySelector('#deletemsgs'); del.parentNode.appendChild(document.createElement('br')); del.parentNode.appendChild(selRead); del.parentNode.appendChild(selDupe); } else if (location.pathname === "/my/stories/") { pageLoad(() => { let adventures = document.querySelectorAll('#stories tr'); if (adventures.length > 0) { adventures.forEach(story => { let buttons = story.querySelectorAll('input.major'); let id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', ''); if (id) { addLink(buttons[0], `/my/stories/info/${id}`); addLink(buttons[1], `/my/stories/pages/${id}`); } }); return true; } }); let guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ]; let links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"]; let authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"]; let parentTd = document.querySelector('.container > tbody > tr:last-child > td'); let unofficial = parentTd.querySelector('span'); unofficial.textContent = "Unofficial Guides"; let guideTable = document.createElement('table'); let guideTbody = document.createElement('tbody'); guideTable.style.width = "100%"; guideTable.style.textAlign = "center"; guideTable.appendChild(guideTbody); parentTd.appendChild(guideTable); for (let i = 0; i < guides.length; i++) { let guideTr = guideTbody.insertRow(i); let guideTd = guideTr.insertCell(0); let guideLink = document.createElement('a'); guideLink.href = links[i]; guideLink.textContent = guides[i]; guideLink.className = "major"; guideTd.appendChild(guideLink); guideTd.appendChild(document.createElement('br')); guideTd.appendChild(document.createTextNode('by '+authors[i])); guideTd.appendChild(document.createElement('br')); guideTd.appendChild(document.createElement('br')); } } else if (location.pathname === "/my/stories/info/" && location.search) { addLink(document.querySelector('#userfavs'), `/readers/${location.search}`); addLink(document.querySelector('#editpages'), `/my/stories/pages/${location.search}`); } else if (location.pathname === "/my/stories/pages/" && location.search) { addLink(document.querySelector('#editinfo'), `/my/stories/info/${location.search}`); } else if (location.pathname === "/user/") { pageLoad(() => { let msgButton = document.querySelector('#sendmsg'); if (msgButton) { addLink(msgButton, '/my/messages/new/'); // note: doesn't input the desired user's id addLink(document.querySelector('#favstories'), `/favs/${location.search}`); } }); pageLoad(() => { if (window.MSPFA) { window.MSPFA.request(0, { do: "user", u: location.search.slice(3) }, user => { if (typeof user !== "undefined") { let stats = document.querySelector('#userinfo table'); let joinTr = stats.insertRow(1); let joinTextTd = joinTr.insertCell(0); joinTextTd.appendChild(document.createTextNode("Account created:")); let d = new Date(user.d).toString().split(' ').splice(1, 4).join(' '); let joinDate = joinTr.insertCell(1); let joinTime = document.createElement('b'); joinTime.appendChild(document.createTextNode(d)); joinDate.appendChild(joinTime); } }, status => { console.log(status); }, true); return true; } }); } else if (location.pathname === "/favs/" && location.search) { pageLoad(() => { let stories = document.querySelectorAll('#stories tr'); if (stories.length > 0) { stories.forEach(story => { let id = story.querySelector('a').href.replace('https://mspfa.com/', ''); pageLoad(() => { if (window.MSPFA.me.i) { addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`); return; } }); addLink(story.querySelector('.rss.major'), `/rss/${id}`); }); return true; } }); } else if (location.pathname === "/search/" && location.search) { let pages = document.querySelector('#pages'); let statTable = document.createElement('table'); let statTbody = document.createElement('tbody'); let statTr = statTbody.insertRow(0); let charCount = statTr.insertCell(0); let wordCount = statTr.insertCell(0); let statParentTr = pages.parentNode.parentNode.insertRow(2); let statParentTd = statParentTr.insertCell(0); let statHeaderTr = statTbody.insertRow(0); let statHeader = document.createElement('th'); statHeader.colSpan = '2'; statHeaderTr.appendChild(statHeader); statHeader.textContent = 'Statistics may not be entirely accurate.'; statTable.style.width = "100%"; charCount.textContent = "Character count: loading..."; wordCount.textContent = "Word count: loading..."; statTable.appendChild(statTbody); statParentTd.appendChild(statTable); pageLoad(() => { if (document.querySelector('#pages br')) { let bbc = window.MSPFA.BBC.slice(); bbc.splice(0, 3); window.MSPFA.request(0, { do: "story", s: location.search.replace('?s=', '') }, story => { if (typeof story !== "undefined") { let pageContent = []; story.p.forEach(p => { pageContent.push(p.c); pageContent.push(p.b); }); let storyText = pageContent.join(' ') .replace(/\n/g, ' ') .replace(bbc[0][0], '$1') .replace(bbc[1][0], '$1') .replace(bbc[2][0], '$1') .replace(bbc[3][0], '$1') .replace(bbc[4][0], '$2') .replace(bbc[5][0], '$3') .replace(bbc[6][0], '$3') .replace(bbc[7][0], '$3') .replace(bbc[8][0], '$3') .replace(bbc[9][0], '$3') .replace(bbc[10][0], '$2') .replace(bbc[11][0], '$1') .replace(bbc[12][0], '$3') .replace(bbc[13][0], '$3') .replace(bbc[14][0], '') .replace(bbc[16][0], '$1') .replace(bbc[17][0], '$2 $4 $5') .replace(bbc[18][0], '$2 $4 $5') .replace(bbc[19][0], '') .replace(bbc[20][0], '') .replace(/<(.*?)>/g, ''); wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`; charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`; } }, status => { console.log(status); }, true); return true; } }); } })();