diff --git a/backend/package.json b/backend/package.json index bb33ea4..81178bc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.146", + "version": "2.14.147", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/restful/file.js b/backend/src/restful/file.js index 29a3738..d589a6e 100644 --- a/backend/src/restful/file.js +++ b/backend/src/restful/file.js @@ -2,8 +2,12 @@ import { deleteByName, findByName, updateByName } from '@/utils/database'; import { FILES_KEY } from '@/constants'; import { failed, success } from '@/restful/response'; import $ from '@/core/app'; -import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors'; -import { ProxyUtils } from '@/core/proxy-utils'; +import { + RequestInvalidError, + ResourceNotFoundError, + InternalServerError, +} from '@/restful/errors'; +import { produceArtifact } from '@/restful/sync'; export default function register($app) { if (!$.read(FILES_KEY)) $.write([], FILES_KEY); @@ -44,22 +48,72 @@ function createFile(req, res) { async function getFile(req, res) { let { name } = req.params; name = decodeURIComponent(name); + + $.info(`正在下载文件:${name}`); + let { url, ua, content, mergeSources, ignoreFailedRemoteFile } = req.query; + if (url) { + url = decodeURIComponent(url); + $.info(`指定远程文件 URL: ${url}`); + } + if (ua) { + ua = decodeURIComponent(ua); + $.info(`指定远程文件 User-Agent: ${ua}`); + } + if (content) { + content = decodeURIComponent(content); + $.info(`指定本地文件: ${content}`); + } + if (mergeSources) { + mergeSources = decodeURIComponent(mergeSources); + $.info(`指定合并来源: ${mergeSources}`); + } + if (ignoreFailedRemoteFile != null && ignoreFailedRemoteFile !== '') { + ignoreFailedRemoteFile = decodeURIComponent(ignoreFailedRemoteFile); + $.info(`指定忽略失败的远程文件: ${ignoreFailedRemoteFile}`); + } + const allFiles = $.read(FILES_KEY); const file = findByName(allFiles, name); if (file) { - let content = file.content ?? ''; - content = await ProxyUtils.process(content, file.process || []); - res.set('Content-Type', 'text/plain; charset=utf-8').send( - content ?? '', - ); + try { + const output = await produceArtifact({ + type: 'file', + name, + url, + ua, + content, + mergeSources, + ignoreFailedRemoteFile, + }); + + res.set('Content-Type', 'text/plain; charset=utf-8').send( + output ?? '', + ); + } catch (err) { + $.notify( + `🌍 Sub-Store 下载文件失败`, + `❌ 无法下载文件:${name}!`, + `🤔 原因:${err.message ?? err}`, + ); + $.error(err.message ?? err); + failed( + res, + new InternalServerError( + 'INTERNAL_SERVER_ERROR', + `Failed to download file: ${name}`, + `Reason: ${err.message ?? err}`, + ), + ); + } } else { + $.notify(`🌍 Sub-Store 下载文件失败`, `❌ 未找到文件:${name}!`); failed( res, new ResourceNotFoundError( - `FILE_NOT_FOUND`, - `File ${name} does not exist`, - 404, + 'RESOURCE_NOT_FOUND', + `File ${name} does not exist!`, ), + 404, ); } } diff --git a/backend/src/restful/preview.js b/backend/src/restful/preview.js index 04be134..d287262 100644 --- a/backend/src/restful/preview.js +++ b/backend/src/restful/preview.js @@ -14,12 +14,63 @@ export default function register($app) { async function previewFile(req, res) { try { - let { content = '', process = [] } = req.body; + const file = req.body; + let content; + if ( + file.source === 'local' && + !['localFirst', 'remoteFirst'].includes(file.mergeSources) + ) { + content = file.content; + } else { + const errors = {}; + content = await Promise.all( + file.url + .split(/[\r\n]+/) + .map((i) => i.trim()) + .filter((i) => i.length) + .map(async (url) => { + try { + return await download(url, file.ua); + } catch (err) { + errors[url] = err; + $.error( + `文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), + ); - const processed = await ProxyUtils.process(content, process || []); + if ( + !file.ignoreFailedRemoteFile && + Object.keys(errors).length > 0 + ) { + throw new Error( + `文件 ${file.name} 的远程文件 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, + ); + } + if (file.mergeSources === 'localFirst') { + content.unshift(file.content); + } else if (file.mergeSources === 'remoteFirst') { + content.push(file.content); + } + } + // parse proxies + const original = (Array.isArray(content) ? content : [content]) + .flat() + .filter((i) => i != null && i !== '') + .join('\n'); + + // apply processors + const processed = await ProxyUtils.process( + original, + file.process || [], + ); // produce - success(res, { original: content, processed }); + success(res, { original, processed }); } catch (err) { $.error(err.message ?? err); failed( diff --git a/backend/src/restful/sync.js b/backend/src/restful/sync.js index e1f137d..3f29a91 100644 --- a/backend/src/restful/sync.js +++ b/backend/src/restful/sync.js @@ -32,6 +32,7 @@ async function produceArtifact({ content, mergeSources, ignoreFailedRemoteSub, + ignoreFailedRemoteFile, }) { platform = platform || 'JSON'; @@ -332,7 +333,95 @@ async function produceArtifact({ const allFiles = $.read(FILES_KEY); const file = findByName(allFiles, name); if (!file) throw new Error(`找不到文件 ${name}`); - let content = file.content ?? ''; + let raw; + 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(async (url) => { + try { + return await download(url, ua || file.ua); + } catch (err) { + errors[url] = err; + $.error( + `文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), + ); + let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile; + if ( + ignoreFailedRemoteFile != null && + ignoreFailedRemoteFile !== '' + ) { + fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile; + } + if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) { + throw new Error( + `文件 ${file.name} 的远程文件 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, + ); + } + if (mergeSources === 'localFirst') { + raw.unshift(content); + } else if (mergeSources === 'remoteFirst') { + raw.push(content); + } + } else if ( + file.source === 'local' && + !['localFirst', 'remoteFirst'].includes(file.mergeSources) + ) { + raw = file.content; + } else { + const errors = {}; + raw = await Promise.all( + file.url + .split(/[\r\n]+/) + .map((i) => i.trim()) + .filter((i) => i.length) + .map(async (url) => { + try { + return await download(url, ua || file.ua); + } catch (err) { + errors[url] = err; + $.error( + `文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`, + ); + return ''; + } + }), + ); + let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile; + if ( + ignoreFailedRemoteFile != null && + ignoreFailedRemoteFile !== '' + ) { + fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile; + } + if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) { + throw new Error( + `文件 ${file.name} 的远程文件 ${Object.keys(errors).join( + ', ', + )} 发生错误, 请查看日志`, + ); + } + if (file.mergeSources === 'localFirst') { + raw.unshift(file.content); + } else if (file.mergeSources === 'remoteFirst') { + raw.push(file.content); + } + } + let content = (Array.isArray(raw) ? raw : [raw]) + .flat() + .filter((i) => i != null && i !== '') + .join('\n'); content = await ProxyUtils.process(content, file.process || []); return content ?? ''; }