// ==UserScript==
// @name MangaDex Follows
// @namespace https://greasyfork.org/es/scripts/411685-mangadex-follows
// @version 1.3.6
// @description Manage your follows page
// @author Australis
// @match https://mangadex.org/*
// @exclude https://mangadex.org/chapter/*
// @grant none
// ==/UserScript==
console.log("Executing");
let blacklist = [
// [23811,657],
// [23811,12],
]
let whitelist = [
// [23811,3043],
// [22843,12672],
// [26359,10883],
// [19193,12],
// [17274,14562],
]
let gap = [
// [12253,[13,16]],
]
var enhance = true
checker = []
function Checker(node){
if(checker.length == 0) checker = [node]
else checker.push(node)
console.log(node)
}
function CleanChecker(id){
try{
for(var x=checker.length-1; x>=0; x--){
if(checker[x][0] == id) checker.splice(x,1)
}
}catch(e){
console.log("CleanChecker error: "+e)
checker = []
}
}
function CleanCheckerB(id,row){
try{
for(var x=checker.length-1; x>=0; x--){
if(checker[x][0] == id && checker[x][1] == row) checker.splice(x,1)
}
}catch(e){
console.log("CleanChecker error: "+e)
checker = []
}
}
function Ahead(){
if(checker.length){
for(c of checker){
c[1].getElementsByClassName("chapter_mark_read_button grey")[0].setAttribute("class","chapter_mark_read_button")
c[1].getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: red")
}
}
checker = []
}
function FindSeries(array, number){
return array.findIndex(q => q[0]*1 == number*1)
// for(var i=0; i < array.length; i++){
// if(array[i][0]*1 == number*1) return i;
// }
// return -1;
}
try{
series = JSON.parse(localStorage.getItem('series'))
console.log("series loaded")
}catch(e){
series = []
console.log("failed to load series")
}
//add titles from follows
if(document.URL.includes('mangadex.org/follows/manga')){
series_col = document.getElementsByClassName("manga-entry border-bottom");
for(var s of series_col){
var row = s.getElementsByClassName("row m-0")[1];
var name = row.children[0].innerText;
var state = row.children[2].innerText;
var id = s.dataset.id;
var node = [id,name,state,0,0]
var add = true;
if(series){
for(var m of series){
if(m[0] === id){
add = false;
if(m[2] !== state) m[2] = state;
}
}
if(add) series.push(node);
}
else series = [node]
}
localStorage.setItem('series',JSON.stringify(series))
}
else if(document.URL.includes('mangadex.org/follows')){
//read chapters
row = document.getElementsByClassName("chapter-container")[0].children;
// row[0].remove()
function Hide(DataSet){
for(var b of blacklist){
if(b[0] == DataSet.mangaId && b[1] == DataSet.group) return true
}
return false
}
function OnlyThis(DataSet){
for(var w of whitelist){
if(w[0] == DataSet.mangaId && w[1] != DataSet.group) return 0
if(w[0] == DataSet.mangaId && w[1] == DataSet.group) return 1
}
return 2
}
let changeH = false
let changeO = false
for(var r of row){
let dataset = r.children[2].childNodes[1].dataset;
if(dataset.mangaId){
let manga_id = dataset.mangaId*1;
if(enhance) try{
if(Hide(dataset)) {
title = r.children[0].innerHTML
r.setAttribute("style","display: none")
changeH = true
}
else if(changeH){
title2 = r.children[0].innerHTML
if(r.children[0].classList.contains("d-md-flex")) r.children[0].innerHTML = title
title = title2
changeH = false
}
if(!OnlyThis(dataset)) {
title = r.children[0].innerHTML
r.setAttribute("style","display: none")
changeO = true
}
else if(changeO){
title2 = r.children[0].innerHTML
if(r.children[0].classList.contains("d-md-flex")) r.children[0].innerHTML = title
title = title2
changeO = false
}
}catch(e){
console.log("enhance error")
}
try{
if(series[FindSeries(series,manga_id)][3].toString().includes("v")){//if is divided by volume
console.log("a volume one, "+series[FindSeries(series,manga_id)][3])
q = series[FindSeries(series,manga_id)][3].split("v")
vol = q[0]
chap = q[1]
if(r.innerHTML.includes("mark_unread")){
if(chap*1 < dataset.chapter*1 && vol*1 == dataset.volume*1) {
console.log("new chapter")
series[FindSeries(series,manga_id)][3] = dataset.volume+"v"+dataset.chapter;
}
if(vol < dataset.volume*1) {
console.log("new volume")
series[FindSeries(series,manga_id)][3] = dataset.volume+"v"+dataset.chapter;
}
}
else if(enhance){
if((chap*1 >= dataset.chapter*1 && vol*1 == dataset.volume*1) || vol*1 > dataset.volume*1){ //a duplicate
r.getElementsByClassName("chapter_mark_read_button grey")[0].setAttribute("class","chapter_mark_read_button")
if(gap.findIndex(q => q[0] == manga_id) > 0){
let lgap = gap[gap.findIndex(q => q[0] == dataset.mangaId)][1]
console.log("a gapped one: ",lgap)
console.log("current chapter: "+dataset.volume+"v"+dataset.chapter)
if(chap*1 >= lgap[0].split("v")[1] && vol*1>= lgap[0].split("v")[0] &&
lgap[1].split("v")[1] >= dataset.chapter*1 && vol*1<= lgap[1].split("v")[0]) r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: green")
}
else if(!(dataset.title.toLowerCase().includes("extra") || dataset.title.toLowerCase().includes("omake"))){
r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: blue")
}
else r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: green")
}
else{//a new one
if(chap*1 < dataset.chapter*1+1.1 && vol*1 == dataset.volume*1){//skipped chapter
Checker([manga_id,r])
}
if(chap*1 < dataset.chapter*1 && vol*1 == dataset.volume*1 && dataset.chapter*1 <= chap*1+1.1){//next chapter
// CleanChecker(manga_id)
CleanCheckerB(manga_id,r)
}
}
}
}
else{//if not divided by volume
this_last = series[FindSeries(series,manga_id)][3]*1
if(r.innerHTML.includes("mark_unread")){
if(this_last < dataset.chapter*1) series[FindSeries(series,manga_id)][3] = dataset.chapter*1;
}
else if(enhance){
if(this_last >= dataset.chapter*1){ //a duplicate
console.log("a duplicate")
r.getElementsByClassName("chapter_mark_read_button grey")[0].setAttribute("class","chapter_mark_read_button")
if(gap.findIndex(q => q[0] == manga_id) > 0){
let lgap = gap[gap.findIndex(q => q[0] == dataset.mangaId)][1]
console.log("a gapped one: ",lgap)
console.log("current chapter: "+dataset.chapter)
if(dataset.chapter*1 >= lgap[0] &&
lgap[1] >= dataset.chapter*1) r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: green")
}
else if(dataset.title.toLowerCase().includes("extra") || dataset.title.toLowerCase().includes("omake") || (dataset.chapter*1)%1 == 0.5){
r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: green")
}
else r.getElementsByClassName("chapter_mark_read_button")[0].setAttribute("style","color: blue")
}
else{//a new one
if(this_last != 0
&& this_last < dataset.chapter*1+1.1){//skipped chapter
// if((dataset.chapter*1)%1 && series[FindSeries(series,manga_id)][3]*1+0.1 <= dataset.chapter*1) console.log("skipped parted chapter")
Checker([manga_id,r])
}
if(this_last < dataset.chapter*1
&& dataset.chapter*1 <= this_last+1.1){//next chapter
if((dataset.chapter*1)%1 && dataset.chapter*1 == this_last+0.1) {
console.log("next parted chapter")
CleanCheckerB(manga_id,r)
}
else if((dataset.chapter*1)%1 && dataset.chapter*1 == this_last+0.5) {
console.log("next extra chapter")
CleanCheckerB(manga_id,r)
}
else if((dataset.chapter*1)%1 && (dataset.chapter*1)%1 && this_last-this_last%1 < dataset.chapter*1-(dataset.chapter*1)%1
&& dataset.chapter*1 == this_last-this_last%1+1.1){
console.log("next new parted chapter")
CleanCheckerB(manga_id,r)
}
else if((dataset.chapter*1)%1 == 0) CleanCheckerB(manga_id,r)
}
}
}
}
}catch(e){
//series not included
console.log("series not included?\n"+e)
console.log(dataset.mangaId+" "+r.children[0].innerText)
let this_last = 0
if(r.innerHTML.includes("mark_unread")) this_last = dataset.chapter*1;
if(FindSeries(series,dataset.mangaId) == -1) series.push([dataset.mangaId,r.children[0].innerText,' Reading',this_last,dataset.timestamp])
}
}//end if
}//end for
Ahead()
localStorage.setItem('series',JSON.stringify(series))
}
if(document.URL.includes('/title/')){
console.log("title")
var follow_stat = document.getElementsByClassName("btn-group")[0].childNodes[0].childNodes[0].className;
let mangaId = document.URL.split("/")[4]*1
function ProcVol(){
try{
for(let t of temp){
[lastv,lastc] = last.split("v")
if(t.dataset != null){
let dataset = t.dataset;
let manga_id = dataset.mangaId;
let vol = dataset.volume*1
if(process && lastc == dataset.chapter*1 && lastv == vol){
if(!t.innerHTML.includes("mark_unread")){
last = "0v0"
console.log("reset")
}
else process = false
}
if(t.innerHTML.includes("mark_unread")) {
if(lastc < dataset.chapter*1 && lastv == vol) {//a new read chapter from same volume
last = vol+"v"+dataset.chapter
process = false
series[FindSeries(series,manga_id)][3] = last;
}
if(lastv < vol){//a new volume is read
console.log("new volume")
last = vol+"v"+dataset.chapter
series[FindSeries(series,manga_id)][3] = last;
}
if(time < dataset.timestamp*1){
time = dataset.timestamp*1
series[FindSeries(series,manga_id)][4] = dataset.timestamp*1
}
}
}
}
console.log("done ProcVol, current last:"+last)
}catch(e){
console.log("ProcVol error\n"+e)
}
}
function Chapters(){
try{
if(last.toString().includes("v")){//is parted as independent chapters per volume
ProcVol()
}
else{//is default
for(let t of temp){
if(t.dataset != null){
let dataset = t.dataset;
let manga_id = dataset.mangaId;
let timestamp = dataset.timestamp
if(dataset.volume != null) {
vol = dataset.volume*1
}
else {
vol = 10000-1
}
if(checkvol && cvol > vol && cvol != 10000) {//volume change and not first iteration
if(cchp <= dataset.chapter*1){ //if it's not a successive chapter
console.log("volume!!")
checkvol = false
last = "0v0"
ProcVol()
}
}
if(!last.toString().includes("v")){
if(process && last == dataset.chapter*1){
if(!t.innerHTML.includes("mark_unread")){
last = 0
console.log("reset")
}
else process = false
}
if(t.innerHTML.includes("mark_unread")) {
if(last < dataset.chapter*1) {
process = false
last = dataset.chapter*1;
series[FindSeries(series,manga_id)][3] = dataset.chapter*1;
}
if(time < dataset.timestamp*1){
time = dataset.timestamp*1
series[FindSeries(series,manga_id)][4] = dataset.timestamp*1
}
}
cvol = vol
cchp = dataset.chapter*1
}
}
}
console.log("new last: "+last)
localStorage.setItem('series',JSON.stringify(series))
console.log("OK");
}
}catch(e){
console.log("Chapters error\n"+e)
}
}
var temp = document.getElementsByClassName("chapter-row d-flex row no-gutters p-2 align-items-center border-bottom odd-row");
var last = 0
var time = 0
var pos = FindSeries(series,mangaId)
if(pos > 0) {
last = series[pos][3]
if(series[pos].length == 4) series[pos].push(time)
else time = series[pos][4]
}
console.log("old last: "+last)
process = true
var cvol = 10000
var cchp = 0
checkvol = true
let state = document.getElementsByClassName("btn dropdown-toggle")[0].getElementsByClassName("d-none d-xl-inline")[0].innerText
let name = document.title.split(" (Title)")[0]
let id = document.URL.split("/")[4]
let node = [id,name,state,last,time]
let add = true;
let z = FindSeries(series,id)
if(z == -1){
series.push(node)
console.log(name+" added to series")
}
else{
if(series[z][2] !== state) series[z][2] = state;
}
if(!follow_stat.includes("fa-bookmark")){
Chapters()
}
// if(follow_stat.includes("fa-eye") || follow_stat.includes("fa-check")){
// Chapters()
// }
// else{
// if(!follow_stat.includes("fa-bookmark")){
// Chapters()
// }
// }
}
if(document.URL.includes('mangadex.org/follows') && !document.URL.includes('mangadex.org/follows/manga')){
let data = [['ID','Series name','Status','Last Read','Last Update']].concat(series);
for(var d of data){
if(d[1].includes("\"")) d[1] = d[1].replaceAll("\"","|")
if(d[1].includes(";")) d[1] = d[1].replaceAll(";",":")
if(d.length == 5) d[4] = Date(d[4])
else d.push("Unknown")
}
// Building the CSV from the Data two-dimensional array
// Each column is separated by ";" and new line "\n" for next row
let csvContent = '';
data.forEach(function(infoArray, index) {
dataString = infoArray.join(';');
csvContent += index < data.length ? dataString + '\n' : dataString;
});
// The download function takes a CSV string, the filename and mimeType as parameters
// Scroll/look down at the bottom of this snippet to see how download is called
let download = function(content, fileName, mimeType) {
var a = document.createElement('a');
mimeType = mimeType || 'application/octet-stream';
if (navigator.msSaveBlob) { // IE10
navigator.msSaveBlob(new Blob([content], {
type: mimeType
}), fileName);
} else if (URL && 'download' in a) { //html5 A[download]
a.href = URL.createObjectURL(new Blob([content], {
type: mimeType
}));
a.setAttribute('download', fileName);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
location.href = 'data:application/octet-stream,' + encodeURIComponent(content); // only this mime type is supported
}
}
window.download = function(){download(csvContent, 'MangaDex.csv', 'text/csv;encoding:utf-8')}
let boton = document.createElement("li")
boton.setAttribute("class","nav-item")
boton.setAttribute("href","#")
boton.setAttribute("onclick","download()")
boton.appendChild(document.createElement("a"))
boton.children[0].setAttribute("class","nav-link")
boton.children[0].appendChild(document.createElement("span"))
boton.children[0].children[0].setAttribute("class","fas fa-download fa-fw")
boton.children[0].children[0].setAttribute("aria-hidden","true")
boton.children[0].appendChild(document.createElement("span"))
boton.children[0].children[1].setAttribute("class","d-none d-md-inline")
boton.children[0].children[1].innerText = "Export"
document.getElementsByClassName("nav nav-tabs")[0].insertBefore(boton,document.getElementsByClassName("nav nav-tabs")[0].children[4])
}