Compare commits

..

7 Commits

16 changed files with 202 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.14.56", "version": "2.14.63",
"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": {

View File

@@ -63,7 +63,7 @@ function parse(raw) {
return proxies; return proxies;
} }
async function process(proxies, operators = [], targetPlatform) { async function process(proxies, operators = [], targetPlatform, source) {
for (const item of operators) { for (const item of operators) {
// process script // process script
let script; let script;
@@ -122,6 +122,7 @@ async function process(proxies, operators = [], targetPlatform) {
script, script,
targetPlatform, targetPlatform,
$arguments, $arguments,
source,
); );
} else { } else {
processor = PROXY_PROCESSORS[item.type](item.args || {}); processor = PROXY_PROCESSORS[item.type](item.args || {});
@@ -208,7 +209,7 @@ function lastParse(proxy) {
delete proxy.network; delete proxy.network;
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
proxy.tls = true; proxy.tls = true;
} }
if (proxy.tls && !proxy.sni) { if (proxy.tls && !proxy.sni) {

View File

@@ -267,6 +267,8 @@ function URI_VMess() {
params.obfs === 'http' params.obfs === 'http'
) { ) {
proxy.network = 'http'; proxy.network = 'http';
} else if (['grpc'].includes(params.net)) {
proxy.network = 'grpc';
} }
if (proxy.network) { if (proxy.network) {
let transportHost = params.host ?? params.obfsParam; let transportHost = params.host ?? params.obfsParam;
@@ -285,10 +287,17 @@ function URI_VMess() {
} }
} }
if (transportPath || transportHost) { if (transportPath || transportHost) {
proxy[`${proxy.network}-opts`] = { if (['grpc'].includes(proxy.network)) {
path: getIfNotBlank(transportPath), proxy[`${proxy.network}-opts`] = {
headers: { Host: getIfNotBlank(transportHost) }, 'grpc-service-name': getIfNotBlank(transportPath),
}; '_grpc-type': getIfNotBlank(params.type),
};
} else {
proxy[`${proxy.network}-opts`] = {
path: getIfNotBlank(transportPath),
headers: { Host: getIfNotBlank(transportHost) },
};
}
} else { } else {
delete proxy.network; delete proxy.network;
} }
@@ -365,6 +374,10 @@ function URI_VLESS() {
if (params.serviceName) { if (params.serviceName) {
opts[`${proxy.network}-service-name`] = params.serviceName; opts[`${proxy.network}-service-name`] = params.serviceName;
} }
// https://github.com/XTLS/Xray-core/issues/91
if (['grpc'].includes(proxy.network)) {
opts['_grpc-type'] = params.mode || 'gun';
}
if (Object.keys(opts).length > 0) { if (Object.keys(opts).length > 0) {
proxy[`${proxy.network}-opts`] = opts; proxy[`${proxy.network}-opts`] = opts;
} }
@@ -423,6 +436,7 @@ function Clash_All() {
'tuic', 'tuic',
'vless', 'vless',
'hysteria', 'hysteria',
'hysteria2',
'wireguard', 'wireguard',
].includes(proxy.type) ].includes(proxy.type)
) { ) {

View File

@@ -7,6 +7,7 @@ import lodash from 'lodash';
import $ from '@/core/app'; import $ from '@/core/app';
import { hex_md5 } from '@/vendor/md5'; import { hex_md5 } from '@/vendor/md5';
import { ProxyUtils } from '@/core/proxy-utils'; import { ProxyUtils } from '@/core/proxy-utils';
import env from '@/utils/env';
/** /**
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows: The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
@@ -294,7 +295,7 @@ function RegexDeleteOperator(regex) {
1. This function name should be `operator`! 1. This function name should be `operator`!
2. Always declare variables before using them! 2. Always declare variables before using them!
*/ */
function ScriptOperator(script, targetPlatform, $arguments) { function ScriptOperator(script, targetPlatform, $arguments, source) {
return { return {
name: 'Script Operator', name: 'Script Operator',
func: async (proxies) => { func: async (proxies) => {
@@ -305,7 +306,7 @@ function ScriptOperator(script, targetPlatform, $arguments) {
script, script,
$arguments, $arguments,
); );
output = operator(proxies, targetPlatform); output = operator(proxies, targetPlatform, { source, ...env });
})(); })();
return output; return output;
}, },
@@ -562,7 +563,7 @@ function TypeFilter(types) {
1. This function name should be `filter`! 1. This function name should be `filter`!
2. Always declare variables before using them! 2. Always declare variables before using them!
*/ */
function ScriptFilter(script, targetPlatform, $arguments) { function ScriptFilter(script, targetPlatform, $arguments, source) {
return { return {
name: 'Script Filter', name: 'Script Filter',
func: async (proxies) => { func: async (proxies) => {
@@ -573,7 +574,7 @@ function ScriptFilter(script, targetPlatform, $arguments) {
script, script,
$arguments, $arguments,
); );
output = filter(proxies, targetPlatform); output = filter(proxies, targetPlatform, { source, ...env });
})(); })();
return output; return output;
}, },

View File

@@ -90,10 +90,22 @@ export default function Clash_Producer() {
proxy['http-opts'].headers.Host = [httpHost]; proxy['http-opts'].headers.Host = [httpHost];
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { if (
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
proxy.type,
)
) {
delete proxy.tls; delete proxy.tls;
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })
.join('') .join('')

View File

@@ -108,11 +108,23 @@ export default function ClashMeta_Producer() {
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { if (
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
proxy.type,
)
) {
delete proxy.tls; delete proxy.tls;
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })
.join('') .join('')

View File

@@ -108,11 +108,23 @@ export default function ShadowRocket_Producer() {
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { if (
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
proxy.type,
)
) {
delete proxy.tls; delete proxy.tls;
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })
.join('') .join('')

View File

@@ -120,10 +120,22 @@ export default function Stash_Producer() {
proxy['http-opts'].headers.Host = [httpHost]; proxy['http-opts'].headers.Host = [httpHost];
} }
} }
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) { if (
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
proxy.type,
)
) {
delete proxy.tls; delete proxy.tls;
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];
delete proxy.subName;
delete proxy.collectionName;
if (
['grpc'].includes(proxy.network) &&
proxy[`${proxy.network}-opts`]
) {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
}
return ' - ' + JSON.stringify(proxy) + '\n'; return ' - ' + JSON.stringify(proxy) + '\n';
}) })
.join('') .join('')

