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.

// ==UserScript==
// @name         Generate a List With The Animes/Mangas Titles that were Re-Watched/Re-Read
// @namespace    MAL Automatic Anime/Manga List Generator
// @version      23
// @description  Easily and quickly generate a list with all ReWatched/ReRead entries and how many times.
// @author       hacker09
// @match        https://myanimelist.net/profile/*
// @exclude      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
// @connect      anime.jhiday.net
// @run-at       document-end
// ==/UserScript==

(function() {
  'use strict';
  var HomeresponseText, TotalCompletedEntries, HTMLresponse, AccExists, type, text, FinalHTML = '<a id="dwnldLnk"</a>', CompleteJSONList = [], progress = 1, nextpagenum = 0, increaseby = 300; //Make these variables global

  GM.xmlHttpRequest({ //Start a new xmlHttpRequest to get the last update info and check whether the account exists
    url: `https://anime.jhiday.net/hof/user/${location.pathname.split('/')[2]}`,
    onload: ({ responseText }) => { //When the xmlHttpRequest is completed
      HomeresponseText = responseText; //Create a new const
      if (new DOMParser().parseFromString(HomeresponseText, "text/html").body.innerText.search("Not Found") > -1) //If the account doesn't exist
      { //Starts the if condition
        AccExists = false; //Creates a new variable
        GM.xmlHttpRequest({ //Start a new xmlHttpRequest to create an account
          method: "GET",
          url: "https://anime.jhiday.net/auth/redirect",
          onload: ({ responseText }) => { //When the xmlHttpRequest is completed
            grecaptcha.execute(new DOMParser().parseFromString(responseText, "text/html").querySelector('meta[name="recaptcha_site_key"]').content, { action: "submit" }).then((recaptchaResponse) => { //ByPass the Google Recaptcha
              GM.xmlHttpRequest({ //Start a new xmlHttpRequest to create an account
                method: "POST",
                url: "https://myanimelist.net/submission/authorization",
                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" },
                data: `action_type=approve_authz&csrf_token=${new DOMParser().parseFromString(responseText, "text/html").querySelector('meta[name="csrf_token"]').content}&g-recaptcha-response=${recaptchaResponse}`
         });}); //Finishes the xmlHttpRequest
          } //Finishes the onload function
        }); //Finishes the xmlHttpRequest function
      } //Finishes the if condition
      else //If the account already exists
      { //Starts the else condition
        AccExists = true; //Creates a new variable
        GM.xmlHttpRequest({ method: 'POST', url: `https://anime.jhiday.net/hof/ajax/update-user/${location.pathname.split('/')[2]}` }); //Update the user stats
      } //Finishes the else condition
    } //Finishes the onload function
  }); //Finishes the xmlHttpRequest function

  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"
  document.querySelectorAll("li.clearfix.mb12")[6].onclick = function() //When the ReWatched text is clicked
  { //Starts the onclick event listener
    type = 'anime'; //Change the variable type
    text = 'ReWatched Anime'; //Change the variable type

    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
    { //Starts the if condition
      GM.xmlHttpRequest({ //Start a new xmlHttpRequest to get the user ReWatches
        url: `https://anime.jhiday.net/hof/ajax/rewatches/${location.pathname.split('/')[2]}`,
        onload: ({ responseText }) => { //When the xmlHttpRequest is completed
          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
          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
          scrollTo({ top: 0, behavior: "smooth" }); //Scroll the page to the top
        } //Finishes the onload function
      }); //Finishes the xmlHttpRequest function
    } //Finishes the if condition
    else //If the user chose Cancel or the Account doesn't exist
    { //Starts the else condition
      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
      NETscrape(); //Start the NETscrape function
    } //Finishes the else condition
  }; //Finishes the onclick event listener

  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"
  document.querySelectorAll("li.clearfix.mb12")[14] === undefined ? '' : document.querySelectorAll("li.clearfix.mb12")[14].onclick = function() //When the ReRead text is clicked
  { //Starts the onclick event listener
    type = 'manga'; //Change the variable type
    text = 'ReRead Manga'; //Change the variable type
    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
    NETscrape(); //Start the NETscrape function
  }; //Finishes the onclick event listener

  async function NETscrape() { //Starts the NETscrape function
    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
    { //Starts the if condition
      alert(`${location.pathname.split('/')[2]} doesn\'t have any ${text}!`); //Displays a message
      throw (`${location.pathname.split('/')[2]} doesn\'t have any ${text}!`); //Stops the script
    } //Finishes the if condition

    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

    while (true) { //Starts the while condition to get the Total Number of Entries on the user-completed list
      document.title = 'Getting all completed entries...'; //Shows the current process on the tab title
      const ListJSON = await (await fetch(`https://myanimelist.net/${type}list/${location.pathname.split('/')[2]}/load.json?status=2&offset=${nextpagenum}`)).json(); //Fetch
      nextpagenum += increaseby; //Increase the next page number
      var totalanimestwo = ListJSON.length; //Store the Total Completed Entries Number
      ListJSON.forEach(el => CompleteJSONList.push(el)); //Save the current page json entries on the CompleteJSONList variable
      TotalCompletedEntries += totalanimestwo; //Sum the Total Completed Entries Number and add the result to the variable TotalCompletedEntries
      if (totalanimestwo !== 300) //If the next page has less than 300 completed entries stop the while condition
      { //Starts the if condition
        for (const el of CompleteJSONList) { //Starts a for loop for every single entry JSON response
          document.title = `Processing entry ${progress++} of ${CompleteJSONList.length}`; //Shows the current process on the tab title
          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
            "headers": {
              "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
            },
            "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}`,
            "method": "POST"
          })).text(); //Finishes the fetch

          const newDocument = new DOMParser().parseFromString(HTMLresponse, 'text/html'); //Parses the fetch response

          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
            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
          } //Finishes the if condition
        } //Finishes the for loop

        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

        [...document.querySelectorAll("ul > li")].sort((a, b) => { //Sorts the list
          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

        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

        scrollTo({ top: 0, behavior: "smooth" }); //Scroll the page to the top
        return; //Stops the while condition
      } //Finishes the if condition
    } //Finishes the while condition
  } //Finishes the NETscrape function
})();