mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80d7f5875 | ||
|
|
779950ab11 | ||
|
|
42404537e8 | ||
|
|
228566116d | ||
|
|
9bb06bf438 | ||
|
|
88e52f9787 | ||
|
|
845a173738 | ||
|
|
4a6bcbc9b4 | ||
|
|
bbaac2de6f | ||
|
|
614438ae3d | ||
|
|
4966132397 | ||
|
|
059c4bd148 | ||
|
|
63887e3dad | ||
|
|
7fd585b5d4 | ||
|
|
16c79ac0fc | ||
|
|
14d9885db8 | ||
|
|
1e61088ed8 | ||
|
|
af6904ea50 | ||
|
|
1bc44ccde8 | ||
|
|
bdc7ee50f7 | ||
|
|
812f24d102 | ||
|
|
8c943176a5 | ||
|
|
f4c4cdba67 | ||
|
|
ada03be05f | ||
|
|
5584225413 | ||
|
|
5cbcf4fce4 | ||
|
|
89931c0032 | ||
|
|
88f3198320 | ||
|
|
27a14bb255 | ||
|
|
5ecce27f4e |
27
README.md
27
README.md
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<br>
|
<br>
|
||||||
<img width="200" src="https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png" alt="Sub-Store">
|
<img width="200" src="https://raw.githubusercontent.com/cc63/ICON/main/Sub-Store.png" alt="Sub-Store">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<h2 align="center">Sub-Store<h2>
|
<h2 align="center">Sub-Store<h2>
|
||||||
@@ -30,29 +30,30 @@ Core functionalities:
|
|||||||
- [x] SSR URI
|
- [x] SSR URI
|
||||||
- [x] SSD URI
|
- [x] SSD URI
|
||||||
- [x] V2RayN URI
|
- [x] V2RayN URI
|
||||||
- [x] Hysteria2 URI
|
- [x] Hysteria 2 URI
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, WireGuard, VLESS, Hysteria2)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, WireGuard, VLESS, Hysteria 2)
|
||||||
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, TUIC, Snell, Hysteria2, SSR(external, only for macOS), WireGuard(Surge to Surge))
|
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, TUIC, Snell, Hysteria 2, SSR(external, only for macOS), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, WireGuard(Surfboard to Surfboard))
|
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, WireGuard(Surfboard to Surfboard))
|
||||||
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria2, TUIC)
|
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria2, TUIC)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
|
||||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||||
|
|
||||||
### Supported Target Platforms
|
### Supported Target Platforms
|
||||||
|
|
||||||
- [x] QX
|
- [x] Plain JSON
|
||||||
- [x] Loon
|
|
||||||
- [x] Surge
|
|
||||||
- [x] Surfboard
|
|
||||||
- [x] Stash
|
- [x] Stash
|
||||||
- [x] Clash.Meta
|
- [x] Clash.Meta(mihomo)
|
||||||
- [x] Clash
|
- [x] Clash
|
||||||
|
- [x] Surfboard
|
||||||
|
- [x] Surge
|
||||||
|
- [x] Loon
|
||||||
- [x] Shadowrocket
|
- [x] Shadowrocket
|
||||||
|
- [x] QX
|
||||||
|
- [x] sing-box
|
||||||
- [x] V2Ray
|
- [x] V2Ray
|
||||||
- [x] V2Ray URI
|
- [x] V2Ray URI
|
||||||
- [x] Plain JSON
|
|
||||||
|
|
||||||
## 2. Subscription Formatting
|
## 2. Subscription Formatting
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.154",
|
"version": "2.14.181",
|
||||||
"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": {
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"automerge": "1.0.1-preview.7",
|
"automerge": "1.0.1-preview.7",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"connect-history-api-fallback": "^2.0.0",
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
|
"cron": "^3.1.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-proxy-middleware": "^2.0.6",
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
|
|||||||
32
backend/pnpm-lock.yaml
generated
32
backend/pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ dependencies:
|
|||||||
connect-history-api-fallback:
|
connect-history-api-fallback:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: registry.npmmirror.com/connect-history-api-fallback@2.0.0
|
version: registry.npmmirror.com/connect-history-api-fallback@2.0.0
|
||||||
|
cron:
|
||||||
|
specifier: ^3.1.6
|
||||||
|
version: registry.npmmirror.com/cron@3.1.6
|
||||||
express:
|
express:
|
||||||
specifier: ^4.17.1
|
specifier: ^4.17.1
|
||||||
version: registry.npmmirror.com/express@4.17.1
|
version: registry.npmmirror.com/express@4.17.1
|
||||||
@@ -162,11 +165,6 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/source-map@0.6.1:
|
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@ampproject/remapping@2.2.0:
|
registry.npmmirror.com/@ampproject/remapping@2.2.0:
|
||||||
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz}
|
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz}
|
||||||
name: '@ampproject/remapping'
|
name: '@ampproject/remapping'
|
||||||
@@ -2056,6 +2054,12 @@ packages:
|
|||||||
'@types/node': registry.npmmirror.com/@types/node@17.0.35
|
'@types/node': registry.npmmirror.com/@types/node@17.0.35
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/@types/luxon@3.3.8:
|
||||||
|
resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/luxon/-/luxon-3.3.8.tgz}
|
||||||
|
name: '@types/luxon'
|
||||||
|
version: 3.3.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/@types/minimatch@3.0.5:
|
registry.npmmirror.com/@types/minimatch@3.0.5:
|
||||||
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/minimatch/-/minimatch-3.0.5.tgz}
|
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/minimatch/-/minimatch-3.0.5.tgz}
|
||||||
name: '@types/minimatch'
|
name: '@types/minimatch'
|
||||||
@@ -3696,6 +3700,15 @@ packages:
|
|||||||
sha.js: registry.npmmirror.com/sha.js@2.4.11
|
sha.js: registry.npmmirror.com/sha.js@2.4.11
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/cron@3.1.6:
|
||||||
|
resolution: {integrity: sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cron/-/cron-3.1.6.tgz}
|
||||||
|
name: cron
|
||||||
|
version: 3.1.6
|
||||||
|
dependencies:
|
||||||
|
'@types/luxon': registry.npmmirror.com/@types/luxon@3.3.8
|
||||||
|
luxon: registry.npmmirror.com/luxon@3.4.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/cross-spawn@7.0.3:
|
registry.npmmirror.com/cross-spawn@7.0.3:
|
||||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz}
|
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz}
|
||||||
name: cross-spawn
|
name: cross-spawn
|
||||||
@@ -6851,6 +6864,13 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: registry.npmmirror.com/yallist@4.0.0
|
yallist: registry.npmmirror.com/yallist@4.0.0
|
||||||
|
|
||||||
|
registry.npmmirror.com/luxon@3.4.4:
|
||||||
|
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz}
|
||||||
|
name: luxon
|
||||||
|
version: 3.4.4
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/magic-string@0.23.2:
|
registry.npmmirror.com/magic-string@0.23.2:
|
||||||
resolution: {integrity: sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.23.2.tgz}
|
resolution: {integrity: sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.23.2.tgz}
|
||||||
name: magic-string
|
name: magic-string
|
||||||
@@ -9320,7 +9340,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
acorn: registry.npmmirror.com/acorn@8.7.1
|
acorn: registry.npmmirror.com/acorn@8.7.1
|
||||||
commander: registry.npmmirror.com/commander@2.20.3
|
commander: registry.npmmirror.com/commander@2.20.3
|
||||||
source-map: 0.6.1
|
source-map: registry.npmmirror.com/source-map@0.6.1
|
||||||
source-map-support: registry.npmmirror.com/source-map-support@0.5.21
|
source-map-support: registry.npmmirror.com/source-map-support@0.5.21
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
|||||||
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
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 HEADERS_RESOURCE_CACHE_KEY = '#sub-store-cached-headers-resource';
|
||||||
|
export const CHR_EXPIRATION_TIME_KEY = '#sub-store-chr-expiration-time'; // Custom expiration time key; (Loon|Surge) Default write 1 min
|
||||||
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 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
|
export const CSR_EXPIRATION_TIME_KEY = '#sub-store-csr-expiration-time'; // Custom expiration time key; (Loon|Surge) Default write 48 hour
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ async function process(proxies, operators = [], targetPlatform, source) {
|
|||||||
return proxies;
|
return proxies;
|
||||||
}
|
}
|
||||||
|
|
||||||
function produce(proxies, targetPlatform, type) {
|
function produce(proxies, targetPlatform, type, opts = {}) {
|
||||||
const producer = PROXY_PRODUCERS[targetPlatform];
|
const producer = PROXY_PRODUCERS[targetPlatform];
|
||||||
if (!producer) {
|
if (!producer) {
|
||||||
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
||||||
@@ -154,10 +154,10 @@ function produce(proxies, targetPlatform, type) {
|
|||||||
$.info(`Producing proxies for target: ${targetPlatform}`);
|
$.info(`Producing proxies for target: ${targetPlatform}`);
|
||||||
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
||||||
let localPort = 10000;
|
let localPort = 10000;
|
||||||
return proxies
|
const list = proxies
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
try {
|
try {
|
||||||
let line = producer.produce(proxy, type);
|
let line = producer.produce(proxy, type, opts);
|
||||||
if (
|
if (
|
||||||
line.length > 0 &&
|
line.length > 0 &&
|
||||||
line.includes('__SubStoreLocalPort__')
|
line.includes('__SubStoreLocalPort__')
|
||||||
@@ -179,10 +179,10 @@ function produce(proxies, targetPlatform, type) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((line) => line.length > 0)
|
.filter((line) => line.length > 0);
|
||||||
.join('\n');
|
return type === 'internal' ? list : list.join('\n');
|
||||||
} else if (producer.type === 'ALL') {
|
} else if (producer.type === 'ALL') {
|
||||||
return producer.produce(proxies, type);
|
return producer.produce(proxies, type, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +229,11 @@ function lastParse(proxy) {
|
|||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (['vless'].includes(proxy.type)) {
|
||||||
|
if (!proxy.network) {
|
||||||
|
proxy.network = 'tcp';
|
||||||
|
}
|
||||||
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { getIfNotBlank, isPresent, isNotBlank, getIfPresent } from '@/utils';
|
import {
|
||||||
|
isIPv4,
|
||||||
|
isIPv6,
|
||||||
|
getIfNotBlank,
|
||||||
|
isPresent,
|
||||||
|
isNotBlank,
|
||||||
|
getIfPresent,
|
||||||
|
} from '@/utils';
|
||||||
import getSurgeParser from './peggy/surge';
|
import getSurgeParser from './peggy/surge';
|
||||||
import getLoonParser from './peggy/loon';
|
import getLoonParser from './peggy/loon';
|
||||||
import getQXParser from './peggy/qx';
|
import getQXParser from './peggy/qx';
|
||||||
@@ -331,15 +338,27 @@ function URI_VLESS() {
|
|||||||
};
|
};
|
||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split('vless://')[1];
|
line = line.split('vless://')[1];
|
||||||
|
let isShadowrocket;
|
||||||
|
let parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
if (!parsed) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [_, base64, other] = /^(.*?)(\?.*?$)/.exec(line);
|
||||||
|
line = `${Base64.decode(base64)}${other}`;
|
||||||
|
parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
isShadowrocket = true;
|
||||||
|
}
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, uuid, server, port, ___, addons = '', name] =
|
let [__, uuid, server, port, ___, addons = '', name] = parsed;
|
||||||
/^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
if (isShadowrocket) {
|
||||||
|
uuid = uuid.replace(/^.*?:/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
port = parseInt(`${port}`, 10);
|
port = parseInt(`${port}`, 10);
|
||||||
uuid = decodeURIComponent(uuid);
|
uuid = decodeURIComponent(uuid);
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
}
|
}
|
||||||
name = name ?? `VLESS ${server}:${port}`;
|
|
||||||
const proxy = {
|
const proxy = {
|
||||||
type: 'vless',
|
type: 'vless',
|
||||||
name,
|
name,
|
||||||
@@ -355,9 +374,24 @@ function URI_VLESS() {
|
|||||||
params[key] = value;
|
params[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy.name = name ?? params.remarks ?? `VLESS ${server}:${port}`;
|
||||||
|
|
||||||
proxy.tls = params.security && params.security !== 'none';
|
proxy.tls = params.security && params.security !== 'none';
|
||||||
proxy.sni = params.sni;
|
if (isShadowrocket && /TRUE|1/i.test(params.tls)) {
|
||||||
|
proxy.tls = true;
|
||||||
|
params.security = params.security ?? 'reality';
|
||||||
|
}
|
||||||
|
proxy.sni = params.sni ?? params.peer;
|
||||||
proxy.flow = params.flow;
|
proxy.flow = params.flow;
|
||||||
|
if (!proxy.flow && isShadowrocket && params.xtls) {
|
||||||
|
// "none" is undefined
|
||||||
|
const flow = [undefined, 'xtls-rprx-direct', 'xtls-rprx-vision'][
|
||||||
|
params.xtls
|
||||||
|
];
|
||||||
|
if (flow) {
|
||||||
|
proxy.flow = flow;
|
||||||
|
}
|
||||||
|
}
|
||||||
proxy['client-fingerprint'] = params.fp;
|
proxy['client-fingerprint'] = params.fp;
|
||||||
proxy.alpn = params.alpn ? params.alpn.split(',') : undefined;
|
proxy.alpn = params.alpn ? params.alpn.split(',') : undefined;
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.allowInsecure);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.allowInsecure);
|
||||||
@@ -371,21 +405,30 @@ function URI_VLESS() {
|
|||||||
opts['short-id'] = params.sid;
|
opts['short-id'] = params.sid;
|
||||||
}
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
|
// proxy[`${params.security}-opts`] = opts;
|
||||||
proxy[`${params.security}-opts`] = opts;
|
proxy[`${params.security}-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.network = params.type;
|
proxy.network = params.type;
|
||||||
|
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
||||||
|
proxy.network = 'http';
|
||||||
|
}
|
||||||
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
|
proxy.network = params.obfs;
|
||||||
|
}
|
||||||
if (proxy.network && !['tcp', 'none'].includes(proxy.network)) {
|
if (proxy.network && !['tcp', 'none'].includes(proxy.network)) {
|
||||||
const opts = {};
|
const opts = {};
|
||||||
if (params.path) {
|
|
||||||
opts.path = params.path;
|
|
||||||
}
|
|
||||||
if (params.host) {
|
if (params.host) {
|
||||||
opts.headers = { Host: params.host };
|
opts.headers = { Host: params.host };
|
||||||
}
|
}
|
||||||
if (params.serviceName) {
|
if (params.serviceName) {
|
||||||
opts[`${proxy.network}-service-name`] = params.serviceName;
|
opts[`${proxy.network}-service-name`] = params.serviceName;
|
||||||
|
} else if (isShadowrocket && params.path) {
|
||||||
|
opts[`${proxy.network}-service-name`] = params.path;
|
||||||
|
delete params.path;
|
||||||
|
}
|
||||||
|
if (params.path) {
|
||||||
|
opts.path = params.path;
|
||||||
}
|
}
|
||||||
// https://github.com/XTLS/Xray-core/issues/91
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
@@ -535,6 +578,10 @@ function Clash_All() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy.fingerprint) {
|
||||||
|
proxy['tls-fingerprint'] = proxy.fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
if (proxy['benchmark-url']) {
|
if (proxy['benchmark-url']) {
|
||||||
proxy['test-url'] = proxy['benchmark-url'];
|
proxy['test-url'] = proxy['benchmark-url'];
|
||||||
}
|
}
|
||||||
@@ -580,6 +627,15 @@ function QX_VMess() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function QX_VLESS() {
|
||||||
|
const name = 'QX VLESS Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^vless\s*=/.test(line.split(',')[0].trim());
|
||||||
|
};
|
||||||
|
const parse = (line) => getQXParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
function QX_Trojan() {
|
function QX_Trojan() {
|
||||||
const name = 'QX Trojan Parser';
|
const name = 'QX Trojan Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
@@ -838,6 +894,79 @@ function Surge_Socks5() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Surge_External() {
|
||||||
|
const name = 'Surge External Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*external/.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
let parsed = /^\s*(.*?)\s*?=\s*?external\s*?,\s*(.*?)\s*$/.exec(line);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [_, name, other] = parsed;
|
||||||
|
line = other;
|
||||||
|
|
||||||
|
// exec = "/usr/bin/ssh" 或 exec = /usr/bin/ssh
|
||||||
|
let exec = /(,|^)\s*?exec\s*?=\s*"(.*?)"\s*?(,|$)/.exec(line)?.[2];
|
||||||
|
if (!exec) {
|
||||||
|
exec = /(,|^)\s*?exec\s*?=\s*(.*?)\s*?(,|$)/.exec(line)?.[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// local-port = "1080" 或 local-port = 1080
|
||||||
|
let localPort = /(,|^)\s*?local-port\s*?=\s*"(.*?)"\s*?(,|$)/.exec(
|
||||||
|
line,
|
||||||
|
)?.[2];
|
||||||
|
if (!localPort) {
|
||||||
|
localPort = /(,|^)\s*?local-port\s*?=\s*(.*?)\s*?(,|$)/.exec(
|
||||||
|
line,
|
||||||
|
)?.[2];
|
||||||
|
}
|
||||||
|
// args = "-m", args = "rc4-md5"
|
||||||
|
// args = -m, args = rc4-md5
|
||||||
|
const argsRegex = /(,|^)\s*?args\s*?=\s*("(.*?)"|(.*?))(?=\s*?(,|$))/g;
|
||||||
|
let argsMatch;
|
||||||
|
const args = [];
|
||||||
|
while ((argsMatch = argsRegex.exec(line)) !== null) {
|
||||||
|
if (argsMatch[3] != null) {
|
||||||
|
args.push(argsMatch[3]);
|
||||||
|
} else if (argsMatch[4] != null) {
|
||||||
|
args.push(argsMatch[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// addresses = "[ipv6]",,addresses = "ipv6", addresses = "ipv4"
|
||||||
|
// addresses = [ipv6], addresses = ipv6, addresses = ipv4
|
||||||
|
const addressesRegex =
|
||||||
|
/(,|^)\s*?addresses\s*?=\s*("(.*?)"|(.*?))(?=\s*?(,|$))/g;
|
||||||
|
let addressesMatch;
|
||||||
|
const addresses = [];
|
||||||
|
while ((addressesMatch = addressesRegex.exec(line)) !== null) {
|
||||||
|
let ip;
|
||||||
|
if (addressesMatch[3] != null) {
|
||||||
|
ip = addressesMatch[3];
|
||||||
|
} else if (addressesMatch[4] != null) {
|
||||||
|
ip = addressesMatch[4];
|
||||||
|
}
|
||||||
|
if (ip != null) {
|
||||||
|
ip = `${ip}`.trim().replace(/^\[/, '').replace(/\]$/, '');
|
||||||
|
}
|
||||||
|
if (isIP(ip)) {
|
||||||
|
addresses.push(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
type: 'external',
|
||||||
|
name,
|
||||||
|
exec,
|
||||||
|
'local-port': localPort,
|
||||||
|
args,
|
||||||
|
addresses,
|
||||||
|
};
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
function Surge_Snell() {
|
function Surge_Snell() {
|
||||||
const name = 'Surge Snell Parser';
|
const name = 'Surge Snell Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
@@ -873,6 +1002,10 @@ function Surge_Hysteria2() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isIP(ip) {
|
||||||
|
return isIPv4(ip) || isIPv6(ip);
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
URI_SS(),
|
URI_SS(),
|
||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
@@ -890,6 +1023,7 @@ export default [
|
|||||||
Surge_WireGuard(),
|
Surge_WireGuard(),
|
||||||
Surge_Hysteria2(),
|
Surge_Hysteria2(),
|
||||||
Surge_Socks5(),
|
Surge_Socks5(),
|
||||||
|
Surge_External(),
|
||||||
Loon_SS(),
|
Loon_SS(),
|
||||||
Loon_SSR(),
|
Loon_SSR(),
|
||||||
Loon_VMess(),
|
Loon_VMess(),
|
||||||
@@ -901,6 +1035,7 @@ export default [
|
|||||||
QX_SS(),
|
QX_SS(),
|
||||||
QX_SSR(),
|
QX_SSR(),
|
||||||
QX_VMess(),
|
QX_VMess(),
|
||||||
|
QX_VLESS(),
|
||||||
QX_Trojan(),
|
QX_Trojan(),
|
||||||
QX_Http(),
|
QX_Http(),
|
||||||
QX_Socks5(),
|
QX_Socks5(),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (trojan/shadowsocks/vmess/http/socks5) {
|
start = (trojan/shadowsocks/vmess/vless/http/socks5) {
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +91,13 @@ vmess = "vmess" equals address
|
|||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vless = "vless" equals address
|
||||||
|
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
|
||||||
|
proxy.type = "vless";
|
||||||
|
proxy.cipher = proxy.cipher || "none";
|
||||||
|
handleObfs();
|
||||||
|
}
|
||||||
|
|
||||||
http = "http" equals address
|
http = "http" equals address
|
||||||
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (trojan/shadowsocks/vmess/http/socks5) {
|
start = (trojan/shadowsocks/vmess/vless/http/socks5) {
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +89,13 @@ vmess = "vmess" equals address
|
|||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vless = "vless" equals address
|
||||||
|
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
|
||||||
|
proxy.type = "vless";
|
||||||
|
proxy.cipher = proxy.cipher || "none";
|
||||||
|
handleObfs();
|
||||||
|
}
|
||||||
|
|
||||||
http = "http" equals address
|
http = "http" equals address
|
||||||
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
(username/password/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/server_check_url/others)*{
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -46,7 +46,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -56,18 +56,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/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,10 +76,10 @@ 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/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
@@ -89,10 +89,10 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
|||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -44,7 +44,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -54,18 +54,18 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/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,10 +74,10 @@ 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/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
@@ -87,10 +87,10 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
|||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,19 @@ function Clash() {
|
|||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
// Clash YAML format
|
// Clash YAML format
|
||||||
const proxies = safeLoad(raw).proxies;
|
const {
|
||||||
return proxies.map((p) => JSON.stringify(p)).join('\n');
|
proxies,
|
||||||
|
'global-client-fingerprint': globalClientFingerprint,
|
||||||
|
} = safeLoad(raw);
|
||||||
|
return proxies
|
||||||
|
.map((p) => {
|
||||||
|
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
|
||||||
|
if (globalClientFingerprint && !p['client-fingerprint']) {
|
||||||
|
p['client-fingerprint'] = globalClientFingerprint;
|
||||||
|
}
|
||||||
|
return JSON.stringify(p);
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import { ProxyUtils } from '@/core/proxy-utils';
|
|||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
import { getFlowHeaders, parseFlowHeaders, flowTransfer } from '@/utils/flow';
|
import {
|
||||||
|
getFlowField,
|
||||||
|
getFlowHeaders,
|
||||||
|
parseFlowHeaders,
|
||||||
|
flowTransfer,
|
||||||
|
} from '@/utils/flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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:
|
||||||
@@ -319,7 +324,11 @@ function ScriptOperator(script, targetPlatform, $arguments, source) {
|
|||||||
const operator = createDynamicFunction(
|
const operator = createDynamicFunction(
|
||||||
'operator',
|
'operator',
|
||||||
`async function operator(input = []) {
|
`async function operator(input = []) {
|
||||||
if (Array.isArray(input)) {
|
if (input?.$files || input?.$content) {
|
||||||
|
let { $content, $files } = input
|
||||||
|
${script}
|
||||||
|
return { $content, $files }
|
||||||
|
} else {
|
||||||
let proxies = input
|
let proxies = input
|
||||||
let list = []
|
let list = []
|
||||||
for await (let $server of proxies) {
|
for await (let $server of proxies) {
|
||||||
@@ -327,12 +336,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source) {
|
|||||||
list.push($server)
|
list.push($server)
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
} else {
|
|
||||||
let { $content, $files } = input
|
|
||||||
${script}
|
|
||||||
return { $content, $files }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}`,
|
}`,
|
||||||
$arguments,
|
$arguments,
|
||||||
);
|
);
|
||||||
@@ -681,7 +685,7 @@ async function ApplyFilter(filter, objs) {
|
|||||||
selected = await filter.nodeFunc(objs);
|
selected = await filter.nodeFunc(objs);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
`Cannot apply filter ${filter.name}(node script)! Reason: ${err}`,
|
`Cannot apply filter ${filter.name}(shortcut script)! Reason: ${err}`,
|
||||||
);
|
);
|
||||||
let nodeErr = '';
|
let nodeErr = '';
|
||||||
let nodeErrMsg = `${err.message ?? err}`;
|
let nodeErrMsg = `${err.message ?? err}`;
|
||||||
@@ -689,7 +693,7 @@ async function ApplyFilter(filter, objs) {
|
|||||||
nodeErr = '';
|
nodeErr = '';
|
||||||
funcErr = `执行失败 ${funcErrMsg}`;
|
funcErr = `执行失败 ${funcErrMsg}`;
|
||||||
} else {
|
} else {
|
||||||
nodeErr = `执行节点快捷过滤脚本 失败 ${nodeErr}`;
|
nodeErr = `执行快捷过滤脚本 失败 ${nodeErrMsg}`;
|
||||||
}
|
}
|
||||||
throw new Error(`脚本过滤 ${funcErr}${nodeErr}`);
|
throw new Error(`脚本过滤 ${funcErr}${nodeErr}`);
|
||||||
}
|
}
|
||||||
@@ -708,7 +712,9 @@ async function ApplyOperator(operator, objs) {
|
|||||||
if (
|
if (
|
||||||
funcErrMsg.includes('$server is not defined') ||
|
funcErrMsg.includes('$server is not defined') ||
|
||||||
funcErrMsg.includes('$content is not defined') ||
|
funcErrMsg.includes('$content is not defined') ||
|
||||||
funcErrMsg.includes('$files is not defined')
|
funcErrMsg.includes('$files is not defined') ||
|
||||||
|
output?.$files ||
|
||||||
|
output?.$content
|
||||||
) {
|
) {
|
||||||
funcErr = '';
|
funcErr = '';
|
||||||
} else {
|
} else {
|
||||||
@@ -722,7 +728,7 @@ async function ApplyOperator(operator, objs) {
|
|||||||
if (output_) output = output_;
|
if (output_) output = output_;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
`Cannot apply operator ${operator.name}(node script)! Reason: ${err}`,
|
`Cannot apply operator ${operator.name}(shortcut script)! Reason: ${err}`,
|
||||||
);
|
);
|
||||||
let nodeErr = '';
|
let nodeErr = '';
|
||||||
let nodeErrMsg = `${err.message ?? err}`;
|
let nodeErrMsg = `${err.message ?? err}`;
|
||||||
@@ -730,7 +736,7 @@ async function ApplyOperator(operator, objs) {
|
|||||||
nodeErr = '';
|
nodeErr = '';
|
||||||
funcErr = `执行失败 ${funcErrMsg}`;
|
funcErr = `执行失败 ${funcErrMsg}`;
|
||||||
} else {
|
} else {
|
||||||
nodeErr = `执行节点快捷脚本 失败 ${nodeErr}`;
|
nodeErr = `执行快捷脚本 失败 ${nodeErrMsg}`;
|
||||||
}
|
}
|
||||||
throw new Error(`脚本操作 ${funcErr}${nodeErr}`);
|
throw new Error(`脚本操作 ${funcErr}${nodeErr}`);
|
||||||
}
|
}
|
||||||
@@ -780,7 +786,12 @@ function removeFlag(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createDynamicFunction(name, script, $arguments) {
|
function createDynamicFunction(name, script, $arguments) {
|
||||||
const flowUtils = { getFlowHeaders, parseFlowHeaders, flowTransfer };
|
const flowUtils = {
|
||||||
|
getFlowField,
|
||||||
|
getFlowHeaders,
|
||||||
|
parseFlowHeaders,
|
||||||
|
flowTransfer,
|
||||||
|
};
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
'$arguments',
|
'$arguments',
|
||||||
|
|||||||
@@ -2,136 +2,139 @@ 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, type, opts = {}) => {
|
||||||
// VLESS XTLS is not supported by Clash
|
// VLESS XTLS is not supported by Clash
|
||||||
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L532
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L532
|
||||||
// github.com/Dreamacro/clash/pull/2891/files
|
// github.com/Dreamacro/clash/pull/2891/files
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
// https://clash.wiki/configuration/outbound.html#shadowsocks
|
// https://clash.wiki/configuration/outbound.html#shadowsocks
|
||||||
proxies = proxies.filter((proxy) => {
|
const list = proxies
|
||||||
if (
|
.filter((proxy) => {
|
||||||
![
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
'ss',
|
if (
|
||||||
'ssr',
|
|
||||||
'vmess',
|
|
||||||
'vless',
|
|
||||||
'socks5',
|
|
||||||
'http',
|
|
||||||
'snell',
|
|
||||||
'trojan',
|
|
||||||
'wireguard',
|
|
||||||
].includes(proxy.type) ||
|
|
||||||
(proxy.type === 'ss' &&
|
|
||||||
![
|
![
|
||||||
'aes-128-gcm',
|
'ss',
|
||||||
'aes-192-gcm',
|
'ssr',
|
||||||
'aes-256-gcm',
|
'vmess',
|
||||||
'aes-128-cfb',
|
'vless',
|
||||||
'aes-192-cfb',
|
'socks5',
|
||||||
'aes-256-cfb',
|
'http',
|
||||||
'aes-128-ctr',
|
'snell',
|
||||||
'aes-192-ctr',
|
'trojan',
|
||||||
'aes-256-ctr',
|
'wireguard',
|
||||||
'rc4-md5',
|
].includes(proxy.type) ||
|
||||||
'chacha20-ietf',
|
(proxy.type === 'ss' &&
|
||||||
'xchacha20',
|
![
|
||||||
'chacha20-ietf-poly1305',
|
'aes-128-gcm',
|
||||||
'xchacha20-ietf-poly1305',
|
'aes-192-gcm',
|
||||||
].includes(proxy.cipher)) ||
|
'aes-256-gcm',
|
||||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
'aes-128-cfb',
|
||||||
(proxy.type === 'vless' &&
|
'aes-192-cfb',
|
||||||
(typeof proxy.flow !== 'undefined' ||
|
'aes-256-cfb',
|
||||||
proxy['reality-opts']))
|
'aes-128-ctr',
|
||||||
) {
|
'aes-192-ctr',
|
||||||
return false;
|
'aes-256-ctr',
|
||||||
}
|
'rc4-md5',
|
||||||
return true;
|
'chacha20-ietf',
|
||||||
});
|
'xchacha20',
|
||||||
return (
|
'chacha20-ietf-poly1305',
|
||||||
'proxies:\n' +
|
'xchacha20-ietf-poly1305',
|
||||||
proxies
|
].includes(proxy.cipher)) ||
|
||||||
.map((proxy) => {
|
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||||
if (proxy.type === 'vmess') {
|
(proxy.type === 'vless' &&
|
||||||
// handle vmess aead
|
(typeof proxy.flow !== 'undefined' ||
|
||||||
if (isPresent(proxy, 'aead')) {
|
proxy['reality-opts']))
|
||||||
if (proxy.aead) {
|
) {
|
||||||
proxy.alterId = 0;
|
return false;
|
||||||
}
|
}
|
||||||
delete proxy.aead;
|
return true;
|
||||||
}
|
})
|
||||||
if (isPresent(proxy, 'sni')) {
|
.map((proxy) => {
|
||||||
proxy.servername = proxy.sni;
|
if (proxy.type === 'vmess') {
|
||||||
delete proxy.sni;
|
// handle vmess aead
|
||||||
}
|
if (isPresent(proxy, 'aead')) {
|
||||||
// https://dreamacro.github.io/clash/configuration/outbound.html#vmess
|
if (proxy.aead) {
|
||||||
if (
|
proxy.alterId = 0;
|
||||||
isPresent(proxy, 'cipher') &&
|
|
||||||
![
|
|
||||||
'auto',
|
|
||||||
'aes-128-gcm',
|
|
||||||
'chacha20-poly1305',
|
|
||||||
'none',
|
|
||||||
].includes(proxy.cipher)
|
|
||||||
) {
|
|
||||||
proxy.cipher = 'auto';
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'wireguard') {
|
|
||||||
proxy.keepalive =
|
|
||||||
proxy.keepalive ?? proxy['persistent-keepalive'];
|
|
||||||
proxy['persistent-keepalive'] = proxy.keepalive;
|
|
||||||
proxy['preshared-key'] =
|
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
|
||||||
} else if (proxy.type === 'vless') {
|
|
||||||
if (isPresent(proxy, 'sni')) {
|
|
||||||
proxy.servername = proxy.sni;
|
|
||||||
delete proxy.sni;
|
|
||||||
}
|
}
|
||||||
|
delete proxy.aead;
|
||||||
}
|
}
|
||||||
|
if (isPresent(proxy, 'sni')) {
|
||||||
|
proxy.servername = 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';
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'wireguard') {
|
||||||
|
proxy.keepalive =
|
||||||
|
proxy.keepalive ?? proxy['persistent-keepalive'];
|
||||||
|
proxy['persistent-keepalive'] = proxy.keepalive;
|
||||||
|
proxy['preshared-key'] =
|
||||||
|
proxy['preshared-key'] ?? proxy['pre-shared-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 (
|
||||||
|
['vmess', 'vless'].includes(proxy.type) &&
|
||||||
|
proxy.network === 'http'
|
||||||
|
) {
|
||||||
|
let httpPath = proxy['http-opts']?.path;
|
||||||
if (
|
if (
|
||||||
['vmess', 'vless'].includes(proxy.type) &&
|
isPresent(proxy, 'http-opts.path') &&
|
||||||
proxy.network === 'http'
|
!Array.isArray(httpPath)
|
||||||
) {
|
) {
|
||||||
let httpPath = proxy['http-opts']?.path;
|
proxy['http-opts'].path = [httpPath];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let httpHost = proxy['http-opts']?.headers?.Host;
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
isPresent(proxy, 'http-opts.headers.Host') &&
|
||||||
proxy.type,
|
!Array.isArray(httpHost)
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete proxy.tls;
|
||||||
|
}
|
||||||
|
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
) {
|
) {
|
||||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
}
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return proxy;
|
||||||
})
|
});
|
||||||
.join('')
|
return type === 'internal'
|
||||||
);
|
? list
|
||||||
|
: 'proxies:\n' +
|
||||||
|
list
|
||||||
|
.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n')
|
||||||
|
.join('');
|
||||||
};
|
};
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Loon_Producer from './loon';
|
|||||||
import URI_Producer from './uri';
|
import URI_Producer from './uri';
|
||||||
import V2Ray_Producer from './v2ray';
|
import V2Ray_Producer from './v2ray';
|
||||||
import QX_Producer from './qx';
|
import QX_Producer from './qx';
|
||||||
import ShadowRocket_Producer from './shadowrocket';
|
import Shadowrocket_Producer from './shadowrocket';
|
||||||
import Surfboard_Producer from './surfboard';
|
import Surfboard_Producer from './surfboard';
|
||||||
import singbox_Producer from './sing-box';
|
import singbox_Producer from './sing-box';
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ function JSON_Producer() {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
QX: QX_Producer(),
|
QX: QX_Producer(),
|
||||||
|
QuantumultX: QX_Producer(),
|
||||||
Surge: Surge_Producer(),
|
Surge: Surge_Producer(),
|
||||||
SurgeMac: SurgeMac_Producer(),
|
SurgeMac: SurgeMac_Producer(),
|
||||||
Loon: Loon_Producer(),
|
Loon: Loon_Producer(),
|
||||||
@@ -28,7 +29,8 @@ export default {
|
|||||||
V2Ray: V2Ray_Producer(),
|
V2Ray: V2Ray_Producer(),
|
||||||
JSON: JSON_Producer(),
|
JSON: JSON_Producer(),
|
||||||
Stash: Stash_Producer(),
|
Stash: Stash_Producer(),
|
||||||
ShadowRocket: ShadowRocket_Producer(),
|
Shadowrocket: Shadowrocket_Producer(),
|
||||||
|
ShadowRocket: Shadowrocket_Producer(),
|
||||||
Surfboard: Surfboard_Producer(),
|
Surfboard: Surfboard_Producer(),
|
||||||
'sing-box': singbox_Producer(),
|
'sing-box': singbox_Producer(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ function trojan(proxy) {
|
|||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=trojan,${proxy.server},${proxy.port},"${proxy.password}"`,
|
`${proxy.name}=trojan,${proxy.server},${proxy.port},"${proxy.password}"`,
|
||||||
);
|
);
|
||||||
|
if (proxy.network === 'tcp') {
|
||||||
|
delete proxy.network;
|
||||||
|
}
|
||||||
// transport
|
// transport
|
||||||
if (isPresent(proxy, 'network')) {
|
if (isPresent(proxy, 'network')) {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
@@ -134,7 +136,9 @@ function vmess(proxy) {
|
|||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
|
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
|
||||||
);
|
);
|
||||||
|
if (proxy.network === 'tcp') {
|
||||||
|
delete proxy.network;
|
||||||
|
}
|
||||||
// transport
|
// transport
|
||||||
if (isPresent(proxy, 'network')) {
|
if (isPresent(proxy, 'network')) {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
@@ -195,13 +199,15 @@ function vmess(proxy) {
|
|||||||
|
|
||||||
function vless(proxy) {
|
function vless(proxy) {
|
||||||
if (proxy['reality-opts']) {
|
if (proxy['reality-opts']) {
|
||||||
throw new Error(`reality is unsupported`);
|
throw new Error(`VLESS REALITY is unsupported`);
|
||||||
}
|
}
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(
|
result.append(
|
||||||
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
|
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
|
||||||
);
|
);
|
||||||
|
if (proxy.network === 'tcp') {
|
||||||
|
delete proxy.network;
|
||||||
|
}
|
||||||
// transport
|
// transport
|
||||||
if (isPresent(proxy, 'network')) {
|
if (isPresent(proxy, 'network')) {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { isPresent, Result } from './utils';
|
|||||||
const targetPlatform = 'QX';
|
const targetPlatform = 'QX';
|
||||||
|
|
||||||
export default function QX_Producer() {
|
export default function QX_Producer() {
|
||||||
const produce = (proxy) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy);
|
||||||
@@ -17,6 +17,14 @@ export default function QX_Producer() {
|
|||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'socks5':
|
case 'socks5':
|
||||||
return socks5(proxy);
|
return socks5(proxy);
|
||||||
|
case 'vless':
|
||||||
|
if (opts['include-unsupported-proxy']) {
|
||||||
|
return vless(proxy);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Platform ${targetPlatform}(App Store Release) does not support proxy type: ${proxy.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||||
@@ -325,6 +333,105 @@ function vmess(proxy) {
|
|||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
function vless(proxy) {
|
||||||
|
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
|
||||||
|
throw new Error(`VLESS XTLS/REALITY is not supported`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = new Result(proxy);
|
||||||
|
const append = result.append.bind(result);
|
||||||
|
const appendIfPresent = result.appendIfPresent.bind(result);
|
||||||
|
|
||||||
|
append(`vless=${proxy.server}:${proxy.port}`);
|
||||||
|
|
||||||
|
// The method field for vless should be none.
|
||||||
|
let cipher = 'none';
|
||||||
|
// if (proxy.cipher === 'auto') {
|
||||||
|
// cipher = 'chacha20-ietf-poly1305';
|
||||||
|
// } else {
|
||||||
|
// cipher = proxy.cipher;
|
||||||
|
// }
|
||||||
|
append(`,method=${cipher}`);
|
||||||
|
|
||||||
|
append(`,password=${proxy.uuid}`);
|
||||||
|
|
||||||
|
// obfs
|
||||||
|
if (needTls(proxy)) {
|
||||||
|
proxy.tls = true;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'network')) {
|
||||||
|
if (proxy.network === 'ws') {
|
||||||
|
if (proxy.tls) append(`,obfs=wss`);
|
||||||
|
else append(`,obfs=ws`);
|
||||||
|
} else if (proxy.network === 'http') {
|
||||||
|
append(`,obfs=http`);
|
||||||
|
} else if (!['tcp'].includes(proxy.network)) {
|
||||||
|
throw new Error(`network ${proxy.network} is unsupported`);
|
||||||
|
}
|
||||||
|
let transportPath = proxy[`${proxy.network}-opts`]?.path;
|
||||||
|
let transportHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
|
||||||
|
appendIfPresent(
|
||||||
|
`,obfs-uri=${
|
||||||
|
Array.isArray(transportPath) ? transportPath[0] : transportPath
|
||||||
|
}`,
|
||||||
|
`${proxy.network}-opts.path`,
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,obfs-host=${
|
||||||
|
Array.isArray(transportHost) ? transportHost[0] : transportHost
|
||||||
|
}`,
|
||||||
|
`${proxy.network}-opts.headers.Host`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// over-tls
|
||||||
|
if (proxy.tls) append(`,obfs=over-tls`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needTls(proxy)) {
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
|
'tls-pubkey-sha256',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-alpn=${proxy['tls-alpn']}`, 'tls-alpn');
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-ticket=${proxy['tls-no-session-ticket']}`,
|
||||||
|
'tls-no-session-ticket',
|
||||||
|
);
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-no-session-reuse=${proxy['tls-no-session-reuse']}`,
|
||||||
|
'tls-no-session-reuse',
|
||||||
|
);
|
||||||
|
// tls fingerprint
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
|
'tls-fingerprint',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tls verification
|
||||||
|
appendIfPresent(
|
||||||
|
`,tls-verification=${!proxy['skip-cert-verify']}`,
|
||||||
|
'skip-cert-verify',
|
||||||
|
);
|
||||||
|
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
|
||||||
|
}
|
||||||
|
|
||||||
|
// tfo
|
||||||
|
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
|
// udp
|
||||||
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
|
// server_check_url
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
|
'test-url',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tag
|
||||||
|
append(`,tag=${proxy.name}`);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
function http(proxy) {
|
function http(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
|
|||||||
@@ -2,162 +2,161 @@ import { isPresent } from '@/core/proxy-utils/producers/utils';
|
|||||||
|
|
||||||
export default function ShadowRocket_Producer() {
|
export default function ShadowRocket_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
return (
|
const list = proxies
|
||||||
'proxies:\n' +
|
.filter((proxy) => {
|
||||||
proxies
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
.filter((proxy) => {
|
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((proxy) => {
|
||||||
|
if (proxy.type === 'vmess') {
|
||||||
|
// handle vmess aead
|
||||||
|
if (isPresent(proxy, 'aead')) {
|
||||||
|
if (proxy.aead) {
|
||||||
|
proxy.alterId = 0;
|
||||||
|
}
|
||||||
|
delete proxy.aead;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'sni')) {
|
||||||
|
proxy.servername = 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 (
|
if (
|
||||||
proxy.type === 'snell' &&
|
isPresent(proxy, 'cipher') &&
|
||||||
String(proxy.version) === '4'
|
![
|
||||||
|
'auto',
|
||||||
|
'aes-128-gcm',
|
||||||
|
'chacha20-poly1305',
|
||||||
|
'none',
|
||||||
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
return false;
|
proxy.cipher = 'auto';
|
||||||
}
|
}
|
||||||
return true;
|
} else if (proxy.type === 'tuic') {
|
||||||
})
|
if (isPresent(proxy, 'alpn')) {
|
||||||
.map((proxy) => {
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
if (proxy.type === 'vmess') {
|
? proxy.alpn
|
||||||
// handle vmess aead
|
: [proxy.alpn];
|
||||||
if (isPresent(proxy, 'aead')) {
|
} else {
|
||||||
if (proxy.aead) {
|
proxy.alpn = ['h3'];
|
||||||
proxy.alterId = 0;
|
|
||||||
}
|
|
||||||
delete proxy.aead;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'sni')) {
|
|
||||||
proxy.servername = 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'];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'tfo') &&
|
|
||||||
!isPresent(proxy, 'fast-open')
|
|
||||||
) {
|
|
||||||
proxy['fast-open'] = proxy.tfo;
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'hysteria') {
|
|
||||||
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'auth_str') &&
|
|
||||||
!isPresent(proxy, 'auth-str')
|
|
||||||
) {
|
|
||||||
proxy['auth-str'] = proxy['auth_str'];
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'alpn')) {
|
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
|
||||||
? proxy.alpn
|
|
||||||
: [proxy.alpn];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'tfo') &&
|
|
||||||
!isPresent(proxy, 'fast-open')
|
|
||||||
) {
|
|
||||||
proxy['fast-open'] = proxy.tfo;
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'hysteria2') {
|
|
||||||
if (
|
|
||||||
proxy['obfs-password'] &&
|
|
||||||
proxy.obfs == 'salamander'
|
|
||||||
) {
|
|
||||||
proxy.obfs = proxy['obfs-password'];
|
|
||||||
delete proxy['obfs-password'];
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'alpn')) {
|
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
|
||||||
? proxy.alpn
|
|
||||||
: [proxy.alpn];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'tfo') &&
|
|
||||||
!isPresent(proxy, 'fast-open')
|
|
||||||
) {
|
|
||||||
proxy['fast-open'] = proxy.tfo;
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'wireguard') {
|
|
||||||
proxy.keepalive =
|
|
||||||
proxy.keepalive ?? proxy['persistent-keepalive'];
|
|
||||||
proxy['persistent-keepalive'] = proxy.keepalive;
|
|
||||||
proxy['preshared-key'] =
|
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-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 (
|
||||||
|
isPresent(proxy, 'tfo') &&
|
||||||
|
!isPresent(proxy, 'fast-open')
|
||||||
|
) {
|
||||||
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'hysteria') {
|
||||||
|
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'auth_str') &&
|
||||||
|
!isPresent(proxy, 'auth-str')
|
||||||
|
) {
|
||||||
|
proxy['auth-str'] = proxy['auth_str'];
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'alpn')) {
|
||||||
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
|
? proxy.alpn
|
||||||
|
: [proxy.alpn];
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'tfo') &&
|
||||||
|
!isPresent(proxy, 'fast-open')
|
||||||
|
) {
|
||||||
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'hysteria2') {
|
||||||
|
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||||
|
proxy.obfs = proxy['obfs-password'];
|
||||||
|
delete proxy['obfs-password'];
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'alpn')) {
|
||||||
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
|
? proxy.alpn
|
||||||
|
: [proxy.alpn];
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'tfo') &&
|
||||||
|
!isPresent(proxy, 'fast-open')
|
||||||
|
) {
|
||||||
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'wireguard') {
|
||||||
|
proxy.keepalive =
|
||||||
|
proxy.keepalive ?? proxy['persistent-keepalive'];
|
||||||
|
proxy['persistent-keepalive'] = proxy.keepalive;
|
||||||
|
proxy['preshared-key'] =
|
||||||
|
proxy['preshared-key'] ?? proxy['pre-shared-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 (
|
||||||
|
['vmess', 'vless'].includes(proxy.type) &&
|
||||||
|
proxy.network === 'http'
|
||||||
|
) {
|
||||||
|
let httpPath = proxy['http-opts']?.path;
|
||||||
if (
|
if (
|
||||||
['vmess', 'vless'].includes(proxy.type) &&
|
isPresent(proxy, 'http-opts.path') &&
|
||||||
proxy.network === 'http'
|
!Array.isArray(httpPath)
|
||||||
) {
|
) {
|
||||||
let httpPath = proxy['http-opts']?.path;
|
proxy['http-opts'].path = [httpPath];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let httpHost = proxy['http-opts']?.headers?.Host;
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'http-opts.headers.Host') &&
|
||||||
|
!Array.isArray(httpHost)
|
||||||
|
) {
|
||||||
|
proxy['http-opts'].headers.Host = [httpHost];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
proxy.type,
|
proxy.type,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
) {
|
) {
|
||||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
}
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
return proxy;
|
||||||
})
|
});
|
||||||
.join('')
|
return type === 'internal'
|
||||||
);
|
? list
|
||||||
|
: 'proxies:\n' +
|
||||||
|
list
|
||||||
|
.map((proxy) => {
|
||||||
|
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
};
|
};
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -537,12 +537,16 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wireguardParser = (proxy = {}) => {
|
const wireguardParser = (proxy = {}) => {
|
||||||
|
const local_address = ['ip', 'ipv6']
|
||||||
|
.map((i) => proxy[i])
|
||||||
|
.filter((i) => i)
|
||||||
|
.map((i) => (/\\/.test(i) ? i : `${i}/32`));
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
type: 'wireguard',
|
type: 'wireguard',
|
||||||
server: proxy.server,
|
server: proxy.server,
|
||||||
server_port: parseInt(`${proxy.port}`, 10),
|
server_port: parseInt(`${proxy.port}`, 10),
|
||||||
local_address: [proxy.ip, proxy.ipv6],
|
local_address,
|
||||||
private_key: proxy['private-key'],
|
private_key: proxy['private-key'],
|
||||||
peer_public_key: proxy['public-key'],
|
peer_public_key: proxy['public-key'],
|
||||||
pre_shared_key: proxy['pre-shared-key'],
|
pre_shared_key: proxy['pre-shared-key'],
|
||||||
@@ -563,7 +567,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
server: p.server,
|
server: p.server,
|
||||||
server_port: parseInt(`${p.port}`, 10),
|
server_port: parseInt(`${p.port}`, 10),
|
||||||
public_key: p['public-key'],
|
public_key: p['public-key'],
|
||||||
allowed_ips: p.allowed_ips,
|
allowed_ips: p['allowed-ips'] || p.allowed_ips,
|
||||||
reserved: [],
|
reserved: [],
|
||||||
};
|
};
|
||||||
if (typeof p.reserved === 'string') {
|
if (typeof p.reserved === 'string') {
|
||||||
@@ -582,7 +586,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
|
|
||||||
export default function singbox_Producer() {
|
export default function singbox_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
const list = [];
|
const list = [];
|
||||||
ClashMeta_Producer()
|
ClashMeta_Producer()
|
||||||
.produce(proxies, 'internal', { 'include-unsupported-proxy': true })
|
.produce(proxies, 'internal', { 'include-unsupported-proxy': true })
|
||||||
@@ -610,9 +614,15 @@ export default function singbox_Producer() {
|
|||||||
list.push(ssParser(proxy));
|
list.push(ssParser(proxy));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// case 'ssr':
|
case 'ssr':
|
||||||
// list.push(ssrParser(proxy));
|
if (opts['include-unsupported-proxy']) {
|
||||||
// break;
|
list.push(ssrParser(proxy));
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
if (
|
if (
|
||||||
!proxy.network ||
|
!proxy.network ||
|
||||||
|
|||||||
@@ -2,241 +2,243 @@ import { isPresent } from '@/core/proxy-utils/producers/utils';
|
|||||||
|
|
||||||
export default function Stash_Producer() {
|
export default function Stash_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
// https://stash.wiki/proxy-protocols/proxy-types#shadowsocks
|
// https://stash.wiki/proxy-protocols/proxy-types#shadowsocks
|
||||||
return (
|
const list = proxies
|
||||||
'proxies:\n' +
|
.filter((proxy) => {
|
||||||
proxies
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
.filter((proxy) => {
|
if (
|
||||||
if (
|
![
|
||||||
|
'ss',
|
||||||
|
'ssr',
|
||||||
|
'vmess',
|
||||||
|
'socks5',
|
||||||
|
'http',
|
||||||
|
'snell',
|
||||||
|
'trojan',
|
||||||
|
'tuic',
|
||||||
|
'vless',
|
||||||
|
'wireguard',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
].includes(proxy.type) ||
|
||||||
|
(proxy.type === 'ss' &&
|
||||||
![
|
![
|
||||||
'ss',
|
'aes-128-gcm',
|
||||||
'ssr',
|
'aes-192-gcm',
|
||||||
'vmess',
|
'aes-256-gcm',
|
||||||
'socks5',
|
'aes-128-cfb',
|
||||||
'http',
|
'aes-192-cfb',
|
||||||
'snell',
|
'aes-256-cfb',
|
||||||
'trojan',
|
'aes-128-ctr',
|
||||||
'tuic',
|
'aes-192-ctr',
|
||||||
'vless',
|
'aes-256-ctr',
|
||||||
'wireguard',
|
'rc4-md5',
|
||||||
'hysteria',
|
'chacha20-ietf',
|
||||||
'hysteria2',
|
'xchacha20',
|
||||||
].includes(proxy.type) ||
|
'chacha20-ietf-poly1305',
|
||||||
(proxy.type === 'ss' &&
|
'xchacha20-ietf-poly1305',
|
||||||
![
|
].includes(proxy.cipher)) ||
|
||||||
'aes-128-gcm',
|
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||||
'aes-192-gcm',
|
(proxy.type === 'vless' && proxy['reality-opts'])
|
||||||
'aes-256-gcm',
|
) {
|
||||||
'aes-128-cfb',
|
return false;
|
||||||
'aes-192-cfb',
|
}
|
||||||
'aes-256-cfb',
|
return true;
|
||||||
'aes-128-ctr',
|
})
|
||||||
'aes-192-ctr',
|
.map((proxy) => {
|
||||||
'aes-256-ctr',
|
if (proxy.type === 'vmess') {
|
||||||
'rc4-md5',
|
// handle vmess aead
|
||||||
'chacha20-ietf',
|
if (isPresent(proxy, 'aead')) {
|
||||||
'xchacha20',
|
if (proxy.aead) {
|
||||||
'chacha20-ietf-poly1305',
|
proxy.alterId = 0;
|
||||||
'xchacha20-ietf-poly1305',
|
}
|
||||||
].includes(proxy.cipher)) ||
|
delete proxy.aead;
|
||||||
(proxy.type === 'snell' &&
|
|
||||||
String(proxy.version) === '4') ||
|
|
||||||
(proxy.type === 'vless' && proxy['reality-opts'])
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
if (isPresent(proxy, 'sni')) {
|
||||||
})
|
proxy.servername = proxy.sni;
|
||||||
.map((proxy) => {
|
delete proxy.sni;
|
||||||
if (proxy.type === 'vmess') {
|
|
||||||
// handle vmess aead
|
|
||||||
if (isPresent(proxy, 'aead')) {
|
|
||||||
if (proxy.aead) {
|
|
||||||
proxy.alterId = 0;
|
|
||||||
}
|
|
||||||
delete proxy.aead;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'sni')) {
|
|
||||||
proxy.servername = 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'];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'tfo') &&
|
|
||||||
!isPresent(proxy, 'fast-open')
|
|
||||||
) {
|
|
||||||
proxy['fast-open'] = proxy.tfo;
|
|
||||||
delete proxy.tfo;
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'hysteria') {
|
|
||||||
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'auth_str') &&
|
|
||||||
!isPresent(proxy, 'auth-str')
|
|
||||||
) {
|
|
||||||
proxy['auth-str'] = proxy['auth_str'];
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'alpn')) {
|
|
||||||
proxy.alpn = Array.isArray(proxy.alpn)
|
|
||||||
? proxy.alpn
|
|
||||||
: [proxy.alpn];
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'down-speed')) {
|
|
||||||
proxy['down-speed'] =
|
|
||||||
`${proxy['down-speed']}`.match(/\d+/)?.[0] || 0;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'up-speed')) {
|
|
||||||
proxy['up-speed'] =
|
|
||||||
`${proxy['up-speed']}`.match(/\d+/)?.[0] || 0;
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'down-speed')) {
|
|
||||||
proxy['down-speed'] =
|
|
||||||
`${proxy['down-speed']}`.match(/\d+/)?.[0] || 0;
|
|
||||||
}
|
|
||||||
if (isPresent(proxy, 'up-speed')) {
|
|
||||||
proxy['up-speed'] =
|
|
||||||
`${proxy['up-speed']}`.match(/\d+/)?.[0] || 0;
|
|
||||||
}
|
|
||||||
} else if (proxy.type === 'wireguard') {
|
|
||||||
proxy.keepalive =
|
|
||||||
proxy.keepalive ?? proxy['persistent-keepalive'];
|
|
||||||
proxy['persistent-keepalive'] = proxy.keepalive;
|
|
||||||
proxy['preshared-key'] =
|
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
|
||||||
} else if (proxy.type === 'vless') {
|
|
||||||
if (isPresent(proxy, 'sni')) {
|
|
||||||
proxy.servername = 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 (
|
if (
|
||||||
['vmess', 'vless'].includes(proxy.type) &&
|
isPresent(proxy, 'cipher') &&
|
||||||
proxy.network === 'http'
|
![
|
||||||
|
'auto',
|
||||||
|
'aes-128-gcm',
|
||||||
|
'chacha20-poly1305',
|
||||||
|
'none',
|
||||||
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
let httpPath = proxy['http-opts']?.path;
|
proxy.cipher = 'auto';
|
||||||
if (
|
}
|
||||||
isPresent(proxy, 'http-opts.path') &&
|
} else if (proxy.type === 'tuic') {
|
||||||
!Array.isArray(httpPath)
|
if (isPresent(proxy, 'alpn')) {
|
||||||
) {
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
proxy['http-opts'].path = [httpPath];
|
? proxy.alpn
|
||||||
}
|
: [proxy.alpn];
|
||||||
let httpHost = proxy['http-opts']?.headers?.Host;
|
} else {
|
||||||
if (
|
proxy.alpn = ['h3'];
|
||||||
isPresent(proxy, 'http-opts.headers.Host') &&
|
|
||||||
!Array.isArray(httpHost)
|
|
||||||
) {
|
|
||||||
proxy['http-opts'].headers.Host = [httpHost];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
isPresent(proxy, 'tfo') &&
|
||||||
proxy.type,
|
!isPresent(proxy, 'fast-open')
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
proxy['fast-open'] = proxy.tfo;
|
||||||
|
delete proxy.tfo;
|
||||||
}
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/adapter/outbound/tuic.go#L197
|
||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
|
||||||
}
|
|
||||||
delete proxy['tls-fingerprint'];
|
|
||||||
|
|
||||||
if (proxy['test-url']) {
|
|
||||||
proxy['benchmark-url'] = proxy['test-url'];
|
|
||||||
delete proxy['test-url'];
|
|
||||||
}
|
|
||||||
|
|
||||||
delete proxy.subName;
|
|
||||||
delete proxy.collectionName;
|
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
(!proxy.token || proxy.token.length === 0) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
!isPresent(proxy, 'version')
|
||||||
) {
|
) {
|
||||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
proxy.version = 5;
|
||||||
}
|
}
|
||||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
} else if (proxy.type === 'hysteria') {
|
||||||
})
|
// auth_str 将会在未来某个时候删除 但是有的机场不规范
|
||||||
.join('')
|
if (
|
||||||
);
|
isPresent(proxy, 'auth_str') &&
|
||||||
|
!isPresent(proxy, 'auth-str')
|
||||||
|
) {
|
||||||
|
proxy['auth-str'] = proxy['auth_str'];
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'alpn')) {
|
||||||
|
proxy.alpn = Array.isArray(proxy.alpn)
|
||||||
|
? proxy.alpn
|
||||||
|
: [proxy.alpn];
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'down-speed')) {
|
||||||
|
proxy['down-speed'] =
|
||||||
|
`${proxy['down-speed']}`.match(/\d+/)?.[0] || 0;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'up-speed')) {
|
||||||
|
proxy['up-speed'] =
|
||||||
|
`${proxy['up-speed']}`.match(/\d+/)?.[0] || 0;
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'down-speed')) {
|
||||||
|
proxy['down-speed'] =
|
||||||
|
`${proxy['down-speed']}`.match(/\d+/)?.[0] || 0;
|
||||||
|
}
|
||||||
|
if (isPresent(proxy, 'up-speed')) {
|
||||||
|
proxy['up-speed'] =
|
||||||
|
`${proxy['up-speed']}`.match(/\d+/)?.[0] || 0;
|
||||||
|
}
|
||||||
|
} else if (proxy.type === 'wireguard') {
|
||||||
|
proxy.keepalive =
|
||||||
|
proxy.keepalive ?? proxy['persistent-keepalive'];
|
||||||
|
proxy['persistent-keepalive'] = proxy.keepalive;
|
||||||
|
proxy['preshared-key'] =
|
||||||
|
proxy['preshared-key'] ?? proxy['pre-shared-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 (
|
||||||
|
['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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete proxy.tls;
|
||||||
|
}
|
||||||
|
if (proxy['tls-fingerprint']) {
|
||||||
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['test-url']) {
|
||||||
|
proxy['benchmark-url'] = proxy['test-url'];
|
||||||
|
delete proxy['test-url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete proxy.subName;
|
||||||
|
delete proxy.collectionName;
|
||||||
|
if (
|
||||||
|
['grpc'].includes(proxy.network) &&
|
||||||
|
proxy[`${proxy.network}-opts`]
|
||||||
|
) {
|
||||||
|
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
|
});
|
||||||
|
return type === 'internal'
|
||||||
|
? list
|
||||||
|
: 'proxies:\n' +
|
||||||
|
list
|
||||||
|
.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n')
|
||||||
|
.join('');
|
||||||
};
|
};
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { Result } from './utils';
|
import { Result } from './utils';
|
||||||
import Surge_Producer from './surge';
|
import Surge_Producer from './surge';
|
||||||
|
import { isIPv4, isIPv6, isPresent } from '@/utils';
|
||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
// const targetPlatform = 'SurgeMac';
|
const targetPlatform = 'SurgeMac';
|
||||||
|
|
||||||
const surge_Producer = Surge_Producer();
|
const surge_Producer = Surge_Producer();
|
||||||
|
|
||||||
export default function SurgeMac_Producer() {
|
export default function SurgeMac_Producer() {
|
||||||
const produce = (proxy) => {
|
const produce = (proxy) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
|
case 'external':
|
||||||
|
return external(proxy);
|
||||||
case 'ssr':
|
case 'ssr':
|
||||||
return shadowsocksr(proxy);
|
return shadowsocksr(proxy);
|
||||||
default:
|
default:
|
||||||
@@ -16,19 +20,67 @@ export default function SurgeMac_Producer() {
|
|||||||
};
|
};
|
||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
function external(proxy) {
|
||||||
function shadowsocksr(proxy) {
|
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
|
if (!proxy.exec || !proxy['local-port']) {
|
||||||
proxy.local_port = '__SubStoreLocalPort__';
|
throw new Error(`${proxy.type}: exec and local-port are required`);
|
||||||
proxy.local_address = proxy.local_address ?? '127.0.0.1';
|
}
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
`${proxy.name} = external, exec = "${
|
`${proxy.name}=external,exec="${proxy.exec}",local-port=${proxy['local-port']}`,
|
||||||
proxy.exec || '/usr/local/bin/ssr-local'
|
|
||||||
}", address = "${proxy.server}", local-port = ${proxy.local_port}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Array.isArray(proxy.args)) {
|
||||||
|
proxy.args.map((args) => {
|
||||||
|
result.append(`,args="${args}"`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Array.isArray(proxy.addresses)) {
|
||||||
|
proxy.addresses.map((addresses) => {
|
||||||
|
result.append(`,addresses=${addresses}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
|
'no-error-alert',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
function shadowsocksr(proxy) {
|
||||||
|
const external_proxy = {
|
||||||
|
...proxy,
|
||||||
|
type: 'external',
|
||||||
|
exec: proxy.exec || '/usr/local/bin/ssr-local',
|
||||||
|
'local-port': '__SubStoreLocalPort__',
|
||||||
|
args: [],
|
||||||
|
addresses: [],
|
||||||
|
'local-address':
|
||||||
|
proxy.local_address ?? proxy['local-address'] ?? '127.0.0.1',
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://manual.nssurge.com/policy/external-proxy.html
|
||||||
|
if (isIP(proxy.server)) {
|
||||||
|
external_proxy.addresses.push(proxy.server);
|
||||||
|
} else {
|
||||||
|
$.log(
|
||||||
|
`Platform ${targetPlatform}, proxy type ${proxy.type}: addresses should be an IP address, but got ${proxy.server}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries({
|
for (const [key, value] of Object.entries({
|
||||||
cipher: '-m',
|
cipher: '-m',
|
||||||
obfs: '-o',
|
obfs: '-o',
|
||||||
@@ -37,14 +89,16 @@ function shadowsocksr(proxy) {
|
|||||||
protocol: '-O',
|
protocol: '-O',
|
||||||
'protocol-param': '-G',
|
'protocol-param': '-G',
|
||||||
server: '-s',
|
server: '-s',
|
||||||
local_port: '-l',
|
'local-port': '-l',
|
||||||
local_address: '-b',
|
'local-address': '-b',
|
||||||
})) {
|
})) {
|
||||||
result.appendIfPresent(
|
external_proxy.args.push(value);
|
||||||
`, args = "${value}", args = "${proxy[key]}"`,
|
external_proxy.args.push(external_proxy[key]);
|
||||||
key,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.toString();
|
return external(external_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIP(ip) {
|
||||||
|
return isIPv4(ip) || isIPv6(ip);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ async function doSync() {
|
|||||||
platform: artifact.platform,
|
platform: artifact.platform,
|
||||||
});
|
});
|
||||||
|
|
||||||
files[artifact.name] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -54,10 +54,9 @@ async function doSync() {
|
|||||||
if (artifact.sync) {
|
if (artifact.sync) {
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
artifact.url = body.files[artifact.name].raw_url.replace(
|
artifact.url = body.files[
|
||||||
/\/raw\/[^/]*\/(.*)/,
|
encodeURIComponent(artifact.name)
|
||||||
'/raw/$1',
|
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export default function register($app) {
|
|||||||
if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY);
|
if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY);
|
||||||
|
|
||||||
// RESTful APIs
|
// RESTful APIs
|
||||||
|
$app.get('/api/artifacts/restore', restoreArtifacts);
|
||||||
|
|
||||||
$app.route('/api/artifacts')
|
$app.route('/api/artifacts')
|
||||||
.get(getAllArtifacts)
|
.get(getAllArtifacts)
|
||||||
.post(createArtifact)
|
.post(createArtifact)
|
||||||
@@ -30,6 +32,72 @@ export default function register($app) {
|
|||||||
.delete(deleteArtifact);
|
.delete(deleteArtifact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function restoreArtifacts(_, res) {
|
||||||
|
$.info('开始恢复远程配置...');
|
||||||
|
try {
|
||||||
|
const { gistToken } = $.read(SETTINGS_KEY);
|
||||||
|
if (!gistToken) {
|
||||||
|
return Promise.reject('未设置 GitHub Token!');
|
||||||
|
}
|
||||||
|
const manager = new Gist({
|
||||||
|
token: gistToken,
|
||||||
|
key: ARTIFACT_REPOSITORY_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gist = await manager.locate();
|
||||||
|
if (!gist?.files) {
|
||||||
|
throw new Error(`找不到 Sub-Store Gist 文件列表`);
|
||||||
|
}
|
||||||
|
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||||
|
const failed = [];
|
||||||
|
Object.keys(gist.files).map((key) => {
|
||||||
|
const filename = gist.files[key]?.filename;
|
||||||
|
if (filename) {
|
||||||
|
if (encodeURIComponent(filename) !== filename) {
|
||||||
|
$.error(`文件名 ${filename} 未编码 不保存`);
|
||||||
|
failed.push(filename);
|
||||||
|
} else {
|
||||||
|
const artifact = findByName(allArtifacts, filename);
|
||||||
|
if (artifact) {
|
||||||
|
updateByName(allArtifacts, filename, {
|
||||||
|
...artifact,
|
||||||
|
url: gist.files[key]?.raw_url.replace(
|
||||||
|
/\/raw\/[^/]*\/(.*)/,
|
||||||
|
'/raw/$1',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
allArtifacts.push({
|
||||||
|
name: `${filename}`,
|
||||||
|
url: gist.files[key]?.raw_url.replace(
|
||||||
|
/\/raw\/[^/]*\/(.*)/,
|
||||||
|
'/raw/$1',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
|
} catch (err) {
|
||||||
|
$.error(`查找 Sub-Store Gist 时发生错误: ${err.message ?? err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
success(res);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`恢复远程配置失败,原因:${e.message ?? e}`);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new InternalServerError(
|
||||||
|
`FAILED_TO_RESTORE_ARTIFACTS`,
|
||||||
|
`Failed to restore artifacts`,
|
||||||
|
`Reason: ${e.message ?? e}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getAllArtifacts(req, res) {
|
function getAllArtifacts(req, res) {
|
||||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||||
success(res, allArtifacts);
|
success(res, allArtifacts);
|
||||||
@@ -140,6 +208,12 @@ async function deleteArtifact(req, res) {
|
|||||||
files[encodeURIComponent(artifact.name)] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: '',
|
content: '',
|
||||||
};
|
};
|
||||||
|
if (encodeURIComponent(artifact.name) !== artifact.name) {
|
||||||
|
files[artifact.name] = {
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
|
// 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
|
||||||
try {
|
try {
|
||||||
await syncToGist(files);
|
await syncToGist(files);
|
||||||
@@ -171,7 +245,7 @@ function validateArtifactName(name) {
|
|||||||
async function syncToGist(files) {
|
async function syncToGist(files) {
|
||||||
const { gistToken } = $.read(SETTINGS_KEY);
|
const { gistToken } = $.read(SETTINGS_KEY);
|
||||||
if (!gistToken) {
|
if (!gistToken) {
|
||||||
return Promise.reject('未设置Gist Token!');
|
return Promise.reject('未设置 GitHub Token!');
|
||||||
}
|
}
|
||||||
const manager = new Gist({
|
const manager = new Gist({
|
||||||
token: gistToken,
|
token: gistToken,
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ async function downloadSubscription(req, res) {
|
|||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
|
||||||
$.info(`正在下载订阅:${name}`);
|
$.info(`正在下载订阅:${name}`);
|
||||||
let { url, ua, content, mergeSources, ignoreFailedRemoteSub } = req.query;
|
let {
|
||||||
|
url,
|
||||||
|
ua,
|
||||||
|
content,
|
||||||
|
mergeSources,
|
||||||
|
ignoreFailedRemoteSub,
|
||||||
|
produceType,
|
||||||
|
includeUnsupportedProxy,
|
||||||
|
} = req.query;
|
||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程订阅 URL: ${url}`);
|
$.info(`指定远程订阅 URL: ${url}`);
|
||||||
@@ -41,6 +49,14 @@ async function downloadSubscription(req, res) {
|
|||||||
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
||||||
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
||||||
}
|
}
|
||||||
|
if (produceType) {
|
||||||
|
produceType = decodeURIComponent(produceType);
|
||||||
|
$.info(`指定生产类型: ${produceType}`);
|
||||||
|
}
|
||||||
|
if (includeUnsupportedProxy) {
|
||||||
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
|
}
|
||||||
|
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
@@ -55,6 +71,10 @@ async function downloadSubscription(req, res) {
|
|||||||
content,
|
content,
|
||||||
mergeSources,
|
mergeSources,
|
||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
|
produceType,
|
||||||
|
produceOpts: {
|
||||||
|
'include-unsupported-proxy': includeUnsupportedProxy,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sub.source !== 'local' || url) {
|
if (sub.source !== 'local' || url) {
|
||||||
@@ -121,12 +141,22 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
$.info(`正在下载组合订阅:${name}`);
|
$.info(`正在下载组合订阅:${name}`);
|
||||||
|
|
||||||
let { ignoreFailedRemoteSub } = req.query;
|
let { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy } =
|
||||||
|
req.query;
|
||||||
|
|
||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
||||||
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
|
||||||
}
|
}
|
||||||
|
if (produceType) {
|
||||||
|
produceType = decodeURIComponent(produceType);
|
||||||
|
$.info(`指定生产类型: ${produceType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeUnsupportedProxy) {
|
||||||
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
try {
|
try {
|
||||||
@@ -135,6 +165,10 @@ async function downloadCollection(req, res) {
|
|||||||
name,
|
name,
|
||||||
platform,
|
platform,
|
||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
|
produceType,
|
||||||
|
produceOpts: {
|
||||||
|
'include-unsupported-proxy': includeUnsupportedProxy,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// forward flow header from the first subscription in this collection
|
// forward flow header from the first subscription in this collection
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import express from '@/vendor/express';
|
|||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
import download from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
|
import { syncArtifacts } from '@/restful/sync';
|
||||||
|
|
||||||
import registerSubscriptionRoutes from './subscriptions';
|
import registerSubscriptionRoutes from './subscriptions';
|
||||||
import registerCollectionRoutes from './collections';
|
import registerCollectionRoutes from './collections';
|
||||||
@@ -41,6 +42,28 @@ export default function serve() {
|
|||||||
$app.start();
|
$app.start();
|
||||||
|
|
||||||
if ($.env.isNode) {
|
if ($.env.isNode) {
|
||||||
|
const backend_cron = eval('process.env.SUB_STORE_BACKEND_CRON');
|
||||||
|
if (backend_cron) {
|
||||||
|
$.info(`[CRON] ${backend_cron} enabled`);
|
||||||
|
const { CronJob } = eval(`require("cron")`);
|
||||||
|
new CronJob(
|
||||||
|
backend_cron,
|
||||||
|
async function () {
|
||||||
|
try {
|
||||||
|
$.info(`[CRON] ${backend_cron} started`);
|
||||||
|
await syncArtifacts();
|
||||||
|
$.info(`[CRON] ${backend_cron} finished`);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`[CRON] ${backend_cron} error: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, // onTick
|
||||||
|
null, // onComplete
|
||||||
|
true, // start
|
||||||
|
// 'Asia/Shanghai' // timeZone
|
||||||
|
);
|
||||||
|
}
|
||||||
const path = eval(`require("path")`);
|
const path = eval(`require("path")`);
|
||||||
const fs = eval(`require("fs")`);
|
const fs = eval(`require("fs")`);
|
||||||
const data_url = eval('process.env.SUB_STORE_DATA_URL');
|
const data_url = eval('process.env.SUB_STORE_DATA_URL');
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ async function previewFile(req, res) {
|
|||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
const files = (Array.isArray(content) ? content : [content]).flat();
|
const files = (Array.isArray(content) ? content : [content]).flat();
|
||||||
const filesContent = files
|
let filesContent = files
|
||||||
.filter((i) => i != null && i !== '')
|
.filter((i) => i != null && i !== '')
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ async function getSettings(req, res) {
|
|||||||
if (!settings.avatarUrl) await updateGitHubAvatar();
|
if (!settings.avatarUrl) await updateGitHubAvatar();
|
||||||
if (!settings.artifactStore) await updateArtifactStore();
|
if (!settings.artifactStore) await updateArtifactStore();
|
||||||
success(res, settings);
|
success(res, settings);
|
||||||
|
// TODO: 缺错误处理 前端也缺
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateSettings(req, res) {
|
async function updateSettings(req, res) {
|
||||||
@@ -31,6 +32,7 @@ async function updateSettings(req, res) {
|
|||||||
await updateGitHubAvatar();
|
await updateGitHubAvatar();
|
||||||
await updateArtifactStore();
|
await updateArtifactStore();
|
||||||
success(res, newSettings);
|
success(res, newSettings);
|
||||||
|
// TODO: 缺错误处理 前端也缺
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateGitHubAvatar() {
|
export async function updateGitHubAvatar() {
|
||||||
@@ -62,25 +64,28 @@ export async function updateGitHubAvatar() {
|
|||||||
export async function updateArtifactStore() {
|
export async function updateArtifactStore() {
|
||||||
$.log('Updating artifact store');
|
$.log('Updating artifact store');
|
||||||
const settings = $.read(SETTINGS_KEY);
|
const settings = $.read(SETTINGS_KEY);
|
||||||
const { githubUser, gistToken } = settings;
|
const { gistToken } = settings;
|
||||||
if (githubUser && gistToken) {
|
if (gistToken) {
|
||||||
const manager = new Gist({
|
const manager = new Gist({
|
||||||
token: gistToken,
|
token: gistToken,
|
||||||
key: ARTIFACT_REPOSITORY_KEY,
|
key: ARTIFACT_REPOSITORY_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const gistId = await manager.locate();
|
const gist = await manager.locate();
|
||||||
if (gistId !== -1) {
|
if (gist?.html_url) {
|
||||||
settings.artifactStore = `https://gist.github.com/${githubUser}/${gistId}`;
|
$.log(`找到 Sub-Store Gist: ${gist.html_url}`);
|
||||||
$.write(settings, SETTINGS_KEY);
|
// 只需要保证 token 是对的, 现在 username 错误只会导致头像错误
|
||||||
|
settings.artifactStore = gist.html_url;
|
||||||
|
settings.artifactStoreStatus = 'VALID';
|
||||||
|
} else {
|
||||||
|
$.error(`找不到 Sub-Store Gist`);
|
||||||
|
settings.artifactStoreStatus = 'NOT FOUND';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(`查找 Sub-Store Gist 时发生错误: ${err.message ?? err}`);
|
||||||
`Failed to fetch artifact store for User: ${githubUser}. Reason: ${
|
settings.artifactStoreStatus = 'ERROR';
|
||||||
err.message ?? err
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$.write(settings, SETTINGS_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,15 @@ async function produceArtifact({
|
|||||||
mergeSources,
|
mergeSources,
|
||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
|
produceType,
|
||||||
|
produceOpts = {},
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
if (type === 'subscription') {
|
if (type === 'subscription') {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
|
if (!sub) throw new Error(`找不到订阅 ${name}`);
|
||||||
let raw;
|
let raw;
|
||||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
||||||
raw = content;
|
raw = content;
|
||||||
@@ -154,11 +157,12 @@ async function produceArtifact({
|
|||||||
exist[proxy.name] = true;
|
exist[proxy.name] = true;
|
||||||
}
|
}
|
||||||
// produce
|
// produce
|
||||||
return ProxyUtils.produce(proxies, platform);
|
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
||||||
} else if (type === 'collection') {
|
} else if (type === 'collection') {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
|
if (!collection) throw new Error(`找不到组合订阅 ${name}`);
|
||||||
const subnames = collection.subscriptions;
|
const subnames = collection.subscriptions;
|
||||||
const results = {};
|
const results = {};
|
||||||
const errors = {};
|
const errors = {};
|
||||||
@@ -301,10 +305,11 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
exist[proxy.name] = true;
|
exist[proxy.name] = true;
|
||||||
}
|
}
|
||||||
return ProxyUtils.produce(proxies, platform);
|
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
||||||
} else if (type === 'rule') {
|
} else if (type === 'rule') {
|
||||||
const allRules = $.read(RULES_KEY);
|
const allRules = $.read(RULES_KEY);
|
||||||
const rule = findByName(allRules, name);
|
const rule = findByName(allRules, name);
|
||||||
|
if (!rule) throw new Error(`找不到规则 ${name}`);
|
||||||
let rules = [];
|
let rules = [];
|
||||||
for (let i = 0; i < rule.urls.length; i++) {
|
for (let i = 0; i < rule.urls.length; i++) {
|
||||||
const url = rule.urls[i];
|
const url = rule.urls[i];
|
||||||
@@ -419,7 +424,7 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
||||||
const filesContent = files
|
let filesContent = files
|
||||||
.filter((i) => i != null && i !== '')
|
.filter((i) => i != null && i !== '')
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
@@ -436,29 +441,50 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncAllArtifacts(_, res) {
|
async function syncArtifacts() {
|
||||||
$.info('开始同步所有远程配置...');
|
$.info('开始同步所有远程配置...');
|
||||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const invalid = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allArtifacts.map(async (artifact) => {
|
allArtifacts.map(async (artifact) => {
|
||||||
if (artifact.sync) {
|
try {
|
||||||
$.info(`正在同步云配置:${artifact.name}...`);
|
if (artifact.sync && artifact.source) {
|
||||||
const output = await produceArtifact({
|
$.info(`正在同步云配置:${artifact.name}...`);
|
||||||
type: artifact.type,
|
const output = await produceArtifact({
|
||||||
name: artifact.source,
|
type: artifact.type,
|
||||||
platform: artifact.platform,
|
name: artifact.source,
|
||||||
});
|
platform: artifact.platform,
|
||||||
|
produceOpts: {
|
||||||
|
'include-unsupported-proxy':
|
||||||
|
artifact.includeUnsupportedProxy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
files[artifact.name] = {
|
// if (!output || output.length === 0)
|
||||||
content: output,
|
// throw new Error('该配置的结果为空 不进行上传');
|
||||||
};
|
|
||||||
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
|
content: output,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (invalid.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
@@ -466,26 +492,34 @@ async function syncAllArtifacts(_, res) {
|
|||||||
if (artifact.sync) {
|
if (artifact.sync) {
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
artifact.url = body.files[artifact.name].raw_url.replace(
|
artifact.url = body.files[
|
||||||
/\/raw\/[^/]*\/(.*)/,
|
encodeURIComponent(artifact.name)
|
||||||
'/raw/$1',
|
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.info('全部订阅同步成功!');
|
$.info('全部订阅同步成功!');
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function syncAllArtifacts(_, res) {
|
||||||
|
$.info('开始同步所有远程配置...');
|
||||||
|
try {
|
||||||
|
await syncArtifacts();
|
||||||
success(res);
|
success(res);
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
|
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
`FAILED_TO_SYNC_ARTIFACTS`,
|
`FAILED_TO_SYNC_ARTIFACTS`,
|
||||||
`Failed to sync all artifacts`,
|
`Failed to sync all artifacts`,
|
||||||
`Reason: ${err}`,
|
`Reason: ${e.message ?? e}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$.info(`同步订阅失败,原因:${err}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +536,20 @@ async function syncArtifact(req, res) {
|
|||||||
res,
|
res,
|
||||||
new ResourceNotFoundError(
|
new ResourceNotFoundError(
|
||||||
'RESOURCE_NOT_FOUND',
|
'RESOURCE_NOT_FOUND',
|
||||||
`Artifact ${name} does not exist!`,
|
`找不到远程配置 ${name}`,
|
||||||
|
),
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!artifact.source) {
|
||||||
|
$.error(`远程配置 ${name} 未设置来源`);
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new ResourceNotFoundError(
|
||||||
|
'RESOURCE_HAS_NO_SOURCE',
|
||||||
|
`远程配置 ${name} 未设置来源`,
|
||||||
),
|
),
|
||||||
404,
|
404,
|
||||||
);
|
);
|
||||||
@@ -514,6 +561,9 @@ async function syncArtifact(req, res) {
|
|||||||
type: artifact.type,
|
type: artifact.type,
|
||||||
name: artifact.source,
|
name: artifact.source,
|
||||||
platform: artifact.platform,
|
platform: artifact.platform,
|
||||||
|
produceOpts: {
|
||||||
|
'include-unsupported-proxy': artifact.includeUnsupportedProxy,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
$.info(
|
$.info(
|
||||||
@@ -523,6 +573,8 @@ async function syncArtifact(req, res) {
|
|||||||
2,
|
2,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
// if (!output || output.length === 0)
|
||||||
|
// throw new Error('该配置的结果为空 不进行上传');
|
||||||
const resp = await syncToGist({
|
const resp = await syncToGist({
|
||||||
[encodeURIComponent(artifact.name)]: {
|
[encodeURIComponent(artifact.name)]: {
|
||||||
content: output,
|
content: output,
|
||||||
@@ -532,11 +584,11 @@ async function syncArtifact(req, res) {
|
|||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
artifact.url = body.files[
|
artifact.url = body.files[
|
||||||
encodeURIComponent(artifact.name)
|
encodeURIComponent(artifact.name)
|
||||||
].raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, artifact);
|
success(res, artifact);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(`远程配置 ${artifact.name} 发生错误: ${err}`);
|
$.error(`远程配置 ${artifact.name} 发生错误: ${err.message ?? err}`);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
@@ -548,4 +600,4 @@ async function syncArtifact(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { produceArtifact };
|
export { produceArtifact, syncArtifacts };
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ 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 headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
|
import { getFlowField } from '@/utils/flow';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(url, ua, timeout) {
|
export default async function download(rawUrl, ua, timeout) {
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
|
let url = rawUrl.replace(/#noFlow$/, '');
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
if (rawArgs.length > 1) {
|
if (rawArgs.length > 1) {
|
||||||
try {
|
try {
|
||||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
@@ -71,7 +75,13 @@ export default async function download(url, ua, timeout) {
|
|||||||
);
|
);
|
||||||
http.get(url)
|
http.get(url)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
const body = resp.body;
|
const { body, headers } = resp;
|
||||||
|
if (headers) {
|
||||||
|
const flowInfo = getFlowField(headers);
|
||||||
|
if (flowInfo) {
|
||||||
|
headersResourceCache.set(url, flowInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (body.replace(/\s/g, '').length === 0)
|
if (body.replace(/\s/g, '').length === 0)
|
||||||
reject(new Error('远程资源内容为空!'));
|
reject(new Error('远程资源内容为空!'));
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -1,30 +1,90 @@
|
|||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
import { HTTP } from '@/vendor/open-api';
|
import { HTTP } from '@/vendor/open-api';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
|
|
||||||
export async function getFlowHeaders(url, ua, timeout) {
|
export function getFlowField(headers) {
|
||||||
const { defaultFlowUserAgent, defaultTimeout } = $.read(SETTINGS_KEY);
|
|
||||||
const userAgent =
|
|
||||||
ua ||
|
|
||||||
defaultFlowUserAgent ||
|
|
||||||
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
|
||||||
const http = HTTP();
|
|
||||||
const { headers } = await http.get({
|
|
||||||
url: url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)[0],
|
|
||||||
headers: {
|
|
||||||
'User-Agent': userAgent,
|
|
||||||
},
|
|
||||||
timeout: requestTimeout,
|
|
||||||
});
|
|
||||||
const subkey = Object.keys(headers).filter((k) =>
|
const subkey = Object.keys(headers).filter((k) =>
|
||||||
/SUBSCRIPTION-USERINFO/i.test(k),
|
/SUBSCRIPTION-USERINFO/i.test(k),
|
||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
|
export async function getFlowHeaders(rawUrl, ua, timeout) {
|
||||||
|
let url = rawUrl;
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($arguments?.noFlow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cached = headersResourceCache.get(url);
|
||||||
|
let flowInfo;
|
||||||
|
if (!$arguments?.noCache && cached) {
|
||||||
|
// $.info(`使用缓存的流量信息: ${url}`);
|
||||||
|
flowInfo = cached;
|
||||||
|
} else {
|
||||||
|
const { defaultFlowUserAgent, defaultTimeout } = $.read(SETTINGS_KEY);
|
||||||
|
const userAgent =
|
||||||
|
ua ||
|
||||||
|
defaultFlowUserAgent ||
|
||||||
|
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
||||||
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
|
const http = HTTP();
|
||||||
|
try {
|
||||||
|
// $.info(`使用 HEAD 方法获取流量信息: ${url}`);
|
||||||
|
const { headers } = await http.head({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`使用 HEAD 方法获取流量信息失败: ${url}: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!flowInfo) {
|
||||||
|
$.info(`使用 GET 方法获取流量信息: ${url}`);
|
||||||
|
const { headers } = await http.get({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
}
|
||||||
|
if (flowInfo) {
|
||||||
|
headersResourceCache.set(url, flowInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flowInfo;
|
||||||
|
}
|
||||||
export function parseFlowHeaders(flowHeaders) {
|
export function parseFlowHeaders(flowHeaders) {
|
||||||
if (!flowHeaders) return;
|
if (!flowHeaders) return;
|
||||||
// unit is KB
|
// unit is KB
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ export default class Gist {
|
|||||||
const gists = JSON.parse(response.body);
|
const gists = JSON.parse(response.body);
|
||||||
for (let g of gists) {
|
for (let g of gists) {
|
||||||
if (g.description === this.key) {
|
if (g.description === this.key) {
|
||||||
return g.id;
|
return g;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,15 @@ export default class Gist {
|
|||||||
return Promise.reject('未提供需上传的文件');
|
return Promise.reject('未提供需上传的文件');
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = await this.locate();
|
const gist = await this.locate();
|
||||||
|
|
||||||
if (id === -1) {
|
if (gist?.id) {
|
||||||
|
// update an existing gist
|
||||||
|
return this.http.patch({
|
||||||
|
url: `/gists/${gist.id}`,
|
||||||
|
body: JSON.stringify({ files }),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
// create a new gist for backup
|
// create a new gist for backup
|
||||||
return this.http.post({
|
return this.http.post({
|
||||||
url: '/gists',
|
url: '/gists',
|
||||||
@@ -56,29 +62,23 @@ export default class Gist {
|
|||||||
files,
|
files,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// update an existing gist
|
|
||||||
return this.http.patch({
|
|
||||||
url: `/gists/${id}`,
|
|
||||||
body: JSON.stringify({ files }),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async download(filename) {
|
async download(filename) {
|
||||||
const id = await this.locate();
|
const gist = await this.locate();
|
||||||
if (id === -1) {
|
if (gist?.id) {
|
||||||
return Promise.reject('未找到Gist备份!');
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
const { files } = await this.http
|
const { files } = await this.http
|
||||||
.get(`/gists/${id}`)
|
.get(`/gists/${gist.id}`)
|
||||||
.then((resp) => JSON.parse(resp.body));
|
.then((resp) => JSON.parse(resp.body));
|
||||||
const url = files[filename].raw_url;
|
const url = files[filename].raw_url;
|
||||||
return await this.http.get(url).then((resp) => resp.body);
|
return await this.http.get(url).then((resp) => resp.body);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.reject('找不到 Sub-Store Gist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
backend/src/utils/headers-resource-cache.js
Normal file
107
backend/src/utils/headers-resource-cache.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import $ from '@/core/app';
|
||||||
|
import {
|
||||||
|
HEADERS_RESOURCE_CACHE_KEY,
|
||||||
|
CHR_EXPIRATION_TIME_KEY,
|
||||||
|
} from '@/constants';
|
||||||
|
|
||||||
|
class ResourceCache {
|
||||||
|
constructor() {
|
||||||
|
this.expires = getExpiredTime();
|
||||||
|
if (!$.read(HEADERS_RESOURCE_CACHE_KEY)) {
|
||||||
|
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
|
this.resourceCache = JSON.parse($.read(HEADERS_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), HEADERS_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(CHR_EXPIRATION_TIME_KEY));
|
||||||
|
if (!$.read(CHR_EXPIRATION_TIME_KEY)) {
|
||||||
|
$.write('6e4', CHR_EXPIRATION_TIME_KEY); // 1分钟
|
||||||
|
}
|
||||||
|
let expiration = 6e4;
|
||||||
|
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(
|
||||||
|
'#\u54cd\u5e94\u5934\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(CHR_EXPIRATION_TIME_KEY);
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ResourceCache();
|
||||||
@@ -20,7 +20,7 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
} else if (UA.indexOf('Decar') !== -1 || UA.indexOf('Loon') !== -1) {
|
} else if (UA.indexOf('Decar') !== -1 || UA.indexOf('Loon') !== -1) {
|
||||||
return 'Loon';
|
return 'Loon';
|
||||||
} else if (UA.indexOf('Shadowrocket') !== -1) {
|
} else if (UA.indexOf('Shadowrocket') !== -1) {
|
||||||
return 'ShadowRocket';
|
return 'Shadowrocket';
|
||||||
} else if (UA.indexOf('Stash') !== -1) {
|
} else if (UA.indexOf('Stash') !== -1) {
|
||||||
return 'Stash';
|
return 'Stash';
|
||||||
} else if (
|
} else if (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#!homepage=https://github.com/sub-store-org/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小时,参数传入
|
||||||
|
#!select = 响应头缓存有效期,1分钟,5分钟,10分钟,30分钟,1小时,2小时,3小时,6小时,12小时,24小时,48小时,72小时,参数传入
|
||||||
|
|
||||||
[Rule]
|
[Rule]
|
||||||
DOMAIN,sub-store.vercel.app,PROXY
|
DOMAIN,sub-store.vercel.app,PROXY
|
||||||
|
|||||||
Reference in New Issue
Block a user