DataScene Thumbnail Viewer

Display thumbnails for torrents in the adult section

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})();
DataScene Thumbnail Viewer | Robomonkey