fix: refactor HLS detection pipeline to restore dropped strategies
- Add Strategy E to m3u8DeepProbeFn: read __goon_plugin_data.manifests from main-world interceptors (was accidentally dropped during refactoring) - Re-add content script query in scanVideos: restores poster URL extraction (og:image, twitter:image) and secondary manifest source from content.js - Extend interceptor Hls.js polling from 3s to 30s + add MutationObserver fallback for dynamically-loaded players - Restore allFrames:true in scrapeTab: catches iframe-hosted video players (e.g. Pornhub embeds player in nested iframes) - Prefer interceptor-collected manifests over inline-script scraps in both dedupManifests sort order and scrapeFn Priority 5 fallback
This commit is contained in:
1 parent
88585a29bf
commit
fe1949acf3
3 files changed
+522
-39
No files matched your search
+105
-20
@@ -96,7 +96,8 @@ function showActions() {
|
||||
|
||||
/**
|
||||
* Deep probe a page for HLS manifest URLs.
|
||||
* Walks known player objects, video elements, inline scripts, and config globals.
|
||||
* Walks known player objects, video elements, inline scripts, config globals,
|
||||
* and interceptor-collected data.
|
||||
* Returns an array of { url, player } objects.
|
||||
*/
|
||||
function m3u8DeepProbeFn() {
|
||||
@@ -193,6 +194,20 @@ function m3u8DeepProbeFn() {
|
||||
if (obj && typeof obj === 'object') walk(obj, name, 0);
|
||||
}
|
||||
|
||||
// Strategy E: Interceptor-collected manifests — the most reliable source.
|
||||
// The main-world interceptors (injected at document_start) capture .m3u8 URLs
|
||||
// from XHR, fetch, and Hls.prototype.loadSource/attachMedia calls, and store
|
||||
// them in window.__goon_plugin_data.manifests[]. This was the original
|
||||
// Strategy 3/6 on master that was accidentally dropped during refactoring.
|
||||
if (window.__goon_plugin_data && Array.isArray(window.__goon_plugin_data.manifests)) {
|
||||
for (const url of window.__goon_plugin_data.manifests) {
|
||||
if (url && typeof url === 'string' && !seen.has(url)) {
|
||||
seen.add(url);
|
||||
manifests.push({ url, player: '__goon_plugin_data' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
@@ -201,7 +216,7 @@ function m3u8DeepProbeFn() {
|
||||
/* ======================================================================== */
|
||||
|
||||
/**
|
||||
* Scrape visible video elements from the main frame.
|
||||
* Scrape visible video elements from the current frame.
|
||||
* Returns at most one video entry (the largest visible one).
|
||||
* @param {Array} hlsManifests - manifests found by the deep probe
|
||||
* @returns {{ videos: Array, videoCount: number, hlsManifest: string|null }}
|
||||
@@ -248,8 +263,15 @@ function scrapeFn(hlsManifests) {
|
||||
mediaType = 'video';
|
||||
}
|
||||
// Priority 5: Deep probe fallback — use manifests found by scanning page globals/scripts
|
||||
// and interceptor-collected manifests. This is the most reliable fallback because
|
||||
// interceptors catch HLS activity at the network layer regardless of player type.
|
||||
if (!hlsManifest && hlsManifests && hlsManifests.length > 0) {
|
||||
hlsManifest = hlsManifests[0].url || hlsManifests[0];
|
||||
// Prefer interceptor-collected manifests over script-scrapped ones
|
||||
const interceptManifest = hlsManifests.find(m => m.player === '__goon_plugin_data') ||
|
||||
hlsManifests.find(m => m.player === 'content-script-intercept') ||
|
||||
hlsManifests.find(m => m.player !== 'inline-script') ||
|
||||
hlsManifests[0];
|
||||
hlsManifest = interceptManifest.url || interceptManifest;
|
||||
mediaType = 'hls';
|
||||
src = hlsManifest;
|
||||
}
|
||||
@@ -318,7 +340,7 @@ async function probeHlsManifests(tabId) {
|
||||
|
||||
/**
|
||||
* Aggregate and deduplicate HLS manifests across frames for one tab.
|
||||
* Prefers manifests from known players over inline-script scraping.
|
||||
* Prefers manifests from known players/interceptors over inline-script scraping.
|
||||
* @param {Array} allManifests
|
||||
* @returns {Array}
|
||||
*/
|
||||
@@ -329,16 +351,39 @@ function dedupManifests(allManifests) {
|
||||
seenUrls.add(m.url);
|
||||
return true;
|
||||
});
|
||||
// Sort: prefer interceptor-collected > player globals > config > inline-script
|
||||
const playerRank = {
|
||||
'__goon_plugin_data': 0,
|
||||
'content-script-intercept': 0,
|
||||
'video._goon_hls_manifest': 1,
|
||||
'Hls.cast': 1,
|
||||
'inline-script': 99,
|
||||
};
|
||||
unique.sort((a, b) => {
|
||||
const aInline = a.player === 'inline-script' ? 1 : 0;
|
||||
const bInline = b.player === 'inline-script' ? 1 : 0;
|
||||
return aInline - bInline;
|
||||
const aRank = playerRank[a.player] ?? 50;
|
||||
const bRank = playerRank[b.player] ?? 50;
|
||||
return aRank - bRank;
|
||||
});
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape video elements from the main frame of a tab.
|
||||
* Query the content script for poster URLs, intercepted manifests, and video metadata.
|
||||
* @param {number} tabId
|
||||
* @returns {Promise<Object|null>} content script response or null on error/timeout
|
||||
*/
|
||||
async function queryContentScript(tabId) {
|
||||
try {
|
||||
return await chrome.tabs.sendMessage(tabId, { type: 'GOON_GET_VIDEO_DATA' });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape video elements from ALL frames in a tab.
|
||||
* On sites like Pornhub, the video player lives inside an iframe — main-frame-only
|
||||
* scraping sees zero <video> elements. Running allFrames catches them.
|
||||
* @param {number} tabId
|
||||
* @param {Array} manifests - HLS manifests found by probing
|
||||
* @returns {Promise<Object|null>} scrape result or null on error
|
||||
@@ -346,11 +391,21 @@ function dedupManifests(allManifests) {
|
||||
async function scrapeTab(tabId, manifests) {
|
||||
try {
|
||||
const results = await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
target: { tabId, allFrames: true },
|
||||
func: scrapeFn,
|
||||
args: [manifests],
|
||||
});
|
||||
return results[0]?.result || null;
|
||||
// Prefer the result with the largest video (may come from an iframe)
|
||||
const valid = results
|
||||
.map(r => r.result)
|
||||
.filter(r => r && r.videos && r.videos.length > 0);
|
||||
if (valid.length === 0) return results[0]?.result || null;
|
||||
valid.sort((a, b) => {
|
||||
const aArea = a.videos[0]?.area || 0;
|
||||
const bArea = b.videos[0]?.area || 0;
|
||||
return bArea - aArea;
|
||||
});
|
||||
return valid[0];
|
||||
} catch {
|
||||
warn('skipping tab', tabId);
|
||||
return null;
|
||||
@@ -361,12 +416,13 @@ async function scrapeTab(tabId, manifests) {
|
||||
* Build a normalized video entry from a scraped video object.
|
||||
* @param {{ src: string, poster: string|null, mediaType: string, hlsManifest: string|null }} video
|
||||
* @param {string} tabTitle
|
||||
* @param {string|null} posterFromContent - poster URL from content script (OG image, etc.)
|
||||
* @returns {{ src: string, poster: string|null, tab: string, mediaType: string, hlsManifest?: string }}
|
||||
*/
|
||||
function makeVideoEntry(video, tabTitle) {
|
||||
function makeVideoEntry(video, tabTitle, posterFromContent) {
|
||||
const entry = {
|
||||
src: video.hlsManifest || video.src || '',
|
||||
poster: video.poster || null,
|
||||
poster: video.poster || posterFromContent || null,
|
||||
tab: tabTitle,
|
||||
mediaType: video.mediaType || 'video',
|
||||
};
|
||||
@@ -375,7 +431,7 @@ function makeVideoEntry(video, tabTitle) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the full scan: probe HLS manifests, scrape video elements, dedup, store.
|
||||
* Run the full scan: probe HLS manifests, scrape video elements, query content scripts, dedup, store.
|
||||
* @param {Array} tabsToScan - tabs returned by chrome.tabs.query
|
||||
* @returns {Promise<{ items: Array, scanned: number, skipped: number }>}
|
||||
*/
|
||||
@@ -393,17 +449,46 @@ async function scanVideos(tabsToScan) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Scrape video elements (main frame only)
|
||||
// Step 2: Scrape video elements (all frames) AND query content scripts (in parallel)
|
||||
const scrapePromises = tabsToScan.map(t =>
|
||||
scrapeTab(t.id, tabManifestMap.get(t.id) || [])
|
||||
);
|
||||
const scrapeResults = await Promise.all(scrapePromises);
|
||||
const contentPromises = tabsToScan.map(t => queryContentScript(t.id));
|
||||
const [scrapeResults, contentResults] = await Promise.all([
|
||||
Promise.all(scrapePromises),
|
||||
Promise.all(contentPromises),
|
||||
]);
|
||||
|
||||
// Step 3: Collect results from tabs that found videos
|
||||
const tabResults = tabsToScan.map((tab, i) => ({
|
||||
tabTitle: tab.title || 'Unknown',
|
||||
result: scrapeResults[i],
|
||||
})).filter(g => g.result && g.result.videos?.length > 0);
|
||||
// Step 3: Merge content-script data into tab results
|
||||
// - Enrich video entries with poster URLs from the content script
|
||||
// - Merge content-script intercepted manifests into the deep probe results
|
||||
const tabResults = tabsToScan.map((tab, i) => {
|
||||
const result = scrapeResults[i];
|
||||
const contentData = contentResults[i];
|
||||
|
||||
// Enrich videos with poster from content script (og:image, twitter:image, etc.)
|
||||
if (result && result.videos?.length > 0 && contentData?.posterUrl) {
|
||||
for (const v of result.videos) {
|
||||
if (!v.poster) v.poster = contentData.posterUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge content-script intercepted manifests into the tab's manifest list
|
||||
if (contentData && contentData.interceptedManifests) {
|
||||
const existing = tabManifestMap.get(tab.id) || [];
|
||||
for (const m of contentData.interceptedManifests) {
|
||||
if (m.url && !existing.some(em => em.url === m.url)) {
|
||||
existing.push({ url: m.url, player: 'content-script-intercept' });
|
||||
}
|
||||
}
|
||||
tabManifestMap.set(tab.id, existing);
|
||||
}
|
||||
|
||||
return {
|
||||
tabTitle: tab.title || 'Unknown',
|
||||
result,
|
||||
};
|
||||
}).filter(g => g.result && g.result.videos?.length > 0);
|
||||
|
||||
// Step 4: Dedup across tabs
|
||||
const seen = new Map();
|
||||
|
||||
Reference in new issue
Block a user