Size
13.2 KB
Version
1.0.1
Created
Nov 17, 2025
Updated
30 days ago
1// ==UserScript==
2// @name M3U8 Live Stream Downloader
3// @description Detect and download M3U8 live streams from any website
4// @version 1.0.1
5// @match *://*/*
6// @icon https://robomonkey.io/icon.png?adc3438f5fbb5315
7// @grant GM.xmlhttpRequest
8// @grant GM.setValue
9// @grant GM.getValue
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 // Store detected M3U8 streams
15 let detectedStreams = new Set();
16 let streamPanel = null;
17
18 // Intercept network requests to detect M3U8 streams
19 function interceptNetworkRequests() {
20 const originalFetch = unsafeWindow.fetch;
21 const originalXHROpen = unsafeWindow.XMLHttpRequest.prototype.open;
22 const originalXHRSend = unsafeWindow.XMLHttpRequest.prototype.send;
23
24 // Intercept fetch requests
25 unsafeWindow.fetch = function(...args) {
26 const url = args[0];
27 if (typeof url === 'string' && url.includes('.m3u8')) {
28 console.log('M3U8 stream detected via fetch:', url);
29 addStream(url);
30 }
31 return originalFetch.apply(this, args);
32 };
33
34 // Intercept XMLHttpRequest
35 unsafeWindow.XMLHttpRequest.prototype.open = function(method, url, ...rest) {
36 this._url = url;
37 if (typeof url === 'string' && url.includes('.m3u8')) {
38 console.log('M3U8 stream detected via XHR:', url);
39 addStream(url);
40 }
41 return originalXHROpen.apply(this, [method, url, ...rest]);
42 };
43
44 console.log('M3U8 Live Stream Downloader: Network interception active');
45 }
46
47 // Add stream to the list
48 function addStream(url) {
49 if (!detectedStreams.has(url)) {
50 detectedStreams.add(url);
51 updateStreamPanel();
52 console.log('New M3U8 stream added:', url);
53 }
54 }
55
56 // Create the stream panel UI
57 function createStreamPanel() {
58 if (streamPanel) return;
59
60 streamPanel = document.createElement('div');
61 streamPanel.id = 'm3u8-stream-panel';
62 streamPanel.style.cssText = `
63 position: fixed;
64 top: 20px;
65 right: 20px;
66 width: 400px;
67 max-height: 500px;
68 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
69 border-radius: 12px;
70 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
71 z-index: 999999;
72 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
73 overflow: hidden;
74 display: none;
75 `;
76
77 streamPanel.innerHTML = `
78 <div style="padding: 16px; background: rgba(255,255,255,0.95); border-bottom: 2px solid #667eea;">
79 <div style="display: flex; justify-content: space-between; align-items: center;">
80 <h3 style="margin: 0; color: #333; font-size: 18px; font-weight: 600;">
81 š„ M3U8 Streams
82 </h3>
83 <button id="m3u8-close-btn" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s;">Ć</button>
84 </div>
85 </div>
86 <div id="m3u8-stream-list" style="max-height: 400px; overflow-y: auto; background: white;">
87 <div style="padding: 20px; text-align: center; color: #666;">
88 No streams detected yet. Play a video to detect M3U8 streams.
89 </div>
90 </div>
91 `;
92
93 document.body.appendChild(streamPanel);
94
95 // Close button
96 document.getElementById('m3u8-close-btn').addEventListener('click', () => {
97 streamPanel.style.display = 'none';
98 });
99
100 // Close button hover effect
101 const closeBtn = document.getElementById('m3u8-close-btn');
102 closeBtn.addEventListener('mouseenter', () => {
103 closeBtn.style.background = '#f0f0f0';
104 });
105 closeBtn.addEventListener('mouseleave', () => {
106 closeBtn.style.background = 'none';
107 });
108
109 console.log('M3U8 stream panel created');
110 }
111
112 // Update the stream panel with detected streams
113 function updateStreamPanel() {
114 if (!streamPanel) return;
115
116 const streamList = document.getElementById('m3u8-stream-list');
117 if (detectedStreams.size === 0) {
118 streamList.innerHTML = `
119 <div style="padding: 20px; text-align: center; color: #666;">
120 No streams detected yet. Play a video to detect M3U8 streams.
121 </div>
122 `;
123 return;
124 }
125
126 streamList.innerHTML = '';
127 let index = 1;
128 detectedStreams.forEach(url => {
129 const streamItem = document.createElement('div');
130 streamItem.style.cssText = `
131 padding: 16px;
132 border-bottom: 1px solid #eee;
133 transition: background 0.2s;
134 `;
135 streamItem.addEventListener('mouseenter', () => {
136 streamItem.style.background = '#f8f9fa';
137 });
138 streamItem.addEventListener('mouseleave', () => {
139 streamItem.style.background = 'white';
140 });
141
142 const shortUrl = url.length > 60 ? url.substring(0, 60) + '...' : url;
143
144 streamItem.innerHTML = `
145 <div style="margin-bottom: 8px;">
146 <strong style="color: #667eea; font-size: 14px;">Stream ${index}</strong>
147 </div>
148 <div style="font-size: 12px; color: #666; margin-bottom: 12px; word-break: break-all;">
149 ${shortUrl}
150 </div>
151 <div style="display: flex; gap: 8px;">
152 <button class="m3u8-copy-btn" data-url="${url}" style="flex: 1; padding: 8px 12px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s;">
153 š Copy URL
154 </button>
155 <button class="m3u8-download-btn" data-url="${url}" style="flex: 1; padding: 8px 12px; background: #10b981; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s;">
156 ā¬ļø Download
157 </button>
158 </div>
159 `;
160
161 streamList.appendChild(streamItem);
162 index++;
163 });
164
165 // Add event listeners for copy buttons
166 document.querySelectorAll('.m3u8-copy-btn').forEach(btn => {
167 btn.addEventListener('click', async (e) => {
168 const url = e.target.getAttribute('data-url');
169 await GM.setClipboard(url);
170 const originalText = e.target.innerHTML;
171 e.target.innerHTML = 'ā Copied!';
172 e.target.style.background = '#10b981';
173 setTimeout(() => {
174 e.target.innerHTML = originalText;
175 e.target.style.background = '#667eea';
176 }, 2000);
177 });
178
179 btn.addEventListener('mouseenter', (e) => {
180 e.target.style.background = '#5568d3';
181 e.target.style.transform = 'translateY(-1px)';
182 });
183 btn.addEventListener('mouseleave', (e) => {
184 if (!e.target.innerHTML.includes('Copied')) {
185 e.target.style.background = '#667eea';
186 }
187 e.target.style.transform = 'translateY(0)';
188 });
189 });
190
191 // Add event listeners for download buttons
192 document.querySelectorAll('.m3u8-download-btn').forEach(btn => {
193 btn.addEventListener('click', async (e) => {
194 const url = e.target.getAttribute('data-url');
195 await downloadM3U8(url, e.target);
196 });
197
198 btn.addEventListener('mouseenter', (e) => {
199 e.target.style.background = '#059669';
200 e.target.style.transform = 'translateY(-1px)';
201 });
202 btn.addEventListener('mouseleave', (e) => {
203 if (!e.target.innerHTML.includes('Downloaded')) {
204 e.target.style.background = '#10b981';
205 }
206 e.target.style.transform = 'translateY(0)';
207 });
208 });
209
210 console.log('Stream panel updated with', detectedStreams.size, 'streams');
211 }
212
213 // Download M3U8 file
214 async function downloadM3U8(url, button) {
215 const originalText = button.innerHTML;
216 button.innerHTML = 'ā³ Downloading...';
217 button.disabled = true;
218
219 try {
220 const response = await GM.xmlhttpRequest({
221 method: 'GET',
222 url: url,
223 responseType: 'text'
224 });
225
226 const blob = new Blob([response.responseText], { type: 'application/vnd.apple.mpegurl' });
227 const downloadUrl = URL.createObjectURL(blob);
228 const a = document.createElement('a');
229 a.href = downloadUrl;
230 a.download = 'stream_' + Date.now() + '.m3u8';
231 document.body.appendChild(a);
232 a.click();
233 document.body.removeChild(a);
234 URL.revokeObjectURL(downloadUrl);
235
236 button.innerHTML = 'ā Downloaded!';
237 button.style.background = '#059669';
238 setTimeout(() => {
239 button.innerHTML = originalText;
240 button.style.background = '#10b981';
241 button.disabled = false;
242 }, 3000);
243
244 console.log('M3U8 file downloaded successfully');
245 } catch (error) {
246 console.error('Error downloading M3U8:', error);
247 button.innerHTML = 'ā Error';
248 button.style.background = '#ef4444';
249 setTimeout(() => {
250 button.innerHTML = originalText;
251 button.style.background = '#10b981';
252 button.disabled = false;
253 }, 3000);
254 }
255 }
256
257 // Create toggle button
258 function createToggleButton() {
259 const toggleBtn = document.createElement('button');
260 toggleBtn.id = 'm3u8-toggle-btn';
261 toggleBtn.innerHTML = 'š„';
262 toggleBtn.title = 'Toggle M3U8 Stream Panel';
263 toggleBtn.style.cssText = `
264 position: fixed;
265 bottom: 20px;
266 right: 20px;
267 width: 56px;
268 height: 56px;
269 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
270 color: white;
271 border: none;
272 border-radius: 50%;
273 font-size: 24px;
274 cursor: pointer;
275 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
276 z-index: 999998;
277 transition: all 0.3s;
278 display: flex;
279 align-items: center;
280 justify-content: center;
281 `;
282
283 toggleBtn.addEventListener('click', () => {
284 if (streamPanel.style.display === 'none') {
285 streamPanel.style.display = 'block';
286 } else {
287 streamPanel.style.display = 'none';
288 }
289 });
290
291 toggleBtn.addEventListener('mouseenter', () => {
292 toggleBtn.style.transform = 'scale(1.1)';
293 toggleBtn.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)';
294 });
295
296 toggleBtn.addEventListener('mouseleave', () => {
297 toggleBtn.style.transform = 'scale(1)';
298 toggleBtn.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
299 });
300
301 document.body.appendChild(toggleBtn);
302 console.log('M3U8 toggle button created');
303 }
304
305 // Monitor DOM for video elements and media sources
306 function monitorMediaElements() {
307 const observer = new MutationObserver(() => {
308 // Check video elements for src attributes
309 document.querySelectorAll('video[src*=".m3u8"], source[src*=".m3u8"]').forEach(el => {
310 const src = el.getAttribute('src');
311 if (src && src.includes('.m3u8')) {
312 console.log('M3U8 stream detected in video element:', src);
313 addStream(src);
314 }
315 });
316 });
317
318 observer.observe(document.body, {
319 childList: true,
320 subtree: true,
321 attributes: true,
322 attributeFilter: ['src']
323 });
324
325 console.log('DOM monitoring active for M3U8 streams');
326 }
327
328 // Initialize the extension
329 function init() {
330 console.log('M3U8 Live Stream Downloader initialized');
331
332 // Wait for body to be ready
333 if (document.body) {
334 createStreamPanel();
335 createToggleButton();
336 interceptNetworkRequests();
337 monitorMediaElements();
338 } else {
339 const observer = new MutationObserver(() => {
340 if (document.body) {
341 observer.disconnect();
342 createStreamPanel();
343 createToggleButton();
344 interceptNetworkRequests();
345 monitorMediaElements();
346 }
347 });
348 observer.observe(document.documentElement, { childList: true });
349 }
350 }
351
352 // Start the extension
353 init();
354})();