Size
8.1 KB
Version
1.1.14
Created
Jan 22, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Pussytorrents Thumbnail Viewer
3// @description Shows thumbnail previews on torrent browse pages
4// @version 1.1.14
5// @match https://*.pussytorrents.org/*
6// @icon https://pussytorrents.org/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Pussytorrents Thumbnail Viewer: Starting...');
12 console.log('Current URL:', window.location.href);
13 console.log('Pathname:', window.location.pathname);
14
15 // Only run on browse pages
16 if (!window.location.pathname.includes('/torrents/browse')) {
17 console.log('Not a browse page, exiting');
18 return;
19 }
20
21 console.log('Browse page detected, initializing...');
22
23 // Add CSS for thumbnails
24 const style = document.createElement('style');
25 style.textContent = `
26 .thumbnail-preview {
27 width: auto;
28 height: auto;
29 max-width: 100%;
30 border-radius: 4px;
31 cursor: pointer;
32 transition: transform 0.2s;
33 }
34 .thumbnail-preview:hover {
35 transform: scale(1.1);
36 z-index: 1000;
37 position: relative;
38 box-shadow: 0 4px 8px rgba(0,0,0,0.3);
39 }
40 .thumbnail-cell {
41 padding: 5px !important;
42 vertical-align: middle !important;
43 }
44 .thumbnail-loading {
45 width: 200px;
46 height: 150px;
47 background: #f0f0f0;
48 border-radius: 4px;
49 display: flex;
50 align-items: center;
51 justify-content: center;
52 color: #666;
53 font-size: 12px;
54 }
55 `;
56 document.head.appendChild(style);
57
58 // Function to fetch thumbnail for a torrent
59 async function fetchThumbnail(torrentId) {
60 try {
61 console.log(`Fetching thumbnail for torrent ${torrentId}`);
62 const response = await fetch(`/torrent/${torrentId}`);
63 const html = await response.text();
64
65 // Parse the HTML to find the first thumbnail
66 const parser = new DOMParser();
67 const doc = parser.parseFromString(html, 'text/html');
68 const firstImage = doc.querySelector('#torrentDetails img[src*="imageservice.pussytorrents.org"]');
69
70 if (firstImage) {
71 // Use the thumbnail image directly (it's already the right size)
72 let thumbnailUrl = firstImage.src;
73 console.log(`Found thumbnail for torrent ${torrentId}: ${thumbnailUrl}`);
74 return thumbnailUrl;
75 }
76 console.log(`No thumbnail found for torrent ${torrentId}`);
77 return null;
78 } catch (error) {
79 console.error(`Error fetching thumbnail for torrent ${torrentId}:`, error);
80 return null;
81 }
82 }
83
84 // Function to add thumbnail column to table
85 function addThumbnailColumn() {
86 const table = document.querySelector('#torrenttable');
87 if (!table) {
88 console.log('Torrent table not found');
89 return;
90 }
91
92 // Add header for thumbnail column
93 const headerRow = table.querySelector('thead tr');
94 if (headerRow && !headerRow.querySelector('.thumbnail-header')) {
95 const th = document.createElement('th');
96 th.className = 'thumbnail-header centered';
97 th.textContent = 'Preview';
98 th.style.width = '130px';
99 headerRow.insertBefore(th, headerRow.firstChild);
100 console.log('Added thumbnail header');
101 }
102
103 // Add thumbnail cells to each row
104 const rows = table.querySelectorAll('tbody tr');
105 console.log(`Found ${rows.length} torrent rows`);
106
107 rows.forEach((row, index) => {
108 // Skip if already has thumbnail cell
109 if (row.querySelector('.thumbnail-cell')) {
110 return;
111 }
112
113 const torrentId = row.id;
114 if (!torrentId) {
115 console.log(`Row ${index} has no ID, skipping`);
116 return;
117 }
118
119 // Create thumbnail cell
120 const td = document.createElement('td');
121 td.className = 'thumbnail-cell centered';
122
123 // Add loading placeholder
124 const loadingDiv = document.createElement('div');
125 loadingDiv.className = 'thumbnail-loading';
126 loadingDiv.textContent = 'Loading...';
127 td.appendChild(loadingDiv);
128
129 row.insertBefore(td, row.firstChild);
130
131 // Fetch and display thumbnail
132 fetchThumbnail(torrentId).then(thumbnailUrl => {
133 if (thumbnailUrl) {
134 const img = document.createElement('img');
135 img.src = thumbnailUrl;
136 img.className = 'thumbnail-preview';
137 img.alt = 'Thumbnail';
138
139 // Add load event to verify image loaded
140 img.onload = function() {
141 console.log(`Image loaded successfully for torrent ${torrentId}, dimensions: ${img.naturalWidth}x${img.naturalHeight}`);
142 };
143
144 img.onerror = function() {
145 console.error(`Image failed to load for torrent ${torrentId}, URL: ${thumbnailUrl}`);
146 td.innerHTML = '<div class="thumbnail-loading">Image error</div>';
147 };
148
149 // Click to open torrent detail page
150 img.addEventListener('click', () => {
151 window.location.href = `/torrent/${torrentId}`;
152 });
153
154 td.innerHTML = '';
155 td.appendChild(img);
156 console.log(`Successfully added thumbnail for torrent ${torrentId}`);
157 } else {
158 td.innerHTML = '<div class="thumbnail-loading">No preview</div>';
159 }
160 }).catch(error => {
161 console.error(`Error loading thumbnail for torrent ${torrentId}:`, error);
162 td.innerHTML = '<div class="thumbnail-loading">Error</div>';
163 });
164 });
165 }
166
167 // Debounce function to avoid too many calls
168 function debounce(func, wait) {
169 let timeout;
170 return function executedFunction(...args) {
171 const later = () => {
172 clearTimeout(timeout);
173 func(...args);
174 };
175 clearTimeout(timeout);
176 timeout = setTimeout(later, wait);
177 };
178 }
179
180 // Wait for the table to be ready
181 function init() {
182 const table = document.querySelector('#torrenttable');
183 if (table) {
184 console.log('Table found, adding thumbnails');
185 addThumbnailColumn();
186
187 // Also observe for dynamic content changes
188 const debouncedAddThumbnails = debounce(() => {
189 const rows = table.querySelectorAll('tbody tr:not(:has(.thumbnail-cell))');
190 if (rows.length > 0) {
191 console.log('New rows detected, adding thumbnails');
192 addThumbnailColumn();
193 }
194 }, 500);
195
196 const observer = new MutationObserver(debouncedAddThumbnails);
197 observer.observe(table, { childList: true, subtree: true });
198 } else {
199 console.log('Table not found, waiting...');
200 setTimeout(init, 1000);
201 }
202 }
203
204 // Watch for the entire torrents div being replaced (when filters are applied)
205 const torrentsContainer = document.querySelector('#torrents');
206 if (torrentsContainer) {
207 const containerObserver = new MutationObserver(debounce(() => {
208 console.log('Torrents container changed, re-initializing...');
209 init();
210 }, 500));
211
212 containerObserver.observe(torrentsContainer.parentElement, { childList: true, subtree: true });
213 }
214
215 // Start when DOM is ready
216 if (document.readyState === 'loading') {
217 document.addEventListener('DOMContentLoaded', init);
218 } else {
219 init();
220 }
221
222 console.log('Pussytorrents Thumbnail Viewer: Initialized');
223})();