// ==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 // @downloadURL none // ==/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(" "); } else { return text.replace(re, replace + num + " "); } } 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(//) != -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(//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 = "" + storyName + rep(/(.*?)\s-\sRated:/g, n_sub, " - Rated:"); } if (isChapters) { n_sub = repmat(/Chapters:\s(.*?)\s/g, n_sub, "
Ch: "); } n_sub = repmat(/Words:\s(.*?)\s/g, n_sub, "W: "); if (isReadingPage && isReviews) { n_sub = repmat(/Reviews:\s.*?>(.*?)<.*?>/g, n_sub, "
Reviews: "); } else if (isReviews) { n_sub = repmat(/Reviews:\s(.*?)\s/g, n_sub, "Reviews: "); } if (isFavs) { n_sub = repmat(/Favs:\s(.*?)\s/g, n_sub, "Favs: "); } if (isFollows) { n_sub = repmat(/Follows:\s(.*?)\s/g, n_sub, "Follows: "); } if (isUpdated) { n_sub = repmat(/Updated:\s.*?>(.*?)<.*?\>/g, n_sub, "Updated: "); } // Moves the "Publish: ... Characters:" before "
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\ <"); } else { n_sub = rep(/
/, n_sub, t + "
"); } 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\">", ","); } /* else if (isCommunityPage) { n_sub = m_repmat(/y">(.*?),/, n_sub, "y\">", ",
Ch: "); }*/ if (isChapters) { n_sub = m_repmat(/Chapters:\s(.*?),/i, n_sub, "
Ch: ", " "); n_sub = m_repmat(/Words:\s(.*?),/i, n_sub, "W: ", " "); } else { n_sub = m_repmat(/Words:\s(.*?),/i, n_sub, "
W: ", " "); } if (isReadingPage && isReviews) { n_sub = m_repmat(/Reviews:\s.*?>(.*?)<.*?>/i, n_sub, "
Reviews: ", " "); } if (isFavs) { n_sub = m_repmat(/\sFavs:\s(.*?),/i, n_sub, "Favs: ", " "); } if (isFollows) { n_sub = m_repmat(/Follows:\s(.*?),/i, n_sub, "Follows: ", " "); } 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, "Updated: ", " "); } // 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
, " + n + "/, ", " + n + "
"); n_sub = m_repmat(/Published:\s(.*?<\/span>).*?
/i, n_sub, "Published: ", "
"); } 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 = "" + isTitle[1] + ", "; } if (isGenre != null) { n_sub = n_sub + isGenre[1]; } /*else { console.log("No Genre! ", n_sub); }*/ n_sub = n_sub + "
Ch: " + (isChapters != null ? isChapters[1] : 1) + " "; n_sub = n_sub + "W: " + isWords[1] + " "; if (isFavs != null) { n_sub = n_sub + "Favs: " + isFavs[1] + " "; } if (isFollows != null) { n_sub = n_sub + "Follows: " + isFollows[1] + " "; } if (isUpdated != null) { n_sub = n_sub + "Updated: " + isUpdated[1] + " "; // 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; } } }); })();