mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2026-03-21 00:22:35 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59a9290f9e | ||
|
|
3637c5eb74 | ||
|
|
80d46597b4 | ||
|
|
ca65e4209e | ||
|
|
53bb4866e7 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.248",
|
||||
"version": "2.14.253",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -231,6 +231,10 @@ const sshParser = (proxy = {}) => {
|
||||
// https://wiki.metacubex.one/config/proxies/ssh
|
||||
// https://sing-box.sagernet.org/zh/configuration/outbound/ssh
|
||||
if (proxy['privateKey']) parsedProxy.private_key_path = proxy['privateKey'];
|
||||
if (proxy['private-key'])
|
||||
parsedProxy.private_key_path = proxy['private-key'];
|
||||
if (proxy['private-key-passphrase'])
|
||||
parsedProxy.private_key_passphrase = proxy['private-key-passphrase'];
|
||||
if (proxy['server-fingerprint']) {
|
||||
parsedProxy.host_key = [proxy['server-fingerprint']];
|
||||
// https://manual.nssurge.com/policy/ssh.html
|
||||
@@ -240,6 +244,9 @@ const sshParser = (proxy = {}) => {
|
||||
proxy['server-fingerprint'].split(' ')[0],
|
||||
];
|
||||
}
|
||||
if (proxy['host-key']) parsedProxy.host_key = proxy['host-key'];
|
||||
if (proxy['host-key-algorithms'])
|
||||
parsedProxy.host_key_algorithms = proxy['host-key-algorithms'];
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
tfoParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
|
||||
@@ -77,12 +77,49 @@ async function downloadSubscription(req, res) {
|
||||
},
|
||||
});
|
||||
|
||||
if (sub.source !== 'local' || url) {
|
||||
if (
|
||||
sub.source !== 'local' ||
|
||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources) ||
|
||||
url
|
||||
) {
|
||||
try {
|
||||
// forward flow headers
|
||||
const flowInfo = await getFlowHeaders(url || sub.url);
|
||||
if (flowInfo) {
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
url = `${url || sub.url}`
|
||||
.split(/[\r\n]+/)
|
||||
.map((i) => i.trim())
|
||||
.filter((i) => i.length)?.[0];
|
||||
|
||||
let $arguments = {};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$arguments.noFlow) {
|
||||
// forward flow headers
|
||||
const flowInfo = await getFlowHeaders(
|
||||
url,
|
||||
undefined,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
if (flowInfo) {
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
@@ -92,6 +129,9 @@ async function downloadSubscription(req, res) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sub.subUserinfo) {
|
||||
res.set('subscription-userinfo', sub.subUserinfo);
|
||||
}
|
||||
|
||||
if (platform === 'JSON') {
|
||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||
@@ -176,11 +216,47 @@ async function downloadCollection(req, res) {
|
||||
const subnames = collection.subscriptions;
|
||||
if (subnames.length > 0) {
|
||||
const sub = findByName(allSubs, subnames[0]);
|
||||
if (sub.source !== 'local') {
|
||||
if (
|
||||
sub.source !== 'local' ||
|
||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||
) {
|
||||
try {
|
||||
const flowInfo = await getFlowHeaders(sub.url);
|
||||
if (flowInfo) {
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
let url = `${sub.url}`
|
||||
.split(/[\r\n]+/)
|
||||
.map((i) => i.trim())
|
||||
.filter((i) => i.length)?.[0];
|
||||
|
||||
let $arguments = {};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$arguments.noFlow) {
|
||||
const flowInfo = await getFlowHeaders(
|
||||
url,
|
||||
undefined,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
if (flowInfo) {
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
@@ -190,6 +266,9 @@ async function downloadCollection(req, res) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sub.subUserinfo) {
|
||||
res.set('subscription-userinfo', sub.subUserinfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (platform === 'JSON') {
|
||||
|
||||
@@ -109,7 +109,12 @@ async function compareSub(req, res) {
|
||||
.filter((i) => i.length)
|
||||
.map(async (url) => {
|
||||
try {
|
||||
return await download(url, sub.ua);
|
||||
return await download(
|
||||
url,
|
||||
sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
$.error(
|
||||
@@ -195,7 +200,12 @@ async function compareCollection(req, res) {
|
||||
.filter((i) => i.length)
|
||||
.map(async (url) => {
|
||||
try {
|
||||
return await download(url, sub.ua);
|
||||
return await download(
|
||||
url,
|
||||
sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
$.error(
|
||||
|
||||
@@ -51,14 +51,20 @@ async function getFlowInfo(req, res) {
|
||||
sub.source === 'local' &&
|
||||
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||
) {
|
||||
failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'NO_FLOW_INFO',
|
||||
'N/A',
|
||||
`Local subscription ${name} has no flow information!`,
|
||||
),
|
||||
);
|
||||
if (sub.subUserinfo) {
|
||||
success(res, {
|
||||
...parseFlowHeaders(sub.subUserinfo),
|
||||
});
|
||||
} else {
|
||||
failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'NO_FLOW_INFO',
|
||||
'N/A',
|
||||
`Local subscription ${name} has no flow information!`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -97,26 +103,42 @@ async function getFlowInfo(req, res) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const flowHeaders = await getFlowHeaders(url);
|
||||
if (!flowHeaders) {
|
||||
failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'NO_FLOW_INFO',
|
||||
'No flow info',
|
||||
`Failed to fetch flow headers`,
|
||||
),
|
||||
if (sub.subUserinfo) {
|
||||
success(res, {
|
||||
...parseFlowHeaders(sub.subUserinfo),
|
||||
remainingDays: getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
const flowHeaders = await getFlowHeaders(
|
||||
url,
|
||||
undefined,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
return;
|
||||
if (!flowHeaders) {
|
||||
failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'NO_FLOW_INFO',
|
||||
'No flow info',
|
||||
`Failed to fetch flow headers`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
success(res, {
|
||||
...parseFlowHeaders(flowHeaders),
|
||||
remainingDays: getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
}),
|
||||
});
|
||||
}
|
||||
success(res, {
|
||||
...parseFlowHeaders(flowHeaders),
|
||||
remainingDays: getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
failed(
|
||||
res,
|
||||
|
||||
@@ -62,7 +62,12 @@ async function produceArtifact({
|
||||
.filter((i) => i.length)
|
||||
.map(async (url) => {
|
||||
try {
|
||||
return await download(url, ua || sub.ua);
|
||||
return await download(
|
||||
url,
|
||||
ua || sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
$.error(
|
||||
@@ -102,7 +107,12 @@ async function produceArtifact({
|
||||
.filter((i) => i.length)
|
||||
.map(async (url) => {
|
||||
try {
|
||||
return await download(url, ua || sub.ua);
|
||||
return await download(
|
||||
url,
|
||||
ua || sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
$.error(
|
||||
@@ -198,7 +208,12 @@ async function produceArtifact({
|
||||
.filter((i) => i.length)
|
||||
.map(async (url) => {
|
||||
try {
|
||||
return await download(url, sub.ua);
|
||||
return await download(
|
||||
url,
|
||||
sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
$.error(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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 {
|
||||
@@ -13,7 +14,7 @@ import $ from '@/core/app';
|
||||
|
||||
const tasks = new Map();
|
||||
|
||||
export default async function download(rawUrl, ua, timeout) {
|
||||
export default async function download(rawUrl, ua, timeout, proxy) {
|
||||
let $arguments = {};
|
||||
let url = rawUrl.replace(/#noFlow$/, '');
|
||||
const rawArgs = url.split('#');
|
||||
@@ -52,7 +53,7 @@ export default async function download(rawUrl, ua, timeout) {
|
||||
// return item.content;
|
||||
// }
|
||||
|
||||
const { isNode } = ENV();
|
||||
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||
$.read(SETTINGS_KEY);
|
||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||
@@ -65,6 +66,10 @@ export default async function download(rawUrl, ua, timeout) {
|
||||
const http = HTTP({
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
...(isStash && proxy
|
||||
? { 'X-Stash-Selected-Proxy': encodeURIComponent(proxy) }
|
||||
: {}),
|
||||
...(isShadowRocket && proxy ? { 'X-Surge-Policy': proxy } : {}),
|
||||
},
|
||||
timeout: requestTimeout,
|
||||
});
|
||||
@@ -78,10 +83,16 @@ export default async function download(rawUrl, ua, timeout) {
|
||||
result = cached;
|
||||
} else {
|
||||
$.info(
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nURL: ${url}`,
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
||||
);
|
||||
try {
|
||||
const { body, headers } = await http.get(url);
|
||||
const { body, headers } = await http.get({
|
||||
url,
|
||||
...(proxy ? { proxy } : {}),
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
});
|
||||
|
||||
if (headers) {
|
||||
const flowInfo = getFlowField(headers);
|
||||
@@ -116,7 +127,11 @@ export default async function download(rawUrl, ua, timeout) {
|
||||
// 检查订阅有效性
|
||||
|
||||
if ($arguments?.validCheck) {
|
||||
await validCheck(parseFlowHeaders(await getFlowHeaders(url)));
|
||||
await validCheck(
|
||||
parseFlowHeaders(
|
||||
await getFlowHeaders(url, undefined, undefined, proxy),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isNode) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SETTINGS_KEY } from '@/constants';
|
||||
import { HTTP } from '@/vendor/open-api';
|
||||
import { HTTP, ENV } from '@/vendor/open-api';
|
||||
import { getPolicyDescriptor } from '@/utils';
|
||||
import $ from '@/core/app';
|
||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||
|
||||
@@ -9,7 +10,7 @@ export function getFlowField(headers) {
|
||||
)[0];
|
||||
return headers[subkey];
|
||||
}
|
||||
export async function getFlowHeaders(rawUrl, ua, timeout) {
|
||||
export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
||||
let url = rawUrl;
|
||||
let $arguments = {};
|
||||
const rawArgs = url.split('#');
|
||||
@@ -33,6 +34,7 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
||||
if ($arguments?.noFlow) {
|
||||
return;
|
||||
}
|
||||
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||
const cached = headersResourceCache.get(url);
|
||||
let flowInfo;
|
||||
if (!$arguments?.noCache && cached) {
|
||||
@@ -55,8 +57,21 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
||||
.filter((i) => i.length)[0],
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
...(isStash && proxy
|
||||
? {
|
||||
'X-Stash-Selected-Proxy':
|
||||
encodeURIComponent(proxy),
|
||||
}
|
||||
: {}),
|
||||
...(isShadowRocket && proxy
|
||||
? { 'X-Surge-Policy': proxy }
|
||||
: {}),
|
||||
},
|
||||
timeout: requestTimeout,
|
||||
...(proxy ? { proxy } : {}),
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
});
|
||||
flowInfo = getFlowField(headers);
|
||||
} catch (e) {
|
||||
@@ -178,7 +193,7 @@ export function getRmainingDays(opt = {}) {
|
||||
|
||||
return daysDiff;
|
||||
} else {
|
||||
if (!resetDay) throw new Error('未提供月重置日 resetDay');
|
||||
if (!resetDay) return;
|
||||
resetDay = parseInt(resetDay);
|
||||
if (isNaN(resetDay) || resetDay <= 0 || resetDay > 31)
|
||||
throw new Error('月重置日应为 1-31 之间的整数');
|
||||
|
||||
@@ -35,6 +35,17 @@ function getIfPresent(obj, defaultValue) {
|
||||
return isPresent(obj) ? obj : defaultValue;
|
||||
}
|
||||
|
||||
function getPolicyDescriptor(str) {
|
||||
if (!str) return {};
|
||||
return /^.+?\s*?=\s*?.+?\s*?,.+?/.test(str)
|
||||
? {
|
||||
'policy-descriptor': str,
|
||||
}
|
||||
: {
|
||||
policy: str,
|
||||
};
|
||||
}
|
||||
|
||||
const utf8ArrayToStr =
|
||||
typeof TextDecoder !== 'undefined'
|
||||
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||
@@ -91,4 +102,5 @@ export {
|
||||
isPresent,
|
||||
getIfPresent,
|
||||
utf8ArrayToStr,
|
||||
getPolicyDescriptor,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user