MangaDex Follows

Manage your follows page

As of 2020-10-15. See the latest version.

// ==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])
}