feat: deep probe strategies G (non-enumerable/Symbol) and H (full window scan)

Strategy G — Independent non-enumerable + Symbol property detection on video elements.
Uses Object.getOwnPropertyNames/Symbols to catch properties hidden by enumerable:false or Symbol keys.
Walks nested objects for URL/levels/config signatures matching HLS instance heuristics.

Strategy H — Full window global enumeration with player-like key filtering.
Catches previously-unknown globals the player wrapper exposes internally.

Content script bridge — merge content-level intercepted manifests into
window.__goon_plugin_data so both interceptors and executeScript share findings.

Add phncdn_reconstructed to dedup priority ranking.
This commit is contained in:
kfj001 committed 2026-08-16 16:22:07 -07:00
1 parent 877feec731
commit 509948def1
2 files changed
+188 -5

No files matched your search

+142 -1
View File
@@ -373,6 +373,145 @@ function m3u8DeepProbeFn() {
} catch(e) { /* ignore */ }
probeLog('Strategy F (dynamic HLS fallback): found', strategyFCount, 'new manifests');
// Strategy G: Non-enumerable + Symbol property detection on video elements.
// Object.keys() only returns enumerable string properties. Players like es6player
// hide instances via Object.defineProperty(enumerable:false) or Symbol keys.
// This runs in main-world via executeScript, so getOwnPropertyNames/Symbols work.
let strategyGCount = 0;
try {
for (const v of document.querySelectorAll('video')) {
// Non-enumerable string properties
const nonEnumProps = Object.getOwnPropertyNames(v);
for (const key of nonEnumProps) {
if (STANDARD_VIDEO_ATTRS.has(key)) continue;
try {
const obj = v[key];
if (!obj || typeof obj !== 'object') continue;
// Check if this object looks like an HLS instance (same heuristics as interceptors)
const isHlsLike = typeof obj.url === 'string' && obj.url.length > 5 ||
(obj.levels && Array.isArray(obj.levels)) ||
typeof obj.attachMedia === 'function' ||
typeof obj.loadSource === 'function' ||
typeof obj.getConfig === 'function' ||
typeof obj.detachMedia === 'function' ||
typeof obj.destroy === 'function' ||
(obj.networkControls && typeof obj.networkControls === 'object');
if (isHlsLike) {
probeLog('Strategy G: found HLS-like object on video via non-enumerable prop', key);
// Extract URL from non-enumerable property
if (typeof obj.url === 'string' && obj.url.length > 5) {
const resolved = resolveUrl(obj.url);
if (resolved && !seen.has(resolved)) {
seen.add(resolved);
manifests.push({ url: resolved, player: `video[G].${key}.url` });
strategyGCount++;
}
}
// Extract level URIs
if (obj.levels && Array.isArray(obj.levels)) {
for (let gi = 0; gi < obj.levels.length; gi++) {
const lvl = obj.levels[gi];
if (lvl && lvl.uri && !seen.has(lvl.uri)) {
const resolved = resolveUrl(lvl.uri) || lvl.uri;
seen.add(resolved);
manifests.push({ url: resolved, player: `video[G].${key}.levels[${gi}].uri` });
strategyGCount++;
}
}
}
} else {
// Also deep-walk nested objects for URL-like values (even without HLS signatures)
if (typeof obj === 'object' && obj !== null) {
walk(obj, `video[G].${key}`, 0);
}
}
} catch(e) { /* circular or inaccessible */ }
}
// Symbol properties
const symbolProps = Object.getOwnPropertySymbols(v);
for (const symKey of symbolProps) {
try {
const symObj = v[symKey];
if (!symObj || typeof symObj !== 'object') continue;
probeLog('Strategy G: video has Symbol prop', symKey.toString());
// Check HLS signatures on symbols too
if (typeof symObj.url === 'string' && symObj.url.length > 5) {
const resolved = resolveUrl(symObj.url);
if (resolved && !seen.has(resolved)) {
seen.add(resolved);
manifests.push({ url: resolved, player: `video[G].${symKey.toString()}.url` });
strategyGCount++;
}
}
if (symObj.levels && Array.isArray(symObj.levels)) {
for (let gi = 0; gi < symObj.levels.length; gi++) {
const lvl = symObj.levels[gi];
if (lvl && lvl.uri && !seen.has(lvl.uri)) {
const resolved = resolveUrl(lvl.uri) || lvl.uri;
seen.add(resolved);
manifests.push({ url: resolved, player: `video[G].${symKey.toString()}.levels[${gi}].uri` });
strategyGCount++;
}
}
}
// Deep-walk nested objects
walk(symObj, `video[G].${symKey.toString()}`, 0);
} catch(e) { /* inaccessible */ }
}
}
} catch(e) { /* ignore */ }
probeLog('Strategy G (non-enumerable/Symbol props): found', strategyGCount, 'new manifests');
// Strategy H: Full window global scan — enumerate ALL objects on window, not just
// known player names. Filter out built-ins and walk any object that looks like a
// player container (.url, .levels, .config with HLS keys, .media reference).
let strategyHCount = 0;
try {
const windowKeys = Object.keys(window);
for (const key of windowKeys) {
const val = window[key];
// Skip primitives
if (typeof val === 'function' || typeof val === 'string' ||
typeof val === 'number' || typeof val === 'boolean' ||
val === null || val === undefined) continue;
// Skip DOM elements directly on window
if (val && typeof val.nodeType === 'number') continue;
if (typeof val !== 'object') continue;
// Quick check: does this object have any player-like keys?
let hasPlayerKey = false;
try {
const subKeys = Object.keys(val).slice(0, 50); // limit scan depth for performance
hasPlayerKey = subKeys.some(k => {
const lk = k.toLowerCase();
return lk.includes('hls') || lk.includes('player') || lk.includes('video') ||
lk.includes('manifest') || lk.includes('source') || lk.includes('level') ||
lk.includes('playlist') || lk.includes('stream') || lk === 'url' ||
lk === 'config' || lk === 'media' || lk === 'levels' ||
lk === 'width' || lk === 'height' || lk === 'volume';
});
} catch { hasPlayerKey = false; }
if (!hasPlayerKey) continue;
probeLog('Strategy H: window global', key, 'has player-like keys');
// Deep-walk this object for HLS instances and manifest URLs
walk(val, `window[H].${key}`, 0);
if (typeof val.url === 'string' && isManifestUrl(val.url)) {
const resolved = resolveUrl(val.url);
if (resolved && !seen.has(resolved)) {
seen.add(resolved);
manifests.push({ url: resolved, player: `window[H].${key}.url` });
strategyHCount++;
}
}
}
} catch(e) { /* ignore */ }
probeLog('Strategy H (full window scan): found', strategyHCount, 'new manifests');
probeLog('Deep probe complete. Total unique manifests found:', manifests.length);
if (manifests.length > 0) {
probeLog('Manifest summary:', JSON.stringify(manifests.map(m => ({ url: m.url.substring(0, 100), player: m.player }))));
@@ -560,7 +699,9 @@ function dedupManifests(allManifests) {
const playerRank = {
'__goon_plugin_data': 0,
'content-script-intercept': 0,
'video._goon_hls_manifest': 1,
'webRequest': 1,
'phncdn_reconstructed': 2,
'video._goon_hls_manifest': 3,
'inline-script': 99,
};
unique.sort((a, b) => {