diff --git a/backend/package.json b/backend/package.json index c7c6deb..f5e985a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.130", + "version": "2.14.131", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/restful/download.js b/backend/src/restful/download.js index 6a9e4c8..8c81663 100644 --- a/backend/src/restful/download.js +++ b/backend/src/restful/download.js @@ -20,7 +20,7 @@ async function downloadSubscription(req, res) { req.query.target || getPlatformFromHeaders(req.headers) || 'JSON'; $.info(`正在下载订阅:${name}`); - let { url, ua, content, mergeSources } = req.query; + let { url, ua, content, mergeSources, ignoreFailedRemoteSub } = req.query; if (url) { url = decodeURIComponent(url); $.info(`指定远程订阅 URL: ${url}`); @@ -37,6 +37,10 @@ async function downloadSubscription(req, res) { mergeSources = decodeURIComponent(mergeSources); $.info(`指定合并来源: ${mergeSources}`); } + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { + ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub); + $.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`); + } const allSubs = $.read(SUBS_KEY); const sub = findByName(allSubs, name); @@ -50,6 +54,7 @@ async function downloadSubscription(req, res) { ua, content, mergeSources, + ignoreFailedRemoteSub, }); if (sub.source !== 'local' || url) { @@ -116,12 +121,20 @@ async function downloadCollection(req, res) { $.info(`正在下载组合订阅:${name}`); + let { ignoreFailedRemoteSub } = req.query; + + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { + ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub); + $.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`); + } + if (collection) { try { const output = await produceArtifact({ type: 'collection', name, platform, + ignoreFailedRemoteSub, }); // forward flow header from the first subscription in this collection diff --git a/backend/src/restful/preview.js b/backend/src/restful/preview.js index 980b0aa..b77e4d4 100644 --- a/backend/src/restful/preview.js +++ b/backend/src/restful/preview.js @@ -22,24 +22,31 @@ async function compareSub(req, res) { ) { content = sub.content; } else { - try { - content = await Promise.all( - sub.url - .split(/[\r\n]+/) - .map((i) => i.trim()) - .filter((i) => i.length) - .map((url) => download(url, sub.ua)), + const errors = {}; + content = await Promise.all( + sub.url + .split(/[\r\n]+/) + .map((i) => i.trim()) + .filter((i) => i.length) + .map(async (url) => { + try { + return await download(url, sub.ua); + } catch (err) { + errors[url] = err; + $.error( + `订阅 ${sub.name} 的远程订阅 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), + ); + + if (!sub.ignoreFailedRemoteSub && Object.keys(errors).length > 0) { + throw new Error( + `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, ); - } catch (err) { - failed( - res, - new NetworkError( - 'FAILED_TO_DOWNLOAD_RESOURCE', - '无法下载远程资源', - `Reason: ${err}`, - ), - ); - return; } if (sub.mergeSources === 'localFirst') { content.unshift(sub.content); @@ -87,69 +94,95 @@ async function compareCollection(req, res) { const collection = req.body; const subnames = collection.subscriptions; const results = {}; - let hasError; + const errors = {}; await Promise.all( subnames.map(async (name) => { - if (!hasError) { - const sub = findByName(allSubs, name); - try { - let raw; - if ( - sub.source === 'local' && - !['localFirst', 'remoteFirst'].includes( - sub.mergeSources, - ) - ) { - raw = sub.content; - } else { - raw = await Promise.all( - sub.url - .split(/[\r\n]+/) - .map((i) => i.trim()) - .filter((i) => i.length) - .map((url) => download(url, sub.ua)), - ); - if (sub.mergeSources === 'localFirst') { - raw.unshift(sub.content); - } else if (sub.mergeSources === 'remoteFirst') { - raw.push(sub.content); - } - } - // parse proxies - let currentProxies = (Array.isArray(raw) ? raw : [raw]) - .map((i) => ProxyUtils.parse(i)) - .flat(); - - currentProxies.forEach((proxy) => { - proxy.subName = sub.name; - proxy.collectionName = collection.name; - }); - - // apply processors - currentProxies = await ProxyUtils.process( - currentProxies, - sub.process || [], - 'JSON', - { [sub.name]: sub, _collection: collection }, + const sub = findByName(allSubs, name); + try { + let raw; + if ( + sub.source === 'local' && + !['localFirst', 'remoteFirst'].includes( + sub.mergeSources, + ) + ) { + raw = sub.content; + } else { + const errors = {}; + raw = await Promise.all( + sub.url + .split(/[\r\n]+/) + .map((i) => i.trim()) + .filter((i) => i.length) + .map(async (url) => { + try { + return await download(url, sub.ua); + } catch (err) { + errors[url] = err; + $.error( + `订阅 ${sub.name} 的远程订阅 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), ); - results[name] = currentProxies; - } catch (err) { - if (!hasError) { - hasError = true; - failed( - res, - new InternalServerError( - 'PROCESS_FAILED', - `处理子订阅 ${name} 失败`, - `Reason: ${err}`, - ), + if ( + !sub.ignoreFailedRemoteSub && + Object.keys(errors).length > 0 + ) { + throw new Error( + `订阅 ${sub.name} 的远程订阅 ${Object.keys( + errors, + ).join(', ')} 发生错误, 请查看日志`, ); } + if (sub.mergeSources === 'localFirst') { + raw.unshift(sub.content); + } else if (sub.mergeSources === 'remoteFirst') { + raw.push(sub.content); + } } + // parse proxies + let currentProxies = (Array.isArray(raw) ? raw : [raw]) + .map((i) => ProxyUtils.parse(i)) + .flat(); + + currentProxies.forEach((proxy) => { + proxy.subName = sub.name; + proxy.collectionName = collection.name; + }); + + // apply processors + currentProxies = await ProxyUtils.process( + currentProxies, + sub.process || [], + 'JSON', + { [sub.name]: sub, _collection: collection }, + ); + results[name] = currentProxies; + } catch (err) { + errors[name] = err; + + $.error( + `❌ 处理组合订阅中的子订阅: ${ + sub.name + }时出现错误:${err}!进度--${ + 100 * (processed / subnames.length).toFixed(1) + }%`, + ); } }), ); - if (hasError) return; + if ( + !collection.ignoreFailedRemoteSub && + Object.keys(errors).length > 0 + ) { + throw new Error( + `组合订阅 ${collection.name} 中的子订阅 ${Object.keys( + errors, + ).join(', ')} 发生错误, 请查看日志`, + ); + } // merge proxies with the original order const original = Array.prototype.concat.apply( [], diff --git a/backend/src/restful/sync.js b/backend/src/restful/sync.js index c80fe39..4ae0a14 100644 --- a/backend/src/restful/sync.js +++ b/backend/src/restful/sync.js @@ -30,6 +30,7 @@ async function produceArtifact({ ua, content, mergeSources, + ignoreFailedRemoteSub, }) { platform = platform || 'JSON'; @@ -40,13 +41,35 @@ async function produceArtifact({ if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) { raw = content; } else if (url) { + const errors = {}; raw = await Promise.all( url .split(/[\r\n]+/) .map((i) => i.trim()) .filter((i) => i.length) - .map((url) => download(url, ua)), + .map(async (url) => { + try { + return await download(url, ua || sub.ua); + } catch (err) { + errors[url] = err; + $.error( + `订阅 ${sub.name} 的远程订阅 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), ); + let subIgnoreFailedRemoteSub = sub.ignoreFailedRemoteSub; + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { + subIgnoreFailedRemoteSub = ignoreFailedRemoteSub; + } + if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) { + throw new Error( + `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, + ); + } if (mergeSources === 'localFirst') { raw.unshift(content); } else if (mergeSources === 'remoteFirst') { @@ -58,13 +81,35 @@ async function produceArtifact({ ) { raw = sub.content; } else { + const errors = {}; raw = await Promise.all( sub.url .split(/[\r\n]+/) .map((i) => i.trim()) .filter((i) => i.length) - .map((url) => download(url, sub.ua)), + .map(async (url) => { + try { + return await download(url, ua || sub.ua); + } catch (err) { + errors[url] = err; + $.error( + `订阅 ${sub.name} 的远程订阅 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), ); + let subIgnoreFailedRemoteSub = sub.ignoreFailedRemoteSub; + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { + subIgnoreFailedRemoteSub = ignoreFailedRemoteSub; + } + if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) { + throw new Error( + `订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, + ); + } if (sub.mergeSources === 'localFirst') { raw.unshift(sub.content); } else if (sub.mergeSources === 'remoteFirst') { @@ -131,13 +176,34 @@ async function produceArtifact({ ) { raw = sub.content; } else { + const errors = {}; raw = await await Promise.all( sub.url .split(/[\r\n]+/) .map((i) => i.trim()) .filter((i) => i.length) - .map((url) => download(url, sub.ua)), + .map(async (url) => { + try { + return await download(url, sub.ua); + } catch (err) { + errors[url] = err; + $.error( + `订阅 ${sub.name} 的远程订阅 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), ); + if ( + !sub.ignoreFailedRemoteSub && + Object.keys(errors).length > 0 + ) { + throw new Error( + `订阅 ${sub.name} 的远程订阅 ${Object.keys( + errors, + ).join(', ')} 发生错误, 请查看日志`, + ); + } if (sub.mergeSources === 'localFirst') { raw.unshift(sub.content); } else if (sub.mergeSources === 'remoteFirst') { @@ -174,15 +240,21 @@ async function produceArtifact({ $.error( `❌ 处理组合订阅中的子订阅: ${ sub.name - }时出现错误:${err},该订阅已被跳过!进度--${ + }时出现错误:${err}!进度--${ 100 * (processed / subnames.length).toFixed(1) }%`, ); } }), ); - - if (Object.keys(errors).length > 0) { + let collectionIgnoreFailedRemoteSub = collection.ignoreFailedRemoteSub; + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { + collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub; + } + if ( + !collectionIgnoreFailedRemoteSub && + Object.keys(errors).length > 0 + ) { throw new Error( `组合订阅 ${name} 中的子订阅 ${Object.keys(errors).join( ', ',