MAL English Titles

Add English Titles to various MyAnimeList pages, whilst still displaying Japanese Titles

As of 2021-02-11. See the latest version.

// ==UserScript==
// @name         MAL English Titles
// @version      1.31.1
// @description  Add English Titles to various MyAnimeList pages, whilst still displaying Japanese Titles
// @author       Animorphs
// @grant        GM_setValue
// @grant        GM_getValue
// @namespace    https://github.com/Animorphs/MAL-English-Titles
// @require      https://code.jquery.com/jquery-3.5.1.min.js
// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @include      https://myanimelist.net/*
// ==/UserScript==

/* globals $ */

// Get the translations and display on page
function translate()
{
    // Top Anime
    if (location.href.includes('https://myanimelist.net/topanime.php'))
    {
        let results = document.getElementsByClassName('hoverinfo_trigger fl-l fs14 fw-b anime_ranking_h3');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = results[i].children[0].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = '.hoverinfo_trigger.fl-l.fs14.fw-b.anime_ranking_h3 > a[href="' + UrlDecoded + '"]';
                addTranslation('anime', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', i);
    }

    // Top Manga
    else if (location.href.includes('https://myanimelist.net/topmanga.php'))
    {
        let results = document.getElementsByClassName('hoverinfo_trigger fs14 fw-b');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('manga' + i))
            {
                let Url = results[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fs14.fw-b';
                addTranslation('manga', i, Url, ID, selector);
            }
        }
        storeTranslated('manga', i);
    }

    // Anime List
    else if (location.href.includes('https://myanimelist.net/animelist'))
    {
        let results = document.getElementsByClassName('data title clearfix');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = results[i].children[0].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('/')[4];
                let selector = '.data.title.clearfix > a[href="' + UrlShortDecoded + '"]';
                addTranslation('anime', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', i);
    }

    // Manga List
    else if (location.href.includes('https://myanimelist.net/mangalist'))
    {
        let results = document.getElementsByClassName('data title');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('manga' + i))
            {
                let Url = results[i].children[0].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('/')[4];
                let selector = '.data.title > a[href="' + UrlShortDecoded + '"]';
                addTranslation('manga', i, Url, ID, selector);
            }
        }
        storeTranslated('manga', i);
    }

    // Search Generic
    else if (location.href.includes('https://myanimelist.net/search/'))
    {
        // Anime Results
        let resultsAnime = document.getElementsByClassName('hoverinfo_trigger fw-b fl-l');
        for (let i = 0; i < 10; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = resultsAnime[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b.fl-l';
                addTranslation('anime', i, Url, ID, selector);
            }
        }

        // Manga Results
        let resultsManga = document.getElementsByClassName('hoverinfo_trigger fw-b');
        for (let i = 10; i < 20; i++)
        {
            if (!document.getElementById('manga' + i))
            {
                let Url = resultsManga[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b';
                addTranslation('manga', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', 10);
        storeTranslated('manga', -1);
    }

    // Search Anime
    else if (location.href.includes('https://myanimelist.net/anime.php?q'))
    {
        let results = document.getElementsByClassName('hoverinfo_trigger fw-b fl-l');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = results[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b.fl-l';
                addTranslation('anime', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', i);
    }

    // Search Manga
    else if (location.href.includes('https://myanimelist.net/manga.php?q'))
    {
        let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('manga' + i))
            {
                let Url = results[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b';
                addTranslation('manga', i, Url, ID, selector);
            }
        }
        storeTranslated('manga', i);
    }

    // Seasonal Anime
    else if (location.href.includes('https://myanimelist.net/anime/season'))
    {
        let results = document.getElementsByClassName('link-title');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = results[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"].link-title';
                addTranslation('anime', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', i);
    }

    // Anime Genres
    else if (location.href.includes('https://myanimelist.net/anime/genre'))
    {
        let i = 0;

        // Tile View
        if ($('.btn-view-style.js-btn-view-style.tile.on')[0])
        {
            let results = document.getElementsByClassName('link-title');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('anime' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].link-title';
                    addTranslation('anime', i, Url, ID, selector);
                }
            }
        }

        // List View
        else if ($('.btn-view-style.js-btn-view-style.list.on')[0])
        {
            let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('anime' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b';
                    addTranslation('anime', i, Url, ID, selector);
                }
            }
        }
        storeTranslated('anime', i);
    }

    // Manga Genres
    else if (location.href.includes('https://myanimelist.net/manga/genre'))
    {
        let i = 0;

        // Tile View
        if ($('.btn-view-style.js-btn-view-style.tile.on')[0])
        {
            let results = document.getElementsByClassName('link-title');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('manga' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].link-title';
                    addTranslation('manga', i, Url, ID, selector);
                }
            }
        }

        // List View
        else if ($('.btn-view-style.js-btn-view-style.list.on')[0])
        {
            let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('manga' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b';
                    addTranslation('manga', i, Url, ID, selector);
                }
            }
        }
        storeTranslated('manga', i);
    }

    // Anime Producers
    else if (location.href.includes('https://myanimelist.net/anime/producer'))
    {
        let i = 0;

        // Tile View
        if ($('.btn-view-style.js-btn-view-style.tile.on')[0])
        {
            let results = document.getElementsByClassName('link-title');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('anime' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].link-title';
                    addTranslation('anime', i, Url, ID, selector);
                }
            }
        }

        // List View
        else if ($('.btn-view-style.js-btn-view-style.list.on')[0])
        {
            let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
            for (i; i < results.length; i++)
            {
                if (!document.getElementById('anime' + i))
                {
                    let Url = results[i].href;
                    let UrlDecoded = decodeURIComponent(Url);
                    let ID = Url.split('/')[4];
                    let selector = 'a[href="' + UrlDecoded + '"].hoverinfo_trigger.fw-b';
                    addTranslation('anime', i, Url, ID, selector);
                }
            }
        }
        storeTranslated('anime', i);
    }

    // Shared Anime
    else if (location.href.includes('https://myanimelist.net/shared.php') && !location.href.includes('&type=manga'))
    {
        let results = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit)');
        let i = 1;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = results[i].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlShortDecoded + '"]';
                addTranslation('anime', i, Url, ID, selector);
            }
        }
        storeTranslated('anime', i);
    }

    // Shared Manga
    else if (location.href.includes('https://myanimelist.net/shared.php') && location.href.includes('&type=manga'))
    {
        let results = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
        let i = 0;
        for (i; i < results.length; i++)
        {
            if (!document.getElementById('manga' + i))
            {
                let Url = results[i].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlShortDecoded + '"]';
                addTranslation('manga', i, Url, ID, selector);
            }
        }
        storeTranslated('manga', i);
    }

    // History
    else if (location.href.includes('https://myanimelist.net/history'))
    {
        // Anime Results
        let resultsAnime = document.querySelectorAll('[href*="/anime.php"]');
        let AnimeIDs = [];
        let i = 1;
        for (i; i < resultsAnime.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = resultsAnime[i].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('=')[1];
                let selector = 'a[href="' + UrlShortDecoded + '"]';
                if (!AnimeIDs.includes(ID))
                {
                    addTranslation('anime', i, Url, ID, selector);
                }
                AnimeIDs.push(ID);
            }
        }

        // Manga Results
        let resultsManga = document.querySelectorAll('[href*="/manga.php"]');
        let MangaIDs = [];
        let j = 1;
        for (j; j < resultsManga.length-1; j++)
        {
            if (!document.getElementById('manga' + j))
            {
                let Url = resultsManga[j].href;
                let UrlShort = Url.slice(23);
                let UrlShortDecoded = decodeURIComponent(UrlShort);
                let ID = Url.split('=')[1];
                let selector = 'a[href="' + UrlShortDecoded + '"]';
                if (!MangaIDs.includes(ID))
                {
                    addTranslation('manga', j, Url, ID, selector);
                }
                MangaIDs.push(ID);
            }
        }
        storeTranslated('anime', i);
        storeTranslated('manga', j);
    }

    // People
    else if (location.href.includes('https://myanimelist.net/people'))
    {
        // Anime Results
        let resultsAnime = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit)');
        let AnimeIDs = [];
        let i = 1;
        for (i; i < resultsAnime.length; i++)
        {
            if (!document.getElementById('anime' + i))
            {
                let Url = resultsAnime[i].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"]:not(.picSurround > a)';
                if (!AnimeIDs.includes(ID))
                {
                    addTranslation('anime', i, Url, ID, selector);
                }
                AnimeIDs.push(ID);
            }
        }

        // Manga Results
        let resultsManga = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
        let MangaIDs = [];
        let j = 0;
        for (j; j < resultsManga.length; j+=2)
        {
            if (!document.getElementById('manga' + j))
            {
                let Url = resultsManga[j].href;
                let UrlDecoded = decodeURIComponent(Url);
                let ID = Url.split('/')[4];
                let selector = 'a[href="' + UrlDecoded + '"]:not(.picSurround > a)';
                if (!MangaIDs.includes(ID))
                {
                    addTranslation('manga', j, Url, ID, selector);
                }
                MangaIDs.push(ID);
            }
        }
        storeTranslated('anime', i);
        storeTranslated('manga', j);
    }
};

