├── .github └── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md ├── README.md └── MAL_English_Titles.user.js /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: Feature Request 6 | assignees: Animorphs 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Extra** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug 4 | title: "[Bug]" 5 | labels: Bug 6 | assignees: Animorphs 7 | 8 | --- 9 | 10 |
31 |
32 | ### Top Anime / Manga
33 |
34 |
35 | ### Seasonal Anime
36 |
37 |
38 | ### Search
39 |
40 |
41 | Special thanks to nicegamer7 for their code contributions.
42 |
--------------------------------------------------------------------------------
/MAL_English_Titles.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name MAL English Titles
3 | // @version 2.2.3
4 | // @description Add English Titles to various MyAnimeList pages, whilst still displaying Japanese Titles
5 | // @author Animorphs
6 | // @grant GM.setValue
7 | // @grant GM.getValue
8 | // @namespace https://github.com/Animorphs/MAL-English-Titles
9 | // @icon https://myanimelist.net/favicon.ico
10 | // @match https://myanimelist.net/*
11 | // @updateURL https://raw.githubusercontent.com/Animorphs/MAL-English-Titles/master/MAL_English_Titles.user.js
12 | // @downloadURL https://raw.githubusercontent.com/Animorphs/MAL-English-Titles/master/MAL_English_Titles.user.js
13 | // ==/UserScript==
14 |
15 |
16 |
17 | // Get Japanese titles from page, and send to be translated (addTranslation)
18 | async function translate()
19 | {
20 | const LOCATION_HREF = location.href;
21 | const URL_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\/([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/;
22 | const URL_PHP_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\.php\?id\=([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/;
23 |
24 | // Anime/Manga Page (store only, don't display)
25 | if (URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF))
26 | {
27 | let titleHtml = document.getElementsByClassName('title-english')[0];
28 | let id = LOCATION_HREF.includes('.php') ? LOCATION_HREF.split('id=')[1] : LOCATION_HREF.split('/')[4];
29 |
30 | let type = LOCATION_HREF.includes('/anime') ? "anime" : "manga";
31 | if (titleHtml)
32 | {
33 | let title = titleHtml.innerText;
34 | console.log(`Updated ${type} ${id}: ${title}`);
35 | type == 'anime' ? await storeAnime(id, title) : await storeManga(id, title);
36 | }
37 | else if (storedAnime[id][0] === '' || !storedAnime.hasOwnProperty(id))
38 | {
39 | console.log(`Updated ${type} ${id}`);
40 | type == 'anime' ? await storeAnime(id, '') : await storeManga(id, '');
41 | }
42 | }
43 |
44 | // Anime/Manga Page User Recommendations
45 | if ((URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF)) && LOCATION_HREF.includes('/userrecs'))
46 | {
47 | let results = document.querySelectorAll('[style*="margin-bottom: 2px"]');
48 | let type = LOCATION_HREF.includes('/anime') ? 'anime' : 'manga';
49 | for (let i = 0; i < results.length; i++)
50 | {
51 | if (!document.getElementById(type + i))
52 | {
53 | let url = results[i].children[0].href;
54 | let urlDecoded = decodeURIComponent(url);
55 | let id = url.split('/')[4];
56 | //console.log(id)
57 | let selector = 'div[style="margin-bottom: 2px;"] > a[href="' + urlDecoded + '"]';
58 | addTranslation(type, i, url, id, selector);
59 | }
60 | }
61 | }
62 |
63 | // Recommendations
64 | else if (LOCATION_HREF.includes('https://myanimelist.net/recommendations.php'))
65 | {
66 | let results = document.querySelectorAll('.spaceit.borderClass a:has(strong)');
67 | let arr = []
68 | let type = LOCATION_HREF.includes('&t=anime') ? 'anime' : 'manga';
69 | for (let i = 0; i < results.length; i++)
70 | {
71 | if (!document.getElementById(type + i))
72 | {
73 | let url = results[i].href;
74 | let urlDecoded = decodeURIComponent(url);
75 | let parts = urlDecoded.split('/' + type);
76 | let urlShort = '/' + type + parts[1];
77 | if (!arr.includes(urlShort))
78 | {
79 | arr.push(urlShort)
80 | let id = url.split('/')[4];
81 | let selector = 'td > a[href*="' + urlShort + '"]:not(:has(+ div[style="font-weight:bold"]))';
82 | addTranslation(type, i, url, id, selector);
83 | }
84 | }
85 | }
86 | }
87 |
88 | // Anime Top
89 | else if (LOCATION_HREF.includes('https://myanimelist.net/topanime.php'))
90 | {
91 | let results = document.getElementsByClassName('fl-l fs14 fw-b anime_ranking_h3');
92 | for (let i = 0; i < results.length; i++)
93 | {
94 | if (!document.getElementById('anime' + i))
95 | {
96 | let url = results[i].children[0].href;
97 | let urlDecoded = decodeURIComponent(url);
98 | let id = url.split('/')[4];
99 | let selector = '.fl-l.fs14.fw-b.anime_ranking_h3 > a[href="' + urlDecoded + '"]';
100 | addTranslation('anime', i, url, id, selector);
101 | }
102 | }
103 | }
104 |
105 | // Manga Top
106 | else if (LOCATION_HREF.includes('https://myanimelist.net/topmanga.php'))
107 | {
108 | let results = document.getElementsByClassName('hoverinfo_trigger fs14 fw-b');
109 | for (let i = 0; i < results.length; i++)
110 | {
111 | if (!document.getElementById('manga' + i))
112 | {
113 | let url = results[i].href;
114 | let urlDecoded = decodeURIComponent(url);
115 | let id = url.split('/')[4];
116 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fs14.fw-b';
117 | addTranslation('manga', i, url, id, selector);
118 | }
119 | }
120 | }
121 |
122 | // Anime List and Manga List
123 | else if (LOCATION_HREF.includes('https://myanimelist.net/animelist') || LOCATION_HREF.includes('https://myanimelist.net/mangalist'))
124 | {
125 | let type = LOCATION_HREF.substring(24, 29);
126 | let results = document.querySelectorAll('tbody:not([style]) .data.title');
127 |
128 | function processResults(tempResults)
129 | {
130 | for (let i = 0; i < tempResults.length; i++)
131 | {
132 | let url = tempResults[i].children[0].href;
133 | let urlShort = url.slice(23);
134 | let urlShortDecoded = decodeURIComponent(urlShort);
135 | let id = url.split('/')[4];
136 | let selector = '.data.title > a[href="' + urlShortDecoded + '"]';
137 | addTranslation(type, i, url, id, selector);
138 | }
139 | }
140 |
141 | function attachMutationObserver(listTable)
142 | {
143 | new MutationObserver(function(mutationsList, observer)
144 | {
145 | mutationsList.forEach(function(mutation)
146 | {
147 | processResults(
148 | Array.from(
149 | mutation.addedNodes,
150 | (addedNode) => addedNode.children[0].children[3]
151 | )
152 | );
153 | });
154 |
155 | if ((listTable.children.length - 1) % 150 !== 0)
156 | {
157 | observer.disconnect();
158 | }
159 | }).observe(
160 | listTable,
161 | {childList: true}
162 | );
163 | }
164 |
165 | let table = document.querySelector('table');
166 |
167 | if (results.length)
168 | {
169 | processResults(results);
170 | if (results.length === 150)
171 | {
172 | attachMutationObserver(table);
173 | }
174 | }
175 | else if (table)
176 | {
177 | new MutationObserver(function(mutationsList, observer)
178 | {
179 | mutationsList.some(function(mutation)
180 | {
181 | return Array.from(mutation.addedNodes).some(function(addedNode)
182 | {
183 | if (addedNode.tagName === 'TABLE')
184 | {
185 | let results = addedNode.querySelectorAll('.data.title');
186 | processResults(results);
187 | if (results.length === 150)
188 | {
189 | attachMutationObserver(addedNode);
190 | }
191 | observer.disconnect();
192 | return true;
193 | }
194 | });
195 | });
196 | }).observe(
197 | table.parentElement,
198 | {childList: true}
199 | );
200 | }
201 | }
202 |
203 | // Search
204 | else if (LOCATION_HREF.includes('https://myanimelist.net/search/'))
205 | {
206 | // Anime Results
207 | let resultsAnime = document.querySelectorAll('[class="hoverinfo_trigger fw-b fl-l"][href*="/anime/"]');
208 | for (let i = 0; i < resultsAnime.length; i++)
209 | {
210 | if (!document.getElementById('anime' + i))
211 | {
212 | let url = resultsAnime[i].href;
213 | let urlDecoded = decodeURIComponent(url);
214 | let id = url.split('/')[4];
215 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b.fl-l';
216 | addTranslation('anime', i, url, id, selector, true);
217 | }
218 | }
219 |
220 | // Manga Results
221 | let resultsManga = document.querySelectorAll('[class="hoverinfo_trigger fw-b"][href*="/manga/"]');
222 | for (let i = 0; i < resultsManga.length; i++)
223 | {
224 | if (!document.getElementById('manga' + i))
225 | {
226 | let url = resultsManga[i].href;
227 | let urlDecoded = decodeURIComponent(url);
228 | let id = url.split('/')[4];
229 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b';
230 | addTranslation('manga', i, url, id, selector);
231 | }
232 | }
233 | }
234 |
235 | // Anime Search
236 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime.php?q') || LOCATION_HREF.includes('https://myanimelist.net/anime.php?cat'))
237 | {
238 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b fl-l');
239 | for (let i = 0; i < results.length; i++)
240 | {
241 | if (!document.getElementById('anime' + i))
242 | {
243 | let url = results[i].href;
244 | let urlDecoded = decodeURIComponent(url);
245 | let id = url.split('/')[4];
246 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b.fl-l';
247 | addTranslation('anime', i, url, id, selector, true);
248 | }
249 | }
250 | }
251 |
252 | // Manga Search
253 | else if (LOCATION_HREF.includes('https://myanimelist.net/manga.php?q') || LOCATION_HREF.includes('https://myanimelist.net/manga.php?cat'))
254 | {
255 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
256 | for (let i = 0; i < results.length; i++)
257 | {
258 | if (!document.getElementById('manga' + i))
259 | {
260 | let url = results[i].href;
261 | let urlDecoded = decodeURIComponent(url);
262 | let id = url.split('/')[4];
263 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b';
264 | addTranslation('manga', i, url, id, selector);
265 | }
266 | }
267 | }
268 |
269 | // Anime Seasonal
270 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/season'))
271 | {
272 | let results = document.getElementsByClassName('link-title');
273 | for (let i = 0; i < results.length; i++)
274 | {
275 | if (!document.getElementById('anime' + i))
276 | {
277 | let url = results[i].href;
278 | let urlDecoded = decodeURIComponent(url);
279 | let id = url.split('/')[4];
280 | let selector = 'a[href="' + urlDecoded + '"].link-title';
281 | addTranslation('anime', i, url, id, selector, false, true);
282 | }
283 | }
284 | }
285 |
286 | // Reviews
287 | else if (LOCATION_HREF.includes('https://myanimelist.net/reviews.php'))
288 | {
289 | let type = LOCATION_HREF.includes('t=manga') ? 'manga' : 'anime';
290 | let results = document.querySelectorAll('.review-element .titleblock a.title');
291 | let processedIds = new Set();
292 |
293 | for (let i = 0; i < results.length; i++)
294 | {
295 | let url = results[i].href;
296 | let urlDecoded = decodeURIComponent(url);
297 | let id = url.split('/')[4];
298 | if (!processedIds.has(id))
299 | {
300 | processedIds.add(id);
301 | let selector = '.review-element .titleblock a.title[href="' + urlDecoded + '"]';
302 | addTranslation(type, i, url, id, selector);
303 | }
304 | }
305 | }
306 |
307 | // Anime Genres
308 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/genre'))
309 | {
310 | // Seasonal View
311 | if (document.getElementsByClassName('js-btn-view-style seasonal on')[0])
312 | {
313 | let results = document.getElementsByClassName('link-title');
314 | for (let i = 0; i < results.length; i++)
315 | {
316 | if (!document.getElementById('anime' + i))
317 | {
318 | let url = results[i].href;
319 | let urlDecoded = decodeURIComponent(url);
320 | let id = url.split('/')[4];
321 | let selector = 'a[href="' + urlDecoded + '"].link-title';
322 | addTranslation('anime', i, url, id, selector, true, true);
323 | }
324 | }
325 | }
326 |
327 | // List View
328 | else if (document.getElementsByClassName('js-btn-view-style list on')[0])
329 | {
330 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
331 | for (let i = 0; i < results.length; i++)
332 | {
333 | if (!document.getElementById('anime' + i))
334 | {
335 | let url = results[i].href;
336 | let urlDecoded = decodeURIComponent(url);
337 | let id = url.split('/')[4];
338 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b';
339 | addTranslation('anime', i, url, id, selector);
340 | }
341 | }
342 | }
343 | }
344 |
345 | // Manga Genres
346 | else if (LOCATION_HREF.includes('https://myanimelist.net/manga/genre') || LOCATION_HREF.includes('https://myanimelist.net/manga/adapted'))
347 | {
348 | // Seasonal View
349 | if (document.getElementsByClassName('js-btn-view-style seasonal on')[0])
350 | {
351 | let results = document.getElementsByClassName('link-title');
352 | for (let i = 0; i < results.length; i++)
353 | {
354 | if (!document.getElementById('manga' + i))
355 | {
356 | let url = results[i].href;
357 | let urlDecoded = decodeURIComponent(url);
358 | let id = url.split('/')[4];
359 | let selector = 'a[href="' + urlDecoded + '"].link-title';
360 | addTranslation('manga', i, url, id, selector, false, true);
361 | }
362 | }
363 | }
364 |
365 | // List View
366 | else if (document.getElementsByClassName('js-btn-view-style list on')[0])
367 | {
368 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b');
369 | for (let i = 0; i < results.length; i++)
370 | {
371 | if (!document.getElementById('manga' + i))
372 | {
373 | let url = results[i].href;
374 | let urlDecoded = decodeURIComponent(url);
375 | let id = url.split('/')[4];
376 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b';
377 | addTranslation('manga', i, url, id, selector);
378 | }
379 | }
380 | }
381 | }
382 |
383 | // Anime Producers
384 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/producer'))
385 | {
386 | // Tile View
387 | if (document.getElementsByClassName('js-btn-view-style2 tile on')[0])
388 | {
389 | let results = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all ');
390 | for (let i = 0; i < results.length; i++)
391 | {
392 | if (!document.getElementById('anime' + i))
393 | {
394 | let url = results[i].children[0].children[0].href
395 | let urlDecoded = decodeURIComponent(url);
396 | let id = url.split('/')[4];
397 | let selector = '.seasonal-anime.js-seasonal-anime.js-anime-type-all > .title > a[href="' + urlDecoded + '"]';
398 | addTranslation('anime', i, url, id, selector, false, false, true);
399 | }
400 | }
401 | }
402 | //
403 | // Seasonal View
404 | if (document.getElementsByClassName('js-btn-view-style2 seasonal on')[0])
405 | {
406 | let results = document.getElementsByClassName('link-title');
407 | for (let i = 0; i < results.length; i++)
408 | {
409 | if (!document.getElementById('anime' + i))
410 | {
411 | let url = results[i].href;
412 | let urlDecoded = decodeURIComponent(url);
413 | let id = url.split('/')[4];
414 | let selector = 'a[href="' + urlDecoded + '"].link-title';
415 | addTranslation('anime', i, url, id, selector, false, true);
416 | }
417 | }
418 | }
419 |
420 | // List View
421 | else if (document.getElementsByClassName('js-btn-view-style2 list on')[0])
422 | {
423 | let results = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all');
424 | for (let i = 0; i < results.length; i++)
425 | {
426 | if (!document.getElementById('anime' + i))
427 | {
428 | let url = results[i].children[0].children[0].children[0].href;
429 | let urlDecoded = decodeURIComponent(url);
430 | let id = url.split('/')[4];
431 | let selector = '.spaceit_pad > a[href="' + urlDecoded + '"]';
432 | addTranslation('anime', i, url, id, selector);
433 | }
434 | }
435 | }
436 | }
437 |
438 | // Anime Shared
439 | else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && !LOCATION_HREF.includes('&type=manga'))
440 | {
441 | let results = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])');
442 | for (let i = 0; i < results.length; i++)
443 | {
444 | if (!document.getElementById('anime' + i))
445 | {
446 | let url = results[i].href;
447 | let urlShort = url.slice(23);
448 | let urlShortDecoded = decodeURIComponent(urlShort);
449 | let id = url.split('/')[4];
450 | let selector = 'a[href="' + urlShortDecoded + '"]';
451 | addTranslation('anime', i, url, id, selector);
452 | }
453 | }
454 | }
455 |
456 | // Manga Shared
457 | else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && LOCATION_HREF.includes('&type=manga'))
458 | {
459 | let results = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
460 | for (let i = 0; i < results.length; i++)
461 | {
462 | if (!document.getElementById('manga' + i))
463 | {
464 | let url = results[i].href;
465 | let urlShort = url.slice(23);
466 | let urlShortDecoded = decodeURIComponent(urlShort);
467 | let id = url.split('/')[4];
468 | let selector = 'a[href="' + urlShortDecoded + '"]';
469 | addTranslation('manga', i, url, id, selector);
470 | }
471 | }
472 | }
473 |
474 | // History
475 | else if (LOCATION_HREF.includes('https://myanimelist.net/history'))
476 | {
477 | // Anime Results
478 | let resultsAnime = document.querySelectorAll('[href*="/anime.php?id="]');
479 | let animeIds = [];
480 | for (let i = 0; i < resultsAnime.length; i++)
481 | {
482 | if (!document.getElementById('anime' + i))
483 | {
484 | let url = resultsAnime[i].href;
485 | let urlShort = url.slice(23);
486 | let urlShortDecoded = decodeURIComponent(urlShort);
487 | let id = url.split('=')[1];
488 | let selector = 'a[href="' + urlShortDecoded + '"]';
489 | if (!animeIds.includes(id))
490 | {
491 | addTranslation('anime', i, url, id, selector);
492 | }
493 | animeIds.push(id);
494 | }
495 | }
496 |
497 | // Manga Results
498 | let resultsManga = document.querySelectorAll('[href*="/manga.php?id="]');
499 | let mangaIds = [];
500 | for (let i = 0; i < resultsManga.length-1; i++)
501 | {
502 | if (!document.getElementById('manga' + i))
503 | {
504 | let url = resultsManga[i].href;
505 | let urlShort = url.slice(23);
506 | let urlShortDecoded = decodeURIComponent(urlShort);
507 | let id = url.split('=')[1];
508 | let selector = 'a[href="' + urlShortDecoded + '"]';
509 | if (!mangaIds.includes(id))
510 | {
511 | addTranslation('manga', i, url, id, selector);
512 | }
513 | mangaIds.push(id);
514 | }
515 | }
516 | }
517 |
518 | // People
519 | else if (LOCATION_HREF.includes('https://myanimelist.net/people'))
520 | {
521 | // Anime Results
522 | let resultsAnime = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])');
523 | let animeIds = [];
524 | for (let i = 0; i < resultsAnime.length; i++)
525 | {
526 | if (!document.getElementById('anime' + i))
527 | {
528 | let url = resultsAnime[i].href;
529 | let urlDecoded = decodeURIComponent(url);
530 | let id = url.split('/')[4];
531 | let selector = 'a[href="' + urlDecoded + '"]:not(.picSurround > a)';
532 | if (!animeIds.includes(id))
533 | {
534 | addTranslation('anime', i, url, id, selector);
535 | }
536 | animeIds.push(id);
537 | }
538 | }
539 |
540 | // Manga Results
541 | let resultsManga = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)');
542 | let mangaIds = [];
543 | for (let i = 0; i < resultsManga.length; i+=2)
544 | {
545 | if (!document.getElementById('manga' + i))
546 | {
547 | let url = resultsManga[i].href;
548 | let urlDecoded = decodeURIComponent(url);
549 | let id = url.split('/')[4];
550 | let selector = 'a[href="' + urlDecoded + '"]:not(.picSurround > a)';
551 | if (!mangaIds.includes(id))
552 | {
553 | addTranslation('manga', i, url, id, selector);
554 | }
555 | mangaIds.push(id);
556 | }
557 | }
558 | }
559 | }
560 |
561 | // English title element to be added to page
562 | function createTranslationElement(styleId, englishTitle, styleIdEnd) {
563 | const container = document.createElement('div');
564 | container.innerHTML = styleId + englishTitle + styleIdEnd;
565 | container.firstElementChild.title = englishTitle;
566 | return container.firstElementChild;
567 | }
568 |
569 | // Get English title (storedAnime and getEnglishTitle) and add to page
570 | function addTranslation(type, count, url, id, selector, parent=false, tile=false, producer=false)
571 | {
572 | let styleId = ""
573 | let styleIdEnd = ""
574 | if (tile)
575 | {
576 | styleId = '