Size
11.4 KB
Version
1.1.11
Created
Jan 17, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Extension for nzbs.moe
3// @description A new extension
4// @version 1.1.11
5// @match https://*.nzbs.moe/*
6// @match https://nyaa.si/*
7// @icon https://nzbs.moe/static/img/favicon.svg
8// ==/UserScript==
9(function() {
10 'use strict';
11
12 console.log('Extension initialized on:', window.location.hostname);
13
14 const ANIMEBYTES_API_KEY = 'qDml6cPVnAsuE7AceT6XRKW3rvi4cGsfGXlik6dwZgtnqurWpqWgz8lc';
15
16 async function searchAniList(animeTitle) {
17 const query = `
18 query ($search: String) {
19 Page {
20 media(search: $search, type: ANIME) {
21 id
22 idMal
23 title {
24 romaji
25 english
26 native
27 }
28 }
29 }
30 }
31 `;
32
33 const variables = {
34 search: animeTitle
35 };
36
37 try {
38 const response = await fetch('https://graphql.anilist.co', {
39 method: 'POST',
40 headers: {
41 'Content-Type': 'application/json',
42 'Accept': 'application/json',
43 },
44 body: JSON.stringify({
45 query: query,
46 variables: variables
47 })
48 });
49
50 const data = await response.json();
51
52 if (data.data && data.data.Page && data.data.Page.media && data.data.Page.media.length > 0) {
53 return {
54 anilistId: data.data.Page.media[0].id,
55 malId: data.data.Page.media[0].idMal
56 };
57 }
58
59 return null;
60 } catch (error) {
61 console.error('Error searching AniList:', error);
62 return null;
63 }
64 }
65
66 async function searchAnimeBytes(animeTitle) {
67 try {
68 const encodedTitle = encodeURIComponent(animeTitle);
69 const url = `https://animebytes.tv/scrape.php?torrent_pass=${ANIMEBYTES_API_KEY}&type=anime&searchstr=${encodedTitle}&search_type=title`;
70
71 const response = await fetch(url);
72 const data = await response.json();
73
74 console.log('AnimeBytes search results:', data);
75
76 if (data.Groups && data.Groups.length > 0) {
77 // Return the series ID of the first result
78 return data.Groups[0].SeriesID;
79 }
80
81 return null;
82 } catch (error) {
83 console.error('Error searching AnimeBytes:', error);
84 return null;
85 }
86 }
87
88 function extractAnimeTitle(fullTitle) {
89 // Remove release group tags like [FLE], [SubsPlease], etc.
90 let title = fullTitle.replace(/^\[.*?\]\s*/, '');
91
92 // Split by pipe and take the first part (before the pipe is usually the main title)
93 if (title.includes('|')) {
94 title = title.split('|')[0].trim();
95 }
96
97 // Remove quality info like (WEB 1080p...), [1080p], etc.
98 title = title.replace(/\(WEB[^)]*\)/gi, '');
99 title = title.replace(/\(BD[^)]*\)/gi, '');
100 title = title.replace(/\([0-9]{3,4}p[^)]*\)/gi, '');
101 title = title.replace(/\[.*?\]/g, '');
102
103 // Remove year in parentheses like (2025), (2024), etc.
104 title = title.replace(/\(\d{4}\)/g, '');
105
106 // Remove common patterns like "- 01", "S01E01", etc. but keep movie titles
107 title = title.replace(/\s*-\s*\d+\s*$/, '');
108 title = title.replace(/\s*S\d+E\d+.*$/i, '');
109
110 // Clean up extra spaces
111 title = title.replace(/\s+/g, ' ').trim();
112
113 return title;
114 }
115
116 function createButton(text, iconClass, backgroundColor, hoverColor) {
117 const button = document.createElement('a');
118 button.href = '#';
119 button.textContent = text;
120 button.style.cssText = `
121 display: inline-flex;
122 align-items: center;
123 gap: 6px;
124 padding: 6px 12px;
125 background-color: ${backgroundColor};
126 color: white;
127 text-decoration: none;
128 border-radius: 4px;
129 font-size: 14px;
130 font-weight: 500;
131 margin-left: 10px;
132 transition: background-color 0.2s;
133 cursor: pointer;
134 `;
135
136 const icon = document.createElement('i');
137 icon.className = iconClass;
138 icon.style.fontSize = '12px';
139 button.appendChild(icon);
140
141 // Hover effect
142 button.addEventListener('mouseenter', () => {
143 button.style.backgroundColor = hoverColor;
144 });
145 button.addEventListener('mouseleave', () => {
146 button.style.backgroundColor = backgroundColor;
147 });
148
149 return button;
150 }
151
152 async function initNzbsMoe() {
153 // Get the anime title from the breadcrumb
154 const breadcrumbLink = document.querySelector('.breadcrumb-item.active a[href^="/series/"]');
155
156 if (!breadcrumbLink) {
157 console.log('Anime title not found');
158 return;
159 }
160
161 const animeTitle = breadcrumbLink.textContent.trim();
162 console.log('Found anime title:', animeTitle);
163
164 // Create SeaDex button
165 const seadexButton = createButton('Search on SeaDex', 'fas fa-external-link-alt', '#0d6efd', '#0b5ed7');
166
167 seadexButton.addEventListener('click', async (e) => {
168 e.preventDefault();
169
170 const originalText = seadexButton.textContent;
171 seadexButton.textContent = 'Searching...';
172 seadexButton.style.pointerEvents = 'none';
173
174 try {
175 const result = await searchAniList(animeTitle);
176
177 if (result && result.anilistId) {
178 console.log('Found AniList ID:', result.anilistId);
179 window.open(`https://releases.moe/${result.anilistId}`, '_blank');
180 } else {
181 console.log('Anime not found on AniList, opening search');
182 window.open('https://releases.moe/', '_blank');
183 }
184 } catch (error) {
185 console.error('Error:', error);
186 window.open('https://releases.moe/', '_blank');
187 } finally {
188 seadexButton.textContent = originalText;
189 seadexButton.style.pointerEvents = 'auto';
190 }
191 });
192
193 // Create AnimeBytes button
194 const animeBytesButton = createButton('Search on AnimeBytes', 'fas fa-external-link-alt', '#dc3545', '#bb2d3b');
195
196 animeBytesButton.addEventListener('click', async (e) => {
197 e.preventDefault();
198
199 const originalText = animeBytesButton.textContent;
200 animeBytesButton.textContent = 'Searching...';
201 animeBytesButton.style.pointerEvents = 'none';
202
203 try {
204 const seriesId = await searchAnimeBytes(animeTitle);
205
206 if (seriesId) {
207 console.log('Found AnimeBytes Series ID:', seriesId);
208 window.open(`https://animebytes.tv/series.php?id=${seriesId}`, '_blank');
209 } else {
210 console.log('Anime not found on AnimeBytes, opening search');
211 const encodedTitle = encodeURIComponent(animeTitle);
212 window.open(`https://animebytes.tv/torrents.php?searchstr=${encodedTitle}&action=advanced&search_type=title`, '_blank');
213 }
214 } catch (error) {
215 console.error('Error:', error);
216 const encodedTitle = encodeURIComponent(animeTitle);
217 window.open(`https://animebytes.tv/torrents.php?searchstr=${encodedTitle}&action=advanced&search_type=title`, '_blank');
218 } finally {
219 animeBytesButton.textContent = originalText;
220 animeBytesButton.style.pointerEvents = 'auto';
221 }
222 });
223
224 // Find a good place to insert the buttons - next to the breadcrumb
225 const breadcrumbContainer = document.querySelector('.breadcrumb-item.active');
226 if (breadcrumbContainer) {
227 breadcrumbContainer.style.display = 'flex';
228 breadcrumbContainer.style.alignItems = 'center';
229 breadcrumbContainer.appendChild(seadexButton);
230 breadcrumbContainer.appendChild(animeBytesButton);
231 console.log('Buttons added successfully');
232 }
233 }
234
235 async function initNyaa() {
236 // Get the torrent title
237 const titleElement = document.querySelector('.panel-title');
238
239 if (!titleElement) {
240 console.log('Torrent title not found');
241 return;
242 }
243
244 const fullTitle = titleElement.textContent.trim();
245 const animeTitle = extractAnimeTitle(fullTitle);
246 console.log('Found torrent title:', fullTitle);
247 console.log('Extracted anime title:', animeTitle);
248
249 // Create nzbs.moe button
250 const nzbsButton = createButton('Search on nzbs.moe', 'fas fa-external-link-alt', '#28a745', '#218838');
251
252 nzbsButton.addEventListener('click', async (e) => {
253 e.preventDefault();
254
255 const originalText = nzbsButton.textContent;
256 nzbsButton.textContent = 'Searching...';
257 nzbsButton.style.pointerEvents = 'none';
258
259 try {
260 // Get AniList ID
261 const result = await searchAniList(animeTitle);
262
263 if (result && result.anilistId) {
264 console.log('Found AniList ID:', result.anilistId);
265 // Use the direct nzbs.moe URL pattern with AniList ID
266 window.open(`https://nzbs.moe/series/al/${result.anilistId}`, '_blank');
267 } else {
268 console.log('Anime not found on AniList, opening search');
269 const encodedTitle = encodeURIComponent(animeTitle);
270 window.open(`https://nzbs.moe/search?query=${encodedTitle}`, '_blank');
271 }
272 } catch (error) {
273 console.error('Error:', error);
274 const encodedTitle = encodeURIComponent(animeTitle);
275 window.open(`https://nzbs.moe/search?query=${encodedTitle}`, '_blank');
276 } finally {
277 nzbsButton.textContent = originalText;
278 nzbsButton.style.pointerEvents = 'auto';
279 }
280 });
281
282 // Find the panel footer and add the button
283 const panelFooter = document.querySelector('.panel-footer');
284 if (panelFooter) {
285 // Add some spacing
286 const separator = document.createTextNode(' or ');
287 panelFooter.insertBefore(separator, panelFooter.firstChild);
288 panelFooter.insertBefore(nzbsButton, panelFooter.firstChild);
289 console.log('nzbs.moe button added successfully');
290 }
291 }
292
293 // Wait for the page to be ready
294 if (document.readyState === 'loading') {
295 document.addEventListener('DOMContentLoaded', init);
296 } else {
297 init();
298 }
299
300 function init() {
301 if (window.location.hostname.includes('nzbs.moe')) {
302 initNzbsMoe();
303 } else if (window.location.hostname.includes('nyaa.si')) {
304 initNyaa();
305 }
306 }
307})();