Skip to content

Commit 6ebb0d3

Browse files
committed
chore(release): 1.0.8
1 parent c8ef893 commit 6ebb0d3

18 files changed

Lines changed: 727 additions & 1045 deletions

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [1.0.8] - 2025-10-03
6+
7+
### Added
8+
- Added `processStreamsForProxy` so the aggregate and provider-specific endpoints automatically rewrite stream URLs through the internal `/m3u8-proxy`/`/ts-proxy` layer whenever `enableProxy` is turned on, stripping provider headers safely.
9+
10+
### Changed
11+
- Showbox provider overhaul: filesystem-only caching (Redis removed), smarter FebBox cookie selection with region fallbacks, TMDB title/image validation that recognises romanized names, plus cached HEAD size lookups for faster listings.
12+
- Vixsrc provider rewritten to parse the `window.masterPlaylist` payload (token + expiry), returning a single master playlist with English subtitle lookup and correct Referer headers.
13+
- Config loader now prefers `utils/user-config.json`, dedupes TMDB keys, defaults provider enable flags to `true`, and scrubs legacy proxy env values when mirroring overrides back to `process.env`.
14+
- 4KHDHub provider permanently drops `.zip` archive links instead of trimming extensions and improves host distribution logging.
15+
- Provider registry dynamically enumerates available modules (removing MoviesClub/Xprime remnants) while keeping per-request cookie stats for the admin debug panel.
16+
17+
### Removed
18+
- Deleted legacy `providers/moviesclub.js`, `providers/xprime.js`, and other unused proxy/auth remnants that were no longer referenced.
19+
20+
### Fixed
21+
- Aggregated responses now obey the proxy flag without client changes—streams returned from `/api/streams/...` are proxy-wrapped and omit upstream header hints when `enableProxy` is active.
22+
523
## [1.0.7] - 2025-09-21
624

725
### Added
@@ -132,6 +150,7 @@ All notable changes to this project will be documented in this file.
132150
## [1.0.0] - 2025-09-16
133151
- Initial stable release.
134152

153+
[1.0.8]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.7...v1.0.8
135154
[1.0.7]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.6...v1.0.7
136155
[1.0.6]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.5...v1.0.6
137156
[1.0.5]: https://github.com/Inside4ndroid/TMDB-Embed-API/compare/v1.0.4...v1.0.5

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<img src="https://img.shields.io/badge/Node.js-18%2B-brightgreen?style=flat" />
77
<img src="https://img.shields.io/badge/Status-Active-success?style=flat" />
88
<img src="https://img.shields.io/badge/License-MIT-blue?style=flat" />
9-
<img src="https://img.shields.io/badge/Version-1.0.7-informational?style=flat" />
9+
<img src="https://img.shields.io/badge/Version-1.0.8-informational?style=flat" />
1010
<img src="https://img.shields.io/docker/pulls/inside4ndroid/tmdb-embed-api?label=Docker%20Pulls&style=flat" />
1111
</p>
1212

apiServer.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,23 @@ const express = require('express');
33
const cors = require('cors');
44
const os = require('os');
55
const { config, saveConfigPatch, OVERRIDE_PATH } = require('./utils/config');
6-
const { authenticate, issueSession, requireAuth, getSession, updatePassword, AUTH_FILE } = require('./utils/auth');
6+
const { authenticate, issueSession, requireAuth, getSession, updatePassword } = require('./utils/auth');
77
const path = require('path');
88
const { listProviders, getProvider, getCookieStats } = require('./providers/registry');
9+
const { createProxyRoutes, processStreamsForProxy } = require('./proxy/proxyServer');
910
const { resolveImdbId } = require('./utils/tmdb');
1011
const { applyFilters } = require('./utils/streamFilters');
1112

1213
const app = express();
1314

