Compare commits

...

29 Commits

Author SHA1 Message Date
xream
feb207b333 fix: servername/sni priority over wss host 2023-08-22 17:28:39 +08:00
xream
9ac1112b37 fix: VMess URI alterId parseInt 2023-08-22 15:29:55 +08:00
xream
96769598ef fix: QX tls 2023-08-22 00:42:53 +08:00
xream
f8ed6a3342 fix: QX tls 2023-08-22 00:08:53 +08:00
xream
99b19c410d fix: vmess/vless http-opts.path/http-opts.headers.Host must be an array in some clients 2023-08-21 22:16:07 +08:00
xream
9e54507bbb fix: double quotes in Surge vmess ws-headers Host 2023-08-21 21:20:31 +08:00
xream
20afa0ad22 Surge 默认模块不带 ability 参数; 分离出固定带参和不带参的模块 2023-08-20 17:22:51 +08:00
walkxspace
c5b6960b35 Update geo.js (#231) 2023-08-19 11:44:55 +08:00
xream
4dd86cb368 feat: Added replaceArtifact API 2023-08-18 13:48:37 +08:00
xream
4a0319e95f fix: flexible cipher for Loon 2023-08-15 21:22:33 +08:00
xream
090d8a978f feat: Added support for scy of VMESS URI 2023-08-15 18:15:04 +08:00
xream
bc9fae6062 feat: Added support for SNI & allowInsecure of Trojan URI 2023-08-15 17:25:25 +08:00
xream
048344268c feat: Added replaceSubscriptions, replaceCollection API 2023-08-15 15:48:57 +08:00
xream
c5746f6a6b Fixed: fast-open tfo 2023-08-15 14:59:27 +08:00
xream
5cb226da62 feat: Added support for SS URI in other formats 2023-08-15 01:48:54 +08:00
xream
d229047744 Fixed: unsupported cipher for Clash/Stash 2023-08-14 10:04:47 +08:00
Hsiaoyi
cb21a8e6ec Merge pull request #229 from xream/feature/tuic
Adjust the logic for determining the tuic version
2023-08-13 17:03:40 +08:00
xream
537a00e8a9 Adjust the logic for determining the tuic version 2023-08-13 17:00:44 +08:00
Hsiaoyi
b770578cba Merge pull request #228 from xream/feature/tuic
feat: Added support for tuic and some compatibility adjustments
2023-08-13 15:56:52 +08:00
xream
47a95e5a3d feat: Added support for tuic and some compatibility adjustments 2023-08-13 15:54:04 +08:00
Hsiaoyi
e99f13d487 Merge pull request #227 from xream/feature/snell
feat: Added support for producing snell nodes with reuse and optional obfs
2023-07-31 18:44:53 +08:00
xream
fcab8401e0 feat: Added support for producing snell nodes with reuse and optional obfs 2023-07-31 18:41:48 +08:00
Hsiaoyi
431b1a3c8e Merge pull request #226 from Keywos/master
fixed deleted gist
2023-07-31 17:42:49 +08:00
Hsiaoyi
36d46003d6 Fixed: empty uploading files 2023-07-31 17:42:31 +08:00
K
ff71f12996 version 2.14.3 2023-07-31 16:41:22 +08:00
K
f7c08e3a56 fixed deleted gist 2023-07-31 16:38:07 +08:00
K
6eea8bb2d0 Merge branch 'sub-store-org:master' into master 2023-07-31 14:58:54 +08:00
Hsiaoyi
fc90e22a48 Added Surge-Noability.sgmodule 2023-07-28 22:38:36 +08:00
K
20d958d74f Update Surge.sgmodule 2023-07-26 01:48:47 +08:00
19 changed files with 399 additions and 100 deletions

View File

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

@@ -1,5 +1,5 @@
import download from '@/utils/download'; import download from '@/utils/download';
import { isIPv4, isIPv6 } from '@/utils';
import PROXY_PROCESSORS, { ApplyProcessor } from './processors'; import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
import PROXY_PREPROCESSORS from './preprocessors'; import PROXY_PREPROCESSORS from './preprocessors';
import PROXY_PRODUCERS from './producers'; import PROXY_PRODUCERS from './producers';
@@ -36,6 +36,10 @@ function parse(raw) {
if (lastParser) { if (lastParser) {
const [proxy, error] = tryParse(lastParser, line); const [proxy, error] = tryParse(lastParser, line);
if (!error) { if (!error) {
// 前面已经处理过普通情况下的 SNI, 这里显式设置 SNI, 防止之后解析成 IP 后丢失域名 SNI
if (proxy.tls && !proxy.sni && !isIP(proxy.server)) {
proxy.sni = proxy.server;
}
proxies.push(proxy); proxies.push(proxy);
success = true; success = true;
} }
@@ -182,3 +186,7 @@ function safeMatch(parser, line) {
return false; return false;
} }
} }
function isIP(ip) {
return isIPv4(ip) || isIPv6(ip);
}

View File

@@ -23,12 +23,19 @@ function URI_SS() {
}; };
content = content.split('#')[0]; // strip proxy name content = content.split('#')[0]; // strip proxy name
// handle IPV4 and IPV6 // handle IPV4 and IPV6
const serverAndPort = content.match(/@([^/]*)(\/|$)/)[1]; let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
let userInfoStr = Base64.decode(content.split('@')[0]);
if (!serverAndPortArray) {
content = Base64.decode(content);
userInfoStr = content.split('@')[0];
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
}
const serverAndPort = serverAndPortArray[1];
const portIdx = serverAndPort.lastIndexOf(':'); const portIdx = serverAndPort.lastIndexOf(':');
proxy.server = serverAndPort.substring(0, portIdx); proxy.server = serverAndPort.substring(0, portIdx);
proxy.port = serverAndPort.substring(portIdx + 1); proxy.port = serverAndPort.substring(portIdx + 1);
const userInfo = Base64.decode(content.split('@')[0]).split(':'); const userInfo = userInfoStr.split(':');
proxy.cipher = userInfo[0]; proxy.cipher = userInfo[0];
proxy.password = userInfo[1]; proxy.password = userInfo[1];
@@ -209,14 +216,18 @@ function URI_VMess() {
type: 'vmess', type: 'vmess',
server: params.add, server: params.add,
port: params.port, port: params.port,
cipher: 'auto', // V2rayN has no default cipher! use aes-128-gcm as default. cipher: getIfPresent(params.scy, 'auto'),
uuid: params.id, uuid: params.id,
alterId: getIfPresent(params.aid, 0), alterId: parseInt(getIfPresent(params.aid, 0)),
tls: params.tls === 'tls' || params.tls === true, tls: params.tls === 'tls' || params.tls === true,
'skip-cert-verify': isPresent(params.verify_cert) 'skip-cert-verify': isPresent(params.verify_cert)
? !params.verify_cert ? !params.verify_cert
: undefined, : undefined,
}; };
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
if (proxy.tls && proxy.sni) {
proxy.sni = params.sni;
}
// handle obfs // handle obfs
if (params.net === 'ws') { if (params.net === 'ws') {
proxy.network = 'ws'; proxy.network = 'ws';
@@ -224,7 +235,9 @@ function URI_VMess() {
path: getIfNotBlank(params.path), path: getIfNotBlank(params.path),
headers: { Host: getIfNotBlank(params.host) }, headers: { Host: getIfNotBlank(params.host) },
}; };
if (proxy.tls && params.host) { // https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413
// sni 优先级应高于 host
if (proxy.tls && !proxy.sni && params.host) {
proxy.sni = params.host; proxy.sni = params.host;
} }
} }
@@ -270,6 +283,7 @@ function Clash_All() {
'http', 'http',
'snell', 'snell',
'trojan', 'trojan',
'tuic',
].includes(proxy.type) ].includes(proxy.type)
) { ) {
throw new Error( throw new Error(
@@ -474,6 +488,15 @@ function Surge_Snell() {
return { name, test, parse }; return { name, test, parse };
} }
function Surge_Tuic() {
const name = 'Surge Tuic Parser';
const test = (line) => {
return /^.*=\s*tuic(-v5)??/.test(line.split(',')[0]);
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
export default [ export default [
URI_SS(), URI_SS(),
URI_SSR(), URI_SSR(),
@@ -485,6 +508,7 @@ export default [
Surge_Trojan(), Surge_Trojan(),
Surge_Http(), Surge_Http(),
Surge_Snell(), Surge_Snell(),
Surge_Tuic(),
Surge_Socks5(), Surge_Socks5(),
Loon_SS(), Loon_SS(),
Loon_SSR(), Loon_SSR(),

View File

@@ -25,11 +25,14 @@ const grammars = String.raw`
proxy.network = "ws"; proxy.network = "ws";
$set(proxy, "ws-opts.path", obfs.path); $set(proxy, "ws-opts.path", obfs.path);
$set(proxy, "ws-opts.headers", obfs['ws-headers']); $set(proxy, "ws-opts.headers", obfs['ws-headers']);
if (proxy['ws-opts'] && proxy['ws-opts']['headers'] && proxy['ws-opts']['headers'].Host) {
proxy['ws-opts']['headers'].Host = proxy['ws-opts']['headers'].Host.replace(/^"(.*)"$/, '$1')
}
} }
} }
} }
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) { start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) {
return proxy; return proxy;
} }
@@ -73,6 +76,13 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
$set(proxy, "obfs-opts.path", obfs.path); $set(proxy, "obfs-opts.path", obfs.path);
} }
} }
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
proxy.type = "tuic";
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
proxy.type = "tuic";
proxy.version = 5;
}
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* { socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
} }
@@ -175,6 +185,11 @@ uri = $[^,]+
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
tag = match:[^=,]* { proxy.name = match.join("").trim(); } tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _ comma = _ "," _

View File

@@ -23,11 +23,14 @@
proxy.network = "ws"; proxy.network = "ws";
$set(proxy, "ws-opts.path", obfs.path); $set(proxy, "ws-opts.path", obfs.path);
$set(proxy, "ws-opts.headers", obfs['ws-headers']); $set(proxy, "ws-opts.headers", obfs['ws-headers']);
if (proxy['ws-opts'] && proxy['ws-opts']['headers'] && proxy['ws-opts']['headers'].Host) {
proxy['ws-opts']['headers'].Host = proxy['ws-opts']['headers'].Host.replace(/^"(.*)"$/, '$1')
}
} }
} }
} }
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) { start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) {
return proxy; return proxy;
} }
@@ -71,6 +74,13 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
$set(proxy, "obfs-opts.path", obfs.path); $set(proxy, "obfs-opts.path", obfs.path);
} }
} }
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* {
proxy.type = "tuic";
}
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
proxy.type = "tuic";
proxy.version = 5;
}
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* { socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
} }
@@ -173,6 +183,11 @@ uri = $[^,]+
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; }
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
tag = match:[^=,]* { proxy.name = match.join("").trim(); } tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _ comma = _ "," _

View File

@@ -4,11 +4,27 @@ export default function Clash_Producer() {
const type = 'ALL'; const type = 'ALL';
const produce = (proxies) => { const produce = (proxies) => {
// filter unsupported proxies // filter unsupported proxies
proxies = proxies.filter((proxy) => proxies = proxies.filter((proxy) => {
['ss', 'ssr', 'vmess', 'socks', 'http', 'snell', 'trojan'].includes( if (
proxy.type, ![
), 'ss',
); 'ssr',
'vmess',
'socks',
'http',
'snell',
'trojan',
].includes(proxy.type)
) {
return false;
} else if (
proxy.type === 'snell' &&
String(proxy.version) === '4'
) {
return false;
}
return true;
});
return ( return (
'proxies:\n' + 'proxies:\n' +
proxies proxies
@@ -25,6 +41,38 @@ export default function Clash_Producer() {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;
delete proxy.sni; delete proxy.sni;
} }
// https://dreamacro.github.io/clash/configuration/outbound.html#vmess
if (
isPresent(proxy, 'cipher') &&
![
'auto',
'aes-128-gcm',
'chacha20-poly1305',
'none',
].includes(proxy.cipher)
) {
proxy.cipher = 'auto';
}
}
if (
['vmess', 'vless'].includes(proxy.type) &&
proxy.network === 'http'
) {
let httpPath = proxy['http-opts']?.path;
if (
isPresent(proxy, 'http-opts.path') &&
!Array.isArray(httpPath)
) {
proxy['http-opts'].path = [httpPath];
}
let httpHost = proxy['http-opts']?.headers?.Host;
if (
isPresent(proxy, 'http-opts.headers.Host') &&
!Array.isArray(httpHost)
) {
proxy['http-opts'].headers.Host = [httpHost];
}
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];

View File

@@ -127,9 +127,7 @@ function trojan(proxy) {
function vmess(proxy) { function vmess(proxy) {
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=vmess,${proxy.server},${proxy.port},${ `${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
proxy.cipher === 'auto' ? 'none' : proxy.cipher
},"${proxy.uuid}"`,
); );
// transport // transport
@@ -146,12 +144,14 @@ function vmess(proxy) {
); );
} else if (proxy.network === 'http') { } else if (proxy.network === 'http') {
result.append(`,transport=http`); result.append(`,transport=http`);
let httpPath = proxy['http-opts']?.path;
let httpHost = proxy['http-opts']?.headers?.Host;
result.appendIfPresent( result.appendIfPresent(
`,path=${proxy['http-opts'].path}`, `,path=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
'http-opts.path', 'http-opts.path',
); );
result.appendIfPresent( result.appendIfPresent(
`,host=${proxy['http-opts'].headers.Host}`, `,host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
'http-opts.headers.Host', 'http-opts.headers.Host',
); );
} else { } else {
@@ -208,12 +208,14 @@ function vless(proxy) {
); );
} else if (proxy.network === 'http') { } else if (proxy.network === 'http') {
result.append(`,transport=http`); result.append(`,transport=http`);
let httpPath = proxy['http-opts']?.path;
let httpHost = proxy['http-opts']?.headers?.Host;
result.appendIfPresent( result.appendIfPresent(
`,path=${proxy['http-opts'].path}`, `,path=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
'http-opts.path', 'http-opts.path',
); );
result.appendIfPresent( result.appendIfPresent(
`,host=${proxy['http-opts'].headers.Host}`, `,host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
'http-opts.headers.Host', 'http-opts.headers.Host',
); );
} else { } else {

View File

@@ -62,18 +62,20 @@ function shadowsocks(proxy) {
); );
} }
// tls fingerprint if (needTls(proxy)) {
appendIfPresent( // tls fingerprint
`,tls-cert-sha256=${proxy['tls-fingerprint']}`, appendIfPresent(
'tls-fingerprint', `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
); 'tls-fingerprint',
);
// tls verification // tls verification
appendIfPresent( appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`, `,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify', 'skip-cert-verify',
); );
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni'); appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo // tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -150,18 +152,20 @@ function trojan(proxy) {
append(`,over-tls=true`); append(`,over-tls=true`);
} }
// tls fingerprint if (needTls(proxy)) {
appendIfPresent( // tls fingerprint
`,tls-cert-sha256=${proxy['tls-fingerprint']}`, appendIfPresent(
'tls-fingerprint', `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
); 'tls-fingerprint',
);
// tls verification // tls verification
appendIfPresent( appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`, `,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify', 'skip-cert-verify',
); );
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni'); appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo // tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -206,12 +210,14 @@ function vmess(proxy) {
} else { } else {
throw new Error(`network ${proxy.network} is unsupported`); throw new Error(`network ${proxy.network} is unsupported`);
} }
let httpPath = proxy[`${proxy.network}-opts`]?.path;
let httpHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
appendIfPresent( appendIfPresent(
`,obfs-uri=${proxy[`${proxy.network}-opts`].path}`, `,obfs-uri=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
`${proxy.network}-opts.path`, `${proxy.network}-opts.path`,
); );
appendIfPresent( appendIfPresent(
`,obfs-host=${proxy[`${proxy.network}-opts`].headers.Host}`, `,obfs-host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
`${proxy.network}-opts.headers.Host`, `${proxy.network}-opts.headers.Host`,
); );
} else { } else {
@@ -219,18 +225,20 @@ function vmess(proxy) {
if (proxy.tls) append(`,obfs=over-tls`); if (proxy.tls) append(`,obfs=over-tls`);
} }
// tls fingerprint if (needTls(proxy)) {
appendIfPresent( // tls fingerprint
`,tls-cert-sha256=${proxy['tls-fingerprint']}`, appendIfPresent(
'tls-fingerprint', `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
); 'tls-fingerprint',
);
// tls verification // tls verification
appendIfPresent( appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`, `,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify', 'skip-cert-verify',
); );
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni'); appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// AEAD // AEAD
if (isPresent(proxy, 'aead')) { if (isPresent(proxy, 'aead')) {
@@ -266,18 +274,20 @@ function http(proxy) {
} }
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls'); appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
// tls fingerprint if (needTls(proxy)) {
appendIfPresent( // tls fingerprint
`,tls-cert-sha256=${proxy['tls-fingerprint']}`, appendIfPresent(
'tls-fingerprint', `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
); 'tls-fingerprint',
);
// tls verification // tls verification
appendIfPresent( appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`, `,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify', 'skip-cert-verify',
); );
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni'); appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo // tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -306,18 +316,20 @@ function socks5(proxy) {
} }
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls'); appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
// tls fingerprint if (needTls(proxy)) {
appendIfPresent( // tls fingerprint
`,tls-cert-sha256=${proxy['tls-fingerprint']}`, appendIfPresent(
'tls-fingerprint', `,tls-cert-sha256=${proxy['tls-fingerprint']}`,
); 'tls-fingerprint',
);
// tls verification // tls verification
appendIfPresent( appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`, `,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify', 'skip-cert-verify',
); );
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni'); appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo // tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -332,11 +344,5 @@ function socks5(proxy) {
} }
function needTls(proxy) { function needTls(proxy) {
return ( return proxy.tls;
proxy.tls ||
proxy.sni ||
typeof proxy['skip-cert-verify'] !== 'undefined' ||
typeof proxy['tls-fingerprint'] !== 'undefined' ||
typeof proxy['tls-host'] !== 'undefined'
);
} }

View File

@@ -6,6 +6,15 @@ export default function Stash_Producer() {
return ( return (
'proxies:\n' + 'proxies:\n' +
proxies proxies
.filter((proxy) => {
if (
proxy.type === 'snell' &&
String(proxy.version) === '4'
) {
return false;
}
return true;
})
.map((proxy) => { .map((proxy) => {
if (proxy.type === 'vmess') { if (proxy.type === 'vmess') {
// handle vmess aead // handle vmess aead
@@ -19,6 +28,54 @@ export default function Stash_Producer() {
proxy.servername = proxy.sni; proxy.servername = proxy.sni;
delete proxy.sni; delete proxy.sni;
} }
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L400
// https://stash.wiki/proxy-protocols/proxy-types#vmess
if (
isPresent(proxy, 'cipher') &&
![
'auto',
'aes-128-gcm',
'chacha20-poly1305',
'none',
].includes(proxy.cipher)
) {
proxy.cipher = 'auto';
}
} else if (proxy.type === 'tuic') {
if (isPresent(proxy, 'alpn')) {
proxy.alpn = Array.isArray(proxy.alpn)
? proxy.alpn
: [proxy.alpn];
} else {
proxy.alpn = ['h3'];
}
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
if (
(!proxy.token || proxy.token.length === 0) &&
!isPresent(proxy, 'version')
) {
proxy.version = 5;
}
}
if (
['vmess', 'vless'].includes(proxy.type) &&
proxy.network === 'http'
) {
let httpPath = proxy['http-opts']?.path;
if (
isPresent(proxy, 'http-opts.path') &&
!Array.isArray(httpPath)
) {
proxy['http-opts'].path = [httpPath];
}
let httpHost = proxy['http-opts']?.headers?.Host;
if (
isPresent(proxy, 'http-opts.headers.Host') &&
!Array.isArray(httpHost)
) {
proxy['http-opts'].headers.Host = [httpHost];
}
} }
delete proxy['tls-fingerprint']; delete proxy['tls-fingerprint'];

View File

@@ -4,6 +4,14 @@ import $ from '@/core/app';
const targetPlatform = 'Surge'; const targetPlatform = 'Surge';
const ipVersions = {
dual: 'dual',
ipv4: 'v4-only',
ipv6: 'v6-only',
'ipv4-prefer': 'prefer-v4',
'ipv6-prefer': 'prefer-v6',
};
export default function Surge_Producer() { export default function Surge_Producer() {
const produce = (proxy) => { const produce = (proxy) => {
switch (proxy.type) { switch (proxy.type) {
@@ -19,6 +27,8 @@ export default function Surge_Producer() {
return socks5(proxy); return socks5(proxy);
case 'snell': case 'snell':
return snell(proxy); return snell(proxy);
case 'tuic':
return tuic(proxy);
} }
throw new Error( throw new Error(
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`, `Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
@@ -215,15 +225,15 @@ function snell(proxy) {
// obfs // obfs
result.appendIfPresent( result.appendIfPresent(
`,obfs=${proxy['obfs-opts'].mode}`, `,obfs=${proxy['obfs-opts']?.mode}`,
'obfs-opts.mode', 'obfs-opts.mode',
); );
result.appendIfPresent( result.appendIfPresent(
`,obfs-host=${proxy['obfs-opts'].host}`, `,obfs-host=${proxy['obfs-opts']?.host}`,
'obfs-opts.host', 'obfs-opts.host',
); );
result.appendIfPresent( result.appendIfPresent(
`,obfs-uri=${proxy['obfs-opts'].path}`, `,obfs-uri=${proxy['obfs-opts']?.path}`,
'obfs-opts.path', 'obfs-opts.path',
); );
@@ -233,6 +243,49 @@ 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');
// reuse
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
return result.toString();
}
function tuic(proxy) {
const result = new Result(proxy);
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
let type = proxy.type;
if (!proxy.token || proxy.token.length === 0) {
type = 'tuic-v5';
}
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,uuid=${proxy.uuid}`, 'uuid');
result.appendIfPresent(`,password=${proxy.password}`, 'password');
result.appendIfPresent(`,token=${proxy.token}`, 'token');
result.appendIfPresent(
`,alpn=${Array.isArray(proxy.alpn) ? proxy.alpn[0] : proxy.alpn}`,
'alpn',
);
result.appendIfPresent(
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
'ip-version',
);
// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
// tfo
result.appendIfPresent(`,tfo=${proxy['fast-open']}`, 'fast-open');
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
return result.toString(); return result.toString();
} }
@@ -248,7 +301,13 @@ function handleTransport(result, proxy) {
if (isPresent(proxy, 'ws-opts.headers')) { if (isPresent(proxy, 'ws-opts.headers')) {
const headers = proxy['ws-opts'].headers; const headers = proxy['ws-opts'].headers;
const value = Object.keys(headers) const value = Object.keys(headers)
.map((k) => `${k}:${headers[k]}`) .map((k) => {
let v = headers[k];
if (['Host'].includes(k)) {
v = `"${v}"`;
}
return `${k}:${v}`;
})
.join('|'); .join('|');
if (isNotBlank(value)) { if (isNotBlank(value)) {
result.append(`,ws-headers=${value}`); result.append(`,ws-headers=${value}`);

View File

@@ -65,16 +65,23 @@ export default function URI_Producer() {
net: proxy.network || 'tcp', net: proxy.network || 'tcp',
tls: proxy.tls ? 'tls' : '', tls: proxy.tls ? 'tls' : '',
}; };
if (proxy.tls && proxy.sni) {
result.sni = proxy.sni;
}
// obfs // obfs
if (proxy.network === 'ws') { if (proxy.network === 'ws') {
result.path = proxy['ws-opts'].path || '/'; result.path = proxy['ws-opts'].path || '/';
result.host = proxy['ws-opts'].headers.Host || proxy.server; if (proxy['ws-opts'].headers.Host) {
result.host = proxy['ws-opts'].headers.Host;
}
} }
result = 'vmess://' + Base64.encode(JSON.stringify(result)); result = 'vmess://' + Base64.encode(JSON.stringify(result));
break; break;
case 'trojan': case 'trojan':
result = `trojan://${proxy.password}@${proxy.server}:${ result = `trojan://${proxy.password}@${proxy.server}:${
proxy.port proxy.port
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
}#${encodeURIComponent(proxy.name)}`; }#${encodeURIComponent(proxy.name)}`;
break; break;
} }

View File

@@ -19,7 +19,10 @@ export default function register($app) {
if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY); if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY);
// RESTful APIs // RESTful APIs
$app.route('/api/artifacts').get(getAllArtifacts).post(createArtifact); $app.route('/api/artifacts')
.get(getAllArtifacts)
.post(createArtifact)
.put(replaceArtifact);
$app.route('/api/artifact/:name') $app.route('/api/artifact/:name')
.get(getArtifact) .get(getArtifact)
@@ -32,6 +35,12 @@ function getAllArtifacts(req, res) {
success(res, allArtifacts); success(res, allArtifacts);
} }
function replaceArtifact(req, res) {
const allArtifacts = req.body;
$.write(allArtifacts, ARTIFACTS_KEY);
success(res);
}
async function getArtifact(req, res) { async function getArtifact(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name); name = decodeURIComponent(name);
@@ -131,7 +140,12 @@ async function deleteArtifact(req, res) {
files[encodeURIComponent(artifact.name)] = { files[encodeURIComponent(artifact.name)] = {
content: '', content: '',
}; };
await syncToGist(files); // 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
try {
await syncToGist(files);
} catch (i) {
$.error(`Function syncToGist: ${name} : ${i}`);
}
} }
// delete local cache // delete local cache
deleteByName(allArtifacts, name); deleteByName(allArtifacts, name);

