M3U8 Live Stream Downloader

Detect and download M3U8 live streams from any website

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})();
M3U8 Live Stream Downloader | Robomonkey