View File

@@ -71,6 +71,12 @@ function shadowsocks(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -107,6 +113,12 @@ function trojan(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -150,6 +162,12 @@ function vmess(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -182,6 +200,12 @@ function http(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -216,6 +240,12 @@ function socks5(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -245,6 +275,12 @@ function snell(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
// reuse // reuse
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse'); result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
@@ -288,6 +324,12 @@ function tuic(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }
@@ -304,10 +346,7 @@ function wireguard(proxy) {
`,no-error-alert=${proxy['no-error-alert']}`, `,no-error-alert=${proxy['no-error-alert']}`,
'no-error-alert', 'no-error-alert',
); );
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
result.appendIfPresent( result.appendIfPresent(
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`, `,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
'ip-version', 'ip-version',
@@ -316,6 +355,12 @@ function wireguard(proxy) {
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
// underlying-proxy
result.appendIfPresent(
`,underlying-proxy=${proxy['underlying-proxy']}`,
'underlying-proxy',
);
return result.toString(); return result.toString();
} }

View File

@@ -91,6 +91,16 @@ export default function URI_Producer() {
? vmessTransportHost[0] ? vmessTransportHost[0]
: vmessTransportHost; : vmessTransportHost;
} }
if (['grpc'].includes(proxy.network)) {
result.path =
proxy[`${proxy.network}-opts`]?.[
'grpc-service-name'
];
// https://github.com/XTLS/Xray-core/issues/91
result.type =
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
'gun';
}
} }
result = 'vmess://' + Base64.encode(JSON.stringify(result)); result = 'vmess://' + Base64.encode(JSON.stringify(result));
break; break;
@@ -141,6 +151,12 @@ export default function URI_Producer() {
let vlessTransport = `&type=${encodeURIComponent( let vlessTransport = `&type=${encodeURIComponent(
proxy.network, proxy.network,
)}`; )}`;
if (['grpc'].includes(proxy.network)) {
// https://github.com/XTLS/Xray-core/issues/91
vlessTransport += `&mode=${encodeURIComponent(
proxy[`${proxy.network}-opts`]?.['_grpc-type'] || 'gun',
)}`;
}
let vlessTransportServiceName = let vlessTransportServiceName =
proxy[`${proxy.network}-opts`]?.[ proxy[`${proxy.network}-opts`]?.[

View File

@@ -20,6 +20,8 @@ import registerArtifactRoutes from '@/restful/artifacts';
import registerSettingRoutes from '@/restful/settings'; import registerSettingRoutes from '@/restful/settings';
import registerMiscRoutes from '@/restful/miscs'; import registerMiscRoutes from '@/restful/miscs';
import registerSortRoutes from '@/restful/sort'; import registerSortRoutes from '@/restful/sort';
import registerFileRoutes from '@/restful/file';
import registerModuleRoutes from '@/restful/module';
migrate(); migrate();
serve(); serve();
@@ -30,6 +32,8 @@ function serve() {
// register routes // register routes
registerCollectionRoutes($app); registerCollectionRoutes($app);
registerSubscriptionRoutes($app); registerSubscriptionRoutes($app);
registerFileRoutes($app);
registerModuleRoutes($app);
registerArtifactRoutes($app); registerArtifactRoutes($app);
registerSettingRoutes($app); registerSettingRoutes($app);
registerSortRoutes($app); registerSortRoutes($app);

View File

@@ -1,7 +1,6 @@
import $ from '@/core/app'; import $ from '@/core/app';
import { ENV } from '@/vendor/open-api'; import { ENV } from '@/vendor/open-api';
import { failed, success } from '@/restful/response'; import { failed, success } from '@/restful/response';
import { version as substoreVersion } from '../../package.json';
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings'; import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
import resourceCache from '@/utils/resource-cache'; import resourceCache from '@/utils/resource-cache';
import { import {
@@ -12,6 +11,7 @@ import {
import { InternalServerError, RequestInvalidError } from '@/restful/errors'; import { InternalServerError, RequestInvalidError } from '@/restful/errors';
import Gist from '@/utils/gist'; import Gist from '@/utils/gist';
import migrate from '@/utils/migration'; import migrate from '@/utils/migration';
import env from '@/utils/env';
export default function register($app) { export default function register($app) {
// utils // utils
@@ -49,19 +49,7 @@ export default function register($app) {
} }
function getEnv(req, res) { function getEnv(req, res) {
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV(); success(res, env);
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
success(res, {
backend,
version: substoreVersion,
});
} }
async function refresh(_, res) { async function refresh(_, res) {

View File

@@ -39,6 +39,7 @@ async function compareSub(req, res) {
// add id // add id
original.forEach((proxy, i) => { original.forEach((proxy, i) => {
proxy.id = i; proxy.id = i;
proxy.subName = sub.name;
}); });
// apply processors // apply processors
@@ -46,6 +47,7 @@ async function compareSub(req, res) {
original, original,
sub.process || [], sub.process || [],
target, target,
{ [sub.name]: sub },
); );
// produce // produce
@@ -82,11 +84,18 @@ async function compareCollection(req, res) {
} }
// parse proxies // parse proxies
let currentProxies = ProxyUtils.parse(raw); let currentProxies = ProxyUtils.parse(raw);
currentProxies.forEach((proxy) => {
proxy.subName = sub.name;
proxy.collectionName = collection.name;
});
// apply processors // apply processors
currentProxies = await ProxyUtils.process( currentProxies = await ProxyUtils.process(
currentProxies, currentProxies,
sub.process || [], sub.process || [],
'JSON', 'JSON',
{ [sub.name]: sub, _collection: collection },
); );
results[name] = currentProxies; results[name] = currentProxies;
} catch (err) { } catch (err) {
@@ -110,12 +119,14 @@ async function compareCollection(req, res) {
original.forEach((proxy, i) => { original.forEach((proxy, i) => {
proxy.id = i; proxy.id = i;
proxy.collectionName = collection.name;
}); });
const processed = await ProxyUtils.process( const processed = await ProxyUtils.process(
original, original,
collection.process || [], collection.process || [],
'JSON', 'JSON',
{ _collection: collection },
); );
success(res, { original, processed }); success(res, { original, processed });

View File

@@ -36,11 +36,15 @@ async function produceArtifact({ type, name, platform }) {
} }
// parse proxies // parse proxies
let proxies = ProxyUtils.parse(raw); let proxies = ProxyUtils.parse(raw);
proxies.forEach((proxy) => {
proxy.subName = sub.name;
});
// apply processors // apply processors
proxies = await ProxyUtils.process( proxies = await ProxyUtils.process(
proxies, proxies,
sub.process || [], sub.process || [],
platform, platform,
{ [sub.name]: sub },
); );
if (proxies.length === 0) { if (proxies.length === 0) {
throw new Error(`订阅 ${name} 中不含有效节点`); throw new Error(`订阅 ${name} 中不含有效节点`);
@@ -51,7 +55,7 @@ async function produceArtifact({ type, name, platform }) {
if (exist[proxy.name]) { if (exist[proxy.name]) {
$.notify( $.notify(
'🌍 Sub-Store', '🌍 Sub-Store',
'⚠️ 订阅包含重复节点!', `⚠️ 订阅 ${name} 包含重复节点 ${proxy.name}`,
'请仔细检测配置!', '请仔细检测配置!',
{ {
'media-url': 'media-url':
@@ -86,11 +90,18 @@ async function produceArtifact({ type, name, platform }) {
} }
// parse proxies // parse proxies
let currentProxies = ProxyUtils.parse(raw); let currentProxies = ProxyUtils.parse(raw);
currentProxies.forEach((proxy) => {
proxy.subName = sub.name;
proxy.collectionName = collection.name;
});
// apply processors // apply processors
currentProxies = await ProxyUtils.process( currentProxies = await ProxyUtils.process(
currentProxies, currentProxies,
sub.process || [], sub.process || [],
platform, platform,
{ [sub.name]: sub, _collection: collection },
); );
results[name] = currentProxies; results[name] = currentProxies;
processed++; processed++;
@@ -127,11 +138,16 @@ async function produceArtifact({ type, name, platform }) {
subnames.map((name) => results[name] || []), subnames.map((name) => results[name] || []),
); );
proxies.forEach((proxy) => {
proxy.collectionName = collection.name;
});
// apply own processors // apply own processors
proxies = await ProxyUtils.process( proxies = await ProxyUtils.process(
proxies, proxies,
collection.process || [], collection.process || [],
platform, platform,
{ _collection: collection },
); );
if (proxies.length === 0) { if (proxies.length === 0) {
throw new Error(`组合订阅 ${name} 中不含有效节点`); throw new Error(`组合订阅 ${name} 中不含有效节点`);
@@ -142,7 +158,7 @@ async function produceArtifact({ type, name, platform }) {
if (exist[proxy.name]) { if (exist[proxy.name]) {
$.notify( $.notify(
'🌍 Sub-Store', '🌍 Sub-Store',
'⚠️ 订阅包含重复节点!', `⚠️ 组合订阅 ${name} 包含重复节点 ${proxy.name}`,
'请仔细检测配置!', '请仔细检测配置!',
{ {
'media-url': 'media-url':

16
backend/src/utils/env.js Normal file
View File

@@ -0,0 +1,16 @@
import { version as substoreVersion } from '../../package.json';
import { ENV } from '@/vendor/open-api';
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
export default {
backend,
version: substoreVersion,
};

View File

@@ -60,7 +60,9 @@ export class OpenAPI {
}); });
this.root = {}; this.root = {};
} else { } else {
this.root = JSON.parse(this.node.fs.readFileSync(`${rootPath}`)); this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
} }
// create a json file with the given name if not exists // create a json file with the given name if not exists
@@ -72,9 +74,7 @@ export class OpenAPI {
}); });
this.cache = {}; this.cache = {};
} else { } else {
this.cache = JSON.parse( this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
this.node.fs.readFileSync(`${fpath}`),
);
} }
} }
} }
@@ -316,7 +316,9 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
return ( return (
timer timer
? Promise.race([timer, worker]).then((res) => { ? Promise.race([timer, worker]).then((res) => {
clearTimeout(timeoutid); if (typeof clearTimeout !== 'undefined') {
clearTimeout(timeoutid);
}
return res; return res;
}) })
: worker : worker