Size
12.7 KB
Version
1.1.1
Created
Feb 23, 2026
Updated
about 2 months ago
1// ==UserScript==
2// @name PornXnow Download Manager
3// @description Download videos from PornXnow with ease
4// @version 1.1.1
5// @match https://*.pornxnow.me/*
6// @icon https://pornxnow.me/wp-content/uploads/2019/05/cropped-favicon-pornxnow-32x32.png
7// @grant GM.xmlhttpRequest
8// @grant GM.setValue
9// @grant GM.getValue
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('PornXnow Download Manager initialized');
15
16 // Debounce function to prevent 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 // Add custom styles for the download button
30 function addStyles() {
31 const styles = `
32 .pxnow-download-btn {
33 position: relative;
34 display: inline-flex;
35 align-items: center;
36 gap: 8px;
37 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38 color: white;
39 padding: 12px 24px;
40 border: none;
41 border-radius: 8px;
42 font-size: 16px;
43 font-weight: 600;
44 cursor: pointer;
45 transition: all 0.3s ease;
46 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
47 margin: 20px 0;
48 text-decoration: none;
49 }
50
51 .pxnow-download-btn:hover {
52 transform: translateY(-2px);
53 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
54 }
55
56 .pxnow-download-btn:active {
57 transform: translateY(0);
58 }
59
60 .pxnow-download-btn.loading {
61 opacity: 0.7;
62 cursor: wait;
63 }
64
65 .pxnow-download-btn.loading::after {
66 content: '';
67 position: absolute;
68 width: 16px;
69 height: 16px;
70 top: 50%;
71 right: 12px;
72 margin-top: -8px;
73 border: 2px solid white;
74 border-radius: 50%;
75 border-top-color: transparent;
76 animation: spinner 0.6s linear infinite;
77 }
78
79 @keyframes spinner {
80 to { transform: rotate(360deg); }
81 }
82
83 .pxnow-download-container {
84 background: #f8f9fa;
85 padding: 20px;
86 border-radius: 12px;
87 margin: 20px 0;
88 box-shadow: 0 2px 10px rgba(0,0,0,0.1);
89 }
90
91 .pxnow-download-title {
92 font-size: 18px;
93 font-weight: bold;
94 color: #333;
95 margin-bottom: 15px;
96 }
97
98 .pxnow-quality-option {
99 display: block;
100 padding: 10px 15px;
101 margin: 8px 0;
102 background: white;
103 border: 2px solid #e0e0e0;
104 border-radius: 6px;
105 color: #333;
106 text-decoration: none;
107 transition: all 0.2s ease;
108 }
109
110 .pxnow-quality-option:hover {
111 border-color: #667eea;
112 background: #f0f4ff;
113 transform: translateX(5px);
114 }
115
116 .pxnow-status {
117 padding: 10px;
118 margin: 10px 0;
119 border-radius: 6px;
120 font-size: 14px;
121 }
122
123 .pxnow-status.success {
124 background: #d4edda;
125 color: #155724;
126 border: 1px solid #c3e6cb;
127 }
128
129 .pxnow-status.error {
130 background: #f8d7da;
131 color: #721c24;
132 border: 1px solid #f5c6cb;
133 }
134
135 .pxnow-status.info {
136 background: #d1ecf1;
137 color: #0c5460;
138 border: 1px solid #bee5eb;
139 }
140 `;
141
142 const styleElement = document.createElement('style');
143 styleElement.textContent = styles;
144 document.head.appendChild(styleElement);
145 }
146
147 // Extract video sources from iframe
148 async function extractVideoSources(iframeUrl) {
149 console.log('Extracting video sources from:', iframeUrl);
150
151 return new Promise((resolve, reject) => {
152 GM.xmlhttpRequest({
153 method: 'GET',
154 url: iframeUrl,
155 onload: function(response) {
156 console.log('Iframe content loaded');
157 const html = response.responseText;
158 const sources = [];
159
160 // Try to find video sources in various formats
161 // Look for direct video URLs
162 const videoUrlPatterns = [
163 /sources:\s*\[([^\]]+)\]/g,
164 /file:\s*["']([^"']+\.mp4[^"']*)["']/g,
165 /src:\s*["']([^"']+\.mp4[^"']*)["']/g,
166 /https?:\/\/[^"'\s]+\.mp4[^"'\s]*/g
167 ];
168
169 videoUrlPatterns.forEach(pattern => {
170 let match;
171 while ((match = pattern.exec(html)) !== null) {
172 const url = match[1] || match[0];
173 if (url && url.includes('.mp4')) {
174 sources.push(url.replace(/['"]/g, ''));
175 }
176 }
177 });
178
179 // Look for m3u8 streams
180 const m3u8Pattern = /https?:\/\/[^"'\s]+\.m3u8[^"'\s]*/g;
181 let m3u8Match;
182 while ((m3u8Match = m3u8Pattern.exec(html)) !== null) {
183 sources.push(m3u8Match[0]);
184 }
185
186 console.log('Found sources:', sources);
187
188 if (sources.length > 0) {
189 resolve([...new Set(sources)]); // Remove duplicates
190 } else {
191 reject('No video sources found');
192 }
193 },
194 onerror: function(error) {
195 console.error('Error fetching iframe:', error);
196 reject('Failed to fetch video sources');
197 }
198 });
199 });
200 }
201
202 // Create download button and container
203 function createDownloadUI() {
204 const videoSection = document.querySelector('.single-video-player');
205 if (!videoSection) {
206 console.log('Video section not found');
207 return;
208 }
209
210 // Check if UI already exists
211 if (document.querySelector('.pxnow-download-container')) {
212 console.log('Download UI already exists');
213 return;
214 }
215
216 const container = document.createElement('div');
217 container.className = 'pxnow-download-container';
218 container.innerHTML = `
219 <div class="pxnow-download-title">📥 Download Video</div>
220 <button class="pxnow-download-btn" id="pxnow-fetch-btn">
221 <span>🎬 Get Download Links</span>
222 </button>
223 <div id="pxnow-download-links"></div>
224 `;
225
226 // Insert right after the video player, before video title
227 const videoPlayer = videoSection.querySelector('.responsive-player');
228 if (videoPlayer && videoPlayer.parentNode) {
229 videoPlayer.parentNode.insertBefore(container, videoPlayer.nextSibling);
230 console.log('Download UI created after video player');
231 } else {
232 // Fallback: insert after video wrapper
233 const videoWrapper = videoSection.querySelector('.video-wrapper');
234 if (videoWrapper) {
235 videoWrapper.parentNode.insertBefore(container, videoWrapper.nextSibling);
236 console.log('Download UI created after video wrapper');
237 }
238 }
239
240 // Add click handler
241 const fetchBtn = document.getElementById('pxnow-fetch-btn');
242 if (fetchBtn) {
243 fetchBtn.addEventListener('click', handleDownloadClick);
244 }
245 }
246
247 // Handle download button click
248 async function handleDownloadClick(e) {
249 const btn = e.currentTarget;
250 const linksContainer = document.getElementById('pxnow-download-links');
251
252 // Prevent multiple clicks
253 if (btn.classList.contains('loading')) {
254 return;
255 }
256
257 btn.classList.add('loading');
258 btn.querySelector('span').textContent = 'Fetching video sources...';
259 linksContainer.innerHTML = '<div class="pxnow-status info">🔍 Searching for video sources...</div>';
260
261 try {
262 // Get iframe URL
263 const iframe = document.querySelector('.video-player iframe');
264 if (!iframe || !iframe.src) {
265 throw new Error('Video iframe not found');
266 }
267
268 const iframeUrl = iframe.src;
269 console.log('Processing iframe:', iframeUrl);
270
271 // Extract video sources
272 const sources = await extractVideoSources(iframeUrl);
273
274 if (sources.length === 0) {
275 throw new Error('No video sources found');
276 }
277
278 // Display download links
279 let linksHTML = '<div class="pxnow-status success">✅ Found video sources!</div>';
280
281 sources.forEach((source, index) => {
282 const isM3U8 = source.includes('.m3u8');
283 const quality = isM3U8 ? 'HLS Stream' : `Quality ${index + 1}`;
284 const icon = isM3U8 ? '📺' : '🎥';
285
286 linksHTML += `
287 <a href="${source}"
288 class="pxnow-quality-option"
289 download
290 target="_blank">
291 ${icon} ${quality} - Click to Download
292 </a>
293 `;
294 });
295
296 linksContainer.innerHTML = linksHTML;
297 btn.querySelector('span').textContent = '✅ Download Links Ready';
298
299 // Save to history
300 await saveDownloadHistory(window.location.href, sources);
301
302 } catch (error) {
303 console.error('Error:', error);
304 linksContainer.innerHTML = `
305 <div class="pxnow-status error">
306 ❌ ${error.message || 'Failed to fetch video sources'}
307 <br><small>Try refreshing the page or check if the video is loaded</small>
308 </div>
309 `;
310 btn.querySelector('span').textContent = '🔄 Try Again';
311 } finally {
312 btn.classList.remove('loading');
313 }
314 }
315
316 // Save download history
317 async function saveDownloadHistory(pageUrl, sources) {
318 try {
319 const history = await GM.getValue('download_history', []);
320 history.unshift({
321 url: pageUrl,
322 sources: sources,
323 timestamp: Date.now(),
324 title: document.title
325 });
326
327 // Keep only last 50 entries
328 if (history.length > 50) {
329 history.splice(50);
330 }
331
332 await GM.setValue('download_history', history);
333 console.log('Download history saved');
334 } catch (error) {
335 console.error('Error saving history:', error);
336 }
337 }
338
339 // Initialize the extension
340 function init() {
341 console.log('Initializing PornXnow Download Manager');
342
343 // Add styles
344 addStyles();
345
346 // Wait for page to load
347 if (document.readyState === 'loading') {
348 document.addEventListener('DOMContentLoaded', createDownloadUI);
349 } else {
350 createDownloadUI();
351 }
352
353 // Also try after a delay to ensure video player is loaded
354 setTimeout(createDownloadUI, 2000);
355
356 // Watch for dynamic content changes
357 const observer = new MutationObserver(debounce(() => {
358 if (document.querySelector('.single-video-player') && !document.querySelector('.pxnow-download-container')) {
359 createDownloadUI();
360 }
361 }, 1000));
362
363 observer.observe(document.body, {
364 childList: true,
365 subtree: true
366 });
367 }
368
369 // Start the extension
370 init();
371})();