MSPFA extras

Adds custom features to MSPFA.

As of 07.08.2020. See ბოლო ვერსია.

// ==UserScript==
// @name         MSPFA extras
// @namespace    http://tampermonkey.net/
// @version      1.5.1
// @description  Adds custom features to MSPFA.
// @author       seymour schlong
// @icon         https://raw.githubusercontent.com/GrantGryczan/MSPFA/master/www/images/ico.svg
// @icon64       https://raw.githubusercontent.com/GrantGryczan/MSPFA/master/www/images/ico.svg
// @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.5.1";
    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                   - February 23rd, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes                    - February 23rd, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates        - February 23rd, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates             - February 23rd, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - July 21st, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count        - July 21st, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values          - August 7th, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers             - August 7th, 2020
    *
    * Note: At this point, I think I've done pretty much all that I /can/ do from the Github list. However, that doesn't mean I'll stop working on this.
    *       So long as people have ideas for features to add, I can continue adding things.
    *
    * Extension to-do... maybe...
    *
    * If trying to save a page and any other save button is not disabled, ask the user if they would rather Save All instead, or prompt to disable update notifications.
    */

    // 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 = {};
    let defaultSettings = {
        autospoiler: false,
        style: 0,
        styleURL: "",
        night: false,
        auto502: true,
        textFix: false,
        pixelFix: false,
        intro: false,
        autoUpdate: true,
        version: currentVersion,
        spoilerValues: {}
    }

    // Load any previous settings from localStorage
    if (localStorage.mspfaextra) {
        settings = JSON.parse(localStorage.mspfaextra);
    }

    // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
    Object.assign(defaultSettings, settings);
    saveData(settings);

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

            // Add "Reply" button to comment gear
            document.body.addEventListener('click', evt => {
                if (evt.toElement.classList.contains('gear')) {
                    let userID = evt.path[2].classList[2].replace('u', '');
                    let reportButton = document.querySelector('#dialog button[data-value="Report"]');
                    let replyButton = document.createElement('button');
                    replyButton.classList.add('major');
                    replyButton.type = 'submit';
                    replyButton.setAttribute('data-value', 'Reply');
                    replyButton.textContent = 'Reply';
                    replyButton.style = 'margin-right: 9px';
                    reportButton.parentNode.insertBefore(replyButton, reportButton);

                    replyButton.addEventListener('click', evt => {
                        document.querySelector('#dialog button[data-value="Cancel"]').click();
                        let commentBox = document.querySelector('#commentbox textarea');
                        commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
                        commentBox.focus();
                    });
                } else {
                    return;
                }
            });/**/
        }
    }
    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) {
        let adventureID = /\?s=(\d{5})/.exec(location.search)[1];

        // Button links
        addLink(document.querySelector('#editinfo'), `/my/stories/info/${adventureID}`);

        // Default spoiler values
        let replaceButton = document.querySelector('#replaceall');
        let spoilerButton = document.createElement('input');
        spoilerButton.classList.add('major');
        spoilerButton.value = 'Default Spoiler Values';
        spoilerButton.type = 'button';
        replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
        replaceButton.parentNode.insertBefore(document.createElement('br'), replaceButton);
        replaceButton.parentNode.insertBefore(document.createElement('br'), replaceButton);

        let spoilerSpan = document.createElement('span');
        let spoilerOpen = document.createElement('input');
        let spoilerClose = document.createElement('input');
        spoilerSpan.appendChild(document.createTextNode('Open button text:'));
        spoilerSpan.appendChild(document.createElement('br'));
        spoilerSpan.appendChild(spoilerOpen);
        spoilerSpan.appendChild(document.createElement('br'));
        spoilerSpan.appendChild(document.createElement('br'));
        spoilerSpan.appendChild(document.createTextNode('Close button text:'));
        spoilerSpan.appendChild(document.createElement('br'));
        spoilerSpan.appendChild(spoilerClose);

        if (!settings.spoilerValues[adventureID]) {
            settings.spoilerValues[adventureID] = {
                open: 'Show',
                close: 'Hide'
            }
        }

        spoilerOpen.value = settings.spoilerValues[adventureID].open;
        spoilerClose.value = settings.spoilerValues[adventureID].close;

        spoilerButton.addEventListener('click', evt => {
            window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => {
                if (output === 'Save') {
                    settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value;
                    settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value;
                    if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') {
                        delete settings.spoilerValues[adventureID];
                    }
                    saveData(settings);
                }
            });
        });

        document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => {
            document.querySelector('#dialog input[name="open"]').value = settings.spoilerValues[adventureID].open;
            document.querySelector('#dialog input[name="close"]').value = settings.spoilerValues[adventureID].close;
        });

        // Buttonless spoilers
        let flashButton = document.querySelector('input[title="Flash');
        let newSpoilerButton = document.createElement('input');
        newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
        newSpoilerButton.title = 'Buttonless Spoiler';
        newSpoilerButton.type = 'button';
        newSpoilerButton.style = 'background-image: url("https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/HTML%20and%20CSS%20Things/icons.png"); background-position: -66px -88px;';

        newSpoilerButton.addEventListener('click', evt => {
            let bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
            if (bbe) {
                bbe.focus();
                let start = bbe.selectionStart;
                let end = bbe.selectionEnd;
                bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end);
                bbe.selectionStart = start + 26;
                bbe.selectionEnd = end + 26;
            }
        });

        flashButton.parentNode.insertBefore(newSpoilerButton, flashButton);

        // Open preview in new tab with middle mouse
        document.body.addEventListener('mouseup', evt => {
            if (evt.toElement.value === "Preview" && evt.button === 1) {
                evt.toElement.click();
                return false;
            }
        });
    }
    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;
            }
        });
    }
})();