MSPFA extras

Adds custom quality of life features to MSPFA.

目前為 2020-10-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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.7.2
// @description  Adds custom quality of life features to MSPFA.
// @author       seymour schlong
// @icon         https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
// @icon64       https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
// @match        https://mspfa.com/
// @match        https://mspfa.com/*/
// @match        https://mspfa.com/*/?*
// @match        https://mspfa.com/?s=*
// @match        https://mspfa.com/my/*
// @match        https://mspfa.com/random/
// @exclude      https://mspfa.com/js/*
// @exclude      https://mspfa.com/css/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

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

    const debug = false;

    /**
    * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true
    * Github to-do completion list (and other stuff too)
    *
    * 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
    * https://github.com/GrantGryczan/MSPFA/issues/52 - Hash URLs                       - August 8th, 2020
    *                                                 - Page drafts                     - August 8th, 2020
    *                                                 - Edit pages button               - August 8th, 2020
    *                                                 - Image preloading                - August 20th, 2020
    * https://github.com/GrantGryczan/MSPFA/issues/19 - Manage game saves               - August 22nd, 2020
    *
    * 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.
    *     When adding a new page, store it in an array and if that array length is > 1 when someone tries to save, prompt them to press Save All?
    */

    // A general function that allows for waiting until a certain element appears on the page.
    const pageLoad = (fn, length) => {
        const interval = setInterval(() => {
            if (fn()) clearInterval(interval);
        }, length ? length*1000 : 500);
    };

    // Saves the options data for the script.
    const saveData = (data) => {
        localStorage.mspfaextra = JSON.stringify(data);
        if (debug) {
            console.log('Settings:');
            console.log(data);
        }
    };

    // Saves the data for drafts
    const saveDrafts = (data) => {
        localStorage.mspfadrafts = JSON.stringify(data);
        if (debug) {
            console.log('Drafts:');
            console.log(data);
        }
    };

    // Encases an element within a link
    const addLink = (elm, url, target) => {
        const link = document.createElement('a');
        link.href = url;
        link.draggable = false;
        if (elm.parentNode) elm.parentNode.insertBefore(link, elm);
        if (target) link.target = target;
        link.appendChild(elm);
        return link;
    };

    // Easy br element
    const newBr = () => {
        return document.createElement('br');
    }

    // Make creating label elements easier
    const createLabel = (text, id) => {
        const newLabel = document.createElement('label');
        newLabel.textContent = text;
        newLabel.setAttribute('for', id);
        return newLabel;
    }

    let settings = {};
    let drafts = {};

    const defaultSettings = {
        autospoiler: false,
        style: 0,
        styleURL: "",
        night: false,
        auto502: true,
        textFix: false,
        pixelFix: false,
        intro: false,
        commandScroll: false,
        preload: true,
        dialogKeys: true,
        dialogFocus: false,
        spoilerValues: {}
    }

    let pageLoaded = false;

    const loadDrafts = () => {
        if (localStorage.mspfadrafts) {
            drafts = JSON.parse(localStorage.mspfadrafts);
        }
    }
    loadDrafts();

    // Load any previous settings from localStorage
    if (localStorage.mspfaextra) {
        Object.assign(settings, JSON.parse(localStorage.mspfaextra));

        // Get draft data from settings
        if (typeof settings.drafts === "object") {
            if (Object.keys(settings.drafts).length > 0 && Object.keys(drafts).length === 0) {
                drafts = settings.drafts;
            }
        }
        saveDrafts(drafts);
    }

    // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
    const checkSettings = () => {
        const defaultSettingsKeys = Object.keys(defaultSettings);
        for (let i = 0; i < defaultSettingsKeys.length; i++) {
            if (typeof settings[defaultSettingsKeys[i]] === "undefined") {
                settings[defaultSettingsKeys[i]] = defaultSettings[defaultSettingsKeys[i]];
            }
        }
        saveData(settings);
    }

    checkSettings();

    if (GM_info && GM_info.scriptHandler !== "Tampermonkey" && !settings.warned) {
        alert(`It appears that you're running the MSPFA extras script with ${GM_info.scriptHandler}.\nUnfortunately, this script cannot run at its full potential because of that.\nTry switching to Tampermonkey if you want to use more of the features!\n(this message will only appear once.)`);
        settings.warned = true;
        saveData(settings);
    }

    // Scrolls you to where you need to be
    const hashSearch = location.href.replace(location.origin + location.pathname, '').replace(location.search, '');
    if (hashSearch !== '') {
        pageLoad(() => {
            const idElement = document.querySelector(hashSearch);
            if (idElement) {
                const selected = document.querySelector(hashSearch);
                selected.scrollIntoView();
                selected.style.outline = '3px solid black';
                selected.style.transition = '0.5s';
                pageLoad(() => {
                    if (pageLoaded) {
                        selected.style.outline = '0px solid black';
                    }
                });

                return true;
            }
        }, 1);
    }

    // Ripped shamelessly right from mspfa lol (URL search parameters -- story ID, page num, etc.)
    let rawParams;
    if (location.href.indexOf("#") != -1) {
        rawParams = location.href.slice(0, location.href.indexOf("#"));
    } else {
        rawParams = location.href;
    }
    if (rawParams.indexOf("?") != -1) {
        rawParams = rawParams.slice(rawParams.indexOf("?") + 1).split("&");
    } else {
        rawParams = [];
    }
    const params = {};
    for (let i = 0; i < rawParams.length; i++) {
        try {
            const p = rawParams[i].split("=");
            params[p[0]] = decodeURIComponent(p[1]);
        } catch (err) {}
    }

    if (debug) {
        console.log('URL parameters:');
        console.log(params);
    }

    // Functions to get/change data from the console
    window.MSPFAe = {
        getSettings: () => {
            return settings;
        },
        getSettingsString: (formatted) => {
            if (formatted) {
                console.log(JSON.stringify(settings, null, 4));
            } else {
                console.log(JSON.stringify(settings));
            }
        },
        getDrafts: () => {
            loadDrafts();
            return drafts;
        },
        getDraftsString: (formatted) => {
            loadDrafts();
            if (formatted) {
                console.log(JSON.stringify(drafts, null, 4));
            } else {
                console.log(JSON.stringify(drafts));
            }
        },
        changeSettings: (newSettings) => {
            console.log('Settings updated');
            console.log(settings);
            Object.assign(settings, newSettings);
            saveData(settings);
        },
        changeSettingsString: (fullString) => {
            try {
                JSON.parse(fullString);
            } catch (err) {
                console.error(err);
                return;
            }
            settings = JSON.parse(fullString);
            checkSettings();
            console.log(settings);
        },
        getParams: params
    }

    // Error reloading
    window.addEventListener("load", () => {
        // Reload the page if 502 CloudFlare error page appears
        if (settings.auto502 && document.querySelector('#cf-wrapper')) {
            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);
        }

        pageLoaded = true;
    });

    // Delete any unchanged spoiler values
    if (location.pathname !== "/my/stories/pages/") {
        // Go through spoiler values and remove any that aren't unique
        Object.keys(settings.spoilerValues).forEach(adventure => {
            if (settings.spoilerValues[adventure].open === "Show" && settings.spoilerValues[adventure].close === "Hide") {
                delete settings.spoilerValues[adventure];
            } else if (settings.spoilerValues[adventure].open === '' && settings.spoilerValues[adventure].close === '') {
                delete settings.spoilerValues[adventure];
            }
        });
    }

    const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
    const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', 'https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/themes/dark.css', '/css/theme4.css', '/css/theme5.css'];

    const createDropdown = (parent, explore) => {
        const dropDiv = document.createElement('div');
        dropDiv.className = 'dropdown';
        dropDiv.style.display = 'inline-block';

        const dropContent = document.createElement('div');
        dropContent.className = 'dropdown-content';
        dropContent.style.display = 'none';

        if (!explore) {
            dropDiv.addEventListener('mouseenter', evt => {
                dropContent.style.display = 'block';
                dropContent.style.color = getComputedStyle(parent).color;
                dropContent.querySelectorAll('a').forEach(link => {
                    link.style.color = getComputedStyle(parent).color;
                });
            });
            dropDiv.addEventListener('mouseleave', evt => {
                dropContent.style.display = 'none';
            });
        }

        parent.parentNode.insertBefore(dropDiv, parent);
        dropDiv.appendChild(parent);
        dropDiv.appendChild(dropContent);
        return [dropDiv, dropContent];
    }

    // "MY MSPFA" dropdown
    const myLink = document.querySelector('nav a[href="/my/"]');
    if (myLink) {
        const dropContent = createDropdown(myLink)[1];

        const dLinks = [];
        dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ];
        dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ];

        for (let i = 0; i < dLinks[0].length; i++) {
            const 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) {
                    if (settings.dropFav) {
                        const newFavLink = document.createElement('a');
                        newFavLink.textContent = "My Favourites";
                        newFavLink.href = `/favs/?u=${window.MSPFA.me.i}`;
                        dropContent.appendChild(newFavLink);
                    }
                    const newMyLink = document.createElement('a');
                    newMyLink.textContent = "My Profile";
                    newMyLink.href = `/user/?u=${window.MSPFA.me.i}`;
                    dropContent.appendChild(newMyLink);

                    // Move SETTINGS to the bottom
                    dropContent.appendChild(dropContent.querySelectorAll('a')[2]);
                    return true;
                }
            }
        });
    }

    // "RANDOM" dropdown
    const randomLink = document.querySelector('nav a[href="/random/"]');
    if (randomLink) {
        // Thank you @MadCreativity 🙏
        const dropContent = createDropdown(randomLink)[1];

        (async () => {
            const dLinks = [];
            dLinks[0] = [ 'Recent ongoing' ];
            dLinks[1] = [ await fetch(`https://mspfa-extras-server.herokuapp.com/api/random`).then(e => e.text()) ];

            for (let i = 0; i < dLinks[0].length; i++) {
                const newLink = document.createElement('a');
                newLink.textContent = dLinks[0][i];
                newLink.href = dLinks[1][i];
                dropContent.appendChild(newLink);
            }
        })()
    }

    // "EXPLORE" dropdown
    const exploreLink = document.querySelector('nav a[href="/stories/"');
    if (exploreLink) {
        const dropdown = createDropdown(exploreLink, true);
        const dropDiv = dropdown[0];
        const dropContent = dropdown[1];

        const exploreInput = document.createElement('input');
        Object.assign(exploreInput, { type: 'text', placeholder: 'Search...', id: 'dropdown-explore' });
        dropContent.appendChild(exploreInput);
        exploreInput.addEventListener('keydown', ke => {
            if (ke.code === 'Enter') {
                const searchLink = `/stories/?go=1&n=${encodeURIComponent(exploreInput.value)}&t=&h=14&o=favs&p=p&m=50&load=true`;
                if (ke.altKey || ke.ctrlKey) {
                    window.open(searchLink, '_blank').focus();
                } else {
                    location.href = searchLink;
                }
                return;
            }
        });

        dropDiv.addEventListener('mouseenter', evt => {
            dropContent.style.display = 'block';
        });
        dropDiv.addEventListener('mouseleave', evt => {
            // If input is focused
            if (document.activeElement !== exploreInput) {
                dropContent.style.display = 'none';
            }
        });
        document.body.addEventListener('click', evt => {
            if (document.activeElement !== exploreInput) {
                dropContent.style.display = 'none';
            }
        });
    }

    document.querySelector('header .mspfalogo').parentNode.draggable = false;
    addLink(document.querySelector('footer .mspfalogo'), 'javascript:void(0);');

    // Message that shows when you first get the script
    const showIntroDialog = () => {
        const 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 if show intro dialog has displayed
    if (!settings.intro) {
        pageLoad(() => {
            if (window.MSPFA) {
                showIntroDialog();
                settings.intro = true;
                saveData(settings);
                return true;
            }
        });
    }

    const details = document.querySelector('#details');

    // Add 'link' at the bottom to show the intro dialog again
    const introLink = document.createElement('a');
    introLink.textContent = 'View Script Message';
    introLink.href = 'javascript:void(0);';
    introLink.addEventListener('click', showIntroDialog);
    details.appendChild(introLink);

    // vbar!!!!
    const vbar = document.createElement('span');
    Object.assign(vbar, {className: 'vbar', textContent: '|'});
    details.appendChild(document.createTextNode(' '));
    details.appendChild(vbar);
    details.appendChild(document.createTextNode(' '));

    // if you really enjoy the script and has some extra moneys 🥺
    const donateLink = document.createElement('a');
    donateLink.textContent = 'Donate';
    donateLink.href = 'https://ko-fi.com/ironbean';
    donateLink.target = "blank";
    details.appendChild(donateLink);

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

    const pixelText = () => {
        return settings.pixelFix ? 'body { image-rendering: pixelated; image-rendering: -moz-crisp-edges; }' : '';
    }

    // Dropdown menu and pixelated scaling
    const mspfaeCSS = document.createElement('link');
    Object.assign(mspfaeCSS, { id: 'script-css', type: 'text/css', rel: 'stylesheet', href: 'https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/mspfae.css' });
    document.querySelector('head').appendChild(mspfaeCSS);

    const extraStyle = document.createElement('style');
    if (!document.querySelector('#extra-style')) {
        extraStyle.id = 'extra-style';
        extraStyle.textContent = pixelText();
        document.querySelector('head').appendChild(extraStyle);
    }

    let nightSwitch = [];

    // Enabling night mode.
    document.querySelector('footer .mspfalogo').addEventListener('click', evt => {
        settings.night = !settings.night;
        saveData(settings);

        for (let i = 0; i < nightSwitch.length; i++) {
            clearTimeout(nightSwitch[i]);
        }
        nightSwitch = [];

        // Transition to make it feel nicer on the eyes
        extraStyle.textContent = pixelText();
        extraStyle.textContent = pixelText() + ' *{transition:1.5s;}';

        if (settings.night) {
            updateTheme(styleUrls[3]);
        } else {
            updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
        }

        nightSwitch.push(setTimeout(() => {
            extraStyle.textContent = pixelText();
        }, 1500));
    });

    // Enable keyboard controls for some dialog boxes (enter/esc to accept/close)
    const dialog = document.querySelector('#dialog');
    document.addEventListener('keydown', evt => {
        if (settings.dialogKeys && !dialog.textContent.includes('BBCode')) {
            if (dialog.style.display === '' && (evt.code === 'Enter' || evt.code === "Escape") && (document.activeElement === document.body || settings.dialogFocus)) {
                let buttons = dialog.querySelectorAll('button');
                if (buttons.length === 1) {
                    buttons[0].click();
                } else if (buttons.length === 2) {
                    if (buttons[0].textContent === 'Okay' && evt.code === "Enter") {
                        buttons[0].click();
                    }
                }
                if (["Cancel", "Close"].indexOf(buttons[buttons.length - 1].textContent) !== -1 && evt.code === "Escape") {
                    buttons[buttons.length - 1].click();
                }
            }
        }
    });

    if (location.pathname.includes('//')) {
        location.href = location.pathname.replace(/\/\//g, '/') + location.search;
    }

    if (location.pathname === "/" || location.pathname === "/preview/") {
        if (location.search) {
            // Remove the current theme if the adventure has CSS (to prevent conflicts);
            if (settings.style > 0) {
                pageLoad(() => {
                    if (window.MSPFA) {
                        if (window.MSPFA.story && window.MSPFA.story.y && (window.MSPFA.story.y.toLowerCase().includes('import') || window.MSPFA.story.y.includes('{'))) {
                            if (!settings.night) updateTheme('');
                            return true;
                        }
                    }
                    if (pageLoaded) return true;
                });
            }

            // Preload adjacent pages
            if (settings.preload) {
                const preloadImages = document.createElement('div');
                preloadImages.id = 'preload';
                document.querySelector('#container').appendChild(preloadImages);
                window.MSPFA.slide.push(p => {
                    preloadImages.innerHTML = '';
                    if (window.MSPFA.story.p[p-2]) {
                        let page = window.MSPFA.parseBBCode(window.MSPFA.story.p[p-2].b);
                        page.querySelectorAll('img').forEach(image => {
                            preloadImages.appendChild(image);
                        });
                        page.innerHTML = '';
                    }
                    if (window.MSPFA.story.p[p]) {
                        let page = window.MSPFA.parseBBCode(window.MSPFA.story.p[p].b);
                        page.querySelectorAll('img').forEach(image => {
                            preloadImages.appendChild(image);
                        });
                        page.innerHTML = '';
                    }
                });
            }

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

            // Scroll up to the nav bar when changing page so you don't have to scroll down as much =)
            if (settings.commandScroll) {
                const heightTop = document.querySelector('nav').getBoundingClientRect().top - document.body.getBoundingClientRect().top;
                let temp = -2; // To prevent moving the page down when loading it for the first time
                window.MSPFA.slide.push((p) => {
                    if (temp < 0) {
                        temp++;
                    } else {
                        window.scroll(0, heightTop);
                    }
                });
            }

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

            // Hash scrolling and opening infobox or commmentbox
            if (['#infobox', '#commentbox', '#newcomment', '#latestpages'].indexOf(hashSearch) !== -1) {
                pageLoad(() => {
                    if (document.querySelector(hashSearch)) {
                        if (hashSearch === '#infobox') {
                            document.querySelector('input[data-open="Show Adventure Info"]').click();
                        } else if (hashSearch === '#commentbox' || hashSearch === '#newcomment') {
                            document.querySelector('input[data-open="Show Comments"]').click();
                        } else if (hashSearch === '#latestpages') {
                            document.querySelector('input[data-open="Show Adventure Info"]').click();
                            document.querySelector('input[data-open="Show Latest Pages"]').click();
                        }
                        return true;
                    }
                });
            }

            // Attempt to fix text errors
            if (settings.textFix && location.pathname !== "/preview/") {
                pageLoad(() => {
                    if (window.MSPFA.story && window.MSPFA.story.p) {
                        // russian/bulgarian is not possible =(
                        const currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]);
                        const library = [
                            ["&acirc;��", "'"],
                            ["&Atilde;�", "Ñ"],
                            ["&Atilde;&plusmn;", "ñ"],
                            ["&Atilde;&sup3;", "ó"],
                            ["&Atilde;&iexcl;", "á"],
                            ["&Auml;�", "ą"],
                            ["&Atilde;&shy;", "í"],
                            ["&Atilde;&ordm;", "ú"],
                            ["&Atilde;&copy;", "é"],
                            ["&Aring;�", "ł"],
                            ["&Aring;&frac14;", "ż"],
                            ["&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);
                        });
                        return true;
                    }
                });
            }

            // Turn buttons into links
            const pageButton = document.createElement('button');
            const pageLink = addLink(pageButton, `/my/stories/pages/?s=${params.s}#p${params.p}`);
            pageButton.className = 'pages edit major';
            pageButton.type = 'button';
            pageButton.title = 'Edit Pages';

            // Edit pages button & button link
            pageLoad(() => {
                const infoButton = document.querySelector('.edit.major');
                if (infoButton) {
                    pageLoad(() => {
                        if (window.MSPFA.me.i) {
                            infoButton.title = "Edit Info";
                            infoButton.parentNode.insertBefore(pageLink, infoButton);
                            infoButton.parentNode.insertBefore(document.createTextNode(' '), infoButton);
                            addLink(infoButton, `/my/stories/info/?s=${params.s}`);
                            pageButton.style.display = document.querySelector('.edit.major:not(.pages)').style.display;

                            // Change change page link when switching pages
                            window.MSPFA.slide.push(p => {
                                const newSearch = location.search.split('&p=');
                                pageLink.href = `/my/stories/pages/?s=${params.s}#p${newSearch[1].split('#')[0]}`;
                            });
                            return true;
                        }
                    });
                    addLink(document.querySelector('.rss.major'), `/rss/?s=${params.s}`);
                    return true;
                }
            });

            // Add "Reply" button next to comment gear
            setInterval(() => {
                if (document.querySelector('#commentbox > .spoiler.open')) {
                    document.querySelectorAll('.gear').forEach(gear => {
                        if (!gear.parentNode.querySelector('.reply')) {
                            const replyDiv = document.createElement('div');
                            replyDiv.className = 'reply';
                            gear.insertAdjacentElement('afterEnd', replyDiv);
                            gear.insertAdjacentHTML('afterEnd', '<span style="float: right"> </span>');
                            const userID = gear.parentNode.parentNode.classList[2].replace('u', '');

                            replyDiv.addEventListener('click', () => {
                                const commentBox = document.querySelector('#commentbox textarea');
                                commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
                                commentBox.focus();
                                commentBox.parentNode.scrollIntoView();
                            });
                        }
                    });
                }
            }, 500);
        }
    }
    else if (location.pathname === "/my/") {
        const parent = document.querySelector('#editstories').parentNode;
        const viewSaves = document.createElement('a');
        Object.assign(viewSaves, { id: 'viewsaves', className: 'major', textContent: 'View Adventure Saves' });

        parent.appendChild(viewSaves);
        parent.appendChild(newBr());
        parent.appendChild(newBr());

        pageLoad(() => {
            if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) {
                viewSaves.href = `/?s=36596&p=6`;
                return true;
            }
        });

        document.querySelector('#editstories').classList.remove('alt');
    }
    else if (location.pathname === "/my/settings/") { // Custom settings
        const saveBtn = document.querySelector('#savesettings');

        const table = document.querySelector("#editsettings tbody");
        let saveTr = table.querySelectorAll("tr");
        saveTr = saveTr[saveTr.length - 1];

        const headerTr = document.createElement('tr');
        const header = document.createElement('th');
        Object.assign(header, { id: 'extraSettings', textContent: 'Extra Settings' });
        headerTr.appendChild(header);

        const moreTr = document.createElement('tr');
        const 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);

        const settingsTr = document.createElement('tr');
        const localMsg = document.createElement('span');
        const 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!";
        const plusTable = document.createElement('table');
        const plusTbody = document.createElement('tbody');
        plusTable.appendChild(plusTbody);
        settingsTd.appendChild(localMsg);
        settingsTd.appendChild(newBr());
        settingsTd.appendChild(newBr());
        settingsTd.appendChild(plusTable);
        settingsTr.appendChild(settingsTd);

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

        // Create checkbox (soooo much better)
        const createCheckbox = (text, checked, id) => {
            const optionTr = plusTbody.insertRow(plusTbody.childNodes.length);
            const optionTextTd = optionTr.insertCell(0);
            const optionLabel = createLabel(text, id);
            const optionInputTd = optionTr.insertCell(1);
            const optionInput = document.createElement('input');
            optionInputTd.appendChild(optionInput);

            optionTextTd.appendChild(optionLabel);
            optionInput.type = "checkbox";
            optionInput.checked = checked;
            optionInput.id = id;

            return optionInput;
        }

        const spoilerInput = createCheckbox("Automatically open spoilers:", settings.autospoiler, 'autospoiler');
        const preloadInput = createCheckbox("Preload images for the pages immediately before and after:", settings.preload, 'preload');
        const dropFavInput = createCheckbox("Adds \"My Favourites\" to the dropdown menu:", settings.dropFav, 'dropFav');
        const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", settings.auto502, 'auto502');
        const commandScrollInput = createCheckbox("Scroll back up to the nav bar when switching page:", settings.commandScroll, 'commandScroll');
        const dialogKeysInput = createCheckbox("Use enter/escape keys to accept/exit control some dialogs:", settings.dialogKeys, 'dialogKeys');
        const dialogFocusInput = createCheckbox("Let keys work while dialog isn't focused (above required):", settings.dialogFocus, 'dialogFocus');
        const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", settings.pixelFix, 'pixelFix');
        const textFixInput = createCheckbox("Attempt to fix text errors (experimental)*:", settings.textFix, 'textFix');

        const cssTr = plusTbody.insertRow(plusTbody.childNodes.length);
        const cssTextTd = cssTr.insertCell(0);
        const cssSelectTd = cssTr.insertCell(1);
        const cssSelect = document.createElement('select');
        cssSelectTd.appendChild(cssSelect);

        cssTextTd.textContent = "Change style:";

        const customTr = plusTbody.insertRow(plusTbody.childNodes.length);
        const customTextTd = customTr.insertCell(0);
        const customCssTd = customTr.insertCell(1);
        const 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)));

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

        const buttonSpan = document.createElement('span');
        const draftButton = document.createElement('input');
        const spoilerButton = document.createElement('input');
        draftButton.value = 'Manage Drafts';
        draftButton.className = 'major';
        draftButton.type = 'button';
        spoilerButton.value = 'Manage Spoiler Values';
        spoilerButton.className = 'major';
        spoilerButton.type = 'button';
        buttonSpan.appendChild(draftButton);
        buttonSpan.appendChild(document.createTextNode(' '));
        buttonSpan.appendChild(spoilerButton);
        settingsTd.appendChild(buttonSpan);

        const draftMsg = window.MSPFA.parseBBCode('Here you can manage the drafts that you have saved for your adventure(s).\n');
        const listTable = document.createElement('table');
        listTable.id = 'draft-table';
        const listTbody = document.createElement('tbody');
        listTable.appendChild(listTbody);

        const draftsEmpty = () => {
            loadDrafts();
            let empty = true;
            Object.keys(drafts).forEach(adv => {
                if (empty) {
                    const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1;
                    if (Object.keys(drafts[adv]).length > length) {
                        empty = false;
                    }
                }
            });
            return empty;
        }

        setInterval(() => {
            draftButton.disabled = draftsEmpty();
        }, 1000);

        draftButton.addEventListener('click', () => {
            draftMsg.appendChild(listTable);
            listTbody.innerHTML = '';
            loadDrafts();

            const addAdv = (story, name) => {
                const storyTr = listTbody.insertRow(listTable.rows);
                const titleLink = document.createElement('a');
                Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${story}&click=d`, textContent: name, target: '_blank' });
                storyTr.insertCell(0).appendChild(titleLink);
                const deleteButton = document.createElement('input');
                Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
                storyTr.insertCell(1).appendChild(deleteButton);

                deleteButton.addEventListener('click', () => {
                    setTimeout(() => {
                        window.MSPFA.dialog('Delete adventure draft?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
                            if (output === "Yes") {
                                loadDrafts();
                                drafts[story] = {};

                                if (settings.drafts && settings.drafts[story]) {
                                    delete settings.drafts[story];
                                    saveData(settings);
                                }

                                saveDrafts(drafts);

                                setTimeout(() => {
                                    draftButton.click();
                                }, 1);

                                if (draftsEmpty) {
                                    draftButton.disabled = true;
                                }
                            }
                        });
                    }, 1);
                });
            }

            Object.keys(drafts).forEach(adv => {
                const length = typeof drafts[adv].cachedTitle === "undefined" ? 0 : 1;
                if (Object.keys(drafts[adv]).length > length) {
                    if (!!length) {
                        addAdv(adv, drafts[adv].cachedTitle);
                    }
                    else {
                        window.MSPFA.request(0, {
                            do: "story",
                            s: adv
                        }, story => {
                            if (typeof story !== "undefined") {
                                console.log(story);
                                addAdv(adv, story.n);
                            }
                        });
                    }
                }
            });

            window.MSPFA.dialog('Manage Drafts', draftMsg, ["Delete All", "Close"], (output, form) => {
                if (output === "Delete All") {
                    setTimeout(() => {
                        window.MSPFA.dialog('Delete all Drafts?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
                            if (output === "Yes") {
                                Object.keys(drafts).forEach(adv => {
                                    drafts[adv] = {};
                                });
                                saveDrafts(drafts);

                                if (typeof settings.drafts !== "undefined") {
                                    delete settings.drafts;
                                    saveData(settings);
                                }

                                draftButton.disabled = true;
                            }
                        });
                    }, 1);
                }
            });
        });

        if (Object.keys(settings.spoilerValues).length === 0) {
            spoilerButton.disabled = true;
        }

        const spoilerMsg = window.MSPFA.parseBBCode('Here you can manage the spoiler values that you have set for your adventure(s).\nClick on an adventure\'s title to see the values.\n');

        spoilerButton.addEventListener('click', () => {
            spoilerMsg.appendChild(listTable);
            listTbody.innerHTML = '';
            Object.keys(settings.spoilerValues).forEach(adv => {
                window.MSPFA.request(0, {
                    do: "story",
                    s: adv
                }, story => {
                    if (typeof story !== "undefined") {
                        const storyTr = listTbody.insertRow(listTable.rows);
                        const titleLink = document.createElement('a');
                        Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${adv}&click=s`, textContent: story.n, target: '_blank' });
                        storyTr.insertCell(0).appendChild(titleLink);
                        const deleteButton = document.createElement('input');
                        Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
                        storyTr.insertCell(1).appendChild(deleteButton);

                        deleteButton.addEventListener('click', () => {
                            setTimeout(() => {
                                window.MSPFA.dialog('Delete adventure spoilers?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
                                    if (output === "Yes") {
                                        delete settings.spoilerValues[adv];
                                        saveData(settings);

                                        setTimeout(() => {
                                            spoilerButton.click();
                                        }, 1);

                                        if (Object.keys(settings.spoilerValues).length === 0) {
                                            spoilerButton.disabled = true;
                                        }
                                    }
                                });
                            }, 1);
                        });
                    }
                });
            });
            window.MSPFA.dialog('Manage Spoiler Values', spoilerMsg, ["Delete All", "Close"], (output, form) => {
                if (output === "Delete All") {
                    setTimeout(() => {
                        window.MSPFA.dialog('Delete all Spoiler Values?', 'Are you sure you want to delete all spoiler values?\nThis action cannot be undone!', ["Yes", "No"], (output, form) => {
                            if (output === "Yes") {
                                settings.spoilerValues = {};
                                saveData(settings);
                                spoilerButton.disabled = true;
                            }
                        });
                    }, 1);
                }
            });
        });

        // 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.dialogKeys = dialogKeysInput.checked;
            settings.dialogFocus = dialogFocusInput.checked;
            settings.commandScroll = commandScrollInput.checked;
            settings.preload = preloadInput.checked;
            settings.dropFav = dropFavInput.checked;
            settings.night = false;
            console.log(settings);
            saveData(settings);

            updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);

            extraStyle.textContent = pixelText() + ' *{transition:1s}';

            extraStyle.textContent = pixelText();
            setTimeout(() => {
                    extraStyle.textContent = pixelText();
            }, 1000);
        });
    }
    else if (location.pathname === "/my/messages/") { // New buttons
        // Select all read messages button.
        const selRead = document.createElement('input');
        Object.assign(selRead, { value: 'Select Read', className: 'major', 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');
        Object.assign(selDupe, { value: 'Select Same', className: 'major', type: 'button', style: 'margin-top: 6px' });

        selDupe.addEventListener('mouseup', evt => {
            const temp = document.querySelectorAll('#messages > tr');
            const msgs = [];
            for (let i = temp.length - 1; i >= 0; i--) {
                msgs.push(temp[i]);
            }
            const titles = [];
            msgs.forEach((msg) => {
                const title = msg.querySelector('a.major').textContent;
                // Select only adventure updates
                if (/^New update: /.test(title)) {
                    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();
                    }
                }
            });
        });

        // Prune button
        const pruneButton = document.createElement('input');
        Object.assign(pruneButton, { type: 'button', value: 'Prune', className: 'major' });

        pruneButton.addEventListener('click', () => {
            const ageInput = document.createElement('input');
            Object.assign(ageInput, { type: 'number', min: 1, max: 10, value: 1 });

            const msgState = document.createElement('select');
            ['all', 'all unread', 'all read'].forEach(option => {
                const op = document.createElement('option');
                op.textContent = option;
                msgState.appendChild(op);
            });

            const timeUnit = document.createElement('select');
            ['month(s)', 'week(s)', 'day(s)'].forEach(option => {
                const op = document.createElement('option');
                op.textContent = option;
                timeUnit.appendChild(op);
            });
            timeUnit.childNodes[1].setAttribute('selected', 'selected');

            const msg = document.createElement('span');
            msg.appendChild(document.createTextNode('Prune '));
            msg.appendChild(msgState);
            msg.appendChild(document.createTextNode(' messages older than '));
            msg.appendChild(ageInput);
            msg.appendChild(timeUnit);

            window.MSPFA.dialog('Prune messages', msg, ['Prune', 'Cancel'], (output, form) => {
                if (output === 'Prune') {
                    document.querySelector('#messages').childNodes.forEach(node => {
                        if (node.firstChild.firstChild.checked) {
                            node.firstChild.firstChild.click();
                        }

                        const selectedState = msgState.selectedOptions[0].textContent;
                        const selectedUnit = timeUnit.selectedOptions[0].textContent;

                        if (selectedState === 'all unread') {
                            if (node.firstChild.style.borderLeftColor === 'rgb(221, 221, 221)') {
                                return;
                            }
                        }
                        else if (selectedState === 'all read') {
                            if (node.firstChild.style.borderLeftColor === 'rgb(92, 174, 223)') {
                                return;
                            }
                        }
                        const dateText = node.childNodes[2].childNodes[2].textContent.split(' - ');
                        const messageDate = new Date(dateText[dateText.length-1]);
                        const currentDate = Date.now();
                        const diff = Math.floor(Math.round((currentDate-messageDate)/(1000*60*60))/24); // Difference in days

                        if (selectedUnit === 'month(s)') diff = Math.floor(diff / 30);
                        else if (selectedUnit === 'week(s)') diff = Math.floor(diff / 7);

                        if (diff >= ageInput.value) {
                            node.firstChild.firstChild.click();
                        }
                    });

                    setTimeout(() => {
                        document.querySelector('input[value=Delete]').click();
                    }, 1);
                }
            });
        });

        // Maybe add a "Merge Updates" button?
        // [Merge Updates] would create a list of updates, similar to [Select Same]

        // Add buttons to the page.
        const del = document.querySelector('#deletemsgs');
        del.parentNode.appendChild(newBr());
        del.parentNode.appendChild(selRead);
        del.parentNode.appendChild(document.createTextNode(' '));
        del.parentNode.appendChild(selDupe);
        del.parentNode.appendChild(document.createTextNode(' '));
        del.parentNode.appendChild(pruneButton);

        // Click the green cube to open the update/comment in a new tab, and mark notification as read.
        pageLoad(() => {
            if (document.querySelector('#messages').childNodes.length > 0) {
                if (document.querySelector('#messages').textContent === 'No new messages were found.') {
                    // Disable some buttons if there are no messages.
                    pruneButton.disabled = true;
                    selDupe.disabled = true;
                    return true;
                } else {
                    document.querySelector('#messages').childNodes.forEach(node => {
                        if (node.textContent.includes('New update:') && node.textContent.includes('MS Paint Fan Adventures')) {
                            const link = addLink(node.querySelector('.cellicon'), node.querySelector('.spoiler a').href);
                            link.addEventListener('mouseup', () => {
                                const spoiler = node.querySelector('.spoiler');
                                const button = spoiler.querySelector('input');
                                spoiler.className = 'spoiler closed';
                                button.click();
                                button.click();
                            });
                        }
                        else if (node.textContent.includes('New comment on ') && node.textContent.includes('MS Paint Fan Adventures')) {
                            const link = addLink(node.querySelector('.cellicon'), node.querySelectorAll('.spoiler a')[1].href + '#commentbox');
                            link.addEventListener('mouseup', () => {
                                const spoiler = node.querySelector('.spoiler');
                                const button = spoiler.querySelector('input');
                                spoiler.className = 'spoiler closed';
                                button.click();
                                button.click();
                            });
                        }
                    });
                    return true;
                }
            }
        });
    }
    else if (location.pathname === "/my/messages/new/" && location.search) { // Auto-fill user when linked from a user page
        const recipientInput = document.querySelector('#addrecipient');
        recipientInput.value = params.u;
        pageLoad(() => {
            const recipientButton = document.querySelector('#addrecipientbtn');
            if (recipientButton) {
                recipientButton.click();
                if (recipientInput.value === "") { // If the button press doesn't work
                    return true;
                }
            }
        });
    }
    else if (location.pathname === "/my/stories/") {
        // Add links to buttons
        pageLoad(() => {
            const adventures = document.querySelectorAll('#stories tr');
            if (adventures.length > 0) {
                adventures.forEach(story => {
                    const buttons = story.querySelectorAll('input.major');
                    const 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}`);
                        addLink(story.querySelector('img'), `/${id}&p=1`);
                    }
                });
                return true;
            }
            if (pageLoaded) return true;
        });

        // Add user guides
        const guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ];
        const links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"];
        const authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"];

        const parentTd = document.querySelector('.container > tbody > tr:last-child > td');
        const unofficial = parentTd.querySelector('span');
        unofficial.textContent = "Unofficial Guides";
        const guideTable = document.createElement('table');
        const 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++) {
            const guideTr = guideTbody.insertRow(i);
            const guideTd = guideTr.insertCell(0);
            const guideLink = document.createElement('a');
            Object.assign(guideLink, { href: links[i], textContent: guides[i], className: 'major' });
            guideTd.appendChild(guideLink);
            guideTd.appendChild(newBr());
            guideTd.appendChild(document.createTextNode('by '+authors[i]));
            guideTd.appendChild(newBr());
            guideTd.appendChild(newBr());
        }
    }
    else if (location.pathname === "/my/stories/info/" && location.search) {
        // Button links
        addLink(document.querySelector('#userfavs'), `/readers/?s=${params.s}`);
        addLink(document.querySelector('#editpages'), `/my/stories/pages/?s=${params.s}`);

        // Download adventure data
        if (params.s !== 'new') {
            const downloadButton = document.createElement('input');
            Object.assign(downloadButton, { className: 'major', value: 'Export Data', type: 'button', style: 'margin-top: 6px' });
            const downloadLink = document.createElement('a');
            window.MSPFA.request(0, {
                do: "story",
                s: params.s
            }, (s) => {
                if (s) {
                    downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(s, null, 4)));
                }
            });
            downloadLink.setAttribute('download', `${params.s}.json`);
            downloadLink.appendChild(downloadButton);
            document.querySelector('#savestory').parentNode.appendChild(newBr());
            document.querySelector('#savestory').parentNode.appendChild(downloadLink);
        }
    }
    else if (location.pathname === "/my/stories/pages/" && location.search) {
        const adventureID = params.s;

        const notifyLabel = createLabel('Notify readers of new pages during this editing session: ', 'notifyreaders');
        const notifyButton = document.querySelector('#notifyreaders');
        notifyButton.previousSibling.textContent = '';
        notifyButton.parentNode.insertBefore(notifyLabel, notifyButton);/**/

        if (!drafts[adventureID]) {
            drafts[adventureID] = {};
            saveDrafts(drafts);
        }

        pageLoad(() => {
            if (document.querySelector('#storyname').textContent !== '-') {
                drafts[adventureID].cachedTitle = document.querySelector('#storyname').textContent;
                saveDrafts(drafts);
                return true;
            }
        });

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

        // Default spoiler values
        const replaceButton = document.querySelector('#replaceall');
        const spoilerButton = document.createElement('input');
        Object.assign(spoilerButton, { className: 'major', value: 'Default Spoiler Values', type: 'button'});
        replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
        replaceButton.parentNode.insertBefore(newBr(), replaceButton);
        replaceButton.parentNode.insertBefore(newBr(), replaceButton);

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

        spoilerButton.addEventListener('click', evt => {
            const spoilerSpan = document.createElement('span');
            const spoilerOpen = document.createElement('input');
            const spoilerClose = document.createElement('input');
            spoilerSpan.appendChild(document.createTextNode('Open button text:'));
            spoilerSpan.appendChild(newBr());
            spoilerSpan.appendChild(spoilerOpen);
            spoilerSpan.appendChild(newBr());
            spoilerSpan.appendChild(newBr());
            spoilerSpan.appendChild(document.createTextNode('Close button text:'));
            spoilerSpan.appendChild(newBr());
            spoilerSpan.appendChild(spoilerClose);

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

            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 = document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open;
            document.querySelector('#dialog input[name="close"]').value = document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close;
        });

        // --- Custom BBToolbar buttons
        // Buttonless spoilers
        const flashButton = document.querySelector('input[title=Flash]');
        const newSpoilerButton = document.createElement('input');
        newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
        Object.assign(newSpoilerButton, { title: 'Buttonless Spoiler', type: 'button', style: 'background-position: -66px -88px;' });

        newSpoilerButton.addEventListener('click', evt => {
            const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
            if (bbe) {
                bbe.focus();
                const start = bbe.selectionStart;
                const 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);

        // Audio button
        const audioButton = document.createElement('input');
        Object.assign(audioButton, { title: 'Audio Player', type: 'button', style: 'background-position: -22px -110px' });

        audioButton.addEventListener('click', evt => {
            const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
            if (bbe) {
                const msg = window.MSPFA.parseBBCode('Audio URL:<br>');
                const audioInput = document.createElement('input');
                Object.assign(audioInput, { type: 'url', name: 'audio-url', required: true });

                const autoplayButton = document.createElement('input');
                autoplayButton.type = 'checkbox';
                autoplayButton.id = 'autoplay';
                autoplayButton.checked = true;

                const loopButton = document.createElement('input');
                loopButton.type = 'checkbox';
                loopButton.id = 'loop';
                loopButton.checked = true;

                const controlsButton = document.createElement('input');
                controlsButton.type = 'checkbox';
                controlsButton.id = 'controls';

                msg.appendChild(audioInput);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Autoplay: ', 'autoplay'));
                msg.appendChild(autoplayButton);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Loop: ', 'loop'));
                msg.appendChild(loopButton);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Show controls: ', 'controls'));
                msg.appendChild(controlsButton);
                msg.appendChild(newBr());

                window.MSPFA.dialog("Audio Player", msg, ["Okay", "Cancel"], (output, form) => {
                    if (output == "Okay") {
                        bbe.focus();
                        const start = bbe.selectionStart;
                        const end = bbe.selectionEnd;
                        const properties = `"${autoplayButton.checked ? ' autoplay' : ''}${loopButton.checked ? ' loop' : ''}${controlsButton.checked ? ' controls' : ''}`;
                        bbe.value = bbe.value.slice(0, start) + '<audio src="' + audioInput.value + properties +'>' + bbe.value.slice(start);
                        bbe.selectionStart = start + properties.length + audioInput.value.length + 13;
                        bbe.selectionEnd = end + properties.length + audioInput.value.length + 13;
                    }

                });

                audioInput.select();
            }
        });

        flashButton.insertAdjacentElement('afterEnd', audioButton);

        // YouTube button
        const youtubeButton = document.createElement('input');
        Object.assign(youtubeButton, { title: 'YouTube Video', type: 'button', style: 'background-position: 0px -110px' });

        youtubeButton.addEventListener('click', evt => {
            const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
            if (bbe) {
                const msg = window.MSPFA.parseBBCode('Video URL:<br>');
                const videoUrl = document.createElement('input');
                videoUrl.type = 'url';
                videoUrl.name = 'youtube';
                videoUrl.required = true;

                const autoplayButton = document.createElement('input');
                autoplayButton.type = 'checkbox';
                autoplayButton.checked = true;
                autoplayButton.id = 'autoplay';

                const controlsButton = document.createElement('input');
                controlsButton.type = 'checkbox';
                controlsButton.checked = true;
                controlsButton.id = 'controls';

                const fullscreenButton = document.createElement('input');
                fullscreenButton.type = 'checkbox';
                fullscreenButton.checked = true;
                fullscreenButton.id = 'fullscreen';

                const widthInput = document.createElement('input');
                Object.assign(widthInput, { type: 'number', required: true, value: 650, style: 'width: 5em' });

                const heightInput = document.createElement('input');
                Object.assign(heightInput, { type: 'number', required: true, value: 450, style: 'width: 5em' });

                msg.appendChild(videoUrl);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Autoplay: ', 'autoplay'));
                msg.appendChild(autoplayButton);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Show controls: ', 'controls'));
                msg.appendChild(controlsButton);
                msg.appendChild(newBr());
                msg.appendChild(createLabel('Allow fullscreen: ', 'fullscreen'));
                msg.appendChild(fullscreenButton);
                msg.appendChild(newBr());
                msg.appendChild(document.createTextNode('Embed size: '));
                msg.appendChild(widthInput);
                msg.appendChild(document.createTextNode('x'));
                msg.appendChild(heightInput);

                window.MSPFA.dialog("YouTube Embed", msg, ["Okay", "Cancel"], (output, form) => {
                    if (output == "Okay") {
                        let videoID = videoUrl.value.split('/');
                        videoID = videoID[videoID.length-1].replace('watch?v=', '').split('&')[0];

                        bbe.focus();
                        const start = bbe.selectionStart;
                        const end = bbe.selectionEnd;
                        const iframeContent = `<iframe width="${widthInput.value}" height="${heightInput.value}" src="https://www.youtube.com/embed/${videoID}?autoplay=${+autoplayButton.checked}&controls=${+controlsButton.checked}" frameborder="0" allow="accelerometer; ${autoplayButton.checked ? 'autoplay; ' : ''}encrypted-media;"${fullscreenButton.checked ? ' allowfullscreen' : ''}></iframe>`;
                        bbe.value = bbe.value.slice(0, start) + iframeContent + bbe.value.slice(start);
                        bbe.selectionStart = start + iframeContent + 13;
                        bbe.selectionEnd = end + iframeContent + 13;
                    }

                });

                videoUrl.select();
            }
        });

        flashButton.insertAdjacentElement('afterEnd', youtubeButton);
        flashButton.insertAdjacentText('afterEnd', ' ');

        // Get preview link
        const getPreviewLink = (form) => {
            const page = parseInt(form.querySelector('a.major').textContent.replace('Page ', ''));
            return "/preview/?s=" + params.s + "&p=" + page + "&d=" + encodeURIComponent(JSON.stringify({
                p: page,
                c: form.querySelector('input[name=cmd]').value,
                b: form.querySelector('textarea[name=body]').value,
                n: form.querySelector('input[name=next]').value,
                k: !form.querySelector('input[name=usekeys]').checked
            }));
        }

        // -- Drafts --
        // Accessing draft text
        const accessDraftsButton = document.createElement('input');
        Object.assign(accessDraftsButton, { className: 'major', value: 'Saved Drafts', type: 'button' });
        replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton);
        accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
        accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);

        accessDraftsButton.addEventListener('click', () => {
            loadDrafts();

            const draftDialog = window.MSPFA.parseBBCode('Use the textbox below to copy out the data and save to a file somewhere else, or click the download button below.\nYou can also paste in data to replace the current drafts to ones stored there.');
            const draftInputTextarea = document.createElement('textarea');
            draftInputTextarea.placeholder = 'Paste your draft data here';
            draftInputTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';

            const downloadLink = document.createElement('a');
            downloadLink.textContent = 'Download drafts';
            downloadLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(drafts[adventureID], null, 4)));
            downloadLink.setAttribute('download', `${adventureID}.json`);

            draftInputTextarea.rows = 8;
            draftDialog.appendChild(newBr());
            draftDialog.appendChild(newBr());
            draftDialog.appendChild(draftInputTextarea);
            draftDialog.appendChild(newBr());
            draftDialog.appendChild(newBr());
            draftDialog.appendChild(downloadLink);
            setTimeout(() => {
                draftInputTextarea.focus();
                draftInputTextarea.selectionStart = 0;
                draftInputTextarea.selectionEnd = 0;
                draftInputTextarea.scrollTop = 0;
            }, 1);

            draftInputTextarea.value = JSON.stringify(drafts[adventureID], null, 4);

            window.MSPFA.dialog('Saved Drafts', draftDialog, ["Load Draft", "Cancel"], (output, form) => {
                if (output === "Load Draft") {
                    if (draftInputTextarea.value === '') {
                        setTimeout(() => {
                            window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to delete this adventure\'s draft data?\nMake sure you have it saved somewhere!'), ["Delete", "Cancel"], (output, form) => {
                                if (output === "Delete") {
                                    loadDrafts();
                                    drafts[adventureID] = {};

                                    if (settings.drafts && settings.drafts[adventureID]) {
                                        delete settings.drafts[adventureID];
                                        saveData(settings);
                                    }

                                    saveDrafts(drafts);
                                }
                            });
                        }, 1);
                    } else if (draftInputTextarea.value !== JSON.stringify(drafts[adventureID], null, 4)) {
                        setTimeout(() => {
                            window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to load this draft data?\nAll previous draft data for this adventure will be lost!'), ["Load", "Cancel"], (output, form) => {
                                if (output === "Load") {
                                    let newData = {};
                                    try { // Just in case the data given is invalid.
                                        newData = JSON.parse(draftInputTextarea.value);
                                    } catch (err) {
                                        console.error(err);
                                        setTimeout(() => {
                                            window.MSPFA.dialog('Error', window.MSPFA.parseBBCode('The entered data is invalid.'), ["Okay"]);
                                        }, 1);
                                        return;
                                    }

                                    loadDrafts();
                                    drafts[adventureID] = newData;
                                    saveDrafts(drafts);
                                }
                            });
                        }, 1);
                    }
                }
            });
        });

        // Draft stuff
        const showDraftDialog = (pageNum) => {
            loadDrafts();

            const msg = document.createElement('span');
            msg.appendChild(document.createTextNode('Command:'));
            msg.appendChild(document.createElement('br'));

            const commandInput = document.createElement('input');
            Object.assign(commandInput, { style: 'width: 100%; box-sizing: border-box;', readOnly: true, });

            msg.appendChild(commandInput);
            msg.appendChild(document.createElement('br'));
            msg.appendChild(document.createElement('br'));

            msg.appendChild(document.createTextNode('Body:'));

            const bodyInput = document.createElement('textarea');
            Object.assign(bodyInput, { style: 'width: 100%; box-sizing: border-box; resize: vertical;', readOnly: true, rows: 8 });

            msg.appendChild(bodyInput);

            const pageElement = document.querySelector(`#p${pageNum}`);

            let shownMessage = msg;
            let optionButtons = [];

            const commandElement = pageElement.querySelector('input[name="cmd"]');
            const pageContentElement = pageElement.querySelector('textarea[name="body"]');

            if (typeof drafts[adventureID][pageNum] === "undefined") {
                shownMessage = document.createTextNode('There is no draft saved for this page.');
                optionButtons = ["Save New", "Close"];
            } else {
                commandInput.value = drafts[adventureID][pageNum].command;
                bodyInput.textContent = drafts[adventureID][pageNum].pageContent;
                optionButtons = ["Save New", "Load", "Delete", "Close"];
            }

            window.MSPFA.dialog(`Page ${pageNum} Draft`, shownMessage, optionButtons, (output, form) => {
                if (output === "Save New") {
                    if (typeof drafts[adventureID][pageNum] === "undefined") {
                        loadDrafts();
                        drafts[adventureID][pageNum] = {
                            command: commandElement.value,
                            pageContent: pageContentElement.value
                        }
                        saveDrafts(drafts);
                    } else {
                        setTimeout(() => {
                            window.MSPFA.dialog('Overwrite current draft?', document.createTextNode('Doing this will overwrite your current draft with what is currently written in the page box. Are you sure?'), ["Yes", "No"], (output, form) => {
                                if (output === "Yes") {
                                    loadDrafts();
                                    drafts[adventureID][pageNum] = {
                                        command: commandElement.value,
                                        pageContent: pageContentElement.value
                                    }
                                    saveDrafts(drafts);
                                }
                            });
                        }, 1);
                    }
                } else if (output === "Load") {
                    if (pageContentElement.value === '' && (commandElement.value === '' || commandElement.value === document.querySelector('#defaultcmd').value)) {
                        commandElement.value = drafts[adventureID][pageNum].command;
                        pageContentElement.value = drafts[adventureID][pageNum].pageContent;
                        pageElement.querySelector('input[value="Save"]').disabled = false;
                    } else {
                        setTimeout(() => {
                            window.MSPFA.dialog('Overwrite current page?', document.createTextNode('Doing this will overwrite the page\'s content with what is currently written in the draft. Are you sure?'), ["Yes", "No"], (output, form) => {
                                if (output === "Yes") {
                                    commandElement.value = drafts[adventureID][pageNum].command;
                                    pageContentElement.value = drafts[adventureID][pageNum].pageContent;
                                    pageElement.querySelector('input[value="Save"]').disabled = false;
                                }
                            });
                        }, 1);
                    }
                } else if (output === "Delete") {
                    setTimeout(() => {
                        window.MSPFA.dialog('Delete this draft?', document.createTextNode('This action is irreversable! Are you sure?'), ["Yes", "No"], (output, form) => {
                            if (output === "Yes") {
                                loadDrafts();
                                delete drafts[adventureID][pageNum];

                                if (settings.drafts && settings.drafts[adventureID] && settings.drafts[adventureID][pageNum]) {
                                    delete settings.drafts[adventureID][pageNum];
                                    saveData(settings);
                                }

                                saveDrafts(drafts);
                            }
                        });
                    }, 1);
                }
            });
        }

        const createDraftButton = (form) => {
            const draftButton = document.createElement('input');
            Object.assign(draftButton, { className: 'major draft', type: 'button', value: 'Draft' });
            draftButton.addEventListener('click', () => {
                showDraftDialog(form.id.replace('p', ''));
            });
            return draftButton;
        }

        pageLoad(() => {
            let allPages = document.querySelectorAll('#storypages form:not(#newpage)');
            if (allPages.length !== 0) {
                allPages.forEach(form => {
                    const prevButton = form.querySelector('input[name="preview"]');
                    prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
                    prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton);

                    // Preview
                    const previewButton = form.querySelector('input[value=Preview]');
                    const previewLink = addLink(previewButton, getPreviewLink(form), '_blank');
                    previewButton.addEventListener('mousedown', () => {
                        previewLink.href = getPreviewLink(form);
                    });

                    // "Enable keyboard shortcuts" label
                    const shortcutCheck = form.querySelector('input[type="checkbox"]');
                    shortcutCheck.previousSibling.textContent = '';
                    shortcutCheck.id = `key-${form.id}`;
                    shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
                });
                document.querySelector('input[value="Add"]').addEventListener('click', () => {
                    allPages = document.querySelectorAll('#storypages form:not(#newpage)');
                    const form = document.querySelector(`#p${allPages.length}`);
                    const prevButton = form.querySelector('input[name="preview"]');
                    prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
                    prevButton.parentNode.insertBefore(document.createTextNode(' '), prevButton);

                    // Preview link
                    const previewButton = form.querySelector('input[value=Preview]');
                    const previewLink = addLink(previewButton, getPreviewLink(form), '_blank');
                    previewButton.addEventListener('mousedown', () => {
                        previewLink.href = getPreviewLink(form);
                    });

                    // "Enable keyboard shortcuts" label
                    const shortcutCheck = form.querySelector('input[type="checkbox"]');
                    shortcutCheck.previousSibling.textContent = '';
                    shortcutCheck.id = `key-${form.id}`;
                    shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
                });
                const newForm = document.querySelector('#newpage');
                {
                    // "Enable keyboard shortcuts" label
                    const shortcutCheck = newForm.querySelector('input[type="checkbox"]');
                    shortcutCheck.previousSibling.textContent = '';
                    shortcutCheck.id = `key-${newForm.id}`;
                    shortcutCheck.parentNode.insertBefore(createLabel('Enable keyboard shortcuts: ', shortcutCheck.id), shortcutCheck);
                }
                const newPreviewButton = newForm.querySelector('input[value=Preview]');
                const newPreviewLink = addLink(newPreviewButton, getPreviewLink(newForm), '_blank');
                newPreviewButton.addEventListener('mousedown', () => {
                    newPreviewLink.href = getPreviewLink(newForm);
                });
                return true;
            }
        });

        if (params.click) {
            if (params.click === 's') {
                spoilerButton.click();
            } else if (params.click === 'd') {
                accessDraftsButton.click();
            }
        }

        // Don't scroll after pressing a BBToolbar button (awesome)
        let lastScroll = window.scrollY;
        pageLoad(() => {
            if (document.querySelectorAll('#storypages textarea').length > 1) {
                document.querySelectorAll('#storypages textarea').forEach(textarea => {
                    textarea.addEventListener('focus', () => {
                        window.scrollTo(window.scrollX, lastScroll);
                    });
                });

                document.addEventListener('scroll', evt => {
                    lastScroll = window.scrollY;
                });
                return true;
            }
        });

        // Focus on the text input when clicking on the Color or Background Color BBToolbar buttons
        const colourButtons = [document.querySelector('#bbtoolbar input[data-tag=color]'), document.querySelector('#bbtoolbar input[data-tag=background]')];
        colourButtons.forEach(button => {
            button.addEventListener('click', () => {
                document.querySelector('#dialog input[type=text]').select();
            });
        });
    }
    else if (location.pathname === "/my/profile/") {
        // Nothing
    }
    else if (location.pathname === "/user/") {
        // Button links
        pageLoad(() => {
            const msgButton = document.querySelector('#sendmsg');
            if (msgButton) {
                addLink(msgButton, `/my/messages/new/?u=${params.u}`);
                addLink(document.querySelector('#favstories'), `/favs/?u=${params.u}`);
                return true;
            }
        });

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

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

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

                // Show user creation date
                window.MSPFA.request(0, {
                    do: "user",
                    u: params.u
                }, user => {
                    if (typeof user !== "undefined") {
                        joinTime.textContent = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
                    }

                    // Show created adventures
                    window.MSPFA.request(0, {
                        do: "editor",
                        u: params.u
                    }, s => {
                        if (typeof s !== "undefined") {
                            advCountText.textContent = s.length;
                        }

                        // Show favourites
                        if (document.querySelector('#favstories').style.display !== 'none') {
                            const favCountTr = stats.insertRow(3);
                            const favTextTd = favCountTr.insertCell(0);
                            favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
                            const favCount = favCountTr.insertCell(1);
                            const favCountText = document.createElement('b');
                            favCountText.textContent = "Loading...";
                            window.MSPFA.request(0, {
                                do: "favs",
                                u: params.u
                            }, s => {
                                if (typeof s !== "undefined") {
                                    favCountText.textContent = s.length;
                                }
                            });
                            favCount.appendChild(favCountText);
                        }
                    });
                });

                return true;
            }
        });
    }
    else if (location.pathname === "/favs/" && location.search) {
        const toggleButton = document.createElement('input');
        Object.assign(toggleButton, { className: "major", type: "button", value: "Toggle Muted Adventures" });
        const buttonRow = document.querySelector('table.container.alt').insertRow(2);
        const actionSpan = document.createElement('span');

        let stories = [];
        // Button links
        pageLoad(() => {
            stories = document.querySelectorAll('#stories tr');
            let favCount = 0;

            if (stories.length > 0) {
                stories.forEach(story => {
                    favCount++;
                    const 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 true;
                        }
                        if (pageLoaded) return true;
                    });
                    addLink(story.querySelector('.rss.major'), `/rss/${id}`);
                });

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

                return true;
            }
            if (pageLoaded) return true;
        });

        pageLoad(() => {
            if (window.MSPFA && window.MSPFA.me) {
                if (window.MSPFA.me.i === params.u) {
                    const cell = buttonRow.insertCell(0);
                    cell.appendChild(toggleButton);
                    cell.appendChild(newBr());
                    cell.appendChild(actionSpan);
                    return true;
                }
            }
            if (pageLoaded) return true;
        });

        let type = 0;

        toggleButton.addEventListener('click', () => {
            type++;
            if (type > 2) type = 0;

            stories.forEach(story => {
                const unmuted = story.querySelector('.notify').className.includes(' lit');
                story.style.display = '';
                if (type === 2 && unmuted || type === 1 && !unmuted) {
                    story.style.display = 'none';
                }
            });

            if (type === 0) {
                // show all
                actionSpan.textContent = '';
            }
            else if (type === 1) {
                // hide muted
                actionSpan.textContent = 'Showing only unmuted favourites';
            }
            else {
                // only muted
                actionSpan.textContent = 'Showing only muted favourites';
            }
        });
    }
    else if (location.pathname === "/search/" && location.search) {
        // Character and word statistics
        const statTable = document.createElement('table');
        const statTbody = document.createElement('tbody');
        const statTr = statTbody.insertRow(0);
        const charCount = statTr.insertCell(0);
        const wordCount = statTr.insertCell(0);
        const statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2);
        const statParentTd = statParentTr.insertCell(0);

        const statHeaderTr = statTbody.insertRow(0);
        const 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')) {
                const bbc = window.MSPFA.BBC.slice();
                bbc.splice(0, 3);

                window.MSPFA.request(0, {
                    do: "story",
                    s: params.s
                }, story => {
                    if (typeof story !== "undefined") {
                        const pageContent = [];
                        story.p.forEach(p => {
                            pageContent.push(p.c);
                            pageContent.push(p.b);
                        });

                        const 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;
            }
        });
    }
    else if (location.pathname === "/stories/" && location.search) {
        // Click text to check/uncheck boxes
        ['Ongoing', 'Complete', 'Inactive'].forEach(t => {
            const check = document.querySelector(`input[name="${t.toLowerCase()}"]`);
            check.id = `check_${t.toLowerCase()}`;
            const label = createLabel(' ' + t + ' ', check.id);
            check.nextSibling.textContent = '';
            check.parentNode.insertBefore(label, check.nextSibling);
        });

        const adventureList = document.querySelector('#doit');
        const resultAmount = document.createElement('span');
        adventureList.parentNode.appendChild(resultAmount);

        pageLoad(() => {
            if (window.MSPFA) {
                window.MSPFA.request(0, {
                    do: "stories",
                    n: params.n,
                    t: params.t,
                    h: params.h,
                    o: params.o,
                    p: params.p,
                    m: 20000
                }, (s) => {
                    resultAmount.textContent = `Number of results: ${s.length}`;
                    return true;
                });
                return true;
            }
        },1);

        pageLoad(() => {
            const stories = document.querySelector('#stories');
            if (stories.childNodes.length > 0) {
                if (params.load && stories.childNodes.length === 1) {
                    stories.querySelector('a').click();
                }

                stories.querySelectorAll('tr').forEach(story => {
                    const storyID = story.querySelector('a.major').href.split('&')[0].replace(/\D/g, '');
                    addLink(story.querySelector('.rss'), `/rss/?s=${storyID}`);

                    pageLoad(() => {
                        if (window.MSPFA.me.i) {
                            addLink(story.querySelector('.edit.major'), `/my/stories/info/?s=${storyID}`);
                            return true;
                        }
                        if (pageLoaded) return true;
                    });
                });
                return true;
            }
            if (pageLoaded) return true;
        });
    }
})();