// ==UserScript== // @name Total Duration Time + Total Episodes Of Any Animes Franchises // @namespace DurationByFranchise // @version 1.0.0.10 // @description This is a tool to easily and quickly see how long it will take for you to finish watching the whole anime Franchise, and you can also see how many episodes and entries the Franchise has. // @author hacker09 // @include /^https:\/\/myanimelist\.net\/anime\/[\d]+(\/.*)?/ // @match https://chiaki.site/?/tools/watch_order/id/* // @match https://chiaki.site/?/tools/watch_order/group_id/* // @icon https://www.google.com/s2/favicons?domain=myanimelist.net // @run-at document-end // @grant GM_deleteValue // @grant GM_listValues // @grant GM_getValue // @grant GM_setValue // @downloadURL none // ==/UserScript== (function() { 'use strict'; var Remaining = ''; //Creates a new blank variable var TotalHrMins = []; //Creates a new blank array var TotalEpsResult = []; //Creates a new blank array var TotalEpisodesTypeTV = ['0']; //Creates a new array var TotalEpisodesTypeOVA = ['0']; //Creates a new array var TotalEpisodesTypeONA = ['0']; //Creates a new array var TotalEpisodesTypeMusic = ['0']; //Creates a new array var TotalEpisodesTypeMovie = ['0']; //Creates a new array var TotalEpisodesTypeSpecial = ['0']; //Creates a new array if (top.location.host === 'myanimelist.net') { //Starts the if condition var animeid = location.pathname.match(/\d+/)[0]; //Detect the anime id var CompletedAnimeIdsArray = []; //Creates a new blank array GM_listValues().forEach(a => CompletedAnimeIdsArray.push('^' + a)); //Add all anime IDs on tampermonkey to the array var CompletedAnimeIdsRegex = new RegExp(CompletedAnimeIdsArray.join('$|')); //Create a new variable and regex containing all the values saved on tampermonkey and replace the , separator with the or $| regex symbols if (document.querySelector("#myinfo_status.btn-user-status-add-list.js-form-user-status.js-form-user-status-btn.myinfo_addtolist") === null) { //If the anime is on the user list document.querySelectorAll("#myinfo_status")[1].addEventListener('change', function() { //Listen for the anime entry status changes if (this.value !== '6' && this.value !== '1') { //If any status besides Plan To Watch and Watching is selected GM_setValue(animeid, 'Completed Anime Id'); //Get and save the anime id as a variable } //Finishes the if condition if (this.value === '6') //If Plan To Watch is selected { //Starts the if condition GM_deleteValue(animeid); //Remove the anime if of the completed script anime ids storage } //Finishes the if condition }, false); //Finishes the change advent listener } //Finishes the if condition if (document.querySelector("#myinfo_status.btn-user-status-add-list.js-form-user-status.js-form-user-status-btn.myinfo_addtolist") === null) { //If the entry is on the user list document.querySelectorAll("i.fa.fa-plus-circle")[1].addEventListener('click', function() { //Listen for clicks on the + 1 ep button setTimeout(function() { //Starts the function settimeout if (document.querySelector("#myinfo_watchedeps").value === document.querySelector("#curEps").innerText) //If the number of watched eps is = the total entry eps { //Starts the if condition GM_setValue(animeid, 'Completed Anime Id'); //Get and save the anime id as a variable } //Finishes the if condition }, 800); //Finishes the function settimeout }, false); //Finishes the click advent listener } //Finishes the if condition } //Finishes the if condition async function ProcessRawTextContent() //Creates a function to Process the RawTextContent { //Starts the function if (top.location.host === 'myanimelist.net') //If The User Is On The https://myanimelist.net/ Website Fetch Chiaki { //Starts the if condition var IsUserOnMAL = true; const response = await fetch('https://api.allorigins.win/raw?url=https://chiaki.site/?/tools/watch_order/id/' + animeid); //Fetch const html = await response.text(); //Gets the fetch response const newDocument = new DOMParser().parseFromString(html, 'text/html'); //Parses the fetch response var TextElement = newDocument.querySelectorAll("span.uk-text-muted.uk-text-small"); //Creates a variable to loop though the elements after } //Finishes the if condition else //If The User Is On The https://chiaki.site/ Website Start Processing The Content { //Starts the else condition var IsUserOnMAL = false; var TextElement = document.querySelectorAll("span.uk-text-muted.uk-text-small"); //Creates a variable to loop though the elements after } //Finishes the else condition for (var i = 0; i < TextElement.length; i++) { //Starts the for condition var TotalRawDurationHasSecs = TextElement[i].textContent.split("× ")[1].match('sec'); //Creates a variable to check later if there's an entry that has secs var TotalRawDuration = TextElement[i].textContent.split("× ")[1].split(' |')[0].match(/\d+|\?/g); //Creates a variable to hold the total unprocessed times var TotalEpisodes = TextElement[i].textContent.split("× ")[0].split(' |')[2].match(/\d+|\?/g); //Creates a variable to hold the total episodes var EpisodeType = TextElement[i].textContent.split("× ")[0].split(' |')[1]; //Creates a variable to check the episode types TotalEpsResult.push(TotalEpisodes); //Add The Eps To The Array if (IsUserOnMAL === true) //If The User Is On The https://myanimelist.net/ Website { //Starts the if condition if (TextElement[i].children[0].href.match(/\d+/)[0].match(CompletedAnimeIdsRegex) !== null && CompletedAnimeIdsRegex.toLocaleString() !== '/(?:)/') //If the current url anime id matches an anime entry that was already completed, and if the Regex contains 1 or more anime ids { //Starts the if condition TotalRawDuration = '?'; //Remove the Total Raw Duration value of this anime to not count it later Remaining = 'Remaining '; //Adds the text "Remaining" to the variable } //Finishes the if condition } //Finishes the if condition if (TotalRawDuration.length !== 1 && TotalRawDurationHasSecs === null) //If has Hrs and Mins and not secs { //Starts the if condition var ExtractHrs = TotalRawDuration[0] * 60; //Extract Hrs And Convert To Mins var TotalHrs = TotalEpisodes * ExtractHrs; //Multiply Eps By Hrs var TotalMins = TotalEpisodes * TotalRawDuration[1]; //Multiply Extracted Eps By Mins TotalHrMins.push(TotalHrs, TotalMins); //Add Hrs And Mins To The Array } //Finishes the if condition else if (TotalRawDurationHasSecs === null) //Extract only Mins and not secs { //Starts the else condition TotalMins = TotalEpisodes * TotalRawDuration[0]; //Multiply Extracted Eps By Mins TotalHrMins.push(TotalMins); //Add Mins To The Array } //Finishes the else condition if (EpisodeType.match('Music') !== null) //If it's Music { //Starts the if condition TotalEpisodesTypeMusic.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition if (EpisodeType.match('TV') !== null) //If it's TV { //Starts the if condition TotalEpisodesTypeTV.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition if (EpisodeType.match('OVA') !== null) //If it's OVA { //Starts the if condition TotalEpisodesTypeOVA.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition if (EpisodeType.match('Special') !== null) //If it's Special { //Starts the if condition TotalEpisodesTypeSpecial.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition if (EpisodeType.match('ONA') !== null) //If it's ONA { //Starts the if condition TotalEpisodesTypeONA.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition if (EpisodeType.match('Movie') !== null) //If it's Movie { //Starts the if condition TotalEpisodesTypeMovie.push(TotalEpisodes); //Add The Eps To The Array } //Finishes the if condition } //Finishes the for condition var TotalEpsFinal = TotalEpsResult.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b); //Sum The Total Eps var TotalMinsResult = TotalHrMins.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b); //Sum Hrs in Mins + Total Mins var ONAEpisodesResult = TotalEpisodesTypeONA.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' ONA(s)'; //Sum The Total Eps var OVAEpisodesResult = TotalEpisodesTypeOVA.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' OVA(s)'; //Sum The Total Eps var TVEpisodesResult = TotalEpisodesTypeTV.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' TV Episode(s)'; //Sum The Total Eps var MusicEpisodesResult = TotalEpisodesTypeMusic.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' Music(s)'; //Sum The Total Eps var MovieEpisodesResult = TotalEpisodesTypeMovie.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' Movie(s)'; //Sum The Total Eps var SpecialEpisodesResult = TotalEpisodesTypeSpecial.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b) + ' Special(s)'; //Sum The Total Eps //The Commands Below Converts The Total Franchise Time To Precise Days,Hours And Minutes var days = Math.floor(TotalMinsResult / 1440); var hours = Math.floor((TotalMinsResult % 1440) / 60); var minutes = (TotalMinsResult % 1440) % 60; //Start the function to correctly format the date and time from now function formatDateTime(date) { //Starts the function var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var hh = date.getHours(); var m = date.getMinutes(); var s = date.getSeconds(); var dd = "AM"; var h = hh; if (h >= 12) { h = hh - 12; dd = "PM"; } if (h == 0) { h = 12; } month = month < 10 ? "0" + month : month; day = day < 10 ? "0" + day : day; m = m < 10 ? "0" + m : m; s = s < 10 ? "0" + s : s; h = h < 10 ? "0" + h : h; var display = month + "/" + day + "/" + year + " " + h + ":" + m; display += ":" + s; display += " " + dd; return display; } //Finishes the function var SecondsFromNow = 0; //Creates a new variable var date, date2; //Creates a new global variable function UpdateFinishTime() { //Starts the function UpdateFinishTime var DisplayedFranchiseDays = $('div.spaceit:contains("Franchise Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) day\(s\) )?(?:(\d+) hr\(s\) )?(\d+) min\(s\)/)[1]; //Gets the actual displayed franchise days and save the days number to a variable var DisplayedFranchiseHours = $('div.spaceit:contains("Franchise Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) day\(s\) )?(?:(\d+) hr\(s\) )?(\d+) min\(s\)/)[2]; //Gets the actual displayed franchise hour and save the hour number to a variable var DisplayedFranchiseMinutes = $('div.spaceit:contains("Franchise Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) day\(s\) )?(?:(\d+) hr\(s\) )?(\d+) min\(s\)/)[3]; //Gets the actual displayed franchise minutes and save the minutes number to a variable date = new Date(); //Creates a new date var secondsToAdd = DisplayedFranchiseDays * 60 * 60 * 24 + DisplayedFranchiseHours * 60 * 60 + DisplayedFranchiseMinutes * 60 + SecondsFromNow; //Convert the actual Total/Remaining Franchise Duration days,hours and minutes to seconds date.setSeconds(date.getSeconds() + secondsToAdd); if (document.querySelector("td.borderClass").innerText.search("Remaining time:") > -1) //If the text "Remaining time:" exists { //Starts the if condition var RemainingEntryHours = parseInt($('div.spaceit:contains("Remaining time") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[1]); //Get and save the entry remaining hours var RemainingEntryMinutes = parseInt($('div.spaceit:contains("Remaining time") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[2]); //Get and save the entry remaining minutes date2 = new Date(); //Creates a new date var secondsToAdd2 = 0 * 60 * 60 * 24 + RemainingEntryHours * 60 * 60 + RemainingEntryMinutes * 60 + SecondsFromNow; //Convert the actual "Remaining time" entry Duration hours and minutes to seconds date2.setSeconds(date2.getSeconds() + secondsToAdd2); } //Finishes the if condition } //Finishes the function UpdateFinishTime var title = 'title="' + OVAEpisodesResult + ' ' + ONAEpisodesResult + ' ' + MusicEpisodesResult + ' ' + SpecialEpisodesResult + ' ' + MovieEpisodesResult + ' ' + TVEpisodesResult + '"'; //Content that will be shown on mouse hover if (TextElement.length === 1) //If there's only 1 entry on chiaki.site for the whole franchise { //Starts the if condition var html; //Creates a blank variable IsUserOnMAL === true && document.querySelector("td.borderClass").innerText.search("Remaining time:") > -1 ? html = '' : html = 'Franchise Duration: ' + days + ' day(s) ' + hours + ' hr(s) ' + minutes + ' min(s)'; //If the text "Remaining time:" exists don't show the Franchise Duration text var HasMoreThan1Entry = false; //Create a varible to confirm that the Franchise thas less than 1 entry } //Finishes the if condition else //If there's more than 1 entry on chiaki.site for the whole franchise { //Starts the else condition html = Remaining + ' Franchise Duration: ' + days + ' day(s) ' + hours + ' hr(s) ' + minutes + ' min(s)'; //Shows the real html on MAL HasMoreThan1Entry = true; //Create a varible to confirm that the Franchise thas more than 1 entry if (IsUserOnMAL === true) //If The User Is On The https://myanimelist.net/ Website { //Starts the if condition var EpsSeen, EntryTotalHours, EntryTotalMinutes; //Make these variables global function GetWatchedEps() { //Starts the function GetWatchedEps() EpsSeen = parseInt(document.querySelector("#myinfo_watchedeps").value); //Get the total number of eps seen and convert the value to int EntryTotalHours = $('div.spaceit:contains("Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[1] * EpsSeen * 60; //Get the Entry Total Hours of seen episodes and convert from hrs to mins EntryTotalMinutes = parseInt($('div.spaceit:contains("Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[2] * EpsSeen); //Get the Entry Total Minutes of seen episodes if ($('div.spaceit:contains("Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[1] === undefined) { //Starts the if condition EntryTotalHours = 0; //Set the Entry Total Hours as 0 } //Finishes the if condition } //Finishes the function if (document.querySelectorAll("i.fa.fa-plus-circle").length === 2) //If the anime is already on the user list { //Starts the if condition document.querySelectorAll("i.fa.fa-plus-circle")[1].onclick = function() { //Adds an advent listener to the plus button on the right side of the anime image setTimeout(async function() { //Creates and starts the settimout function await GetWatchedEps(); //Starts the function GetWatchedEps() days = Math.floor((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) / 1440); //Sum again the total Franchise days duration - the total watched eps duration time hours = Math.floor(((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) % 1440) / 60); //Sum again the total Franchise hours duration - the total watched eps duration time minutes = ((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) % 1440) % 60; //Sum again the total Franchise minutes duration - the duration total watched eps time $('div.spaceit:contains("Franchise Duration") > span')[0].textContent = 'Remaining Franchise Duration: '; //Add "Remaining" in front of the Franchise Duration text $('div.spaceit:contains("Franchise Duration") > span')[0].nextSibling.textContent = days + ' day(s) ' + hours + ' hr(s) ' + minutes + ' min(s)'; //Update the Franchise Duration Time if ($('div.spaceit:contains("Remaining time") > span')[0] !== undefined) //If the "Remaining time" text exists { //Starts the if condition var TotalEntryHours = (parseInt($('div.spaceit:contains("Total Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[1] * 60)); //Get and save the entry remaining hours, and convert to minutes var TotalEntryMinutes = (parseInt($('div.spaceit:contains("Total Duration") > span')[0].nextSibling.textContent.match(/(?:(\d+) hr\. )?(\d+) min\./)[2])); //Get and save the entry remaining minutes var RemainingTimeHours = Math.floor((TotalEntryMinutes + TotalEntryHours - EntryTotalHours - EntryTotalMinutes % 1440) / 60); //Sum the total remaining Franchise hours duration - the total watched eps duration time var RemainingTimeMinutes = (TotalEntryMinutes + TotalEntryHours - EntryTotalHours - EntryTotalMinutes % 1440) % 60; //Sum again the total remaining Franchise minutes duration - the duration total watched eps time $('div.spaceit:contains("Remaining time") > span')[0].nextSibling.textContent = ' ' + RemainingTimeHours + ' hr. ' + RemainingTimeMinutes + ' min.'; //Update the "Remaining time" Duration } //Finishes the if condition }, 800); //Finishes the settimeout function }; //Finishes the onclick advent listener } //Finishes the if condition if (document.querySelector("#myinfo_watchedeps").value !== '0' && document.querySelector("#myinfo_watchedeps").value !== '' && parseInt(document.querySelector("#curEps").innerText) >= 1 && animeid.match(CompletedAnimeIdsRegex) === null) //If there's at least 1 watched episode, and if the entry has more than 1 episode, and if the animeid has the PTW or Watching status { //Starts the if condition GetWatchedEps(); //Starts the function GetWatchedEps() days = Math.floor((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) / 1440); //Sum again the total Franchise days duration - the total watched eps duration time hours = Math.floor(((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) % 1440) / 60); //Sum again the total Franchise hours duration - the total watched eps duration time minutes = ((TotalMinsResult - EntryTotalHours - EntryTotalMinutes) % 1440) % 60; //Sum again the total Franchise minutes duration - the duration total watched eps time var html = 'Remaining Franchise Duration: ' + days + ' day(s) ' + hours + ' hr(s) ' + minutes + ' min(s)'; //Shows the real html on MAL } //Finishes the if condition } //Finishes the if condition } //Finishes the else condition if (IsUserOnMAL === true) //If The User Is On The https://myanimelist.net/ Website { //Starts the if condition function findTheInformationheader() { const headers = [...document.querySelectorAll("h2")]; //Select all h2 elements on MAL return headers.find(h2 => h2.textContent === "Information"); } //Find the h2 element that has the text Information function findTheRatingText() { const allInfo = [...findTheInformationheader().parentNode.querySelectorAll("div")]; //Select all divs inside the Information h2 element return allInfo.find(info => info.innerText.includes("Rating")); } //Find the Rating text that's inside the information h2 element findTheRatingText().insertAdjacentHTML('beforeend', '
' + html + '
'); //Show The Total Duration if (HasMoreThan1Entry === true) //If the Franchise has more than 1 Entry { //Starts the if condition findTheRatingText().insertAdjacentHTML('beforeend', '
Franchise Episodes: ' + TotalEpsFinal + '
'); //Show The Total Episodes } //Finishes the if condition findTheRatingText().insertAdjacentHTML('beforeend', '
Franchise Entries: ' + TextElement.length + '
'); //Show The Total Entries } //Finishes the if condition else //If The User Is On The https://chiaki.site/ Website { //Starts the else condition document.querySelector("ul.uk-flex-center.noborder.uk-tab").insertAdjacentHTML('beforeend', '
  • Total Duration: ' + days + ' day(s) ' + hours + ' hr(s) ' + minutes + ' min(s)
  • '); //Show The Total Duration if (HasMoreThan1Entry === true) //If the Franchise has more than 1 Entry { //Starts the if condition document.querySelector("ul.uk-flex-center.noborder.uk-tab").insertAdjacentHTML('beforeend', '
  • Total Episodes: ' + TotalEpsFinal + '
  • '); //Show The Total Episodes document.querySelector("ul.uk-flex-center.noborder.uk-tab").insertAdjacentHTML('beforeend', '
  • Total Entries: ' + TextElement.length + '
  • '); //Show The Total Entries } //Finishes the if condition } //Finishes the else condition if (IsUserOnMAL === true) //If The User Is On The https://myanimelist.net/ Website { //Starts the if condition $('span.dark_text:contains("Franchise Duration")').css("cursor", "pointer"); //Make the "Franchise Duration" text look like it's clickable $('span.dark_text:contains("Franchise Duration")')[0].onclick = function() { //When the Franchise Duration text is clicked UpdateFinishTime(); //Starts the function UpdateFinishTime if (document.querySelector("td.borderClass").innerText.search("Remaining time:") > -1) //If the text "Remaining time:" exists { //Starts the if condition alert('You will finish watching this 🡳: (Without stopping)\nEntry on: ' + formatDateTime(date2) + '\nFranchise on: ' + formatDateTime(date)); //Display a message showing the days and hours from now } //Finishes the if condition else //If the text "Remaining time:" does NOT exist { //Starts the else condition alert('You will finish watching this franchise without stopping on ' + formatDateTime(date)); //Display a message showing the days and hours from now } //Finishes the else condition }; //Finishes the onclick advent listener } //Finishes the if condition } //Finishes the async function ProcessRawTextContent(); //Starts the function })();