// ==UserScript== // @name 😈 Media Stream Hunter (Silent & Master File Focus) // @namespace http://masterfile.example.com/ // @version 1.0 // @description Aggressively targets Master Manifests (.m3u8/.mpd) and Full Media Files. Silently ignores fragmented media segments. Polished UX. // @author Balta zar // @match *://*/* // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/553347/%F0%9F%98%88%20Media%20Stream%20Hunter%20%28Silent%20%20Master%20File%20Focus%29.user.js // @updateURL https://update.greasyfork.icu/scripts/553347/%F0%9F%98%88%20Media%20Stream%20Hunter%20%28Silent%20%20Master%20File%20Focus%29.meta.js // ==/UserScript== (function () { 'use strict'; /************************************************************************** * CONFIG & HEURISTICS **************************************************************************/ const UI_ZINDEX = 9999999; const NOTIFY_MS = 2500; // Patterns of fragmented stream segments (to be IGNORED) const STREAM_SEGMENT_PATTERNS = /(\.ts$|\.m4s$|\.vtt$|segment|chunk|frag|range=)/i; const MIN_SEGMENT_SIZE_BYTES = 1024 * 100; // 100 KB (Fallback size filter, though aggressive filtering is primary) const MANIFEST_EXT = /\.(m3u8|mpd)(\?|$)/i; const MEDIA_EXT = /\.(mp4|mp3|webm|ogg|wav|m4a|flac|jpg|png|jpeg|gif|webp|mov|avi|mkv)(\?|$)/i; const PRIMARY_COLOR = '#007bff'; const ACCENT_COLOR = '#28a745'; /************************************************************************** * STATE & STORAGE **************************************************************************/ const storage = { get: (k, def) => { try { const v = GM_getValue(k); return v === undefined ? def : v; } catch (e) { return def; } }, set: (k, v) => { try { GM_setValue(k, v); } catch (e) {} } }; const state = { captures: new Map(), order: [], uiOpen: storage.get('uiOpen', false), activeCategory: 'All' }; const CATEGORIES = ['All', 'Stream', 'Video', 'Audio', 'Image', 'Other']; const CATEGORY_ICONS = { 'All': '📦', 'Stream': '📺', 'Video': '🎥', 'Audio': '🎧', 'Image': '🖼️', 'Other': '❓' }; /************************************************************************** * UTILS **************************************************************************/ function humanSize(bytes) { if (!bytes || isNaN(bytes)) return '—'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return bytes.toFixed(1) + ' ' + units[i]; } function nowISO() { return new Date().toISOString().substring(11, 19); } function short(url, n = 80) { if (!url) return ''; return url.length <= n ? url : url.slice(0, 55) + '...' + url.slice(-20); } function safeId(url) { try { return 'mh-' + btoa(unescape(encodeURIComponent(url))).replace(/=+$/g, ''); } catch (e) { return 'mh-' + Math.random().toString(36).slice(2, 9); } } function copyTextToClipboard(text) { try { navigator.clipboard.writeText(text); } catch (e) { /* fallback */ } } function categorize(url) { if (!url) return 'Other'; if (MANIFEST_EXT.test(url)) return 'Stream'; if (/\.(mp3|m4a|aac|wav|flac|ogg)(\?|$)/i.test(url)) return 'Audio'; if (/\.(mp4|mkv|webm|avi|mov)(\?|$)/i.test(url)) return 'Video'; if (/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/i.test(url)) return 'Image'; if (STREAM_SEGMENT_PATTERNS.test(url)) return 'Stream'; return 'Other'; } /************************************************************************** * UI BUILD (Polished UX) - Modified **************************************************************************/ let overlay, listEl, toggleBtn, toastEl, tabsEl, statsEl; function createStyledButton(text, onClick, isPrimary = false, styleOverride = {}) { const btn = document.createElement('button'); btn.textContent = text; Object.assign(btn.style, { padding: '6px 12px', borderRadius: '4px', cursor: 'pointer', border: isPrimary ? 'none' : '1px solid #444', background: isPrimary ? PRIMARY_COLOR : '#333', color: '#fff', fontSize: '13px', transition: 'background 0.2s', ...styleOverride }); btn.addEventListener('click', onClick); return btn; } function buildUIOnce() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', buildUIOnce); return; } if (document.getElementById('mh-overlay')) return; // ... (UI setup elements - mostly unchanged Polished UX structure) ... overlay = document.createElement('div'); overlay.id = 'mh-overlay'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0, 0, 0, 0.9)', backdropFilter: 'blur(4px)', zIndex: UI_ZINDEX, color: '#fff', fontFamily: 'system-ui, sans-serif', padding: '20px', display: state.uiOpen ? 'block' : 'none', overflowY: 'auto', boxSizing: 'border-box' }); const header = document.createElement('div'); Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }); header.innerHTML = `

