MSPFA extras

Adds custom features to MSPFA.

目前為 2020-08-06 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MSPFA extras
// @namespace    http://tampermonkey.net/
// @version      1.4
// @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';

    const currentVersion = "1.4";
    console.log(`MSPFA extras script v${currentVersion} by seymour schlong`);

    /**
    * 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. maybe when a dialog appears instead?)
    * 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 checkbox 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);
    };

    // 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);
    };

    // Returns true if version 2 is newer
    const compareVer = (ver1, ver2) => {
        ver1 = ver1.split(/\./); // current version
        ver2 = ver2.split(/\./); // new version
        ver1.push(0);
        ver2.push(0);
        if (parseInt(ver2[0]) > parseInt(ver1[0])) { // 1.x.x
            return true;
        } else if (parseInt(ver2[1]) > parseInt(ver1[1])) { // x.1.x
            return true;
        } else if (parseInt(ver2[2]) > parseInt(ver1[2])) { // x.x.1
            return true;
        }
        return false;
    }

    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;
        settings.textFix = false;
        settings.pixelFix = false;
        settings.intro = false;
        settings.autoUpdate = true;
        settings.version = currentVersion;
        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;
    }
    if (typeof settings.intro === "undefined") {
        settings.intro = false;
    }
    if (typeof settings.autoUpdate === "undefined") {
        settings.autoUpdate = true;
    }
    if (typeof settings.version === "undefined") {
        settings.version = currentVersion;
    }

    // Update saved version to the version used in the script to prevent unnecessary notifications
    if (compareVer(settings.version, currentVersion)) {
        settings.version = currentVersion;
        saveData(settings);
    }

    window.MSPFAe = {}
    window.MSPFAe.getSettings = () => {
        return settings;
    }
    window.MSPFAe.getSettingsString = () => {
        console.log(JSON.stringify(settings));
    }
    window.MSPFAe.changeSettings = (newSettings) => {
        console.log('Settings updated');
        console.log(settings);
        Object.assign(settings, newSettings);
        saveData(settings);
    }

    //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'];

    // Dropdown menu
    let myLink = document.querySelector('nav a[href="/my/"]');
    if (myLink) {
        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';
        });

        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);
        }

        // 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;
                }
            }
        });
    }

    // Error reloading
    window.addEventListener("load", () => {
        // Reload the page if 502 CloudFlare error page appears
        if (settings.auto502 && document.querySelector('.cf-error-overview')) {
            window.location.reload();
        }

        // Wait five seconds, then refresh the page
        if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") {
            setTimeout(() => {
                window.location.reload();
            }, 5000);
        }
    });

    // Message that shows when you first get the script
    const showIntroDialog = () => {
        let msg = window.MSPFA.parseBBCode('Hi! Thanks for installing this script!\n\nBe sure to check the [url=https://greasyfork.org/en/scripts/396798-mspfa-extras#additional-info]GreasyFork[/url] page to see a full list of features, and don\'t forget to check out your [url=https://mspfa.com/my/settings/#extraSettings]settings[/url] page to tweak things to how you want.\n\nIf you have any suggestions, or you find a bug, please be sure to let me know on Discord at [url=discord://discordapp.com/users/277928549866799125]@seymour schlong#3669[/url].\n\n[size=12]This dialog will only appear once. To view it again, click "View Script Message" at the bottom of the site.[/size]');
        window.MSPFA.dialog("MSPFA extras message", msg, ["Okay"]);
    }

    // Check for updates by comparing currentVersion to text data from an adventure that has update text and info
    const checkForUpdates = (evt) => {
        window.MSPFA.request(0, {
            do: "story",
            s: "36596"
        }, story => {
            if (typeof story !== "undefined") {
                let ver = settings.version.split(/\./);
                let newVer = story.p[1].c.split(/\./);
                // compare versions
                if (compareVer(settings.version, story.p[1].c) || (evt && evt.type === 'click')) {
                    let msg = window.MSPFA.parseBBCode(story.p[1].b);
                    settings.version = story.p[1].c;
                    saveData(settings);
                    window.MSPFA.dialog(`MSPFA extras update! (${story.p[1].c})`, msg, ["Opt-out", "Dismiss", "Update"], (output, form) => {
                        if (output === "Update") {
                            window.open('https://greasyfork.org/en/scripts/396798-mspfa-extras', '_blank').focus();
                        } else if (output === "Opt-out") {
                            settings.autoUpdate = false;
                            saveData(settings);
                        }
                    });
                }
            }
        });
    };


    // Check for updates and show intro dialog if needed
    pageLoad(() => {
        if (window.MSPFA) {
            if (settings.autoUpdate) {
                checkForUpdates();
            }

            if (!settings.intro) {
                showIntroDialog();
                settings.intro = true;
                saveData(settings);
            }
            return true;
        }
    });

    let details = document.querySelector('#details');
    // Add 'link' at the bottom to show the intro dialog again
    let introLink = document.createElement('a');
    introLink.textContent = 'View Script Message';
    introLink.style = 'cursor: pointer; color: #00E; text-decoration: underline;';
    introLink.className = 'intro-link';
    introLink.addEventListener('click', showIntroDialog);
    details.appendChild(introLink);

    // vbar!!!!
    let vbar = document.createElement('span');
    vbar.className = 'vbar';
    vbar.style = 'padding: 0 5px';
    vbar.textContent = '|';
    details.appendChild(vbar);

    // Add 'link' at the bottom to show the update dialog again
    let updateLink = document.createElement('a');
    updateLink.textContent = 'View Update';
    updateLink.style = 'cursor: pointer; color: #00E; text-decoration: underline;';
    updateLink.className = 'intro-link';
    updateLink.addEventListener('click', checkForUpdates);
    details.appendChild(updateLink);

    // Theme stuff
    let theme = document.createElement('link');
    Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' });
    const updateTheme = (src) => {
        theme.href = src;
    }
    if (!document.querySelector('#theme') && !/^\/css\/|^\/js\//.test(location.pathname)) {
        document.querySelector('head').appendChild(theme);
        if (settings.night) {
            updateTheme('/css/?s=36237');
        } else {
            updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
        }
    }

    // Dropdown menu and pixelated scaling
    let dropStyle = document.createElement('style');
    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;}`;
    if (!document.querySelector('#dropdown-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;}';

        document.querySelector('head').appendChild(dropStyle);
    }

    // Remove the current theme if the adventure has CSS (to prevent conflicts);
    pageLoad(() => {
        if (window.MSPFA) {
            if (window.MSPFA.story && window.MSPFA.story.y && window.MSPFA.story.y.length > 0) {
                updateTheme('');
            }
            return true;
        }
    });

    // Enabling night mode.
    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/") {
        // Automatic spoiler opening
        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) {
            // Show creation date
            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;
                }
            });
            // Attempt to fix text errors
            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 = [
                            ["&acirc;��", "'"],
                            ["&Atilde;�", "Ñ"],
                            ["&Atilde;&plusmn;", "ñ"],
                            ["&Atilde;&sup3;", "ó"],
                            ["&Atilde;&iexcl;", "á"],
                            ["&Atilde;&shy;", "í"],
                            ["&Atilde;&ordm;", "ú"],
                            ["&Atilde;&copy;", "é"],
                            ["&Acirc;&iexcl;", "¡"],
                            ["&Acirc;&iquest;", "¿"],
                            ["N&Acirc;&ordm;", "#"]
                        ];
                        // 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;
                    }
                });
            }
            // Turn buttons into links
            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;
                }
            });
        }
    }
    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');
        headerTr.id = "extraSettings";
        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);

        plusTable.style = "text-align: center;";

        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);

        spoilerTextTd.textContent = "Automatically open spoilers:";
        spoilerInput.type = "checkbox";
        spoilerInput.checked = settings.autospoiler;

        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);

        errorTextTd.textContent = "Automatically reload Cloudflare 502 error pages:";
        errorInput.type = "checkbox";
        errorInput.checked = settings.auto502;

        let updateTr = plusTbody.insertRow(plusTbody.childNodes.length);
        let updateTextTd = updateTr.insertCell(0);
        let updateInputTd = updateTr.insertCell(1);
        let updateInput = document.createElement('input');
        updateInputTd.appendChild(updateInput);

        updateTextTd.textContent = "Automatically check for updates:";
        updateInput.type = "checkbox";
        updateInput.checked = settings.autoUpdate;

        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);

        pixelFixTextTd.textContent = "Change pixel scaling to nearest neighbour:";
        pixelFixInput.type = "checkbox";
        pixelFixInput.checked = settings.pixelFix;

        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);

        textFixTextTd.textContent = "Attempt to fix text errors (experimental)*:";
        textFixInput.type = "checkbox";
        textFixInput.checked = settings.textFix;

        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);

        cssTextTd.textContent = "Change style:";

        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);

        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

        saveTr.parentNode.insertBefore(headerTr, saveTr);
        saveTr.parentNode.insertBefore(settingsTr, saveTr);
        saveTr.parentNode.insertBefore(moreTr, saveTr);
        cssSelect.selectedIndex = settings.style;

        // Add event listeners
        plusTbody.querySelectorAll('input, select').forEach(elm => {
            elm.addEventListener("change", () => {
                saveBtn.disabled = false;
            });
        });

        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.autoUpdate = updateInput.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/") {
        // Add links to buttons
        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;
            }
        });

        // Add user guides
        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) {
        // Button links
        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) {
        // Button links
        addLink(document.querySelector('#editinfo'), `/my/stories/info/${location.search}`);
    }
    else if (location.pathname === "/user/") {
        let id = location.search.slice(3);
        const statAdd = [];
        // Button links
        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}`);
                return true;
            }
        });

        // Add extra user stats
        pageLoad(() => {
            if (window.MSPFA) {
                let stats = document.querySelector('#userinfo table');

                let joinTr = stats.insertRow(1);
                let joinTextTd = joinTr.insertCell(0);
                joinTextTd.appendChild(document.createTextNode("Account created:"));
                let joinDate = joinTr.insertCell(1);
                let joinTime = document.createElement('b');
                joinTime.textContent = "Loading...";
                joinDate.appendChild(joinTime);

                let advCountTr = stats.insertRow(2);
                let advTextTd = advCountTr.insertCell(0);
                advTextTd.appendChild(document.createTextNode("Adventures created:"));
                let advCount = advCountTr.insertCell(1);
                let advCountText = document.createElement('b');
                advCountText.textContent = "Loading...";
                advCount.appendChild(advCountText);

                if (statAdd.indexOf('date') === -1) {
                    window.MSPFA.request(0, {
                        do: "user",
                        u: id
                    }, user => {
                        if (typeof user !== "undefined") {
                            statAdd.push('date');
                            let d = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
                            joinTime.textContent = d;
                        }
                    });
                }

                if (statAdd.indexOf('made') === -1) {
                    window.MSPFA.request(0, {
                        do: "editor",
                        u: id
                    }, s => {
                        if (typeof s !== "undefined") {
                            statAdd.push('made');
                            advCountText.textContent = s.length;
                        }
                    });
                }

                if (document.querySelector('#favstories').style.display !== 'none' && statAdd.indexOf('fav') === -1) {
                    statAdd.push('fav');
                    let favCountTr = stats.insertRow(3);
                    let favTextTd = favCountTr.insertCell(0);
                    favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
                    let favCount = favCountTr.insertCell(1);
                    let favCountText = document.createElement('b');
                    favCountText.textContent = "Loading...";
                    window.MSPFA.request(0, {
                        do: "favs",
                        u: id
                    }, s => {
                        if (typeof s !== "undefined") {
                            favCountText.textContent = s.length;
                        }
                    });
                    favCount.appendChild(favCountText);
                }

                return true;
            }
        });
    }
    else if (location.pathname === "/favs/" && location.search) {
        // Button links
        pageLoad(() => {
            let stories = document.querySelectorAll('#stories tr');
            let favCount = 0;

            if (stories.length > 0) {
                stories.forEach(story => {
                    favCount++;
                    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}`);
                });

                // Fav count
                let username = document.querySelector('#username');
                username.parentNode.appendChild(document.createElement('br'));
                username.parentNode.appendChild(document.createElement('br'));
                username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`));

                return true;
            }
        });
    }
    else if (location.pathname === "/search/" && location.search) {
        // Character and word statistics
        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 = document.querySelector('#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}`;
                    }
                });
                return true;
            }
        });
    }
})();