Generate a List With The Animes/Mangas Titles that were Re-Watched/Re-Read

Easily and quickly generate a list with all ReWatched/ReRead entries and how many times.

Від 16.09.2023. Дивіться остання версія.

  1. // ==UserScript==
  2. // @name Generate a List With The Animes/Mangas Titles that were Re-Watched/Re-Read
  3. // @namespace MAL Automatic Anime/Manga List Generator
  4. // @version 14
  5. // @description Easily and quickly generate a list with all ReWatched/ReRead entries and how many times.
  6. // @author hacker09
  7. // @match https://myanimelist.net/profile/*
  8. // @exclude https://myanimelist.net/profile/*/*
  9. // @icon https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://myanimelist.net&size=64
  10. // @grant GM.xmlHttpRequest
  11. // @connect anime.jhiday.net
  12. // @run-at document-end
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. var HomeresponseText, TotalCompletedEntries, HTMLresponse, AccExists, type, text, FinalHTML = '<a id="dwnldLnk"</a>', CompleteJSONList = [], progress = 1, nextpagenum = 0, increaseby = 300; //Make these variables global
  18.  
  19. GM.xmlHttpRequest({ //Start a new xmlHttpRequest to get the last update info and check whether the account exists
  20. url: `https://anime.jhiday.net/hof/user/${location.pathname.split('/')[2]}`,
  21. onload: ({ responseText }) => { //When the xmlHttpRequest is completed
  22. HomeresponseText = responseText; //Create a new const
  23. if (new DOMParser().parseFromString(HomeresponseText, "text/html").body.innerText.search("Not Found") > -1) //If the account doesn't exist
  24. { //Starts the if condition
  25. AccExists = false; //Creates a new variable
  26. GM.xmlHttpRequest({ //Start a new xmlHttpRequest to create an account
  27. method: "GET",
  28. url: "https://anime.jhiday.net/auth/redirect",
  29. onload: ({ responseText }) => { //When the xmlHttpRequest is completed
  30. grecaptcha.execute(new DOMParser().parseFromString(responseText, "text/html").querySelector('meta[name="recaptcha_site_key"]').content, { action: "submit" }).then((recaptchaResponse) => { //ByPass the Google Recaptcha
  31. GM.xmlHttpRequest({ //Start a new xmlHttpRequest to create an account
  32. method: "POST",
  33. url: "https://myanimelist.net/submission/authorization",
  34. headers: { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "content-type": "application/x-www-form-urlencoded" },
  35. data: `action_type=approve_authz&csrf_token=${new DOMParser().parseFromString(responseText, "text/html").querySelector('meta[name="csrf_token"]').content}&g-recaptcha-response=${recaptchaResponse}`
  36. });}); //Finishes the xmlHttpRequest
  37. } //Finishes the onload function
  38. }); //Finishes the xmlHttpRequest function
  39. } //Finishes the if condition
  40. else //If the account already exists
  41. { //Starts the else condition
  42. AccExists = true; //Creates a new variable
  43. GM.xmlHttpRequest({ //Start a new xmlHttpRequest to update the user stats
  44. url: `https://anime.jhiday.net/hof/ajax/update-user/${location.pathname.split('/')[2]}`
  45. }); //Finishes the xmlHttpRequest
  46. } //Finishes the else condition
  47. } //Finishes the onload function
  48. }); //Finishes the xmlHttpRequest function
  49.  
  50. document.querySelectorAll("li.clearfix.mb12 > span")[7].outerHTML = `<span title="Click to generate the Rewatched Anime List" class="di-ib fl-l fn-grey2" style="color: ${document.querySelector(".dark-mode") !== null ? '#abc4ed' : 'blue'} !important; cursor: pointer;">Rewatched</span>`; //The CSS for the ReWatched "button"
  51. document.querySelectorAll("li.clearfix.mb12")[6].onclick = function() //When the ReWatched text is clicked
  52. { //Starts the onclick event listener
  53. type = 'anime'; //Change the variable type
  54. text = 'ReWatched Anime'; //Change the variable type
  55.  
  56. if (AccExists && confirm(`OK = Instantly shows the ${text} list.\n\nCancel = Gets the most recent ${text} list.\n(This process will take ${new Date(parseInt(document.querySelectorAll("span.di-ib.fl-r.lh10")[1].innerText.replace(',', '')) * 200).toLocaleTimeString([], { minute: "numeric", second: "2-digit", })} minutes to complete)`)) //Show the confirmation alert box text if the acc exists
  57. { //Starts the if condition
  58. GM.xmlHttpRequest({ //Start a new xmlHttpRequest to get the user ReWatches
  59. url: `https://anime.jhiday.net/hof/ajax/rewatches/${location.pathname.split('/')[2]}`,
  60. onload: ({ responseText }) => { //When the xmlHttpRequest is completed
  61. document.documentElement.innerHTML = `<title>Done! List Generated!</title><style>img.malIcon {display: none;}</style><div style='max-width:650px; font-size: 18px; margin:0px auto; white-space: nowrap;'><a style='position: absolute; left:10px;'>Last Updated ${new DOMParser().parseFromString(HomeresponseText, "text/html").querySelector("#updateBlock > p > i").innerText}</a><a href='https://myanimelist.net/profile/${location.pathname.split('/')[2]}' style='margin-inline-start: 226px;'>Return To User Profile</a><a id='dwnldLnk' style='margin-left: 910px !important; white-space: nowrap; margin-top: -21px !important; float: left;' download='${location.pathname.split('/')[2]} ${text} List!.html' title='${location.pathname.split('/')[2]} ${text} List!.html'>Download List</a><h1> ${location.pathname.split('/')[2]} ${text} List</h1><h3><em>List of Entries that ${location.pathname.split('/')[2]} has ReWatched/ReRead:</em></h3><ul>${new DOMParser().parseFromString(responseText, "text/html").querySelector("body > ul:nth-child(4)").innerHTML.replaceAll('/hof', '')}</ul></div>`; //Add the ReWatched/ReRead list on the page
  62. document.getElementById('dwnldLnk').href = `data:text/css;charset=utf-8,${encodeURIComponent(document.documentElement.innerHTML.replace('Download List', ' '))}`; //Adds the scrapped results as a link on the a element
  63. scrollTo({ top: 0, behavior: "smooth" }); //Scroll the page to the top
  64. } //Finishes the onload function
  65. }); //Finishes the xmlHttpRequest function
  66. } //Finishes the if condition
  67. else //If the user chose Cancel or the Account doesn't exist
  68. { //Starts the else condition
  69. AccExists === false && parseInt(document.querySelectorAll(".fl-r > li > .fl-r")[1].innerText.replace(',', '')) !== 0 ? alert(`This process will take ${new Date(parseInt(document.querySelectorAll("span.di-ib.fl-r.lh10")[1].innerText.replace(',', '')) * 200).toLocaleTimeString([], { minute: "numeric", second: "2-digit", })} minutes to complete`) : ''; //Displays a message
  70. NETscrape(); //Start the NETscrape function
  71. } //Finishes the else condition
  72. }; //Finishes the onclick event listener
  73.  
  74. document.querySelectorAll("li.clearfix.mb12 > span")[18] === undefined ? '' : document.querySelectorAll("li.clearfix.mb12 > span")[18].outerHTML = `<span title="Click to generate the Reread Manga List" class="di-ib fl-l fn-grey2" style="color: ${document.querySelector(".dark-mode") !== null ? '#abc4ed' : 'blue'} !important; cursor: pointer;">Reread</span>`; //The CSS for the ReRead "button"
  75. document.querySelectorAll("li.clearfix.mb12")[14] === undefined ? '' : document.querySelectorAll("li.clearfix.mb12")[14].onclick = function() //When the ReRead text is clicked
  76. { //Starts the onclick event listener
  77. type = 'manga'; //Change the variable type
  78. text = 'ReRead Manga'; //Change the variable type
  79. parseInt(document.querySelectorAll(".fl-r > li > .fl-r")[4].innerText.replace(',', '')) !== 0 ? alert(`This process will take ${new Date(parseInt(document.querySelectorAll("span.di-ib.fl-r.lh10")[6].innerText.replace(',', '')) * 200).toLocaleTimeString([], { minute: "numeric", second: "2-digit", })} minutes to complete`) : ''; //Displays a message
  80. NETscrape(); //Start the NETscrape function
  81. }; //Finishes the onclick event listener
  82.  
  83. async function NETscrape() { //Starts the NETscrape function
  84. if ((type === 'anime' && parseInt(document.querySelectorAll(".fl-r > li > .fl-r")[1].innerText.replace(',', '')) === 0) || (type === 'manga' && parseInt(document.querySelectorAll(".fl-r > li > .fl-r")[4].innerText.replace(',', '')) === 0)) //If the user haven't ReWatched or ReRead anything
  85. { //Starts the if condition
  86. alert(`The user ${location.pathname.split('/')[2]} doesn\'t have any ${text}!`); //Displays a message
  87. throw (`The user ${location.pathname.split('/')[2]} doesn\'t have any ${text}!`); //Stops the script
  88. } //Finishes the if condition
  89.  
  90. document.body.insertAdjacentHTML('beforeEnd', '<div id="loadingScreen" style="position: fixed;width: 100%;height: 100%;background-color: #00000054;top: 0;z-index: 1000;background-image: url(https://i.imgur.com/ka06oyE.gif);background-repeat: no-repeat;background-position: center;"></div>'); //Show the loading screen
  91.  
  92. while (true) { //Starts the while condition to get the Total Number of Entries on the user-completed list
  93. document.title = 'Getting all completed entries...'; //Shows the current process on the tab title
  94. const ListJSON = await (await fetch(`https://myanimelist.net/${type}list/${location.pathname.split('/')[2]}/load.json?status=2&offset=${nextpagenum}`)).json(); //Fetch
  95. nextpagenum += increaseby; //Increase the next page number
  96. var totalanimestwo = ListJSON.length; //Store the Total Completed Entries Number
  97. ListJSON.forEach(el => CompleteJSONList.push(el)); //Save the current page json entries on the CompleteJSONList variable
  98. TotalCompletedEntries += totalanimestwo; //Sum the Total Completed Entries Number and add the result to the variable TotalCompletedEntries
  99. if (totalanimestwo !== 300) //If the next page has less than 300 completed entries stop the while condition
  100. { //Starts the if condition
  101. for (const el of CompleteJSONList) { //Starts a for loop for every single entry JSON response
  102. document.title = `Processing entry ${progress++} of ${CompleteJSONList.length}`; //Shows the current process on the tab title
  103. HTMLresponse = await (await fetch("https://myanimelist.net/includes/ajax-no-auth.inc.php?t=6", { //Fetch the more info btn HTML codes for every single entry JSON response
  104. "headers": {
  105. "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  106. },
  107. "body": `color=1&id=${type === 'anime' ? el.anime_id : el.manga_id}&memId=${document.querySelector('input[name="profileMemId"]') === null ? document.querySelector(".mr0").href.match(/\d+/) : document.querySelector('input[name="profileMemId"]').value}&type=${type}&csrf_token=${document.head.querySelector("[name='csrf_token']").content}`,
  108. "method": "POST"
  109. })).text(); //Finishes the fetch
  110.  
  111. const newDocument = new DOMParser().parseFromString(HTMLresponse, 'text/html'); //Parses the fetch response
  112.  
  113. if ((type === 'anime' && parseInt(newDocument.querySelector('body > table > tbody > tr > td > div > a > strong').innerText.split('<')[0]) >= 1) || (type === 'manga' && parseInt(newDocument.body.textContent.match(/(?<=Times Read: )[0-9]+/)[0]) >= 1)) { //If the current entry has been ReWatched/ReRead at least once
  114. FinalHTML += `<li>${(type === 'anime' && parseInt(newDocument.querySelector('body > table > tbody > tr > td > div > a > strong').innerText.split('<')[0]) > 1) || (type === 'manga' && parseInt(newDocument.body.textContent.match(/(?<=Times Read: )[0-9]+/)[0]) >= 1) ? `<b>[x${type === 'anime' ? newDocument.querySelector('body > table > tbody > tr > td > div > a > strong').innerText.split('<')[0] : parseInt(newDocument.body.textContent.match(/(?<=Times Read: )[0-9]+/)[0])}] </b>` : ''}<a href='https://myanimelist.net${type === 'anime' ? el.anime_url : el.manga_url}'>${type === 'anime' ? el.anime_title : el.manga_title}</a></li>`; //Add the entry info on the Final HTML codes result
  115. } //Finishes the if condition
  116. } //Finishes the for loop
  117.  
  118. document.documentElement.innerHTML = FinalHTML === '<a id="dwnldLnk"</a>' ? `The Rewatched/ReRead entries number shown on the user ${location.pathname.split('/')[2]} profile page is wrong and outdated!<br><br>${location.pathname.split('/')[2]} currently has no Rewatched/ReRead entries!` : `<title>Done! List Generated!</title><div style='max-width:650px; font-size: 18px; margin:0px auto; white-space: nowrap;'><a href='https://myanimelist.net/profile/${location.pathname.split('/')[2]}' style='margin-inline-start: 226px;'>Return To User Profile</a><a id='dwnldLnk' style='margin-left: 910px !important; white-space: nowrap; margin-top: -21px !important; float: left;' download='${location.pathname.split('/')[2]} ${text} List!.html' title='${location.pathname.split('/')[2]} ${text} List!.html'>Download List</a><h1> ${location.pathname.split('/')[2]} ${text} List</h1><h3><em>List of Entries that ${location.pathname.split('/')[2]} has ReWatched/ReRead:</em></h3><ul>${FinalHTML}</ul></div>`; //Add the ReWatched/ReRead list on the page
  119.  
  120. [...document.querySelectorAll("ul > li")].sort((a, b) => { //Sorts the list
  121. return ((b.textContent.match(/\[x(\d+)\]/) ? parseInt(b.textContent.match(/\[x(\d+)\]/)[1]) : -1) - (a.textContent.match(/\[x(\d+)\]/) ? parseInt(a.textContent.match(/\[x(\d+)\]/)[1]) : -1)) || a.textContent.localeCompare(b.textContent);}).forEach(item => document.querySelector("ul").appendChild(item)); //Sort by the amount of ReWatches/ReReads first, then alphabetically
  122.  
  123. document.querySelector("#dwnldLnk").href = `data:text/css;charset=utf-8,${encodeURIComponent(document.documentElement.innerHTML.replace('Download List', ''))}`; //Adds the scrapped results as a link on the a element
  124.  
  125. scrollTo({ top: 0, behavior: "smooth" }); //Scroll the page to the top
  126. return; //Stops the while condition
  127. } //Finishes the if condition
  128. } //Finishes the while condition
  129. } //Finishes the NETscrape function
  130. })();