Size
12.5 KB
Version
1.1.1
Created
Jan 28, 2026
Updated
7 days ago
1// ==UserScript==
2// @name DataScene Thumbnail Viewer
3// @description Display thumbnails for torrents in the adult section
4// @version 1.1.1
5// @match https://*.datascene.xyz/*
6// @icon https://datascene.xyz/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('DataScene Thumbnail Viewer initialized');
12
13 // Add styles for thumbnail display
14 TM_addStyle(`
15 .thumbnail-grid {
16 display: grid;
17 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
18 gap: 20px;
19 padding: 20px;
20 }
21
22 .thumbnail-item {
23 background: #1a1a1a;
24 border-radius: 8px;
25 overflow: hidden;
26 transition: transform 0.2s, box-shadow 0.2s;
27 cursor: pointer;
28 }
29
30 .thumbnail-item:hover {
31 transform: translateY(-5px);
32 box-shadow: 0 5px 20px rgba(0,0,0,0.3);
33 }
34
35 .thumbnail-image-container {
36 width: 100%;
37 height: 400px;
38 background: #2a2a2a;
39 display: flex;
40 align-items: center;
41 justify-content: center;
42 overflow: hidden;
43 position: relative;
44 }
45
46 .thumbnail-image {
47 width: 100%;
48 height: 100%;
49 object-fit: cover;
50 }
51
52 .thumbnail-placeholder {
53 color: #666;
54 font-size: 14px;
55 text-align: center;
56 padding: 20px;
57 }
58
59 .thumbnail-info {
60 padding: 12px;
61 }
62
63 .thumbnail-title {
64 font-size: 14px;
65 font-weight: bold;
66 color: #fff;
67 margin-bottom: 8px;
68 display: -webkit-box;
69 -webkit-line-clamp: 2;
70 -webkit-box-orient: vertical;
71 overflow: hidden;
72 text-overflow: ellipsis;
73 line-height: 1.4;
74 }
75
76 .thumbnail-meta {
77 display: flex;
78 justify-content: space-between;
79 font-size: 12px;
80 color: #999;
81 margin-top: 8px;
82 }
83
84 .thumbnail-stats {
85 display: flex;
86 gap: 10px;
87 font-size: 11px;
88 color: #999;
89 }
90
91 .thumbnail-stat {
92 display: flex;
93 align-items: center;
94 gap: 3px;
95 }
96
97 .thumbnail-stat i {
98 font-size: 10px;
99 }
100
101 .view-toggle-container {
102 display: flex;
103 justify-content: flex-end;
104 padding: 10px 20px;
105 gap: 10px;
106 }
107
108 .view-toggle-btn {
109 padding: 8px 16px;
110 background: #2a2a2a;
111 color: #fff;
112 border: 1px solid #444;
113 border-radius: 4px;
114 cursor: pointer;
115 font-size: 14px;
116 transition: all 0.2s;
117 }
118
119 .view-toggle-btn:hover {
120 background: #3a3a3a;
121 border-color: #666;
122 }
123
124 .view-toggle-btn.active {
125 background: #007bff;
126 border-color: #007bff;
127 }
128
129 .thumbnail-loading {
130 position: absolute;
131 top: 50%;
132 left: 50%;
133 transform: translate(-50%, -50%);
134 color: #666;
135 }
136 `);
137
138 async function init() {
139 // Check if we're on the adult section
140 const isAdultSection = window.location.href.includes('categoryIds[0]=6') ||
141 window.location.href.includes('/torrents?categoryIds%5B0%5D=6');
142
143 if (!isAdultSection) {
144 console.log('Not on adult section, extension inactive');
145 return;
146 }
147
148 console.log('Adult section detected, adding thumbnail view');
149
150 // Wait for the table to be present
151 const tableWrapper = document.querySelector('.data-table-wrapper.torrent-search--list__results');
152 if (!tableWrapper) {
153 console.log('Table wrapper not found');
154 return;
155 }
156
157 // Get current view preference
158 const currentView = await GM.getValue('thumbnailView', 'grid');
159
160 // Add view toggle buttons
161 addViewToggle(tableWrapper, currentView);
162
163 // Apply the current view
164 if (currentView === 'grid') {
165 await switchToGridView(tableWrapper);
166 }
167 }
168
169 function addViewToggle(tableWrapper, currentView) {
170 const toggleContainer = document.createElement('div');
171 toggleContainer.className = 'view-toggle-container';
172 toggleContainer.innerHTML = `
173 <button class="view-toggle-btn ${currentView === 'list' ? 'active' : ''}" data-view="list">
174 <i class="fas fa-list"></i> List View
175 </button>
176 <button class="view-toggle-btn ${currentView === 'grid' ? 'active' : ''}" data-view="grid">
177 <i class="fas fa-th"></i> Grid View
178 </button>
179 `;
180
181 tableWrapper.parentElement.insertBefore(toggleContainer, tableWrapper);
182
183 // Add click handlers
184 toggleContainer.querySelectorAll('.view-toggle-btn').forEach(btn => {
185 btn.addEventListener('click', async () => {
186 const view = btn.dataset.view;
187 await GM.setValue('thumbnailView', view);
188
189 // Update active state
190 toggleContainer.querySelectorAll('.view-toggle-btn').forEach(b => b.classList.remove('active'));
191 btn.classList.add('active');
192
193 // Switch view
194 if (view === 'grid') {
195 await switchToGridView(tableWrapper);
196 } else {
197 switchToListView(tableWrapper);
198 }
199 });
200 });
201 }
202
203 async function switchToGridView(tableWrapper) {
204 console.log('Switching to grid view');
205
206 // Get all torrent rows
207 const rows = tableWrapper.querySelectorAll('tbody tr[data-torrent-id]');
208 console.log(`Found ${rows.length} torrent rows`);
209
210 if (rows.length === 0) {
211 console.log('No torrent rows found');
212 return;
213 }
214
215 // Create grid container
216 const gridContainer = document.createElement('div');
217 gridContainer.className = 'thumbnail-grid';
218 gridContainer.id = 'thumbnail-grid-view';
219
220 // Extract torrent data and create grid items
221 const torrents = [];
222 rows.forEach(row => {
223 const torrentId = row.dataset.torrentId;
224 const nameLink = row.querySelector('a.torrent-search--list__name');
225 const name = nameLink ? nameLink.textContent.trim() : 'Unknown';
226 const url = nameLink ? nameLink.href : '#';
227
228 const seeders = row.querySelector('.torrent-search--list__seeders')?.textContent.trim() || '0';
229 const leechers = row.querySelector('.torrent-search--list__leechers')?.textContent.trim() || '0';
230 const size = row.querySelector('.torrent-search--list__size')?.textContent.trim() || 'N/A';
231
232 torrents.push({ torrentId, name, url, seeders, leechers, size });
233 });
234
235 // Create thumbnail items
236 for (const torrent of torrents) {
237 const item = createThumbnailItem(torrent);
238 gridContainer.appendChild(item);
239 }
240
241 // Hide table and show grid
242 tableWrapper.style.display = 'none';
243 tableWrapper.parentElement.appendChild(gridContainer);
244
245 // Fetch thumbnails
246 await fetchThumbnails(torrents);
247 }
248
249 function switchToListView(tableWrapper) {
250 console.log('Switching to list view');
251
252 // Remove grid view
253 const gridView = document.getElementById('thumbnail-grid-view');
254 if (gridView) {
255 gridView.remove();
256 }
257
258 // Show table
259 tableWrapper.style.display = 'block';
260 }
261
262 function createThumbnailItem(torrent) {
263 const item = document.createElement('div');
264 item.className = 'thumbnail-item';
265 item.dataset.torrentId = torrent.torrentId;
266 item.innerHTML = `
267 <div class="thumbnail-image-container" data-torrent-id="${torrent.torrentId}">
268 <div class="thumbnail-loading">
269 <i class="fas fa-spinner fa-spin"></i> Loading...
270 </div>
271 </div>
272 <div class="thumbnail-info">
273 <div class="thumbnail-title">${torrent.name}</div>
274 <div class="thumbnail-meta">
275 <span>${torrent.size}</span>
276 </div>
277 <div class="thumbnail-stats">
278 <span class="thumbnail-stat">
279 <i class="fas fa-arrow-up"></i> ${torrent.seeders}
280 </span>
281 <span class="thumbnail-stat">
282 <i class="fas fa-arrow-down"></i> ${torrent.leechers}
283 </span>
284 </div>
285 </div>
286 `;
287
288 // Add click handler to open torrent page
289 item.addEventListener('click', () => {
290 window.location.href = torrent.url;
291 });
292
293 return item;
294 }
295
296 async function fetchThumbnails(torrents) {
297 console.log(`Fetching thumbnails for ${torrents.length} torrents`);
298
299 // Fetch thumbnails in batches to avoid overwhelming the server
300 const batchSize = 5;
301 for (let i = 0; i < torrents.length; i += batchSize) {
302 const batch = torrents.slice(i, i + batchSize);
303 await Promise.all(batch.map(torrent => fetchThumbnail(torrent)));
304
305 // Small delay between batches
306 if (i + batchSize < torrents.length) {
307 await new Promise(resolve => setTimeout(resolve, 500));
308 }
309 }
310 }
311
312 async function fetchThumbnail(torrent) {
313 try {
314 console.log(`Fetching thumbnail for torrent ${torrent.torrentId}`);
315
316 // Fetch the torrent detail page
317 const response = await GM.xmlhttpRequest({
318 method: 'GET',
319 url: torrent.url,
320 timeout: 10000
321 });
322
323 // Parse the HTML to find the poster/thumbnail
324 const parser = new DOMParser();
325 const doc = parser.parseFromString(response.responseText, 'text/html');
326
327 // Look for poster image - common selectors for torrent sites
328 let imageUrl = null;
329
330 // Try various selectors
331 const posterImg = doc.querySelector('.torrent-poster img, .movie-poster img, img[alt*="poster"], img[alt*="Poster"], .poster img, [class*="poster"] img');
332 if (posterImg) {
333 imageUrl = posterImg.src;
334 }
335
336 // Try meta tags
337 if (!imageUrl) {
338 const ogImage = doc.querySelector('meta[property="og:image"]');
339 if (ogImage) {
340 imageUrl = ogImage.content;
341 }
342 }
343
344 // Update the thumbnail
345 const container = document.querySelector(`.thumbnail-image-container[data-torrent-id="${torrent.torrentId}"]`);
346 if (container) {
347 if (imageUrl) {
348 console.log(`Found thumbnail for ${torrent.torrentId}: ${imageUrl}`);
349 container.innerHTML = `<img src="${imageUrl}" class="thumbnail-image" alt="${torrent.name}">`;
350 } else {
351 console.log(`No thumbnail found for ${torrent.torrentId}`);
352 container.innerHTML = `
353 <div class="thumbnail-placeholder">
354 <i class="fas fa-image" style="font-size: 48px; color: #444;"></i>
355 <div style="margin-top: 10px;">No thumbnail available</div>
356 </div>
357 `;
358 }
359 }
360 } catch (error) {
361 console.error(`Error fetching thumbnail for ${torrent.torrentId}:`, error);
362 const container = document.querySelector(`.thumbnail-image-container[data-torrent-id="${torrent.torrentId}"]`);
363 if (container) {
364 container.innerHTML = `
365 <div class="thumbnail-placeholder">
366 <i class="fas fa-exclamation-triangle" style="font-size: 48px; color: #666;"></i>
367 <div style="margin-top: 10px;">Failed to load</div>
368 </div>
369 `;
370 }
371 }
372 }
373
374 // Initialize when DOM is ready
375 if (document.readyState === 'loading') {
376 document.addEventListener('DOMContentLoaded', init);
377 } else {
378 init();
379 }
380})();