Compare commits

...

6 Commits

Author SHA1 Message Date
xream
4d27e5bdac feat: 脚本链接叠加参数调整
Some checks are pending
build / build (push) Waiting to run
2025-03-27 12:52:19 +08:00
xream
e2011de69e feat: Loon 解析器支持参数 resourceUrlOnly 仅使用远程资源, 忽略 Loon 自身解析数据
Some checks failed
build / build (push) Has been cancelled
2025-03-26 00:26:31 +08:00
xream
9568f4d6d9 feat: 优化日志, Loon 解析器自动读取 build 2025-03-25 23:58:28 +08:00
xream
543641de9d feat: VLESS 兼容 Shadowrocket 传输层 none 2025-03-25 23:35:23 +08:00
xream
2fbc589a8a feat: Loon 输入输出支持 VLESS REALITY(flow 为 xtls-rprx-vision). 需 includeUnsupportedProxy 或 build >= 838 自动开启) 2025-03-25 22:22:29 +08:00
xream
c854614efc feat: 调整 User-Agent 判断
Some checks are pending
build / build (push) Waiting to run
2025-03-25 17:49:47 +08:00
8 changed files with 97 additions and 32 deletions

View File

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

View File

@@ -114,12 +114,7 @@ async function processFn(
if (item.type.indexOf('Script') !== -1) {
const { mode, content } = item.args;
if (mode === 'link') {
let noCache;
let url = content || '';
if (url.endsWith('#noCache')) {
url = url.replace(/#noCache$/, '');
noCache = true;
}
// extract link arguments
const rawArgs = url.split('#');
if (rawArgs.length > 1) {
@@ -138,7 +133,14 @@ async function processFn(
}
}
}
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
url = `${url.split('#')[0]}${
rawArgs[2]
? `#${rawArgs[2]}`
: $arguments?.noCache != null ||
$arguments?.insecure != null
? `#${rawArgs[1]}`
: ''
}`;
const downloadUrlMatch = url.match(
/^\/api\/(file|module)\/(.+)/,
);

View File

@@ -634,6 +634,9 @@ function URI_VLESS() {
}
if (!proxy.network && isShadowrocket && params.obfs) {
proxy.network = params.obfs;
if (['none'].includes(proxy.network)) {
proxy.network = 'tcp';
}
}
if (['websocket'].includes(proxy.network)) {
proxy.network = 'ws';

View File

@@ -60,7 +60,7 @@ vmess = tag equals "vmess"i address method uuid (transport/transport_host/transp
proxy.alterId = proxy.alterId || 0;
handleTransport();
}
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/others)* {
proxy.type = "vless";
handleTransport();
}
@@ -180,6 +180,10 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }

View File

@@ -58,7 +58,7 @@ vmess = tag equals "vmess"i address method uuid (transport/transport_host/transp
proxy.alterId = proxy.alterId || 0;
handleTransport();
}
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/others)* {
proxy.type = "vless";
handleTransport();
}
@@ -178,6 +178,10 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }

View File

@@ -23,7 +23,7 @@ export default function Loon_Producer() {
case 'vmess':
return vmess(proxy);
case 'vless':
return vless(proxy);
return vless(proxy, opts['include-unsupported-proxy']);
case 'http':
return http(proxy);
case 'socks5':
@@ -347,10 +347,28 @@ function vmess(proxy) {
return result.toString();
}
function vless(proxy) {
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
function vless(proxy, includeUnsupportedProxy) {
if (
!includeUnsupportedProxy &&
(typeof proxy.flow !== 'undefined' || proxy['reality-opts'])
) {
throw new Error(`VLESS XTLS/REALITY is not supported`);
}
let isReality = false;
if (includeUnsupportedProxy) {
if (
proxy['reality-opts'] &&
['xtls-rprx-vision'].includes(proxy.flow)
) {
isReality = true;
} else if (proxy['reality-opts']) {
throw new Error(
`VLESS REALITY with flow(${proxy.flow}) is not supported`,
);
} else if (proxy.flow) {
throw new Error(`VLESS XTLS is not supported`);
}
}
const result = new Result(proxy);
result.append(
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
@@ -399,7 +417,21 @@ function vless(proxy) {
);
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
if (isReality) {
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,public-key="${proxy['reality-opts']['public-key']}"`,
'reality-opts.public-key',
);
result.appendIfPresent(
`,short-id=${proxy['reality-opts']['short-id']}`,
'reality-opts.short-id',
);
} else {
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
}
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',

View File

@@ -14,10 +14,12 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
`
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Sub-Store -- v${version}
Loon -- ${$loon}
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
const build = $loon.match(/\((\d+)\)$/)?.[1];
let arg;
if (typeof $argument != 'undefined') {
arg = Object.fromEntries(
@@ -26,23 +28,28 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
} else {
arg = {};
}
console.log(`arg: ${JSON.stringify(arg)}`);
const RESOURCE_TYPE = {
PROXY: 1,
RULE: 2,
};
result = resource;
if (!arg.resourceUrlOnly) {
result = resource;
}
if (resourceType === RESOURCE_TYPE.PROXY) {
try {
let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
});
} catch (e) {
console.log('解析器: 使用 resource 出现错误');
console.log(e.message ?? e);
if (!arg.resourceUrlOnly) {
try {
let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 838,
});
} catch (e) {
console.log('解析器: 使用 resource 出现错误');
console.log(e.message ?? e);
}
}
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
@@ -59,18 +66,21 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
);
let proxies = ProxyUtils.parse(raw);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 838,
});
} catch (e) {
console.log(e.message ?? e);
}
}
} else if (resourceType === RESOURCE_TYPE.RULE) {
try {
const rules = RuleUtils.parse(resource);
result = RuleUtils.produce(rules, 'Loon');
} catch (e) {
console.log(e.message ?? e);
if (!arg.resourceUrlOnly) {
try {
const rules = RuleUtils.parse(resource);
result = RuleUtils.produce(rules, 'Loon');
} catch (e) {
console.log(e.message ?? e);
}
}
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);

View File

@@ -47,7 +47,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) {
return 'Clash';
} else if (ua.indexOf('v2ray') !== -1) {
return 'V2Ray';
} else if (ua.indexOf('sing-box') !== -1) {
} else if (ua.indexOf('sing-box') !== -1 || ua.indexOf('singbox') !== -1) {
return 'sing-box';
} else if (accept.indexOf('application/json') === 0) {
return 'JSON';
@@ -66,10 +66,12 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
UA: ua,
ua: ua.toLowerCase(),
});
if (!['Stash', 'Egern'].includes(target)) {
if (!['Stash', 'Egern', 'Loon'].includes(target)) {
return false;
}
const version = coerce(ua).version;
const coerceVersion = coerce(ua);
$.log(JSON.stringify(coerceVersion, null, 2));
const { version } = coerceVersion;
if (
platform === 'Stash' &&
target === 'Stash' &&
@@ -84,6 +86,14 @@ export function shouldIncludeUnsupportedProxy(platform, ua) {
) {
return true;
}
// Loon 的 UA 不规范, version 取出来是 build
if (
platform === 'Loon' &&
target === 'Loon' &&
gte(version, '838.0.0')
) {
return true;
}
} catch (e) {
$.error(`获取版本号失败: ${e}`);
}