diff --git a/sync-server/server.js b/sync-server/server.js index 10a3fe8..49c454f 100644 --- a/sync-server/server.js +++ b/sync-server/server.js @@ -311,20 +311,132 @@ function downloadFile(url, dest) { // --- Music API Proxy Function --- function proxyRequest(targetUrl, req, res) { const parsedTarget = url.parse(targetUrl, true); - const isPicRequest = parsedTarget.query && parsedTarget.query.type === 'pic'; + const requestType = parsedTarget.query && parsedTarget.query.type; + const isPicRequest = requestType === 'pic'; + const isUrlRequest = requestType === 'url'; + const maxRedirects = 5; + + const buildHeaders = () => { + const 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-Encoding': 'identity' + }; + if (req.headers.range) headers['Range'] = req.headers.range; + if (req.headers['if-range']) headers['If-Range'] = req.headers['if-range']; + return headers; + }; + + const requestStream = (nextUrl, redirectCount = 0) => { + if (redirectCount > maxRedirects) { + res.writeHead(502, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Too many redirects' })); + return; + } + + const parsedNext = url.parse(nextUrl); + const isHttps = parsedNext.protocol === 'https:'; + const requestModule = isHttps ? https : http; + const options = { + hostname: parsedNext.hostname, + port: parsedNext.port || (isHttps ? 443 : 80), + path: parsedNext.path, + method: req.method, + headers: buildHeaders() + }; + + const proxyReq = requestModule.request(options, (proxyRes) => { + if (proxyRes.statusCode >= 300 && proxyRes.statusCode < 400 && proxyRes.headers.location) { + const resolved = url.resolve(nextUrl, proxyRes.headers.location); + proxyRes.resume(); + requestStream(resolved, redirectCount + 1); + return; + } + + const headers = { ...proxyRes.headers }; + delete headers['content-encoding']; + delete headers['transfer-encoding']; + res.writeHead(proxyRes.statusCode || 200, headers); + proxyRes.pipe(res); + }); + + proxyReq.on('error', (e) => { + console.error('[Proxy] Stream error:', e.message); + res.writeHead(502, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Proxy stream failed', message: e.message })); + }); + + if (req.method === 'POST') { + req.pipe(proxyReq); + } else { + proxyReq.end(); + } + }; const options = { hostname: parsedTarget.hostname, port: 443, path: parsedTarget.path, method: req.method, - 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-Encoding': 'identity' - } + headers: buildHeaders() }; + if (isUrlRequest) { + const proxyReq = https.request(options, (proxyRes) => { + if (proxyRes.statusCode >= 300 && proxyRes.statusCode < 400 && proxyRes.headers.location) { + const resolved = url.resolve(targetUrl, proxyRes.headers.location); + proxyRes.resume(); + requestStream(resolved, 1); + return; + } + + const contentType = proxyRes.headers['content-type'] || ''; + const shouldInspectBody = contentType.includes('application/json') || contentType.startsWith('text/'); + + if (shouldInspectBody) { + let body = ''; + proxyRes.setEncoding('utf8'); + proxyRes.on('data', chunk => body += chunk); + proxyRes.on('end', () => { + try { + const parsedBody = JSON.parse(body); + if (parsedBody && parsedBody.url) { + requestStream(parsedBody.url, 1); + return; + } + } catch (e) { + // fall through to return original body + } + + res.writeHead(proxyRes.statusCode || 200, { + 'Content-Type': proxyRes.headers['content-type'] || 'application/json' + }); + res.end(body); + }); + return; + } + + const headers = { ...proxyRes.headers }; + delete headers['content-encoding']; + delete headers['transfer-encoding']; + res.writeHead(proxyRes.statusCode || 200, headers); + proxyRes.pipe(res); + }); + + proxyReq.on('error', (e) => { + console.error('[Proxy] Request error:', e.message); + res.writeHead(502, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Proxy request failed', message: e.message })); + }); + + if (req.method === 'POST') { + req.pipe(proxyReq); + } else { + proxyReq.end(); + } + return; + } + const proxyReq = https.request(options, (proxyRes) => { const passThrough = () => { const headers = { ...proxyRes.headers }; @@ -457,4 +569,4 @@ function writeMetadata(inputPath, outputPath, metadata) { reject(err); }); }); -} \ No newline at end of file +}