Greasy Fork is available in English.

Have we ever talked before? - MAL

Quickly know if you have ever received Private Messages or profile comments from specific users by opening the user's profile page or by hovering over the user image/name on any page on MAL.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Have we ever talked before? - MAL
// @namespace    MALCuriosity
// @version      13
// @description  Quickly know if you have ever received Private Messages or profile comments from specific users by opening the user's profile page or by hovering over the user image/name on any page on MAL.
// @author       hacker09
// @match        https://myanimelist.net/*
// @icon         https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://myanimelist.net&size=64
// @run-at       document-end
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        window.close
// @grant        GM_listValues
// @grant        GM.xmlHttpRequest
// ==/UserScript==

(async function() {
  'use strict';
  var newDocument, ProfileID; //Creates a new global variable
  var HoveredUserName = ''; //Creates a new global variable

  if (GM_getValue('ScriptUserName') === undefined) //If the variable ScriptUserName doesn't exist yet
  { //Starts the if condition
    GM_setValue('ScriptUserName', document.querySelector("a.header-profile-link").innerText); //Save the User Name to the variable ScriptUserName
  } //Finishes the if condition

  async function GetComments() //Creates a function to get the user id and Starts the function
  { //Starts the function
    if (location.href.split('/')[3] === 'profile' && location.href.split('/')[4] !== GM_getValue('ScriptUserName') && HoveredUserName === '') //If the opened page is a profile and if the profile username isn't of the account owner profile, and if the variable HoveredUserName is equal nothing
    { //Starts the if condition
      ProfileID = document.querySelector("input[name*=profileMemId]") !== null ? document.querySelector("input[name*=profileMemId]").value : document.querySelector(".mr0").href.match(/\d+/g)[0]; //Save the user id to the variable ProfileID
    } //Finishes the if condition
    else //If the opened page isn't a profile page
    { //Starts the else condition
      const response1 = await (await fetch('https://api.jikan.moe/v4/users/' + HoveredUserName)).json(); //Fetch
      ProfileID = response1.data.mal_id; //Saves the user ID to the variable ProfileID
    } //Finishes the else condition

    const response = await (await fetch('https://myanimelist.net/comtocom.php?id1=' + ProfileID + '&id2=' + GM_getValue("ScriptUserID"))).text(); //Fetch
    newDocument = new DOMParser().parseFromString(response, 'text/html'); //Parses the fetch response
  } //Finishes the async function

  document.querySelectorAll("a[href*='/profile/']").forEach(Element => Element.addEventListener("mouseover", async function() { //Get all the profile link elements and add an event listener to the link element

    if (this.title.match('comments') === null) //If the element doesn't have the word comments on its title attribute
    { //Starts the if condition
      HoveredUserName = this.href.split('/')[4]; //Save the hovered username to a variable
      this.title = "Hover again to see the updated comments and PMs message."; //Add the text "comments" to the username/image, so that even if the element is hovered again too fast the fetch request won't happen again
      await GetComments(); //Starts the function

      if (newDocument.body.innerText.search("No comments found") > -1) //If the text "No comments found" exists
      { //Starts the if condition
        this.title = "❌ There are no comments between you and this user!"; //Add a text to the username/image
      } //Finishes the if condition
      else //If the text "No comments found" doesn't exist
      { //Starts the else condition
        this.title = "✅ There are comments between you and this user!"; //Add a text to the username/image
      } //Finishes the else condition

      if (GM_listValues().includes(HoveredUserName)) //If the current HoveredUserName is on the user PMed list
      { //Starts the if condition
        this.title = this.title + "\n✅ There are PMs between you and this user!"; //Add a text to the username/image
      } //Finishes the if condition
      else //If the current HoveredUserName isn't on the user PMed list
      { //Starts the else condition
        this.title = this.title + "\n❌ There are no PMs between you and this user!"; //Add a text to the username/image
      } //Finishes the else condition
    } //Finishes the if condition
  })); //Finishes the forEach

  if (location.href === 'https://myanimelist.net/mymessages.php' || (GM_getValue('ScriptUserID') === undefined && location.href.split('/')[4] === GM_getValue('ScriptUserName'))) //If the opened profile username is the account owner profile and if the variable ScriptUserID doesn't exist yet or if the opened URL is = 'https://myanimelist.net/mymessages.php'
  { //Starts the if condition

    var array = []; //Creates a new global array
    var nextpagenum = 0; //Creates a new variable
    if (GM_getValue('ScriptUserID') === undefined && location.href.split('/')[4] === GM_getValue('ScriptUserName')) //If the opened profile username is the account owner profile and if the variable ScriptUserID doesn't exist yet
    { //Starts the if condition
      const response1 = await (await fetch('https://api.jikan.moe/v4/users/' + location.href.split('/')[4])).json(); //Fetch
      GM_setValue('ScriptUserID', response1.data.mal_id); //Save the user id to the variable ScriptUserID
    } //Finishes the if condition

    while (true) { //While the fetched page contains User Names
      const url = 'https://myanimelist.net/mymessages.php?go=&show=' + nextpagenum; //Creates a variable to fetch the PM pages
      var response = await (await fetch(url)).text(); //Fetches the PM pages and converts the fetched pages to text
      const newDocument = new DOMParser().parseFromString(response, 'text/html'); //Parses the fetch response
      nextpagenum += 20; //Increase the next page number by 20

      if (location.href === 'https://myanimelist.net/mymessages.php') //If the opened URL is = 'https://myanimelist.net/mymessages.php'
      { //Starts the if condition
        var DisplayedTotalPMs = 999999999; //Creates a new variable to make the script fetch only the 1-page
      } //Finishes the if condition
      else //If the opened URL isn't = 'https://myanimelist.net/mymessages.php'
      { //Starts the else condition
        DisplayedTotalPMs = parseInt(newDocument.querySelector("div.di-ib").innerText.match(/\d+/g)[2]); //Creates a new variable
      } //Finishes the else condition

      for (const UserNames of newDocument.querySelectorAll("div.mym.mym_user")) { //For every single User Name that sent a PM to the user
        array.push(UserNames.innerText); //Get and save the all mal User Names that sent PMs to the script user
      } //Finishes the for condition

      if (DisplayedTotalPMs >= parseInt(newDocument.querySelector("div.di-ib").innerText.match(/\d+/g)[0])) { //If the fetched page displayed messages total number is greater or equal the User total inbox messages number
        array = [...new Set(array)]; //Remove the duplicated usernames of the array
        array = array.filter(d => !GM_listValues().includes(d)); //Remove the duplicated usernames of the array comparing the usernames that the array has and tampermonkey is missing
        array.forEach(name => GM_setValue(name, 'PMed MAL User Name')) //Get and save the current PMed MAL User Name

        if (location.href !== 'https://myanimelist.net/mymessages.php') //If the opened URL isn't = 'https://myanimelist.net/mymessages.php'
        { //Starts the if condition
          close(); //Close the current tab
        } //Finishes the if condition
        return; //Make the while condition false and stop fetching
      } //Finishes the if condition
      await new Promise(resolve => setTimeout(resolve, 600)); //Timeout to start the next fetch request
    } //Finishes the while condition
  } //Finishes the if condition

  if (location.href.match(/https:\/\/myanimelist\.net\/profile\/[^\/]+(\/)?$/) !== null && location.href.split('/')[4] !== GM_getValue('ScriptUserName')) //If the opened page is a profile page and the profile username isn't of the script user profile
  { //Starts the if condition
    const HasCommented = document.createElement("a"); //Creates an a element
    HasCommented.setAttribute("id", "HasCommented"); //Adds the id HasCommented to the a element
    HasCommented.setAttribute("style", "cursor: pointer; margin-right: 15%;"); //Set the CSS for the button

    await GetComments(); //Starts the function

    if (newDocument.body.innerText.search("No comments found") > -1) //If the text "No comments found" is found
    { //Starts the if condition
      HasCommented.innerHTML = "❌"; //Add the text ❌ to the button
    } //Finishes the if condition
    else //If the text "No comments found" isn't found
    { //Starts the else condition
      HasCommented.innerHTML = "✅"; //Add a text to the button
      HasCommented.onclick = function() { //Detects the mouse click on the '✅' button
        open(`https://myanimelist.net/comtocom.php?id1=${ProfileID}&id2=${GM_getValue("ScriptUserID")}`, '_self'); //Open the user comments page on the same tab
      }; //Finishes the onclick event listener
    } //Finishes the else condition

    document.querySelector("#comment").parentElement.appendChild(HasCommented); //Shows the button

    setTimeout(function() { //Starts the setTimeout function
      document.querySelector("div.mt8 > input").parentElement.appendChild(document.querySelector("#HasCommented").cloneNode(true)); //Clone and append the HasCommented button
      document.querySelectorAll("#HasCommented")[1].onclick = function() { //Detects the mouse click on the second HasCommented button
        document.querySelector("#HasCommented").click(); //Click on the first HasCommented element
      }; //Finishes the onclick event listener
    }, 3000); //Finishes the setTimeout function

    const HasPMed = document.createElement("a"); //Creates an a element
    HasPMed.setAttribute("style", "cursor: pointer; margin-right: 47%;"); //Set the css for the button

    HasPMed.innerHTML = GM_listValues().includes(location.href.split('/')[4]) === true ? "✅" : "❌"; //If the current opened profile User Name is on the user PMed list Add a text to the button

    document.querySelector("#comment").parentElement.appendChild(HasPMed); //Shows the button
  } //Finishes the if condition
})();