Stream Hunter (Master Focus)

`; const closeBtn = createStyledButton('Close (X)', () => toggleOverlay(false), false, { background: '#dc3545' }); header.appendChild(closeBtn); overlay.appendChild(header); const actionsBar = document.createElement('div'); Object.assign(actionsBar.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '10px', marginBottom: '15px' }); statsEl = document.createElement('span'); Object.assign(statsEl.style, { fontSize: '14px', opacity: 0.8 }); actionsBar.appendChild(statsEl); const utilityBtns = document.createElement('div'); Object.assign(utilityBtns.style, { display: 'flex', gap: '8px' }); const clearBtn = createStyledButton('Clear All', clearAll, false); const copyAllBtn = createStyledButton('Copy All URLs', copyAll, false); utilityBtns.appendChild(clearBtn); utilityBtns.appendChild(copyAllBtn); actionsBar.appendChild(utilityBtns); overlay.appendChild(actionsBar); tabsEl = document.createElement('div'); tabsEl.id = 'mh-tabs'; Object.assign(tabsEl.style, { display: 'flex', gap: '6px', flexWrap: 'wrap', marginBottom: '15px' }); CATEGORIES.forEach(cat => { const b = createStyledButton(`${CATEGORY_ICONS[cat]} ${cat}`, () => { state.activeCategory = cat; updateActiveTab(); renderList(); }, false, { padding: '8px 10px', background: '#444', border: '1px solid #555' }); b.dataset.cat = cat; tabsEl.appendChild(b); }); overlay.appendChild(tabsEl); listEl = document.createElement('ul'); listEl.id = 'mh-list'; Object.assign(listEl.style, { listStyle: 'none', padding: '0', margin: '0' }); overlay.appendChild(listEl); toastEl = document.createElement('div'); toastEl.id = 'mh-toast-area'; Object.assign(toastEl.style, { position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)', pointerEvents: 'none', width: 'auto', maxWidth: '90%' }); document.body.appendChild(toastEl); document.body.appendChild(overlay); toggleBtn = createStyledButton('📂 Media', () => toggleOverlay(!state.uiOpen), true, { position: 'fixed', bottom: '20px', right: '20px', zIndex: UI_ZINDEX + 1, padding: '12px 18px', borderRadius: '50px', background: PRIMARY_COLOR, color: '#fff', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)' }); document.body.appendChild(toggleBtn); updateActiveTab(); refreshStats(); renderList(); } // --- UI HELPERS --- // The core notify function is kept for download/copy feedback, but not for capture events. function notify(msg, ms = NOTIFY_MS) { if (!toastEl) return; const d = document.createElement('div'); Object.assign(d.style, { background: 'rgba(40, 44, 52, 0.95)', padding: '10px 15px', borderRadius: '6px', margin: '6px 0', color: '#fff', fontSize: '14px', boxShadow: '0 3px 10px rgba(0,0,0,0.2)', transition: 'opacity 0.5s' }); d.textContent = msg; toastEl.appendChild(d); setTimeout(() => d.style.opacity = '0', ms); setTimeout(() => d.remove(), ms + 500); } function updateActiveTab() { if (!tabsEl) return; tabsEl.querySelectorAll('button').forEach(btn => { if (btn.dataset.cat === state.activeCategory) { Object.assign(btn.style, { background: PRIMARY_COLOR, border: `1px solid ${PRIMARY_COLOR}` }); } else { Object.assign(btn.style, { background: '#444', border: '1px solid #555' }); } }); } function refreshStats() { if (!statsEl) return; statsEl.textContent = `Total Captured: ${state.captures.size}`; } function clearAll() { state.captures.clear(); state.order = []; if (listEl) listEl.innerHTML = ''; refreshStats(); notify('Cleared all captures'); } function copyAll() { const text = Array.from(state.captures.values()).map(c => `${c.url} (Source: ${c.tag} | Size: ${c.size ? humanSize(c.size) : '—'})`).join('\n'); copyTextToClipboard(text); notify('All captured URLs copied to clipboard!'); } function toggleOverlay(open) { state.uiOpen = !!open; storage.set('uiOpen', state.uiOpen); if (overlay) overlay.style.display = state.uiOpen ? 'block' : 'none'; if (state.uiOpen) renderList(); } function renderList() { if (!listEl) { buildUIOnce(); return; } listEl.innerHTML = ''; const items = Array.from(state.captures.values()).sort((a, b) => new Date(b.time) - new Date(a.time)); if(items.length === 0){ listEl.innerHTML = `
  • No primary media or manifest URLs detected yet. Click the icon later.
  • `; return; } for (const cap of items) { const cat = categorize(cap.url); if (state.activeCategory !== 'All' && state.activeCategory !== cat) continue; const li = document.createElement('li'); li.id = safeId(cap.url); Object.assign(li.style, { padding: '12px', margin: '8px 0', borderRadius: '6px', backgroundColor: '#343a40', borderLeft: `4px solid ${cat === 'Stream' ? '#ffc107' : PRIMARY_COLOR}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '15px' }); const details = document.createElement('div'); details.style.minWidth = '0'; details.innerHTML = `
    ${short(cap.url, 120)}
    ${cap.tag} ${CATEGORY_ICONS[cat]} ${cat} ${cap.time} ${cap.size === null ? '—' : humanSize(cap.size)}
    `; const actions = document.createElement('div'); Object.assign(actions.style, { display: 'flex', gap: '8px', flexShrink: 0 }); const copyBtn = createStyledButton('Copy', (e) => { e.stopPropagation(); copyTextToClipboard(cap.url); notify('Copied URL'); }, false, { background: '#555' }); const dlBtn = createStyledButton('Download', async (e) => { e.stopPropagation(); try { await GM_download({ url: cap.url, name: filenameFromUrl(cap.url) }); notify('Download started', 4000); } catch (err) { notify('Download failed: ' + (err && err.message ? err.message : 'error'), 4000); } }, true, { background: ACCENT_COLOR }); actions.appendChild(copyBtn); actions.appendChild(dlBtn); li.appendChild(details); li.appendChild(actions); listEl.appendChild(li); } } /************************************************************************** * CORE CAPTURE PIPELINE (Aggressive Filtering) **************************************************************************/ function registerCapture(url, tag, size = null) { if (!url) return; try { url = String(url).trim(); } catch (e) { return; } const category = categorize(url); // **NEW AGGRESSIVE FILTERING LOGIC:** // Check if the URL is NOT a main media file and NOT a manifest file, but IS a stream segment pattern. const isMainMediaOrManifest = MANIFEST_EXT.test(url) || MEDIA_EXT.test(url); const isSegmentPattern = STREAM_SEGMENT_PATTERNS.test(url); if (isSegmentPattern && !isMainMediaOrManifest) { // IGNORE segments that don't look like main files (i.e., .m3u8 or .mp4) return; } // Handle existing capture (Update size/tag if needed) if (state.captures.has(url)) { const existing = state.captures.get(url); existing.time = nowISO(); if (size !== null && (existing.size === null || size > existing.size)) { existing.size = size; updateSizeInUI(url, size); } if (!existing.tag.includes(tag)) existing.tag += ', ' + tag; const idx = state.order.indexOf(url); if (idx !== -1) state.order.splice(idx, 1); state.order.unshift(url); return; } // New capture const capture = { url, tag, size, time: nowISO() }; state.captures.set(url, capture); state.order.unshift(url); buildUIOnce(); refreshStats(); renderList(); // Try to get file size via HEAD request (only if size is unknown) if (size === null && !(/^blob:/i.test(url) || MANIFEST_EXT.test(url))) { try { GM_xmlhttpRequest({ method: 'HEAD', url: url, headers: { 'Accept': '*/*' }, onload: function (res) { const headers = (res.responseHeaders || ''); const m = headers.match(/content-length:\s*(\d+)/i); if (m) { const newSize = parseInt(m[1], 10); // Post-HEAD check to confirm large size for primary files if (newSize < MIN_SEGMENT_SIZE_BYTES && !isMainMediaOrManifest) { state.captures.delete(url); state.order = state.order.filter(u => u !== url); renderList(); return; } capture.size = newSize; updateSizeInUI(url, newSize); } }, onerror: function () { /* ignore */ } }); } catch (e) { /* ignore */ } } // Note: NO notify() call here. } function updateSizeInUI(url, size) { const cap = state.captures.get(url); if (cap) cap.size = size; const id = safeId(url); const li = document.getElementById(id); if (li) { const span = li.querySelector('.mh-size'); if (span) span.textContent = humanSize(size); } } /************************************************************************** * PAGE-CONTEXT INJECTION (API Hooking) - Unchanged **************************************************************************/ function injectPageHook() { // (Injection code remains the same to hook fetch, XHR, and MSE for data capture) try { const injectedCode = `(() => { try { const post = (type, data) => { window.postMessage({mh_injected: true, type, data}, '*'); }; (function(){ const orig = window.fetch; if(!orig) return; window.fetch = function(...args){ const url = args && args[0]; post('fetch-request', {url: url && url.url ? url.url : url, time: Date.now()}); return orig.apply(this, args).then(async resp => { try{ const contentType = resp.headers.get('Content-Type'); const contentLength = resp.headers.get('Content-Length'); const size = contentLength ? parseInt(contentLength, 10) : null; post('fetch-response', {url: resp.url, type: resp.type, contentType, size}); }catch(e){}; return resp; }); }; })(); (function(){ const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url){ this.__mh_url = url; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(...args){ try{ this.addEventListener('load', function(){ try{ const size = this.getResponseHeader('Content-Length') ? parseInt(this.getResponseHeader('Content-Length'), 10) : null; post('xhr-load', {url: this.responseURL || this.__mh_url, status: this.status, size, contentType: this.getResponseHeader('Content-Type')}); }catch(e){} }); }catch(e){} return origSend.apply(this, args); }; })(); (function(){ if(!URL || !URL.createObjectURL) return; const origCreate = URL.createObjectURL.bind(URL); URL.createObjectURL = function(obj){ const url = origCreate(obj); try{ if(obj && obj.type && /(video|audio|image)/.test(obj.type)){ post('create-object-url', {url, type: obj.type, size: obj.size}); } }catch(e){} return url; }; })(); (function(){ if(!window.MediaSource) return; const origAdd = MediaSource.prototype.addSourceBuffer; MediaSource.prototype.addSourceBuffer = function(mime){ const sb = origAdd.apply(this, arguments); if(sb && sb.appendBuffer){ const origAppend = sb.appendBuffer.bind(sb); sb.appendBuffer = function(buf){ try{ post('mse-append', {len: buf && buf.byteLength, mime}); }catch(e){} return origAppend(buf); }; } return sb; }; })(); } catch (e) {} })();`; const s = document.createElement('script'); s.textContent = injectedCode; (document.head || document.documentElement || document.body || document).appendChild(s); s.remove(); } catch (e) {} } /************************************************************************** * MESSAGE BRIDGE & INITIAL SCAN - Modified **************************************************************************/ window.addEventListener('message', function (evt) { try { const m = evt.data; if (!m || !m.mh_injected) return; const t = m.type; const d = m.data || {}; const size = d.size !== null && !isNaN(d.size) ? parseInt(d.size, 10) : null; if (t === 'fetch-request') { registerCapture(d.url, 'F-REQ'); } else if (t === 'fetch-response') { const tag = d.contentType && (d.contentType.includes('video/') || d.contentType.includes('audio/') || MANIFEST_EXT.test(d.url)) ? 'F-RESP-MEDIA' : 'F-RESP'; registerCapture(d.url, tag, size); } else if (t === 'xhr-load') { const tag = d.contentType && (d.contentType.includes('video/') || d.contentType.includes('audio/') || MANIFEST_EXT.test(d.url)) ? 'XHR-LOAD-MEDIA' : 'XHR-LOAD'; registerCapture(d.url, tag, size); } else if (t === 'create-object-url') { registerCapture(d.url, 'BLOB-URL', size); } else if (t === 'mse-append') { // Only use MSE detection to give a hint, but not a full notification } } catch (e) { /* ignore */ } }); function filenameFromUrl(u) { try { const p = u.split('?')[0].split('/').pop(); return p || 'media'; } catch (e) { return 'media'; } } function initialScan() { try { document.querySelectorAll && document.querySelectorAll('video,audio,source,img,a[href]').forEach(node => { let url = null; let tag = null; if (node.tagName === 'A' && node.href && (MEDIA_EXT.test(node.href) || MANIFEST_EXT.test(node.href))) { url = node.href; tag = 'ANCHOR'; } else if (node.tagName === 'SOURCE' && node.src) { url = node.src; tag = 'SOURCE-TAG'; } else if ((node.tagName === 'AUDIO' || node.tagName === 'VIDEO') && (node.currentSrc || node.src)) { url = node.currentSrc || node.src; tag = 'INITIAL-MEDIA'; } else if (node.tagName === 'IMG' && node.src) { url = node.src; tag = 'IMG-TAG'; } if (url) registerCapture(url, tag); }); } catch (e) { /* ignore */ } } /************************************************************************** * BOOTSTRAP **************************************************************************/ try { buildUIOnce(); } catch (e) { } try { injectPageHook(); } catch (e) { } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initialScan); else initialScan(); })();