mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-02 18:10:35 +08:00
276 lines
9.3 KiB
JavaScript
276 lines
9.3 KiB
JavaScript
import { SETTINGS_KEY } from '@/constants';
|
|
import { HTTP, ENV } from '@/vendor/open-api';
|
|
import { hex_md5 } from '@/vendor/md5';
|
|
import { getPolicyDescriptor } from '@/utils';
|
|
import resourceCache from '@/utils/resource-cache';
|
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
|
import {
|
|
getFlowField,
|
|
getFlowHeaders,
|
|
parseFlowHeaders,
|
|
validCheck,
|
|
} from '@/utils/flow';
|
|
import $ from '@/core/app';
|
|
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
|
|
const clashPreprocessor = PROXY_PREPROCESSORS.find(
|
|
(processor) => processor.name === 'Clash Pre-processor',
|
|
);
|
|
|
|
const tasks = new Map();
|
|
|
|
export default async function download(
|
|
rawUrl = '',
|
|
ua,
|
|
timeout,
|
|
customProxy,
|
|
skipCustomCache,
|
|
awaitCustomCache,
|
|
noCache,
|
|
preprocess,
|
|
) {
|
|
let $arguments = {};
|
|
let url = rawUrl.replace(/#noFlow$/, '');
|
|
const rawArgs = url.split('#');
|
|
url = url.split('#')[0];
|
|
if (rawArgs.length > 1) {
|
|
try {
|
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
|
} catch (e) {
|
|
for (const pair of rawArgs[1].split('&')) {
|
|
const key = pair.split('=')[0];
|
|
const value = pair.split('=')[1];
|
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
|
$arguments[key] =
|
|
value == null || value === ''
|
|
? true
|
|
: decodeURIComponent(value);
|
|
}
|
|
}
|
|
}
|
|
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
|
const {
|
|
defaultProxy,
|
|
defaultUserAgent,
|
|
defaultTimeout,
|
|
cacheThreshold: defaultCacheThreshold,
|
|
} = $.read(SETTINGS_KEY);
|
|
const cacheThreshold = defaultCacheThreshold || 1024;
|
|
let proxy = customProxy || defaultProxy;
|
|
if ($.env.isNode) {
|
|
proxy = proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
|
}
|
|
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
|
const requestTimeout = timeout || defaultTimeout || 8000;
|
|
const id = hex_md5(userAgent + url);
|
|
|
|
if ($arguments?.cacheKey === true) {
|
|
$.error(`使用自定义缓存时 cacheKey 的值不能为空`);
|
|
$arguments.cacheKey = undefined;
|
|
}
|
|
|
|
const customCacheKey = $arguments?.cacheKey
|
|
? `#sub-store-cached-custom-${$arguments?.cacheKey}`
|
|
: undefined;
|
|
|
|
if (customCacheKey && !skipCustomCache) {
|
|
const customCached = $.read(customCacheKey);
|
|
const cached = resourceCache.get(id);
|
|
if (!noCache && !$arguments?.noCache && cached) {
|
|
$.info(
|
|
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
|
);
|
|
return cached;
|
|
}
|
|
if (customCached) {
|
|
if (awaitCustomCache) {
|
|
$.info(`乐观缓存: URL ${url}\n本次进行请求 尝试更新缓存`);
|
|
try {
|
|
await download(
|
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
|
ua,
|
|
timeout,
|
|
proxy,
|
|
true,
|
|
undefined,
|
|
undefined,
|
|
preprocess,
|
|
);
|
|
} catch (e) {
|
|
$.error(
|
|
`乐观缓存: URL ${url} 更新缓存发生错误 ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
$.info('使用乐观缓存的数据刷新缓存, 防止后续请求');
|
|
resourceCache.set(id, customCached);
|
|
}
|
|
} else {
|
|
$.info(
|
|
`乐观缓存: URL ${url}\n本次返回自定义缓存 ${$arguments?.cacheKey}\n并进行请求 尝试异步更新缓存`,
|
|
);
|
|
download(
|
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
|
ua,
|
|
timeout,
|
|
proxy,
|
|
true,
|
|
undefined,
|
|
undefined,
|
|
preprocess,
|
|
).catch((e) => {
|
|
$.error(
|
|
`乐观缓存: URL ${url} 异步更新缓存发生错误 ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
});
|
|
}
|
|
return customCached;
|
|
}
|
|
}
|
|
|
|
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
|
// if (downloadUrlMatch) {
|
|
// let type = downloadUrlMatch?.[1];
|
|
// let name = downloadUrlMatch?.[2];
|
|
// if (name == null) {
|
|
// throw new Error(`本地 ${type} URL 无效: ${url}`);
|
|
// }
|
|
// name = decodeURIComponent(name);
|
|
// const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
|
// const item = findByName($.read(key), name);
|
|
// if (!item) {
|
|
// throw new Error(`找不到本地 ${type}: ${name}`);
|
|
// }
|
|
|
|
// return item.content;
|
|
// }
|
|
|
|
if (!isNode && tasks.has(id)) {
|
|
return tasks.get(id);
|
|
}
|
|
|
|
const http = HTTP({
|
|
headers: {
|
|
'User-Agent': userAgent,
|
|
...(isStash && proxy
|
|
? { 'X-Stash-Selected-Proxy': encodeURIComponent(proxy) }
|
|
: {}),
|
|
...(isShadowRocket && proxy ? { 'X-Surge-Policy': proxy } : {}),
|
|
},
|
|
timeout: requestTimeout,
|
|
});
|
|
|
|
let result;
|
|
|
|
// try to find in app cache
|
|
const cached = resourceCache.get(id);
|
|
if (!noCache && !$arguments?.noCache && cached) {
|
|
$.info(`使用缓存: ${url}, ${userAgent}`);
|
|
result = cached;
|
|
if (customCacheKey) {
|
|
$.info(`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`);
|
|
$.write(cached, customCacheKey);
|
|
}
|
|
} else {
|
|
const insecure = $arguments?.insecure
|
|
? isNode
|
|
? { strictSSL: false }
|
|
: { insecure: true }
|
|
: undefined;
|
|
$.info(
|
|
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nInsecure: ${!!insecure}\nPreprocess: ${preprocess}\nURL: ${url}`,
|
|
);
|
|
try {
|
|
let { body, headers, statusCode } = await http.get({
|
|
url,
|
|
...(proxy ? { proxy } : {}),
|
|
...(isLoon && proxy ? { node: proxy } : {}),
|
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
|
...(insecure ? insecure : {}),
|
|
});
|
|
$.info(`statusCode: ${statusCode}`);
|
|
if (statusCode < 200 || statusCode >= 400) {
|
|
throw new Error(`statusCode: ${statusCode}`);
|
|
}
|
|
|
|
if (headers) {
|
|
const flowInfo = getFlowField(headers);
|
|
if (flowInfo) {
|
|
headersResourceCache.set(id, flowInfo);
|
|
}
|
|
}
|
|
if (body.replace(/\s/g, '').length === 0)
|
|
throw new Error(new Error('远程资源内容为空'));
|
|
if (preprocess) {
|
|
try {
|
|
if (clashPreprocessor.test(body)) {
|
|
body = clashPreprocessor.parse(body, true);
|
|
}
|
|
} catch (e) {
|
|
$.error(`Clash Pre-processor error: ${e}`);
|
|
}
|
|
}
|
|
let shouldCache = true;
|
|
if (cacheThreshold) {
|
|
const size = body.length / 1024;
|
|
if (size > cacheThreshold) {
|
|
$.info(
|
|
`资源大小 ${size.toFixed(
|
|
2,
|
|
)} KB 超过了 ${cacheThreshold} KB, 不缓存`,
|
|
);
|
|
shouldCache = false;
|
|
}
|
|
}
|
|
if (shouldCache) {
|
|
resourceCache.set(id, body);
|
|
if (customCacheKey) {
|
|
$.info(
|
|
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
|
|
);
|
|
$.write(body, customCacheKey);
|
|
}
|
|
}
|
|
|
|
result = body;
|
|
} catch (e) {
|
|
if (customCacheKey) {
|
|
const cached = $.read(customCacheKey);
|
|
if (cached) {
|
|
$.info(
|
|
`无法下载 URL ${url}: ${
|
|
e.message ?? e
|
|
}\n使用自定义缓存 ${$arguments?.cacheKey}`,
|
|
);
|
|
return cached;
|
|
}
|
|
}
|
|
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
|
|
}
|
|
}
|
|
|
|
// 检查订阅有效性
|
|
|
|
if ($arguments?.validCheck) {
|
|
await validCheck(
|
|
parseFlowHeaders(
|
|
await getFlowHeaders(
|
|
url,
|
|
$arguments.flowUserAgent,
|
|
undefined,
|
|
proxy,
|
|
$arguments.flowUrl,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!isNode) {
|
|
tasks.set(id, result);
|
|
}
|
|
return result;
|
|
}
|