Compare commits

...

10 Commits

Author SHA1 Message Date
xream
6cf8080cd3 fix: 修复 VMess VLESS servername 2024-10-17 14:01:44 +08:00
xream
839fcacf63 fix: 修复传输层和 SNI 的问题(有问题麻烦即时反馈 谢谢) 2024-10-16 21:31:41 +08:00
xream
a2e45bcb10 commit
feat: Surge 支持 Shadowsocks 2022(为了兼容 必须使用 `includeUnsupportedProxy` 参数或开启 `包含官方/商店版不支持的协议` 开关)
2024-10-15 17:00:13 +08:00
xream
ea0eb91691 doc: README 2024-10-12 14:51:58 +08:00
xream
1f0ddf2d28 fix: 修复组合订阅预览 2024-10-12 10:46:39 +08:00
xream
a660c6ff90 feat: 组合订阅支持通过单条订阅的标签进行关联 2024-10-11 20:57:45 +08:00
xream
71d9adbc07 chore: bump release version 2024-10-11 20:00:09 +08:00
pillarcoin
97bec9183a fix: clash 配置中 VLESS 节点的 short-id 值被错误解析 2024-10-11 19:10:02 +08:00
xream
ef85b6d0e9 feat: 文件支持设置代理/策略, 链接支持传入 proxy 参数指定代理/策略; 修复代理/策略优先级 2024-10-07 22:05:07 +08:00
xream
8ffb060cb4 feat: 组合订阅支持设置代理/策略, 链接支持传入 proxy 参数指定代理/策略 2024-10-07 20:56:33 +08:00
12 changed files with 117 additions and 57 deletions

View File

