// ==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\, n_sub, t + "
<");
} 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;
}
}
});
})();