mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
52 Commits
fix-negati
...
2.14.21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
497bc264e3 | ||
|
|
feb207b333 | ||
|
|
9ac1112b37 | ||
|
|
96769598ef | ||
|
|
f8ed6a3342 | ||
|
|
99b19c410d | ||
|
|
9e54507bbb | ||
|
|
20afa0ad22 | ||
|
|
c5b6960b35 | ||
|
|
4dd86cb368 | ||
|
|
4a0319e95f | ||
|
|
090d8a978f | ||
|
|
bc9fae6062 | ||
|
|
048344268c | ||
|
|
c5746f6a6b | ||
|
|
5cb226da62 | ||
|
|
d229047744 | ||
|
|
cb21a8e6ec | ||
|
|
537a00e8a9 | ||
|
|
b770578cba | ||
|
|
47a95e5a3d | ||
|
|
e99f13d487 | ||
|
|
fcab8401e0 | ||
|
|
431b1a3c8e | ||
|
|
36d46003d6 | ||
|
|
ff71f12996 | ||
|
|
f7c08e3a56 | ||
|
|
6eea8bb2d0 | ||
|
|
fc90e22a48 | ||
|
|
26d47b019b | ||
|
|
8e49a78f45 | ||
|
|
edee10cee3 | ||
|
|
20d958d74f | ||
|
|
6427f99545 | ||
|
|
7d2ea10206 | ||
|
|
e862235cb8 | ||
|
|
38f1728e42 | ||
|
|
d963be87f8 | ||
|
|
390e4540d2 | ||
|
|
0bd00406f3 | ||
|
|
b9ce4e8f20 | ||
|
|
de15bbf3ea | ||
|
|
5d6bd1415b | ||
|
|
6e9c3ead4c | ||
|
|
b3ccd5743a | ||
|
|
e18c215fe4 | ||
|
|
e4b54b43a1 | ||
|
|
21726bf950 | ||
|
|
f6ca9af00f | ||
|
|
39b79b6ca4 | ||
|
|
999271fa9d | ||
|
|
5de35c7720 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "14"
|
node-version: "16"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.13.4",
|
"version": "2.14.21",
|
||||||
"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": {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"@babel/preset-env": "^7.18.0",
|
"@babel/preset-env": "^7.18.0",
|
||||||
"@babel/register": "^7.17.7",
|
"@babel/register": "^7.17.7",
|
||||||
"@types/gulp": "^4.0.9",
|
"@types/gulp": "^4.0.9",
|
||||||
"axios": "^0.20.0",
|
"axios": "^0.21.2",
|
||||||
"babel-plugin-relative-path-import": "^2.0.1",
|
"babel-plugin-relative-path-import": "^2.0.1",
|
||||||
"babelify": "^10.0.0",
|
"babelify": "^10.0.0",
|
||||||
"browser-pack-flat": "^3.4.2",
|
"browser-pack-flat": "^3.4.2",
|
||||||
@@ -51,4 +51,4 @@
|
|||||||
"prettier-plugin-sort-imports": "^1.6.1",
|
"prettier-plugin-sort-imports": "^1.6.1",
|
||||||
"tinyify": "^3.0.0"
|
"tinyify": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8528
backend/pnpm-lock.yaml
generated
8528
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -9,3 +9,5 @@ export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
|||||||
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
||||||
export const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
|
export const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
|
||||||
export const CACHE_EXPIRATION_TIME_MS = 60 * 60 * 1000; // 1 hour
|
export const CACHE_EXPIRATION_TIME_MS = 60 * 60 * 1000; // 1 hour
|
||||||
|
export const SCRIPT_RESOURCE_CACHE_KEY = '#sub-store-cached-script-resource'; // cached-script-resource CSR
|
||||||
|
export const CSR_EXPIRATION_TIME_KEY = '#sub-store-csr-expiration-time'; // Custom expiration time key; (Loon|Surge) Default write 48 hour
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -281,6 +295,16 @@ function Clash_All() {
|
|||||||
if (proxy.type === 'vmess') {
|
if (proxy.type === 'vmess') {
|
||||||
proxy.sni = proxy.servername;
|
proxy.sni = proxy.servername;
|
||||||
delete 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
@@ -474,6 +498,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 +518,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(),
|
||||||
|
|||||||
@@ -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 = _ "," _
|
||||||
|
|||||||
@@ -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 = _ "," _
|
||||||
|
|||||||
@@ -95,22 +95,10 @@ function FullConfig() {
|
|||||||
return /^(\[server_local\]|\[Proxy\])/gm.test(raw);
|
return /^(\[server_local\]|\[Proxy\])/gm.test(raw);
|
||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
const regex = /^\[server_local]|\[Proxy]/gm;
|
const match = raw.match(
|
||||||
const match = regex.exec(raw);
|
/^\[server_local|Proxy\]([\s\S]+?)^\[.+?\](\r?\n|$)/im,
|
||||||
const results = [];
|
)?.[1];
|
||||||
|
return match || raw;
|
||||||
let first = true;
|
|
||||||
if (match) {
|
|
||||||
raw = raw.substring(match.index);
|
|
||||||
for (const line of raw.split('\n')) {
|
|
||||||
if (!first && !line.test(/^\s*\[/)) {
|
|
||||||
results.push(line);
|
|
||||||
}
|
|
||||||
// skip the first line
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
return results.join('\n');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
import { FULL } from '@/utils/logical';
|
import { FULL } from '@/utils/logical';
|
||||||
import { getFlag } from '@/utils/geo';
|
import { getFlag } from '@/utils/geo';
|
||||||
@@ -633,6 +634,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
'$httpClient',
|
'$httpClient',
|
||||||
'$notification',
|
'$notification',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
|
'scriptResourceCache',
|
||||||
`${script}\n return ${name}`,
|
`${script}\n return ${name}`,
|
||||||
)(
|
)(
|
||||||
$arguments,
|
$arguments,
|
||||||
@@ -645,6 +647,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
$notification,
|
$notification,
|
||||||
ProxyUtils,
|
ProxyUtils,
|
||||||
|
scriptResourceCache,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new Function(
|
return new Function(
|
||||||
@@ -652,7 +655,8 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
'$substore',
|
'$substore',
|
||||||
'lodash',
|
'lodash',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
|
'scriptResourceCache',
|
||||||
`${script}\n return ${name}`,
|
`${script}\n return ${name}`,
|
||||||
)($arguments, $, lodash, ProxyUtils);
|
)($arguments, $, lodash, ProxyUtils, scriptResourceCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Clash_Producer from './clash';
|
|||||||
import Stash_Producer from './stash';
|
import Stash_Producer from './stash';
|
||||||
import Loon_Producer from './loon';
|
import Loon_Producer from './loon';
|
||||||
import URI_Producer from './uri';
|
import URI_Producer from './uri';
|
||||||
|
import V2Ray_Producer from './v2ray';
|
||||||
import QX_Producer from './qx';
|
import QX_Producer from './qx';
|
||||||
|
|
||||||
function JSON_Producer() {
|
function JSON_Producer() {
|
||||||
@@ -17,6 +18,7 @@ export default {
|
|||||||
Loon: Loon_Producer(),
|
Loon: Loon_Producer(),
|
||||||
Clash: Clash_Producer(),
|
Clash: Clash_Producer(),
|
||||||
URI: URI_Producer(),
|
URI: URI_Producer(),
|
||||||
|
V2Ray: V2Ray_Producer(),
|
||||||
JSON: JSON_Producer(),
|
JSON: JSON_Producer(),
|
||||||
Stash: Stash_Producer(),
|
Stash: Stash_Producer(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
@@ -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}`,
|
||||||
@@ -55,6 +65,10 @@ function shadowsocks(proxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +101,10 @@ function trojan(proxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +145,9 @@ function vmess(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +176,10 @@ function http(proxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +210,10 @@ function socks5(proxy) {
|
|||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,20 +225,67 @@ 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',
|
||||||
);
|
);
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,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}`);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
12
backend/src/core/proxy-utils/producers/v2ray.js
Normal file
12
backend/src/core/proxy-utils/producers/v2ray.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
import URI_Producer from './uri';
|
||||||
|
|
||||||
|
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'));
|
||||||
|
return { type, produce };
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,8 +69,12 @@ async function getFlowInfo(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unit is KB
|
// unit is KB
|
||||||
const upload = Number(flowHeaders.match(/upload=(\d+)/)[1]);
|
const uploadMatch = flowHeaders.match(/upload=(-?)(\d+)/);
|
||||||
const download = Number(flowHeaders.match(/download=(\d+)/)[1]);
|
const upload = Number(uploadMatch[1] + uploadMatch[2]);
|
||||||
|
|
||||||
|
const downloadMatch = flowHeaders.match(/download=(-?)(\d+)/);
|
||||||
|
const download = Number(downloadMatch[1] + downloadMatch[2]);
|
||||||
|
|
||||||
const total = Number(flowHeaders.match(/total=(\d+)/)[1]);
|
const total = Number(flowHeaders.match(/total=(\d+)/)[1]);
|
||||||
|
|
||||||
// optional expire timestamp
|
// optional expire timestamp
|
||||||
@@ -198,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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { HTTP } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(url, ua) {
|
export default async function download(url, ua) {
|
||||||
|
const { isNode } = ENV();
|
||||||
ua = ua || 'Quantumult%20X/1.0.29 (iPhone14,5; iOS 15.4.1)';
|
ua = ua || 'Quantumult%20X/1.0.29 (iPhone14,5; iOS 15.4.1)';
|
||||||
const id = hex_md5(ua + url);
|
const id = hex_md5(ua + url);
|
||||||
if (tasks.has(id)) {
|
if (!isNode && tasks.has(id)) {
|
||||||
return tasks.get(id);
|
return tasks.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ export default async function download(url, ua) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tasks.set(id, result);
|
if (!isNode) {
|
||||||
|
tasks.set(id, result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function getFlag(name) {
|
|||||||
'🇲🇴': ['Macao', '澳门', '澳門', 'CTM'],
|
'🇲🇴': ['Macao', '澳门', '澳門', 'CTM'],
|
||||||
'🇲🇹': ['Malta', '马耳他'],
|
'🇲🇹': ['Malta', '马耳他'],
|
||||||
'🇲🇽': ['Mexico', '墨西哥'],
|
'🇲🇽': ['Mexico', '墨西哥'],
|
||||||
'🇲🇾': ['Malaysia', '马来西亚', '馬來西亞', '吉隆坡', '大馬'],
|
'🇲🇾': ['Malaysia', '马来', '馬來', '吉隆坡', '大馬'],
|
||||||
'🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'],
|
'🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'],
|
||||||
'🇳🇴': ['Norway', '挪威'],
|
'🇳🇴': ['Norway', '挪威'],
|
||||||
'🇳🇵': ['Nepal', '尼泊尔'],
|
'🇳🇵': ['Nepal', '尼泊尔'],
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -16,8 +16,13 @@ class ResourceCache {
|
|||||||
let clear = false;
|
let clear = false;
|
||||||
Object.entries(this.resourceCache).forEach((entry) => {
|
Object.entries(this.resourceCache).forEach((entry) => {
|
||||||
const [id, updated] = entry;
|
const [id, updated] = entry;
|
||||||
if (new Date().getTime() - updated > this.expires) {
|
if (!updated.time) {
|
||||||
|
// clear old version cache
|
||||||
|
delete this.resourceCache[id];
|
||||||
$.delete(`#${id}`);
|
$.delete(`#${id}`);
|
||||||
|
clear = true;
|
||||||
|
}
|
||||||
|
if (new Date().getTime() - updated.time > this.expires) {
|
||||||
delete this.resourceCache[id];
|
delete this.resourceCache[id];
|
||||||
clear = true;
|
clear = true;
|
||||||
}
|
}
|
||||||
@@ -26,9 +31,6 @@ class ResourceCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
revokeAll() {
|
revokeAll() {
|
||||||
Object.keys(this.resourceCache).forEach((id) => {
|
|
||||||
$.delete(`#${id}`);
|
|
||||||
});
|
|
||||||
this.resourceCache = {};
|
this.resourceCache = {};
|
||||||
this._persist();
|
this._persist();
|
||||||
}
|
}
|
||||||
@@ -38,17 +40,16 @@ class ResourceCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(id) {
|
get(id) {
|
||||||
const updated = this.resourceCache[id];
|
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||||
return $.read(`#${id}`);
|
return this.resourceCache[id].data;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(id, value) {
|
set(id, value) {
|
||||||
this.resourceCache[id] = new Date().getTime();
|
this.resourceCache[id] = { time: new Date().getTime(), data: value }
|
||||||
this._persist();
|
this._persist();
|
||||||
$.write(value, `#${id}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
105
backend/src/utils/script-resource-cache.js
Normal file
105
backend/src/utils/script-resource-cache.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import $ from '@/core/app';
|
||||||
|
import {
|
||||||
|
SCRIPT_RESOURCE_CACHE_KEY,
|
||||||
|
CSR_EXPIRATION_TIME_KEY,
|
||||||
|
} from '@/constants';
|
||||||
|
|
||||||
|
class ResourceCache {
|
||||||
|
constructor() {
|
||||||
|
this.expires = getExpiredTime();
|
||||||
|
if (!$.read(SCRIPT_RESOURCE_CACHE_KEY)) {
|
||||||
|
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
|
this.resourceCache = JSON.parse($.read(SCRIPT_RESOURCE_CACHE_KEY));
|
||||||
|
this._cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup() {
|
||||||
|
// clear obsolete cached resource
|
||||||
|
let clear = false;
|
||||||
|
Object.entries(this.resourceCache).forEach((entry) => {
|
||||||
|
const [id, updated] = entry;
|
||||||
|
if (!updated.time) {
|
||||||
|
// clear old version cache
|
||||||
|
delete this.resourceCache[id];
|
||||||
|
$.delete(`#${id}`);
|
||||||
|
clear = true;
|
||||||
|
}
|
||||||
|
if (new Date().getTime() - updated.time > this.expires) {
|
||||||
|
delete this.resourceCache[id];
|
||||||
|
clear = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (clear) this._persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
revokeAll() {
|
||||||
|
this.resourceCache = {};
|
||||||
|
this._persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
_persist() {
|
||||||
|
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(id) {
|
||||||
|
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||||
|
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||||
|
return this.resourceCache[id].data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gettime(id) {
|
||||||
|
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||||
|
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||||
|
return this.resourceCache[id].time;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(id, value) {
|
||||||
|
this.resourceCache[id] = { time: new Date().getTime(), data: value };
|
||||||
|
this._persist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpiredTime() {
|
||||||
|
// console.log($.read(CSR_EXPIRATION_TIME_KEY));
|
||||||
|
if (!$.read(CSR_EXPIRATION_TIME_KEY)) {
|
||||||
|
$.write('1728e5', CSR_EXPIRATION_TIME_KEY); // 48 * 3600 * 1000
|
||||||
|
}
|
||||||
|
let expiration = 1728e5;
|
||||||
|
if ($.env.isLoon) {
|
||||||
|
const loont = {
|
||||||
|
// Loon 插件自义定
|
||||||
|
'1\u5206\u949f': 6e4,
|
||||||
|
'5\u5206\u949f': 3e5,
|
||||||
|
'10\u5206\u949f': 6e5,
|
||||||
|
'30\u5206\u949f': 18e5, // "30分钟"
|
||||||
|
'1\u5c0f\u65f6': 36e5,
|
||||||
|
'2\u5c0f\u65f6': 72e5,
|
||||||
|
'3\u5c0f\u65f6': 108e5,
|
||||||
|
'6\u5c0f\u65f6': 216e5,
|
||||||
|
'12\u5c0f\u65f6': 432e5,
|
||||||
|
'24\u5c0f\u65f6': 864e5,
|
||||||
|
'48\u5c0f\u65f6': 1728e5,
|
||||||
|
'72\u5c0f\u65f6': 2592e5, // "72小时"
|
||||||
|
'\u53c2\u6570\u4f20\u5165': 'readcachets', // "参数输入"
|
||||||
|
};
|
||||||
|
let intimed = $.read('#\u8282\u70b9\u7f13\u5b58\u6709\u6548\u671f'); // Loon #节点缓存有效期
|
||||||
|
// console.log(intimed);
|
||||||
|
if (intimed in loont) {
|
||||||
|
expiration = loont[intimed];
|
||||||
|
if (expiration === 'readcachets') {
|
||||||
|
expiration = intimed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expiration;
|
||||||
|
} else {
|
||||||
|
expiration = $.read(CSR_EXPIRATION_TIME_KEY);
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ResourceCache();
|
||||||
@@ -4,12 +4,16 @@
|
|||||||
#!author=Peng-YM
|
#!author=Peng-YM
|
||||||
#!homepage=https://github.com/Peng-YM/Sub-Store
|
#!homepage=https://github.com/Peng-YM/Sub-Store
|
||||||
#!icon=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png
|
#!icon=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png
|
||||||
|
#!select = 节点缓存有效期,1分钟,5分钟,10分钟,30分钟,1小时,2小时,3小时,6小时,12小时,24小时,48小时,72小时,参数传入
|
||||||
|
|
||||||
|
[Rule]
|
||||||
|
DOMAIN,sub-store.vercel.app,PROXY
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname=sub.store
|
hostname=sub.store
|
||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
http-request ^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, tag=Sub-Store Core
|
http-request ^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, tag=Sub-Store Core
|
||||||
http-request https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
||||||
|
|
||||||
cron "0 0 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync
|
cron "0 0 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync
|
||||||
12
config/Surge-Noability.sgmodule
Normal file
12
config/Surge-Noability.sgmodule
Normal 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
|
||||||
11
config/Surge-ability.sgmodule
Normal file
11
config/Surge-ability.sgmodule
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ class ResourceCache {
|
|||||||
let clear = false;
|
let clear = false;
|
||||||
Object.entries(this.resourceCache).forEach((entry) => {
|
Object.entries(this.resourceCache).forEach((entry) => {
|
||||||
const [id, updated] = entry;
|
const [id, updated] = entry;
|
||||||
if (new Date().getTime() - updated > this.expires) {
|
if (!updated.time) {
|
||||||
|
// clear old version cache
|
||||||
|
delete this.resourceCache[id];
|
||||||
$.delete(`#${id}`);
|
$.delete(`#${id}`);
|
||||||
|
clear = true;
|
||||||
|
}
|
||||||
|
if (new Date().getTime() - updated.time > this.expires) {
|
||||||
delete this.resourceCache[id];
|
delete this.resourceCache[id];
|
||||||
clear = true;
|
clear = true;
|
||||||
}
|
}
|
||||||
@@ -27,9 +32,6 @@ class ResourceCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
revokeAll() {
|
revokeAll() {
|
||||||
Object.keys(this.resourceCache).forEach((id) => {
|
|
||||||
$.delete(`#${id}`);
|
|
||||||
});
|
|
||||||
this.resourceCache = {};
|
this.resourceCache = {};
|
||||||
this._persist();
|
this._persist();
|
||||||
}
|
}
|
||||||
@@ -39,17 +41,16 @@ class ResourceCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(id) {
|
get(id) {
|
||||||
const updated = this.resourceCache[id];
|
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||||
return $.read(`#${id}`);
|
return this.resourceCache[id].data;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(id, value) {
|
set(id, value) {
|
||||||
this.resourceCache[id] = new Date().getTime();
|
this.resourceCache[id] = { time: new Date().getTime(), data: value }
|
||||||
this._persist();
|
this._persist();
|
||||||
$.write(value, `#${id}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user