diff --git a/CHANGELOG.md b/CHANGELOG.md index dce2ce5..696be8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,22 +2,31 @@ All notable changes to this project will be documented in this file. -## [1.0.4] - 2025-09-18 +## [1.0.5] - 2025-09-19 -### Added -- New provider: `moviesclub` with registry mapping (`moviesclub.js` → `getMoviesClubStreams`). +### Improved +- 4khdhub provider: Permanently block `r2.dev` FSL links (previous optional flag removed). +- Added Referer/Origin headers automatically for FSL Server links during validation (prior to block enforcement ensured proper behavior). +- Tightened URL validation: removed unconditional trust for `r2.dev`; validation logic now consistent across hosts. +- Host distribution instrumentation logs final hostname counts for easier diagnostics. +- Preserved HubCloud worker `.zip` links by stripping the `.zip` extension instead of discarding them (enables direct playback attempts). +- MoviesClub provider: Added automatic `Origin`/`Referer` headers for `vidora.stream` sources to prevent 403 responses. -### Changed -- Unified provider stream schema across all providers: - ```json - { "title": "…", "url": "…", "quality": "…", "provider": "…", "headers": { } } - ``` -- Provider integration docs: When adding a provider, also add its exported function to `providerFunctionMap` in `providers/registry.js`. -- README Docker quick run snippet updated to: `docker run -it -p 8787:8787 inside4ndroid/tmdb-embed-api:latest`. +### Notes +- `r2.dev` links are always removed from final output; no env flag required. ### Documentation - README: Updated Providers section, unified schema example, MoviesClub listed, registry mapping instructions, and badge version. +## [1.0.4] - 2025-09-18 + +### Added + +### Changed + ```json + { "title": "…", "url": "…", "quality": "…", "provider": "…", "headers": { } } + ``` + ## [1.0.3] - 2025-09-17 ### Fixed diff --git a/README.md b/README.md index 152ff66..c980eb5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - +

diff --git a/package.json b/package.json index 847e162..295992b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tmdb-embed-api", - "version": "1.0.4", + "version": "1.0.5", "description": "Standalone streaming embed source aggregation API (TMDB ID in, streams out)", "main": "apiServer.js", "scripts": { diff --git a/providers/4khdhub.js b/providers/4khdhub.js index b5f1404..c711bf1 100644 --- a/providers/4khdhub.js +++ b/providers/4khdhub.js @@ -9,6 +9,9 @@ const path = require('path'); const DOMAINS_URL = 'https://raw.githubusercontent.com/phisher98/TVVVV/refs/heads/main/domains.json'; let cachedDomains = null; +// Permanent policy: block r2.dev links (previously optional via env flag) +console.log('[4KHDHub] r2.dev links will be removed (permanent policy)'); + // --- Caching Configuration --- const CACHE_ENABLED = process.env.DISABLE_CACHE !== 'true'; console.log(`[4KHDHub] Internal cache is ${CACHE_ENABLED ? 'enabled' : 'disabled'}.`); @@ -61,8 +64,7 @@ function validateUrl(url) { // Skip validation for known reliable hosting services const trustedHosts = [ - 'pixeldrain.dev', - 'r2.dev' + 'pixeldrain.dev' ]; const isTrustedHost = trustedHosts.some(host => urlObj.hostname.includes(host)); @@ -74,11 +76,21 @@ function validateUrl(url) { const protocol = urlObj.protocol === 'https:' ? https : http; + // Add referer/origin for r2.dev validation attempts + const extraHeaders = {}; + if (/r2\.dev$/i.test(urlObj.hostname)) { + const origin = `${urlObj.protocol}//${urlObj.hostname}`; + extraHeaders['Referer'] = origin + '/'; + extraHeaders['Origin'] = origin; + console.log(`[4KHDHub] Adding Referer/Origin headers for validation of r2.dev URL: ${url}`); + } + const options = { method: 'HEAD', timeout: 15000, headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + ...extraHeaders } }; @@ -751,6 +763,14 @@ function extractHubCloudLinks(url, referer) { if (displayFilename) titleParts.push(displayFilename); if (size) titleParts.push(size); const finalTitle = titleParts.join('\n'); + let headers = {}; + try { + const host = new URL(link).hostname; + if (/r2\.dev$/i.test(host)) { + headers = { Referer: baseUrl + '/', Origin: baseUrl }; + console.log('[4KHDHub] Added Referer/Origin headers for r2.dev FSL link'); + } + } catch {} resolve({ name: `4KHDHub - FSL Server${qualityLabel}`, @@ -758,7 +778,7 @@ function extractHubCloudLinks(url, referer) { url: link, quality: quality, provider: '4khdhub', - headers: {} + headers }); }) .catch(() => { @@ -767,6 +787,14 @@ function extractHubCloudLinks(url, referer) { if (displayFilename) titleParts.push(displayFilename); if (size) titleParts.push(size); const finalTitle = titleParts.join('\n'); + let headers = {}; + try { + const host = new URL(link).hostname; + if (/r2\.dev$/i.test(host)) { + headers = { Referer: baseUrl + '/', Origin: baseUrl }; + console.log('[4KHDHub] Added Referer/Origin headers for r2.dev FSL link (fallback)'); + } + } catch {} resolve({ name: `4KHDHub - FSL Server${qualityLabel}`, @@ -774,7 +802,7 @@ function extractHubCloudLinks(url, referer) { url: link, quality: quality, provider: '4khdhub', - headers: {} + headers }); }); } else if (text.includes('Download File')) { @@ -1306,12 +1334,22 @@ function extractStreamingLinks(downloadLinks) { .then(results => { const validResults = results.filter(result => result !== null); const flatResults = validResults.flat(); - // Filter out .zip files - const filteredResults = flatResults.filter(link => { - return link && link.url && !link.url.toLowerCase().endsWith('.zip'); - }); + // Keep .zip links but strip the trailing .zip extension (attempt direct file URL) + const transformedResults = flatResults + .filter(link => link && link.url) + .map(link => { + try { + const lower = link.url.toLowerCase(); + if (lower.endsWith('.zip')) { + const newUrl = link.url.slice(0, -4); // remove .zip + console.log(`[4KHDHub] Converted zip URL to non-zip: ${link.url} -> ${newUrl}`); + return { ...link, url: newUrl }; + } + } catch {} + return link; + }); // Note: Link count will be logged after validation completes - return filteredResults; + return transformedResults; }); } @@ -1778,7 +1816,7 @@ async function get4KHDHubStreams(tmdbId, type, season = null, episode = null) { } // Normalize to generic stream object format - const streams = validatedLinks.map(link => ({ + let streams = validatedLinks.map(link => ({ name: link.name, // Don't add prefix since it's already included title: link.title || link.name, url: link.url, @@ -1787,6 +1825,28 @@ async function get4KHDHubStreams(tmdbId, type, season = null, episode = null) { headers: {} })); + // Always remove r2.dev links + { + const before = streams.length; + streams = streams.filter(s => { + try { return !new URL(s.url).hostname.endsWith('r2.dev'); } catch { return true; } + }); + const removed = before - streams.length; + if (removed > 0) console.log(`[4KHDHub] Removed ${removed} r2.dev streams (permanent policy)`); + } + + // Host distribution instrumentation + try { + const hostCounts = streams.reduce((acc, s) => { + try { + const h = new URL(s.url).hostname; + acc[h] = (acc[h] || 0) + 1; + } catch {} + return acc; + }, {}); + console.log('[4KHDHub] Final host distribution:', hostCounts); + } catch {} + console.log(`[4KHDHub] Returning ${streams.length} streams`); return streams; diff --git a/providers/moviesclub.js b/providers/moviesclub.js index 27c105b..26c713f 100644 --- a/providers/moviesclub.js +++ b/providers/moviesclub.js @@ -309,7 +309,15 @@ async function handleSingleServer(embedUrl, mediaType, season, episode, tmdbId) title: playerConfig.title || `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - MoviesClub`, url: playerConfig.file, quality: extractQualityFromUrl(playerConfig.file) || '720p', - headers: {} + headers: (() => { + if (playerConfig.file.includes('vidora.stream')) { + return { + Origin: 'https://vidora.stream', + Referer: 'https://vidora.stream/' + }; + } + return {}; + })() }; // Add poster if available @@ -384,7 +392,7 @@ function extractStreamsFromServer(serverData, serverName, mediaType, season, epi title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - ${serverName}`, url: serverData, quality: extractQualityFromUrl(serverData) || '720p', - headers: {} + headers: serverData.includes('vidora.stream') ? { Origin: 'https://vidora.stream', Referer: 'https://vidora.stream/' } : {} }; streams.push(stream); } @@ -406,7 +414,7 @@ function extractStreamsFromServer(serverData, serverName, mediaType, season, epi title: `${mediaType === 'movie' ? 'Movie' : `S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`} ${tmdbId} - ${serverName}`, url: serverData[key], quality: extractQualityFromUrl(serverData[key]) || '720p', - headers: {} + headers: serverData[key].includes('vidora.stream') ? { Origin: 'https://vidora.stream', Referer: 'https://vidora.stream/' } : {} }; // Add poster if available