XXXClub Thumbnail Viewer

A new extension

Size

7.0 KB

Version

1.1.1

Created

Feb 28, 2026

Updated

22 days ago

1// ==UserScript==
2// @name		XXXClub Thumbnail Viewer
3// @description		A new extension
4// @version		1.1.1
5// @match		https://*.xxxclub.to/*
6// @icon		https://xxxclub.to/assets/icons/favicon-32x32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('XXXClub Thumbnail Extension loaded');
12
13    // Cache for storing fetched thumbnails
14    const thumbnailCache = {};
15
16    // Debounce function to avoid excessive calls
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            const later = () => {
21                clearTimeout(timeout);
22                func(...args);
23            };
24            clearTimeout(timeout);
25            timeout = setTimeout(later, wait);
26        };
27    }
28
29    // Fetch thumbnail from torrent detail page
30    async function fetchThumbnail(torrentUrl, torrentId) {
31        // Check cache first
32        if (thumbnailCache[torrentId]) {
33            return thumbnailCache[torrentId];
34        }
35
36        try {
37            console.log('Fetching thumbnail for:', torrentId);
38            const response = await fetch(torrentUrl);
39            const html = await response.text();
40            
41            const parser = new DOMParser();
42            const doc = parser.parseFromString(html, 'text/html');
43            
44            // Find the poster image
45            const posterImg = doc.querySelector('img.detailsposter');
46            
47            if (posterImg && posterImg.src) {
48                thumbnailCache[torrentId] = posterImg.src;
49                return posterImg.src;
50            }
51            
52            console.log('No thumbnail found for:', torrentId);
53            return null;
54        } catch (error) {
55            console.error('Error fetching thumbnail:', error);
56            return null;
57        }
58    }
59
60    // Add thumbnail to a torrent list item
61    async function addThumbnailToItem(listItem) {
62        // Skip if already processed
63        if (listItem.dataset.thumbnailProcessed) {
64            return;
65        }
66        listItem.dataset.thumbnailProcessed = 'true';
67
68        // Find the torrent link
69        const torrentLink = listItem.querySelector('a[href*="/torrents/details/"]');
70        if (!torrentLink) {
71            return;
72        }
73
74        const torrentUrl = torrentLink.href;
75        const torrentId = torrentUrl.split('/').pop();
76
77        // Find the name span (second span in the list item)
78        const nameSpan = listItem.querySelector('span:nth-child(2)');
79        if (!nameSpan) {
80            return;
81        }
82
83        // Create thumbnail container
84        const thumbnailContainer = document.createElement('div');
85        thumbnailContainer.className = 'xc-thumbnail-container';
86        thumbnailContainer.innerHTML = '<div class="xc-thumbnail-loading">Loading...</div>';
87
88        // Insert thumbnail before the name
89        nameSpan.insertBefore(thumbnailContainer, nameSpan.firstChild);
90
91        // Fetch and display thumbnail
92        const thumbnailUrl = await fetchThumbnail(torrentUrl, torrentId);
93        
94        if (thumbnailUrl) {
95            thumbnailContainer.innerHTML = `
96                <a href="${torrentUrl}" class="xc-thumbnail-link">
97                    <img src="${thumbnailUrl}" alt="Thumbnail" class="xc-thumbnail-img" loading="lazy">
98                </a>
99            `;
100        } else {
101            thumbnailContainer.innerHTML = '<div class="xc-thumbnail-placeholder">No image</div>';
102        }
103    }
104
105    // Process all torrent items on the page
106    async function processTorrents() {
107        console.log('Processing torrents...');
108        const torrentItems = document.querySelectorAll('.browsetableinside ul li:not(:first-child)');
109        
110        console.log('Found', torrentItems.length, 'torrent items');
111        
112        // Process items in batches to avoid overwhelming the server
113        const batchSize = 3;
114        for (let i = 0; i < torrentItems.length; i += batchSize) {
115            const batch = Array.from(torrentItems).slice(i, i + batchSize);
116            await Promise.all(batch.map(item => addThumbnailToItem(item)));
117            
118            // Small delay between batches
119            if (i + batchSize < torrentItems.length) {
120                await new Promise(resolve => setTimeout(resolve, 500));
121            }
122        }
123        
124        console.log('Finished processing torrents');
125    }
126
127    // Add CSS styles
128    function addStyles() {
129        const style = document.createElement('style');
130        style.textContent = `
131            .xc-thumbnail-container {
132                display: inline-block;
133                margin-right: 12px;
134                vertical-align: middle;
135            }
136            
137            .xc-thumbnail-link {
138                display: block !important;
139                border: 2px solid #ddd;
140                border-radius: 4px;
141                overflow: hidden;
142                transition: border-color 0.2s, transform 0.2s;
143            }
144            
145            .xc-thumbnail-link:hover {
146                border-color: #007bff;
147                transform: scale(1.05);
148            }
149            
150            .xc-thumbnail-img {
151                width: auto !important;
152                height: 200px !important;
153                max-width: 300px !important;
154                object-fit: contain;
155                display: block !important;
156                visibility: visible !important;
157                opacity: 1 !important;
158            }
159            
160            .xc-thumbnail-loading,
161            .xc-thumbnail-placeholder {
162                width: 150px;
163                height: 200px;
164                display: flex;
165                align-items: center;
166                justify-content: center;
167                background: #f0f0f0;
168                color: #666;
169                font-size: 11px;
170                text-align: center;
171                border-radius: 4px;
172                border: 2px solid #ddd;
173            }
174            
175            /* Adjust the name span to accommodate thumbnail */
176            .browsetableinside ul li span:nth-child(2) {
177                display: flex;
178                align-items: center;
179            }
180        `;
181        document.head.appendChild(style);
182    }
183
184    // Initialize the extension
185    function init() {
186        console.log('Initializing XXXClub Thumbnail Extension');
187        
188        // Add styles
189        addStyles();
190        
191        // Wait for page to be ready
192        if (document.readyState === 'loading') {
193            document.addEventListener('DOMContentLoaded', processTorrents);
194        } else {
195            processTorrents();
196        }
197        
198        // Watch for dynamic content changes
199        const debouncedProcess = debounce(processTorrents, 1000);
200        const observer = new MutationObserver(debouncedProcess);
201        
202        // Observe the browse table for changes
203        const browseTable = document.querySelector('.browsetableinside');
204        if (browseTable) {
205            observer.observe(browseTable, {
206                childList: true,
207                subtree: true
208            });
209        }
210    }
211
212    // Start the extension
213    init();
214})();