mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-11 15:29:01 +08:00
feat: 文件支持远程/合并, /api/file/name
接口支持参数覆盖
This commit is contained in:
parent
c059296224
commit
5915416232
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.146",
|
"version": "2.14.147",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -2,8 +2,12 @@ import { deleteByName, findByName, updateByName } from '@/utils/database';
|
|||||||
import { FILES_KEY } from '@/constants';
|
import { FILES_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
import {
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
RequestInvalidError,
|
||||||
|
ResourceNotFoundError,
|
||||||
|
InternalServerError,
|
||||||
|
} from '@/restful/errors';
|
||||||
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
@ -44,22 +48,72 @@ function createFile(req, res) {
|
|||||||
async function getFile(req, res) {
|
async function getFile(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
name = decodeURIComponent(name);
|
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 allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (file) {
|
if (file) {
|
||||||
let content = file.content ?? '';
|
try {
|
||||||
content = await ProxyUtils.process(content, file.process || []);
|
const output = await produceArtifact({
|
||||||
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
type: 'file',
|
||||||
content ?? '',
|
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 {
|
} else {
|
||||||
|
$.notify(`🌍 Sub-Store 下载文件失败`, `❌ 未找到文件:${name}!`);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new ResourceNotFoundError(
|
new ResourceNotFoundError(
|
||||||
`FILE_NOT_FOUND`,
|
'RESOURCE_NOT_FOUND',
|
||||||
`File ${name} does not exist`,
|
`File ${name} does not exist!`,
|
||||||
404,
|
|
||||||
),
|
),
|
||||||
|
404,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,63 @@ export default function register($app) {
|
|||||||
|
|
||||||
async function previewFile(req, res) {
|
async function previewFile(req, res) {
|
||||||
try {
|
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
|
// produce
|
||||||
success(res, { original: content, processed });
|
success(res, { original, processed });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(err.message ?? err);
|
$.error(err.message ?? err);
|
||||||
failed(
|
failed(
|
||||||
|
@ -32,6 +32,7 @@ async function produceArtifact({
|
|||||||
content,
|
content,
|
||||||
mergeSources,
|
mergeSources,
|
||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
|
ignoreFailedRemoteFile,
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
@ -332,7 +333,95 @@ async function produceArtifact({
|
|||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (!file) throw new Error(`找不到文件 ${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 || []);
|
content = await ProxyUtils.process(content, file.process || []);
|
||||||
return content ?? '';
|
return content ?? '';
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user