nCore Seed Limiter

Filter torrents by minimum seed count with a toggleable limiter

Size

10.5 KB

Version

1.1.1

Created

Feb 3, 2026

Updated

about 15 hours ago

1// ==UserScript==
2// @name		nCore Seed Limiter
3// @description		Filter torrents by minimum seed count with a toggleable limiter
4// @version		1.1.1
5// @match		https://*.ncore.pro/*
6// @icon		https://static.ncore.pro/styles/ncore.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('nCore Seed Limiter extension loaded');
12
13    // Wait for the page to load
14    if (document.readyState === 'loading') {
15        document.addEventListener('DOMContentLoaded', init);
16    } else {
17        init();
18    }
19
20    async function init() {
21        console.log('Initializing seed limiter...');
22        
23        // Check if we're on the torrents page
24        if (!window.location.href.includes('torrents.php')) {
25            console.log('Not on torrents page, skipping');
26            return;
27        }
28
29        // Remove ads first
30        removeAds();
31
32        // Wait for the torrent list to be present
33        const torrentList = await waitForElement('.lista_all');
34        if (!torrentList) {
35            console.log('Torrent list not found');
36            return;
37        }
38
39        // Load saved settings
40        const savedLimit = await GM.getValue('seedLimit', 50);
41        const savedEnabled = await GM.getValue('limiterEnabled', false);
42
43        // Create the limiter UI
44        createLimiterUI(savedLimit, savedEnabled);
45
46        // Apply filter if enabled
47        if (savedEnabled) {
48            applyFilter(savedLimit);
49        }
50    }
51
52    function removeAds() {
53        console.log('Removing ads...');
54        
55        // Remove banner ads with iframes
56        const banners = document.querySelectorAll('.banner[data-banner-id], .banner[data-zone-id]');
57        banners.forEach(banner => {
58            console.log('Removing banner ad:', banner);
59            banner.remove();
60        });
61
62        // Remove promotional links (hessteg ads)
63        const promoLinks = document.querySelectorAll('.hessteg-ad, a.list_alert.hessteg-ad');
64        promoLinks.forEach(link => {
65            console.log('Removing promotional link:', link);
66            link.remove();
67        });
68
69        // Use MutationObserver to remove ads that load dynamically
70        const observer = new MutationObserver((mutations) => {
71            mutations.forEach((mutation) => {
72                mutation.addedNodes.forEach((node) => {
73                    if (node.nodeType === 1) { // Element node
74                        // Check if the added node is a banner
75                        if (node.classList && (node.classList.contains('banner') || node.classList.contains('hessteg-ad'))) {
76                            console.log('Removing dynamically added ad:', node);
77                            node.remove();
78                        }
79                        // Check for banners within the added node
80                        const innerBanners = node.querySelectorAll ? node.querySelectorAll('.banner[data-banner-id], .banner[data-zone-id], .hessteg-ad') : [];
81                        innerBanners.forEach(banner => {
82                            console.log('Removing dynamically added inner ad:', banner);
83                            banner.remove();
84                        });
85                    }
86                });
87            });
88        });
89
90        observer.observe(document.body, {
91            childList: true,
92            subtree: true
93        });
94
95        console.log('Ad removal initialized');
96    }
97
98    function createLimiterUI(initialLimit, initialEnabled) {
99        console.log('Creating limiter UI with limit:', initialLimit, 'enabled:', initialEnabled);
100
101        // Find the pager area to insert our UI
102        const pagerTop = document.querySelector('#pager_top');
103        if (!pagerTop) {
104            console.log('Pager not found');
105            return;
106        }
107
108        // Create container for our UI
109        const container = document.createElement('div');
110        container.id = 'seed-limiter-container';
111        container.style.cssText = `
112            display: flex;
113            align-items: center;
114            gap: 10px;
115            margin: 10px 0;
116            padding: 10px;
117            background: #2a2a2a;
118            border-radius: 5px;
119            border: 1px solid #444;
120        `;
121
122        // Create label
123        const label = document.createElement('label');
124        label.textContent = 'Minimum seed count:';
125        label.style.cssText = `
126            color: #fff;
127            font-weight: bold;
128            font-size: 14px;
129        `;
130
131        // Create input field
132        const input = document.createElement('input');
133        input.type = 'number';
134        input.id = 'seed-limit-input';
135        input.value = initialLimit;
136        input.min = '0';
137        input.step = '1';
138        input.style.cssText = `
139            width: 80px;
140            padding: 5px 8px;
141            border: 1px solid #555;
142            border-radius: 3px;
143            background: #1a1a1a;
144            color: #fff;
145            font-size: 14px;
146        `;
147
148        // Create toggle button
149        const toggleBtn = document.createElement('button');
150        toggleBtn.id = 'seed-limiter-toggle';
151        toggleBtn.textContent = initialEnabled ? 'Disable Filter' : 'Enable Filter';
152        toggleBtn.style.cssText = `
153            padding: 6px 12px;
154            border: none;
155            border-radius: 3px;
156            background: ${initialEnabled ? '#dc3545' : '#28a745'};
157            color: #fff;
158            font-weight: bold;
159            cursor: pointer;
160            font-size: 14px;
161            transition: background 0.3s;
162        `;
163
164        toggleBtn.addEventListener('mouseenter', () => {
165            toggleBtn.style.opacity = '0.8';
166        });
167
168        toggleBtn.addEventListener('mouseleave', () => {
169            toggleBtn.style.opacity = '1';
170        });
171
172        // Create status text
173        const statusText = document.createElement('span');
174        statusText.id = 'seed-limiter-status';
175        statusText.style.cssText = `
176            color: #aaa;
177            font-size: 13px;
178            margin-left: 10px;
179        `;
180        updateStatusText(statusText, initialEnabled, initialLimit);
181
182        // Add event listeners
183        toggleBtn.addEventListener('click', async () => {
184            const currentEnabled = await GM.getValue('limiterEnabled', false);
185            const newEnabled = !currentEnabled;
186            const limit = parseInt(input.value) || 0;
187
188            await GM.setValue('limiterEnabled', newEnabled);
189            await GM.setValue('seedLimit', limit);
190
191            toggleBtn.textContent = newEnabled ? 'Disable Filter' : 'Enable Filter';
192            toggleBtn.style.background = newEnabled ? '#dc3545' : '#28a745';
193
194            updateStatusText(statusText, newEnabled, limit);
195
196            if (newEnabled) {
197                applyFilter(limit);
198            } else {
199                removeFilter();
200            }
201
202            console.log('Filter toggled:', newEnabled ? 'enabled' : 'disabled', 'with limit:', limit);
203        });
204
205        input.addEventListener('change', async () => {
206            const limit = parseInt(input.value) || 0;
207            await GM.setValue('seedLimit', limit);
208            
209            const enabled = await GM.getValue('limiterEnabled', false);
210            updateStatusText(statusText, enabled, limit);
211
212            if (enabled) {
213                applyFilter(limit);
214            }
215
216            console.log('Seed limit updated to:', limit);
217        });
218
219        // Assemble the UI
220        container.appendChild(label);
221        container.appendChild(input);
222        container.appendChild(toggleBtn);
223        container.appendChild(statusText);
224
225        // Insert before the pager
226        pagerTop.parentNode.insertBefore(container, pagerTop);
227    }
228
229    function updateStatusText(statusElement, enabled, limit) {
230        if (enabled) {
231            const hiddenCount = countHiddenTorrents(limit);
232            statusElement.textContent = `Filter active - Showing torrents with ${limit}+ seeds (${hiddenCount} hidden)`;
233            statusElement.style.color = '#ffc107';
234        } else {
235            statusElement.textContent = 'Filter inactive - Showing all torrents';
236            statusElement.style.color = '#aaa';
237        }
238    }
239
240    function applyFilter(minSeeds) {
241        console.log('Applying filter with minimum seeds:', minSeeds);
242        
243        const torrentBoxes = document.querySelectorAll('.box_torrent');
244        let hiddenCount = 0;
245
246        torrentBoxes.forEach(box => {
247            const seedBox = box.querySelector('.box_s2 a');
248            if (seedBox) {
249                const seedCount = parseInt(seedBox.textContent.trim()) || 0;
250                
251                if (seedCount < minSeeds) {
252                    box.style.display = 'none';
253                    hiddenCount++;
254                } else {
255                    box.style.display = '';
256                }
257            }
258        });
259
260        console.log(`Filter applied: ${hiddenCount} torrents hidden`);
261
262        // Update status text
263        const statusText = document.querySelector('#seed-limiter-status');
264        if (statusText) {
265            statusText.textContent = `Filter active - Showing torrents with ${minSeeds}+ seeds (${hiddenCount} hidden)`;
266        }
267    }
268
269    function removeFilter() {
270        console.log('Removing filter - showing all torrents');
271        
272        const torrentBoxes = document.querySelectorAll('.box_torrent');
273        torrentBoxes.forEach(box => {
274            box.style.display = '';
275        });
276    }
277
278    function countHiddenTorrents(minSeeds) {
279        const torrentBoxes = document.querySelectorAll('.box_torrent');
280        let count = 0;
281
282        torrentBoxes.forEach(box => {
283            const seedBox = box.querySelector('.box_s2 a');
284            if (seedBox) {
285                const seedCount = parseInt(seedBox.textContent.trim()) || 0;
286                if (seedCount < minSeeds) {
287                    count++;
288                }
289            }
290        });
291
292        return count;
293    }
294
295    function waitForElement(selector, timeout = 5000) {
296        return new Promise((resolve) => {
297            if (document.querySelector(selector)) {
298                return resolve(document.querySelector(selector));
299            }
300
301            const observer = new MutationObserver(() => {
302                if (document.querySelector(selector)) {
303                    observer.disconnect();
304                    resolve(document.querySelector(selector));
305                }
306            });
307
308            observer.observe(document.body, {
309                childList: true,
310                subtree: true
311            });
312
313            setTimeout(() => {
314                observer.disconnect();
315                resolve(null);
316            }, timeout);
317        });
318    }
319})();
nCore Seed Limiter | Robomonkey