mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 09:19:00 +08:00
feat: 文件支持远程/合并, /api/file/name
接口支持参数覆盖
This commit is contained in:
parent
c059296224
commit
5915416232
@ -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": {
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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 ?? '';
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user