Pussytorrents Thumbnail Viewer

Shows thumbnail previews on torrent browse pages

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