This commit is contained in:
1 parent
b234c8dcae
commit
001cf146ee
10 files changed
+189
-217
No files matched your search
+93
-16
@@ -829,29 +829,72 @@ function filterManifests(manifests) {
|
||||
(u.includes('/playlist') && !u.endsWith('.ts'));
|
||||
});
|
||||
|
||||
// If we have actual manifests, use them directly
|
||||
if (actualManifests.length > 0) {
|
||||
return actualManifests.map(m => ({ url: m.url }));
|
||||
// Reject fake reconstructed manifest paths.
|
||||
// Real manifests are under /hls/videos/{year}/{subdir}/{videoId}/...
|
||||
// Fakes look like: phncdn.com/videos/{id}/playlist.m3u8 or .../master.m3u8 (no /hls/)
|
||||
const valid = actualManifests.filter(m => {
|
||||
const u = m.url.toLowerCase();
|
||||
try {
|
||||
const urlObj = new URL(u);
|
||||
// phncdn URLs must have /hls/ in the path — fake reconstructed paths lack it
|
||||
if (u.includes('phncdn') || u.includes('pornhubcdn')) {
|
||||
if (!urlObj.pathname.includes('/hls/') && !u.includes('?h=')) {
|
||||
return false; // Fake: phncdn URL without /hls/ or auth query param
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return true;
|
||||
});
|
||||
|
||||
// If we have actual (non-fake) manifests, use them directly
|
||||
if (valid.length > 0) {
|
||||
return valid.map(m => ({ url: m.url }));
|
||||
}
|
||||
|
||||
// No .m3u8 found — interceptors only captured .ts segments.
|
||||
// Reconstruct the manifest URL from segment URLs.
|
||||
// Segment URLs look like: /hls/videos/{video-id}/seg-{n}-v{ver}-a{aud}.ts
|
||||
// Manifest URL would be: /hls/videos/{video-id}/playlist.m3u8
|
||||
// Reconstruct the parent manifest URL from segment paths.
|
||||
// Segment: https://ev-h.phncdn.com/hls/videos/202008/01/338337051/720P_4000K_338337051.mp4/seg-1-v1-a1.ts?h=tmrD...&e=1786932752&f=1
|
||||
// Manifest: https://ev-h.phncdn.com/hls/videos/202008/01/338337051/720P_4000K_338337051.mp4/master.m3u8?h=tmrD...&e=1786932752&f=1
|
||||
//
|
||||
// Auth tokens (h, e, f or validfrom, validto, hash) are extracted from segment query params
|
||||
// and reapplied to the reconstructed manifest URL. This is critical because CDN domains
|
||||
// require auth — a path-only URL will be rejected with 403.
|
||||
const seen = new Set();
|
||||
const reconstructed = [];
|
||||
|
||||
for (const m of manifests) {
|
||||
const u = m.url.toLowerCase();
|
||||
if (!u.includes('/hls/') || !u.endsWith('.ts')) continue;
|
||||
if (!u.includes('/hls/videos/') || !/\/seg-\d+/.test(u)) continue;
|
||||
try {
|
||||
const url = new URL(m.url);
|
||||
const pathParts = url.pathname.split('/');
|
||||
// Find the /hls/videos/ segment and the video-id directory
|
||||
const urlObj = new URL(m.url);
|
||||
// Skip non-phncdn hosts — we only reconstruct for known video CDNs
|
||||
const host = urlObj.host.toLowerCase();
|
||||
if (!host.includes('phncdn')) continue;
|
||||
|
||||
const pathParts = urlObj.pathname.split('/');
|
||||
// Find the /hls/ marker and extract: hls/videos/{year}/{subdir}/{videoId}/{variant}/...
|
||||
const hlsIdx = pathParts.findIndex(p => p === 'hls');
|
||||
if (hlsIdx === -1 || hlsIdx + 2 >= pathParts.length) continue;
|
||||
// Build manifest URL: origin + /hls/videos/{video-id}/playlist.m3u8
|
||||
const manifestPath = `${pathParts[hlsIdx]}/${pathParts[hlsIdx + 1]}/${pathParts[hlsIdx + 2]}/playlist.m3u8`;
|
||||
const manifestUrl = `${url.protocol}//${url.host}${manifestPath}`;
|
||||
if (hlsIdx === -1 || hlsIdx + 5 >= pathParts.length) continue;
|
||||
// Reconstruct with BOTH "hls" prefix AND variant directory included:
|
||||
// segment: /hls/videos/202008/01/338337051/720P_4000K_338337051.mp4/seg-1.ts
|
||||
// manifest: /hls/videos/202008/01/338337051/720P_4000K_338337051.mp4/master.m3u8
|
||||
const year = pathParts[hlsIdx + 2]; // e.g. 2020
|
||||
const subDir = pathParts[hlsIdx + 3]; // e.g. 01
|
||||
const videoId = pathParts[hlsIdx + 4]; // e.g. 338337051
|
||||
const variant = pathParts[hlsIdx + 5]; // e.g. 720P_4000K_338337051.mp4
|
||||
|
||||
// Extract auth params from segment query string — different CDN variants use different keys:
|
||||
// ev-h/validfrom/validto/hash vs hv-h/h/e/f
|
||||
const authParts = [];
|
||||
for (const [key, val] of urlObj.searchParams) {
|
||||
if (['h', 'e', 'f', 'validfrom', 'validto', 'hash'].includes(key)) {
|
||||
authParts.push(`${key}=${val}`);
|
||||
}
|
||||
}
|
||||
const authStr = authParts.length > 0 ? '?' + authParts.join('&') : '';
|
||||
|
||||
// Build manifest URL: include hls prefix AND variant directory
|
||||
const manifestUrl = `${urlObj.protocol}//${urlObj.host}/${pathParts[hlsIdx]}/${pathParts[hlsIdx + 1]}/${year}/${subDir}/${videoId}/${variant}/master.m3u8${authStr}`;
|
||||
if (!seen.has(manifestUrl)) {
|
||||
seen.add(manifestUrl);
|
||||
reconstructed.push({ url: manifestUrl });
|
||||
@@ -861,8 +904,30 @@ function filterManifests(manifests) {
|
||||
return reconstructed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a manifest URL uses a real phncdn.com path structure.
|
||||
* Rejects fake paths like /videos/{id}/playlist.m3u8 — real manifests use
|
||||
* /hls/videos/{year}/{subdir}/{videoId}/master.m3u8 or similar /hls/ paths.
|
||||
*/
|
||||
function isValidManifestUrl(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
// Real phncdn HLS manifest: must contain /hls/videos/ with sufficient path depth
|
||||
const hlsIdx = url.indexOf('/hls/videos/');
|
||||
if (hlsIdx === -1) {
|
||||
// Not phncdn — could be a different CDN, allow it
|
||||
if (!url.includes('phncdn') && !url.includes('pornhubcdn')) return true;
|
||||
// phncdn without /hls/ prefix is invalid for manifests
|
||||
return false;
|
||||
}
|
||||
// Check there are at least {year}/{subdir}/{videoId} after /hls/videos/
|
||||
const after = url.substring(hlsIdx + '/hls/videos/'.length);
|
||||
if (after.indexOf('/') === -1) return false; // need at least one more segment
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a normalized video entry from a scraped video object.
|
||||
* Validates manifest URLs to prevent storing fake paths like /videos/{id}/playlist.m3u8.
|
||||
* @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.)
|
||||
@@ -875,7 +940,19 @@ function makeVideoEntry(video, tabTitle, posterFromContent) {
|
||||
tab: tabTitle,
|
||||
mediaType: video.mediaType || 'video',
|
||||
};
|
||||
if (video.hlsManifest) entry.hlsManifest = video.hlsManifest;
|
||||
if (video.hlsManifest) {
|
||||
// Reject fake phncdn manifest paths before storing
|
||||
if (!isValidManifestUrl(video.hlsManifest)) {
|
||||
console.warn('[goon] Rejecting fake manifest URL:', video.hlsManifest.substring(0, 120));
|
||||
} else {
|
||||
entry.hlsManifest = video.hlsManifest;
|
||||
}
|
||||
}
|
||||
// Also validate src when it looks like a manifest URL (no hlsManifest set but src has .m3u8)
|
||||
if (!entry.hlsManifest && entry.src?.includes('.m3u8') && !isValidManifestUrl(entry.src)) {
|
||||
console.warn('[goon] Rejecting fake manifest URL from src:', entry.src.substring(0, 120));
|
||||
// Don't treat it as HLS — leave mediaType as-is (likely 'video' or unset)
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -930,7 +1007,7 @@ async function scanVideos(tabsToScan) {
|
||||
// Step 1.6: Strategy 8 — Query phncdn.com requests and reconstruct manifest URLs.
|
||||
// When the player uses in-memory dynamic HLS (zero .m3u8 network requests), we can
|
||||
// still detect it by recognizing segment URL patterns from phncdn.com and deriving
|
||||
// the parent manifest URL: /videos/{id}/playlist.m3u8
|
||||
// the parent manifest URL: /hls/videos/{id}/master.m3u8
|
||||
const phncdnPromises = tabsToScan.map(t => queryPhncdnRequests(t.id));
|
||||
const phncdnResults = await Promise.all(phncdnPromises);
|
||||
let totalPhncdnManifests = 0;
|
||||
|
||||
Reference in new issue
Block a user