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})();