Size
10.5 KB
Version
1.1.1
Created
Dec 31, 2025
Updated
about 16 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})();