mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21ce0be16 | ||
|
|
fa65eb1850 | ||
|
|
79c9b89c5f | ||
|
|
fca508ba8a | ||
|
|
21b531a44d | ||
|
|
4e5b46a43d | ||
|
|
bf81ca4acf | ||
|
|
e7c0b23222 | ||
|
|
40fb0fd7f3 | ||
|
|
b061fca356 | ||
|
|
d3c6c99b0a | ||
|
|
3fbc280e28 | ||
|
|
9e3e4c6e46 | ||
|
|
bc0dd4b175 | ||
|
|
7603fac036 | ||
|
|
9acc161684 | ||
|
|
024582a99d | ||
|
|
1d31a80b9f | ||
|
|
b2d0276836 | ||
|
|
3211fbf357 | ||
|
|
33a17c2d66 | ||
|
|
2c89a0ddbd | ||
|
|
939022e5a3 | ||
|
|
59bca5670d | ||
|
|
07b38cf971 | ||
|
|
28186f596f | ||
|
|
ea31b1d0ec | ||
|
|
77191f9caa | ||
|
|
07a270963e | ||
|
|
f1e1d50a2c | ||
|
|
a65cd1f1c9 | ||
|
|
5b0e2e1ef2 | ||
|
|
b29266ac57 |
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@@ -64,3 +64,8 @@ jobs:
|
|||||||
./backend/dist/sub-store-parser.loon.min.js
|
./backend/dist/sub-store-parser.loon.min.js
|
||||||
./backend/dist/cron-sync-artifacts.min.js
|
./backend/dist/cron-sync-artifacts.min.js
|
||||||
./backend/dist/sub-store.bundle.js
|
./backend/dist/sub-store.bundle.js
|
||||||
|
- name: Sync to GitLab
|
||||||
|
env:
|
||||||
|
GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
curl -X POST --fail -F token=$GITLAB_PIPELINE_TOKEN -F ref=master https://gitlab.com/api/v4/projects/48891296/trigger/pipeline
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.
|
Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/Peng-YM/Sub-Store/actions/workflows/main.yml)     
|
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
||||||
|
|
||||||
[](https://www.buymeacoffee.com/PengYM)
|
[](https://www.buymeacoffee.com/PengYM)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ Core functionalities:
|
|||||||
- [x] V2RayN URI
|
- [x] V2RayN URI
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
|
||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS)
|
||||||
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, SSR(external, only for macOS))
|
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, SSR(external, only for macOS), WireGuard(Surge to Surge))
|
||||||
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
|
||||||
@@ -99,6 +99,11 @@ This project is under the GPL V3 LICENSE.
|
|||||||
|
|
||||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FPeng-YM%2FSub-Store?ref=badge_large)
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2FPeng-YM%2FSub-Store?ref=badge_large)
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#sub-store-org/sub-store&Date)
|
||||||
|
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
|
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* @updated: <%= updated %>
|
* @updated: <%= updated %>
|
||||||
* @version: <%= pkg.version %>
|
* @version: <%= pkg.version %>
|
||||||
* @author: Peng-YM
|
* @author: Peng-YM
|
||||||
* @github: https://github.com/Peng-YM/Sub-Store
|
* @github: https://github.com/sub-store-org/Sub-Store
|
||||||
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.45",
|
"version": "2.14.74",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ export const SCHEMA_VERSION_KEY = 'schemaVersion';
|
|||||||
export const SETTINGS_KEY = 'settings';
|
export const SETTINGS_KEY = 'settings';
|
||||||
export const SUBS_KEY = 'subs';
|
export const SUBS_KEY = 'subs';
|
||||||
export const COLLECTIONS_KEY = 'collections';
|
export const COLLECTIONS_KEY = 'collections';
|
||||||
|
export const FILES_KEY = 'files';
|
||||||
|
export const MODULES_KEY = 'modules';
|
||||||
export const ARTIFACTS_KEY = 'artifacts';
|
export const ARTIFACTS_KEY = 'artifacts';
|
||||||
export const RULES_KEY = 'rules';
|
export const RULES_KEY = 'rules';
|
||||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ function parse(raw) {
|
|||||||
return proxies;
|
return proxies;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function process(proxies, operators = [], targetPlatform) {
|
async function process(proxies, operators = [], targetPlatform, source) {
|
||||||
for (const item of operators) {
|
for (const item of operators) {
|
||||||
// process script
|
// process script
|
||||||
let script;
|
let script;
|
||||||
const $arguments = {};
|
let $arguments = {};
|
||||||
if (item.type.indexOf('Script') !== -1) {
|
if (item.type.indexOf('Script') !== -1) {
|
||||||
const { mode, content } = item.args;
|
const { mode, content } = item.args;
|
||||||
if (mode === 'link') {
|
if (mode === 'link') {
|
||||||
@@ -75,10 +75,19 @@ async function process(proxies, operators = [], targetPlatform) {
|
|||||||
// extract link arguments
|
// extract link arguments
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
if (rawArgs.length > 1) {
|
if (rawArgs.length > 1) {
|
||||||
for (const pair of rawArgs[1].split('&')) {
|
try {
|
||||||
const key = pair.split('=')[0];
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
const value = pair.split('=')[1] || true;
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
$arguments[key] = value;
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +122,7 @@ async function process(proxies, operators = [], targetPlatform) {
|
|||||||
script,
|
script,
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
$arguments,
|
$arguments,
|
||||||
|
source,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
processor = PROXY_PROCESSORS[item.type](item.args || {});
|
processor = PROXY_PROCESSORS[item.type](item.args || {});
|
||||||
@@ -199,7 +209,7 @@ function lastParse(proxy) {
|
|||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (proxy.tls && !proxy.sni) {
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ function URI_VMess() {
|
|||||||
params.add = server;
|
params.add = server;
|
||||||
}
|
}
|
||||||
const proxy = {
|
const proxy = {
|
||||||
name: params.ps ?? params.remark,
|
name: params.ps ?? params.remarks,
|
||||||
type: 'vmess',
|
type: 'vmess',
|
||||||
server: params.add,
|
server: params.add,
|
||||||
port: parseInt(getIfPresent(params.port), 10),
|
port: parseInt(getIfPresent(params.port), 10),
|
||||||
@@ -267,9 +267,19 @@ function URI_VMess() {
|
|||||||
params.obfs === 'http'
|
params.obfs === 'http'
|
||||||
) {
|
) {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
|
} else if (['grpc'].includes(params.net)) {
|
||||||
|
proxy.network = 'grpc';
|
||||||
}
|
}
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
let transportHost = params.host ?? params.obfsParam;
|
let transportHost = params.host ?? params.obfsParam;
|
||||||
|
try {
|
||||||
|
const parsedObfs = JSON.parse(transportHost);
|
||||||
|
const parsedHost = parsedObfs?.Host;
|
||||||
|
if (parsedHost) {
|
||||||
|
transportHost = parsedHost;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
let transportPath = params.path;
|
let transportPath = params.path;
|
||||||
|
|
||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
@@ -285,10 +295,17 @@ function URI_VMess() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (transportPath || transportHost) {
|
if (transportPath || transportHost) {
|
||||||
proxy[`${proxy.network}-opts`] = {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
path: getIfNotBlank(transportPath),
|
proxy[`${proxy.network}-opts`] = {
|
||||||
headers: { Host: getIfNotBlank(transportHost) },
|
'grpc-service-name': getIfNotBlank(transportPath),
|
||||||
};
|
'_grpc-type': getIfNotBlank(params.type),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
proxy[`${proxy.network}-opts`] = {
|
||||||
|
path: getIfNotBlank(transportPath),
|
||||||
|
headers: { Host: getIfNotBlank(transportHost) },
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
@@ -365,6 +382,10 @@ function URI_VLESS() {
|
|||||||
if (params.serviceName) {
|
if (params.serviceName) {
|
||||||
opts[`${proxy.network}-service-name`] = params.serviceName;
|
opts[`${proxy.network}-service-name`] = params.serviceName;
|
||||||
}
|
}
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
opts['_grpc-type'] = params.mode || 'gun';
|
||||||
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
@@ -383,6 +404,53 @@ function URI_VLESS() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_Hysteria2() {
|
||||||
|
const name = 'URI Hysteria2 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^hysteria2:\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
line = line.split('hysteria2://')[1];
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [__, password, server, ___, port, addons, name] =
|
||||||
|
/^(.*?)@(.*?)(:(\d+))?\/?\?(.*?)(?:#(.*?))$/.exec(line);
|
||||||
|
port = parseInt(`${port}`, 10);
|
||||||
|
if (isNaN(port)) {
|
||||||
|
port = 443;
|
||||||
|
}
|
||||||
|
password = decodeURIComponent(password);
|
||||||
|
name = decodeURIComponent(name) ?? `Hysteria2 ${server}:${port}`;
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
type: 'hysteria2',
|
||||||
|
name,
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
for (const addon of addons.split('&')) {
|
||||||
|
const [key, valueRaw] = addon.split('=');
|
||||||
|
let value = valueRaw;
|
||||||
|
value = decodeURIComponent(valueRaw);
|
||||||
|
params[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.sni = params.sni;
|
||||||
|
if (!proxy.sni && params.peer) {
|
||||||
|
proxy.sni = params.peer;
|
||||||
|
}
|
||||||
|
proxy.obfs = params.obfs;
|
||||||
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
|
proxy.fingerprint = params.pinSHA256;
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
// Trojan URI format
|
// Trojan URI format
|
||||||
function URI_Trojan() {
|
function URI_Trojan() {
|
||||||
@@ -423,6 +491,7 @@ function Clash_All() {
|
|||||||
'tuic',
|
'tuic',
|
||||||
'vless',
|
'vless',
|
||||||
'hysteria',
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
@@ -739,7 +808,7 @@ function Surge_Socks5() {
|
|||||||
function Surge_Snell() {
|
function Surge_Snell() {
|
||||||
const name = 'Surge Snell Parser';
|
const name = 'Surge Snell Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
return /^.*=\s*snell?/.test(line.split(',')[0]);
|
return /^.*=\s*snell/.test(line.split(',')[0]);
|
||||||
};
|
};
|
||||||
const parse = (line) => getSurgeParser().parse(line);
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
@@ -748,7 +817,24 @@ function Surge_Snell() {
|
|||||||
function Surge_Tuic() {
|
function Surge_Tuic() {
|
||||||
const name = 'Surge Tuic Parser';
|
const name = 'Surge Tuic Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
return /^.*=\s*tuic(-v5)??/.test(line.split(',')[0]);
|
return /^.*=\s*tuic(-v5)?/.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
function Surge_WireGuard() {
|
||||||
|
const name = 'Surge WireGuard Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*wireguard/.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
|
function Surge_Hysteria2() {
|
||||||
|
const name = 'Surge Hysteria2 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*hysteria2/.test(line.split(',')[0]);
|
||||||
};
|
};
|
||||||
const parse = (line) => getSurgeParser().parse(line);
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
@@ -759,6 +845,7 @@ export default [
|
|||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
URI_VLESS(),
|
URI_VLESS(),
|
||||||
|
URI_Hysteria2(),
|
||||||
URI_Trojan(),
|
URI_Trojan(),
|
||||||
Clash_All(),
|
Clash_All(),
|
||||||
Surge_SS(),
|
Surge_SS(),
|
||||||
@@ -767,6 +854,8 @@ export default [
|
|||||||
Surge_Http(),
|
Surge_Http(),
|
||||||
Surge_Snell(),
|
Surge_Snell(),
|
||||||
Surge_Tuic(),
|
Surge_Tuic(),
|
||||||
|
Surge_WireGuard(),
|
||||||
|
Surge_Hysteria2(),
|
||||||
Surge_Socks5(),
|
Surge_Socks5(),
|
||||||
Loon_SS(),
|
Loon_SS(),
|
||||||
Loon_SSR(),
|
Loon_SSR(),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls
|
|||||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
http = tag equals "http" address (username password)? (fast_open/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -76,13 +76,19 @@ 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)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
|
||||||
|
proxy.type = "wireguard-surge";
|
||||||
|
}
|
||||||
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/download_bandwidth/ecn/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
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";
|
||||||
}
|
}
|
||||||
@@ -185,8 +191,15 @@ 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; }
|
||||||
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
tfo = comma "tfo" 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(""); }
|
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
||||||
|
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
||||||
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ https = tag equals "https" address (username password)? (sni/tls_fingerprint/tls
|
|||||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
http = tag equals "http" address (username password)? (fast_open/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/reuse/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -74,13 +74,19 @@ 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)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/ecn/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/ecn/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/others)* {
|
||||||
|
proxy.type = "wireguard-surge";
|
||||||
|
}
|
||||||
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/download_bandwidth/ecn/others)* {
|
||||||
|
proxy.type = "hysteria2";
|
||||||
|
}
|
||||||
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";
|
||||||
}
|
}
|
||||||
@@ -183,8 +189,15 @@ 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; }
|
||||||
|
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||||
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
tfo = comma "tfo" 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(""); }
|
ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
||||||
|
section_name = comma "section-name" equals match:[^,]+ { proxy["section-name"] = match.join(""); }
|
||||||
|
no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-alert"] = match.join(""); }
|
||||||
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ function Base64Encoded() {
|
|||||||
const name = 'Base64 Pre-processor';
|
const name = 'Base64 Pre-processor';
|
||||||
|
|
||||||
const keys = [
|
const keys = [
|
||||||
'dm1lc3M',
|
'dm1lc3M', // vmess
|
||||||
'c3NyOi8v',
|
'c3NyOi8v', // ssr://
|
||||||
'dHJvamFu',
|
'dHJvamFu', // trojan
|
||||||
'c3M6Ly',
|
'c3M6Ly', // ss:/
|
||||||
'c3NkOi8v',
|
'c3NkOi8v', // ssd://
|
||||||
'c2hhZG93',
|
'c2hhZG93', // shadow
|
||||||
'aHR0c',
|
'aHR0c', // htt
|
||||||
|
'dmxlc3M=', // vless
|
||||||
|
'aHlzdGVyaWEy', // hysteria2
|
||||||
];
|
];
|
||||||
|
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import lodash from 'lodash';
|
|||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||||
@@ -294,7 +295,7 @@ function RegexDeleteOperator(regex) {
|
|||||||
1. This function name should be `operator`!
|
1. This function name should be `operator`!
|
||||||
2. Always declare variables before using them!
|
2. Always declare variables before using them!
|
||||||
*/
|
*/
|
||||||
function ScriptOperator(script, targetPlatform, $arguments) {
|
function ScriptOperator(script, targetPlatform, $arguments, source) {
|
||||||
return {
|
return {
|
||||||
name: 'Script Operator',
|
name: 'Script Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
@@ -305,7 +306,7 @@ function ScriptOperator(script, targetPlatform, $arguments) {
|
|||||||
script,
|
script,
|
||||||
$arguments,
|
$arguments,
|
||||||
);
|
);
|
||||||
output = operator(proxies, targetPlatform);
|
output = operator(proxies, targetPlatform, { source, ...env });
|
||||||
})();
|
})();
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
@@ -562,7 +563,7 @@ function TypeFilter(types) {
|
|||||||
1. This function name should be `filter`!
|
1. This function name should be `filter`!
|
||||||
2. Always declare variables before using them!
|
2. Always declare variables before using them!
|
||||||
*/
|
*/
|
||||||
function ScriptFilter(script, targetPlatform, $arguments) {
|
function ScriptFilter(script, targetPlatform, $arguments, source) {
|
||||||
return {
|
return {
|
||||||
name: 'Script Filter',
|
name: 'Script Filter',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
@@ -573,7 +574,7 @@ function ScriptFilter(script, targetPlatform, $arguments) {
|
|||||||
script,
|
script,
|
||||||
$arguments,
|
$arguments,
|
||||||
);
|
);
|
||||||
output = filter(proxies, targetPlatform);
|
output = filter(proxies, targetPlatform, { source, ...env });
|
||||||
})();
|
})();
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { isPresent } from '@/core/proxy-utils/producers/utils';
|
|||||||
export default function Clash_Producer() {
|
export default function Clash_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => {
|
const produce = (proxies) => {
|
||||||
|
// VLESS XTLS is not supported by Clash
|
||||||
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L532
|
||||||
|
// github.com/Dreamacro/clash/pull/2891/files
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter((proxy) => {
|
proxies = proxies.filter((proxy) => {
|
||||||
if (
|
if (
|
||||||
@@ -10,17 +13,17 @@ export default function Clash_Producer() {
|
|||||||
'ss',
|
'ss',
|
||||||
'ssr',
|
'ssr',
|
||||||
'vmess',
|
'vmess',
|
||||||
|
'vless',
|
||||||
'socks',
|
'socks',
|
||||||
'http',
|
'http',
|
||||||
'snell',
|
'snell',
|
||||||
'trojan',
|
'trojan',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type) ||
|
||||||
) {
|
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||||
return false;
|
(proxy.type === 'vless' &&
|
||||||
} else if (
|
(typeof proxy.flow !== 'undefined' ||
|
||||||
proxy.type === 'snell' &&
|
proxy['reality-opts']))
|
||||||
String(proxy.version) === '4'
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -87,10 +90,22 @@ export default function Clash_Producer() {
|
|||||||
proxy['http-opts'].headers.Host = [httpHost];
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
|
} else if (proxy.type === 'vless') {
|
||||||
|
if (isPresent(proxy, 'sni')) {
|
||||||
|
proxy.servername = proxy.sni;
|
||||||
|
delete proxy.sni;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -103,11 +108,23 @@ export default function ClashMeta_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -108,11 +108,23 @@ export default function ShadowRocket_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export default function Stash_Producer() {
|
|||||||
'tuic',
|
'tuic',
|
||||||
'vless',
|
'vless',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
].includes(proxy.type) ||
|
].includes(proxy.type) ||
|
||||||
(proxy.type === 'snell' &&
|
(proxy.type === 'snell' &&
|
||||||
String(proxy.version) === '4') ||
|
String(proxy.version) === '4') ||
|
||||||
@@ -67,6 +69,7 @@ export default function Stash_Producer() {
|
|||||||
!isPresent(proxy, 'fast-open')
|
!isPresent(proxy, 'fast-open')
|
||||||
) {
|
) {
|
||||||
proxy['fast-open'] = proxy.tfo;
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
}
|
}
|
||||||
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
|
||||||
if (
|
if (
|
||||||
@@ -86,6 +89,50 @@ export default function Stash_Producer() {
|
|||||||
!isPresent(proxy, 'fast-open')
|
!isPresent(proxy, 'fast-open')
|
||||||
) {
|
) {
|
||||||
proxy['fast-open'] = proxy.tfo;
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'down') &&
|
||||||
|
!isPresent(proxy, 'down-speed')
|
||||||
|
) {
|
||||||
|
proxy['down-speed'] = proxy.down;
|
||||||
|
delete proxy.down;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'up') &&
|
||||||
|
!isPresent(proxy, 'up-speed')
|
||||||
|
) {
|
||||||
|
proxy['up-speed'] = proxy.up;
|
||||||
|
delete proxy.up;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'hysteria2') {
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'password') &&
|
||||||
|
!isPresent(proxy, 'auth')
|
||||||
|
) {
|
||||||
|
proxy.auth = proxy.password;
|
||||||
|
delete proxy.password;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'tfo') &&
|
||||||
|
!isPresent(proxy, 'fast-open')
|
||||||
|
) {
|
||||||
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'down') &&
|
||||||
|
!isPresent(proxy, 'down-speed')
|
||||||
|
) {
|
||||||
|
proxy['down-speed'] = proxy.down;
|
||||||
|
delete proxy.down;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'up') &&
|
||||||
|
!isPresent(proxy, 'up-speed')
|
||||||
|
) {
|
||||||
|
proxy['up-speed'] = proxy.up;
|
||||||
|
delete proxy.up;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'wireguard') {
|
} else if (proxy.type === 'wireguard') {
|
||||||
proxy.keepalive =
|
proxy.keepalive =
|
||||||
@@ -120,10 +167,22 @@ export default function Stash_Producer() {
|
|||||||
proxy['http-opts'].headers.Host = [httpHost];
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ export default function Surge_Producer() {
|
|||||||
return snell(proxy);
|
return snell(proxy);
|
||||||
case 'tuic':
|
case 'tuic':
|
||||||
return tuic(proxy);
|
return tuic(proxy);
|
||||||
|
case 'wireguard-surge':
|
||||||
|
return wireguard(proxy);
|
||||||
|
case 'hysteria2':
|
||||||
|
return hysteria2(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}`,
|
||||||
@@ -69,6 +73,12 @@ function shadowsocks(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +115,12 @@ function trojan(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +164,12 @@ function vmess(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +202,12 @@ function http(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +242,12 @@ function socks5(proxy) {
|
|||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +277,12 @@ 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');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
// reuse
|
// reuse
|
||||||
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
|
result.appendIfPresent(`,reuse=${proxy['reuse']}`, 'reuse');
|
||||||
|
|
||||||
@@ -280,12 +320,111 @@ function tuic(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy['fast-open']}`, 'fast-open');
|
if (isPresent(proxy, 'tfo')) {
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.append(`,tfo=${proxy['tfo']}`);
|
||||||
|
} else if (isPresent(proxy, 'fast-open')) {
|
||||||
|
result.append(`,tfo=${proxy['fast-open']}`);
|
||||||
|
}
|
||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireguard(proxy) {
|
||||||
|
const result = new Result(proxy);
|
||||||
|
|
||||||
|
result.append(`${proxy.name}=wireguard`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,section-name=${proxy['section-name']}`,
|
||||||
|
'section-name',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
|
'no-error-alert',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
||||||
|
'ip-version',
|
||||||
|
);
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteria2(proxy) {
|
||||||
|
if (proxy.obfs || proxy['obfs-password']) {
|
||||||
|
throw new Error(`obfs is unsupported`);
|
||||||
|
}
|
||||||
|
const result = new Result(proxy);
|
||||||
|
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
||||||
|
'ip-version',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
|
'no-error-alert',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tls verification
|
||||||
|
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||||
|
'skip-cert-verify',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server-cert-fingerprint-sha256=${proxy.fingerprint}`,
|
||||||
|
'fingerprint',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tfo
|
||||||
|
if (isPresent(proxy, 'tfo')) {
|
||||||
|
result.append(`,tfo=${proxy['tfo']}`);
|
||||||
|
} else if (isPresent(proxy, 'fast-open')) {
|
||||||
|
result.append(`,tfo=${proxy['fast-open']}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test-url
|
||||||
|
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
// download-bandwidth
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,download-bandwidth=${`${proxy['down']}`.match(/\d+/)?.[0] || 0}`,
|
||||||
|
'down',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ export default function URI_Producer() {
|
|||||||
? vmessTransportHost[0]
|
? vmessTransportHost[0]
|
||||||
: vmessTransportHost;
|
: vmessTransportHost;
|
||||||
}
|
}
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
result.path =
|
||||||
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
|
'grpc-service-name'
|
||||||
|
];
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
result.type =
|
||||||
|
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
|
||||||
|
'gun';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
||||||
break;
|
break;
|
||||||
@@ -141,6 +151,12 @@ export default function URI_Producer() {
|
|||||||
let vlessTransport = `&type=${encodeURIComponent(
|
let vlessTransport = `&type=${encodeURIComponent(
|
||||||
proxy.network,
|
proxy.network,
|
||||||
)}`;
|
)}`;
|
||||||
|
if (['grpc'].includes(proxy.network)) {
|
||||||
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
|
vlessTransport += `&mode=${encodeURIComponent(
|
||||||
|
proxy[`${proxy.network}-opts`]?.['_grpc-type'] || 'gun',
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
let vlessTransportServiceName =
|
let vlessTransportServiceName =
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||||
* Advanced Subscription Manager for QX, Loon, Surge and Clash.
|
* Advanced Subscription Manager for QX, Loon, Surge and Clash.
|
||||||
* @author: Peng-YM
|
* @author: Peng-YM
|
||||||
* @github: https://github.com/Peng-YM/Sub-Store
|
* @github: https://github.com/sub-store-org/Sub-Store
|
||||||
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
||||||
*/
|
*/
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import registerArtifactRoutes from '@/restful/artifacts';
|
|||||||
import registerSettingRoutes from '@/restful/settings';
|
import registerSettingRoutes from '@/restful/settings';
|
||||||
import registerMiscRoutes from '@/restful/miscs';
|
import registerMiscRoutes from '@/restful/miscs';
|
||||||
import registerSortRoutes from '@/restful/sort';
|
import registerSortRoutes from '@/restful/sort';
|
||||||
|
import registerFileRoutes from '@/restful/file';
|
||||||
|
import registerModuleRoutes from '@/restful/module';
|
||||||
|
|
||||||
migrate();
|
migrate();
|
||||||
serve();
|
serve();
|
||||||
@@ -30,6 +32,8 @@ function serve() {
|
|||||||
// register routes
|
// register routes
|
||||||
registerCollectionRoutes($app);
|
registerCollectionRoutes($app);
|
||||||
registerSubscriptionRoutes($app);
|
registerSubscriptionRoutes($app);
|
||||||
|
registerFileRoutes($app);
|
||||||
|
registerModuleRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
registerSettingRoutes($app);
|
registerSettingRoutes($app);
|
||||||
registerSortRoutes($app);
|
registerSortRoutes($app);
|
||||||
|
|||||||
109
backend/src/restful/file.js
Normal file
109
backend/src/restful/file.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
|
import { FILES_KEY } from '@/constants';
|
||||||
|
import { failed, success } from '@/restful/response';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
|
|
||||||
|
export default function register($app) {
|
||||||
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
|
|
||||||
|
$app.route('/api/file/:name')
|
||||||
|
.get(getFile)
|
||||||
|
.patch(updateFile)
|
||||||
|
.delete(deleteFile);
|
||||||
|
|
||||||
|
$app.route('/api/files').get(getAllFiles).post(createFile).put(replaceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// file API
|
||||||
|
function createFile(req, res) {
|
||||||
|
const file = req.body;
|
||||||
|
file.name = `${file.name ?? Date.now()}`;
|
||||||
|
$.info(`正在创建文件:${file.name}`);
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
if (findByName(allFiles, file.name)) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'DUPLICATE_KEY',
|
||||||
|
req.body.name
|
||||||
|
? `已存在 name 为 ${file.name} 的文件`
|
||||||
|
: `无法同时创建相同的文件 可稍后重试`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
allFiles.push(file);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res, file, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
const file = findByName(allFiles, name);
|
||||||
|
if (file) {
|
||||||
|
res.status(200).json(file.content);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
`FILE_NOT_FOUND`,
|
||||||
|
`File ${name} does not exist`,
|
||||||
|
404,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
let file = req.body;
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
const oldFile = findByName(allFiles, name);
|
||||||
|
if (oldFile) {
|
||||||
|
const newFile = {
|
||||||
|
...oldFile,
|
||||||
|
...file,
|
||||||
|
};
|
||||||
|
$.info(`正在更新文件:${name}...`);
|
||||||
|
|
||||||
|
updateByName(allFiles, name, newFile);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res, newFile);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
'RESOURCE_NOT_FOUND',
|
||||||
|
`File ${name} does not exist!`,
|
||||||
|
),
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFile(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
$.info(`正在删除文件:${name}`);
|
||||||
|
let allFiles = $.read(FILES_KEY);
|
||||||
|
deleteByName(allFiles, name);
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllFiles(req, res) {
|
||||||
|
const allFiles = $.read(FILES_KEY);
|
||||||
|
success(
|
||||||
|
res, // eslint-disable-next-line no-unused-vars
|
||||||
|
allFiles.map(({ content, ...rest }) => rest),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceFile(req, res) {
|
||||||
|
const allFiles = req.body;
|
||||||
|
$.write(allFiles, FILES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import $ from '@/core/app';
|
|||||||
import registerSubscriptionRoutes from './subscriptions';
|
import registerSubscriptionRoutes from './subscriptions';
|
||||||
import registerCollectionRoutes from './collections';
|
import registerCollectionRoutes from './collections';
|
||||||
import registerArtifactRoutes from './artifacts';
|
import registerArtifactRoutes from './artifacts';
|
||||||
|
import registerFileRoutes from './file';
|
||||||
|
import registerModuleRoutes from './module';
|
||||||
import registerSyncRoutes from './sync';
|
import registerSyncRoutes from './sync';
|
||||||
import registerDownloadRoutes from './download';
|
import registerDownloadRoutes from './download';
|
||||||
import registerSettingRoutes from './settings';
|
import registerSettingRoutes from './settings';
|
||||||
@@ -23,6 +25,8 @@ export default function serve() {
|
|||||||
registerSortingRoutes($app);
|
registerSortingRoutes($app);
|
||||||
registerSettingRoutes($app);
|
registerSettingRoutes($app);
|
||||||
registerArtifactRoutes($app);
|
registerArtifactRoutes($app);
|
||||||
|
registerFileRoutes($app);
|
||||||
|
registerModuleRoutes($app);
|
||||||
registerSyncRoutes($app);
|
registerSyncRoutes($app);
|
||||||
registerNodeInfoRoutes($app);
|
registerNodeInfoRoutes($app);
|
||||||
registerMiscRoutes($app);
|
registerMiscRoutes($app);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { ENV } from '@/vendor/open-api';
|
import { ENV } from '@/vendor/open-api';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import { version as substoreVersion } from '../../package.json';
|
|
||||||
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
|
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
// utils
|
// utils
|
||||||
@@ -49,19 +49,7 @@ export default function register($app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEnv(req, res) {
|
function getEnv(req, res) {
|
||||||
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
success(res, env);
|
||||||
let backend = 'Node';
|
|
||||||
if (isNode) backend = 'Node';
|
|
||||||
if (isQX) backend = 'QX';
|
|
||||||
if (isLoon) backend = 'Loon';
|
|
||||||
if (isSurge) backend = 'Surge';
|
|
||||||
if (isStash) backend = 'Stash';
|
|
||||||
if (isShadowRocket) backend = 'ShadowRocket';
|
|
||||||
|
|
||||||
success(res, {
|
|
||||||
backend,
|
|
||||||
version: substoreVersion,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh(_, res) {
|
async function refresh(_, res) {
|
||||||
@@ -118,6 +106,23 @@ async function gistBackup(req, res) {
|
|||||||
case 'download':
|
case 'download':
|
||||||
$.info(`还原备份中...`);
|
$.info(`还原备份中...`);
|
||||||
content = await gist.download(GIST_BACKUP_FILE_NAME);
|
content = await gist.download(GIST_BACKUP_FILE_NAME);
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
Object.keys(JSON.parse(content).settings).length ===
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'备份文件应该至少包含 settings 字段',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||||
|
err.message ?? err
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||||
|
}
|
||||||
// restore settings
|
// restore settings
|
||||||
$.write(content, '#sub-store');
|
$.write(content, '#sub-store');
|
||||||
if ($.env.isNode) {
|
if ($.env.isNode) {
|
||||||
|
|||||||
114
backend/src/restful/module.js
Normal file
114
backend/src/restful/module.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
|
import { MODULES_KEY } from '@/constants';
|
||||||
|
import { failed, success } from '@/restful/response';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
|
|
||||||
|
export default function register($app) {
|
||||||
|
if (!$.read(MODULES_KEY)) $.write([], MODULES_KEY);
|
||||||
|
|
||||||
|
$app.route('/api/module/:name')
|
||||||
|
.get(getModule)
|
||||||
|
.patch(updateModule)
|
||||||
|
.delete(deleteModule);
|
||||||
|
|
||||||
|
$app.route('/api/modules')
|
||||||
|
.get(getAllModules)
|
||||||
|
.post(createModule)
|
||||||
|
.put(replaceModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
// module API
|
||||||
|
function createModule(req, res) {
|
||||||
|
const module = req.body;
|
||||||
|
module.name = `${module.name ?? hex_md5(JSON.stringify(module))}`;
|
||||||
|
$.info(`正在创建模块:${module.name}`);
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
if (findByName(allModules, module.name)) {
|
||||||
|
return failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'DUPLICATE_KEY',
|
||||||
|
req.body.name
|
||||||
|
? `已存在 name 为 ${module.name} 的模块`
|
||||||
|
: `已存在相同的模块 请勿重复添加`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
allModules.push(module);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res, module, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
const module = findByName(allModules, name);
|
||||||
|
if (module) {
|
||||||
|
res.status(200).json(module.content);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
`MODULE_NOT_FOUND`,
|
||||||
|
`Module ${name} does not exist`,
|
||||||
|
404,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
let module = req.body;
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
const oldModule = findByName(allModules, name);
|
||||||
|
if (oldModule) {
|
||||||
|
const newModule = {
|
||||||
|
...oldModule,
|
||||||
|
...module,
|
||||||
|
};
|
||||||
|
$.info(`正在更新模块:${name}...`);
|
||||||
|
|
||||||
|
updateByName(allModules, name, newModule);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res, newModule);
|
||||||
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
'RESOURCE_NOT_FOUND',
|
||||||
|
`Module ${name} does not exist!`,
|
||||||
|
),
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteModule(req, res) {
|
||||||
|
let { name } = req.params;
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
$.info(`正在删除模块:${name}`);
|
||||||
|
let allModules = $.read(MODULES_KEY);
|
||||||
|
deleteByName(allModules, name);
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllModules(req, res) {
|
||||||
|
const allModules = $.read(MODULES_KEY);
|
||||||
|
success(
|
||||||
|
res,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
allModules.map(({ content, ...rest }) => rest),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceModule(req, res) {
|
||||||
|
const allModules = req.body;
|
||||||
|
$.write(allModules, MODULES_KEY);
|
||||||
|
success(res);
|
||||||
|
}
|
||||||
@@ -12,98 +12,133 @@ export default function register($app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function compareSub(req, res) {
|
async function compareSub(req, res) {
|
||||||
const sub = req.body;
|
try {
|
||||||
const target = req.query.target || 'JSON';
|
const sub = req.body;
|
||||||
let content;
|
const target = req.query.target || 'JSON';
|
||||||
if (sub.source === 'local') {
|
let content;
|
||||||
content = sub.content;
|
if (sub.source === 'local') {
|
||||||
} else {
|
content = sub.content;
|
||||||
try {
|
} else {
|
||||||
content = await download(sub.url, sub.ua);
|
|
||||||
} catch (err) {
|
|
||||||
failed(
|
|
||||||
res,
|
|
||||||
new NetworkError(
|
|
||||||
'FAILED_TO_DOWNLOAD_RESOURCE',
|
|
||||||
'无法下载远程资源',
|
|
||||||
`Reason: ${err}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// parse proxies
|
|
||||||
const original = ProxyUtils.parse(content);
|
|
||||||
|
|
||||||
// add id
|
|
||||||
original.forEach((proxy, i) => {
|
|
||||||
proxy.id = i;
|
|
||||||
});
|
|
||||||
|
|
||||||
// apply processors
|
|
||||||
const processed = await ProxyUtils.process(
|
|
||||||
original,
|
|
||||||
sub.process || [],
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
|
|
||||||
// produce
|
|
||||||
success(res, { original, processed });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compareCollection(req, res) {
|
|
||||||
const allSubs = $.read(SUBS_KEY);
|
|
||||||
const collection = req.body;
|
|
||||||
const subnames = collection.subscriptions;
|
|
||||||
const results = {};
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
subnames.map(async (name) => {
|
|
||||||
const sub = findByName(allSubs, name);
|
|
||||||
try {
|
try {
|
||||||
let raw;
|
content = await download(sub.url, sub.ua);
|
||||||
if (sub.source === 'local') {
|
|
||||||
raw = sub.content;
|
|
||||||
} else {
|
|
||||||
raw = await download(sub.url, sub.ua);
|
|
||||||
}
|
|
||||||
// parse proxies
|
|
||||||
let currentProxies = ProxyUtils.parse(raw);
|
|
||||||
// apply processors
|
|
||||||
currentProxies = await ProxyUtils.process(
|
|
||||||
currentProxies,
|
|
||||||
sub.process || [],
|
|
||||||
'JSON',
|
|
||||||
);
|
|
||||||
results[name] = currentProxies;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new NetworkError(
|
||||||
'PROCESS_FAILED',
|
'FAILED_TO_DOWNLOAD_RESOURCE',
|
||||||
`处理子订阅 ${name} 失败`,
|
'无法下载远程资源',
|
||||||
`Reason: ${err}`,
|
`Reason: ${err}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
);
|
// parse proxies
|
||||||
|
const original = ProxyUtils.parse(content);
|
||||||
|
|
||||||
// merge proxies with the original order
|
// add id
|
||||||
const original = Array.prototype.concat.apply(
|
original.forEach((proxy, i) => {
|
||||||
[],
|
proxy.id = i;
|
||||||
subnames.map((name) => results[name] || []),
|
proxy.subName = sub.name;
|
||||||
);
|
});
|
||||||
|
|
||||||
original.forEach((proxy, i) => {
|
// apply processors
|
||||||
proxy.id = i;
|
const processed = await ProxyUtils.process(
|
||||||
});
|
original,
|
||||||
|
sub.process || [],
|
||||||
|
target,
|
||||||
|
{ [sub.name]: sub },
|
||||||
|
);
|
||||||
|
|
||||||
const processed = await ProxyUtils.process(
|
// produce
|
||||||
original,
|
success(res, { original, processed });
|
||||||
collection.process || [],
|
} catch (err) {
|
||||||
'JSON',
|
$.error(err.message ?? err);
|
||||||
);
|
failed(
|
||||||
|
res,
|
||||||
success(res, { original, processed });
|
new InternalServerError(
|
||||||
|
`INTERNAL_SERVER_ERROR`,
|
||||||
|
`Failed to preview subscription`,
|
||||||
|
`Reason: ${err.message ?? err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compareCollection(req, res) {
|
||||||
|
try {
|
||||||
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
const collection = req.body;
|
||||||
|
const subnames = collection.subscriptions;
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
subnames.map(async (name) => {
|
||||||
|
const sub = findByName(allSubs, name);
|
||||||
|
try {
|
||||||
|
let raw;
|
||||||
|
if (sub.source === 'local') {
|
||||||
|
raw = sub.content;
|
||||||
|
} else {
|
||||||
|
raw = await download(sub.url, sub.ua);
|
||||||
|
}
|
||||||
|
// parse proxies
|
||||||
|
let currentProxies = ProxyUtils.parse(raw);
|
||||||
|
|
||||||
|
currentProxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// apply processors
|
||||||
|
currentProxies = await ProxyUtils.process(
|
||||||
|
currentProxies,
|
||||||
|
sub.process || [],
|
||||||
|
'JSON',
|
||||||
|
{ [sub.name]: sub, _collection: collection },
|
||||||
|
);
|
||||||
|
results[name] = currentProxies;
|
||||||
|
} catch (err) {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
'PROCESS_FAILED',
|
||||||
|
`处理子订阅 ${name} 失败`,
|
||||||
|
`Reason: ${err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// merge proxies with the original order
|
||||||
|
const original = Array.prototype.concat.apply(
|
||||||
|
[],
|
||||||
|
subnames.map((name) => results[name] || []),
|
||||||
|
);
|
||||||
|
|
||||||
|
original.forEach((proxy, i) => {
|
||||||
|
proxy.id = i;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
const processed = await ProxyUtils.process(
|
||||||
|
original,
|
||||||
|
collection.process || [],
|
||||||
|
'JSON',
|
||||||
|
{ _collection: collection },
|
||||||
|
);
|
||||||
|
|
||||||
|
success(res, { original, processed });
|
||||||
|
} catch (err) {
|
||||||
|
$.error(err.message ?? err);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
`INTERNAL_SERVER_ERROR`,
|
||||||
|
`Failed to preview collection`,
|
||||||
|
`Reason: ${err.message ?? err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function register($app) {
|
|||||||
function sortSubs(req, res) {
|
function sortSubs(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
allSubs.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allSubs.sort((a, b) => orders.indexOf(a.name) - orders.indexOf(b.name));
|
||||||
$.write(allSubs, SUBS_KEY);
|
$.write(allSubs, SUBS_KEY);
|
||||||
success(res, allSubs);
|
success(res, allSubs);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ function sortSubs(req, res) {
|
|||||||
function sortCollections(req, res) {
|
function sortCollections(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
allCols.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allCols.sort((a, b) => orders.indexOf(a.name) - orders.indexOf(b.name));
|
||||||
$.write(allCols, COLLECTIONS_KEY);
|
$.write(allCols, COLLECTIONS_KEY);
|
||||||
success(res, allCols);
|
success(res, allCols);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,9 @@ function sortCollections(req, res) {
|
|||||||
function sortArtifacts(req, res) {
|
function sortArtifacts(req, res) {
|
||||||
const orders = req.body;
|
const orders = req.body;
|
||||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||||
allArtifacts.sort((a, b) => orders.indexOf(a) - orders.indexOf(b));
|
allArtifacts.sort(
|
||||||
|
(a, b) => orders.indexOf(a.name) - orders.indexOf(b.name),
|
||||||
|
);
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, allArtifacts);
|
success(res, allArtifacts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,15 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let proxies = ProxyUtils.parse(raw);
|
let proxies = ProxyUtils.parse(raw);
|
||||||
|
proxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
});
|
||||||
// apply processors
|
// apply processors
|
||||||
proxies = await ProxyUtils.process(
|
proxies = await ProxyUtils.process(
|
||||||
proxies,
|
proxies,
|
||||||
sub.process || [],
|
sub.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ [sub.name]: sub },
|
||||||
);
|
);
|
||||||
if (proxies.length === 0) {
|
if (proxies.length === 0) {
|
||||||
throw new Error(`订阅 ${name} 中不含有效节点`);
|
throw new Error(`订阅 ${name} 中不含有效节点`);
|
||||||
@@ -51,7 +55,7 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
if (exist[proxy.name]) {
|
if (exist[proxy.name]) {
|
||||||
$.notify(
|
$.notify(
|
||||||
'🌍 Sub-Store',
|
'🌍 Sub-Store',
|
||||||
'⚠️ 订阅包含重复节点!',
|
`⚠️ 订阅 ${name} 包含重复节点 ${proxy.name}!`,
|
||||||
'请仔细检测配置!',
|
'请仔细检测配置!',
|
||||||
{
|
{
|
||||||
'media-url':
|
'media-url':
|
||||||
@@ -86,11 +90,18 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let currentProxies = ProxyUtils.parse(raw);
|
let currentProxies = ProxyUtils.parse(raw);
|
||||||
|
|
||||||
|
currentProxies.forEach((proxy) => {
|
||||||
|
proxy.subName = sub.name;
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
// apply processors
|
// apply processors
|
||||||
currentProxies = await ProxyUtils.process(
|
currentProxies = await ProxyUtils.process(
|
||||||
currentProxies,
|
currentProxies,
|
||||||
sub.process || [],
|
sub.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ [sub.name]: sub, _collection: collection },
|
||||||
);
|
);
|
||||||
results[name] = currentProxies;
|
results[name] = currentProxies;
|
||||||
processed++;
|
processed++;
|
||||||
@@ -127,11 +138,16 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
subnames.map((name) => results[name] || []),
|
subnames.map((name) => results[name] || []),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
proxies.forEach((proxy) => {
|
||||||
|
proxy.collectionName = collection.name;
|
||||||
|
});
|
||||||
|
|
||||||
// apply own processors
|
// apply own processors
|
||||||
proxies = await ProxyUtils.process(
|
proxies = await ProxyUtils.process(
|
||||||
proxies,
|
proxies,
|
||||||
collection.process || [],
|
collection.process || [],
|
||||||
platform,
|
platform,
|
||||||
|
{ _collection: collection },
|
||||||
);
|
);
|
||||||
if (proxies.length === 0) {
|
if (proxies.length === 0) {
|
||||||
throw new Error(`组合订阅 ${name} 中不含有效节点`);
|
throw new Error(`组合订阅 ${name} 中不含有效节点`);
|
||||||
@@ -142,7 +158,7 @@ async function produceArtifact({ type, name, platform }) {
|
|||||||
if (exist[proxy.name]) {
|
if (exist[proxy.name]) {
|
||||||
$.notify(
|
$.notify(
|
||||||
'🌍 Sub-Store',
|
'🌍 Sub-Store',
|
||||||
'⚠️ 订阅包含重复节点!',
|
`⚠️ 组合订阅 ${name} 包含重复节点 ${proxy.name}!`,
|
||||||
'请仔细检测配置!',
|
'请仔细检测配置!',
|
||||||
{
|
{
|
||||||
'media-url':
|
'media-url':
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
|
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
||||||
|
import { findByName } from '@/utils/database';
|
||||||
import { HTTP, ENV } 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';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(url, ua) {
|
export default async function download(url, ua) {
|
||||||
|
const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
|
if (downloadUrlMatch) {
|
||||||
|
let type = downloadUrlMatch?.[1];
|
||||||
|
let name = downloadUrlMatch?.[2];
|
||||||
|
if (name == null) {
|
||||||
|
throw new Error(`本地 ${type} URL 无效: ${url}`);
|
||||||
|
}
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
||||||
|
const item = findByName($.read(key), name);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error(`找不到本地 ${type}: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.content;
|
||||||
|
}
|
||||||
|
|
||||||
const { isNode } = ENV();
|
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);
|
||||||
|
|||||||
16
backend/src/utils/env.js
Normal file
16
backend/src/utils/env.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { version as substoreVersion } from '../../package.json';
|
||||||
|
import { ENV } from '@/vendor/open-api';
|
||||||
|
|
||||||
|
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
||||||
|
let backend = 'Node';
|
||||||
|
if (isNode) backend = 'Node';
|
||||||
|
if (isQX) backend = 'QX';
|
||||||
|
if (isLoon) backend = 'Loon';
|
||||||
|
if (isSurge) backend = 'Surge';
|
||||||
|
if (isStash) backend = 'Stash';
|
||||||
|
if (isShadowRocket) backend = 'ShadowRocket';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
backend,
|
||||||
|
version: substoreVersion,
|
||||||
|
};
|
||||||
2
backend/src/vendor/express.js
vendored
2
backend/src/vendor/express.js
vendored
@@ -17,7 +17,7 @@ export default function express({ substore: $, port }) {
|
|||||||
const express_ = eval(`require("express")`);
|
const express_ = eval(`require("express")`);
|
||||||
const bodyParser = eval(`require("body-parser")`);
|
const bodyParser = eval(`require("body-parser")`);
|
||||||
const app = express_();
|
const app = express_();
|
||||||
app.use(bodyParser.json({ verify: rawBodySaver }));
|
app.use(bodyParser.json({ verify: rawBodySaver, limit: '1mb' }));
|
||||||
app.use(
|
app.use(
|
||||||
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
|
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
|
||||||
);
|
);
|
||||||
|
|||||||
32
backend/src/vendor/open-api.js
vendored
32
backend/src/vendor/open-api.js
vendored
@@ -49,27 +49,32 @@ export class OpenAPI {
|
|||||||
|
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
// create a json for root cache
|
// create a json for root cache
|
||||||
let fpath = 'root.json';
|
const basePath =
|
||||||
if (!this.node.fs.existsSync(fpath)) {
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
let rootPath = `${basePath}/root.json`;
|
||||||
|
|
||||||
|
this.log(`Root path: ${rootPath}`);
|
||||||
|
if (!this.node.fs.existsSync(rootPath)) {
|
||||||
|
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
|
||||||
flag: 'wx',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.root = {};
|
this.root = {};
|
||||||
} else {
|
} else {
|
||||||
this.root = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
this.root = JSON.parse(
|
||||||
|
this.node.fs.readFileSync(`${rootPath}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a json file with the given name if not exists
|
// create a json file with the given name if not exists
|
||||||
fpath = `${this.name}.json`;
|
let fpath = `${basePath}/${this.name}.json`;
|
||||||
|
this.log(`Data path: ${fpath}`);
|
||||||
if (!this.node.fs.existsSync(fpath)) {
|
if (!this.node.fs.existsSync(fpath)) {
|
||||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
||||||
flag: 'wx',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
} else {
|
} else {
|
||||||
this.cache = JSON.parse(
|
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
||||||
this.node.fs.readFileSync(`${this.name}.json`),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,14 +85,17 @@ export class OpenAPI {
|
|||||||
if (isQX) $prefs.setValueForKey(data, this.name);
|
if (isQX) $prefs.setValueForKey(data, this.name);
|
||||||
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
|
const basePath =
|
||||||
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
|
|
||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
`${this.name}.json`,
|
`${basePath}/${this.name}.json`,
|
||||||
data,
|
data,
|
||||||
{ flag: 'w' },
|
{ flag: 'w' },
|
||||||
(err) => console.log(err),
|
(err) => console.log(err),
|
||||||
);
|
);
|
||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
'root.json',
|
`${basePath}/root.json`,
|
||||||
JSON.stringify(this.root, null, 2),
|
JSON.stringify(this.root, null, 2),
|
||||||
{ flag: 'w' },
|
{ flag: 'w' },
|
||||||
(err) => console.log(err),
|
(err) => console.log(err),
|
||||||
@@ -308,7 +316,9 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
return (
|
return (
|
||||||
timer
|
timer
|
||||||
? Promise.race([timer, worker]).then((res) => {
|
? Promise.race([timer, worker]).then((res) => {
|
||||||
clearTimeout(timeoutid);
|
if (typeof clearTimeout !== 'undefined') {
|
||||||
|
clearTimeout(timeoutid);
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
: worker
|
: worker
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#!desc=高级订阅管理工具
|
#!desc=高级订阅管理工具
|
||||||
#!openUrl=https://sub.store
|
#!openUrl=https://sub.store
|
||||||
#!author=Peng-YM
|
#!author=Peng-YM
|
||||||
#!homepage=https://github.com/Peng-YM/Sub-Store
|
#!homepage=https://github.com/sub-store-org/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小时,参数传入
|
#!select = 节点缓存有效期,1分钟,5分钟,10分钟,30分钟,1小时,2小时,3小时,6小时,12小时,24小时,48小时,72小时,参数传入
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,31 @@
|
|||||||
|
|
||||||
## 查看更新说明:
|
## 查看更新说明:
|
||||||
|
|
||||||
[Sub-Store Releases](https://github.com/sub-store-org/Sub-Store/releases)
|
Sub-Store Releases: [`https://github.com/sub-store-org/Sub-Store/releases`](https://github.com/sub-store-org/Sub-Store/releases)
|
||||||
|
|
||||||
[Telegram 频道](https://t.me/cool_scripts)
|
Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
||||||
|
|
||||||
## 脚本配置:
|
## 脚本配置:
|
||||||
|
|
||||||
### 1. Loon
|
### 1. Loon
|
||||||
安装使用[插件](https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/config/Loon.plugin)即可。
|
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
|
||||||
|
|
||||||
### 2. Surge
|
### 2. Surge
|
||||||
安装使用[模块](https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/config/Surge.sgmodule)即可。
|
1. 官方默认版模块(目前不带 ability 参数, 不保证以后不会改动): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
||||||
|
|
||||||
|
2. 固定带 ability 参数版本,可能会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
|
||||||
|
|
||||||
|
3. 固定不带 ability 参数版本: [`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)
|
||||||
|
|
||||||
|
|
||||||
### 3. QX
|
### 3. QX
|
||||||
订阅[重写](https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/config/QX.snippet)即可
|
订阅 重写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet) 即可。
|
||||||
|
|
||||||
### 4. Stash
|
### 4. Stash
|
||||||
安装使用[ Stash 覆写](https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/config/Stash.stoverride)即可。
|
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
|
||||||
|
|
||||||
### 5. Shadowrocket
|
### 5. Shadowrocket
|
||||||
安装使用[模块](https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/config/Surge.sgmodule)即可。
|
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule) 即可。
|
||||||
|
|
||||||
## 使用 Sub-Store
|
## 使用 Sub-Store
|
||||||
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
||||||
|
|||||||
Reference in New Issue
Block a user