flxemt / AniList - Show advanced scores in list

// ==UserScript==
// @name         AniList - Show advanced scores in list
// @namespace    https://anilist.co/
// @downloadURL  https://openuserjs.org/install/flxemt/AniList_-_Show_advanced_scores_in_list.user.js
// @updateURL    https://openuserjs.org/meta/flxemt/AniList_-_Show_advanced_scores_in_list.meta.js
// @version      1.1
// @description  Hover over a score in a user's list to view advanced scores
// @author       lumiuko
// @match        https://anilist.co/*
// @grant        none
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';
    let lastCheckedUrlPath = null;
    let additionalCSS = document.createElement('style');
    document.head.appendChild(additionalCSS);
    additionalCSS.appendChild(document.createTextNode(`.list-entries > .entry.row .score::before { margin-left: 80px; }`));
    const observer = new MutationObserver((mutations, observer) => {
        let pathname = window.location.pathname;
        if (lastCheckedUrlPath === pathname) return;
        lastCheckedUrlPath = pathname;
        if (lastCheckedUrlPath.includes('/animelist') || lastCheckedUrlPath.includes('/mangalist')) applyChanges();
    });
    const target = document.querySelector('head > title');
    observer.observe(target, { childList: true, characterData: true });
})();

function applyChanges() {
    let waitForLoading = setInterval(() => {
        if (document.querySelectorAll('.list-entries > .entry.row').length) {
            clearInterval(waitForLoading);
            const query = `
              query ($name: String) {
                User (name: $name) {
                  mediaListOptions {
                    animeList {
                      advancedScoringEnabled
                    }
                  }
                }
              }
            `;
            const variables = {
                name: document.querySelector('.banner-content .name').innerText
            }
            const url = 'https://graphql.anilist.co';
            const options = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                },
                body: JSON.stringify({
                    query: query,
                    variables: variables
                })
            };
            fetch(url, options).then(res => {
                return res.json().then(json => {
                    return res.ok ? json : Promise.reject(json);
                });
            }).then(data => {
                if (data.data.User.mediaListOptions.animeList.advancedScoringEnabled) {
                    document.querySelectorAll('.list-entries > .entry.row').forEach(entry => addNotes(entry));
                    const observer = new MutationObserver((mutations, observer) => {
                        mutations.forEach(mutation => {
                            if (mutation.addedNodes[0]) addNotes(mutation.addedNodes[0]);
                        });
                    });
                    observer.observe(document.querySelector('.medialist'), { childList: true, characterData: true, subtree: true });
                }
            });
        }
    }, 500);
}

function addNotes(entry) {
    const scoreItem = entry.querySelector('.score');
    if (scoreItem.getAttribute('score') != 0) {
        scoreItem.classList.add('notes');
        scoreItem.style.marginLeft = 0;
        scoreItem.setAttribute('label', 'Loading...');
        scoreItem.addEventListener('mouseover', () => {
            getData(scoreItem);
        });
    }
}

function getData(item) {
    if (item.getAttribute('label') !== 'Loading...') return;
    const query = `
      query ($name: String, $mediaId: Int, $type: MediaType) {
        MediaList (userName: $name, mediaId: $mediaId, type: $type) {
          userId,
          score,
          advancedScores
        }
      }
    `;
    const variables = {
        name: document.querySelector('.banner-content .name').innerText,
        mediaId: item.previousElementSibling.firstElementChild.href.match(/(anime|manga)\/(\d{1,})/)[2],
        type: item.previousElementSibling.firstElementChild.href.match(/(anime|manga)\/(\d{1,})/)[1].toUpperCase(),
    }
    const url = 'https://graphql.anilist.co';
    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
        body: JSON.stringify({
            query: query,
            variables: variables
        })
    };
    fetch(url, options).then(res => {
        return res.json().then(json => {
            return res.ok ? json : Promise.reject(json);
        });
    }).then(data => {
        let score = '';
        if (!Object.values(data.data.MediaList.advancedScores).filter(item => item > 0).length) score = 'No Advanced Scores';
        else {
            for (let [key, value] of Object.entries(data.data.MediaList.advancedScores)) {
                if (value !== 0) score += `${key}: ${value}\n`;
            }
        }
        item.setAttribute('label', score);
    });
}