mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eef4fa0751 | ||
|
|
84dc0d63f2 | ||
|
|
9ce14351c5 | ||
|
|
76e781c711 | ||
|
|
f0acf4a2a7 | ||
|
|
9abeb4ce7b | ||
|
|
153802c7c4 | ||
|
|
19418b631f | ||
|
|
97caeed208 | ||
|
|
dd8d1d85e8 | ||
|
|
14ed56b5d5 | ||
|
|
9785271c5b | ||
|
|
05bdf95a29 | ||
|
|
317a804b36 | ||
|
|
10ec8a25a2 | ||
|
|
aa0943a909 | ||
|
|
a0c1bbbf70 | ||
|
|
fea9de4fae |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.348",
|
||||
"version": "2.14.366",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -26,6 +26,7 @@
|
||||
"dns-packet": "^5.6.1",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"ip-address": "^9.0.5",
|
||||
"js-base64": "^3.7.2",
|
||||
"jsrsasign": "^11.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
25
backend/pnpm-lock.yaml
generated
25
backend/pnpm-lock.yaml
generated
@@ -32,6 +32,9 @@ dependencies:
|
||||
http-proxy-middleware:
|
||||
specifier: ^2.0.6
|
||||
version: registry.npmmirror.com/http-proxy-middleware@2.0.6
|
||||
ip-address:
|
||||
specifier: ^9.0.5
|
||||
version: registry.npmmirror.com/ip-address@9.0.5
|
||||
js-base64:
|
||||
specifier: ^3.7.2
|
||||
version: registry.npmmirror.com/js-base64@3.7.2
|
||||
@@ -6031,6 +6034,16 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/ip-address@9.0.5:
|
||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz}
|
||||
name: ip-address
|
||||
version: 9.0.5
|
||||
engines: {node: '>= 12'}
|
||||
dependencies:
|
||||
jsbn: registry.npmmirror.com/jsbn@1.1.0
|
||||
sprintf-js: registry.npmmirror.com/sprintf-js@1.1.3
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/ip6addr@0.2.5:
|
||||
resolution: {integrity: sha512-9RGGSB6Zc9Ox5DpDGFnJdIeF0AsqXzdH+FspCfPPaU/L/4tI6P+5lIoFUFm9JXs9IrJv1boqAaNCQmoDADTSKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ip6addr/-/ip6addr-0.2.5.tgz}
|
||||
name: ip6addr
|
||||
@@ -6576,6 +6589,12 @@ packages:
|
||||
version: 0.1.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/jsbn@1.1.0:
|
||||
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz}
|
||||
name: jsbn
|
||||
version: 1.1.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/jsesc@0.5.0:
|
||||
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz}
|
||||
name: jsesc
|
||||
@@ -9095,6 +9114,12 @@ packages:
|
||||
version: 1.0.3
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/sprintf-js@1.1.3:
|
||||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz}
|
||||
name: sprintf-js
|
||||
version: 1.1.3
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/sshpk@1.16.1:
|
||||
resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sshpk/-/sshpk-1.16.1.tgz}
|
||||
name: sshpk
|
||||
|
||||
@@ -2,7 +2,14 @@ import { Buffer } from 'buffer';
|
||||
import rs from '@/utils/rs';
|
||||
import YAML from '@/utils/yaml';
|
||||
import download from '@/utils/download';
|
||||
import { isIPv4, isIPv6, isValidPortNumber, isNotBlank } from '@/utils';
|
||||
import {
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
isValidPortNumber,
|
||||
isNotBlank,
|
||||
ipAddress,
|
||||
getRandomPort,
|
||||
} from '@/utils';
|
||||
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
||||
import PROXY_PREPROCESSORS from './preprocessors';
|
||||
import PROXY_PRODUCERS from './producers';
|
||||
@@ -214,6 +221,17 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
||||
delete proxy['tls-fingerprint'];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 端口跳跃
|
||||
if (proxy.ports) {
|
||||
if (!['ClashMeta'].includes(targetPlatform)) {
|
||||
proxy.ports = proxy.ports.replace(/\//g, ',');
|
||||
}
|
||||
if (!proxy.port) {
|
||||
proxy.port = getRandomPort(proxy.ports);
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
});
|
||||
|
||||
@@ -267,6 +285,8 @@ export const ProxyUtils = {
|
||||
parse,
|
||||
process: processFn,
|
||||
produce,
|
||||
ipAddress,
|
||||
getRandomPort,
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
isIP,
|
||||
@@ -407,6 +427,15 @@ function lastParse(proxy) {
|
||||
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) {
|
||||
delete proxy.ports;
|
||||
}
|
||||
if (
|
||||
['hysteria2'].includes(proxy.type) &&
|
||||
proxy.obfs &&
|
||||
!['salamander'].includes(proxy.obfs) &&
|
||||
!proxy['obfs-password']
|
||||
) {
|
||||
proxy['obfs-password'] = proxy.obfs;
|
||||
proxy.obfs = 'salamander';
|
||||
}
|
||||
if (['vless'].includes(proxy.type)) {
|
||||
// 删除 reality-opts: {}
|
||||
if (
|
||||
|
||||
@@ -158,7 +158,7 @@ function URI_SSR() {
|
||||
for (const item of line) {
|
||||
let [key, val] = item.split('=');
|
||||
val = val.trim();
|
||||
if (val.length > 0) {
|
||||
if (val.length > 0 && val !== '(null)') {
|
||||
other_params[key] = val;
|
||||
}
|
||||
}
|
||||
@@ -296,6 +296,11 @@ function URI_VMess() {
|
||||
? !params.verify_cert
|
||||
: undefined,
|
||||
};
|
||||
if (!proxy['skip-cert-verify'] && isPresent(params.allowInsecure)) {
|
||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(
|
||||
params.allowInsecure,
|
||||
);
|
||||
}
|
||||
// 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;
|
||||
@@ -351,6 +356,7 @@ function URI_VMess() {
|
||||
transportPath = '/';
|
||||
}
|
||||
}
|
||||
// 传输层应该有配置, 暂时不考虑兼容不给配置的节点
|
||||
if (transportPath || transportHost) {
|
||||
if (['grpc'].includes(proxy.network)) {
|
||||
proxy[`${proxy.network}-opts`] = {
|
||||
|
||||
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/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/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
proxy.type = "hysteria2";
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -172,6 +172,8 @@ vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseIn
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
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'); }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
@@ -180,6 +182,7 @@ ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
|
||||
|
||||
|
||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
|
||||
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/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/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||
proxy.type = "hysteria2";
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
@@ -170,6 +170,8 @@ vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseIn
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
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'); }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import resourceCache from '@/utils/resource-cache';
|
||||
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
import { isIPv4, isIPv6, ipAddress } from '@/utils';
|
||||
import { FULL } from '@/utils/logical';
|
||||
import { getFlag, removeFlag } from '@/utils/geo';
|
||||
import { doh } from '@/utils/dns';
|
||||
@@ -364,9 +364,6 @@ function parseIP4P(IP4P) {
|
||||
let server;
|
||||
let port;
|
||||
try {
|
||||
if (!/^2001::[^:]+:[^:]+:[^:]+$/.test(IP4P)) {
|
||||
throw new Error(`Invalid IP4P: ${IP4P}`);
|
||||
}
|
||||
let array = IP4P.split(':');
|
||||
|
||||
port = parseInt(array[2], 16);
|
||||
@@ -395,18 +392,23 @@ const DOMAIN_RESOLVERS = {
|
||||
const id = hex_md5(`CUSTOM:${url}:${domain}:${type}`);
|
||||
const cached = resourceCache.get(id);
|
||||
if (!noCache && cached) return cached;
|
||||
const answerType = type === 'IPv6' ? 'AAAA' : 'A';
|
||||
const res = await doh({
|
||||
url,
|
||||
domain,
|
||||
type: type === 'IPv6' ? 'AAAA' : 'A',
|
||||
type: answerType,
|
||||
timeout,
|
||||
edns,
|
||||
});
|
||||
|
||||
const { answers } = res;
|
||||
if (!Array.isArray(answers) || answers.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
const result = answers.map((i) => i?.data).filter((i) => i);
|
||||
const result = answers
|
||||
.filter((i) => i?.type === answerType)
|
||||
.map((i) => i?.data)
|
||||
.filter((i) => i);
|
||||
if (result.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
@@ -622,26 +624,44 @@ function ResolveDomainOperator({
|
||||
if (!p['_no-resolve']) {
|
||||
if (results[p.server]) {
|
||||
p._resolved_ips = results[p.server];
|
||||
const ip = Array.isArray(results[p.server])
|
||||
let ip = Array.isArray(results[p.server])
|
||||
? results[p.server][
|
||||
Math.floor(
|
||||
Math.random() * results[p.server].length,
|
||||
)
|
||||
]
|
||||
: results[p.server];
|
||||
if (_type === 'IP4P') {
|
||||
const { server, port } = parseIP4P(ip);
|
||||
if (server && port) {
|
||||
if (type === 'IPv6' && isIPv6(ip)) {
|
||||
try {
|
||||
ip = new ipAddress.Address6(ip).correctForm();
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`Failed to parse IPv6 address: ${ip}: ${e}`,
|
||||
);
|
||||
}
|
||||
if (/^2001::[^:]+:[^:]+:[^:]+$/.test(ip)) {
|
||||
p._IP4P = ip;
|
||||
const { server, port } = parseIP4P(ip);
|
||||
if (server && port) {
|
||||
p._domain = p.server;
|
||||
p.server = server;
|
||||
p.port = port;
|
||||
p.resolved = true;
|
||||
p._IPv4 = p.server;
|
||||
if (!isIP(p._IP)) {
|
||||
p._IP = p.server;
|
||||
}
|
||||
} else if (!p.resolved) {
|
||||
p.resolved = false;
|
||||
}
|
||||
} else {
|
||||
p._domain = p.server;
|
||||
p.server = server;
|
||||
p.port = port;
|
||||
p.server = ip;
|
||||
p.resolved = true;
|
||||
p._IPv4 = p.server;
|
||||
p[`_${type}`] = p.server;
|
||||
if (!isIP(p._IP)) {
|
||||
p._IP = p.server;
|
||||
}
|
||||
} else {
|
||||
p.resolved = false;
|
||||
}
|
||||
} else {
|
||||
p._domain = p.server;
|
||||
@@ -652,7 +672,7 @@ function ResolveDomainOperator({
|
||||
p._IP = p.server;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (!p.resolved) {
|
||||
p.resolved = false;
|
||||
}
|
||||
}
|
||||
@@ -967,6 +987,7 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
'scriptResourceCache',
|
||||
'flowUtils',
|
||||
'produceArtifact',
|
||||
'require',
|
||||
`${script}\n return ${name}`,
|
||||
)(
|
||||
$arguments,
|
||||
@@ -982,6 +1003,7 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
scriptResourceCache,
|
||||
flowUtils,
|
||||
produceArtifact,
|
||||
require,
|
||||
);
|
||||
} else {
|
||||
return new Function(
|
||||
@@ -992,6 +1014,7 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
'scriptResourceCache',
|
||||
'flowUtils',
|
||||
'produceArtifact',
|
||||
'require',
|
||||
|
||||
`${script}\n return ${name}`,
|
||||
)(
|
||||
@@ -1002,6 +1025,7 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
scriptResourceCache,
|
||||
flowUtils,
|
||||
produceArtifact,
|
||||
require,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,14 @@ function trojan(proxy) {
|
||||
|
||||
// sni
|
||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||
'tls-fingerprint',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||
'tls-pubkey-sha256',
|
||||
);
|
||||
|
||||
// tfo
|
||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||
@@ -215,6 +223,14 @@ function vmess(proxy) {
|
||||
|
||||
// sni
|
||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||
'tls-fingerprint',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||
'tls-pubkey-sha256',
|
||||
);
|
||||
|
||||
// AEAD
|
||||
if (isPresent(proxy, 'aead')) {
|
||||
@@ -286,6 +302,14 @@ function vless(proxy) {
|
||||
|
||||
// sni
|
||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||
'tls-fingerprint',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||
'tls-pubkey-sha256',
|
||||
);
|
||||
|
||||
// tfo
|
||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||
@@ -339,6 +363,11 @@ function socks5(proxy) {
|
||||
// tfo
|
||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||
|
||||
// udp
|
||||
if (proxy.udp) {
|
||||
result.append(`,udp=true`);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@@ -418,6 +447,14 @@ function hysteria2(proxy) {
|
||||
|
||||
// sni
|
||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||
result.appendIfPresent(
|
||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||
'tls-fingerprint',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||
'tls-pubkey-sha256',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||
'skip-cert-verify',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Result } from './utils';
|
||||
import { Result, isPresent } from './utils';
|
||||
import Surge_Producer from './surge';
|
||||
import { isIPv4, isIPv6, isPresent } from '@/utils';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
import $ from '@/core/app';
|
||||
|
||||
const targetPlatform = 'SurgeMac';
|
||||
@@ -84,6 +84,7 @@ function shadowsocksr(proxy) {
|
||||
for (const [key, value] of Object.entries({
|
||||
cipher: '-m',
|
||||
obfs: '-o',
|
||||
'obfs-param': '-g',
|
||||
password: '-k',
|
||||
port: '-p',
|
||||
protocol: '-O',
|
||||
@@ -92,8 +93,10 @@ function shadowsocksr(proxy) {
|
||||
'local-port': '-l',
|
||||
'local-address': '-b',
|
||||
})) {
|
||||
external_proxy.args.push(value);
|
||||
external_proxy.args.push(external_proxy[key]);
|
||||
if (external_proxy[key] != null) {
|
||||
external_proxy.args.push(value);
|
||||
external_proxy.args.push(external_proxy[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return external(external_proxy);
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { Base64 } from 'js-base64';
|
||||
import URI_Producer from './uri';
|
||||
import $ from '@/core/app';
|
||||
|
||||
const URI = URI_Producer();
|
||||
|
||||
export default function V2Ray_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies) =>
|
||||
Base64.encode(proxies.map((proxy) => URI.produce(proxy)).join('\n'));
|
||||
const produce = (proxies) => {
|
||||
let result = [];
|
||||
proxies.map((proxy) => {
|
||||
try {
|
||||
result.push(URI.produce(proxy));
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Cannot produce proxy: ${JSON.stringify(
|
||||
proxy,
|
||||
null,
|
||||
2,
|
||||
)}\nReason: ${err}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return Base64.encode(result.join('\n'));
|
||||
};
|
||||
|
||||
return { type, produce };
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ export function getFlag(name) {
|
||||
'沪俄',
|
||||
'Moscow',
|
||||
],
|
||||
'🇸🇦': ['Saudi', '沙特阿拉伯', '沙特'],
|
||||
'🇸🇦': ['Saudi', '沙特阿拉伯', '沙特', 'Riyadh', '利雅得'],
|
||||
'🇸🇪': ['Sweden', '瑞典'],
|
||||
'🇸🇬': [
|
||||
'Singapore',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as ipAddress from 'ip-address';
|
||||
// source: https://stackoverflow.com/a/36760050
|
||||
const IPV4_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
||||
|
||||
@@ -93,7 +94,25 @@ function getPolicyDescriptor(str) {
|
||||
// };
|
||||
// })();
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function getRandomPort(portString) {
|
||||
let portParts = portString.split(/,|\//);
|
||||
let randomPart = portParts[Math.floor(Math.random() * portParts.length)];
|
||||
if (randomPart.includes('-')) {
|
||||
let [min, max] = randomPart.split('-').map(Number);
|
||||
return getRandomInt(min, max);
|
||||
} else {
|
||||
return Number(randomPart);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ipAddress,
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
isValidPortNumber,
|
||||
@@ -103,4 +122,5 @@ export {
|
||||
getIfPresent,
|
||||
// utf8ArrayToStr,
|
||||
getPolicyDescriptor,
|
||||
getRandomPort,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,9 @@ export function getPlatformFromUserAgent({ ua, UA }) {
|
||||
return 'Stash';
|
||||
} else if (
|
||||
ua === 'meta' ||
|
||||
(ua.indexOf('clash') !== -1 && ua.indexOf('meta') !== -1)
|
||||
(ua.indexOf('clash') !== -1 && ua.indexOf('meta') !== -1) ||
|
||||
ua.indexOf('clash-verge') !== -1 ||
|
||||
ua.indexOf('flclash') !== -1
|
||||
) {
|
||||
return 'ClashMeta';
|
||||
} else if (ua.indexOf('clash') !== -1) {
|
||||
|
||||
37
config/Egern.yaml
Normal file
37
config/Egern.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Sub-Store
|
||||
description: '支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *'
|
||||
compat_arguments:
|
||||
ability: http-client-policy
|
||||
cronexp: 55 23 * * *
|
||||
sync: '"Sub-Store Sync"'
|
||||
timeout: '120'
|
||||
engine: auto
|
||||
produce: '"# Sub-Store Produce"'
|
||||
produce_cronexp: 50 */6 * * *
|
||||
produce_sub: '"sub1,sub2"'
|
||||
produce_col: '"col1,col2"'
|
||||
compat_arguments_desc: '\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 ''同步'' 或 ''同步配置''\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n脚本超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6️⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7️⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9️⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠️ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅'
|
||||
scriptings:
|
||||
- http_request:
|
||||
name: Sub-Store Core
|
||||
match: ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info)))
|
||||
script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js
|
||||
body_required: true
|
||||
- http_request:
|
||||
name: Sub-Store Simple
|
||||
match: ^https?:\/\/sub\.store
|
||||
script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js
|
||||
body_required: true
|
||||
- schedule:
|
||||
name: '{{{sync}}}'
|
||||
cron: '{{{cronexp}}}'
|
||||
script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||
- schedule:
|
||||
name: '{{{produce}}}'
|
||||
cron: '{{{produce_cronexp}}}'
|
||||
script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||
arguments:
|
||||
_compat.$argument: '"sub={{{produce_sub}}}&col={{{produce_col}}}"'
|
||||
mitm:
|
||||
hostnames:
|
||||
- sub.store
|
||||
@@ -45,6 +45,9 @@ Surge Mac 版如何支持 SSR, 如何去除 HTTP 传输层以支持 类似 VMess
|
||||
### 5. Shadowrocket
|
||||
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 即可。
|
||||
|
||||
### 6. Egern
|
||||
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml) 即可。
|
||||
|
||||
## 使用 Sub-Store
|
||||
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
||||
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
|
||||
@@ -56,4 +59,4 @@ https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%9
|
||||
|
||||
## 脚本使用说明
|
||||
|
||||
https://github.com/sub-store-org/Sub-Store/wiki/%E8%84%9A%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
|
||||
https://github.com/sub-store-org/Sub-Store/wiki/%E8%84%9A%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
function operator(proxies = [], targetPlatform, context) {
|
||||
// 支持快捷操作 不一定要写一个 function
|
||||
// 可参考 https://t.me/zhetengsha/970
|
||||
// https://t.me/zhetengsha/1009
|
||||
|
||||
// https://t.me/zhetengsha/1009
|
||||
|
||||
// proxies 为传入的内部节点数组
|
||||
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
||||
// 0. 结构大致参考了 Clash.Meta(mihomo), 可参考 mihomo 的文档, 例如 `xudp`, `smux` 都可以自己设置. 但是有私货, 下面是我能想起来的一些私货
|
||||
// 1. `_no-resolve` 为不解析域名
|
||||
// 2. 域名解析后 会多一个 `_resolved` 字段, 表示是否解析成功
|
||||
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_domain` 字段, `_resolved_ips` 为解析出的所有 IP
|
||||
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_IP4P`(若解析类型为 IPv6 且符合 IP4P 类型, 将自动转换), `_domain` 字段, `_resolved_ips` 为解析出的所有 IP
|
||||
// 4. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
||||
// 5. `_subName` 为单条订阅名
|
||||
// 6. `_collectionName` 为组合订阅名
|
||||
@@ -19,7 +18,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
||||
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
||||
// 12. 以 Surge 为例, 最新的参数一般我都会跟进, 以 Surge 文档为例, 一些常用的: TUIC/Hysteria 2 的 `ecn`, Snell 的 `reuse` 连接复用, QUIC 策略 block-quic`, Hysteria 2 下载带宽 `down`
|
||||
//
|
||||
//
|
||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||
|
||||
// $arguments 为传入的脚本参数
|
||||
@@ -30,7 +29,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
|
||||
// $substore 为 OpenAPI
|
||||
// 参考 https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
|
||||
|
||||
|
||||
// scriptResourceCache 缓存
|
||||
// 可参考 https://t.me/zhetengsha/1003
|
||||
// const cache = scriptResourceCache
|
||||
@@ -43,6 +42,8 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// parse, // 订阅解析
|
||||
// process, // 节点操作/文件操作
|
||||
// produce, // 输出订阅
|
||||
// getRandomPort, // 获取随机端口(参考 ports 端口跳跃的格式 443,8443,5000-6000)
|
||||
// ipAddress, // https://github.com/beaugunderson/ip-address
|
||||
// isIPv4,
|
||||
// isIPv6,
|
||||
// isIP,
|
||||
@@ -76,7 +77,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// }
|
||||
// })
|
||||
// $content = proxies
|
||||
|
||||
|
||||
// 2. sing-box
|
||||
|
||||
// 但是一般不需要这样用, 可参考
|
||||
@@ -110,7 +111,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
|
||||
// 4. 一个比较折腾的方案: 在脚本操作中, 把内容同步到另一个 gist
|
||||
// 见 https://t.me/zhetengsha/1428
|
||||
//
|
||||
//
|
||||
// const content = ProxyUtils.produce([...proxies], platform)
|
||||
|
||||
// // YAML
|
||||
@@ -130,12 +131,11 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// yaml.proxies.unshift(...clashMetaProxies)
|
||||
// $content = ProxyUtils.yaml.dump(yaml)
|
||||
|
||||
|
||||
// { $content, $files } will be passed to the next operator
|
||||
// { $content, $files } will be passed to the next operator
|
||||
// $content is the final content of the file
|
||||
|
||||
// flowUtils 为机场订阅流量信息处理工具
|
||||
// 可参考:
|
||||
// 可参考:
|
||||
// 1. https://t.me/zhetengsha/948
|
||||
|
||||
// context 为传入的上下文
|
||||
@@ -224,7 +224,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 参数说明
|
||||
// 可参考 https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
||||
|
||||
console.log(JSON.stringify(context, null, 2))
|
||||
console.log(JSON.stringify(context, null, 2));
|
||||
|
||||
return proxies
|
||||
return proxies;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user