Extension for nzbs.moe

A new extension

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})();