function addTranslation(type, count, url, ID, selector)
{
    let styleId = '<div style="font-weight:bold" id="' + type + count + '">'
    let styleIdEnd = '</div>';
    if (type == 'manga')
    {
        if (checkManga(ID, true))
        {
            $(selector).before(styleId + storedManga[ID][0] + styleIdEnd);
        }
        else
        {
            $(selector).before($(styleId).load(url + ' .title-english'));
        }
    }
    else if (type == 'anime')
    {
        if (checkAnime(ID, true))
        {
            $(selector).before(styleId + storedAnime[ID][0] + styleIdEnd);
        }
        else
        {
            $(selector).before($(styleId).load(url + ' .title-english'));
        }
    }
}

// Get anime/manga IDs and English titles from page, and send to be cached. Repeat storage so as to not store blank still-loading results
function storeTranslated(type, count)
{
    setTimeout(function()
    {
        if (type == 'anime')
        {
            for (let i = 0; i < count; i++)
            {
                if (document.getElementById('anime' + i))
                {
                    let animeTitle = document.getElementById('anime' + i).innerText;
                    let animeID = '';
                    if (location.href.includes('https://myanimelist.net/history'))
                    {
                        animeID = document.getElementById('anime' + i).nextSibling.href.split('id=')[1];
                    }
                    else
                    {
                        animeID = document.getElementById('anime' + i).nextSibling.href.split('/')[4];
                    }
                    if (!(animeTitle == '' && checkAnime(animeID, false)))
                    {
                        storeAnime(animeID, animeTitle);
                    }
                }
            }
        }
        else if (type == 'manga')
        {
            if (count == -1)
            {
                for (let i = 10; i < 20; i++)
                {
                    let mangaTitle = document.getElementById('manga' + i).innerText;
                    let mangaID = document.getElementById('manga' + i).nextSibling.href.split('/')[4];
                    if (!(mangaTitle == '' && checkManga(mangaID, false)))
                    {
                        storeManga(mangaID, mangaTitle);
                    }
                }
            }
            else
            {
                for (let i = 0; i < count; i++)
                {
                    if (document.getElementById('manga' + i))
                    {
                        let mangaTitle = document.getElementById('manga' + i).innerText;
                        let mangaID = '';
                        if (location.href.includes('https://myanimelist.net/history'))
                        {
                            mangaID = document.getElementById('manga' + i).nextSibling.href.split('id=')[1];
                        }
                        else
                        {
                            mangaID = document.getElementById('manga' + i).nextSibling.href.split('/')[4];
                        }
                        if (!(mangaTitle == '' && checkManga(mangaID, false)))
                        {
                            storeManga(mangaID, mangaTitle);
                        }
                    }
                }
            }
        }
        if (repeat < 10)
        {
            repeat++;
            storeTranslated(type, count);
        }
    }, 5000);
}

