Anime list total time - MAL

Shows the total amount of time you will take to watch all entries on your anime list.

当前为 2022-08-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         Anime list total time - MAL
// @namespace    https://gf.qytechs.cn/en/users/670188-hacker09?sort=daily_installs
// @version      4
// @description  Shows the total amount of time you will take to watch all entries on your anime list.
// @author       hacker09
// @match        https://myanimelist.net/profile/*
// @icon         https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://myanimelist.net&size=64
// @grant        GM.xmlHttpRequest
// @run-at       document-end
// ==/UserScript==

(async function() {
  'use strict';
  document.head.insertAdjacentHTML('beforeend', '<style>.di-ib.fl-r.lh10 {cursor: pointer;}</style>'); //Make each status total entry number look clickable
  document.querySelectorAll(".di-ib.fl-r.lh10").forEach(async function(el, i) { //ForEach status total entry number
    el.onclick = async function() //When the user clicks on the status total entry number
    { //Starts the onclick event listener
      var stats; //Set a global variable
      var EntryIds = []; //Create a new array
      var TotalEntrySecs = [0]; //Create a new array

      alert(`This will take ${parseInt(el.innerText.match(/\d+,?\d+/g)[0].replace(',','') * 200 / 1000 / 60) % 60 !== 0 ? parseInt(el.innerText.match(/\d+,?\d+/g)[0].replace(',','') * 200 / 1000 / 60) % 60 + ' minutes and ' : '' }${parseInt(el.innerText.match(/\d+,?\d+/g)[0].replace(',','') * 200 / 1000) % 60} seconds to complete.`); //Let the user know how log it will take

      switch (i) { //Detect the user choice
        case 0: //If the user clicked on Watching
          stats = 'watching'; //Change the variable value
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#2db039'; //Change the bg color to match the selected status
          break; //Stop executing the switch statement
        case 1: //If the user clicked on Completed
          stats = 'completed'; //Change the variable value
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#26448f'; //Change the bg color to match the selected status
          break; //Stop executing the switch statement
        case 2: //If the user clicked on On-Hold
          stats = 'on_hold'; //Change the variable value
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#f9d457'; //Change the bg color to match the selected status
          break; //Stop executing the switch statement
        case 3: //If the user clicked on Dropped
          stats = 'dropped'; //Change the variable value
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#a12f31'; //Change the bg color to match the selected status
          break; //Stop executing the switch statement
        case 4: //If the user clicked on Plan to Watch
          stats = 'plan_to_watch'; //Change the variable value
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#c3c3c3'; //Change the bg color to match the selected status
          break; //Stop executing the switch statement
      } //Finishes the switch statement

      var url = `https://api.myanimelist.net/v2/users/${location.href.split('/')[4]}/animelist?nsfw=true&fields=list_status&status=${stats}&limit=1000`; //API url
      while (true) { //Starts the while condition to get the Total Number of Entries on the user list
        var entries = await new Promise((resolve) => GM.xmlHttpRequest({ //Starts the xmlHttpRequest
          responseType: 'json',
          url: url,
          headers: {
            "x-mal-client-id": "8ef0267fd86a187d479e6fcd7e1bb42a"
          },
          onload: r => resolve(r.response)
        })); //Finishes the xmlHttpRequest

        if (entries === undefined) //If the API is being rated
        { //Starts the if condition
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#fff'; //Change the bg color to match the selected status
          alert('Cool down!\nThe MAL API is being time rated!') //Show a message to the user
          console.log(`%cFailed getting ${location.href.split('/')[4]}'s anime list`, "font-size: 15px; color: red;"); //Shows an error message
          return; //Stop the script
        } //Finishes the if condition
        if (entries.message !== undefined && entries.message.match('restricted') !== null) //If the user has a private list
        { //Starts the if condition
          document.querySelector("h5,.pt16.pb12").style.backgroundColor = '#fff'; //Change the bg color to match the selected status
          alert(location.href.split('/')[4] + ' has a private anime list!') //Show a message to the user
          console.log(`%cFailed getting ${location.href.split('/')[4]}'s anime list`, "font-size: 15px; color: red;"); //Shows an error message
          return; //Stop the script
        } //Finishes the if condition

        url = entries.paging.next; //Update the url variable with the next page url
        if (entries.paging.next === undefined) //If the next page has less than 1000 entries stop looping the while condition
        { //Starts the if condition
          entries.data.forEach(el => EntryIds.push(el.node.id)); //Save all entry ids
        } //Finishes the if condition

        if (entries.data.length !== 1000) //If the next page has less than 1000 entries stop looping the while condition
        { //Starts the if condition
          console.log(`%cFinished Getting ${location.href.split('/')[4]}'s Total Anime Entries Number!`, "font-size: 15px;"); //Shows a console message
          EntryIds.forEach(async function(id, index) { //For each entry id
            setTimeout(async function() { //Starts the settimeout
              var TotalTimeandEps = await new Promise((resolve) => GM.xmlHttpRequest({ //Starts the xmlHttpRequest
                responseType: 'json',
                url: `https://api.myanimelist.net/v2/anime/${id}?fields=num_episodes,average_episode_duration`,
                headers: {
                  "x-mal-client-id": "8ef0267fd86a187d479e6fcd7e1bb42a"
                },
                onload: r => resolve(r.response)
              })); //Finishes the xmlHttpRequest

              TotalEntrySecs.push(TotalTimeandEps.num_episodes * TotalTimeandEps.average_episode_duration); //Multiply the total entry amount of eps Eps By total entry Secs and To The Array
              var TotalStatusSecs = TotalEntrySecs.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b); //Sum Total Secs
              var days = Math.floor(TotalStatusSecs / (3600 * 24)); //Get total days amount
              var hours = parseInt(TotalStatusSecs / 3600) % 24; //Get total hours amount
              var minutes = parseInt(TotalStatusSecs / 60) % 60; //Get total minutes amount Math.floor((TotalStatusSecs / 60) % 1440) % 60
              var seconds = TotalStatusSecs % 60; //Get total seconds amount
              document.querySelector("h5,.pt16.pb12").innerText = 'Anime Stats (' + days + ' Days ' + hours + ' Hours ' + minutes + ' Minutes ' + seconds + ' Seconds)'; //Show the total time

              if (EntryIds.length - 1 === index) //If it's the last loop
              { //Starts the if condition
                document.querySelector("h5,.pt16.pb12").innerText += ' ✔️'; //Adds a checkmark after the total time
              } //Finishes the if condition
            }, index * 200); //Finishes the settimeout function
          }); //Finishes the for each condition
          return; //Stop the script if fetched page has less than 1000 entries and run the EntryIds.forEach loop
        } //Finishes the if condition
      } //Finishes the while condition
    }; //Finishes the onclick event listener
  }); //Finishes the foreach loop
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址