Add English Titles to various MyAnimeList pages, whilst still displaying Japanese Titles
- // ==UserScript==
- // @name MAL English Titles
- // @version 2.2.0
- // @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
- // @match https://myanimelist.net/*
- // ==/UserScript==
- // Get Japanese titles from page, and send to be translated (addTranslation)
- function translate()
- {
- const LOCATION_HREF = location.href;
- const URL_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\/([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/;
- const URL_PHP_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\.php\?id\=([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/;
- // Anime/Manga Page (store only, don't display)
- if (URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF))
- {
- let titleHtml = document.getElementsByClassName('title-english')[0];
- let id = LOCATION_HREF.includes('.php') ? LOCATION_HREF.split('id=')[1] : LOCATION_HREF.split('/')[4];
- let type = LOCATION_HREF.includes('/anime') ? "anime" : "manga";
- if (titleHtml)
- {
- let title = titleHtml.innerText;
- console.log(`Updated ${type} ${id}: ${title}`);
- type == 'anime' ? storeAnime(id, title) : storeManga(id, title);
- }
- else if (storedAnime[id][0] === '' || !storedAnime.hasOwnProperty(id))
- {
- console.log(`Updated ${type} ${id}`);
- type == 'anime' ? storeAnime(id, '') : storeManga(id, '');
- }
- }
- // // Anime/Manga Page User Recommendations
- if ((URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF)) && LOCATION_HREF.includes('/userrecs'))
- {
- let r###lts = document.querySelectorAll('[style*="margin-bottom: 2px"]');
- let type = LOCATION_HREF.includes('/anime') ? 'anime' : 'manga';
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById(type + i))
- {
- let url = r###lts[i].children[0].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- //console.log(id)
- let selector = 'div[style="margin-bottom: 2px;"] > a[href="' + urlDecoded + '"]';
- addTranslation(type, i, url, id, selector);
- }
- }
- }
- // Recommendations
- else if (LOCATION_HREF.includes('https://myanimelist.net/recommendations.php'))
- {
- let r###lts = document.querySelectorAll('.spaceit.borderClass a:has(strong)');
- let arr = []
- let type = LOCATION_HREF.includes('&t=anime') ? 'anime' : 'manga';
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById(type + i))
- {
- let url = r###lts[i].href;
- let urlDecoded = decodeURIComponent(url);
- let parts = urlDecoded.split('/' + type);
- let urlShort = '/' + type + parts[1];
- if (!arr.includes(urlShort))
- {
- arr.push(urlShort)
- let id = url.split('/')[4];
- let selector = 'td > a[href*="' + urlShort + '"]:not(:has(+ div[style="font-weight:bold"]))';
- addTranslation(type, i, url, id, selector);
- }
- }
- }
- }
- // Anime Top
- else if (LOCATION_HREF.includes('https://myanimelist.net/topanime.php'))
- {
- let r###lts = document.getElementsByClassName('fl-l fs14 fw-b anime_ranking_h3');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].children[0].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = '.fl-l.fs14.fw-b.anime_ranking_h3 > a[href="' + urlDecoded + '"]';
- addTranslation('anime', i, url, id, selector);
- }
- }
- }
- // Manga Top
- else if (LOCATION_HREF.includes('https://myanimelist.net/topmanga.php'))
- {
- let r###lts = document.getElementsByClassName('hoverinfo_trigger fs14 fw-b');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- // Anime List and Manga List
- else if (LOCATION_HREF.includes('https://myanimelist.net/animelist') || LOCATION_HREF.includes('https://myanimelist.net/mangalist'))
- {
- let type = LOCATION_HREF.substring(24, 29);
- let r###lts = document.querySelectorAll('tbody:not([style]) .data.title');
- function processR###lts(tempR###lts)
- {
- for (let i = 0; i < tempR###lts.length; i++)
- {
- let url = tempR###lts[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(type, i, url, id, selector);
- }
- }
- function attachMutationObserver(listTable)
- {
- new MutationObserver(function(mutationsList, observer)
- {
- mutationsList.forEach(function(mutation)
- {
- processR###lts(
- Array.from(
- mutation.addedNodes,
- (addedNode) => addedNode.children[0].children[3]
- )
- );
- });
- if ((listTable.children.length - 1) % 150 !== 0)
- {
- observer.disconnect();
- }
- }).observe(
- listTable,
- {childList: true}
- );
- }
- let table = document.querySelector('table');
- if (r###lts.length)
- {
- processR###lts(r###lts);
- if (r###lts.length === 150)
- {
- attachMutationObserver(table);
- }
- }
- else if (table)
- {
- new MutationObserver(function(mutationsList, observer)
- {
- mutationsList.some(function(mutation)
- {
- return Array.from(mutation.addedNodes).some(function(addedNode)
- {
- if (addedNode.tagName === 'TABLE')
- {
- let r###lts = addedNode.querySelectorAll('.data.title');
- processR###lts(r###lts);
- if (r###lts.length === 150)
- {
- attachMutationObserver(addedNode);
- }
- observer.disconnect();
- return true;
- }
- });
- });
- }).observe(
- table.parentElement,
- {childList: true}
- );
- }
- }
- // Search
- else if (LOCATION_HREF.includes('https://myanimelist.net/search/'))
- {
- // Anime R###lts
- let r###ltsAnime = document.querySelectorAll('[class="hoverinfo_trigger fw-b fl-l"][href*="/anime/"]');
- for (let i = 0; i < r###ltsAnime.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###ltsAnime[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, true);
- }
- }
- // Manga R###lts
- let r###ltsManga = document.querySelectorAll('[class="hoverinfo_trigger fw-b"][href*="/manga/"]');
- for (let i = 0; i < r###ltsManga.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###ltsManga[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);
- }
- }
- }
- // Anime Search
- else if (LOCATION_HREF.includes('https://myanimelist.net/anime.php?q') || LOCATION_HREF.includes('https://myanimelist.net/anime.php?cat'))
- {
- let r###lts = document.getElementsByClassName('hoverinfo_trigger fw-b fl-l');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[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, true);
- }
- }
- }
- // Manga Search
- else if (LOCATION_HREF.includes('https://myanimelist.net/manga.php?q') || LOCATION_HREF.includes('https://myanimelist.net/manga.php?cat'))
- {
- let r###lts = document.getElementsByClassName('hoverinfo_trigger fw-b');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- // Anime Seasonal
- else if (LOCATION_HREF.includes('https://myanimelist.net/anime/season'))
- {
- let r###lts = document.getElementsByClassName('link-title');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = 'a[href="' + urlDecoded + '"].link-title';
- addTranslation('anime', i, url, id, selector, false, true);
- }
- }
- }
- // Anime Genres
- else if (LOCATION_HREF.includes('https://myanimelist.net/anime/genre'))
- {
- // Seasonal View
- if (document.getElementsByClassName('js-btn-view-style seasonal on')[0])
- {
- let r###lts = document.getElementsByClassName('link-title');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = 'a[href="' + urlDecoded + '"].link-title';
- addTranslation('anime', i, url, id, selector, true, true);
- }
- }
- }
- // List View
- else if (document.getElementsByClassName('js-btn-view-style list on')[0])
- {
- let r###lts = document.getElementsByClassName('hoverinfo_trigger fw-b');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- }
- // Manga Genres
- else if (LOCATION_HREF.includes('https://myanimelist.net/manga/genre') || LOCATION_HREF.includes('https://myanimelist.net/manga/adapted'))
- {
- // Seasonal View
- if (document.getElementsByClassName('js-btn-view-style seasonal on')[0])
- {
- let r###lts = document.getElementsByClassName('link-title');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###lts[i].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = 'a[href="' + urlDecoded + '"].link-title';
- addTranslation('manga', i, url, id, selector, false, true);
- }
- }
- }
- // List View
- else if (document.getElementsByClassName('js-btn-view-style list on')[0])
- {
- let r###lts = document.getElementsByClassName('hoverinfo_trigger fw-b');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- }
- // Anime Producers
- else if (LOCATION_HREF.includes('https://myanimelist.net/anime/producer'))
- {
- // Tile View
- if (document.getElementsByClassName('js-btn-view-style2 tile on')[0])
- {
- let r###lts = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all ');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].children[0].children[0].href
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = '.seasonal-anime.js-seasonal-anime.js-anime-type-all > .title > a[href="' + urlDecoded + '"]';
- addTranslation('anime', i, url, id, selector, false, false, true);
- }
- }
- }
- //
- // Seasonal View
- if (document.getElementsByClassName('js-btn-view-style2 seasonal on')[0])
- {
- let r###lts = document.getElementsByClassName('link-title');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = 'a[href="' + urlDecoded + '"].link-title';
- addTranslation('anime', i, url, id, selector, false, true);
- }
- }
- }
- // List View
- else if (document.getElementsByClassName('js-btn-view-style2 list on')[0])
- {
- let r###lts = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[i].children[0].children[0].children[0].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = '.spaceit_pad > a[href="' + urlDecoded + '"]';
- addTranslation('anime', i, url, id, selector);
- }
- }
- }
- }
- // Anime Shared
- else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && !LOCATION_HREF.includes('&type=manga'))
- {
- let r###lts = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- // Manga Shared
- else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && LOCATION_HREF.includes('&type=manga'))
- {
- let r###lts = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
- for (let i = 0; i < r###lts.length; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###lts[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);
- }
- }
- }
- // History
- else if (LOCATION_HREF.includes('https://myanimelist.net/history'))
- {
- // Anime R###lts
- let r###ltsAnime = document.querySelectorAll('[href*="/anime.php?id="]');
- let animeIds = [];
- for (let i = 0; i < r###ltsAnime.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###ltsAnime[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 R###lts
- let r###ltsManga = document.querySelectorAll('[href*="/manga.php?id="]');
- let mangaIds = [];
- for (let i = 0; i < r###ltsManga.length-1; i++)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###ltsManga[i].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', i, url, id, selector);
- }
- mangaIds.push(id);
- }
- }
- }
- // People
- else if (LOCATION_HREF.includes('https://myanimelist.net/people'))
- {
- // Anime R###lts
- let r###ltsAnime = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])');
- let animeIds = [];
- for (let i = 0; i < r###ltsAnime.length; i++)
- {
- if (!document.getElementById('anime' + i))
- {
- let url = r###ltsAnime[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 R###lts
- let r###ltsManga = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
- let mangaIds = [];
- for (let i = 0; i < r###ltsManga.length; i+=2)
- {
- if (!document.getElementById('manga' + i))
- {
- let url = r###ltsManga[i].href;
- let urlDecoded = decodeURIComponent(url);
- let id = url.split('/')[4];
- let selector = 'a[href="' + urlDecoded + '"]:not(.picSurround > a)';
- if (!mangaIds.includes(id))
- {
- addTranslation('manga', i, url, id, selector);
- }
- mangaIds.push(id);
- }
- }
- }
- }
- // Get English title (storedAnime and getEnglishTitle) and add to page
- function addTranslation(type, count, url, id, selector, parent=false, tile=false, producer=false)
- {
- let styleId = ""
- let styleIdEnd = ""
- if (tile)
- {
- styleId = '<h3 class="h3_anime_subtitle" id="' + type + count + '">';
- styleIdEnd = '</h3>';
- }
- else
- {
- styleId = '<div style="font-weight:bold" id="' + type + count + '">';
- styleIdEnd = '</div>';
- }
- if (type === 'anime')
- {
- if (producer)
- {
- document.getElementsByClassName('category')[count].style.visibility = 'hidden'
- }
- if (checkAnime(id))
- {
- document.querySelectorAll(selector).forEach(function(element)
- {
- if (parent)
- {
- element = element.parentElement;
- }
- element.insertAdjacentHTML('beforebegin', styleId + storedAnime[id][0] + styleIdEnd);
- });
- }
- else
- {
- getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd);
- }
- }
- else if (type === 'manga')
- {
- if (checkManga(id))
- {
- document.querySelectorAll(selector).forEach(function(element)
- {
- if (parent)
- {
- element = element.parentElement;
- }
- element.insertAdjacentHTML('beforebegin', styleId + storedManga[id][0] + styleIdEnd);
- });
- }
- else
- {
- getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd);
- }
- }
- }
- // Request English title from MAL and send to be stored (storeAnime)
- function getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd)
- {
- // Create new request
- let xhr = new XMLHttpRequest();
- xhr.responseType = 'document';
- // Set the callback
- xhr.onload = function()
- {
- if (xhr.readyState === xhr.DONE && xhr.status === 200 && xhr.responseXML !== null)
- {
- let englishTitleElement = xhr.responseXML.querySelector('.title-english');
- let englishTitle;
- if (englishTitleElement)
- {
- englishTitle = englishTitleElement.innerText;
- }
- else
- {
- englishTitle = '';
- }
- if (type === 'anime')
- {
- storeAnime(id, englishTitle);
- }
- else if (type === 'manga')
- {
- storeManga(id, englishTitle);
- }
- document.querySelectorAll(selector).forEach(function(element)
- {
- if (parent)
- {
- element = element.parentElement;
- }
- element.insertAdjacentHTML('beforebegin', styleId + englishTitle + styleIdEnd);
- });
- }
- };
- // Send the request
- xhr.open('GET', url);
- xhr.send();
- }
- // 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, and recheck if empty + last check was >3 weeks
- function checkAnime(id)
- {
- if (storedAnime.hasOwnProperty(id))
- {
- if (storedAnime[id][0] === '')
- {
- let dateNow = Date.now();
- let dateOld = storedAnime[id][1];
- if (dateNow - dateOld > 2628000000)
- {
- console.log('Updated anime ' + id);
- return false;
- }
- }
- return true;
- }
- console.log('New anime ' + id);
- return false;
- }
- // Check if English title for manga is cached, and recheck if empty + last check was >3 weeks
- function checkManga(id)
- {
- if (storedManga.hasOwnProperty(id))
- {
- if (storedManga[id][0] === '')
- {
- let dateNow = Date.now();
- let dateOld = storedManga[id][1];
- if (dateNow - dateOld > 2628000000)
- {
- console.log('Updated manga ' + id);
- return false;
- }
- }
- return true;
- }
- 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 = {};
- }
- // Launch actual script
- translate();