// Store English titles for anime in cache
function storeAnime(ID, engTitle)
{
    storedAnime[ID] = [engTitle, Date.now()];
    GM_setValue('anime', storedAnime);
}

// Store English titles for manga in cache
function storeManga(ID, engTitle)
{
    storedManga[ID] = [engTitle, Date.now()];
    GM_setValue('manga', storedManga);
}

// Check if English title for anime is cached
function checkAnime(ID, print)
{
    if (storedAnime.hasOwnProperty(ID))
    {
        if (storedAnime[ID][0] == '')
        {
            let dateNow = Date.now();
            let dateOld = storedAnime[ID][1];
            if (dateNow - dateOld > 2628000000)
            {
                if (print)
                {
                    console.log('Updated anime ' + ID);
                }
                return false;
            }
        }
        return true;
    }
    if (print)
    {
        console.log('New anime ' + ID);
    }
    return false;
}

// Check if English title for manga is cached
function checkManga(ID, print)
{
    if (storedManga.hasOwnProperty(ID))
    {
        if (storedManga[ID][0] == '')
        {
            let dateNow = Date.now();
            let dateOld = storedManga[ID][1];
            if (dateNow - dateOld > 2628000000)
            {
                if (print)
                {
                    console.log('Updated manga ' + ID);
                }
                return false;
            }
        }
        return true;
    }
    if (print)
    {
        console.log('New manga ' + ID);
    }
    return false;
}

// Get cached English titles if they exist, else create empty dictionary
var storedAnime = GM_getValue('anime');
var storedManga = GM_getValue('manga');
if (!storedAnime)
{
    GM_setValue('anime',{});
    storedAnime = {};
}
if (!storedManga)
{
    GM_setValue('manga',{});
    storedManga = {};
}

// Save English title of current anime or manga page
if (location.href.match(/https:\/\/myanimelist\.net\/(anime|manga)\/([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/.*/))
{
    let title = document.getElementsByClassName('title-english')[0].innerText;
    let id = location.href.split('/')[4];
    if (location.href.includes('/anime/'))
    {
        storeAnime(id, title);
    }
    else if (location.href.includes('/manga/'))
    {
        storeManga(id, title);
    }
}

// Detect AJAX calls upon infinite scroll, and load new translations
var repeat = 0;
if (location.href.includes('https://myanimelist.net/animelist') || location.href.includes('https://myanimelist.net/mangalist'))
{
    (function(open)
    {
        XMLHttpRequest.prototype.open = function()
        {
            this.addEventListener("readystatechange", function()
            {
                if (this.readyState == 4 && this.status == 200 && (this.responseURL.startsWith('https://myanimelist.net/animelist') || this.responseURL.startsWith('https://myanimelist.net/mangalist')))
                {
                    repeat = 0;
                    setTimeout(translate, 2000);
                }
            }, false);
            open.apply(this, arguments);
        };
    })(XMLHttpRequest.prototype.open);
}

// Launch actual script
setTimeout(translate, 2000);