refactor: 改进音乐API代理的重定向和URL请求处理
- 新增对 type=url 请求的特殊处理逻辑 - 解析JSON响应中的url字段并自动跟随 - 提取 buildHeaders() 函数统一请求头构建 - 新增 requestStream() 函数处理流式请求和重定向 - 限制最大重定向次数为5次 - 改进 Range 和 If-Range 头部传递
This commit is contained in:
@@ -311,20 +311,132 @@ function downloadFile(url, dest) {
|
|||||||
// --- Music API Proxy Function ---
|
// --- Music API Proxy Function ---
|
||||||
function proxyRequest(targetUrl, req, res) {
|
function proxyRequest(targetUrl, req, res) {
|
||||||
const parsedTarget = url.parse(targetUrl, true);
|
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 = {
|
const options = {
|
||||||
hostname: parsedTarget.hostname,
|
hostname: parsedTarget.hostname,
|
||||||
port: 443,
|
port: 443,
|
||||||
path: parsedTarget.path,
|
path: parsedTarget.path,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
headers: {
|
headers: buildHeaders()
|
||||||
'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 (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 proxyReq = https.request(options, (proxyRes) => {
|
||||||
const passThrough = () => {
|
const passThrough = () => {
|
||||||
const headers = { ...proxyRes.headers };
|
const headers = { ...proxyRes.headers };
|
||||||
@@ -457,4 +569,4 @@ function writeMetadata(inputPath, outputPath, metadata) {
|
|||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user