/** * index.js - Popup logic for the Goon Plugin. * Runs inside the popup; scans the current window's tabs for videos * and stores the results for the viewer to consume. */ const STORAGE_KEY = 'goon_videos'; const TAGGED_KEY = 'goon_tagged'; // Set to true to enable verbose console output in the popup. const DEBUG = true; // TEMP: enabled for debugging — revert to false when done function log(...args) { if (DEBUG) console.log('[goon]', ...args); } function warn(...args) { if (DEBUG) console.warn('[goon]', ...args); } /** Log a message to the active tab's page console (visible to the user in DevTools). */ async function logToPage(msg) { if (!DEBUG) return; try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab) return; await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: (m) => { console.log('[goon-page]', m); }, args: [msg], }); } catch { /* ignore — tab may have navigated away */ } } // UI references const findBtn = document.getElementById('find-btn'); const clearBtn = document.getElementById('clear-btn'); const openBtn = document.getElementById('open-btn'); const statusEl = document.getElementById('status'); const listEl = document.getElementById('video-list'); const btnRow = document.getElementById('btn-row'); listEl.style.wordBreak = 'break-word'; listEl.style.whiteSpace = 'pre-wrap'; /* ======================================================================== */ /* UI helpers */ /* ======================================================================== */ function setStatus(msg) { statusEl.textContent = msg; log(msg); } /** Load the current video list and tags from storage. */ function loadVideos() { return chrome.storage.local.get([STORAGE_KEY, TAGGED_KEY]).then(data => ({ videos: data[STORAGE_KEY] || [], tags: data[TAGGED_KEY] || [], })); } /** Render the video list UI from stored video entries. */ function renderList(videos) { listEl.innerHTML = ''; if (!videos || videos.length === 0) { listEl.style.display = 'none'; return; } listEl.style.display = 'block'; for (const video of videos) { const link = document.createElement('a'); link.className = 'video-item'; link.href = video.src || video.url || ''; link.textContent = video.label ? `${video.label}: ${link.href}` : link.href; link.target = '_blank'; listEl.appendChild(link); } // Copy all URLs button const copyBtn = document.createElement('button'); copyBtn.className = 'copy-btn'; copyBtn.textContent = 'Copy All URLs'; copyBtn.addEventListener('click', () => { const urls = videos.map(v => v.src || v.url); navigator.clipboard.writeText(urls.join('\n')).then(() => { copyBtn.textContent = 'Copied!'; setTimeout(() => { copyBtn.textContent = 'Copy All URLs'; }, 1500); }); }); listEl.appendChild(copyBtn); } /** Show the main button row and render saved videos. */ function showActions() { btnRow.style.display = 'flex'; loadVideos().then(({ videos, tags }) => { if (videos.length > 0) { const items = videos.map((video, i) => ({ src: video.src || video.url || '', label: tags[i], })); renderList(items); setStatus(`Using ${videos.length} video(s) from last search.`); } else { setStatus(''); } }); } /* ======================================================================== */ /* HLS deep probe - runs in target page context via executeScript */ /* ======================================================================== */ /** * Deep probe a page for HLS manifest URLs. * Walks known player objects, video elements, inline scripts, config globals, * and interceptor-collected data. * Returns an array of { url, player } objects. */ function m3u8DeepProbeFn() { const manifests = []; const seen = new Set(); // DEBUG: Log probe activity to console function probeLog(...args) { try { console.log('[goon-probe]', ...args); } catch(e) {} } probeLog('Deep probe started on:', window.location.href, 'World:', typeof chrome !== 'undefined' ? 'ISOLATED' : 'MAIN'); // Helpers available in the MAIN world context function resolveUrl(url) { if (!url) return null; try { if (url.startsWith('//')) return 'https:' + url; if (url.startsWith('http')) return url; return new URL(url, window.location.origin).href; } catch { return null; } } function isManifestUrl(url) { if (!url) return false; const u = url.toLowerCase(); return u.includes('.m3u8') || u.includes('.hlsvod') || (u.includes('/hls/') && u.includes('http')) || (u.includes('/playlist') && u.includes('http')) || (u.includes('/manifest') && u.includes('http')); } function walk(obj, path, depth) { if (depth > 5) return; if (typeof obj === 'string') { const match = obj.match(/https?:\/\/[^"'`\s)]+?(?:\.m3u8|\.hlsvod|\/hls\/|\/playlist[^"'`\s]*|\/manifest[^"'`\s]*)[^"'`\s)]*/gi); if (match) { for (const u of match) { const resolved = resolveUrl(u); if (resolved && isManifestUrl(resolved) && !seen.has(resolved)) { seen.add(resolved); manifests.push({ url: resolved, player: path }); } } } return; } if (!obj || typeof obj !== 'object') return; try { for (const k of Object.keys(obj)) { walk(obj[k], `${path}.${k}`, depth + 1); } } catch { /* avoid circular ref errors */ } } // Standard