@@ -26,6 +26,8 @@ Core functionalities:
### Supported Input Formats
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
- [x] Clash Proxies YAML
- [x] Clash Proxy JSON(single line)
@@ -33,7 +35,6 @@ Core functionalities:
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.389",
"version": "2.14.399",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@@ -408,20 +408,7 @@ function lastParse(proxy) {
proxy['h2-opts'].path = path[0];
}
}
if (proxy.tls && !proxy.sni) {
if (proxy.network) {
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
transportHost = Array.isArray(transportHost)
? transportHost[0]
: transportHost;
if (transportHost) {
proxy.sni = transportHost;
}
}
if (!proxy.sni && !isIP(proxy.server)) {
proxy.sni = proxy.server;
}
}
// 非 tls, 有 ws/http 传输层, 使用域名的节点, 将设置传输层 Host 防止之后域名解析后丢失域名(不覆盖现有的 Host)
if (
!proxy.tls &&
@@ -448,6 +435,20 @@ function lastParse(proxy) {
proxy[`${proxy.network}-opts`].path = [transportPath];
}
}
if (proxy.tls && !proxy.sni) {
if (!isIP(proxy.server)) {
proxy.sni = proxy.server;
}
if (!proxy.sni && proxy.network) {
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
transportHost = Array.isArray(transportHost)
? transportHost[0]
: transportHost;
if (transportHost) {
proxy.sni = transportHost;
}
}
}
// if (['hysteria', 'hysteria2', 'tuic'].includes(proxy.type)) {
if (proxy.ports) {
proxy.ports = String(proxy.ports).replace(/\//g, ',');

View File

@@ -391,12 +391,6 @@ function URI_VMess() {
} else {
delete proxy.network;
}
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413
// sni 优先级应高于 host
if (proxy.tls && !proxy.sni && transportHost) {
proxy.sni = transportHost;
}
}
return proxy;
}
@@ -539,15 +533,6 @@ function URI_VLESS() {
}
}
if (proxy.tls && !proxy.sni) {
if (proxy.network === 'ws') {
proxy.sni = proxy['ws-opts']?.headers?.Host;
} else if (proxy.network === 'http') {
let httpHost = proxy['http-opts']?.headers?.Host;
proxy.sni = Array.isArray(httpHost) ? httpHost[0] : httpHost;
}
}
return proxy;
};
return { name, test, parse };
@@ -898,18 +883,7 @@ function Clash_All() {
if (['vmess', 'vless'].includes(proxy.type)) {
proxy.sni = proxy.servername;
delete proxy.servername;
if (proxy.tls && !proxy.sni) {
if (proxy.network === 'ws') {
proxy.sni = proxy['ws-opts']?.headers?.Host;
} else if (proxy.network === 'http') {
let httpHost = proxy['http-opts']?.headers?.Host;
proxy.sni = Array.isArray(httpHost)
? httpHost[0]
: httpHost;
}
}
}
if (proxy['server-cert-fingerprint']) {
proxy['tls-fingerprint'] = proxy['server-cert-fingerprint'];
}

View File

@@ -200,7 +200,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -198,7 +198,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
proxy.cipher = cipher;
}
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -50,10 +50,32 @@ function Clash() {
};
const parse = function (raw) {
// Clash YAML format
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
// 匹配 short-id 冒号后面的值(包含空格和引号)
const afterReplace = raw.replace(
/short-id:([ ]*[^,\n}]*)/g,
(matched, value) => {
const afterTrim = value.trim();
// 为空
if (!afterTrim || afterTrim === '') {
return 'short-id: ""'
}
// 是否被引号包裹
if (/^(['"]).*\1$/.test(afterTrim)) {
return `short-id: ${afterTrim}`;
} else {
return `short-id: "${afterTrim}"`
}
}
);
const {
proxies,
'global-client-fingerprint': globalClientFingerprint,
} = safeLoad(raw);
} = safeLoad(afterReplace);
return proxies
.map((p) => {
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26

View File

@@ -20,7 +20,7 @@ export default function Surge_Producer() {
}
switch (proxy.type) {
case 'ss':
return shadowsocks(proxy);
return shadowsocks(proxy, opts['include-unsupported-proxy']);
case 'trojan':
return trojan(proxy);
case 'vmess':
@@ -51,7 +51,7 @@ export default function Surge_Producer() {
return { produce };
}
function shadowsocks(proxy) {
function shadowsocks(proxy, includeUnsupportedProxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (!proxy.cipher) {
@@ -85,6 +85,9 @@ function shadowsocks(proxy) {
'chacha20',
'chacha20-ietf',
'none',
...(includeUnsupportedProxy
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
: []),
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);

View File

@@ -69,6 +69,7 @@ async function downloadSubscription(req, res) {
produceType,
includeUnsupportedProxy,
resultFormat,
proxy,
} = req.query;
let $options = {};
if (req.query.$options) {
@@ -92,6 +93,10 @@ async function downloadSubscription(req, res) {
url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`);
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程订阅 User-Agent: ${ua}`);
@@ -135,6 +140,7 @@ async function downloadSubscription(req, res) {
'include-unsupported-proxy': includeUnsupportedProxy,
},
$options,
proxy,
});
if (
@@ -175,7 +181,7 @@ async function downloadSubscription(req, res) {
url,
$arguments.flowUserAgent,
undefined,
sub.proxy,
proxy || sub.proxy,
$arguments.flowUrl,
);
if (flowInfo) {
@@ -264,6 +270,7 @@ async function downloadCollection(req, res) {
produceType,
includeUnsupportedProxy,
resultFormat,
proxy,
} = req.query;
let $options = {};
@@ -285,6 +292,11 @@ async function downloadCollection(req, res) {
$.info(`传入 $options: ${JSON.stringify($options)}`);
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
@@ -311,6 +323,7 @@ async function downloadCollection(req, res) {
'include-unsupported-proxy': includeUnsupportedProxy,
},
$options,
proxy,
});
// forward flow header from the first subscription in this collection
@@ -355,7 +368,7 @@ async function downloadCollection(req, res) {
url,
$arguments.flowUserAgent,
undefined,
sub.proxy,
proxy || sub.proxy || collection.proxy,
$arguments.flowUrl,
);
if (flowInfo) {

View File

@@ -59,6 +59,7 @@ async function getFile(req, res) {
content,
mergeSources,
ignoreFailedRemoteFile,
proxy,
} = req.query;
let $options = {};
if (req.query.$options) {
@@ -82,6 +83,10 @@ async function getFile(req, res) {
url = decodeURIComponent(url);
$.info(`指定远程文件 URL: ${url}`);
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程文件 User-Agent: ${ua}`);
@@ -120,6 +125,7 @@ async function getFile(req, res) {
mergeSources,
ignoreFailedRemoteFile,
$options,
proxy,
});
try {
@@ -129,6 +135,8 @@ async function getFile(req, res) {
const flowInfo = await getFlowHeaders(
subInfoUrl,
subInfoUserAgent || file.subInfoUserAgent,
undefined,
proxy || file.proxy,
);
if (flowInfo) {
res.set('subscription-userinfo', flowInfo);

View File

@@ -177,7 +177,20 @@ async function compareCollection(req, res) {
try {
const allSubs = $.read(SUBS_KEY);
const collection = req.body;
const subnames = collection.subscriptions;
const subnames = [...collection.subscriptions];
let subscriptionTags = collection.subscriptionTags;
if (Array.isArray(subscriptionTags) && subscriptionTags.length > 0) {
allSubs.forEach((sub) => {
if (
Array.isArray(sub.tag) &&
sub.tag.length > 0 &&
!subnames.includes(sub.name) &&
sub.tag.some((tag) => subscriptionTags.includes(tag))
) {
subnames.push(sub.name);
}
});
}
const results = {};
const errors = {};
await Promise.all(

View File

@@ -38,6 +38,7 @@ async function produceArtifact({
subscription,
awaitCustomCache,
$options,
proxy,
}) {
platform = platform || 'JSON';
@@ -68,7 +69,7 @@ async function produceArtifact({
url,
ua || sub.ua,
undefined,
sub.proxy,
proxy || sub.proxy,
undefined,
awaitCustomCache,
);
@@ -115,7 +116,7 @@ async function produceArtifact({
url,
ua || sub.ua,
undefined,
sub.proxy,
proxy || sub.proxy,
undefined,
awaitCustomCache,
);
@@ -189,7 +190,20 @@ async function produceArtifact({
const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name);
if (!collection) throw new Error(`找不到组合订阅 ${name}`);
const subnames = collection.subscriptions;
const subnames = [...collection.subscriptions];
let subscriptionTags = collection.subscriptionTags;
if (Array.isArray(subscriptionTags) && subscriptionTags.length > 0) {
allSubs.forEach((sub) => {
if (
Array.isArray(sub.tag) &&
sub.tag.length > 0 &&
!subnames.includes(sub.name) &&
sub.tag.some((tag) => subscriptionTags.includes(tag))
) {
subnames.push(sub.name);
}
});
}
const results = {};
const errors = {};
let processed = 0;
@@ -220,7 +234,9 @@ async function produceArtifact({
url,
sub.ua,
undefined,
sub.proxy,
proxy ||
sub.proxy ||
collection.proxy,
);
} catch (err) {
errors[url] = err;
@@ -344,7 +360,6 @@ async function produceArtifact({
}
exist[proxy.name] = true;
}
console.log(proxies);
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
} else if (type === 'rule') {
const allRules = $.read(RULES_KEY);
@@ -390,7 +405,12 @@ async function produceArtifact({
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(url, ua || file.ua);
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
);
} catch (err) {
errors[url] = err;
$.error(
@@ -433,7 +453,12 @@ async function produceArtifact({
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(url, ua || file.ua);
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
);
} catch (err) {
errors[url] = err;
$.error(