View File

@@ -14,7 +14,8 @@ export default function register($app) {
$app.route('/api/collections') $app.route('/api/collections')
.get(getAllCollections) .get(getAllCollections)
.post(createCollection); .post(createCollection)
.put(replaceCollection);
} }
// collection API // collection API
@@ -111,3 +112,9 @@ function getAllCollections(req, res) {
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
success(res, allCols); success(res, allCols);
} }
function replaceCollection(req, res) {
const allCols = req.body;
$.write(allCols, COLLECTIONS_KEY);
success(res);
}

View File

@@ -20,7 +20,10 @@ export default function register($app) {
.patch(updateSubscription) .patch(updateSubscription)
.delete(deleteSubscription); .delete(deleteSubscription);
$app.route('/api/subs').get(getAllSubscriptions).post(createSubscription); $app.route('/api/subs')
.get(getAllSubscriptions)
.post(createSubscription)
.put(replaceSubscriptions);
} }
// subscriptions API // subscriptions API
@@ -66,10 +69,10 @@ async function getFlowInfo(req, res) {
} }
// unit is KB // unit is KB
const uploadMatch = flowHeaders.match(/upload=(-?)(\d+)/) const uploadMatch = flowHeaders.match(/upload=(-?)(\d+)/);
const upload = Number(uploadMatch[1] + uploadMatch[2]); const upload = Number(uploadMatch[1] + uploadMatch[2]);
const downloadMatch = flowHeaders.match(/download=(-?)(\d+)/) const downloadMatch = flowHeaders.match(/download=(-?)(\d+)/);
const download = Number(downloadMatch[1] + downloadMatch[2]); const download = Number(downloadMatch[1] + downloadMatch[2]);
const total = Number(flowHeaders.match(/total=(\d+)/)[1]); const total = Number(flowHeaders.match(/total=(\d+)/)[1]);
@@ -202,3 +205,9 @@ function getAllSubscriptions(req, res) {
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
success(res, allSubs); success(res, allSubs);
} }
function replaceSubscriptions(req, res) {
const allSubs = req.body;
$.write(allSubs, SUBS_KEY);
success(res);
}

