🏠 Home 

Nyaa MyAnimeList Search Button

Adds a quick MyAnimeList (MAL) search button to Nyaa.si posts (during search and inside posts) for Anime, Manga, and Light Novels.


Install this script?
// ==UserScript==
// @name         Nyaa MyAnimeList Search Button
// @namespace    https://greasyfork.org/users/whitewriter
// @version      1.3
// @description  Adds a quick MyAnimeList (MAL) search button to Nyaa.si posts (during search and inside posts) for Anime, Manga, and Light Novels.
// @author       WhiteWriter
// @match        https://nyaa.si/*
// @license      MIT
// @icon         https://nyaa.si/static/favicon.png
// @run-at       document-end
// ==/UserScript==
(function() {
'use strict';
// Check if we're inside a post (/view/ in the url) or the main/search page
const isViewPage = window.location.pathname.startsWith('/view/');
if (isViewPage) {
// Handle individual post page
processViewPage();
} else {
// Handle main/search page
processMainPage();
}
// Function to process main/search page
function processMainPage() {
// Select all post rows (any <tr> inside <tbody>)
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
// Get category from first <td>
const categoryTd = row.children[0];
if (!categoryTd) return;
const categoryA = categoryTd.querySelector('a');
if (!categoryA) return;
const categoryTitle = categoryA.getAttribute('title').toLowerCase();
// Determine contentType (literature counts for both manga and light novel)
let contentType;
if (categoryTitle.includes('anime')) {
contentType = 'anime';
} else if (categoryTitle.includes('literature')) {
contentType = 'manga';
}
if (!contentType) return;
// Get title from second <td>, skipping comments if present
const titleTd = row.children[1];
if (!titleTd) return;
const titleA = Array.from(titleTd.querySelectorAll('a')).find(a => !a.classList.contains('comments'));
if (!titleA) return;
const title = titleA.getAttribute('title');
// Clean title and proceed. contentName is the best approximation to the
// content's title in order to search on MAL (doesn't need to be exact)
const contentName = cleanTitle(title);
if (contentName) {
addMalButton(row.children[2], contentType, contentName);
}
});
}
// Function to process individual view page (inside a post)
function processViewPage() {
// Get title from <h3 class="panel-title">
const titleElement = document.querySelector('div.panel-heading h3.panel-title');
if (!titleElement) return;
const title = titleElement.textContent;
// Get category from <div class="col-md-5"> <a>
const categoryElement = document.querySelector('div.panel-body div.row div.col-md-5 a');
if (!categoryElement) return;
const categoryText = categoryElement.textContent.toLowerCase();
// Determine contentType
let contentType;
if (categoryText.includes('anime')) {
contentType = 'anime';
} else if (categoryText.includes('literature')) {
contentType = 'manga';
}
if (!contentType) return;
// Clean title
const contentName = cleanTitle(title);
if (contentName) {
// Add button to panel-footer
const footer = document.querySelector('div.panel-footer.clearfix');
if (footer) {
addMalButton(footer, contentType, contentName);
}
}
}
// Function to add the MAL button
function addMalButton(container, contentType, contentName) {
const baseUrl = contentType === 'anime'
? 'https://myanimelist.net/anime.php'
: 'https://myanimelist.net/manga.php';
const params = new URLSearchParams();
params.append('q', contentName);
const malUrl = `${baseUrl}?${params.toString()}`;
const buttonHtml = `<a href="${malUrl}" target="_blank" style="display: inline-block; padding: 5px 10px; background-color: #337ab7; color: white; text-decoration: none; border-radius: 3px; margin-left: 5px;">MAL</a>`;
container.insertAdjacentHTML('beforeend', buttonHtml);
}
// Function to clean the title and extract contentName
function cleanTitle(title) {
// Remove text within brackets (may sacrifice the button on posts with the content title within brackets)
let cleaned = title
.replace(/\[.*?\]/g, '')
.replace(/\([^()]*?\)/g, '')
.replace(/\{.*?\}/g, '');
// Remove specific keywords with optional punctuation
const keywords = [
'1080p', '2160p', '720p', '480p', '360p', 'Multi-Audio', 'Multi Audio', '10bit',
'AV1', 'MP4', 'AAC', 'EAC3', 'E-AC3', 'AC3', 'DTS', 'DTS-HD', 'UHD', 'HDR',
'English Dub', 'Dual-Audio', 'Dual Audio', 'x264', 'x265', 'h.264', 'h.265',
'Opus', 'AVI', 'WMV', 'VFVOSTFR', 'BDRip', 'BluRay', 'BD', 'WEB', 'Eng Sub',
'Subbed', 'FLAC', '10-bit', 'Batch', 'HD', 'Horribl###bs', 'Horrible-Subs',
'Multi-Subs', 'VOSTFR', 'FLAC2.0', 'FLAC5.1', 'FLAC7.1', 'MPEG', 'WebRip',
'HEVC', '8bit', 'Web-DL', 'AAC2.0', 'AAC5.1', 'Multi-Sub',
'Multi Audio', 'CR', 'DDP'
];
const regex = new RegExp(
'(?<!\\w)(?:' + keywords.join('|') + ')(?:[.,|-])?(?!\\w)',
'gi'
);
cleaned = cleaned.replace(regex, '');
// Trim and normalize spaces, remove leftover punctuation
cleaned = cleaned
.trim()
.replace(/\s+/g, ' ')
.replace(/^[.,|-]+|[.,|-]+$/g, '');
return cleaned;
}
})();