Greasy Fork is available in English.
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();