View File

@@ -155,7 +155,7 @@ export function getFlag(name) {
'🇲🇴': ['Macao', '澳门', '澳門', 'CTM'], '🇲🇴': ['Macao', '澳门', '澳門', 'CTM'],
'🇲🇹': ['Malta', '马耳他'], '🇲🇹': ['Malta', '马耳他'],
'🇲🇽': ['Mexico', '墨西哥'], '🇲🇽': ['Mexico', '墨西哥'],
'🇲🇾': ['Malaysia', '马来西亚', '馬來西亞', '吉隆坡', '大馬'], '🇲🇾': ['Malaysia', '马来', '馬來', '吉隆坡', '大馬'],
'🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'], '🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'],
'🇳🇴': ['Norway', '挪威'], '🇳🇴': ['Norway', '挪威'],
'🇳🇵': ['Nepal', '尼泊尔'], '🇳🇵': ['Nepal', '尼泊尔'],

View File

@@ -40,6 +40,10 @@ export default class Gist {
} }
async upload(files) { async upload(files) {
if (Object.keys(files).length === 0) {
return Promise.reject('未提供需上传的文件');
}
const id = await this.locate(); const id = await this.locate();
if (id === -1) { if (id === -1) {

View File

@@ -0,0 +1,12 @@
#!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数
[MITM]
hostname = %APPEND% sub.store
[Script]
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 则可以使用此脚本
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js

View File

@@ -0,0 +1,11 @@
#!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 可能会爆内存, 如果不需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用不带 ability 参数版本
[MITM]
hostname = %APPEND% sub.store
[Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js

View File

@@ -1,10 +1,11 @@
#!name=Sub-Store #!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM #!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数
[MITM] [MITM]
hostname=%APPEND% sub.store hostname = %APPEND% sub.store
[Script] [Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js