Size
9.7 KB
Version
1.0.1
Created
Jan 10, 2026
Updated
11 days ago
1// ==UserScript==
2// @name PornXnow Video Downloader
3// @description Download videos from pornxnow.me with one click
4// @version 1.0.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// @connect pxnw.xyz
9// @connect *.pxnw.xyz
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('PornXnow Video Downloader initialized');
15
16 // Debounce function to prevent multiple rapid 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 // Function to extract video URL from iframe
30 async function extractVideoUrl(iframeSrc) {
31 try {
32 console.log('Fetching iframe content from:', iframeSrc);
33
34 return new Promise((resolve, reject) => {
35 GM.xmlhttpRequest({
36 method: 'GET',
37 url: iframeSrc,
38 headers: {
39 'Referer': window.location.href,
40 'User-Agent': navigator.userAgent
41 },
42 onload: function(response) {
43 TM_log('Response status:', response.status);
44 TM_log('Response received, length:', response.responseText ? response.responseText.length : 0);
45
46 const html = response.responseText;
47 TM_log('HTML content preview (first 1000 chars):', html ? html.substring(0, 1000) : 'NO HTML');
48
49 // Try to find video URL in various formats
50 let videoUrl = null;
51
52 // Method 1: Look for direct video sources
53 const videoSourceMatch = html.match(/sources:\s*\[\s*{[^}]*file:\s*["']([^"']+\.mp4[^"']*)["']/i);
54 if (videoSourceMatch) {
55 videoUrl = videoSourceMatch[1];
56 }
57
58 // Method 2: Look for file: property
59 if (!videoUrl) {
60 const fileMatch = html.match(/file:\s*["']([^"']+\.mp4[^"']*)["']/i);
61 if (fileMatch) {
62 videoUrl = fileMatch[1];
63 }
64 }
65
66 // Method 3: Look for video tag src
67 if (!videoUrl) {
68 const videoTagMatch = html.match(/<video[^>]+src=["']([^"']+)["']/i);
69 if (videoTagMatch) {
70 videoUrl = videoTagMatch[1];
71 }
72 }
73
74 // Method 4: Look for any .mp4 URL
75 if (!videoUrl) {
76 const mp4Match = html.match(/(https?:\/\/[^"'\s]+\.mp4[^"'\s]*)/i);
77 if (mp4Match) {
78 videoUrl = mp4Match[1];
79 }
80 }
81
82 if (videoUrl) {
83 // Clean up the URL
84 videoUrl = videoUrl.replace(/\\/g, '');
85 console.log('Video URL found:', videoUrl);
86 resolve(videoUrl);
87 } else {
88 console.error('Could not find video URL in iframe content');
89 console.log('HTML preview:', html.substring(0, 500));
90 reject(new Error('Could not find video URL in iframe content'));
91 }
92 },
93 onerror: function(error) {
94 console.error('GM.xmlhttpRequest error:', error);
95 reject(new Error('Failed to fetch iframe content: ' + (error.error || 'Network error')));
96 },
97 ontimeout: function() {
98 console.error('Request timed out');
99 reject(new Error('Request timed out'));
100 }
101 });
102 });
103 } catch (error) {
104 console.error('Error extracting video URL:', error);
105 throw error;
106 }
107 }
108
109 // Function to create download button
110 function createDownloadButton() {
111 console.log('Creating download button');
112
113 // Find the video player container
114 const videoWrapper = document.querySelector('.video-wrapper');
115 if (!videoWrapper) {
116 console.error('Video wrapper not found');
117 return;
118 }
119
120 // Check if button already exists
121 if (document.getElementById('custom-video-downloader')) {
122 console.log('Download button already exists');
123 return;
124 }
125
126 // Create download button container
127 const downloadContainer = document.createElement('div');
128 downloadContainer.id = 'custom-video-downloader';
129 downloadContainer.style.cssText = `
130 margin: 15px 0;
131 padding: 15px;
132 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
133 border-radius: 8px;
134 text-align: center;
135 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
136 `;
137
138 // Create download button
139 const downloadButton = document.createElement('button');
140 downloadButton.id = 'download-video-btn';
141 downloadButton.innerHTML = '<i class="fa fa-download"></i> Download Video (HD)';
142 downloadButton.style.cssText = `
143 background: #fff;
144 color: #667eea;
145 border: none;
146 padding: 12px 30px;
147 font-size: 16px;
148 font-weight: bold;
149 border-radius: 5px;
150 cursor: pointer;
151 transition: all 0.3s ease;
152 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
153 `;
154
155 downloadButton.onmouseover = function() {
156 this.style.transform = 'scale(1.05)';
157 this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';
158 };
159
160 downloadButton.onmouseout = function() {
161 this.style.transform = 'scale(1)';
162 this.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
163 };
164
165 downloadButton.onclick = async function() {
166 const originalText = this.innerHTML;
167 this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Extracting video...';
168 this.disabled = true;
169
170 try {
171 // Get iframe source
172 const iframe = document.querySelector('.responsive-player iframe');
173 if (!iframe || !iframe.src) {
174 throw new Error('Video iframe not found');
175 }
176
177 const videoUrl = await extractVideoUrl(iframe.src);
178
179 if (videoUrl) {
180 this.innerHTML = '<i class="fa fa-check"></i> Opening download...';
181
182 // Open video in new tab for download
183 GM.openInTab(videoUrl, false);
184
185 setTimeout(() => {
186 this.innerHTML = originalText;
187 this.disabled = false;
188 }, 2000);
189 } else {
190 throw new Error('Could not extract video URL');
191 }
192 } catch (error) {
193 console.error('Download error:', error);
194 console.error('Error message:', error.message);
195 console.error('Error stack:', error.stack);
196 this.innerHTML = '<i class="fa fa-exclamation-triangle"></i> Error - Try again';
197 this.style.background = '#ff6b6b';
198 this.style.color = '#fff';
199
200 setTimeout(() => {
201 this.innerHTML = originalText;
202 this.style.background = '#fff';
203 this.style.color = '#667eea';
204 this.disabled = false;
205 }, 3000);
206 }
207 };
208
209 downloadContainer.appendChild(downloadButton);
210
211 // Insert after video player
212 const videoPlayer = videoWrapper.querySelector('.responsive-player');
213 if (videoPlayer && videoPlayer.nextSibling) {
214 videoWrapper.insertBefore(downloadContainer, videoPlayer.nextSibling);
215 } else {
216 videoWrapper.appendChild(downloadContainer);
217 }
218
219 console.log('Download button created successfully');
220 }
221
222 // Initialize the extension
223 function init() {
224 console.log('Initializing video downloader');
225
226 // Wait for page to load
227 if (document.readyState === 'loading') {
228 document.addEventListener('DOMContentLoaded', init);
229 return;
230 }
231
232 // Check if we're on a video page
233 const videoWrapper = document.querySelector('.video-wrapper');
234 if (!videoWrapper) {
235 console.log('Not a video page, extension will not run');
236 return;
237 }
238
239 // Wait a bit for iframe to load
240 setTimeout(() => {
241 createDownloadButton();
242 }, 2000);
243
244 // Observe DOM changes in case content loads dynamically
245 const observer = new MutationObserver(debounce(() => {
246 if (!document.getElementById('custom-video-downloader')) {
247 createDownloadButton();
248 }
249 }, 1000));
250
251 observer.observe(document.body, {
252 childList: true,
253 subtree: true
254 });
255 }
256
257 // Start the extension
258 init();
259})();