15+
// Conditionally mount proxy routes early so downstream handlers can use them
16+
if (config.enableProxy) {
17+
console.log('[startup] enableProxy flag active: mounting proxy routes');
18+
createProxyRoutes(app);
19+
} else {
20+
console.log('[startup] enableProxy flag disabled: proxy routes not mounted');
21+
}
22+
1423
// --- Simple In-Memory Rate Limiting for /auth/login ---
1524
const loginAttempts = new Map(); // key: ip, value: { count, first, last, lockedUntil }
1625
const MAX_ATTEMPTS_WINDOW = 5; // attempts allowed
@@ -340,6 +349,12 @@ app.get('/api/streams/:type/:tmdbId', async (req,res) => {
340349
let streams = results.flat();
341350
streams = applyFilters(streams, 'aggregate', config.minQualities, config.excludeCodecs);
342351
metrics.streamsReturned += streams.length;
352+
if (config.enableProxy) {
353+
const serverUrl = `${req.protocol}://${req.get('host')}`;
354+
streams = processStreamsForProxy(streams, serverUrl);
355+
// Omit original headers when proxying to avoid leaking upstream requirements
356+
streams = streams.map(s => { if (s && typeof s === 'object') { const { headers, ...rest } = s; return rest; } return s; });
357+
}
343358
res.json({ success:true, tmdbId, imdbId, count: streams.length, providerTimings, streams });
344359
} catch (e) {
345360
metrics.lastError = e.message;
@@ -366,6 +381,11 @@ app.get('/api/streams/:provider/:type/:tmdbId', async (req,res) => {
366381
const providerTimings = { [prov.name]: Date.now()-t0 };
367382
streams = applyFilters(streams, prov.name, config.minQualities, config.excludeCodecs);
368383
metrics.streamsReturned += streams.length;
384+
if (config.enableProxy) {
385+
const serverUrl = `${req.protocol}://${req.get('host')}`;
386+
streams = processStreamsForProxy(streams, serverUrl);
387+
streams = streams.map(s => { if (s && typeof s === 'object') { const { headers, ...rest } = s; return rest; } return s; });
388+
}
369389
res.json({ success:true, provider: prov.name, tmdbId, imdbId, count: streams.length, providerTimings, streams });
370390
} catch (e) {
371391
metrics.lastError = e.message;

package-lock.json

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tmdb-embed-api",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "Standalone streaming embed source aggregation API (TMDB ID in, streams out)",
55
"main": "apiServer.js",
66
"scripts": {

providers/4khdhub.js

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,9 @@ function extractHubCloudLinks(url, referer) {
770770
headers = { Referer: baseUrl + '/', Origin: baseUrl };
771771
console.log('[4KHDHub] Added Referer/Origin headers for r2.dev FSL link');
772772
}
773-
} catch {}
773+
} catch {
774+
// Ignore URL parsing errors
775+
}
774776

775777
resolve({
776778
name: `4KHDHub - FSL Server${qualityLabel}`,
@@ -794,7 +796,9 @@ function extractHubCloudLinks(url, referer) {
794796
headers = { Referer: baseUrl + '/', Origin: baseUrl };
795797
console.log('[4KHDHub] Added Referer/Origin headers for r2.dev FSL link (fallback)');
796798
}
797-
} catch {}
799+
} catch {
800+
// Ignore URL parsing errors
801+
}
798802

799803
resolve({
800804
name: `4KHDHub - FSL Server${qualityLabel}`,
@@ -1332,24 +1336,18 @@ function extractStreamingLinks(downloadLinks) {
13321336

13331337
return Promise.all(promises)
13341338
.then(results => {
1335-
const validResults = results.filter(result => result !== null);
1336-
const flatResults = validResults.flat();
1337-
// Keep .zip links but strip the trailing .zip extension (attempt direct file URL)
1338-
const transformedResults = flatResults
1339-
.filter(link => link && link.url)
1340-
.map(link => {
1341-
try {
1342-
const lower = link.url.toLowerCase();
1343-
if (lower.endsWith('.zip')) {
1344-
const newUrl = link.url.slice(0, -4); // remove .zip
1345-
console.log(`[4KHDHub] Converted zip URL to non-zip: ${link.url} -> ${newUrl}`);
1346-
return { ...link, url: newUrl };
1347-
}
1348-
} catch {}
1349-
return link;
1350-
});
1351-
// Note: Link count will be logged after validation completes
1352-
return transformedResults;
1339+
const validResults = results.filter(r => r !== null).flat();
1340+
const filtered = validResults.filter(link => {
1341+
if (!link || !link.url) return false;
1342+
const lower = link.url.toLowerCase();
1343+
if (lower.endsWith('.zip')) {
1344+
console.log(`[4KHDHub] Skipping archive (zip) link: ${link.url}`);
1345+
return false;
1346+
}
1347+
return true;
1348+
});
1349+
console.log(`[4KHDHub] Post-processing: kept ${filtered.length}, skipped ${validResults.length - filtered.length} zip archive links`);
1350+
return filtered;
13531351
});
13541352
}
13551353

@@ -1841,11 +1839,15 @@ async function get4KHDHubStreams(tmdbId, type, season = null, episode = null) {
18411839
try {
18421840
const h = new URL(s.url).hostname;
18431841
acc[h] = (acc[h] || 0) + 1;
1844-
} catch {}
1842+
} catch {
1843+
// Ignore URL parsing errors
1844+
}
18451845
return acc;
18461846
}, {});
18471847
console.log('[4KHDHub] Final host distribution:', hostCounts);
1848-
} catch {}
1848+
} catch {
1849+
// Ignore URL parsing errors
1850+
}
18491851

18501852
console.log(`[4KHDHub] Returning ${streams.length} streams`);
18511853
return streams;

providers/Showbox.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2679,10 +2679,9 @@ const sortStreamsByQuality = (streams) => {
26792679

26802680
// Provider priority ordering (lower number = higher priority)
26812681
const providerSortKeys = {
2682-
'ShowBox': 1,
2683-
'Xprime.tv': 2,
2684-
'HollyMovieHD': 3,
2685-
'Soaper TV': 4,
2682+
'ShowBox': 1,
2683+
'HollyMovieHD': 2,
2684+
'Soaper TV': 3,
26862685
// Default for unknown providers
26872686
default: 99
26882687
};

providers/VidZee.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
const axios = require('axios');
22

3+
// Confirmed AES-CBC decoder for VidZee encrypted link format.
4+
// Format (after initial atob in site script): ivBase64:cipherBase64
5+
// ivBase64 -> CryptoJS Base64 parsed to WordArray (IV)
6+
// Key: UTF-8 bytes of "qrincywincyspider" padded with nulls to 32 bytes (AES-256-CBC)
7+
// Cipher: base64 ciphertext, PKCS7 padding, mode CBC.
8+
let cryptoJs; // lazy load to avoid cost if not needed
9+
function decodeVidZeeToken(token, debug) {
10+
try {
11+
if (typeof token !== 'string') return null;
12+
if (/^https?:\/\//i.test(token)) return null; // already a URL
13+
// Expect pattern base64:base64 (iv:cipher)
14+
const raw = Buffer.from(token, 'base64').toString('utf8');
15+
if (!raw.includes(':')) return null;
16+
const [ivB64, cipherB64] = raw.split(':');
17+
if (!ivB64 || !cipherB64) return null;
18+
if (!cryptoJs) cryptoJs = require('crypto-js');
19+
const iv = cryptoJs.enc.Base64.parse(ivB64.trim());
20+
const keyStr = 'qrincywincyspider';
21+
// Pad key with nulls to 32 bytes
22+
const keyUtf8 = cryptoJs.enc.Utf8.parse(keyStr.padEnd(32, '\0'));
23+
const decrypted = cryptoJs.AES.decrypt(cipherB64.trim(), keyUtf8, {
24+
iv,
25+
mode: cryptoJs.mode.CBC,
26+
padding: cryptoJs.pad.Pkcs7
27+
}).toString(cryptoJs.enc.Utf8);
28+
if (!decrypted) return null;
29+
if (!/^https?:\/\//i.test(decrypted)) {
30+
if (debug) console.log('[VidZee] decrypted but not a URL', decrypted.slice(0,80));
31+
return null;
32+
}
33+
if (debug) console.log('[VidZee] AES decoded token', { tokenSnippet: token.slice(0, 24)+'...', url: decrypted.slice(0,120) });
34+
return decrypted.trim();
35+
} catch (e) {
36+
if (debug) console.log('[VidZee] AES decode error', e.message);
37+
return null;
38+
}
39+
}
40+
341
// Removed unused parseArgs helper
442

543
const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
@@ -73,20 +111,23 @@ const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
73111
}
74112

75113
const streams = apiSources.map(sourceItem => {
76-
// Prefer sourceItem.name as label, fallback to sourceItem.type, then 'VidZee Stream'
77114
const label = sourceItem.name || sourceItem.type || 'VidZee';
78-
// Ensure quality has 'p' if it's a resolution, or keep it as is
79115
let quality = String(label).match(/^\d+$/) ? `${label}p` : label;
80-
// Normalize non-numeric or ambiguous quality labels to a baseline so they survive minQuality filter
81116
if (!/(\d{3,4})p/.test(quality.toLowerCase())) {
82117
quality = '720p';
83118
}
84119
const language = sourceItem.language || sourceItem.lang;
85-
120+
let rawLink = sourceItem.link;
121+
const debug = process.env.VIDZEE_DEBUG === '1';
122+
const decoded = decodeVidZeeToken(rawLink, debug);
123+
if (decoded && /^https?:\/\//i.test(decoded)) {
124+
if (debug) console.log('[VidZee] decoded link', { beforeSample: rawLink.slice(0,40)+'...', after: decoded.slice(0,80) });
125+
rawLink = decoded;
126+
}
86127
return {
87128
name: `VidZee Server${sr} - ${quality} - ${language}`,
88129
title: `VidZee Server${sr} - ${quality} - ${language}`,
89-
url: sourceItem.link, // Use sourceItem.link for the URL
130+
url: rawLink,
90131
quality: quality,
91132
provider: "VidZee",
92133
headers: {

0 commit comments

Comments
 (0)