Greasy Fork

Fanfiction.net - Beautify Status Scrolling For Dark Reader

Changes colors and formats stats to make it easier to read while scrolling, works on android. "Fiction Rating: All" as default, blacklist included.

目前为 2023-01-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         Fanfiction.net - Beautify Status Scrolling For Dark Reader
// @namespace    http://tampermonkey.net/
// @version      3.31
// @description  Changes colors and formats stats to make it easier to read while scrolling, works on android. "Fiction Rating: All" as default, blacklist included.
// @author       バカなやつ
// @license      MIT
// @match        *://*.fanfiction.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=fanfiction.net
// @grant        GM_addStyle
// ==/UserScript==
GM_addStyle(".lightGreenHighlight {color:#C4FFCA;}");
GM_addStyle(".greenHighlight {color:#04AD5C;}");
GM_addStyle(".redHighlight {color:#CC5500;}");
GM_addStyle(".yellowHighlight {color:#FFC300;}");
GM_addStyle(".pinkHighlight {color:#FFC0CB;}");
GM_addStyle(".z-indent {padding-left: 60px;}");
GM_addStyle(".reviews {color: rgb(255, 102, 26); text-decoration-color: initial;}");
(function () {
    'use strict';

    // const blacklist_fandoms = ["Harry", "Doctor Who", "Crossover - Star Wars", "Yu-Gi-Oh"];
    const blacklist_fandoms = [];
    // Makes "Fiction Rating: All" default the first time you enter a story listing page!
    const allRatings = true;

    const URL = document.URL;
    // PC
    const isReadingPage = URL.includes("fanfiction.net/s/");
    const isCommunityPage = URL.includes("fanfiction.net/community/");
    const communities = ["anime", "book", "cartoon", "comic", "game", "misc", "play", "movie", "tv"];
    const isStoriesPage = communities.some(v => URL.includes("fanfiction.net/" + v));
    const isCrossoverPage = URL.includes("Crossovers/");
    const isAuthorPage = URL.includes("/u/");
    const isJustIn = URL.includes("/j/");
    const isMobile = URL.startsWith("https://m.fanfiction.net/");

    if (isCommunityPage && allRatings &&
        // If URL does not contain any filter id's
        URL.search(/\/\d\d\d\d*\/$/) != -1) {
        let l = URL.split("/");
        let endURL = 99 + "/";
        location.href = URL + endURL;
    }

    if ((isStoriesPage || isCrossoverPage) && allRatings &&
        // If URL does not contains any filter id's
        URL.search(/\/\?/) == -1) {
        location.href = URL + "?&srt=1&r=10";
    }

    // Replaces pattern on text with string
    function rep(pattern, text, replace) {
        let re = new RegExp(pattern);
        return text.replace(re, replace);
    }
    // Matches the pattern and returns the second group
    function mat(pattern, text) {
        let re = new RegExp(pattern)
        return re.exec(text)[1];
    }
    // Short-form for replacematch, wrapping the text pattern
    function repmat(pattern, text, replace) {
        let re = new RegExp(pattern);
        let num = re.exec(text)[1];
        if (replace.startsWith("<a")) {
            return text.replace(re, replace + num + "</a> ");
        } else {
            return text.replace(re, replace + num + "</span> ");
        }
    }
    function m_repmat(pattern, text, start, end) {
        let re = new RegExp(pattern);
        let value = re.exec(text)[1];
        return text.replace(re, start + value + end);
    }
    // Content List
    function cList(story, status) {
        let n_sub = status.innerHTML;
        // Checks if these exists, returns true or false
        const isReviews = (isReadingPage) ? story.innerHTML.search(/Reviews:\s.*?>(.*?)<.*?>/) != -1 : story.innerHTML.search(/<a class="reviews".*?f="(.*?)">/) != -1;
        const isChapters = n_sub.includes("Chapters:");
        const isFavs = n_sub.includes("Favs:");
        const isFollows = n_sub.includes("Follows:");
        const isUpdated = n_sub.includes("Updated:");
        const isPublished = n_sub.includes("Published:");

        let ahref;
        if (isReadingPage && isReviews) {
            ahref = status.getElementsByTagName("a")[1].getAttribute("href");
        } else if (isReviews) {
            ahref = mat(/<a class="reviews".*?f="(.*?)">/g, story.innerHTML);
            // Removes the original Review Link
            let review = story.getElementsByClassName("reviews")[0];
            review.parentNode.removeChild(review);
        }
        // Highlights the fandom/crossover title
        if (isCommunityPage || isCrossoverPage || isAuthorPage || isJustIn) {
            let storyName = mat(/(.*?)\s-\sRated:/g, n_sub);
           n_sub = "<span class='greenHighlight'>" + storyName + rep(/(.*?)\s-\sRated:/g, n_sub, "</span> - Rated:");
        }

        if (isChapters) {
            n_sub = repmat(/Chapters:\s(.*?)\s/g, n_sub, "<br><span class='yellowHighlight'>Ch: ");
        }

        n_sub = repmat(/Words:\s(.*?)\s/g, n_sub, "<span class='pinkHighlight'>W: ");

        if (isReadingPage && isReviews) {
            n_sub = repmat(/Reviews:\s.*?>(.*?)<.*?>/g, n_sub, "<a class='reviews'>Reviews: ");
        } else if (isReviews) {
            n_sub = repmat(/Reviews:\s(.*?)\s/g, n_sub, "<a class='reviews'>Reviews: ");
        }

        if (isFavs) {
            n_sub = repmat(/Favs:\s(.*?)\s/g, n_sub, "<span class='lightGreenHighlight'>Favs: ");
        }

        if (isFollows) {
            n_sub = repmat(/Follows:\s(.*?)\s/g, n_sub, "<span class='lightGreenHighlight'>Follows: ");
        }

        if (isUpdated) {
            n_sub = repmat(/Updated:\s.*?>(.*?)<.*?\>/g, n_sub, "<span class='lightGreenHighlight'>Updated: ");
        }
        // Moves the "Publish: ... Characters:" before "<br>Chapters:"
        let t = n_sub.slice(n_sub.search(/Published:/));
        // Remove previous "Publish: ... Characters:"
        n_sub = n_sub.slice(0, n_sub.search(/\s\-\sPublished:/));
        // Moves it depending on whether Chapter exists
        if (!isChapters) {
            n_sub = rep(/\-\s\</, n_sub, t + "<br> <");
        } else {
            n_sub = rep(/<br>/, n_sub, t + "<br>");
        }

        status.innerHTML = n_sub;
        // Adds the link to reviews
        if (isReviews) {
            status.getElementsByClassName("reviews")[0].setAttribute("href", ahref);
        }
    }
    // Mobile counterpart
    function m_cList(story) {
        let n_sub = story.innerHTML;

        const isReviews = n_sub.search(/Reviews:/i) != -1;
        const isChapters = n_sub.search(/Chapters:/i) != -1;
        const isFavs = n_sub.search(/Favs:/i) != -1;
        const isFollows = n_sub.search(/Follows:/i) != -1;
        const isUpdated = n_sub.search(/Updated:/i) != -1;
        const isPublished = n_sub.search(/Published:/i) != -1;

        let n;
        if (isCommunityPage) {
            n_sub = m_repmat(/y">(.*?),/, n_sub, "y\"><span class='greenHighlight'>", "</span>,");
        }
        /*
        else if (isCommunityPage) {
            n_sub = m_repmat(/y">(.*?),/, n_sub, "y\"><span class='greenHighlight'>", "</span>, <br><span class='yellowHighlight'>Ch: ");
        }*/

        if (isChapters) {
            n_sub = m_repmat(/Chapters:\s(.*?),/i, n_sub, "<br><span class='yellowHighlight'>Ch: ", "</span> ");
            n_sub = m_repmat(/Words:\s(.*?),/i, n_sub, "<span class='pinkHighlight'>W: ", "</span> ");
        } else {
            n_sub = m_repmat(/Words:\s(.*?),/i, n_sub, "<br><span class='pinkHighlight'>W: ", "</span> ");
        }

        if (isReadingPage && isReviews) {
            n_sub = m_repmat(/Reviews:\s.*?>(.*?)<.*?>/i, n_sub, "<a class='reviews'>Reviews: ", "</a> ");
        }
        if (isFavs) {
            n_sub = m_repmat(/\sFavs:\s(.*?),/i, n_sub, "<span class='lightGreenHighlight'>Favs: ", "</span> ");
        }
        if (isFollows) {
            n_sub = m_repmat(/Follows:\s(.*?),/i, n_sub, "<span class='lightGreenHighlight'>Follows: ", "</span> ");
        }
        if (isReadingPage) {
            n = n_sub.slice(n_sub.search(/Published:/i), n_sub.search(/Updated:/));
        } else {
            n = n_sub.slice(n_sub.search(/Published:/i), n_sub.search(/<\/div/));
        }
        if (isUpdated) {
            // console.log(n_sub);
            n_sub = m_repmat(/Updated:\s.*?>(.*?)<.*?\>\s/i, n_sub, "<span class='lightGreenHighlight'>Updated: ", "</span> ");
        }

        // n_sub = n_sub.replace(/Published:\s.*?>.*?<.*?\>/i, "");
        n_sub = n_sub.replace(/Published:\s(.*?<\/span>),|Published:\s(.*?<\/span>)/i, "");

        if (isReadingPage) {
            n_sub = n_sub.replace(/<\/span>,\s<hr/i, "</span>, " + n + "<hr");
        } else if (isPublished) {
            n_sub = n_sub.replace(/,\s<br>/, ", " + n + "<br>");
            n_sub = m_repmat(/Published:\s(.*?<\/span>).*?<br>/i, n_sub, "Published: ", "<br>");
        }
        story.innerHTML = n_sub;
    }
    // Mobile prototype function. Only using concatination, but full of bugs.
    function prototype(stats) {
        let s = stats.innerHTML;
        let isTitle = s.match(/(.*?),\s/);
        let isGenre = s.match(/.*?,\s(.*?),\schapters:|.*?,\s(.*?),\swords:/i);
        let isChapters = s.match(/chapters:\s(.*?),/i);
        let isWords = s.match(/words:\s(.*?),/i);
        let isFavs = s.match(/favs:\s(.*?),/i);
        let isFollows = s.match(/follows:\s(.*?),/i);
        let isUpdated = s.match(/Updated:\s.*?>(.*?)<.*?\>\s/i);
        let isPublished = s.match(/<.*?>.*?<.*?>.*?<.*?>(.*?)<.*?>|<.*?data.*?>(.*?)<.*?>/i);

        let n_sub = "";

        if (isCommunityPage) {
            n_sub = "<span class='greenHighlight'>" + isTitle[1] + "</span>, ";
        }

        if (isGenre != null) {
            n_sub = n_sub + isGenre[1];
        }
        /*else {
            console.log("No Genre! ", n_sub);
        }*/

        n_sub = n_sub + "<br><span class='yellowHighlight'>Ch: " + (isChapters != null ? isChapters[1] : 1) + "</span> ";
        n_sub = n_sub + "<span class='pinkHighlight'>W: " + isWords[1] + "</span> ";

        if (isFavs != null) {
            n_sub = n_sub + "<span class='lightGreenHighlight'>Favs: " + isFavs[1] + "</span> ";
        }

        if (isFollows != null) {
            n_sub = n_sub + "<span class='lightGreenHighlight'>Follows: " + isFollows[1] + "</span> ";
        }

        if (isUpdated != null) {
            n_sub = n_sub + "<span class='lightGreenHighlight'>Updated: " + isUpdated[1] + "</span> ";
            // console.log(s, "\n", n_sub, isPublished);
        }

        // FIXME: not working as expected, missing Published.
        n_sub = n_sub + "Published: " + isPublished[1] + ", ";


        stats.innerHTML = n_sub;
    }

    window.addEventListener("load", function() {
        if (isMobile) {
            if (isReadingPage) {
                let story = document.getElementById("content");
                m_cList(story);
            } else {
                let story = document.getElementsByClassName("bs");
                let stats = document.getElementsByClassName("gray");
                for (let i = story.length - 1; i > -1; i--) {
                    let status = story[i].getElementsByClassName("gray");
                    let text = status[0].innerHTML;
                    if (blacklist_fandoms.some(v => text.includes(v))) {
                        story[i].parentNode.removeChild(story[i]);
                        continue;
                    }
                    m_cList(story[i]);
                }
            }
        } else {
            if (isReadingPage) {
                let story = document.getElementById("profile_top");
                let status = document.getElementsByClassName("xgray")[0];
                cList(story, status);
            }
            else {
                let stories = document.getElementsByClassName("z-list");
                let statuses = document.getElementsByClassName("z-padtop2");
                for (let i = stories.length - 1; i > -1; i--) {
                    let status = stories[i].getElementsByClassName("z-padtop2");
                    let text = status[0].innerHTML;
                    if (blacklist_fandoms.some(v => text.includes(v))) {
                        stories[i].parentNode.removeChild(stories[i]);
                        continue;
                    }
                    cList(stories[i], statuses[i]);
                }
            }
        }
    });
    window.addEventListener("keyup", e => {
        var keynum;

        if(window.event) { // IE
            keynum = e.keyCode;
        } else if(e.which){ // Netscape/Firefox/Opera
            keynum = e.which;
        }

        var key = String.fromCharCode(keynum);
        if (!isReadingPage) {
            var a = document.getElementsByTagName("center")[0].getElementsByTagName("a");
            switch (key) {
                case "'":
                    // alert("You pressed right");
                    location.href = a[a.length-1].getAttribute("href");
                    break;
                case "%":
                    // alert("You pressed left");
                    location.href = a[0].getAttribute("href");
                    break;
                default:
                    break;
            }
        }
    });
})();