// ==UserScript== // @name Mangadex Plus // @namespace https://greasyfork.org/users/553660 // @icon https://mangadex.org/images/misc/navbar.svg // @version 1.0 // @description Adds a few useful features to Mangadex // @author Mr. M // @match https://mangadex.org/* // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // @downloadURL none // ==/UserScript== (function() { 'use strict'; /* TODO - Show managa cover on hover */ /* Global values */ var domain = [ "mangadex.org" ] var url = window.location; var nVer = "1.0"; /* Global functions */ // Get the domain from the visiting website function url_domain(data) { var a = document.createElement('a'); a.href = data; return a.hostname; } // Add a global CSS style by inputting a String function addGlobalStyle(css) { var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; head.appendChild(style); } // Get site subpage name/directory from provided url function getSubPage(directoryIndex){ let url = document.location.href; var segment = url.replace(/^https?:\/\//, '').split('/')[directoryIndex]; return segment; } // Get current Url function currentUrl(){ return document.location.href; } /* Storage defaults */ let first_time; if (GM_getValue("first_time") != "false"){ first_time = false; GM_setValue("first_time", "false"); //GM_setValue("folders_index", 0); let folders = {"folders" : []}; GM_setValue("folders", JSON.stringify(folders)); let options = {"options" : [{"preloading" : 0}]}; GM_setValue("options", JSON.stringify(options)); GM_setValue("update_notif", "true"); alert("Thank you for installing Mangadex Plus! Go to Settings -> About to learn more about Mangadex Plus!") } // Version check (last version / newest version) if (GM_getValue("version") != nVer && first_time == undefined){ GM_setValue("version", nVer); GM_setValue("update_notif", "true") alert("A feature update is here! MD+ has been updated to " + GM_getValue("version") + "! Check Change Log in MD+ Settings for more info!"); } var red_badge; if (GM_getValue("update_notif") == "true"){ GM_setValue("notif_badge", ' ! ') } else{ GM_setValue("notif_badge", '') } /* Site functions */ // Startup functions function startup(){ if (getSubPage(1) == "title"){ main("title"); } else if (getSubPage(1) == "chapter"){ main("chapter") } else{ main("other"); } } /* HTML Elements */ // Color categorizing CSS classes -> "badge" || "text" + "-" + "success"/"danger"/"primary"/nothing //let friends_icon = ''; //red_badge = ' ! '; //let green_badge = ' ! '; //let msg_icon = ''; let container = document.getElementsByTagName("body")[0]; let pageMask = '
'; let closeButton = ''; /* Functions and features */ function main(level){ let menuButton = ''; if (level == "title" || (level == "chapter" || level == "other")){ function mainButton(){ container.insertAdjacentHTML("afterbegin", menuButton); addGlobalStyle("#menuButton { position: fixed; z-index: 99999; top: 10px; right: 15px; list-style-type: none; border-radius: 0.25rem; padding: unset;}"); addGlobalStyle("p.dropdown-item { margin: 0px; }"); folders(); settings(); options(); } mainButton(); } if (level == "title"){ function cardButtons(){ } cardButtons(); actionsBar(); } if (level == "chapter"){ function readerFeatures(){ preload(); } readerFeatures(); } } function closeMenu(){ try{ document.getElementsByClassName("plus-box")[0].remove(); document.getElementById("page-mask").remove(); document.getElementsByClassName("plus-clsBtn")[0].remove(); } catch(e){ //Nothing } try{ document.getElementsByClassName("plus-options-window")[0].remove(); document.getElementById("page-mask").remove(); document.getElementsByClassName("plus-clsBtn")[0].remove(); } catch(e){ //Nothing } } function settings(){ let notes = "(*) This will restore default values for options.
" + "(**) This will wipe all Storage data (Folders and entries).
" + "(***) This will restore default values for options and wipe storage (Folders and entries)." let items = ''; function openSettingsMenu(){ container.insertAdjacentHTML("afterbegin", items); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-reset-wipe")[0].addEventListener("click", function(){resetMDP("rw")}); document.getElementsByClassName("plus-reset")[0].addEventListener("click", function(){resetMDP("r")}); document.getElementsByClassName("plus-wipe")[0].addEventListener("click", function(){resetMDP("w")}); document.getElementsByClassName("plus-change-log")[0].addEventListener("click", changeLog); document.getElementsByClassName("plus-about")[0].addEventListener("click", about); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); } function about(){ GM_setValue("update_notif", "false"); GM_setValue("notif_badge", "") let text = "Welcome to Mangadex Plus!

" + "\"What is Mangadex Plus?\"
Mangadex Plus is a Userscript that adds some useful features to Mangadex.

" + "\"What are these Useful features you speak of?\"
These currently include:

" + "" + "

For more info on the features head to the Change Log. As of now these are all but more features will be added through time.

" + "\"I really want a feature to be added to this script! Do you take suggestions?\"
I do so Welcomely. Please go to the GreasyFork page of this Userscript and add a suggestion there.

" + "\"Hey! I found a bug! Where can I report It?\"
Please report bugs on the GreasyFork page of this Userscript and I'll do my best to fix them ASAP.

" + '

To remove the red badge ( ! ) please reload the page.'; let element = '

'; closeMenu(); container.insertAdjacentHTML("afterbegin", element); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementsByClassName("plus-back-settings")[0].addEventListener("click", function(){closeMenu(); settings(); openSettingsMenu();}); document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); } function changeLog(){ let text = "Mangadex Plus version 1.0 is here!

" + "The very first version brings these features to the table:

" + "

" + "I am planning on improving the current features and adding new ones. If you have a suggestion/bug report please let me know on the Userscript's GreasyFork page forum."; let element = '

'; closeMenu(); container.insertAdjacentHTML("afterbegin", element); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementsByClassName("plus-back-settings")[0].addEventListener("click", function(){closeMenu(); openSettingsMenu();}); document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); } function resetMDP(level){ function prompt(level){ let text_rw = "WARNING: By confirming this you will DELETE ALL Your Mangadex Plus saved data! This includes options and folders! \nAre you sure you want to proceed?"; let text_r = "WARNING: By confirming this you will RESET ALL Your Mangadex Plus options to Default values! \nAre you sure you want to proceed?"; let text_w = "WARNING: By confirming this you will WIPE ALL Your Mangadex Plus folders and It's Entries! \nAre you sure you want to proceed?"; if (level == "rw"){ if (confirm(text_rw)) { resets("rw"); } } else if (level == "r"){ if (confirm(text_r)) { resets("r"); } } else if (level == "w"){ if (confirm(text_w)) { resets("w"); } } } prompt(level); } function checkKey(e) { e = e || window.event; if (e.keyCode == '27' || e.keyCode == '27') { closeMenu(); } } document.onkeydown = checkKey; document.getElementsByClassName("plus-settings")[0].addEventListener("click", openSettingsMenu); } function folders(){ function getFolders(){ let folders = (JSON.parse(GM_getValue("folders"))).folders; let backFolder = '' let createFolder = '' let otherFolders = ""; let edit_btn = ''; let delete_btn = ''; for (let i = 0; i < folders.length; i++){ otherFolders = otherFolders + '
' + edit_btn + delete_btn + '
'; } return backFolder + otherFolders + createFolder; } function folderItems(index){ let backFolder = '' let folders = (JSON.parse(GM_getValue("folders"))).folders; let folder_name = (JSON.parse(GM_getValue("folders"))).folders[index].name; let delete_btn = '

' + '

'; let html = ""; for (let i = 0; i < (folders[index].entries).length; i++){ html = html + '
' + delete_btn + '
'; } html = backFolder + html; return ''; } function createFolder(){ if (editing_busy == false){ editing_busy = true; function writeFolder(){ let value = "" + document.getElementsByClassName("folderInput")[0].value; let folders = JSON.parse(GM_getValue("folders")); folders.folders[folders.folders.length] = {"name" : value + "", "entries" : []}; GM_setValue("folders", JSON.stringify(folders)); editing_busy = false; } let option = document.getElementsByClassName("dropdown-item plus-createFolder")[0]; option.innerHTML = ''; document.getElementsByClassName("plus-createFolder")[0].removeEventListener("click", createFolder); function checkKey(e) { e = e || window.event; if (e.keyCode == '13') { writeFolder(); closeMenu(); openFoldersMenu(); document.onkeydown = null } } document.onkeydown = checkKey; document.getElementsByClassName("folderInput")[0].addEventListener("onsubmit", writeFolder); document.getElementsByClassName("folderInput")[0].select(); } } function refreshFolders(){ return ''; } function openFoldersMenu(){ container.insertAdjacentHTML("afterbegin", refreshFolders()); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); addGlobalStyle(".plus-disabled{ color: #555}"); addGlobalStyle(".plus-disabled:hover{ background-color: unset; color: #555}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-createFolder")[0].addEventListener("click", createFolder); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); let folders = (JSON.parse(GM_getValue("folders"))).folders; for (let i = 0; i < folders.length; i++){ document.getElementsByClassName("folder_" + i)[0].parentNode.addEventListener("click", function(){browseFolder(i)}); document.getElementsByClassName("plus-delete-folder")[i].addEventListener("click", function(){deleteFolder(i)}); document.getElementsByClassName("plus-edit-folder")[i].addEventListener("click", function(){editFolder(i)}); } } function browseFolder(index){ closeMenu(); container.insertAdjacentHTML("afterbegin", folderItems(index)); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementsByClassName("plus-back")[0].addEventListener("click", function(){closeMenu();openFoldersMenu();}) document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); let folders = (JSON.parse(GM_getValue("folders"))).folders[index].entries; for (let i = 0; i < folders.length; i++){ document.getElementsByClassName("plus-delete-entry")[i].addEventListener("click", function(){deleteEntry(index, i)}); } } function deleteFolder(index){ let folder = JSON.parse(GM_getValue("folders")); function prompt(){ if(confirm("Folder " + folder.folders[index].name + " will be deleted. Proceed?")){ folder.folders.splice(index, 1); GM_setValue("folders", JSON.stringify(folder)); closeMenu(); openFoldersMenu(); } } prompt(); } function deleteEntry(folder, entry){ let folders = JSON.parse(GM_getValue("folders")); function prompt(){ if(confirm("\"" + folders.folders[folder].entries[entry].title + "\" will be removed from this Folder. Proceed?")){ folders.folders[folder].entries.splice(entry, 1); GM_setValue("folders", JSON.stringify(folders)); closeMenu(); browseFolder(folder); } } prompt(); } let editing_busy = false; function editFolder(index){ if (editing_busy == false){ editing_busy = true; function writeFolder(){ let value = "" + document.getElementsByClassName("folderInput")[0].value; let folders = JSON.parse(GM_getValue("folders")); folders.folders[index].name = value; GM_setValue("folders", JSON.stringify(folders)); editing_busy = false; } let folder = JSON.parse(GM_getValue("folders")); let option = document.getElementsByClassName("folder_" + index)[0].parentNode; option.innerHTML = ''; document.getElementsByClassName("folderInput")[0].value = "" + folder.folders[index].name var el = document.getElementsByClassName('folderInput')[0].parentNode, elClone = el.cloneNode(true); el.parentNode.replaceChild(elClone, el); function checkKey(e) { e = e || window.event; if (e.keyCode == '13') { writeFolder(); closeMenu(); openFoldersMenu(); document.onkeydown = null } } document.onkeydown = checkKey; document.getElementsByClassName("folderInput")[0].addEventListener("onsubmit", writeFolder); document.getElementsByClassName("folderInput")[0].select(); } } function checkKey(e) { e = e || window.event; if (e.keyCode == '27' || e.keyCode == '27') { closeMenu(); } } document.onkeydown = checkKey; document.getElementsByClassName("plus-folders")[0].addEventListener("click", openFoldersMenu); addGlobalStyle("p.dropdown-item { overflow: hidden;}"); addGlobalStyle(".plus-delete-folder:hover { color: #f00;}"); addGlobalStyle(".plus-delete-entry:hover { color: #f00;}"); } function options(){ var temp_storage = { "preloading" : JSON.parse(GM_getValue("options")).options[0].preloading } var style = window.getComputedStyle(document.getElementById('homepage_settings_modal')); addGlobalStyle(".plus-options-window {position: fixed; left: 0px !important; right: 0px !important; width: 800px !important; margin-left: auto; margin-right: auto; margin-top: 100px; margin-bottom: auto;)"); addGlobalStyle(".modal-footer{display: block;}"); let preloading = '
' + '' + '
' + '' + '
' + '
'; let notes = '

(*) Automatically starts preloading chapter when in the reader. Only works for logged-in users.

' let options = //''; function listeners(){ //Listeners for options buttons for(let i = 0; i < document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a").length; i++){ document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[i].addEventListener("click", function(){selectOption("preloading", i)}); } document.getElementsByClassName("plus-options-save")[0].addEventListener("click", saveOptions); } function openOptionsMenu(){ container.insertAdjacentHTML("afterbegin", options); addGlobalStyle("div.plus-box { position: fixed !important; right: 700px !important; left: 700px !important; top: 100px !important; z-index: 100001;}"); container.insertAdjacentHTML("afterbegin", pageMask); addGlobalStyle("#page-mask { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100000;}"); container.insertAdjacentHTML("afterbegin", closeButton); addGlobalStyle(".plus-clsBtn { right: 150px !important; position: fixed !important; z-index: 100000; width: auto; color: white; background-color: #444;}"); document.getElementById("page-mask").addEventListener("click", closeMenu); document.getElementsByClassName("plus-clsBtn")[0].addEventListener("click", closeMenu); document.getElementsByClassName("plus-close")[0].addEventListener("click", closeMenu); loadOptions(); listeners(); } function checkKey(e) { e = e || window.event; if (e.keyCode == '27' || e.keyCode == '27') { closeMenu(); } } let sample_options = { "options" : [ { "preloading" : 0 } ] } function saveOptions(){ GM_setValue("options", "{\"options\":[" + JSON.stringify(temp_storage) + "]}") location.reload() } function loadOptions(){ var options = JSON.parse(GM_getValue("options")); let preloading = options.options[0].preloading; selectOption("preloading", preloading); } function selectOption(option, index){ if(option == "preloading"){ let val; for(let i = 0; i < document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a").length; i++){ try{ document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[i].classList.remove('selected'); document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[i].classList.remove('active'); val = document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[index].childNodes[1].innerHTML; } catch(e){/*Nothing*/} } document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[index].classList.add("active"); document.getElementsByClassName("plus-preloading-options")[0].getElementsByTagName("a")[index].classList.add("selected"); document.getElementsByClassName("plus-preloading-selected")[0].innerHTML = val; temp_storage.preloading = index; } } document.onkeydown = checkKey; document.getElementsByClassName("plus-options")[0].addEventListener("click", openOptionsMenu); } function actionsBar(){ function makeBar(){ let bar = '
' + '
Mangadex+ Actions:
' + '
' + '
' + '
'; var position = document.getElementsByClassName("col-xl-9 col-lg-8 col-md-7")[0]; position.insertAdjacentHTML("beforeend", bar); } function addToFolder(){ function addEntry(index){ let folders = JSON.parse(GM_getValue("folders")); let link = ("" + window.location).replace("#", ""); let pass = true; for(let i = 0; i < folders.folders[index].entries.length; i++){ try{ if(link == folders.folders[index].entries[0].link){ pass = false; } } catch(e){ if (e == TypeError){ pass = true; } } } if (pass == true){ folders.folders[index].entries[folders.folders[index].entries.length] = {"title": "" + document.getElementsByClassName("card-header")[0].childNodes[3].innerHTML, "link" : "" + link}; GM_setValue("folders","" + JSON.stringify(folders)); } } function listFolders(){ let folders = JSON.parse(GM_getValue("folders")); let html = ""; for (let i = 0; i < folders.folders.length; i++){ html = html + ''; } return html; } let bar = '
' + '' + '' + '
 '; let position2 = document.getElementsByClassName("plus-actionbar")[0]; position2.insertAdjacentHTML("beforeend", bar); let folders = JSON.parse(GM_getValue("folders")); for (let i = 0; i < folders.folders.length; i++){ document.getElementsByClassName("plus-add-folder-" + i)[0].addEventListener("click", function(){addEntry(i)}); } function reloadFolders(){ let index = document.getElementsByClassName("plus-actions-folders")[0].childNodes.length for(let i = 0; i < index; i++){ document.getElementsByClassName("plus-add-folder-" + i)[0].remove(); } let position3 = document.getElementsByClassName("plus-actions-folders")[0]; position3.insertAdjacentHTML("afterbegin", listFolders()); let folders = JSON.parse(GM_getValue("folders")); for (let i = 0; i < folders.folders.length; i++){ document.getElementsByClassName("plus-add-folder-" + i)[0].addEventListener("click", function(){addEntry(i)}); } } document.getElementsByClassName("plus-folder-trigger")[0].addEventListener("click", reloadFolders); } function markAs(){ let mark_button = '
' + '' + '' + '
 '; let position2 = document.getElementsByClassName("plus-actionbar")[0]; position2.insertAdjacentHTML("beforeend", mark_button); } function startReading(){ let manga_id = getSubPage(2); let chapter_id = ""; let api_url = "https://mangadex.org/api/manga/" + manga_id; async function getapi(url) { let response = await fetch(url); var data = await response.json(); var data2 = ""; for(let i = 0; i < Object.entries(data.chapter).length; i++){ if (await Object.entries(Object.entries(data.chapter)[i][1])[3][1] == "English"){ data2 = await Object.entries(data.chapter)[i][0]; break; } } data.chapter[data2] return data2 } getapi(api_url).then(x => { chapter_id = x; let link = "https://mangadex.org/chapter/" + chapter_id; let mark_button = '' var position2 = document.getElementsByClassName("plus-actionbar")[0]; position2.insertAdjacentHTML("beforeend", mark_button); }); } makeBar(); addToFolder(); markAs(); startReading(); } function preload(){ async function doPreload(){ let index = 100; for (let i = 0; i < index; i++){ if (document.getElementById("preload-all").getAttribute("disabled") == ""){ await new Promise(r => setTimeout(r, 100)); } else{ document.getElementById("preload-all").click(); i = index; } } } let enabled = (JSON.parse(GM_getValue("options"))).options[0].preloading if (enabled == 0){ doPreload(); } function checkKey(e) { e = e || window.event; if (e.keyCode == '37' || e.keyCode == '39') { doPreload(); } } document.onkeydown = checkKey; document.getElementsByClassName("reader-images col-auto row no-gutters flex-nowrap m-auto text-center cursor-pointer directional constrained")[0].onclick = function(){doPreload()}; } function resets(level){ if(level == "r"){ let options = {"options" : [{"preloading" : 0}]}; GM_setValue("options", JSON.stringify(options)); location.reload(); } else if(level == "w"){ let folders = {"folders" : []}; GM_setValue("folders", JSON.stringify(folders)); location.reload(); } else if(level == "rw"){ let options = {"options" : [{"preloading" : 0}]}; GM_setValue("options", JSON.stringify(options)); let folders = {"folders" : []}; GM_setValue("folders", JSON.stringify(folders)); GM_deleteValue("first_time"); location.reload(); } } startup(); })();