您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows the total amount of time you will take to watch all entries on your anime list.
// ==UserScript== // @name Anime list total time - MAL // @namespace https://gf.qytechs.cn/en/users/670188-hacker09?sort=daily_installs // @version 5 // @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 // @connect api.myanimelist.net // @grant GM.xmlHttpRequest // @run-at document-end // ==/UserScript== (async function() { 'use strict'; document.head.insertAdjacentHTML('beforeend', '<style>span.lh10 {cursor: pointer;}</style>'); //Make each status total entry number look clickable document.querySelectorAll("span.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 const ListIds = []; //Create a new array var stats; //Creates a new global variable const TotalEntrySecs = [1]; //Create a new array const limit = 1250; //Creates a new global const const delay = 360000; //Creates a new global const const AnimeStats = document.querySelector("h5,.pt16.pb12"); //Get the anime stats header const StatsTotEntries = parseInt(el.innerText.match(/\d+(?:,\d+)?(?= *\(|$)/g)[0].replace(',', '')); //Get the status total entries number if (document.querySelector(".updates.anime > p") !== null) //If the user has a private list { //Starts the if AnimeStats.innerText = `Anime Stats (${location.href.split('/')[4]} has a private anime list!)`; //Change the Anime Stats text throw (`${location.href.split('/')[4]} has a private anime list!`); //Stop the script } //Finishes the if condition alert(`This process will complete ${Math.round(StatsTotEntries / limit) * delay / 1000 / 60 !== 0 ? `in ${Math.round(StatsTotEntries / limit) * delay / 1000 / 60} minutes` : 'instantaneously'}.`); //Let the user know how long the process will take switch (i) { //Detect the user choice case 0: //If the user clicked on Watching stats = 'watching'; //Change the variable value AnimeStats.style.color = '#2db039'; //Change the txt 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 AnimeStats.style.color = '#26448f'; //Change the txt 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 AnimeStats.style.color = '#f9d457'; //Change the txt 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 AnimeStats.style.color = '#a12f31'; //Change the txt 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 AnimeStats.style.color = '#c3c3c3'; //Change the txt 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 const entries = await new Promise((resolve) => GM.xmlHttpRequest({ //Starts the xmlHttpRequest url: url, headers: { "x-mal-client-id": "8ef0267fd86a187d479e6fcd7e1bb42a" }, onload: r => resolve(r) })); //Finishes the xmlHttpRequest if (entries.status !== 200) //If the API is being rated { //Starts the if condition AnimeStats.style.color = ''; //Change the bg color to default AnimeStats.innerText = 'Anime Stats (Cool down! The MAL API is being time rate limited!)'; //Change the Anime Stats text return false; //Stop the script } //Finishes the if condition AnimeStats.innerText = 'Anime Stats'; //Change the Anime Stats text url = JSON.parse(entries.responseText).paging.next; //Update the url variable with the next page url JSON.parse(entries.responseText).data.forEach(el => ListIds.push(el.node.id)); //Save all entry ids if (JSON.parse(entries.responseText).data.length !== 1000) //If the next page has less than 1000 entries stop looping the while condition { //Starts the if condition for (var index = 0; index < ListIds.length; index += limit) { //For Each limit+ entries ListIds.slice(index, index + limit).forEach(async function(id) { //For Each limit+ entries ids if (AnimeStats.style.color !== '') //If the txt color isn't the default { //Starts the if condition const response = await new Promise((resolve) => GM.xmlHttpRequest({ //Starts the xmlHttpRequest url: `https://api.myanimelist.net/v2/anime/${id}?fields=num_episodes,average_episode_duration`, headers: { "x-mal-client-id": "8ef0267fd86a187d479e6fcd7e1bb42a" }, onload: r => resolve(r) })); //Finishes the xmlHttpRequest await new Promise(r => setTimeout(r, 200)); //Wait 200 ms to continue if (response.status !== 200) //If the API is being rated { //Starts the if condition AnimeStats.style.color = ''; //Change the txt color to default AnimeStats.innerText = 'Anime Stats (Cool down! The MAL API is being time rate limited!)'; //Change the Anime Stats text } //Finishes the if condition else //If the API is not being rated { //Starts the else condition const TotalTimeandEps = JSON.parse(response.responseText); //Parse the response as json 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 const Per = ((100 * (TotalEntrySecs.length - 1)) / StatsTotEntries).toFixed(0); //Get the currently completed process % const TotalStatusSecs = TotalEntrySecs.filter(Boolean).map(i => Number(i)).reduce((a, b) => a + b); //Sum Total Secs const days = Math.floor((TotalStatusSecs - 1) / (3600 * 24)); //Get total days amount const hours = parseInt((TotalStatusSecs - 1) / 3600) % 24; //Get total hours amount const minutes = parseInt((TotalStatusSecs - 1) / 60) % 60; //Get total minutes amount const seconds = (TotalStatusSecs - 1) % 60; //Get total seconds amount AnimeStats.title = 'Total Entries Processed: ' + (TotalEntrySecs.length - 1); //Add the total processed entries on mouse hover AnimeStats.innerText = `Anime Stats (${days} Days ${hours} Hours ${minutes} Minutes ${seconds} Seconds) ${Per}%`; //Show the total time and % ListIds.length === TotalEntrySecs.length - 1 ? AnimeStats.innerText = AnimeStats.innerText.replace(/\d+%/, '✔️') : ''; //If the processed entries is = the status entries add ✔️ } //Finishes the else condition } //Finishes the if condition }); //Finishes the for each loop await new Promise(r => setTimeout(r, delay)); //Wait 6 mins before making the next API calls } //Finishes the for loop return; //Stop the script if the fetched page has less than 1000 entries and starts the ListIds.forEach loop } //Finishes the if condition } //Finishes the while condition }; //Finishes the onclick event listener }); //Finishes the foreach loop })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址