Files
party-mix-app/server.js

185 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const url = require('url');
const PORT = 3000;
function proxyRequest(targetUrl, res, extraHeaders = {}) {
const parsedTarget = new URL(targetUrl);
const options = {
hostname: parsedTarget.hostname,
path: parsedTarget.pathname + parsedTarget.search,
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': '*/*',
'Accept-Language': 'ru-RU,ru;q=0.9',
'Referer': 'https://rus.hitmotop.com/',
'Origin': 'https://rus.hitmotop.com',
...extraHeaders
}
};
const req = https.request(options, (proxyRes) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
// Пробрасываем важные заголовки
const ct = proxyRes.headers['content-type'];
if (ct) res.setHeader('Content-Type', ct);
const cl = proxyRes.headers['content-length'];
if (cl) res.setHeader('Content-Length', cl);
const cr = proxyRes.headers['content-range'];
if (cr) res.setHeader('Content-Range', cr);
const ar = proxyRes.headers['accept-ranges'];
if (ar) res.setHeader('Accept-Ranges', ar);
res.statusCode = proxyRes.statusCode;
proxyRes.pipe(res);
});
req.on('error', (e) => {
res.statusCode = 500;
res.end('Proxy error: ' + e.message);
});
req.end();
}
const server = http.createServer((req, res) => {
const parsed = url.parse(req.url, true);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') { res.end(); return; }
// /img?url=... — проксируем обложки альбомов
if (parsed.pathname === '/img') {
const imgurl = parsed.query.url || '';
if (!imgurl.startsWith('http')) { res.statusCode = 400; res.end('Bad url'); return; }
const pu = new URL(imgurl);
const reqOpts = {
hostname: pu.hostname,
path: pu.pathname + pu.search,
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://rus.hitmotop.com/',
}
};
const pr = https.request(reqOpts, (proxyRes) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'public, max-age=86400');
const ct = proxyRes.headers['content-type'] || 'image/jpeg';
res.setHeader('Content-Type', ct);
res.statusCode = proxyRes.statusCode;
proxyRes.pipe(res);
});
pr.on('error', () => { res.statusCode = 500; res.end(); });
pr.end();
return;
}
// /search?q=... — поиск треков
if (parsed.pathname === '/search') {
const q = parsed.query.q || '';
proxyRequest(`https://rus.hitmotop.com/search?q=${encodeURIComponent(q)}`, res);
return;
}
// /mp3?url=... — проксируем MP3 файл с правильным Referer
if (parsed.pathname === '/mp3') {
const mp3url = parsed.query.url || '';
console.log(`\n[MP3] Запрос: ${mp3url}`);
if (!mp3url.startsWith('https://rus.hitmotop.com/') && !mp3url.startsWith('https://hitmotop.com/')) {
console.log('[MP3] ОТКЛОНЁН — не hitmotop URL');
res.statusCode = 403;
res.end('Only hitmotop URLs allowed');
return;
}
// Поддержка Range requests для перемотки
const rangeHeader = req.headers['range'];
if (rangeHeader) console.log(`[MP3] Range: ${rangeHeader}`);
function fetchMp3(targetUrl, redirectCount) {
if (redirectCount > 5) {
console.log('[MP3] Слишком много редиректов!');
res.statusCode = 500;
res.end('Too many redirects');
return;
}
const parsedUrl = new URL(targetUrl);
const reqOptions = {
hostname: parsedUrl.hostname,
path: parsedUrl.pathname + parsedUrl.search,
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'audio/mpeg, audio/*, */*',
'Accept-Language': 'ru-RU,ru;q=0.9',
'Referer': 'https://rus.hitmotop.com/',
'Origin': 'https://rus.hitmotop.com',
...(rangeHeader ? { 'Range': rangeHeader } : {})
}
};
const proxyReq = https.request(reqOptions, (proxyRes) => {
console.log(`[MP3] HTTP ${proxyRes.statusCode}${targetUrl.substring(0,80)}`);
console.log(`[MP3] Content-Type: ${proxyRes.headers['content-type']}`);
// Следуем за редиректом
if ((proxyRes.statusCode === 301 || proxyRes.statusCode === 302 || proxyRes.statusCode === 307 || proxyRes.statusCode === 308) && proxyRes.headers['location']) {
const location = proxyRes.headers['location'];
const nextUrl = location.startsWith('http') ? location : `https://${parsedUrl.hostname}${location}`;
console.log(`[MP3] Редирект → ${nextUrl}`);
proxyRes.resume(); // дочитываем тело чтобы освободить соединение
fetchMp3(nextUrl, redirectCount + 1);
return;
}
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'audio/mpeg');
['content-length','content-range','accept-ranges'].forEach(h => {
if (proxyRes.headers[h]) res.setHeader(h, proxyRes.headers[h]);
});
res.statusCode = proxyRes.statusCode;
proxyRes.pipe(res);
proxyRes.on('end', () => console.log('[MP3] ✓ Передача завершена'));
});
proxyReq.on('error', (e) => {
console.log(`[MP3] ОШИБКА: ${e.message}`);
if (!res.headersSent) {
res.statusCode = 500;
res.end('Proxy error: ' + e.message);
}
});
proxyReq.end();
}
fetchMp3(mp3url, 0);
return;
}
// / или /index.html — отдаём приложение
if (parsed.pathname === '/' || parsed.pathname === '/index.html') {
const filePath = path.join(__dirname, 'index.html');
fs.readFile(filePath, (err, data) => {
if (err) { res.statusCode = 404; res.end('Not found'); return; }
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data);
});
return;
}
res.statusCode = 404;
res.end('Not found');
});
server.listen(PORT, () => {
console.log('\n\x1b[32m🎉 Party Mix запущен!\x1b[0m');
console.log(`\nОткройте в браузере: \x1b[36mhttp://localhost:${PORT}\x1b[0m\n`);
});