mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-07-26 16:32:07 +00:00
Corrections de vulnérabilités sur les commentaires (IDOR) Rajouts de ratelimit sur les commentaires Correction de pas de son sur hlsplayer Livetvplayer détecte le type du flux automatiquement Amélioration du scraper darkiworld (très chiant) Correction du détection de l'extension Correction de la vérification du VIP sur server.py (proxy) Correction du scraper seekstreaming proxy Nouvelle url streamonsport et coflix Correction du scraper fstream (get seasons) car la route fonctionne plus sur leur site Correction du scraper francetv Ajout de hydrackerBatch (c'étais pour des tests)
313 lines
12 KiB
JavaScript
313 lines
12 KiB
JavaScript
/**
|
|
* Proxy route.
|
|
* Extracted from server.js -- raw HTML proxy with domain-specific headers.
|
|
* Mount point: app.use('/proxy', router)
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const axios = require('axios');
|
|
const crypto = require('crypto');
|
|
const path = require('path');
|
|
const fsp = require('fs').promises;
|
|
|
|
const { safeWriteJsonFile } = require('../utils/safeFile');
|
|
const { CACHE_DIR } = require('../utils/cacheManager');
|
|
|
|
// === Routes ===
|
|
|
|
// GET /* - Proxy route for raw HTML (replaces old /proxy route)
|
|
router.get(/^\/(.*)/, async (req, res) => {
|
|
try {
|
|
// Extract the target URL after /
|
|
let targetUrl = req.url.slice(1); // Remove leading '/'
|
|
|
|
// Decode the URL recursively if it's encoded (handles double/triple encoding)
|
|
try {
|
|
let decoded = targetUrl;
|
|
let previousDecoded = '';
|
|
// Keep decoding until the URL doesn't change anymore (handles multiple encodings)
|
|
while (decoded !== previousDecoded) {
|
|
previousDecoded = decoded;
|
|
try {
|
|
decoded = decodeURIComponent(decoded);
|
|
} catch (e) {
|
|
// If decoding fails, break the loop
|
|
break;
|
|
}
|
|
}
|
|
targetUrl = decoded;
|
|
} catch (decodeError) {
|
|
// If decoding fails, use the original URL
|
|
console.warn('Failed to decode URL:', targetUrl, decodeError.message);
|
|
}
|
|
|
|
// Fix recursive proxying issue - remove any localhost/proxy/ patterns
|
|
const localhostProxyPattern = /localhost(:\d+)?\/proxy\//i;
|
|
if (localhostProxyPattern.test(targetUrl)) {
|
|
console.log(`Detected recursive proxy request in: ${targetUrl}`);
|
|
targetUrl = targetUrl.replace(localhostProxyPattern, '');
|
|
console.log(`Corrected to: ${targetUrl}`);
|
|
}
|
|
|
|
// Check if the URL starts with http(s)://, if not, prepend https://
|
|
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
|
targetUrl = 'https://' + targetUrl;
|
|
}
|
|
|
|
// === TVDIRECT SPECIAL HANDLING ===
|
|
if (targetUrl.includes('tvdirect.ddns')) {
|
|
try {
|
|
const cacheKey = crypto.createHash('md5').update(targetUrl).digest('hex');
|
|
// Ensure directory exists
|
|
await fsp.mkdir(CACHE_DIR.TVDIRECT, { recursive: true });
|
|
const cacheFile = path.join(CACHE_DIR.TVDIRECT, `${cacheKey}.json`);
|
|
|
|
// Helper to fetch external resource
|
|
const fetchTvDirect = async () => {
|
|
console.log(`[TVDIRECT] Fetching ${targetUrl}`);
|
|
const resp = await axios.get(targetUrl, {
|
|
headers: {
|
|
'User-Agent': 'stremio',
|
|
'Accept': '*/*'
|
|
},
|
|
responseType: 'text',
|
|
timeout: 15000
|
|
});
|
|
return {
|
|
data: resp.data,
|
|
headers: resp.headers,
|
|
timestamp: Date.now()
|
|
};
|
|
};
|
|
|
|
// Try to read from cache
|
|
let cachedEntry = null;
|
|
try {
|
|
const fileContent = await fsp.readFile(cacheFile, 'utf8');
|
|
cachedEntry = JSON.parse(fileContent);
|
|
} catch (e) { /* No cache or invalid */ }
|
|
|
|
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
const now = Date.now();
|
|
|
|
if (cachedEntry && cachedEntry.data) {
|
|
// Serve cached response
|
|
if (cachedEntry.headers && cachedEntry.headers['content-type']) {
|
|
res.setHeader('Content-Type', cachedEntry.headers['content-type']);
|
|
}
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.send(cachedEntry.data);
|
|
|
|
// Check if stale for background update
|
|
if (now - cachedEntry.timestamp > CACHE_TTL) {
|
|
console.log(`[TVDIRECT] Cache stale for ${targetUrl}, updating in background...`);
|
|
// Background update (no await)
|
|
fetchTvDirect().then(async (newData) => {
|
|
await safeWriteJsonFile(cacheFile, newData);
|
|
console.log(`[TVDIRECT] Background update success for ${targetUrl}`);
|
|
}).catch(err => console.error(`[TVDIRECT] Background update failed: ${err.message}`));
|
|
}
|
|
return; // Exit route
|
|
}
|
|
|
|
// No cache, fetch synchronously
|
|
const newData = await fetchTvDirect();
|
|
await safeWriteJsonFile(cacheFile, newData);
|
|
|
|
if (newData.headers && newData.headers['content-type']) {
|
|
res.setHeader('Content-Type', newData.headers['content-type']);
|
|
}
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.send(newData.data);
|
|
return; // Exit route
|
|
} catch (error) {
|
|
console.error(`[TVDIRECT] Error handling request:`, error);
|
|
return res.status(502).json({ error: 'TVDirect proxy failed', details: error.message });
|
|
}
|
|
}
|
|
|
|
// Préparer les headers à forwarder
|
|
let refererOrigin;
|
|
let targetHost;
|
|
try {
|
|
const urlObj = new URL(targetUrl);
|
|
refererOrigin = urlObj.origin;
|
|
targetHost = urlObj.host;
|
|
} catch (urlError) {
|
|
// Si l'URL est invalide, utiliser une valeur par défaut
|
|
console.warn('Invalid URL for referer:', targetUrl, urlError.message);
|
|
refererOrigin = 'https://vmwesa.online';
|
|
targetHost = 'vmwesa.online';
|
|
}
|
|
|
|
// Headers spécifiques pour vmwesa/vidmoly et certains CDN (ex: getromes.space)
|
|
const isVmwesa = /vmwesa\.online|vidmoly|getromes\.space/i.test(targetUrl);
|
|
|
|
// Headers spécifiques pour dropcdn
|
|
const isDropcdn = /dropcdn/i.test(targetUrl);
|
|
|
|
// Headers spécifiques pour serversicuro
|
|
const isServersicuro = /serversicuro/i.test(targetUrl);
|
|
|
|
// Headers spécifiques pour coflix
|
|
const isCoflix = /coflix\.(bet|si|boo|io)/i.test(targetUrl);
|
|
|
|
const headers = isCoflix ? {
|
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
'Accept-Language': 'fr-FR,fr;q=0.7',
|
|
'Cache-Control': 'no-cache',
|
|
'Pragma': 'no-cache',
|
|
'Priority': 'u=0, i',
|
|
'Referer': 'https://coflix.date/',
|
|
'Sec-CH-UA': '"Brave";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
|
|
'Sec-CH-UA-Mobile': '?0',
|
|
'Sec-CH-UA-Platform': '"Windows"',
|
|
'Sec-Fetch-Dest': 'iframe',
|
|
'Sec-Fetch-Mode': 'navigate',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-Fetch-Storage-Access': 'none',
|
|
'Sec-GPC': '1',
|
|
'Upgrade-Insecure-Requests': '1',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
|
|
} : isVmwesa ? {
|
|
'Accept': 'application/vnd.apple.mpegurl,application/x-mpegURL,*/*',
|
|
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
'Accept-Language': 'fr-FR,fr;q=0.8',
|
|
'Connection': 'keep-alive',
|
|
'Host': targetHost,
|
|
'Origin': 'https://vidmoly.net',
|
|
'Referer': 'https://vidmoly.net/',
|
|
'Sec-CH-UA': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"',
|
|
'Sec-CH-UA-Mobile': '?0',
|
|
'Sec-CH-UA-Platform': '"Windows"',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-GPC': '1',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
|
} : isDropcdn ? {
|
|
'Accept': '*/*',
|
|
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
'Connection': 'keep-alive',
|
|
'Host': targetHost,
|
|
'Origin': 'https://dropload.tv',
|
|
'Referer': 'https://dropload.tv/',
|
|
'Sec-CH-UA': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"',
|
|
'Sec-CH-UA-Mobile': '?0',
|
|
'Sec-CH-UA-Platform': '"Windows"',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-GPC': '1',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
|
} : isServersicuro ? {
|
|
'Accept': '*/*',
|
|
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
'Accept-Language': 'fr-FR,fr;q=0.8',
|
|
'Connection': 'keep-alive',
|
|
'Host': targetHost,
|
|
'Origin': 'https://supervideo.cc',
|
|
'Referer': 'https://supervideo.cc/',
|
|
'Sec-CH-UA': '"Not;A=Brand";v="99", "Brave";v="139", "Chromium";v="139"',
|
|
'Sec-CH-UA-Mobile': '?0',
|
|
'Sec-CH-UA-Platform': '"Windows"',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-GPC': '1',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
|
} : {
|
|
'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
'Referer': refererOrigin,
|
|
'Accept': req.headers['accept'] || '*/*',
|
|
'Accept-Encoding': req.headers['accept-encoding'] || 'gzip, deflate, br',
|
|
'Connection': 'keep-alive',
|
|
};
|
|
// Forward Range header if present (important for video streaming)
|
|
if (req.headers['range']) {
|
|
headers['Range'] = req.headers['range'];
|
|
}
|
|
|
|
// Détecter si c'est un .m3u8 (playlist HLS)
|
|
const isM3U8 = targetUrl.toLowerCase().includes('.m3u8') || (req.headers.accept && req.headers.accept.includes('application/vnd.apple.mpegurl'));
|
|
|
|
// Faire la requête distante avec l'agent de streaming pour le proxy
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: targetUrl,
|
|
responseType: isM3U8 ? 'text' : 'stream',
|
|
headers,
|
|
timeout: 30000,
|
|
maxRedirects: 5,
|
|
validateStatus: status => true, // On gère nous-même les codes d'erreur
|
|
decompress: true // Disable automatic decompression for better performance
|
|
});
|
|
|
|
// Copier les headers utiles
|
|
Object.entries(response.headers).forEach(([key, value]) => {
|
|
// Éviter certains headers problématiques
|
|
if (!['transfer-encoding', 'connection', 'content-encoding'].includes(key.toLowerCase())) {
|
|
res.setHeader(key, value);
|
|
}
|
|
});
|
|
|
|
// CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS', 'DELETE');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Range');
|
|
|
|
// Si c'est un .m3u8, retourner le contenu original sans modification
|
|
if (isM3U8 && typeof response.data === 'string') {
|
|
const contentType = response.headers['content-type'] || '';
|
|
const bodyText = response.data;
|
|
|
|
// Retourner le contenu M3U8 original sans modification
|
|
if (contentType) res.setHeader('content-type', contentType);
|
|
res.status(response.status).send(bodyText);
|
|
return;
|
|
}
|
|
|
|
// Gestion du code de retour (206 pour Range, sinon code d'origine)
|
|
res.status(response.status);
|
|
|
|
// Détruire le stream upstream si le client se déconnecte pour éviter les fuites mémoire
|
|
const upstream = response.data;
|
|
res.on('close', () => {
|
|
if (!upstream.destroyed) {
|
|
upstream.destroy();
|
|
}
|
|
});
|
|
|
|
upstream.on('error', (err) => {
|
|
console.error('Proxy upstream error:', err.message);
|
|
if (!res.headersSent) {
|
|
res.status(502).json({ error: 'Upstream stream error', message: err.message });
|
|
}
|
|
if (!upstream.destroyed) upstream.destroy();
|
|
});
|
|
|
|
upstream.pipe(res);
|
|
} catch (error) {
|
|
console.error('Proxy error:', error.message);
|
|
if (error.response) {
|
|
res.status(error.response.status).json({
|
|
error: `Target server responded with ${error.response.status}`,
|
|
message: error.message
|
|
});
|
|
} else if (error.request) {
|
|
res.status(504).json({
|
|
error: 'Gateway Timeout',
|
|
message: 'No response received from target server'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
error: 'Internal Server Error',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|