fix(scan): run deep probe/scraper in MAIN world; reject route-template "manifests"
CI / test (pull_request) Successful in 5m48s
CI / test (pull_request) Successful in 5m48s
The video-detection pipeline was finding nothing on pages that have a bare
<video> element. Two root causes:
1. world: 'MAIN' placement. The deep probe + scraper are injected with
chrome.scripting.executeScript. Their code (and its own comments) requires
the MAIN world to see page globals — window.Hls, per-<video> Hls instances,
video._goon_hls_manifest, and window.__goon_plugin_data (written by the
main-world interceptors). The calls were missing it, so every main-world
strategy (A/A2/E/H) found 0. It was also briefly placed inside `target`,
which chrome.scripting rejects ("Unexpected property: 'world'"); `world` is
a sibling of `target` on ScriptInjection. Restore it as a sibling of target.
2. fake "manifest" classification. Strategy H was reporting 10 nav links /
server-side route TEMPLATES as manifests — e.g. /playlists/,
/playlists/liked/, /playlist/%1%/video/%2%/, /members/%ID%/playlists/ —
because the old /playlist|/manifest substring check matches nav menus and
leaves %1%/%ID%/%DIR% template placeholders intact. Those then became the
stored "video" entries, drowning out the real element.
isManifestUrl and isValidManifestUrl now reject:
- dynamic route templates (%NAME% or {name} tokens), using a negative
lookahead so genuine %-encoded tokens like %2B/%2F are preserved
(the invariant the codebase explicitly protects),
- plural collection paths (/playlists, /manifests),
- trailing-slash directory listings.
The 'World: ISOLATED' probe log was also misleading (typeof chrome is not a
world discriminator — the probe provably runs in MAIN); replaced it.
Adds a regression test (hls_qa) reproducing thisvid's pageContext.langUrls
scenario: all 10 templates are rejected while a real .m3u8 carrying a %2B token
is still detected. 90/90 tests pass; 0 lint errors; build OK.
This commit is contained in:
1 parent
e3b904783f
commit
7139c5ea6f
2 files changed
+168
-48
No files matched your search
+55
-29
@@ -122,7 +122,11 @@ function m3u8DeepProbeFn() {
|
||||
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');
|
||||
// NOTE: the probe is injected with chrome.scripting's `world: 'MAIN'`, so it
|
||||
// runs in the page's main world and can see page globals. `typeof chrome` is
|
||||
// NOT a reliable world discriminator (extensions expose `chrome` in the
|
||||
// isolated world too), so don't infer it that way.
|
||||
probeLog('Deep probe started on:', window.location.href, '(main world)');
|
||||
|
||||
// Helpers available in the MAIN world context
|
||||
function resolveUrl(url) {
|
||||
@@ -134,14 +138,23 @@ function m3u8DeepProbeFn() {
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
function isManifestUrl(url) {
|
||||
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'));
|
||||
}
|
||||
// Dynamic route TEMPLATES (e.g. /playlist/%1%/ or /videos/{id}/) are nav
|
||||
// links, not fetchable manifests. A placeholder is a `%` that is NOT the
|
||||
// start of a valid %-encoded hex pair (so real tokens like %2B, %2F are
|
||||
// preserved) or a {name} brace token — so genuine %-encoded auth tokens are
|
||||
// never mistaken for template placeholders.
|
||||
if (/%(?!([0-9A-Fa-f]{2}))/i.test(u) || /\{[a-z0-9_]+\}/i.test(u)) return false;
|
||||
// .m3u8 / .hlsvod / /hls/ are concrete HLS signals.
|
||||
if (u.includes('.m3u8') || u.includes('.hlsvod') || u.includes('/hls/')) return true;
|
||||
// Plural collection paths are nav menus, not manifests.
|
||||
if (u.includes('/playlists') || u.includes('/manifests')) return false;
|
||||
// A singular manifest-ish path segment with no concrete file may still be a
|
||||
// real (rare, extensionless) endpoint, so allow it when it carries http.
|
||||
return (u.includes('/manifest') || u.includes('/playlist')) && u.includes('http');
|
||||
}
|
||||
|
||||
function walk(obj, path, depth) {
|
||||
if (depth > 5) return;
|
||||
@@ -666,8 +679,9 @@ async function probeHlsManifests(tabId) {
|
||||
log('probeHlsManifests: executing deep probe on tab', tabId);
|
||||
const raw = await chrome.scripting.executeScript({
|
||||
target: { tabId, allFrames: true },
|
||||
world: 'MAIN',
|
||||
func: m3u8DeepProbeFn,
|
||||
});
|
||||
});
|
||||
// When allFrames is true, destroyed / sandboxed frames may return null entries.
|
||||
// Guard: ensure raw is an array, then filter to objects with a defined .result.
|
||||
const manifests = Array.isArray(raw)
|
||||
@@ -778,13 +792,14 @@ async function queryVideoRequests(tabId) {
|
||||
* @param {Array} manifests - HLS manifests found by probing
|
||||
* @returns {Promise<Object|null>} scrape result or null on error
|
||||
*/
|
||||
async function scrapeTab(tabId, manifests) {
|
||||
try {
|
||||
async function scrapeTab(tabId, manifests) {
|
||||
try {
|
||||
const raw = await chrome.scripting.executeScript({
|
||||
target: { tabId, allFrames: true },
|
||||
world: 'MAIN',
|
||||
func: scrapeFn,
|
||||
args: [manifests],
|
||||
});
|
||||
args: [manifests],
|
||||
});
|
||||
// When allFrames is true, destroyed / sandboxed frames may return null entries.
|
||||
// Guard: ensure raw is an array, then filter to objects with a defined .result.
|
||||
const frameResults = Array.isArray(raw)
|
||||
@@ -871,23 +886,33 @@ function filterManifests(manifests) {
|
||||
* Rejects malformed paths; a manifest-like URL must live in a directory (have a
|
||||
* path separator), which filters out obviously-broken single-segment paths.
|
||||
*/
|
||||
function isValidManifestUrl(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
let urlObj;
|
||||
try {
|
||||
urlObj = new URL(url);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
const path = urlObj.pathname.toLowerCase();
|
||||
const isManifestLike = path.includes('.m3u8') || path.includes('.hlsvod') ||
|
||||
(path.includes('/playlist') && !path.endsWith('.ts')) ||
|
||||
(path.includes('/master') && path.includes('.m3u'));
|
||||
// Not a manifest path — allow it (it may be a direct media source, not HLS).
|
||||
if (!isManifestLike) return true;
|
||||
// A manifest path must sit in a directory, not be a bare top-level file.
|
||||
return path.includes('/');
|
||||
}
|
||||
function isValidManifestUrl(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
// Dynamic route templates / nav links are not fetchable manifests. A
|
||||
// placeholder is a `%` not followed by two hex digits (real %2B etc. are
|
||||
// kept) or a {name} brace token.
|
||||
if (/%(?!([0-9A-Fa-f]{2}))/i.test(url) || /\{[a-z0-9_]+\}/i.test(url)) return false;
|
||||
let urlObj;
|
||||
try {
|
||||
urlObj = new URL(url);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
const path = urlObj.pathname.toLowerCase();
|
||||
// Plural collection paths and trailing-slash directory listings are nav menus,
|
||||
// not manifest files (e.g. /playlists/, /playlists/liked/, /playlist/%id%/).
|
||||
if (path.includes('/playlists') || path.includes('/manifests') || path.endsWith('/')) {
|
||||
return false;
|
||||
}
|
||||
const isManifestLike = path.includes('.m3u8') || path.includes('.hlsvod') ||
|
||||
(path.includes('/master') && path.includes('.m3u')) ||
|
||||
(path.includes('/playlist') && !path.endsWith('.ts')) ||
|
||||
path.includes('/manifest');
|
||||
// Not a manifest path — allow it (it may be a direct media source, not HLS).
|
||||
if (!isManifestLike) return true;
|
||||
// A manifest path must sit in a directory, not be a bare top-level file.
|
||||
return path.includes('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a normalized video entry from a scraped video object.
|
||||
@@ -1150,6 +1175,7 @@ findBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: activeTab.id },
|
||||
world: 'MAIN',
|
||||
func: () => {
|
||||
const diag = [];
|
||||
// Check interceptor data
|
||||
|
||||
Reference in new issue
Block a user