mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-23 05:19:07 +08:00
Compare commits
No commits in common. "master" and "2.16.7" have entirely different histories.
18
.github/workflows/main.yml
vendored
18
.github/workflows/main.yml
vendored
@ -27,18 +27,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
cd backend && pnpm i --no-frozen-lockfile
|
cd backend && pnpm i --no-frozen-lockfile
|
||||||
# - name: Test
|
- name: Test
|
||||||
# run: |
|
run: |
|
||||||
# cd backend
|
cd backend
|
||||||
# pnpm test
|
pnpm test
|
||||||
# - name: Build
|
- name: Build
|
||||||
# run: |
|
run: |
|
||||||
# cd backend
|
cd backend
|
||||||
# pnpm run build
|
pnpm run build
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
pnpm bundle:esbuild
|
pnpm run bundle
|
||||||
- id: tag
|
- id: tag
|
||||||
name: Generate release tag
|
name: Generate release tag
|
||||||
run: |
|
run: |
|
||||||
|
22
README.md
22
README.md
@ -26,24 +26,21 @@ Core functionalities:
|
|||||||
|
|
||||||
### Supported Input Formats
|
### Supported Input Formats
|
||||||
|
|
||||||
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs.
|
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
||||||
|
|
||||||
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
- [x] Normal Proxy(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||||
|
|
||||||
example: `socks5+tls://user:pass@ip:port#name`
|
example: `socks5+tls://user:pass@ip:port#name`
|
||||||
|
|
||||||
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||||
- [x] Clash Proxies YAML
|
- [x] Clash Proxies YAML
|
||||||
- [x] Clash Proxy JSON(single line)
|
- [x] Clash Proxy JSON(single line)
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||||
- [x] Surge (Direct, SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
- [x] Surge (Direct, SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||||
|
|
||||||
Deprecated(The frontend doesn't show it, but the backend still supports it, with the query parameter `target=Clash`):
|
|
||||||
|
|
||||||
- [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
|
||||||
@ -51,6 +48,7 @@ Deprecated(The frontend doesn't show it, but the backend still supports it, with
|
|||||||
- [x] Plain JSON
|
- [x] Plain JSON
|
||||||
- [x] Stash
|
- [x] Stash
|
||||||
- [x] Clash.Meta(mihomo)
|
- [x] Clash.Meta(mihomo)
|
||||||
|
- [x] Clash
|
||||||
- [x] Surfboard
|
- [x] Surfboard
|
||||||
- [x] Surge
|
- [x] Surge
|
||||||
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
||||||
@ -62,10 +60,6 @@ Deprecated(The frontend doesn't show it, but the backend still supports it, with
|
|||||||
- [x] V2Ray
|
- [x] V2Ray
|
||||||
- [x] V2Ray URI
|
- [x] V2Ray URI
|
||||||
|
|
||||||
Deprecated:
|
|
||||||
|
|
||||||
- [x] Clash
|
|
||||||
|
|
||||||
## 2. Subscription Formatting
|
## 2. Subscription Formatting
|
||||||
|
|
||||||
### Filtering
|
### Filtering
|
||||||
@ -128,9 +122,3 @@ This project is under the GPL V3 LICENSE.
|
|||||||
|
|
||||||
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
|
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
|
||||||
- Special thanks to @Orz-3 and @58xinian for their awesome icons.
|
- Special thanks to @Orz-3 and @58xinian for their awesome icons.
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
|
|
||||||
[](https://yxvm.com)
|
|
||||||
|
|
||||||
[NodeSupport](https://github.com/NodeSeekDev/NodeSupport) sponsored this project.
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝
|
* ╚════██║██║ ██║██╔══██╗╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝
|
||||||
* ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗
|
* ███████║╚██████╔╝██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗
|
||||||
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
* ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||||
* Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket!
|
* Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket!
|
||||||
* @updated: <%= updated %>
|
* @updated: <%= updated %>
|
||||||
* @version: <%= pkg.version %>
|
* @version: <%= pkg.version %>
|
||||||
* @author: Peng-YM
|
* @author: Peng-YM
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.19.60",
|
"version": "2.16.7",
|
||||||
"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": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@ -12,37 +12,28 @@
|
|||||||
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"bundle": "node bundle.js",
|
"bundle": "node bundle.js",
|
||||||
"bundle:esbuild": "node bundle-esbuild.js",
|
|
||||||
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
||||||
},
|
},
|
||||||
"author": "Peng-YM",
|
"author": "Peng-YM",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"pnpm": {
|
|
||||||
"patchedDependencies": {
|
|
||||||
"http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maxmind/geoip2-node": "^5.0.0",
|
"@maxmind/geoip2-node": "^5.0.0",
|
||||||
"automerge": "1.0.1-preview.7",
|
"automerge": "1.0.1-preview.7",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"connect-history-api-fallback": "^2.0.0",
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
"cron": "^3.1.6",
|
"cron": "^3.1.6",
|
||||||
"dns-packet": "^5.6.1",
|
"dns-packet": "^5.6.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"mime-types": "^2.1.35",
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"http-proxy-middleware": "^3.0.3",
|
|
||||||
"ip-address": "^9.0.5",
|
"ip-address": "^9.0.5",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
"jsrsasign": "^11.1.0",
|
"jsrsasign": "^11.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"nanoid": "^3.3.3",
|
"nanoid": "^3.3.3",
|
||||||
"semver": "^7.6.3",
|
"request": "^2.88.2",
|
||||||
"static-js-yaml": "^1.0.0",
|
"static-js-yaml": "^1.0.0"
|
||||||
"undici": "^7.4.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.0",
|
"@babel/core": "^7.18.0",
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
diff --git a/lib/http-proxy/common.js b/lib/http-proxy/common.js
|
|
||||||
index 6513e81d80d5250ea249ea833f819ece67897c7e..486d4c896d65a3bb7cf63307af68facb3ddb886b 100644
|
|
||||||
--- a/lib/http-proxy/common.js
|
|
||||||
+++ b/lib/http-proxy/common.js
|
|
||||||
@@ -1,6 +1,5 @@
|
|
||||||
var common = exports,
|
|
||||||
url = require('url'),
|
|
||||||
- extend = require('util')._extend,
|
|
||||||
required = require('requires-port');
|
|
||||||
|
|
||||||
var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i,
|
|
||||||
@@ -40,10 +39,10 @@ common.setupOutgoing = function(outgoing, options, req, forward) {
|
|
||||||
);
|
|
||||||
|
|
||||||
outgoing.method = options.method || req.method;
|
|
||||||
- outgoing.headers = extend({}, req.headers);
|
|
||||||
+ outgoing.headers = Object.assign({}, req.headers);
|
|
||||||
|
|
||||||
if (options.headers){
|
|
||||||
- extend(outgoing.headers, options.headers);
|
|
||||||
+ Object.assign(outgoing.headers, options.headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.auth) {
|
|
||||||
diff --git a/lib/http-proxy/index.js b/lib/http-proxy/index.js
|
|
||||||
index 977a4b3622b9eaac27689f06347ea4c5173a96cd..88b2d0fcfa03c3aafa47c7e6d38e64412c45a7cc 100644
|
|
||||||
--- a/lib/http-proxy/index.js
|
|
||||||
+++ b/lib/http-proxy/index.js
|
|
||||||
@@ -1,5 +1,4 @@
|
|
||||||
var httpProxy = module.exports,
|
|
||||||
- extend = require('util')._extend,
|
|
||||||
parse_url = require('url').parse,
|
|
||||||
EE3 = require('eventemitter3'),
|
|
||||||
http = require('http'),
|
|
||||||
@@ -47,9 +46,9 @@ function createRightProxy(type) {
|
|
||||||
args[cntr] !== res
|
|
||||||
) {
|
|
||||||
//Copy global options
|
|
||||||
- requestOptions = extend({}, options);
|
|
||||||
+ requestOptions = Object.assign({}, options);
|
|
||||||
//Overwrite with request options
|
|
||||||
- extend(requestOptions, args[cntr]);
|
|
||||||
+ Object.assign(requestOptions, args[cntr]);
|
|
||||||
|
|
||||||
cntr--;
|
|
||||||
}
|
|
13645
backend/pnpm-lock.yaml
generated
13645
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,11 @@
|
|||||||
import { Base64 } from 'js-base64';
|
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import rs from '@/utils/rs';
|
import rs from '@/utils/rs';
|
||||||
import YAML from '@/utils/yaml';
|
import YAML from '@/utils/yaml';
|
||||||
import download, { downloadFile } from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
import {
|
import {
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
isValidPortNumber,
|
isValidPortNumber,
|
||||||
isValidUUID,
|
|
||||||
isNotBlank,
|
isNotBlank,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
getRandomPort,
|
getRandomPort,
|
||||||
@ -23,8 +21,6 @@ import { findByName } from '@/utils/database';
|
|||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
import { isPresent } from './producers/utils';
|
|
||||||
import { doh } from '@/utils/dns';
|
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
@ -79,16 +75,7 @@ function parse(raw) {
|
|||||||
$.error(`Failed to parse line: ${line}`);
|
$.error(`Failed to parse line: ${line}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return proxies.filter((proxy) => {
|
return proxies;
|
||||||
if (['vless', 'vmess'].includes(proxy.type)) {
|
|
||||||
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
|
||||||
if (!isProxyUUIDValid) {
|
|
||||||
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
|
||||||
}
|
|
||||||
// return isProxyUUIDValid;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFn(
|
async function processFn(
|
||||||
@ -115,7 +102,12 @@ async function processFn(
|
|||||||
if (item.type.indexOf('Script') !== -1) {
|
if (item.type.indexOf('Script') !== -1) {
|
||||||
const { mode, content } = item.args;
|
const { mode, content } = item.args;
|
||||||
if (mode === 'link') {
|
if (mode === 'link') {
|
||||||
|
let noCache;
|
||||||
let url = content || '';
|
let url = content || '';
|
||||||
|
if (url.endsWith('#noCache')) {
|
||||||
|
url = url.replace(/#noCache$/, '');
|
||||||
|
noCache = true;
|
||||||
|
}
|
||||||
// extract link arguments
|
// extract link arguments
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
if (rawArgs.length > 1) {
|
if (rawArgs.length > 1) {
|
||||||
@ -134,17 +126,10 @@ async function processFn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url = `${url.split('#')[0]}${
|
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
|
||||||
rawArgs[2]
|
const downloadUrlMatch = url.match(
|
||||||
? `#${rawArgs[2]}`
|
/^\/api\/(file|module)\/(.+)/,
|
||||||
: $arguments?.noCache != null ||
|
);
|
||||||
$arguments?.insecure != null
|
|
||||||
? `#${rawArgs[1]}`
|
|
||||||
: ''
|
|
||||||
}`;
|
|
||||||
const downloadUrlMatch = url
|
|
||||||
.split('#')[0]
|
|
||||||
.match(/^\/api\/(file|module)\/(.+)/);
|
|
||||||
if (downloadUrlMatch) {
|
if (downloadUrlMatch) {
|
||||||
let type = '';
|
let type = '';
|
||||||
try {
|
try {
|
||||||
@ -174,17 +159,6 @@ async function processFn(
|
|||||||
);
|
);
|
||||||
throw new Error(`无法加载 ${type}: ${url}`);
|
throw new Error(`无法加载 ${type}: ${url}`);
|
||||||
}
|
}
|
||||||
} else if (url?.startsWith('/')) {
|
|
||||||
try {
|
|
||||||
const fs = eval(`require("fs")`);
|
|
||||||
script = fs.readFileSync(url.split('#')[0], 'utf8');
|
|
||||||
// $.info(`Script loaded: >>>\n ${script}`);
|
|
||||||
} catch (err) {
|
|
||||||
$.error(
|
|
||||||
`Error when reading local script: ${item.args.content}.\n Reason: ${err}`,
|
|
||||||
);
|
|
||||||
throw new Error(`无法从该路径读取脚本文件: ${url}`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// if this is a remote script, download it
|
// if this is a remote script, download it
|
||||||
try {
|
try {
|
||||||
@ -199,7 +173,6 @@ async function processFn(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
script = content;
|
script = content;
|
||||||
$arguments = item.args.arguments || {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,22 +214,10 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter((proxy) => {
|
proxies = proxies.filter(
|
||||||
// 检查代理是否支持目标平台
|
(proxy) =>
|
||||||
if (proxy.supported && proxy.supported[targetPlatform] === false) {
|
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// 对于 vless 和 vmess 代理,需要额外验证 UUID
|
|
||||||
if (['vless', 'vmess'].includes(proxy.type)) {
|
|
||||||
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
|
||||||
if (!isProxyUUIDValid)
|
|
||||||
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
|
||||||
// return isProxyUUIDValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
proxies = proxies.map((proxy) => {
|
proxies = proxies.map((proxy) => {
|
||||||
proxy._resolved = proxy.resolved;
|
proxy._resolved = proxy.resolved;
|
||||||
@ -342,11 +303,6 @@ export const ProxyUtils = {
|
|||||||
MMDB,
|
MMDB,
|
||||||
Gist,
|
Gist,
|
||||||
download,
|
download,
|
||||||
downloadFile,
|
|
||||||
isValidUUID,
|
|
||||||
doh,
|
|
||||||
Buffer,
|
|
||||||
Base64,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@ -387,14 +343,6 @@ function lastParse(proxy) {
|
|||||||
if (typeof proxy.password === 'number') {
|
if (typeof proxy.password === 'number') {
|
||||||
proxy.password = numberToString(proxy.password);
|
proxy.password = numberToString(proxy.password);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
['ss'].includes(proxy.type) &&
|
|
||||||
proxy.cipher === 'none' &&
|
|
||||||
!proxy.password
|
|
||||||
) {
|
|
||||||
// https://github.com/MetaCubeX/mihomo/issues/1677
|
|
||||||
proxy.password = '';
|
|
||||||
}
|
|
||||||
if (proxy.interface) {
|
if (proxy.interface) {
|
||||||
proxy['interface-name'] = proxy.interface;
|
proxy['interface-name'] = proxy.interface;
|
||||||
delete proxy.interface;
|
delete proxy.interface;
|
||||||
@ -444,14 +392,9 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
[
|
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
||||||
'trojan',
|
proxy.type,
|
||||||
'tuic',
|
)
|
||||||
'hysteria',
|
|
||||||
'hysteria2',
|
|
||||||
'juicity',
|
|
||||||
'anytls',
|
|
||||||
].includes(proxy.type)
|
|
||||||
) {
|
) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
@ -535,14 +478,6 @@ function lastParse(proxy) {
|
|||||||
proxy['obfs-password'] = proxy.obfs;
|
proxy['obfs-password'] = proxy.obfs;
|
||||||
proxy.obfs = 'salamander';
|
proxy.obfs = 'salamander';
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
['hysteria2'].includes(proxy.type) &&
|
|
||||||
!proxy['obfs-password'] &&
|
|
||||||
proxy['obfs_password']
|
|
||||||
) {
|
|
||||||
proxy['obfs-password'] = proxy['obfs_password'];
|
|
||||||
delete proxy['obfs_password'];
|
|
||||||
}
|
|
||||||
if (['vless'].includes(proxy.type)) {
|
if (['vless'].includes(proxy.type)) {
|
||||||
// 删除 reality-opts: {}
|
// 删除 reality-opts: {}
|
||||||
if (
|
if (
|
||||||
@ -629,20 +564,6 @@ function lastParse(proxy) {
|
|||||||
if (!proxy['tls-fingerprint'] && caStr) {
|
if (!proxy['tls-fingerprint'] && caStr) {
|
||||||
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
['ss'].includes(proxy.type) &&
|
|
||||||
isPresent(proxy, 'shadow-tls-password')
|
|
||||||
) {
|
|
||||||
proxy.plugin = 'shadow-tls';
|
|
||||||
proxy['plugin-opts'] = {
|
|
||||||
host: proxy['shadow-tls-sni'],
|
|
||||||
password: proxy['shadow-tls-password'],
|
|
||||||
version: proxy['shadow-tls-version'],
|
|
||||||
};
|
|
||||||
delete proxy['shadow-tls-sni'];
|
|
||||||
delete proxy['shadow-tls-password'];
|
|
||||||
delete proxy['shadow-tls-version'];
|
|
||||||
}
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ 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';
|
||||||
import getTrojanURIParser from './peggy/trojan-uri';
|
import getTrojanURIParser from './peggy/trojan-uri';
|
||||||
import $ from '@/core/app';
|
|
||||||
|
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
@ -41,21 +40,8 @@ function URI_PROXY() {
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, type, tls, username, password, server, port, query, name] =
|
let [__, type, tls, username, password, server, port, query, name] =
|
||||||
line.match(
|
line.match(
|
||||||
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?):(\d+?)(\?.*?)?(?:#(.*?))?$/,
|
||||||
);
|
);
|
||||||
if (port) {
|
|
||||||
port = parseInt(port, 10);
|
|
||||||
} else {
|
|
||||||
if (tls) {
|
|
||||||
port = 443;
|
|
||||||
} else if (type === 'http') {
|
|
||||||
port = 80;
|
|
||||||
} else {
|
|
||||||
$.error(`port is not present in line: ${line}`);
|
|
||||||
throw new Error(`port is not present in line: ${line}`);
|
|
||||||
}
|
|
||||||
$.info(`port is not present in line: ${line}, set to ${port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy = {
|
const proxy = {
|
||||||
name:
|
name:
|
||||||
@ -76,46 +62,7 @@ function URI_PROXY() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
function URI_SOCKS() {
|
|
||||||
const name = 'URI SOCKS Parser';
|
|
||||||
const test = (line) => {
|
|
||||||
return /^socks:\/\//.test(line);
|
|
||||||
};
|
|
||||||
const parse = (line) => {
|
|
||||||
// parse url
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
let [__, type, auth, server, port, query, name] = line.match(
|
|
||||||
/^(socks)?:\/\/(?:(.*)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
|
||||||
);
|
|
||||||
if (port) {
|
|
||||||
port = parseInt(port, 10);
|
|
||||||
} else {
|
|
||||||
$.error(`port is not present in line: ${line}`);
|
|
||||||
throw new Error(`port is not present in line: ${line}`);
|
|
||||||
}
|
|
||||||
let username, password;
|
|
||||||
if (auth) {
|
|
||||||
const parsed = Base64.decode(decodeURIComponent(auth)).split(':');
|
|
||||||
username = parsed[0];
|
|
||||||
password = parsed[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy = {
|
|
||||||
name:
|
|
||||||
name != null
|
|
||||||
? decodeURIComponent(name)
|
|
||||||
: `${type} ${server}:${port}`,
|
|
||||||
type: 'socks5',
|
|
||||||
server,
|
|
||||||
port,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
};
|
|
||||||
|
|
||||||
return proxy;
|
|
||||||
};
|
|
||||||
return { name, test, parse };
|
|
||||||
}
|
|
||||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
function URI_SS() {
|
function URI_SS() {
|
||||||
@ -128,13 +75,13 @@ function URI_SS() {
|
|||||||
// parse url
|
// parse url
|
||||||
let content = line.split('ss://')[1];
|
let content = line.split('ss://')[1];
|
||||||
|
|
||||||
let name = line.split('#')[1];
|
|
||||||
const proxy = {
|
const proxy = {
|
||||||
|
name: decodeURIComponent(line.split('#')[1]),
|
||||||
type: 'ss',
|
type: 'ss',
|
||||||
};
|
};
|
||||||
content = content.split('#')[0]; // strip proxy name
|
content = content.split('#')[0]; // strip proxy name
|
||||||
// handle IPV4 and IPV6
|
// handle IPV4 and IPV6
|
||||||
let serverAndPortArray = content.match(/@([^/?]*)(\/|\?|$)/);
|
let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||||
|
|
||||||
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
|
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
|
||||||
let userInfoStr;
|
let userInfoStr;
|
||||||
@ -152,7 +99,6 @@ function URI_SS() {
|
|||||||
query = parsed[2];
|
query = parsed[2];
|
||||||
}
|
}
|
||||||
content = Base64.decode(content);
|
content = Base64.decode(content);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
||||||
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
||||||
@ -166,11 +112,8 @@ function URI_SS() {
|
|||||||
}
|
}
|
||||||
content = `${content}${query}`;
|
content = `${content}${query}`;
|
||||||
}
|
}
|
||||||
userInfoStr = content.match(/(^.*)@/)?.[1];
|
userInfoStr = content.split('@')[0];
|
||||||
serverAndPortArray = content.match(/@([^/@]*)(\/|$)/);
|
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||||
} else if (content.includes('?')) {
|
|
||||||
const parsed = content.match(/(\?.*)$/);
|
|
||||||
query = parsed[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverAndPort = serverAndPortArray[1];
|
const serverAndPort = serverAndPortArray[1];
|
||||||
@ -189,12 +132,11 @@ function URI_SS() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// handle obfs
|
// handle obfs
|
||||||
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
|
const idx = content.indexOf('?plugin=');
|
||||||
const shadowTlsMatch = content.match(/[?&]shadow-tls=([^&]+)/);
|
if (idx !== -1) {
|
||||||
|
|
||||||
if (pluginMatch) {
|
|
||||||
const pluginInfo = (
|
const pluginInfo = (
|
||||||
'plugin=' + decodeURIComponent(pluginMatch[1])
|
'plugin=' +
|
||||||
|
decodeURIComponent(content.split('?plugin=')[1].split('&')[0])
|
||||||
).split(';');
|
).split(';');
|
||||||
const params = {};
|
const params = {};
|
||||||
for (const item of pluginInfo) {
|
for (const item of pluginInfo) {
|
||||||
@ -219,51 +161,18 @@ function URI_SS() {
|
|||||||
tls: getIfPresent(params.tls),
|
tls: getIfPresent(params.tls),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'shadow-tls': {
|
|
||||||
proxy.plugin = 'shadow-tls';
|
|
||||||
const version = getIfNotBlank(params['version']);
|
|
||||||
proxy['plugin-opts'] = {
|
|
||||||
host: getIfNotBlank(params['host']),
|
|
||||||
password: getIfNotBlank(params['password']),
|
|
||||||
version: version ? parseInt(version, 10) : undefined,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported plugin option: ${params.plugin}`,
|
`Unsupported plugin option: ${params.plugin}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Shadowrocket
|
|
||||||
if (shadowTlsMatch) {
|
|
||||||
const params = JSON.parse(Base64.decode(shadowTlsMatch[1]));
|
|
||||||
const version = getIfNotBlank(params['version']);
|
|
||||||
const address = getIfNotBlank(params['address']);
|
|
||||||
const port = getIfNotBlank(params['port']);
|
|
||||||
proxy.plugin = 'shadow-tls';
|
|
||||||
proxy['plugin-opts'] = {
|
|
||||||
host: getIfNotBlank(params['host']),
|
|
||||||
password: getIfNotBlank(params['password']),
|
|
||||||
version: version ? parseInt(version, 10) : undefined,
|
|
||||||
};
|
|
||||||
if (address) {
|
|
||||||
proxy.server = address;
|
|
||||||
}
|
|
||||||
if (port) {
|
|
||||||
proxy.port = parseInt(port, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
||||||
proxy['udp-over-tcp'] = true;
|
proxy['udp-over-tcp'] = true;
|
||||||
}
|
}
|
||||||
if (/(&|\?)tfo=(1|true)/i.test(query)) {
|
if (/(&|\?)tfo=(1|true)/i.test(query)) {
|
||||||
proxy.tfo = true;
|
proxy.tfo = true;
|
||||||
}
|
}
|
||||||
if (name != null) {
|
|
||||||
name = decodeURIComponent(name);
|
|
||||||
}
|
|
||||||
proxy.name = name ?? `SS ${proxy.server}:${proxy.port}`;
|
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
@ -346,7 +255,7 @@ function URI_VMess() {
|
|||||||
};
|
};
|
||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split('vmess://')[1];
|
line = line.split('vmess://')[1];
|
||||||
let content = Base64.decode(line.replace(/\?.*?$/, ''));
|
let content = Base64.decode(line);
|
||||||
if (/=\s*vmess/.test(content)) {
|
if (/=\s*vmess/.test(content)) {
|
||||||
// Quantumult VMess URI format
|
// Quantumult VMess URI format
|
||||||
const partitions = content.split(',').map((p) => p.trim());
|
const partitions = content.split(',').map((p) => p.trim());
|
||||||
@ -439,16 +348,7 @@ function URI_VMess() {
|
|||||||
type: 'vmess',
|
type: 'vmess',
|
||||||
server,
|
server,
|
||||||
port,
|
port,
|
||||||
// https://github.com/2dust/v2rayN/wiki/Description-of-VMess-share-link
|
cipher: getIfPresent(params.scy, 'auto'),
|
||||||
// https://github.com/XTLS/Xray-core/issues/91
|
|
||||||
cipher: [
|
|
||||||
'auto',
|
|
||||||
'aes-128-gcm',
|
|
||||||
'chacha20-poly1305',
|
|
||||||
'none',
|
|
||||||
].includes(params.scy)
|
|
||||||
? params.scy
|
|
||||||
: 'auto',
|
|
||||||
uuid: params.id,
|
uuid: params.id,
|
||||||
alterId: parseInt(
|
alterId: parseInt(
|
||||||
getIfPresent(params.aid ?? params.alterId, 0),
|
getIfPresent(params.aid ?? params.alterId, 0),
|
||||||
@ -465,12 +365,8 @@ function URI_VMess() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
||||||
if (proxy.tls) {
|
if (proxy.tls && params.sni && params.sni !== '') {
|
||||||
if (params.sni && params.sni !== '') {
|
proxy.sni = params.sni;
|
||||||
proxy.sni = params.sni;
|
|
||||||
} else if (params.peer && params.peer !== '') {
|
|
||||||
proxy.sni = params.peer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let httpupgrade = false;
|
let httpupgrade = false;
|
||||||
// handle obfs
|
// handle obfs
|
||||||
@ -482,8 +378,8 @@ function URI_VMess() {
|
|||||||
['http'].includes(params.type)
|
['http'].includes(params.type)
|
||||||
) {
|
) {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
} else if (['grpc', 'kcp', 'quic'].includes(params.net)) {
|
} else if (['grpc'].includes(params.net)) {
|
||||||
proxy.network = params.net;
|
proxy.network = 'grpc';
|
||||||
} else if (
|
} else if (
|
||||||
params.net === 'httpupgrade' ||
|
params.net === 'httpupgrade' ||
|
||||||
proxy.network === 'httpupgrade'
|
proxy.network === 'httpupgrade'
|
||||||
@ -509,11 +405,6 @@ function URI_VMess() {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
let transportPath = params.path;
|
let transportPath = params.path;
|
||||||
|
|
||||||
// 补上默认 path
|
|
||||||
if (['ws'].includes(proxy.network)) {
|
|
||||||
transportPath = transportPath || '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
if (transportHost) {
|
if (transportHost) {
|
||||||
// 1)http(tcp)->host中间逗号(,)隔开
|
// 1)http(tcp)->host中间逗号(,)隔开
|
||||||
@ -533,28 +424,13 @@ function URI_VMess() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 传输层应该有配置, 暂时不考虑兼容不给配置的节点
|
// 传输层应该有配置, 暂时不考虑兼容不给配置的节点
|
||||||
if (
|
if (transportPath || transportHost) {
|
||||||
transportPath ||
|
|
||||||
transportHost ||
|
|
||||||
['kcp', 'quic'].includes(proxy.network)
|
|
||||||
) {
|
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
proxy[`${proxy.network}-opts`] = {
|
proxy[`${proxy.network}-opts`] = {
|
||||||
'grpc-service-name': getIfNotBlank(transportPath),
|
'grpc-service-name': getIfNotBlank(transportPath),
|
||||||
'_grpc-type': getIfNotBlank(params.type),
|
'_grpc-type': getIfNotBlank(params.type),
|
||||||
'_grpc-authority': getIfNotBlank(params.authority),
|
'_grpc-authority': getIfNotBlank(params.authority),
|
||||||
};
|
};
|
||||||
} else if (['kcp', 'quic'].includes(proxy.network)) {
|
|
||||||
proxy[`${proxy.network}-opts`] = {
|
|
||||||
[`_${proxy.network}-type`]: getIfNotBlank(
|
|
||||||
params.type,
|
|
||||||
),
|
|
||||||
[`_${proxy.network}-host`]: getIfNotBlank(
|
|
||||||
getIfNotBlank(transportHost),
|
|
||||||
),
|
|
||||||
[`_${proxy.network}-path`]:
|
|
||||||
getIfNotBlank(transportPath),
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
const opts = {
|
const opts = {
|
||||||
path: getIfNotBlank(transportPath),
|
path: getIfNotBlank(transportPath),
|
||||||
@ -570,12 +446,6 @@ function URI_VMess() {
|
|||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy['client-fingerprint'] = params.fp;
|
|
||||||
proxy.alpn = params.alpn ? params.alpn.split(',') : undefined;
|
|
||||||
// 然而 wiki 和 app 实测中都没有字段表示这个
|
|
||||||
// proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.allowInsecure);
|
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -659,9 +529,6 @@ function URI_VLESS() {
|
|||||||
if (params.sid) {
|
if (params.sid) {
|
||||||
opts['short-id'] = params.sid;
|
opts['short-id'] = params.sid;
|
||||||
}
|
}
|
||||||
if (params.spx) {
|
|
||||||
opts['_spider-x'] = params.spx;
|
|
||||||
}
|
|
||||||
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[`${params.security}-opts`] = opts;
|
||||||
@ -677,9 +544,6 @@ function URI_VLESS() {
|
|||||||
}
|
}
|
||||||
if (!proxy.network && isShadowrocket && params.obfs) {
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
proxy.network = params.obfs;
|
proxy.network = params.obfs;
|
||||||
if (['none'].includes(proxy.network)) {
|
|
||||||
proxy.network = 'tcp';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (['websocket'].includes(proxy.network)) {
|
if (['websocket'].includes(proxy.network)) {
|
||||||
proxy.network = 'ws';
|
proxy.network = 'ws';
|
||||||
@ -732,61 +596,6 @@ function URI_VLESS() {
|
|||||||
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||||
proxy.headerType = params.headerType || 'none';
|
proxy.headerType = params.headerType || 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.mode) {
|
|
||||||
proxy._mode = params.mode;
|
|
||||||
}
|
|
||||||
if (params.extra) {
|
|
||||||
proxy._extra = params.extra;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxy;
|
|
||||||
};
|
|
||||||
return { name, test, parse };
|
|
||||||
}
|
|
||||||
function URI_AnyTLS() {
|
|
||||||
const name = 'URI AnyTLS Parser';
|
|
||||||
const test = (line) => {
|
|
||||||
return /^anytls:\/\//.test(line);
|
|
||||||
};
|
|
||||||
const parse = (line) => {
|
|
||||||
line = line.split(/anytls:\/\//)[1];
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
let [__, password, server, port, addons = '', name] =
|
|
||||||
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
|
||||||
password = decodeURIComponent(password);
|
|
||||||
port = parseInt(`${port}`, 10);
|
|
||||||
if (isNaN(port)) {
|
|
||||||
port = 443;
|
|
||||||
}
|
|
||||||
password = decodeURIComponent(password);
|
|
||||||
if (name != null) {
|
|
||||||
name = decodeURIComponent(name);
|
|
||||||
}
|
|
||||||
name = name ?? `AnyTLS ${server}:${port}`;
|
|
||||||
|
|
||||||
const proxy = {
|
|
||||||
type: 'anytls',
|
|
||||||
name,
|
|
||||||
server,
|
|
||||||
port,
|
|
||||||
password,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const addon of addons.split('&')) {
|
|
||||||
let [key, value] = addon.split('=');
|
|
||||||
key = key.replace(/_/g, '-');
|
|
||||||
value = decodeURIComponent(value);
|
|
||||||
if (['alpn'].includes(key)) {
|
|
||||||
proxy[key] = value ? value.split(',') : undefined;
|
|
||||||
} else if (['insecure'].includes(key)) {
|
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
|
||||||
} else if (['udp'].includes(key)) {
|
|
||||||
proxy[key] = /(TRUE)|1/i.test(value);
|
|
||||||
} else {
|
|
||||||
proxy[key] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
@ -823,7 +632,6 @@ function URI_Hysteria2() {
|
|||||||
] = /^(.*?)@(.*?)(:((\d+(-\d+)?)([,;]\d+(-\d+)?)*))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(
|
] = /^(.*?)@(.*?)(:((\d+(-\d+)?)([,;]\d+(-\d+)?)*))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(
|
||||||
line,
|
line,
|
||||||
);
|
);
|
||||||
|
|
||||||
/* eslint-enable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
if (/^\d+$/.test(port)) {
|
if (/^\d+$/.test(port)) {
|
||||||
port = parseInt(`${port}`, 10);
|
port = parseInt(`${port}`, 10);
|
||||||
@ -867,23 +675,12 @@ function URI_Hysteria2() {
|
|||||||
if (params.obfs && params.obfs !== 'none') {
|
if (params.obfs && params.obfs !== 'none') {
|
||||||
proxy.obfs = params.obfs;
|
proxy.obfs = params.obfs;
|
||||||
}
|
}
|
||||||
if (params.mport) {
|
|
||||||
proxy.ports = params.mport;
|
proxy.ports = params.mport;
|
||||||
}
|
|
||||||
proxy['obfs-password'] = params['obfs-password'];
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
proxy['tls-fingerprint'] = params.pinSHA256;
|
proxy['tls-fingerprint'] = params.pinSHA256;
|
||||||
let hop_interval = params['hop-interval'] || params['hop_interval'];
|
|
||||||
|
|
||||||
if (/^\d+$/.test(hop_interval)) {
|
|
||||||
proxy['hop-interval'] = parseInt(`${hop_interval}`, 10);
|
|
||||||
}
|
|
||||||
let keepalive = params['keepalive'];
|
|
||||||
|
|
||||||
if (/^\d+$/.test(keepalive)) {
|
|
||||||
proxy['keepalive'] = parseInt(`${keepalive}`, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
@ -966,11 +763,8 @@ function URI_TUIC() {
|
|||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split(/tuic:\/\//)[1];
|
line = line.split(/tuic:\/\//)[1];
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, auth, server, port, addons = '', name] =
|
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
|
||||||
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
auth = decodeURIComponent(auth);
|
|
||||||
let [uuid, ...passwordParts] = auth.split(':');
|
|
||||||
let password = passwordParts.join(':');
|
|
||||||
port = parseInt(`${port}`, 10);
|
port = parseInt(`${port}`, 10);
|
||||||
if (isNaN(port)) {
|
if (isNaN(port)) {
|
||||||
port = 443;
|
port = 443;
|
||||||
@ -992,19 +786,14 @@ function URI_TUIC() {
|
|||||||
|
|
||||||
for (const addon of addons.split('&')) {
|
for (const addon of addons.split('&')) {
|
||||||
let [key, value] = addon.split('=');
|
let [key, value] = addon.split('=');
|
||||||
key = key.replace(/_/g, '-');
|
key = key.replace(/_/, '-');
|
||||||
value = decodeURIComponent(value);
|
value = decodeURIComponent(value);
|
||||||
if (['alpn'].includes(key)) {
|
if (['alpn'].includes(key)) {
|
||||||
proxy[key] = value ? value.split(',') : undefined;
|
proxy[key] = value ? value.split(',') : undefined;
|
||||||
} else if (['allow-insecure'].includes(key)) {
|
} else if (['allow-insecure'].includes(key)) {
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||||
} else if (['fast-open'].includes(key)) {
|
|
||||||
proxy.tfo = true;
|
|
||||||
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
||||||
proxy[key] = /(TRUE)|1/i.test(value);
|
proxy[key] = /(TRUE)|1/i.test(value);
|
||||||
} else if (key === 'congestion-control') {
|
|
||||||
proxy['congestion-controller'] = value;
|
|
||||||
delete proxy[key];
|
|
||||||
} else {
|
} else {
|
||||||
proxy[key] = value;
|
proxy[key] = value;
|
||||||
}
|
}
|
||||||
@ -1140,7 +929,6 @@ function Clash_All() {
|
|||||||
const proxy = JSON.parse(line);
|
const proxy = JSON.parse(line);
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
'anytls',
|
|
||||||
'mieru',
|
'mieru',
|
||||||
'juicity',
|
'juicity',
|
||||||
'ss',
|
'ss',
|
||||||
@ -1643,7 +1431,6 @@ function isIP(ip) {
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
URI_PROXY(),
|
URI_PROXY(),
|
||||||
URI_SOCKS(),
|
|
||||||
URI_SS(),
|
URI_SS(),
|
||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
@ -1653,7 +1440,6 @@ export default [
|
|||||||
URI_Hysteria(),
|
URI_Hysteria(),
|
||||||
URI_Hysteria2(),
|
URI_Hysteria2(),
|
||||||
URI_Trojan(),
|
URI_Trojan(),
|
||||||
URI_AnyTLS(),
|
|
||||||
Clash_All(),
|
Clash_All(),
|
||||||
Surge_Direct(),
|
Surge_Direct(),
|
||||||
Surge_SSH(),
|
Surge_SSH(),
|
||||||
|
@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,20 +180,13 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
|
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
|
||||||
|
|
||||||
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
|
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
equals = _ "=" _
|
equals = _ "=" _
|
||||||
|
@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
|
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
proxy.obfs = obfs.type;
|
proxy.obfs = obfs.type;
|
||||||
}
|
}
|
||||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)* {
|
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle ss obfs
|
// handle ss obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
|
|||||||
$set(proxy, "plugin-opts.path", obfs.path);
|
$set(proxy, "plugin-opts.path", obfs.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
|
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
proxy.alterId = proxy.alterId || 0;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
|
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "vless";
|
proxy.type = "vless";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,20 +178,13 @@ tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-
|
|||||||
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
|
||||||
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||||
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
|
|
||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
|
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
|
||||||
|
|
||||||
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
|
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
equals = _ "=" _
|
equals = _ "=" _
|
||||||
|
@ -49,7 +49,7 @@ trojan = "trojan" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol || proxy.type === "ssr") {
|
if (proxy.protocol || proxy.type === "ssr") {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
if (!proxy.protocol) {
|
if (!proxy.protocol) {
|
||||||
@ -86,10 +86,10 @@ vmess = "vmess" 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)* {
|
(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 = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead === false) {
|
if (proxy.aead) {
|
||||||
proxy.alterId = 1;
|
|
||||||
} else {
|
|
||||||
proxy.alterId = 0;
|
proxy.alterId = 0;
|
||||||
|
} else {
|
||||||
|
proxy.alterId = proxy.alterId || 0;
|
||||||
}
|
}
|
||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
@ -145,20 +145,18 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
|
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||||
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
|
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||||
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
|
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||||
|
|
||||||
method = comma "method" equals cipher:cipher {
|
method = comma "method" equals cipher:cipher {
|
||||||
proxy.cipher = cipher;
|
proxy.cipher = cipher;
|
||||||
};
|
};
|
||||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
|
||||||
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
||||||
|
|
||||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||||
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
||||||
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
|
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
|
@ -47,7 +47,7 @@ trojan = "trojan" equals address
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol || proxy.type === "ssr") {
|
if (proxy.protocol || proxy.type === "ssr") {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
if (!proxy.protocol) {
|
if (!proxy.protocol) {
|
||||||
@ -84,10 +84,10 @@ vmess = "vmess" 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)* {
|
(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 = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead === false) {
|
if (proxy.aead) {
|
||||||
proxy.alterId = 1;
|
|
||||||
} else {
|
|
||||||
proxy.alterId = 0;
|
proxy.alterId = 0;
|
||||||
|
} else {
|
||||||
|
proxy.alterId = proxy.alterId || 0;
|
||||||
}
|
}
|
||||||
handleObfs();
|
handleObfs();
|
||||||
}
|
}
|
||||||
@ -143,20 +143,18 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
|
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||||
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
|
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||||
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
|
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||||
|
|
||||||
method = comma "method" equals cipher:cipher {
|
method = comma "method" equals cipher:cipher {
|
||||||
proxy.cipher = cipher;
|
proxy.cipher = cipher;
|
||||||
};
|
};
|
||||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
|
||||||
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
||||||
|
|
||||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||||
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
||||||
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
|
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
|
@ -41,7 +41,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/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/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") {
|
||||||
@ -52,37 +52,36 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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";
|
||||||
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
|
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
proxy.alterId = 0;
|
proxy.alterId = 0;
|
||||||
} else {
|
} else {
|
||||||
proxy.alterId = 1;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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") {
|
||||||
@ -105,20 +104,20 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
|
|||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/fast_open/tfo/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
|
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
||||||
proxy.type = "direct";
|
proxy.type = "direct";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +246,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
|||||||
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
@ -39,7 +39,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/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/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") {
|
||||||
@ -50,37 +50,36 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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";
|
||||||
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
|
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
proxy.alterId = 0;
|
proxy.alterId = 0;
|
||||||
} else {
|
} else {
|
||||||
proxy.alterId = 1;
|
proxy.alterId = proxy.alterId || 0;
|
||||||
}
|
}
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ssh";
|
proxy.type = "ssh";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/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/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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") {
|
||||||
@ -107,16 +106,16 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
|
|||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/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;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
|
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
||||||
proxy.type = "direct";
|
proxy.type = "direct";
|
||||||
}
|
}
|
||||||
address = comma server:server comma port:port {
|
address = comma server:server comma port:port {
|
||||||
@ -244,7 +243,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
|||||||
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
|
||||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||||
|
@ -80,9 +80,6 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params = "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
params[key] = decodeURIComponent(value);
|
|
||||||
}
|
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
proxy['client-fingerprint'] = params.fp;
|
proxy['client-fingerprint'] = params.fp;
|
||||||
@ -118,27 +115,6 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['reality'].includes(params.security)) {
|
|
||||||
const opts = {};
|
|
||||||
if (params.pbk) {
|
|
||||||
opts['public-key'] = params.pbk;
|
|
||||||
}
|
|
||||||
if (params.sid) {
|
|
||||||
opts['short-id'] = params.sid;
|
|
||||||
}
|
|
||||||
if (params.spx) {
|
|
||||||
opts['_spider-x'] = params.spx;
|
|
||||||
}
|
|
||||||
if (params.mode) {
|
|
||||||
proxy._mode = params.mode;
|
|
||||||
}
|
|
||||||
if (params.extra) {
|
|
||||||
proxy._extra = params.extra;
|
|
||||||
}
|
|
||||||
if (Object.keys(opts).length > 0) {
|
|
||||||
$set(proxy, params.security+"-opts", opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.udp = toBool(params["udp"]);
|
proxy.udp = toBool(params["udp"]);
|
||||||
|
@ -78,9 +78,6 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params = "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
params[key] = decodeURIComponent(value);
|
|
||||||
}
|
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
proxy['client-fingerprint'] = params.fp;
|
proxy['client-fingerprint'] = params.fp;
|
||||||
@ -116,27 +113,6 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['reality'].includes(params.security)) {
|
|
||||||
const opts = {};
|
|
||||||
if (params.pbk) {
|
|
||||||
opts['public-key'] = params.pbk;
|
|
||||||
}
|
|
||||||
if (params.sid) {
|
|
||||||
opts['short-id'] = params.sid;
|
|
||||||
}
|
|
||||||
if (params.spx) {
|
|
||||||
opts['_spider-x'] = params.spx;
|
|
||||||
}
|
|
||||||
if (params.mode) {
|
|
||||||
proxy._mode = params.mode;
|
|
||||||
}
|
|
||||||
if (params.extra) {
|
|
||||||
proxy._extra = params.extra;
|
|
||||||
}
|
|
||||||
if (Object.keys(opts).length > 0) {
|
|
||||||
$set(proxy, params.security+"-opts", opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.udp = toBool(params["udp"]);
|
proxy.udp = toBool(params["udp"]);
|
||||||
|
@ -16,7 +16,6 @@ function Base64Encoded() {
|
|||||||
const keys = [
|
const keys = [
|
||||||
'dm1lc3M', // vmess
|
'dm1lc3M', // vmess
|
||||||
'c3NyOi8v', // ssr://
|
'c3NyOi8v', // ssr://
|
||||||
'c29ja3M6Ly', // socks://
|
|
||||||
'dHJvamFu', // trojan
|
'dHJvamFu', // trojan
|
||||||
'c3M6Ly', // ss:/
|
'c3M6Ly', // ss:/
|
||||||
'c3NkOi8v', // ssd://
|
'c3NkOi8v', // ssd://
|
||||||
@ -38,7 +37,7 @@ function Base64Encoded() {
|
|||||||
};
|
};
|
||||||
const parse = function (raw) {
|
const parse = function (raw) {
|
||||||
const decoded = Base64.decode(raw);
|
const decoded = Base64.decode(raw);
|
||||||
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
|
if (!/^\w+:\/\/\w+/m.test(decoded)) {
|
||||||
$.error(
|
$.error(
|
||||||
`Base64 Pre-processor error: decoded line does not start with protocol`,
|
`Base64 Pre-processor error: decoded line does not start with protocol`,
|
||||||
);
|
);
|
||||||
@ -50,26 +49,6 @@ function Base64Encoded() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
function fallbackBase64Encoded() {
|
|
||||||
const name = 'Fallback Base64 Pre-processor';
|
|
||||||
|
|
||||||
const test = function (raw) {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
const parse = function (raw) {
|
|
||||||
const decoded = Base64.decode(raw);
|
|
||||||
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
|
|
||||||
$.error(
|
|
||||||
`Fallback Base64 Pre-processor error: decoded line does not start with protocol`,
|
|
||||||
);
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return decoded;
|
|
||||||
};
|
|
||||||
return { name, test, parse };
|
|
||||||
}
|
|
||||||
|
|
||||||
function Clash() {
|
function Clash() {
|
||||||
const name = 'Clash Pre-processor';
|
const name = 'Clash Pre-processor';
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
@ -83,7 +62,7 @@ function Clash() {
|
|||||||
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||||
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
||||||
const afterReplace = raw.replace(
|
const afterReplace = raw.replace(
|
||||||
/short-id:([ \t]*[^#\n,}]*)/g,
|
/short-id:([ ]*[^,\n}]*)/g,
|
||||||
(matched, value) => {
|
(matched, value) => {
|
||||||
const afterTrim = value.trim();
|
const afterTrim = value.trim();
|
||||||
|
|
||||||
@ -95,8 +74,6 @@ function Clash() {
|
|||||||
// 是否被引号包裹
|
// 是否被引号包裹
|
||||||
if (/^(['"]).*\1$/.test(afterTrim)) {
|
if (/^(['"]).*\1$/.test(afterTrim)) {
|
||||||
return `short-id: ${afterTrim}`;
|
return `short-id: ${afterTrim}`;
|
||||||
} else if (['null'].includes(afterTrim)) {
|
|
||||||
return `short-id: ${afterTrim}`;
|
|
||||||
} else {
|
} else {
|
||||||
return `short-id: "${afterTrim}"`;
|
return `short-id: "${afterTrim}"`;
|
||||||
}
|
}
|
||||||
@ -183,11 +160,4 @@ function FullConfig() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default [
|
export default [HTML(), Clash(), Base64Encoded(), SSD(), FullConfig()];
|
||||||
HTML(),
|
|
||||||
Clash(),
|
|
||||||
Base64Encoded(),
|
|
||||||
SSD(),
|
|
||||||
FullConfig(),
|
|
||||||
fallbackBase64Encoded(),
|
|
||||||
];
|
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
validCheck,
|
validCheck,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
getRmainingDays,
|
getRmainingDays,
|
||||||
normalizeFlowHeader,
|
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
|
|
||||||
function isObject(item) {
|
function isObject(item) {
|
||||||
@ -284,15 +283,7 @@ function SortOperator(order = 'asc') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort by regex
|
// sort by regex
|
||||||
function RegexSortOperator(input) {
|
function RegexSortOperator(expressions) {
|
||||||
const order = input.order || 'asc';
|
|
||||||
let expressions = input.expressions;
|
|
||||||
if (Array.isArray(input)) {
|
|
||||||
expressions = input;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(expressions)) {
|
|
||||||
expressions = [];
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
name: 'Regex Sort Operator',
|
name: 'Regex Sort Operator',
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
@ -303,13 +294,8 @@ function RegexSortOperator(input) {
|
|||||||
if (oA && !oB) return -1;
|
if (oA && !oB) return -1;
|
||||||
if (oB && !oA) return 1;
|
if (oB && !oA) return 1;
|
||||||
if (oA && oB) return oA < oB ? -1 : 1;
|
if (oA && oB) return oA < oB ? -1 : 1;
|
||||||
if (order === 'original') {
|
if ((!oA && !oB) || (oA && oB && oA === oB))
|
||||||
return 0;
|
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
||||||
} else if (order === 'desc') {
|
|
||||||
return a.name < b.name ? 1 : -1;
|
|
||||||
} else {
|
|
||||||
return a.name < b.name ? -1 : 1;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -379,35 +365,22 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
if (output?.$file?.type === 'mihomoProfile') {
|
if (output?.$file?.type === 'mihomoProfile') {
|
||||||
try {
|
try {
|
||||||
let patch = YAML.safeLoad(script);
|
let patch = YAML.safeLoad(script);
|
||||||
let config;
|
if (typeof patch !== 'object') patch = {};
|
||||||
if (output?.$content) {
|
|
||||||
try {
|
|
||||||
config = YAML.safeLoad(output?.$content);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(e.message ?? e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if (typeof patch !== 'object') patch = {};
|
|
||||||
if (typeof patch !== 'object')
|
|
||||||
throw new Error('patch is not an object');
|
|
||||||
output.$content = ProxyUtils.yaml.safeDump(
|
output.$content = ProxyUtils.yaml.safeDump(
|
||||||
deepMerge(
|
deepMerge(
|
||||||
config ||
|
{
|
||||||
(output?.$file?.sourceType === 'none'
|
proxies: await produceArtifact({
|
||||||
? {}
|
type:
|
||||||
: {
|
output?.$file?.sourceType ||
|
||||||
proxies: await produceArtifact({
|
'collection',
|
||||||
type:
|
name: output?.$file?.sourceName,
|
||||||
output?.$file?.sourceType ||
|
platform: 'mihomo',
|
||||||
'collection',
|
produceType: 'internal',
|
||||||
name: output?.$file?.sourceName,
|
produceOpts: {
|
||||||
platform: 'mihomo',
|
'delete-underscore-fields': true,
|
||||||
produceType: 'internal',
|
},
|
||||||
produceOpts: {
|
}),
|
||||||
'delete-underscore-fields': true,
|
},
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
patch,
|
patch,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -438,15 +411,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
if($file.type === 'mihomoProfile') {
|
if($file.type === 'mihomoProfile') {
|
||||||
${script}
|
${script}
|
||||||
if(typeof main === 'function') {
|
if(typeof main === 'function') {
|
||||||
let config;
|
const config = {
|
||||||
if ($content) {
|
|
||||||
try {
|
|
||||||
config = ProxyUtils.yaml.safeLoad($content);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.message ?? e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$content = ProxyUtils.yaml.safeDump(await main(config || ($file.sourceType === 'none' ? {} : {
|
|
||||||
proxies: await produceArtifact({
|
proxies: await produceArtifact({
|
||||||
type: $file.sourceType || 'collection',
|
type: $file.sourceType || 'collection',
|
||||||
name: $file.sourceName,
|
name: $file.sourceName,
|
||||||
@ -456,7 +421,8 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
'delete-underscore-fields': true
|
'delete-underscore-fields': true
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})))
|
}
|
||||||
|
$content = ProxyUtils.yaml.safeDump(await main(config))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
${script}
|
${script}
|
||||||
@ -862,12 +828,7 @@ function UselessFilter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter by regions
|
// filter by regions
|
||||||
function RegionFilter(input) {
|
function RegionFilter(regions) {
|
||||||
let regions = input?.value || input;
|
|
||||||
if (!Array.isArray(regions)) {
|
|
||||||
regions = [];
|
|
||||||
}
|
|
||||||
const keep = input?.keep ?? true;
|
|
||||||
const REGION_MAP = {
|
const REGION_MAP = {
|
||||||
HK: '🇭🇰',
|
HK: '🇭🇰',
|
||||||
TW: '🇹🇼',
|
TW: '🇹🇼',
|
||||||
@ -884,8 +845,7 @@ function RegionFilter(input) {
|
|||||||
// this would be high memory usage
|
// this would be high memory usage
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
const flag = getFlag(proxy.name);
|
const flag = getFlag(proxy.name);
|
||||||
const selected = regions.some((r) => REGION_MAP[r] === flag);
|
return regions.some((r) => REGION_MAP[r] === flag);
|
||||||
return keep ? selected : !selected;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -917,19 +877,11 @@ function buildRegex(str, ...options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter by proxy types
|
// filter by proxy types
|
||||||
function TypeFilter(input) {
|
function TypeFilter(types) {
|
||||||
let types = input?.value || input;
|
|
||||||
if (!Array.isArray(types)) {
|
|
||||||
types = [];
|
|
||||||
}
|
|
||||||
const keep = input?.keep ?? true;
|
|
||||||
return {
|
return {
|
||||||
name: 'Type Filter',
|
name: 'Type Filter',
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => types.some((t) => proxy.type === t));
|
||||||
const selected = types.some((t) => proxy.type === t);
|
|
||||||
return keep ? selected : !selected;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1129,7 +1081,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
flowTransfer,
|
flowTransfer,
|
||||||
validCheck,
|
validCheck,
|
||||||
getRmainingDays,
|
getRmainingDays,
|
||||||
normalizeFlowHeader,
|
|
||||||
};
|
};
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
@ -1141,10 +1092,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
'$httpClient',
|
'$httpClient',
|
||||||
'$notification',
|
'$notification',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
'yaml',
|
|
||||||
'Buffer',
|
|
||||||
'b64d',
|
|
||||||
'b64e',
|
|
||||||
'scriptResourceCache',
|
'scriptResourceCache',
|
||||||
'flowUtils',
|
'flowUtils',
|
||||||
'produceArtifact',
|
'produceArtifact',
|
||||||
@ -1162,10 +1109,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
$notification,
|
$notification,
|
||||||
ProxyUtils,
|
ProxyUtils,
|
||||||
ProxyUtils.yaml,
|
|
||||||
ProxyUtils.Buffer,
|
|
||||||
ProxyUtils.Base64.decode,
|
|
||||||
ProxyUtils.Base64.encode,
|
|
||||||
scriptResourceCache,
|
scriptResourceCache,
|
||||||
flowUtils,
|
flowUtils,
|
||||||
produceArtifact,
|
produceArtifact,
|
||||||
@ -1178,10 +1121,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
'$substore',
|
'$substore',
|
||||||
'lodash',
|
'lodash',
|
||||||
'ProxyUtils',
|
'ProxyUtils',
|
||||||
'yaml',
|
|
||||||
'Buffer',
|
|
||||||
'b64d',
|
|
||||||
'b64e',
|
|
||||||
'scriptResourceCache',
|
'scriptResourceCache',
|
||||||
'flowUtils',
|
'flowUtils',
|
||||||
'produceArtifact',
|
'produceArtifact',
|
||||||
@ -1193,10 +1132,6 @@ function createDynamicFunction(name, script, $arguments, $options) {
|
|||||||
$,
|
$,
|
||||||
lodash,
|
lodash,
|
||||||
ProxyUtils,
|
ProxyUtils,
|
||||||
ProxyUtils.yaml,
|
|
||||||
ProxyUtils.Buffer,
|
|
||||||
ProxyUtils.Base64.decode,
|
|
||||||
ProxyUtils.Base64.encode,
|
|
||||||
scriptResourceCache,
|
scriptResourceCache,
|
||||||
flowUtils,
|
flowUtils,
|
||||||
produceArtifact,
|
produceArtifact,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||||
import $ from '@/core/app';
|
|
||||||
|
|
||||||
export default function Clash_Producer() {
|
export default function Clash_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
@ -47,11 +46,6 @@ export default function Clash_Producer() {
|
|||||||
proxy['reality-opts']))
|
proxy['reality-opts']))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
|
|
||||||
$.error(
|
|
||||||
`Clash 不支持前置代理字段. 已过滤节点 ${proxy.name}`,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@ -87,8 +81,6 @@ export default function Clash_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
|
||||||
delete proxy.udp;
|
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@ -147,7 +139,6 @@ export default function Clash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
'anytls',
|
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
@ -158,6 +149,11 @@ export default function Clash_Producer() {
|
|||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
@ -10,47 +10,6 @@ export default function ClashMeta_Producer() {
|
|||||||
return false;
|
return false;
|
||||||
} else if (['juicity'].includes(proxy.type)) {
|
} else if (['juicity'].includes(proxy.type)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (
|
|
||||||
['ss'].includes(proxy.type) &&
|
|
||||||
![
|
|
||||||
'aes-128-ctr',
|
|
||||||
'aes-192-ctr',
|
|
||||||
'aes-256-ctr',
|
|
||||||
'aes-128-cfb',
|
|
||||||
'aes-192-cfb',
|
|
||||||
'aes-256-cfb',
|
|
||||||
'aes-128-gcm',
|
|
||||||
'aes-192-gcm',
|
|
||||||
'aes-256-gcm',
|
|
||||||
'aes-128-ccm',
|
|
||||||
'aes-192-ccm',
|
|
||||||
'aes-256-ccm',
|
|
||||||
'aes-128-gcm-siv',
|
|
||||||
'aes-256-gcm-siv',
|
|
||||||
'chacha20-ietf',
|
|
||||||
'chacha20',
|
|
||||||
'xchacha20',
|
|
||||||
'chacha20-ietf-poly1305',
|
|
||||||
'xchacha20-ietf-poly1305',
|
|
||||||
'chacha8-ietf-poly1305',
|
|
||||||
'xchacha8-ietf-poly1305',
|
|
||||||
'2022-blake3-aes-128-gcm',
|
|
||||||
'2022-blake3-aes-256-gcm',
|
|
||||||
'2022-blake3-chacha20-poly1305',
|
|
||||||
'lea-128-gcm',
|
|
||||||
'lea-192-gcm',
|
|
||||||
'lea-256-gcm',
|
|
||||||
'rabbit128-poly1305',
|
|
||||||
'aegis-128l',
|
|
||||||
'aegis-256',
|
|
||||||
'aez-384',
|
|
||||||
'deoxys-ii-256-128',
|
|
||||||
'rc4-md5',
|
|
||||||
'none',
|
|
||||||
].includes(proxy.cipher)
|
|
||||||
) {
|
|
||||||
// https://wiki.metacubex.one/config/proxies/ss/#cipher
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@ -73,10 +32,9 @@ export default function ClashMeta_Producer() {
|
|||||||
isPresent(proxy, 'cipher') &&
|
isPresent(proxy, 'cipher') &&
|
||||||
![
|
![
|
||||||
'auto',
|
'auto',
|
||||||
'none',
|
|
||||||
'zero',
|
|
||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'chacha20-poly1305',
|
'chacha20-poly1305',
|
||||||
|
'none',
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
proxy.cipher = 'auto';
|
proxy.cipher = 'auto';
|
||||||
@ -128,8 +86,6 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
|
||||||
delete proxy.udp;
|
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@ -146,9 +102,6 @@ export default function ClashMeta_Producer() {
|
|||||||
password: proxy['shadow-tls-password'],
|
password: proxy['shadow-tls-password'],
|
||||||
version: proxy['shadow-tls-version'],
|
version: proxy['shadow-tls-version'],
|
||||||
};
|
};
|
||||||
delete proxy['shadow-tls-password'];
|
|
||||||
delete proxy['shadow-tls-sni'];
|
|
||||||
delete proxy['shadow-tls-version'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +157,6 @@ export default function ClashMeta_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
'anytls',
|
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { isPresent } from './utils';
|
|
||||||
|
|
||||||
export default function Egern_Producer() {
|
export default function Egern_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||||
const list = proxies
|
const list = proxies
|
||||||
.filter((proxy) => {
|
.filter((proxy) => {
|
||||||
// if (opts['include-unsupported-proxy']) return true;
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
'http',
|
'http',
|
||||||
@ -16,7 +14,6 @@ export default function Egern_Producer() {
|
|||||||
'hysteria2',
|
'hysteria2',
|
||||||
'vless',
|
'vless',
|
||||||
'vmess',
|
'vmess',
|
||||||
'tuic',
|
|
||||||
].includes(proxy.type) ||
|
].includes(proxy.type) ||
|
||||||
(proxy.type === 'ss' &&
|
(proxy.type === 'ss' &&
|
||||||
((proxy.plugin === 'obfs' &&
|
((proxy.plugin === 'obfs' &&
|
||||||
@ -50,12 +47,17 @@ export default function Egern_Producer() {
|
|||||||
'salsa20',
|
'salsa20',
|
||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
'2022-blake3-aes-128-gcm',
|
|
||||||
'2022-blake3-aes-256-gcm',
|
|
||||||
].includes(proxy.cipher))) ||
|
].includes(proxy.cipher))) ||
|
||||||
(proxy.type === 'vmess' &&
|
(proxy.type === 'vmess' &&
|
||||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
(![
|
||||||
proxy.network) ||
|
'auto',
|
||||||
|
'aes-128-gcm',
|
||||||
|
'chacha20-poly1305',
|
||||||
|
'none',
|
||||||
|
'zero',
|
||||||
|
].includes(proxy.cipher) ||
|
||||||
|
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
|
proxy.network))) ||
|
||||||
(proxy.type === 'trojan' &&
|
(proxy.type === 'trojan' &&
|
||||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
proxy.network) ||
|
proxy.network) ||
|
||||||
@ -63,17 +65,13 @@ export default function Egern_Producer() {
|
|||||||
(typeof proxy.flow !== 'undefined' ||
|
(typeof proxy.flow !== 'undefined' ||
|
||||||
proxy['reality-opts'] ||
|
proxy['reality-opts'] ||
|
||||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||||
proxy.network))) ||
|
proxy.network)))
|
||||||
(proxy.type === 'tuic' &&
|
|
||||||
proxy.token &&
|
|
||||||
proxy.token.length !== 0)
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
const original = { ...proxy };
|
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (proxy.tls && !proxy.sni) {
|
||||||
proxy.sni = proxy.server;
|
proxy.sni = proxy.server;
|
||||||
}
|
}
|
||||||
@ -123,10 +121,10 @@ export default function Egern_Producer() {
|
|||||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||||
next_hop: proxy.next_hop,
|
next_hop: proxy.next_hop,
|
||||||
};
|
};
|
||||||
if (original.plugin === 'obfs') {
|
if (proxy.plugin === 'obfs') {
|
||||||
proxy.obfs = original['plugin-opts'].mode;
|
proxy.obfs = proxy['plugin-opts'].mode;
|
||||||
proxy.obfs_host = original['plugin-opts'].host;
|
proxy.obfs_host = proxy['plugin-opts'].host;
|
||||||
proxy.obfs_uri = original['plugin-opts'].path;
|
proxy.obfs_uri = proxy['plugin-opts'].path;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'hysteria2') {
|
} else if (proxy.type === 'hysteria2') {
|
||||||
proxy = {
|
proxy = {
|
||||||
@ -144,30 +142,10 @@ export default function Egern_Producer() {
|
|||||||
port_hopping: proxy.ports,
|
port_hopping: proxy.ports,
|
||||||
port_hopping_interval: proxy['hop-interval'],
|
port_hopping_interval: proxy['hop-interval'],
|
||||||
};
|
};
|
||||||
if (
|
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||||
original['obfs-password'] &&
|
|
||||||
original.obfs == 'salamander'
|
|
||||||
) {
|
|
||||||
proxy.obfs = 'salamander';
|
proxy.obfs = 'salamander';
|
||||||
proxy.obfs_password = original['obfs-password'];
|
proxy.obfs_password = proxy['obfs-password'];
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'tuic') {
|
|
||||||
proxy = {
|
|
||||||
type: 'tuic',
|
|
||||||
name: proxy.name,
|
|
||||||
server: proxy.server,
|
|
||||||
port: proxy.port,
|
|
||||||
uuid: proxy.uuid,
|
|
||||||
password: proxy.password,
|
|
||||||
next_hop: proxy.next_hop,
|
|
||||||
sni: proxy.sni,
|
|
||||||
alpn: Array.isArray(proxy.alpn)
|
|
||||||
? proxy.alpn
|
|
||||||
: [proxy.alpn || 'h3'],
|
|
||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
|
||||||
port_hopping: proxy.ports,
|
|
||||||
port_hopping_interval: proxy['hop-interval'],
|
|
||||||
};
|
|
||||||
} else if (proxy.type === 'trojan') {
|
} else if (proxy.type === 'trojan') {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
proxy.websocket = {
|
proxy.websocket = {
|
||||||
@ -190,20 +168,6 @@ export default function Egern_Producer() {
|
|||||||
websocket: proxy.websocket,
|
websocket: proxy.websocket,
|
||||||
};
|
};
|
||||||
} else if (proxy.type === 'vmess') {
|
} else if (proxy.type === 'vmess') {
|
||||||
// Egern:传输层,支持 ws/wss/http1/http2/tls,不配置则为 tcp
|
|
||||||
let security = proxy.cipher;
|
|
||||||
if (
|
|
||||||
security &&
|
|
||||||
![
|
|
||||||
'auto',
|
|
||||||
'none',
|
|
||||||
'zero',
|
|
||||||
'aes-128-gcm',
|
|
||||||
'chacha20-poly1305',
|
|
||||||
].includes(security)
|
|
||||||
) {
|
|
||||||
security = 'auto';
|
|
||||||
}
|
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
[proxy.tls ? 'wss' : 'ws']: {
|
[proxy.tls ? 'wss' : 'ws']: {
|
||||||
@ -219,11 +183,9 @@ export default function Egern_Producer() {
|
|||||||
};
|
};
|
||||||
} else if (proxy.network === 'http') {
|
} else if (proxy.network === 'http') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http1: {
|
http: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: Array.isArray(proxy['http-opts']?.path)
|
path: proxy['http-opts']?.path,
|
||||||
? proxy['http-opts']?.path[0]
|
|
||||||
: proxy['http-opts']?.path,
|
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@ -234,29 +196,9 @@ export default function Egern_Producer() {
|
|||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (proxy.network === 'h2') {
|
} else if (proxy.network === 'tcp' || !proxy.network) {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http2: {
|
[proxy.tls ? 'tls' : 'tcp']: {
|
||||||
method: proxy['h2-opts']?.method,
|
|
||||||
path: Array.isArray(proxy['h2-opts']?.path)
|
|
||||||
? proxy['h2-opts']?.path[0]
|
|
||||||
: proxy['h2-opts']?.path,
|
|
||||||
headers: {
|
|
||||||
Host: Array.isArray(
|
|
||||||
proxy['h2-opts']?.headers?.Host,
|
|
||||||
)
|
|
||||||
? proxy['h2-opts']?.headers?.Host[0]
|
|
||||||
: proxy['h2-opts']?.headers?.Host,
|
|
||||||
},
|
|
||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
(proxy.network === 'tcp' || !proxy.network) &&
|
|
||||||
proxy.tls
|
|
||||||
) {
|
|
||||||
proxy.transport = {
|
|
||||||
tls: {
|
|
||||||
sni: proxy.tls ? proxy.sni : undefined,
|
sni: proxy.tls ? proxy.sni : undefined,
|
||||||
skip_tls_verify: proxy.tls
|
skip_tls_verify: proxy.tls
|
||||||
? proxy['skip-cert-verify']
|
? proxy['skip-cert-verify']
|
||||||
@ -270,7 +212,7 @@ export default function Egern_Producer() {
|
|||||||
server: proxy.server,
|
server: proxy.server,
|
||||||
port: proxy.port,
|
port: proxy.port,
|
||||||
user_id: proxy.uuid,
|
user_id: proxy.uuid,
|
||||||
security,
|
security: proxy.cipher,
|
||||||
tfo: proxy.tfo || proxy['fast-open'],
|
tfo: proxy.tfo || proxy['fast-open'],
|
||||||
legacy: proxy.legacy,
|
legacy: proxy.legacy,
|
||||||
udp_relay:
|
udp_relay:
|
||||||
@ -298,9 +240,7 @@ export default function Egern_Producer() {
|
|||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http: {
|
http: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: Array.isArray(proxy['http-opts']?.path)
|
path: proxy['http-opts']?.path,
|
||||||
? proxy['http-opts']?.path[0]
|
|
||||||
: proxy['http-opts']?.path,
|
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@ -338,39 +278,6 @@ export default function Egern_Producer() {
|
|||||||
// skip_tls_verify: proxy['skip-cert-verify'],
|
// skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
[
|
|
||||||
'http',
|
|
||||||
'socks5',
|
|
||||||
'ss',
|
|
||||||
'trojan',
|
|
||||||
'vless',
|
|
||||||
'vmess',
|
|
||||||
].includes(original.type)
|
|
||||||
) {
|
|
||||||
if (isPresent(original, 'shadow-tls-password')) {
|
|
||||||
if (original['shadow-tls-version'] != 3)
|
|
||||||
throw new Error(
|
|
||||||
`shadow-tls version ${original['shadow-tls-version']} is not supported`,
|
|
||||||
);
|
|
||||||
proxy.shadow_tls = {
|
|
||||||
password: original['shadow-tls-password'],
|
|
||||||
sni: original['shadow-tls-sni'],
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
['shadow-tls'].includes(original.plugin) &&
|
|
||||||
original['plugin-opts']
|
|
||||||
) {
|
|
||||||
if (original['plugin-opts'].version != 3)
|
|
||||||
throw new Error(
|
|
||||||
`shadow-tls version ${original['plugin-opts'].version} is not supported`,
|
|
||||||
);
|
|
||||||
proxy.shadow_tls = {
|
|
||||||
password: original['plugin-opts'].password,
|
|
||||||
sni: original['plugin-opts'].host,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
@ -3,27 +3,19 @@ const targetPlatform = 'Loon';
|
|||||||
import { isPresent, Result } from './utils';
|
import { isPresent, Result } from './utils';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
|
||||||
const ipVersions = {
|
|
||||||
dual: 'dual',
|
|
||||||
ipv4: 'v4-only',
|
|
||||||
ipv6: 'v6-only',
|
|
||||||
'ipv4-prefer': 'prefer-v4',
|
|
||||||
'ipv6-prefer': 'prefer-v6',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Loon_Producer() {
|
export default function Loon_Producer() {
|
||||||
const produce = (proxy, type, opts = {}) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'ssr':
|
case 'ssr':
|
||||||
return shadowsocksr(proxy);
|
return shadowsocksr(proxy);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
return trojan(proxy);
|
return trojan(proxy);
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
return vmess(proxy, opts['include-unsupported-proxy']);
|
return vmess(proxy);
|
||||||
case 'vless':
|
case 'vless':
|
||||||
return vless(proxy, opts['include-unsupported-proxy']);
|
return vless(proxy);
|
||||||
case 'http':
|
case 'http':
|
||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'socks5':
|
case 'socks5':
|
||||||
@ -40,7 +32,7 @@ export default function Loon_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy) {
|
function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
@ -74,6 +66,8 @@ function shadowsocks(proxy) {
|
|||||||
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let isShadowTLS;
|
||||||
|
|
||||||
// obfs
|
// obfs
|
||||||
if (isPresent(proxy, 'plugin')) {
|
if (isPresent(proxy, 'plugin')) {
|
||||||
if (proxy.plugin === 'obfs') {
|
if (proxy.plugin === 'obfs') {
|
||||||
@ -105,6 +99,7 @@ function shadowsocks(proxy) {
|
|||||||
);
|
);
|
||||||
// udp-port
|
// udp-port
|
||||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||||
|
isShadowTLS = true;
|
||||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
||||||
const password = proxy['plugin-opts'].password;
|
const password = proxy['plugin-opts'].password;
|
||||||
const host = proxy['plugin-opts'].host;
|
const host = proxy['plugin-opts'].host;
|
||||||
@ -127,26 +122,23 @@ function shadowsocks(proxy) {
|
|||||||
`,udp-port=${proxy['udp-port']}`,
|
`,udp-port=${proxy['udp-port']}`,
|
||||||
'udp-port',
|
'udp-port',
|
||||||
);
|
);
|
||||||
|
isShadowTLS = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
if (!includeUnsupportedProxy && isShadowTLS) {
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
throw new Error(
|
||||||
|
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@ -168,63 +160,14 @@ function shadowsocksr(proxy) {
|
|||||||
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
||||||
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
||||||
|
|
||||||
// shadow-tls
|
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
|
||||||
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
|
||||||
'shadow-tls-version',
|
|
||||||
);
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
|
||||||
'shadow-tls-sni',
|
|
||||||
);
|
|
||||||
// udp-port
|
|
||||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
|
||||||
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
|
|
||||||
const password = proxy['plugin-opts'].password;
|
|
||||||
const host = proxy['plugin-opts'].host;
|
|
||||||
const version = proxy['plugin-opts'].version;
|
|
||||||
if (password) {
|
|
||||||
result.append(`,shadow-tls-password=${password}`);
|
|
||||||
if (host) {
|
|
||||||
result.append(`,shadow-tls-sni=${host}`);
|
|
||||||
}
|
|
||||||
if (version) {
|
|
||||||
if (version < 2) {
|
|
||||||
throw new Error(
|
|
||||||
`shadow-tls version ${version} is not supported`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
result.append(`,shadow-tls-version=${version}`);
|
|
||||||
}
|
|
||||||
// udp-port
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,udp-port=${proxy['udp-port']}`,
|
|
||||||
'udp-port',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,26 +216,15 @@ function trojan(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmess(proxy) {
|
function vmess(proxy) {
|
||||||
const isReality = !!proxy['reality-opts'];
|
|
||||||
|
|
||||||
const result = new Result(proxy);
|
const result = new Result(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}"`,
|
||||||
@ -340,32 +272,20 @@ function vmess(proxy) {
|
|||||||
'skip-cert-verify',
|
'skip-cert-verify',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isReality) {
|
// sni
|
||||||
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,public-key="${proxy['reality-opts']['public-key']}"`,
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
'reality-opts.public-key',
|
'tls-fingerprint',
|
||||||
);
|
);
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,short-id=${proxy['reality-opts']['short-id']}`,
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
'reality-opts.short-id',
|
'tls-pubkey-sha256',
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// sni
|
|
||||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
|
||||||
'tls-fingerprint',
|
|
||||||
);
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
|
||||||
'tls-pubkey-sha256',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// AEAD
|
// AEAD
|
||||||
if (isPresent(proxy, 'aead')) {
|
if (isPresent(proxy, 'aead')) {
|
||||||
result.append(`,alterId=${proxy.aead ? 0 : 1}`);
|
result.append(`,alterId=0`);
|
||||||
} else {
|
} else {
|
||||||
result.append(`,alterId=${proxy.alterId}`);
|
result.append(`,alterId=${proxy.alterId}`);
|
||||||
}
|
}
|
||||||
@ -373,34 +293,17 @@ function vmess(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vless(proxy) {
|
function vless(proxy) {
|
||||||
let isXtls = false;
|
if (proxy['reality-opts']) {
|
||||||
const isReality = !!proxy['reality-opts'];
|
throw new Error(`VLESS REALITY is unsupported`);
|
||||||
|
|
||||||
if (typeof proxy.flow !== 'undefined') {
|
|
||||||
if (['xtls-rprx-vision'].includes(proxy.flow)) {
|
|
||||||
isXtls = true;
|
|
||||||
} else {
|
|
||||||
throw new Error(`VLESS flow(${proxy.flow}) is not supported`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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}"`,
|
||||||
@ -448,48 +351,24 @@ function vless(proxy) {
|
|||||||
'skip-cert-verify',
|
'skip-cert-verify',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isXtls) {
|
// sni
|
||||||
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
|
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
||||||
}
|
result.appendIfPresent(
|
||||||
if (isReality) {
|
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
||||||
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
'tls-fingerprint',
|
||||||
result.appendIfPresent(
|
);
|
||||||
`,public-key="${proxy['reality-opts']['public-key']}"`,
|
result.appendIfPresent(
|
||||||
'reality-opts.public-key',
|
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
||||||
);
|
'tls-pubkey-sha256',
|
||||||
result.appendIfPresent(
|
);
|
||||||
`,short-id=${proxy['reality-opts']['short-id']}`,
|
|
||||||
'reality-opts.short-id',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// sni
|
|
||||||
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
|
|
||||||
'tls-fingerprint',
|
|
||||||
);
|
|
||||||
result.appendIfPresent(
|
|
||||||
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
|
|
||||||
'tls-pubkey-sha256',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,16 +391,6 @@ function http(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
function socks5(proxy) {
|
function socks5(proxy) {
|
||||||
@ -545,19 +414,10 @@ function socks5(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
}
|
}
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@ -623,15 +483,6 @@ function wireguard(proxy) {
|
|||||||
presharedKey ?? ''
|
presharedKey ?? ''
|
||||||
}}]`,
|
}}]`,
|
||||||
);
|
);
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@ -667,13 +518,6 @@ function hysteria2(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// block-quic
|
|
||||||
if (proxy['block-quic'] === 'on') {
|
|
||||||
result.append(',block-quic=true');
|
|
||||||
} else if (proxy['block-quic'] === 'off') {
|
|
||||||
result.append(',block-quic=false');
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
@ -686,8 +530,6 @@ function hysteria2(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
result.appendIfPresent(`,ecn=${proxy.ecn}`, 'ecn');
|
||||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,6 @@ function shadowsocks(proxy) {
|
|||||||
'aes-256-gcm',
|
'aes-256-gcm',
|
||||||
'chacha20-ietf-poly1305',
|
'chacha20-ietf-poly1305',
|
||||||
'xchacha20-ietf-poly1305',
|
'xchacha20-ietf-poly1305',
|
||||||
'2022-blake3-aes-128-gcm',
|
|
||||||
'2022-blake3-aes-256-gcm',
|
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||||
@ -130,20 +128,6 @@ function shadowsocks(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||||
|
|
||||||
// udp over tcp
|
|
||||||
if (proxy['_ssr_python_uot']) {
|
|
||||||
append(`,udp-over-tcp=true`);
|
|
||||||
} else if (proxy['udp-over-tcp']) {
|
|
||||||
if (
|
|
||||||
!proxy['udp-over-tcp-version'] ||
|
|
||||||
proxy['udp-over-tcp-version'] === 1
|
|
||||||
) {
|
|
||||||
append(`,udp-over-tcp=sp.v1`);
|
|
||||||
} else if (proxy['udp-over-tcp-version'] === 2) {
|
|
||||||
append(`,udp-over-tcp=sp.v2`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// server_check_url
|
// server_check_url
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,server_check_url=${proxy['test-url']}`,
|
`,server_check_url=${proxy['test-url']}`,
|
||||||
@ -405,8 +389,6 @@ function vless(proxy) {
|
|||||||
else append(`,obfs=ws`);
|
else append(`,obfs=ws`);
|
||||||
} else if (proxy.network === 'http') {
|
} else if (proxy.network === 'http') {
|
||||||
append(`,obfs=http`);
|
append(`,obfs=http`);
|
||||||
} else if (['tcp'].includes(proxy.network)) {
|
|
||||||
if (proxy.tls) append(`,obfs=over-tls`);
|
|
||||||
} else if (!['tcp'].includes(proxy.network)) {
|
} else if (!['tcp'].includes(proxy.network)) {
|
||||||
throw new Error(`network ${proxy.network} is unsupported`);
|
throw new Error(`network ${proxy.network} is unsupported`);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||||
import $ from '@/core/app';
|
|
||||||
|
|
||||||
export default function Shadowrocket_Producer() {
|
export default function ShadowRocket_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type, opts = {}) => {
|
const produce = (proxies, type, opts = {}) => {
|
||||||
const list = proxies
|
const list = proxies
|
||||||
@ -33,10 +32,9 @@ export default function Shadowrocket_Producer() {
|
|||||||
isPresent(proxy, 'cipher') &&
|
isPresent(proxy, 'cipher') &&
|
||||||
![
|
![
|
||||||
'auto',
|
'auto',
|
||||||
'none',
|
|
||||||
'zero',
|
|
||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'chacha20-poly1305',
|
'chacha20-poly1305',
|
||||||
|
'none',
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
proxy.cipher = 'auto';
|
proxy.cipher = 'auto';
|
||||||
@ -104,28 +102,11 @@ export default function Shadowrocket_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
|
||||||
delete proxy.udp;
|
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
delete proxy.sni;
|
delete proxy.sni;
|
||||||
}
|
}
|
||||||
} else if (proxy.type === 'ss') {
|
|
||||||
if (
|
|
||||||
isPresent(proxy, 'shadow-tls-password') &&
|
|
||||||
!isPresent(proxy, 'plugin')
|
|
||||||
) {
|
|
||||||
proxy.plugin = 'shadow-tls';
|
|
||||||
proxy['plugin-opts'] = {
|
|
||||||
host: proxy['shadow-tls-sni'],
|
|
||||||
password: proxy['shadow-tls-password'],
|
|
||||||
version: proxy['shadow-tls-version'],
|
|
||||||
};
|
|
||||||
delete proxy['shadow-tls-password'];
|
|
||||||
delete proxy['shadow-tls-sni'];
|
|
||||||
delete proxy['shadow-tls-version'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -179,7 +160,6 @@ export default function Shadowrocket_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
'anytls',
|
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
@ -31,21 +31,6 @@ const smuxParser = (smux, proxy) => {
|
|||||||
if (smux['min-streams'])
|
if (smux['min-streams'])
|
||||||
proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10);
|
proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10);
|
||||||
if (smux.padding) proxy.multiplex.padding = true;
|
if (smux.padding) proxy.multiplex.padding = true;
|
||||||
if (smux['brutal-opts']?.up || smux['brutal-opts']?.down) {
|
|
||||||
proxy.multiplex.brutal = {
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
if (smux['brutal-opts']?.up)
|
|
||||||
proxy.multiplex.brutal.up_mbps = parseInt(
|
|
||||||
`${smux['brutal-opts']?.up}`,
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
if (smux['brutal-opts']?.down)
|
|
||||||
proxy.multiplex.brutal.down_mbps = parseInt(
|
|
||||||
`${smux['brutal-opts']?.down}`,
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wsParser = (proxy, parsedProxy) => {
|
const wsParser = (proxy, parsedProxy) => {
|
||||||
@ -374,16 +359,7 @@ const ssParser = (proxy = {}) => {
|
|||||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) {
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
parsedProxy.udp_over_tcp = {
|
|
||||||
enabled: true,
|
|
||||||
version:
|
|
||||||
!proxy['udp-over-tcp-version'] ||
|
|
||||||
proxy['udp-over-tcp-version'] === 1
|
|
||||||
? 1
|
|
||||||
: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
networkParser(proxy, parsedProxy);
|
networkParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
@ -519,7 +495,6 @@ const vlessParser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
|
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
@ -568,13 +543,12 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$');
|
const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$');
|
||||||
// sing-box 跟文档不一致, 但是懒得全转, 只处理最常见的 Mbps
|
if (reg.test(`${proxy.up}`)) {
|
||||||
if (reg.test(`${proxy.up}`) && !`${proxy.up}`.endsWith('Mbps')) {
|
|
||||||
parsedProxy.up = `${proxy.up}`;
|
parsedProxy.up = `${proxy.up}`;
|
||||||
} else {
|
} else {
|
||||||
parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||||
}
|
}
|
||||||
if (reg.test(`${proxy.down}`) && !`${proxy.down}`.endsWith('Mbps')) {
|
if (reg.test(`${proxy.down}`)) {
|
||||||
parsedProxy.down = `${proxy.down}`;
|
parsedProxy.down = `${proxy.down}`;
|
||||||
} else {
|
} else {
|
||||||
parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||||
@ -601,7 +575,7 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
const hysteria2Parser = (proxy = {}) => {
|
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
type: 'hysteria2',
|
type: 'hysteria2',
|
||||||
@ -613,14 +587,16 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['hop-interval'])
|
if (includeUnsupportedProxy) {
|
||||||
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
|
if (proxy['hop-interval'])
|
||||||
? `${proxy['hop-interval']}s`
|
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
|
||||||
: proxy['hop-interval'];
|
? `${proxy['hop-interval']}s`
|
||||||
if (proxy['ports'])
|
: proxy['hop-interval'];
|
||||||
parsedProxy.server_ports = proxy['ports']
|
if (proxy['ports'])
|
||||||
.split(/\s*,\s*/)
|
parsedProxy.server_ports = proxy['ports']
|
||||||
.map((p) => p.replace(/\s*-\s*/g, ':'));
|
.split(/\s*,\s*/)
|
||||||
|
.map((p) => p.replace(/\s*-\s*/g, ':'));
|
||||||
|
}
|
||||||
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||||
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||||
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
||||||
@ -665,23 +641,6 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
const anytlsParser = (proxy = {}) => {
|
|
||||||
const parsedProxy = {
|
|
||||||
tag: proxy.name,
|
|
||||||
type: 'anytls',
|
|
||||||
server: proxy.server,
|
|
||||||
server_port: parseInt(`${proxy.port}`, 10),
|
|
||||||
password: proxy.password,
|
|
||||||
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
|
||||||
};
|
|
||||||
if (/^\d+$/.test(proxy['idle-session-check-interval']))
|
|
||||||
parsedProxy.idle_session_check_interval = `${proxy['idle-session-check-interval']}s`;
|
|
||||||
if (/^\d+$/.test(proxy['idle-session-timeout']))
|
|
||||||
parsedProxy.idle_session_timeout = `${proxy['idle-session-timeout']}s`;
|
|
||||||
detourParser(proxy, parsedProxy);
|
|
||||||
tlsParser(proxy, parsedProxy);
|
|
||||||
return parsedProxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
const wireguardParser = (proxy = {}) => {
|
const wireguardParser = (proxy = {}) => {
|
||||||
const local_address = ['ip', 'ipv6']
|
const local_address = ['ip', 'ipv6']
|
||||||
@ -870,9 +829,6 @@ export default function singbox_Producer() {
|
|||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
list.push(wireguardParser(proxy));
|
list.push(wireguardParser(proxy));
|
||||||
break;
|
break;
|
||||||
case 'anytls':
|
|
||||||
list.push(anytlsParser(proxy));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||||
import $ from '@/core/app';
|
|
||||||
|
|
||||||
export default function Stash_Producer() {
|
export default function Stash_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
@ -40,20 +39,17 @@ export default function Stash_Producer() {
|
|||||||
'xchacha20',
|
'xchacha20',
|
||||||
'chacha20-ietf-poly1305',
|
'chacha20-ietf-poly1305',
|
||||||
'xchacha20-ietf-poly1305',
|
'xchacha20-ietf-poly1305',
|
||||||
'2022-blake3-aes-128-gcm',
|
...(opts['include-unsupported-proxy']
|
||||||
'2022-blake3-aes-256-gcm',
|
? [
|
||||||
|
'2022-blake3-aes-128-gcm',
|
||||||
|
'2022-blake3-aes-256-gcm',
|
||||||
|
]
|
||||||
|
: []),
|
||||||
].includes(proxy.cipher)) ||
|
].includes(proxy.cipher)) ||
|
||||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||||
(proxy.type === 'vless' &&
|
(proxy.type === 'vless' && proxy['reality-opts'])
|
||||||
proxy['reality-opts'] &&
|
|
||||||
!['xtls-rprx-vision'].includes(proxy.flow))
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
|
|
||||||
$.error(
|
|
||||||
`Stash 暂不支持前置代理字段. 已过滤节点 ${proxy.name}. 请使用 代理的转发链 https://stash.wiki/proxy-protocols/proxy-groups#relay`,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@ -191,8 +187,6 @@ export default function Stash_Producer() {
|
|||||||
proxy['preshared-key'] =
|
proxy['preshared-key'] =
|
||||||
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
proxy['pre-shared-key'] = proxy['preshared-key'];
|
proxy['pre-shared-key'] = proxy['preshared-key'];
|
||||||
} else if (proxy.type === 'snell' && proxy.version < 3) {
|
|
||||||
delete proxy.udp;
|
|
||||||
} else if (proxy.type === 'vless') {
|
} else if (proxy.type === 'vless') {
|
||||||
if (isPresent(proxy, 'sni')) {
|
if (isPresent(proxy, 'sni')) {
|
||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
@ -251,7 +245,6 @@ export default function Stash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
'anytls',
|
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
@ -261,6 +254,11 @@ export default function Stash_Producer() {
|
|||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export default function Surge_Producer() {
|
|||||||
return { produce };
|
return { produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocks(proxy) {
|
function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
if (!proxy.cipher) {
|
if (!proxy.cipher) {
|
||||||
@ -87,8 +87,9 @@ function shadowsocks(proxy) {
|
|||||||
'chacha20',
|
'chacha20',
|
||||||
'chacha20-ietf',
|
'chacha20-ietf',
|
||||||
'none',
|
'none',
|
||||||
'2022-blake3-aes-128-gcm',
|
...(includeUnsupportedProxy
|
||||||
'2022-blake3-aes-256-gcm',
|
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm']
|
||||||
|
: []),
|
||||||
].includes(proxy.cipher)
|
].includes(proxy.cipher)
|
||||||
) {
|
) {
|
||||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||||
@ -433,9 +434,6 @@ function ssh(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
function http(proxy) {
|
function http(proxy) {
|
||||||
if (proxy.headers && Object.keys(proxy.headers).length > 0) {
|
|
||||||
throw new Error(`headers is unsupported`);
|
|
||||||
}
|
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
const type = proxy.tls ? 'https' : 'http';
|
const type = proxy.tls ? 'https' : 'http';
|
||||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||||
|
@ -141,19 +141,10 @@ function mihomo(proxy, type, opts) {
|
|||||||
dns: {
|
dns: {
|
||||||
enable: true,
|
enable: true,
|
||||||
ipv6,
|
ipv6,
|
||||||
'default-nameserver': opts?.defaultNameserver ||
|
nameserver: [
|
||||||
proxy._defaultNameserver || [
|
'https://223.6.6.6/dns-query',
|
||||||
'180.76.76.76',
|
'https://120.53.53.53/dns-query',
|
||||||
'52.80.52.52',
|
],
|
||||||
'119.28.28.28',
|
|
||||||
'223.6.6.6',
|
|
||||||
],
|
|
||||||
nameserver: opts?.nameserver ||
|
|
||||||
proxy._nameserver || [
|
|
||||||
'https://doh.pub/dns-query',
|
|
||||||
'https://dns.alidns.com/dns-query',
|
|
||||||
'https://doh-pure.onedns.net/dns-query',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
proxies: [
|
proxies: [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@ export default function URI_Producer() {
|
|||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
delete proxy['no-resolve'];
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,21 +23,10 @@ export default function URI_Producer() {
|
|||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
if (
|
if (proxy.server && isIPv6(proxy.server)) {
|
||||||
!['vmess'].includes(proxy.type) &&
|
|
||||||
proxy.server &&
|
|
||||||
isIPv6(proxy.server)
|
|
||||||
) {
|
|
||||||
proxy.server = `[${proxy.server}]`;
|
proxy.server = `[${proxy.server}]`;
|
||||||
}
|
}
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'socks5':
|
|
||||||
result = `socks://${encodeURIComponent(
|
|
||||||
Base64.encode(
|
|
||||||
`${proxy.username ?? ''}:${proxy.password ?? ''}`,
|
|
||||||
),
|
|
||||||
)}@${proxy.server}:${proxy.port}#${proxy.name}`;
|
|
||||||
break;
|
|
||||||
case 'ss':
|
case 'ss':
|
||||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||||
result = `ss://${
|
result = `ss://${
|
||||||
@ -65,11 +54,6 @@ export default function URI_Producer() {
|
|||||||
}${opts.tls ? ';tls' : ''}`,
|
}${opts.tls ? ';tls' : ''}`,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'shadow-tls':
|
|
||||||
result += encodeURIComponent(
|
|
||||||
`shadow-tls;host=${opts.host};password=${opts.password};version=${opts.version}`,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported plugin option: ${proxy.plugin}`,
|
`Unsupported plugin option: ${proxy.plugin}`,
|
||||||
@ -119,17 +103,12 @@ export default function URI_Producer() {
|
|||||||
v: '2',
|
v: '2',
|
||||||
ps: proxy.name,
|
ps: proxy.name,
|
||||||
add: proxy.server,
|
add: proxy.server,
|
||||||
port: `${proxy.port}`,
|
port: proxy.port,
|
||||||
id: proxy.uuid,
|
id: proxy.uuid,
|
||||||
aid: `${proxy.alterId || 0}`,
|
|
||||||
scy: proxy.cipher,
|
|
||||||
net,
|
|
||||||
type,
|
type,
|
||||||
|
aid: proxy.alterId || 0,
|
||||||
|
net,
|
||||||
tls: proxy.tls ? 'tls' : '',
|
tls: proxy.tls ? 'tls' : '',
|
||||||
alpn: Array.isArray(proxy.alpn)
|
|
||||||
? proxy.alpn.join(',')
|
|
||||||
: proxy.alpn,
|
|
||||||
fp: proxy['client-fingerprint'],
|
|
||||||
};
|
};
|
||||||
if (proxy.tls && proxy.sni) {
|
if (proxy.tls && proxy.sni) {
|
||||||
result.sni = proxy.sni;
|
result.sni = proxy.sni;
|
||||||
@ -140,7 +119,16 @@ export default function URI_Producer() {
|
|||||||
proxy[`${proxy.network}-opts`]?.path;
|
proxy[`${proxy.network}-opts`]?.path;
|
||||||
let vmessTransportHost =
|
let vmessTransportHost =
|
||||||
proxy[`${proxy.network}-opts`]?.headers?.Host;
|
proxy[`${proxy.network}-opts`]?.headers?.Host;
|
||||||
|
if (vmessTransportPath) {
|
||||||
|
result.path = Array.isArray(vmessTransportPath)
|
||||||
|
? vmessTransportPath[0]
|
||||||
|
: vmessTransportPath;
|
||||||
|
}
|
||||||
|
if (vmessTransportHost) {
|
||||||
|
result.host = Array.isArray(vmessTransportHost)
|
||||||
|
? vmessTransportHost[0]
|
||||||
|
: vmessTransportHost;
|
||||||
|
}
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
result.path =
|
result.path =
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
@ -152,31 +140,6 @@ export default function URI_Producer() {
|
|||||||
'gun';
|
'gun';
|
||||||
result.host =
|
result.host =
|
||||||
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
|
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
|
||||||
} else if (['kcp', 'quic'].includes(proxy.network)) {
|
|
||||||
// https://github.com/XTLS/Xray-core/issues/91
|
|
||||||
result.type =
|
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
|
||||||
`_${proxy.network}-type`
|
|
||||||
] || 'none';
|
|
||||||
result.host =
|
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
|
||||||
`_${proxy.network}-host`
|
|
||||||
];
|
|
||||||
result.path =
|
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
|
||||||
`_${proxy.network}-path`
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
if (vmessTransportPath) {
|
|
||||||
result.path = Array.isArray(vmessTransportPath)
|
|
||||||
? vmessTransportPath[0]
|
|
||||||
: vmessTransportPath;
|
|
||||||
}
|
|
||||||
if (vmessTransportHost) {
|
|
||||||
result.host = Array.isArray(vmessTransportHost)
|
|
||||||
? vmessTransportHost[0]
|
|
||||||
: vmessTransportHost;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
||||||
@ -186,7 +149,6 @@ export default function URI_Producer() {
|
|||||||
const isReality = proxy['reality-opts'];
|
const isReality = proxy['reality-opts'];
|
||||||
let sid = '';
|
let sid = '';
|
||||||
let pbk = '';
|
let pbk = '';
|
||||||
let spx = '';
|
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
security = 'reality';
|
security = 'reality';
|
||||||
const publicKey = proxy['reality-opts']?.['public-key'];
|
const publicKey = proxy['reality-opts']?.['public-key'];
|
||||||
@ -197,10 +159,6 @@ export default function URI_Producer() {
|
|||||||
if (shortId) {
|
if (shortId) {
|
||||||
sid = `&sid=${encodeURIComponent(shortId)}`;
|
sid = `&sid=${encodeURIComponent(shortId)}`;
|
||||||
}
|
}
|
||||||
const spiderX = proxy['reality-opts']?.['_spider-x'];
|
|
||||||
if (spiderX) {
|
|
||||||
spx = `&spx=${encodeURIComponent(spiderX)}`;
|
|
||||||
}
|
|
||||||
} else if (proxy.tls) {
|
} else if (proxy.tls) {
|
||||||
security = 'tls';
|
security = 'tls';
|
||||||
}
|
}
|
||||||
@ -230,14 +188,6 @@ export default function URI_Producer() {
|
|||||||
if (proxy.flow) {
|
if (proxy.flow) {
|
||||||
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
||||||
}
|
}
|
||||||
let extra = '';
|
|
||||||
if (proxy._extra) {
|
|
||||||
extra = `&extra=${encodeURIComponent(proxy._extra)}`;
|
|
||||||
}
|
|
||||||
let mode = '';
|
|
||||||
if (proxy._mode) {
|
|
||||||
mode = `&mode=${encodeURIComponent(proxy._mode)}`;
|
|
||||||
}
|
|
||||||
let vlessType = proxy.network;
|
let vlessType = proxy.network;
|
||||||
if (
|
if (
|
||||||
proxy.network === 'ws' &&
|
proxy.network === 'ws' &&
|
||||||
@ -304,7 +254,7 @@ export default function URI_Producer() {
|
|||||||
proxy.port
|
proxy.port
|
||||||
}?security=${encodeURIComponent(
|
}?security=${encodeURIComponent(
|
||||||
security,
|
security,
|
||||||
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}#${encodeURIComponent(
|
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
@ -374,54 +324,16 @@ export default function URI_Producer() {
|
|||||||
: proxy.alpn.join(','),
|
: proxy.alpn.join(','),
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
const trojanIsReality = proxy['reality-opts'];
|
|
||||||
let trojanSid = '';
|
|
||||||
let trojanPbk = '';
|
|
||||||
let trojanSpx = '';
|
|
||||||
let trojanSecurity = '';
|
|
||||||
let trojanMode = '';
|
|
||||||
let trojanExtra = '';
|
|
||||||
if (trojanIsReality) {
|
|
||||||
trojanSecurity = `&security=reality`;
|
|
||||||
const publicKey = proxy['reality-opts']?.['public-key'];
|
|
||||||
if (publicKey) {
|
|
||||||
trojanPbk = `&pbk=${encodeURIComponent(publicKey)}`;
|
|
||||||
}
|
|
||||||
const shortId = proxy['reality-opts']?.['short-id'];
|
|
||||||
if (shortId) {
|
|
||||||
trojanSid = `&sid=${encodeURIComponent(shortId)}`;
|
|
||||||
}
|
|
||||||
const spiderX = proxy['reality-opts']?.['_spider-x'];
|
|
||||||
if (spiderX) {
|
|
||||||
trojanSpx = `&spx=${encodeURIComponent(spiderX)}`;
|
|
||||||
}
|
|
||||||
if (proxy._extra) {
|
|
||||||
trojanExtra = `&extra=${encodeURIComponent(
|
|
||||||
proxy._extra,
|
|
||||||
)}`;
|
|
||||||
}
|
|
||||||
if (proxy._mode) {
|
|
||||||
trojanMode = `&mode=${encodeURIComponent(proxy._mode)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = `trojan://${proxy.password}@${proxy.server}:${
|
result = `trojan://${proxy.password}@${proxy.server}:${
|
||||||
proxy.port
|
proxy.port
|
||||||
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
||||||
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
||||||
}${trojanTransport}${trojanAlpn}${trojanFp}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
|
}${trojanTransport}${trojanAlpn}${trojanFp}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
case 'hysteria2':
|
case 'hysteria2':
|
||||||
let hysteria2params = [];
|
let hysteria2params = [];
|
||||||
if (proxy['hop-interval']) {
|
|
||||||
hysteria2params.push(
|
|
||||||
`hop-interval=${proxy['hop-interval']}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (proxy['keepalive']) {
|
|
||||||
hysteria2params.push(`keepalive=${proxy['keepalive']}`);
|
|
||||||
}
|
|
||||||
if (proxy['skip-cert-verify']) {
|
if (proxy['skip-cert-verify']) {
|
||||||
hysteria2params.push(`insecure=1`);
|
hysteria2params.push(`insecure=1`);
|
||||||
}
|
}
|
||||||
@ -501,7 +413,7 @@ export default function URI_Producer() {
|
|||||||
hysteriaParams.push(`obfsParam=${proxy[key]}`);
|
hysteriaParams.push(`obfsParam=${proxy[key]}`);
|
||||||
} else if (['sni'].includes(key)) {
|
} else if (['sni'].includes(key)) {
|
||||||
hysteriaParams.push(`peer=${proxy[key]}`);
|
hysteriaParams.push(`peer=${proxy[key]}`);
|
||||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
} else if (proxy[key]) {
|
||||||
hysteriaParams.push(
|
hysteriaParams.push(
|
||||||
`${i}=${encodeURIComponent(proxy[key])}`,
|
`${i}=${encodeURIComponent(proxy[key])}`,
|
||||||
);
|
);
|
||||||
@ -528,7 +440,6 @@ export default function URI_Producer() {
|
|||||||
'password',
|
'password',
|
||||||
'server',
|
'server',
|
||||||
'port',
|
'port',
|
||||||
'tls',
|
|
||||||
].includes(key)
|
].includes(key)
|
||||||
) {
|
) {
|
||||||
const i = key.replace(/-/, '_');
|
const i = key.replace(/-/, '_');
|
||||||
@ -557,19 +468,10 @@ export default function URI_Producer() {
|
|||||||
['disable-sni', 'reduce-rtt'].includes(key) &&
|
['disable-sni', 'reduce-rtt'].includes(key) &&
|
||||||
proxy[key]
|
proxy[key]
|
||||||
) {
|
) {
|
||||||
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
|
tuicParams.push(`${i}=1`);
|
||||||
} else if (
|
} else if (proxy[key]) {
|
||||||
['congestion-controller'].includes(key)
|
|
||||||
) {
|
|
||||||
tuicParams.push(
|
tuicParams.push(
|
||||||
`congestion_control=${proxy[key]}`,
|
`${i}=${encodeURIComponent(proxy[key])}`,
|
||||||
);
|
|
||||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
|
||||||
tuicParams.push(
|
|
||||||
`${i.replace(
|
|
||||||
/-/g,
|
|
||||||
'_',
|
|
||||||
)}=${encodeURIComponent(proxy[key])}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,54 +486,6 @@ export default function URI_Producer() {
|
|||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'anytls':
|
|
||||||
let anytlsParams = [];
|
|
||||||
Object.keys(proxy).forEach((key) => {
|
|
||||||
if (
|
|
||||||
![
|
|
||||||
'name',
|
|
||||||
'type',
|
|
||||||
'password',
|
|
||||||
'server',
|
|
||||||
'port',
|
|
||||||
'tls',
|
|
||||||
].includes(key)
|
|
||||||
) {
|
|
||||||
const i = key.replace(/-/, '_');
|
|
||||||
if (['alpn'].includes(key)) {
|
|
||||||
if (proxy[key]) {
|
|
||||||
anytlsParams.push(
|
|
||||||
`${i}=${encodeURIComponent(
|
|
||||||
Array.isArray(proxy[key])
|
|
||||||
? proxy[key][0]
|
|
||||||
: proxy[key],
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (['skip-cert-verify'].includes(key)) {
|
|
||||||
if (proxy[key]) {
|
|
||||||
anytlsParams.push(`insecure=1`);
|
|
||||||
}
|
|
||||||
} else if (['udp'].includes(key)) {
|
|
||||||
if (proxy[key]) {
|
|
||||||
anytlsParams.push(`udp=1`);
|
|
||||||
}
|
|
||||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
|
||||||
anytlsParams.push(
|
|
||||||
`${i.replace(/-/g, '_')}=${encodeURIComponent(
|
|
||||||
proxy[key],
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
result = `anytls://${encodeURIComponent(proxy.password)}@${
|
|
||||||
proxy.server
|
|
||||||
}:${proxy.port}/?${anytlsParams.join('&')}#${encodeURIComponent(
|
|
||||||
proxy.name,
|
|
||||||
)}`;
|
|
||||||
break;
|
|
||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
let wireguardParams = [];
|
let wireguardParams = [];
|
||||||
|
|
||||||
@ -653,7 +507,7 @@ export default function URI_Producer() {
|
|||||||
if (proxy[key]) {
|
if (proxy[key]) {
|
||||||
wireguardParams.push(`${key}=1`);
|
wireguardParams.push(`${key}=1`);
|
||||||
}
|
}
|
||||||
} else if (proxy[key] && !/^_/i.test(key)) {
|
} else if (proxy[key]) {
|
||||||
wireguardParams.push(
|
wireguardParams.push(
|
||||||
`${key}=${encodeURIComponent(proxy[key])}`,
|
`${key}=${encodeURIComponent(proxy[key])}`,
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
|
||||||
*/
|
*/
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
import $ from '@/core/app';
|
|
||||||
console.log(
|
console.log(
|
||||||
`
|
`
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
@ -19,6 +18,7 @@ console.log(
|
|||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
import serve from '@/restful';
|
import serve from '@/restful';
|
||||||
|
|
||||||
|
@ -84,15 +84,12 @@ async function doSync() {
|
|||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const valid = [];
|
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const subNames = [];
|
const subNames = [];
|
||||||
let enabledCount = 0;
|
|
||||||
allArtifacts.map((artifact) => {
|
allArtifacts.map((artifact) => {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
enabledCount++;
|
|
||||||
if (artifact.type === 'subscription') {
|
if (artifact.type === 'subscription') {
|
||||||
const subName = artifact.source;
|
const subName = artifact.source;
|
||||||
const sub = findByName(allSubs, subName);
|
const sub = findByName(allSubs, subName);
|
||||||
@ -113,13 +110,6 @@ async function doSync() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (enabledCount === 0) {
|
|
||||||
$.info(
|
|
||||||
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subNames.length > 0) {
|
if (subNames.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
subNames.map(async (subName) => {
|
subNames.map(async (subName) => {
|
||||||
@ -166,46 +156,27 @@ async function doSync() {
|
|||||||
files[encodeURIComponent(artifact.name)] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
};
|
||||||
|
|
||||||
valid.push(artifact.name);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`生成同步配置 ${artifact.name} 发生错误: ${
|
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||||
e.message ?? e
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
invalid.push(artifact.name);
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
if (invalid.length > 0) {
|
||||||
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
|
||||||
|
|
||||||
if (valid.length === 0) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
delete body.history;
|
|
||||||
delete body.forks;
|
|
||||||
delete body.owner;
|
|
||||||
Object.values(body.files).forEach((file) => {
|
|
||||||
delete file.content;
|
|
||||||
});
|
|
||||||
$.info('上传配置响应:');
|
|
||||||
$.info(JSON.stringify(body, null, 2));
|
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (
|
if (artifact.sync) {
|
||||||
artifact.sync &&
|
|
||||||
artifact.source &&
|
|
||||||
valid.includes(artifact.name)
|
|
||||||
) {
|
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
@ -233,18 +204,9 @@ async function doSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.info('上传配置成功');
|
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
||||||
|
|
||||||
if (invalid.length > 0) {
|
|
||||||
$.notify(
|
|
||||||
'🌍 Sub-Store',
|
|
||||||
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$.notify('🌍 Sub-Store', '同步配置完成');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.notify('🌍 Sub-Store', '同步配置失败', `原因:${e.message ?? e}`);
|
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${e.message ?? e}`);
|
||||||
$.error(`无法同步配置到 Gist,原因:${e}`);
|
$.error(`无法同步订阅配置到 Gist,原因:${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,10 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
`
|
`
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
Sub-Store -- v${version}
|
Sub-Store -- v${version}
|
||||||
Loon -- ${$loon}
|
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const build = $loon.match(/\((\d+)\)$/)?.[1];
|
|
||||||
let arg;
|
let arg;
|
||||||
if (typeof $argument != 'undefined') {
|
if (typeof $argument != 'undefined') {
|
||||||
arg = Object.fromEntries(
|
arg = Object.fromEntries(
|
||||||
@ -28,28 +26,23 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
} else {
|
} else {
|
||||||
arg = {};
|
arg = {};
|
||||||
}
|
}
|
||||||
console.log(`arg: ${JSON.stringify(arg)}`);
|
|
||||||
|
|
||||||
const RESOURCE_TYPE = {
|
const RESOURCE_TYPE = {
|
||||||
PROXY: 1,
|
PROXY: 1,
|
||||||
RULE: 2,
|
RULE: 2,
|
||||||
};
|
};
|
||||||
if (!arg.resourceUrlOnly) {
|
|
||||||
result = resource;
|
result = resource;
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceType === RESOURCE_TYPE.PROXY) {
|
if (resourceType === RESOURCE_TYPE.PROXY) {
|
||||||
if (!arg.resourceUrlOnly) {
|
try {
|
||||||
try {
|
let proxies = ProxyUtils.parse(resource);
|
||||||
let proxies = ProxyUtils.parse(resource);
|
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||||
'include-unsupported-proxy':
|
});
|
||||||
arg?.includeUnsupportedProxy || build >= 842,
|
} catch (e) {
|
||||||
});
|
console.log('解析器: 使用 resource 出现错误');
|
||||||
} catch (e) {
|
console.log(e.message ?? e);
|
||||||
console.log('解析器: 使用 resource 出现错误');
|
|
||||||
console.log(e.message ?? e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
||||||
@ -66,21 +59,18 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
|||||||
);
|
);
|
||||||
let proxies = ProxyUtils.parse(raw);
|
let proxies = ProxyUtils.parse(raw);
|
||||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||||
'include-unsupported-proxy':
|
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||||
arg?.includeUnsupportedProxy || build >= 842,
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e.message ?? e);
|
console.log(e.message ?? e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
||||||
if (!arg.resourceUrlOnly) {
|
try {
|
||||||
try {
|
const rules = RuleUtils.parse(resource);
|
||||||
const rules = RuleUtils.parse(resource);
|
result = RuleUtils.produce(rules, 'Loon');
|
||||||
result = RuleUtils.produce(rules, 'Loon');
|
} catch (e) {
|
||||||
} catch (e) {
|
console.log(e.message ?? e);
|
||||||
console.log(e.message ?? e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { COLLECTIONS_KEY, ARTIFACTS_KEY, FILES_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
import { formatDateTime } from '@/utils';
|
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
||||||
@ -60,11 +59,7 @@ function getCollection(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
`sub-store_collection_${name}_${formatDateTime(
|
|
||||||
new Date(),
|
|
||||||
)}.json`,
|
|
||||||
)}"`,
|
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(collection));
|
.send(JSON.stringify(collection));
|
||||||
} else {
|
} else {
|
||||||
@ -106,18 +101,7 @@ function updateCollection(req, res) {
|
|||||||
artifact.source = newCol.name;
|
artifact.source = newCol.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update all files referring this collection
|
|
||||||
const allFiles = $.read(FILES_KEY) || [];
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (
|
|
||||||
file.sourceType === 'collection' &&
|
|
||||||
file.sourceName === oldCol.name
|
|
||||||
) {
|
|
||||||
file.sourceName = newCol.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.write(allFiles, FILES_KEY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateByName(allCols, name, newCol);
|
updateByName(allCols, name, newCol);
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import {
|
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
||||||
getPlatformFromHeaders,
|
|
||||||
shouldIncludeUnsupportedProxy,
|
|
||||||
} from '@/utils/user-agent';
|
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
import { getFlowHeaders } from '@/utils/flow';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { failed } from '@/restful/response';
|
import { failed } from '@/restful/response';
|
||||||
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
@ -111,17 +108,7 @@ async function downloadSubscription(req, res) {
|
|||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let $options = {
|
let $options = {};
|
||||||
_req: {
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
path: req.path,
|
|
||||||
query: req.query,
|
|
||||||
params: req.params,
|
|
||||||
headers: req.headers,
|
|
||||||
body: req.body,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (req.query.$options) {
|
if (req.query.$options) {
|
||||||
try {
|
try {
|
||||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
@ -174,19 +161,7 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!includeUnsupportedProxy &&
|
|
||||||
shouldIncludeUnsupportedProxy(platform, reqUA)
|
|
||||||
) {
|
|
||||||
includeUnsupportedProxy = true;
|
|
||||||
$.info(
|
|
||||||
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
@ -259,7 +234,7 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$arguments.noFlow && /^https?/.test(url)) {
|
if (!$arguments.noFlow) {
|
||||||
// forward flow headers
|
// forward flow headers
|
||||||
flowInfo = await getFlowHeaders(
|
flowInfo = await getFlowHeaders(
|
||||||
$arguments?.insecure ? `${url}#insecure` : url,
|
$arguments?.insecure ? `${url}#insecure` : url,
|
||||||
@ -269,10 +244,7 @@ async function downloadSubscription(req, res) {
|
|||||||
$arguments.flowUrl,
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set(
|
res.set('subscription-userinfo', flowInfo);
|
||||||
'subscription-userinfo',
|
|
||||||
normalizeFlowHeader(flowInfo),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -306,9 +278,7 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
res.set(
|
res.set(
|
||||||
'subscription-userinfo',
|
'subscription-userinfo',
|
||||||
normalizeFlowHeader(
|
[subUserInfo, flowInfo].filter((i) => i).join('; '),
|
||||||
[subUserInfo, flowInfo].filter((i) => i).join(';'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,9 +342,11 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
|
||||||
$.info(
|
$.info(
|
||||||
`正在下载组合订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
||||||
|
req.headers['user-agent'] || req.headers['User-Agent']
|
||||||
|
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@ -386,17 +358,7 @@ async function downloadCollection(req, res) {
|
|||||||
noCache,
|
noCache,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
|
|
||||||
let $options = {
|
let $options = {};
|
||||||
_req: {
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
path: req.path,
|
|
||||||
query: req.query,
|
|
||||||
params: req.params,
|
|
||||||
headers: req.headers,
|
|
||||||
body: req.body,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (req.query.$options) {
|
if (req.query.$options) {
|
||||||
try {
|
try {
|
||||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
@ -431,18 +393,7 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
if (includeUnsupportedProxy) {
|
if (includeUnsupportedProxy) {
|
||||||
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
|
||||||
$.info(
|
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`);
|
||||||
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!includeUnsupportedProxy &&
|
|
||||||
shouldIncludeUnsupportedProxy(platform, reqUA)
|
|
||||||
) {
|
|
||||||
includeUnsupportedProxy = true;
|
|
||||||
$.info(
|
|
||||||
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (useMihomoExternal) {
|
if (useMihomoExternal) {
|
||||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||||
@ -466,7 +417,6 @@ async function downloadCollection(req, res) {
|
|||||||
$options,
|
$options,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
ua: reqUA,
|
|
||||||
});
|
});
|
||||||
let subUserInfoOfSub;
|
let subUserInfoOfSub;
|
||||||
// forward flow header from the first subscription in this collection
|
// forward flow header from the first subscription in this collection
|
||||||
@ -506,7 +456,7 @@ async function downloadCollection(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$arguments.noFlow && /^https?:/.test(url)) {
|
if (!$arguments.noFlow) {
|
||||||
subUserInfoOfSub = await getFlowHeaders(
|
subUserInfoOfSub = await getFlowHeaders(
|
||||||
$arguments?.insecure ? `${url}#insecure` : url,
|
$arguments?.insecure ? `${url}#insecure` : url,
|
||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
@ -572,15 +522,13 @@ async function downloadCollection(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
subUserInfoOfCol = collection.subUserinfo;
|
subUserInfoOfCol = collection.subUserinfo;
|
||||||
}
|
}
|
||||||
const subUserInfo = [subUserInfoOfCol, subUserInfoOfSub]
|
res.set(
|
||||||
.filter((i) => i)
|
'subscription-userinfo',
|
||||||
.join('; ');
|
[subUserInfoOfCol, subUserInfoOfSub]
|
||||||
if (subUserInfo) {
|
.filter((i) => i)
|
||||||
res.set(
|
.join('; '),
|
||||||
'subscription-userinfo',
|
);
|
||||||
normalizeFlowHeader(subUserInfo),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
if (resultFormat === 'nezha') {
|
if (resultFormat === 'nezha') {
|
||||||
output = nezhaTransform(output);
|
output = nezhaTransform(output);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
|
import { getFlowHeaders } from '@/utils/flow';
|
||||||
import { FILES_KEY, ARTIFACTS_KEY } from '@/constants';
|
import { FILES_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import {
|
import {
|
||||||
@ -9,7 +9,6 @@ import {
|
|||||||
InternalServerError,
|
InternalServerError,
|
||||||
} from '@/restful/errors';
|
} from '@/restful/errors';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { formatDateTime } from '@/utils';
|
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||||
@ -52,8 +51,8 @@ function createFile(req, res) {
|
|||||||
async function getFile(req, res) {
|
async function getFile(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
|
||||||
$.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`);
|
$.info(`正在下载文件:${name}`);
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
subInfoUrl,
|
subInfoUrl,
|
||||||
@ -64,19 +63,8 @@ async function getFile(req, res) {
|
|||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
produceType,
|
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let $options = {
|
let $options = {};
|
||||||
_req: {
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
path: req.path,
|
|
||||||
query: req.query,
|
|
||||||
params: req.params,
|
|
||||||
headers: req.headers,
|
|
||||||
body: req.body,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (req.query.$options) {
|
if (req.query.$options) {
|
||||||
try {
|
try {
|
||||||
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
@ -129,10 +117,6 @@ async function getFile(req, res) {
|
|||||||
if (noCache) {
|
if (noCache) {
|
||||||
$.info(`指定不使用缓存: ${noCache}`);
|
$.info(`指定不使用缓存: ${noCache}`);
|
||||||
}
|
}
|
||||||
if (produceType) {
|
|
||||||
produceType = decodeURIComponent(produceType);
|
|
||||||
$.info(`指定生产类型: ${produceType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
@ -149,8 +133,6 @@ async function getFile(req, res) {
|
|||||||
$options,
|
$options,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
produceType,
|
|
||||||
all: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -164,10 +146,7 @@ async function getFile(req, res) {
|
|||||||
proxy || file.proxy,
|
proxy || file.proxy,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set(
|
res.set('subscription-userinfo', flowInfo);
|
||||||
'subscription-userinfo',
|
|
||||||
normalizeFlowHeader(flowInfo),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -185,15 +164,9 @@ async function getFile(req, res) {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
res.set('Content-Type', 'text/plain; charset=utf-8');
|
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
||||||
if (output?.$options?._res?.headers) {
|
output ?? '',
|
||||||
Object.entries(output.$options._res.headers).forEach(
|
);
|
||||||
([key, value]) => {
|
|
||||||
res.set(key, value);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
res.send(output?.$content ?? '');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.notify(
|
$.notify(
|
||||||
`🌍 Sub-Store 下载文件失败`,
|
`🌍 Sub-Store 下载文件失败`,
|
||||||
@ -233,11 +206,7 @@ function getWholeFile(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
`sub-store_file_${name}_${formatDateTime(
|
|
||||||
new Date(),
|
|
||||||
)}.json`,
|
|
||||||
)}"`,
|
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(file));
|
.send(JSON.stringify(file));
|
||||||
} else {
|
} else {
|
||||||
@ -268,20 +237,6 @@ function updateFile(req, res) {
|
|||||||
};
|
};
|
||||||
$.info(`正在更新文件:${name}...`);
|
$.info(`正在更新文件:${name}...`);
|
||||||
|
|
||||||
if (name !== newFile.name) {
|
|
||||||
// update all artifacts referring this collection
|
|
||||||
const allArtifacts = $.read(ARTIFACTS_KEY) || [];
|
|
||||||
for (const artifact of allArtifacts) {
|
|
||||||
if (
|
|
||||||
artifact.type === 'file' &&
|
|
||||||
artifact.source === oldFile.name
|
|
||||||
) {
|
|
||||||
artifact.source = newFile.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateByName(allFiles, name, newFile);
|
updateByName(allFiles, name, newFile);
|
||||||
$.write(allFiles, FILES_KEY);
|
$.write(allFiles, FILES_KEY);
|
||||||
success(res, newFile);
|
success(res, newFile);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import express from '@/vendor/express';
|
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, { downloadFile } from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
import { syncArtifacts, produceArtifact } from '@/restful/sync';
|
import { syncArtifacts } from '@/restful/sync';
|
||||||
import { gistBackupAction } from '@/restful/miscs';
|
import { gistBackupAction } from '@/restful/miscs';
|
||||||
import { TOKENS_KEY } from '@/constants';
|
import { TOKENS_KEY } from '@/constants';
|
||||||
|
|
||||||
@ -29,77 +29,6 @@ export default function serve() {
|
|||||||
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
|
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
|
||||||
}
|
}
|
||||||
const $app = express({ substore: $, port, host });
|
const $app = express({ substore: $, port, host });
|
||||||
if ($.env.isNode) {
|
|
||||||
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
|
||||||
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
|
|
||||||
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
|
||||||
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
|
|
||||||
if (be_prefix || be_merge) {
|
|
||||||
if (!fe_be_path.startsWith('/')) {
|
|
||||||
throw new Error(
|
|
||||||
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (be_merge) {
|
|
||||||
$.info(`[BACKEND] MERGE mode is [ON].`);
|
|
||||||
$.info(`[BACKEND && FRONTEND] ${host}:${port}`);
|
|
||||||
}
|
|
||||||
$.info(`[BACKEND PREFIX] ${host}:${port}${fe_be_path}`);
|
|
||||||
$app.use((req, res, next) => {
|
|
||||||
if (req.path.startsWith(fe_be_path)) {
|
|
||||||
req.url = req.url.replace(fe_be_path, '') || '/';
|
|
||||||
if (be_merge && req.url.startsWith('/api/')) {
|
|
||||||
req.query['share'] = 'true';
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pathname =
|
|
||||||
decodeURIComponent(req._parsedUrl.pathname) || '/';
|
|
||||||
if (
|
|
||||||
be_merge &&
|
|
||||||
req.path.startsWith('/share/') &&
|
|
||||||
req.query.token
|
|
||||||
) {
|
|
||||||
if (req.method.toLowerCase() !== 'get') {
|
|
||||||
res.status(405).send('Method not allowed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tokens = $.read(TOKENS_KEY) || [];
|
|
||||||
const token = tokens.find(
|
|
||||||
(t) =>
|
|
||||||
t.token === req.query.token &&
|
|
||||||
`/share/${t.type}/${t.name}` === pathname &&
|
|
||||||
(t.exp == null || t.exp > Date.now()),
|
|
||||||
);
|
|
||||||
if (token) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (be_merge && fe_path && req.path.indexOf('/', 1) == -1) {
|
|
||||||
if (req.path.indexOf('.') == -1) {
|
|
||||||
req.url = '/index.html';
|
|
||||||
}
|
|
||||||
const express_ = eval(`require("express")`);
|
|
||||||
const mime_ = eval(`require("mime-types")`);
|
|
||||||
const path_ = eval(`require("path")`);
|
|
||||||
const staticFileMiddleware = express_.static(fe_path, {
|
|
||||||
setHeaders: (res, path) => {
|
|
||||||
const type = mime_.contentType(path_.extname(path));
|
|
||||||
if (type) {
|
|
||||||
res.set('Content-Type', type);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
staticFileMiddleware(req, res, next);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.status(403).end('Forbbiden');
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// register routes
|
// register routes
|
||||||
registerCollectionRoutes($app);
|
registerCollectionRoutes($app);
|
||||||
registerSubscriptionRoutes($app);
|
registerSubscriptionRoutes($app);
|
||||||
@ -146,39 +75,6 @@ export default function serve() {
|
|||||||
// 'Asia/Shanghai' // timeZone
|
// 'Asia/Shanghai' // timeZone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
|
|
||||||
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
|
|
||||||
const produce_cron = eval('process.env.SUB_STORE_PRODUCE_CRON');
|
|
||||||
if (produce_cron) {
|
|
||||||
$.info(`[PRODUCE CRON] ${produce_cron} enabled`);
|
|
||||||
const { CronJob } = eval(`require("cron")`);
|
|
||||||
produce_cron.split(/\s*;\s*/).map((item) => {
|
|
||||||
const [cron, type, name] = item.split(/\s*,\s*/);
|
|
||||||
new CronJob(
|
|
||||||
cron.trim(),
|
|
||||||
async function () {
|
|
||||||
try {
|
|
||||||
$.info(
|
|
||||||
`[PRODUCE CRON] ${type} ${name} ${cron} started`,
|
|
||||||
);
|
|
||||||
await produceArtifact({ type, name });
|
|
||||||
$.info(
|
|
||||||
`[PRODUCE CRON] ${type} ${name} ${cron} finished`,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`[PRODUCE CRON] ${type} ${name} ${cron} error: ${
|
|
||||||
e.message ?? e
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, // onTick
|
|
||||||
null, // onComplete
|
|
||||||
true, // start
|
|
||||||
// 'Asia/Shanghai' // timeZone
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const backend_download_cron = eval(
|
const backend_download_cron = eval(
|
||||||
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
|
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
|
||||||
);
|
);
|
||||||
@ -235,60 +131,6 @@ export default function serve() {
|
|||||||
// 'Asia/Shanghai' // timeZone
|
// 'Asia/Shanghai' // timeZone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mmdb_cron = eval('process.env.SUB_STORE_MMDB_CRON');
|
|
||||||
const countryFile = eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH');
|
|
||||||
const countryUrl = eval('process.env.SUB_STORE_MMDB_COUNTRY_URL');
|
|
||||||
const asnFile = eval('process.env.SUB_STORE_MMDB_ASN_PATH');
|
|
||||||
const asnUrl = eval('process.env.SUB_STORE_MMDB_ASN_URL');
|
|
||||||
if (mmdb_cron && ((countryFile && countryUrl) || (asnFile && asnUrl))) {
|
|
||||||
$.info(`[MMDB CRON] ${mmdb_cron} enabled`);
|
|
||||||
const { CronJob } = eval(`require("cron")`);
|
|
||||||
new CronJob(
|
|
||||||
mmdb_cron,
|
|
||||||
async function () {
|
|
||||||
try {
|
|
||||||
$.info(`[MMDB CRON] ${mmdb_cron} started`);
|
|
||||||
if (countryFile && countryUrl) {
|
|
||||||
try {
|
|
||||||
$.info(
|
|
||||||
`[MMDB CRON] downloading ${countryUrl} to ${countryFile}`,
|
|
||||||
);
|
|
||||||
await downloadFile(countryUrl, countryFile);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`[MMDB CRON] ${countryUrl} download failed: ${
|
|
||||||
e.message ?? e
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (asnFile && asnUrl) {
|
|
||||||
try {
|
|
||||||
$.info(
|
|
||||||
`[MMDB CRON] downloading ${asnUrl} to ${asnFile}`,
|
|
||||||
);
|
|
||||||
await downloadFile(asnUrl, asnFile);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`[MMDB CRON] ${asnUrl} download failed: ${
|
|
||||||
e.message ?? e
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.info(`[MMDB CRON] ${mmdb_cron} finished`);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`[MMDB CRON] ${mmdb_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');
|
||||||
@ -300,8 +142,7 @@ export default function serve() {
|
|||||||
const fe_abs_path = path.resolve(
|
const fe_abs_path = path.resolve(
|
||||||
fe_path || path.join(__dirname, 'frontend'),
|
fe_path || path.join(__dirname, 'frontend'),
|
||||||
);
|
);
|
||||||
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
if (fe_path) {
|
||||||
if (fe_path && !be_merge) {
|
|
||||||
try {
|
try {
|
||||||
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -320,15 +161,11 @@ export default function serve() {
|
|||||||
|
|
||||||
const staticFileMiddleware = express_.static(fe_path);
|
const staticFileMiddleware = express_.static(fe_path);
|
||||||
|
|
||||||
|
let be_share_rewrite = '/share/:type/:name';
|
||||||
|
let be_api_rewrite = '';
|
||||||
|
let be_download_rewrite = '';
|
||||||
let be_api = '/api/';
|
let be_api = '/api/';
|
||||||
let be_download = '/download/';
|
let be_download = '/download/';
|
||||||
let be_share = '/share/';
|
|
||||||
let be_download_rewrite = '';
|
|
||||||
let be_api_rewrite = '';
|
|
||||||
let be_share_rewrite = `${be_share}:type/:name`;
|
|
||||||
let prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX')
|
|
||||||
? fe_be_path
|
|
||||||
: '';
|
|
||||||
if (fe_be_path) {
|
if (fe_be_path) {
|
||||||
if (!fe_be_path.startsWith('/')) {
|
if (!fe_be_path.startsWith('/')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -345,9 +182,9 @@ export default function serve() {
|
|||||||
app.use(
|
app.use(
|
||||||
be_share_rewrite,
|
be_share_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}${prefix}`,
|
target: `http://127.0.0.1:${port}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: async (path, req) => {
|
pathRewrite: (path, req) => {
|
||||||
if (req.method.toLowerCase() !== 'get')
|
if (req.method.toLowerCase() !== 'get')
|
||||||
throw new Error('Method not allowed');
|
throw new Error('Method not allowed');
|
||||||
const tokens = $.read(TOKENS_KEY) || [];
|
const tokens = $.read(TOKENS_KEY) || [];
|
||||||
@ -359,26 +196,34 @@ export default function serve() {
|
|||||||
(t.exp == null || t.exp > Date.now()),
|
(t.exp == null || t.exp > Date.now()),
|
||||||
);
|
);
|
||||||
if (!token) throw new Error('Forbbiden');
|
if (!token) throw new Error('Forbbiden');
|
||||||
return req.originalUrl;
|
return path;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
be_api_rewrite,
|
be_api_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}${prefix}${be_api}`,
|
target: `http://127.0.0.1:${port}`,
|
||||||
pathRewrite: async (path) => {
|
pathRewrite: (path) => {
|
||||||
return path.includes('?')
|
const newPath = path.startsWith(be_api_rewrite)
|
||||||
? `${path}&share=true`
|
? path.replace(be_api_rewrite, be_api)
|
||||||
: `${path}?share=true`;
|
: path;
|
||||||
|
return newPath.includes('?')
|
||||||
|
? `${newPath}&share=true`
|
||||||
|
: `${newPath}?share=true`;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
be_download_rewrite,
|
be_download_rewrite,
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: `http://127.0.0.1:${port}${prefix}${be_download}`,
|
target: `http://127.0.0.1:${port}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
pathRewrite: (path) => {
|
||||||
|
return path.startsWith(be_download_rewrite)
|
||||||
|
? path.replace(be_download_rewrite, be_download)
|
||||||
|
: path;
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -398,10 +243,10 @@ export default function serve() {
|
|||||||
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
|
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
|
||||||
if (fe_be_path) {
|
if (fe_be_path) {
|
||||||
$.info(
|
$.info(
|
||||||
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> ${host}:${port}${prefix}${be_api}`,
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> http://127.0.0.1:${port}${be_api}`,
|
||||||
);
|
);
|
||||||
$.info(
|
$.info(
|
||||||
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> ${host}:${port}${prefix}${be_download}`,
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> http://127.0.0.1:${port}${be_download}`,
|
||||||
);
|
);
|
||||||
$.info(
|
$.info(
|
||||||
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
||||||
|
@ -14,7 +14,6 @@ import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
|||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
import migrate from '@/utils/migration';
|
import migrate from '@/utils/migration';
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
import { formatDateTime } from '@/utils';
|
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
// utils
|
// utils
|
||||||
@ -28,9 +27,7 @@ export default function register($app) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
'attachment; filename="sub-store.json"',
|
||||||
`sub-store_data_${formatDateTime(new Date())}.json`,
|
|
||||||
)}"`,
|
|
||||||
)
|
)
|
||||||
.send(
|
.send(
|
||||||
$.env.isNode
|
$.env.isNode
|
||||||
@ -49,17 +46,11 @@ export default function register($app) {
|
|||||||
success(res);
|
success(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ENV().isNode) {
|
// Redirect sub.store to vercel webpage
|
||||||
$app.get('/', getEnv);
|
$app.get('/', async (req, res) => {
|
||||||
} else {
|
// 302 redirect
|
||||||
// Redirect sub.store to vercel webpage
|
res.set('location', 'https://sub-store.vercel.app/').status(302).end();
|
||||||
$app.get('/', async (req, res) => {
|
});
|
||||||
// 302 redirect
|
|
||||||
res.set('location', 'https://sub-store.vercel.app/')
|
|
||||||
.status(302)
|
|
||||||
.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle preflight request for QX
|
// handle preflight request for QX
|
||||||
if (ENV().isQX) {
|
if (ENV().isQX) {
|
||||||
@ -77,19 +68,7 @@ function getEnv(req, res) {
|
|||||||
if (req.query.share) {
|
if (req.query.share) {
|
||||||
env.feature.share = true;
|
env.feature.share = true;
|
||||||
}
|
}
|
||||||
res.set('Content-Type', 'application/json;charset=UTF-8').send(
|
success(res, env);
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
guide: '⚠️⚠️⚠️ 您当前看到的是后端的响应. 若想配合前端使用, 可访问官方前端 https://sub-store.vercel.app 后自行配置后端地址, 或一键配置后端 https://sub-store.vercel.app?api=https://a.com/xxx (假设 https://a.com 是你后端的域名, /xxx 是自定义路径). 需注意 HTTPS 前端无法请求非本地的 HTTP 后端(部分浏览器上也无法访问本地 HTTP 后端). 请配置反代或在局域网自建 HTTP 前端. 如果还有问题, 可查看此排查说明: https://t.me/zhetengsha/1068',
|
|
||||||
...env,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh(_, res) {
|
async function refresh(_, res) {
|
||||||
@ -119,21 +98,6 @@ async function gistBackupAction(action) {
|
|||||||
const updated = settings.syncTime;
|
const updated = settings.syncTime;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
try {
|
|
||||||
content = $.read('#sub-store');
|
|
||||||
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
|
|
||||||
$.info(`下载备份, 与本地内容对比...`);
|
|
||||||
const onlineContent = await gist.download(
|
|
||||||
GIST_BACKUP_FILE_NAME,
|
|
||||||
);
|
|
||||||
if (onlineContent === content) {
|
|
||||||
$.info(`内容一致, 无需上传备份`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
$.error(`${error.message ?? error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update syncTime
|
// update syncTime
|
||||||
settings.syncTime = new Date().getTime();
|
settings.syncTime = new Date().getTime();
|
||||||
$.write(settings, SETTINGS_KEY);
|
$.write(settings, SETTINGS_KEY);
|
||||||
|
@ -15,60 +15,46 @@ export default function register($app) {
|
|||||||
async function previewFile(req, res) {
|
async function previewFile(req, res) {
|
||||||
try {
|
try {
|
||||||
const file = req.body;
|
const file = req.body;
|
||||||
let content = '';
|
let content;
|
||||||
if (file.type !== 'mihomoProfile') {
|
if (
|
||||||
if (
|
file.source === 'local' &&
|
||||||
file.source === 'local' &&
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
) {
|
||||||
) {
|
content = file.content;
|
||||||
content = file.content;
|
} else {
|
||||||
} else {
|
const errors = {};
|
||||||
const errors = {};
|
content = await Promise.all(
|
||||||
content = await Promise.all(
|
file.url
|
||||||
file.url
|
.split(/[\r\n]+/)
|
||||||
.split(/[\r\n]+/)
|
.map((i) => i.trim())
|
||||||
.map((i) => i.trim())
|
.filter((i) => i.length)
|
||||||
.filter((i) => i.length)
|
.map(async (url) => {
|
||||||
.map(async (url) => {
|
try {
|
||||||
try {
|
return await download(url, file.ua);
|
||||||
return await download(
|
} catch (err) {
|
||||||
url,
|
errors[url] = err;
|
||||||
file.ua,
|
$.error(
|
||||||
undefined,
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
file.proxy,
|
);
|
||||||
);
|
return '';
|
||||||
} catch (err) {
|
}
|
||||||
errors[url] = err;
|
}),
|
||||||
$.error(
|
);
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Object.keys(errors).length > 0) {
|
if (
|
||||||
if (!file.ignoreFailedRemoteFile) {
|
!file.ignoreFailedRemoteFile &&
|
||||||
throw new Error(
|
Object.keys(errors).length > 0
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
) {
|
||||||
errors,
|
throw new Error(
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
||||||
);
|
', ',
|
||||||
} else if (file.ignoreFailedRemoteFile === 'enabled') {
|
)} 发生错误, 请查看日志`,
|
||||||
$.notify(
|
);
|
||||||
`🌍 Sub-Store 预览文件失败`,
|
}
|
||||||
`❌ ${file.name}`,
|
if (file.mergeSources === 'localFirst') {
|
||||||
`远程文件 ${Object.keys(errors).join(
|
content.unshift(file.content);
|
||||||
', ',
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
)} 发生错误, 请查看日志`,
|
content.push(file.content);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file.mergeSources === 'localFirst') {
|
|
||||||
content.unshift(file.content);
|
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
|
||||||
content.push(file.content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
@ -143,22 +129,12 @@ async function compareSub(req, res) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Object.keys(errors).length > 0) {
|
if (!sub.ignoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||||
if (!sub.ignoreFailedRemoteSub) {
|
throw new Error(
|
||||||
throw new Error(
|
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
', ',
|
||||||
', ',
|
)} 发生错误, 请查看日志`,
|
||||||
)} 发生错误, 请查看日志`,
|
);
|
||||||
);
|
|
||||||
} else if (sub.ignoreFailedRemoteSub === 'enabled') {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 预览订阅失败`,
|
|
||||||
`❌ ${sub.name}`,
|
|
||||||
`远程订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (sub.mergeSources === 'localFirst') {
|
if (sub.mergeSources === 'localFirst') {
|
||||||
content.unshift(sub.content);
|
content.unshift(sub.content);
|
||||||
@ -261,25 +237,15 @@ async function compareCollection(req, res) {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
if (Object.keys(errors).length > 0) {
|
!sub.ignoreFailedRemoteSub &&
|
||||||
if (!sub.ignoreFailedRemoteSub) {
|
Object.keys(errors).length > 0
|
||||||
throw new Error(
|
) {
|
||||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
throw new Error(
|
||||||
errors,
|
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
errors,
|
||||||
);
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
} else if (
|
);
|
||||||
sub.ignoreFailedRemoteSub === 'enabled'
|
|
||||||
) {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 预览订阅失败`,
|
|
||||||
`❌ ${sub.name}`,
|
|
||||||
`远程订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (sub.mergeSources === 'localFirst') {
|
if (sub.mergeSources === 'localFirst') {
|
||||||
raw.unshift(sub.content);
|
raw.unshift(sub.content);
|
||||||
@ -311,28 +277,20 @@ async function compareCollection(req, res) {
|
|||||||
errors[name] = err;
|
errors[name] = err;
|
||||||
|
|
||||||
$.error(
|
$.error(
|
||||||
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name} 时出现错误:${err}!`,
|
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}!`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
if (Object.keys(errors).length > 0) {
|
!collection.ignoreFailedRemoteSub &&
|
||||||
if (!collection.ignoreFailedRemoteSub) {
|
Object.keys(errors).length > 0
|
||||||
throw new Error(
|
) {
|
||||||
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
|
throw new Error(
|
||||||
errors,
|
`组合订阅 ${collection.name} 中的子订阅 ${Object.keys(
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
errors,
|
||||||
);
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
} else if (collection.ignoreFailedRemoteSub === 'enabled') {
|
);
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 预览组合订阅失败`,
|
|
||||||
`❌ ${collection.name}`,
|
|
||||||
`子订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// merge proxies with the original order
|
// merge proxies with the original order
|
||||||
const original = Array.prototype.concat.apply(
|
const original = Array.prototype.concat.apply(
|
||||||
|
@ -134,15 +134,11 @@ export async function updateArtifactStore() {
|
|||||||
settings.artifactStore = url;
|
settings.artifactStore = url;
|
||||||
settings.artifactStoreStatus = 'VALID';
|
settings.artifactStoreStatus = 'VALID';
|
||||||
} else {
|
} else {
|
||||||
$.error(`找不到 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY})`);
|
$.error(`找不到 Sub-Store Gist`);
|
||||||
settings.artifactStoreStatus = 'NOT FOUND';
|
settings.artifactStoreStatus = 'NOT FOUND';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(`查找 Sub-Store Gist 时发生错误: ${err.message ?? err}`);
|
||||||
`查找 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY}) 时发生错误: ${
|
|
||||||
err.message ?? err
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
settings.artifactStoreStatus = 'ERROR';
|
settings.artifactStoreStatus = 'ERROR';
|
||||||
}
|
}
|
||||||
$.write(settings, SETTINGS_KEY);
|
$.write(settings, SETTINGS_KEY);
|
||||||
|
@ -5,12 +5,7 @@ import {
|
|||||||
RequestInvalidError,
|
RequestInvalidError,
|
||||||
} from './errors';
|
} from './errors';
|
||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import {
|
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
SUBS_KEY,
|
|
||||||
COLLECTIONS_KEY,
|
|
||||||
ARTIFACTS_KEY,
|
|
||||||
FILES_KEY,
|
|
||||||
} from '@/constants';
|
|
||||||
import {
|
import {
|
||||||
getFlowHeaders,
|
getFlowHeaders,
|
||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
@ -18,7 +13,6 @@ import {
|
|||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
import { success, failed } from './response';
|
import { success, failed } from './response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { formatDateTime } from '@/utils';
|
|
||||||
|
|
||||||
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
||||||
|
|
||||||
@ -140,7 +134,7 @@ async function getFlowInfo(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($arguments.noFlow || !/^https?/.test(url)) {
|
if ($arguments.noFlow) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new RequestInvalidError(
|
new RequestInvalidError(
|
||||||
@ -270,11 +264,7 @@ function getSubscription(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
`sub-store_subscription_${name}_${formatDateTime(
|
|
||||||
new Date(),
|
|
||||||
)}.json`,
|
|
||||||
)}"`,
|
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(sub));
|
.send(JSON.stringify(sub));
|
||||||
} else {
|
} else {
|
||||||
@ -325,20 +315,9 @@ function updateSubscription(req, res) {
|
|||||||
artifact.source = sub.name;
|
artifact.source = sub.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update all files referring this subscription
|
|
||||||
const allFiles = $.read(FILES_KEY) || [];
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (
|
|
||||||
file.sourceType === 'subscription' &&
|
|
||||||
file.sourceName == name
|
|
||||||
) {
|
|
||||||
file.sourceName = sub.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.write(allCols, COLLECTIONS_KEY);
|
$.write(allCols, COLLECTIONS_KEY);
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.write(allFiles, FILES_KEY);
|
|
||||||
}
|
}
|
||||||
updateByName(allSubs, name, newSub);
|
updateByName(allSubs, name, newSub);
|
||||||
$.write(allSubs, SUBS_KEY);
|
$.write(allSubs, SUBS_KEY);
|
||||||
|
@ -40,11 +40,10 @@ async function produceArtifact({
|
|||||||
$options,
|
$options,
|
||||||
proxy,
|
proxy,
|
||||||
noCache,
|
noCache,
|
||||||
all,
|
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
if (['subscription', 'sub'].includes(type)) {
|
if (type === 'subscription') {
|
||||||
let sub;
|
let sub;
|
||||||
if (name) {
|
if (name) {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
@ -90,23 +89,12 @@ async function produceArtifact({
|
|||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||||
}
|
}
|
||||||
|
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||||
if (Object.keys(errors).length > 0) {
|
throw new Error(
|
||||||
if (!subIgnoreFailedRemoteSub) {
|
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||||
throw new Error(
|
', ',
|
||||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
)} 发生错误, 请查看日志`,
|
||||||
', ',
|
);
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
} else if (subIgnoreFailedRemoteSub === 'enabled') {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 处理订阅失败`,
|
|
||||||
`❌ ${sub.name}`,
|
|
||||||
`远程订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (mergeSources === 'localFirst') {
|
if (mergeSources === 'localFirst') {
|
||||||
raw.unshift(content);
|
raw.unshift(content);
|
||||||
@ -150,23 +138,12 @@ async function produceArtifact({
|
|||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||||
}
|
}
|
||||||
|
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
|
||||||
if (Object.keys(errors).length > 0) {
|
throw new Error(
|
||||||
if (!subIgnoreFailedRemoteSub) {
|
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
||||||
throw new Error(
|
', ',
|
||||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
|
)} 发生错误, 请查看日志`,
|
||||||
', ',
|
);
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
} else if (subIgnoreFailedRemoteSub === 'enabled') {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 处理订阅失败`,
|
|
||||||
`❌ ${sub.name}`,
|
|
||||||
`远程订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (sub.mergeSources === 'localFirst') {
|
if (sub.mergeSources === 'localFirst') {
|
||||||
raw.unshift(sub.content);
|
raw.unshift(sub.content);
|
||||||
@ -174,9 +151,6 @@ async function produceArtifact({
|
|||||||
raw.push(sub.content);
|
raw.push(sub.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (produceType === 'raw') {
|
|
||||||
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
|
|
||||||
}
|
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let proxies = (Array.isArray(raw) ? raw : [raw])
|
let proxies = (Array.isArray(raw) ? raw : [raw])
|
||||||
.map((i) => ProxyUtils.parse(i))
|
.map((i) => ProxyUtils.parse(i))
|
||||||
@ -216,7 +190,7 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
// produce
|
// produce
|
||||||
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
|
||||||
} else if (['collection', 'col'].includes(type)) {
|
} 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);
|
||||||
@ -242,14 +216,6 @@ async function produceArtifact({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
subnames.map(async (name) => {
|
subnames.map(async (name) => {
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
const passThroughUA = sub.passThroughUA;
|
|
||||||
let reqUA = sub.ua;
|
|
||||||
if (passThroughUA) {
|
|
||||||
$.info(
|
|
||||||
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${ua}`,
|
|
||||||
);
|
|
||||||
reqUA = ua;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
$.info(`正在处理子订阅:${sub.name}...`);
|
$.info(`正在处理子订阅:${sub.name}...`);
|
||||||
let raw;
|
let raw;
|
||||||
@ -271,7 +237,7 @@ async function produceArtifact({
|
|||||||
try {
|
try {
|
||||||
return await download(
|
return await download(
|
||||||
url,
|
url,
|
||||||
reqUA,
|
sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
proxy ||
|
proxy ||
|
||||||
sub.proxy ||
|
sub.proxy ||
|
||||||
@ -290,25 +256,15 @@ async function produceArtifact({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
if (Object.keys(errors).length > 0) {
|
!sub.ignoreFailedRemoteSub &&
|
||||||
if (!sub.ignoreFailedRemoteSub) {
|
Object.keys(errors).length > 0
|
||||||
throw new Error(
|
) {
|
||||||
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
throw new Error(
|
||||||
errors,
|
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
errors,
|
||||||
);
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
} else if (
|
);
|
||||||
sub.ignoreFailedRemoteSub === 'enabled'
|
|
||||||
) {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 处理订阅失败`,
|
|
||||||
`❌ ${sub.name}`,
|
|
||||||
`远程订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (sub.mergeSources === 'localFirst') {
|
if (sub.mergeSources === 'localFirst') {
|
||||||
raw.unshift(sub.content);
|
raw.unshift(sub.content);
|
||||||
@ -363,23 +319,15 @@ async function produceArtifact({
|
|||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
if (Object.keys(errors).length > 0) {
|
!collectionIgnoreFailedRemoteSub &&
|
||||||
if (!collectionIgnoreFailedRemoteSub) {
|
Object.keys(errors).length > 0
|
||||||
throw new Error(
|
) {
|
||||||
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
|
throw new Error(
|
||||||
errors,
|
`组合订阅 ${name} 中的子订阅 ${Object.keys(errors).join(
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
', ',
|
||||||
);
|
)} 发生错误, 请查看日志`,
|
||||||
} else if (collectionIgnoreFailedRemoteSub === 'enabled') {
|
);
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 处理组合订阅失败`,
|
|
||||||
`❌ ${collection.name}`,
|
|
||||||
`子订阅 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge proxies with the original order
|
// merge proxies with the original order
|
||||||
@ -454,128 +402,106 @@ async function produceArtifact({
|
|||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (!file) throw new Error(`找不到文件 ${name}`);
|
if (!file) throw new Error(`找不到文件 ${name}`);
|
||||||
let raw = '';
|
let raw;
|
||||||
if (file.type !== 'mihomoProfile') {
|
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
||||||
|
raw = content;
|
||||||
|
} else if (url) {
|
||||||
|
const errors = {};
|
||||||
|
raw = await Promise.all(
|
||||||
|
url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
if (
|
if (
|
||||||
content &&
|
ignoreFailedRemoteFile != null &&
|
||||||
!['localFirst', 'remoteFirst'].includes(mergeSources)
|
ignoreFailedRemoteFile !== ''
|
||||||
) {
|
) {
|
||||||
raw = content;
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
} else if (url) {
|
}
|
||||||
const errors = {};
|
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
||||||
raw = await Promise.all(
|
throw new Error(
|
||||||
url
|
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
||||||
.split(/[\r\n]+/)
|
', ',
|
||||||
.map((i) => i.trim())
|
)} 发生错误, 请查看日志`,
|
||||||
.filter((i) => i.length)
|
);
|
||||||
.map(async (url) => {
|
}
|
||||||
try {
|
if (mergeSources === 'localFirst') {
|
||||||
return await download(
|
raw.unshift(content);
|
||||||
url,
|
} else if (mergeSources === 'remoteFirst') {
|
||||||
ua || file.ua,
|
raw.push(content);
|
||||||
undefined,
|
}
|
||||||
file.proxy || proxy,
|
} else if (
|
||||||
undefined,
|
file.source === 'local' &&
|
||||||
undefined,
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
noCache,
|
) {
|
||||||
);
|
raw = file.content;
|
||||||
} catch (err) {
|
} else {
|
||||||
errors[url] = err;
|
const errors = {};
|
||||||
$.error(
|
raw = await Promise.all(
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
file.url
|
||||||
);
|
.split(/[\r\n]+/)
|
||||||
return '';
|
.map((i) => i.trim())
|
||||||
}
|
.filter((i) => i.length)
|
||||||
}),
|
.map(async (url) => {
|
||||||
);
|
try {
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
return await download(
|
||||||
if (
|
url,
|
||||||
ignoreFailedRemoteFile != null &&
|
ua || file.ua,
|
||||||
ignoreFailedRemoteFile !== ''
|
undefined,
|
||||||
) {
|
file.proxy || proxy,
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
undefined,
|
||||||
}
|
undefined,
|
||||||
if (
|
noCache,
|
||||||
!fileIgnoreFailedRemoteFile &&
|
);
|
||||||
Object.keys(errors).length > 0
|
} catch (err) {
|
||||||
) {
|
errors[url] = err;
|
||||||
throw new Error(
|
$.error(
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
errors,
|
);
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
return '';
|
||||||
);
|
}
|
||||||
}
|
}),
|
||||||
if (mergeSources === 'localFirst') {
|
);
|
||||||
raw.unshift(content);
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
} else if (mergeSources === 'remoteFirst') {
|
if (
|
||||||
raw.push(content);
|
ignoreFailedRemoteFile != null &&
|
||||||
}
|
ignoreFailedRemoteFile !== ''
|
||||||
} else if (
|
) {
|
||||||
file.source === 'local' &&
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
}
|
||||||
) {
|
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
||||||
raw = file.content;
|
throw new Error(
|
||||||
} else {
|
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
||||||
const errors = {};
|
', ',
|
||||||
raw = await Promise.all(
|
)} 发生错误, 请查看日志`,
|
||||||
file.url
|
);
|
||||||
.split(/[\r\n]+/)
|
}
|
||||||
.map((i) => i.trim())
|
if (file.mergeSources === 'localFirst') {
|
||||||
.filter((i) => i.length)
|
raw.unshift(file.content);
|
||||||
.map(async (url) => {
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
try {
|
raw.push(file.content);
|
||||||
return await download(
|
|
||||||
url,
|
|
||||||
ua || file.ua,
|
|
||||||
undefined,
|
|
||||||
file.proxy || proxy,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
noCache,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
|
||||||
if (
|
|
||||||
ignoreFailedRemoteFile != null &&
|
|
||||||
ignoreFailedRemoteFile !== ''
|
|
||||||
) {
|
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(errors).length > 0) {
|
|
||||||
if (!fileIgnoreFailedRemoteFile) {
|
|
||||||
throw new Error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(
|
|
||||||
errors,
|
|
||||||
).join(', ')} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
} else if (fileIgnoreFailedRemoteFile === 'enabled') {
|
|
||||||
$.notify(
|
|
||||||
`🌍 Sub-Store 处理文件失败`,
|
|
||||||
`❌ ${file.name}`,
|
|
||||||
`远程文件 ${Object.keys(errors).join(
|
|
||||||
', ',
|
|
||||||
)} 发生错误, 请查看日志`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file.mergeSources === 'localFirst') {
|
|
||||||
raw.unshift(file.content);
|
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
|
||||||
raw.push(file.content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (produceType === 'raw') {
|
|
||||||
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
|
|
||||||
}
|
}
|
||||||
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
||||||
let filesContent = files
|
let filesContent = files
|
||||||
@ -596,7 +522,7 @@ async function produceArtifact({
|
|||||||
)
|
)
|
||||||
: { $content: filesContent, $files: files, $options };
|
: { $content: filesContent, $files: files, $options };
|
||||||
|
|
||||||
return (all ? processed : processed?.$content) ?? '';
|
return processed?.$content ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,15 +532,12 @@ async function syncArtifacts() {
|
|||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const valid = [];
|
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const subNames = [];
|
const subNames = [];
|
||||||
let enabledCount = 0;
|
|
||||||
allArtifacts.map((artifact) => {
|
allArtifacts.map((artifact) => {
|
||||||
if (artifact.sync && artifact.source) {
|
if (artifact.sync && artifact.source) {
|
||||||
enabledCount++;
|
|
||||||
if (artifact.type === 'subscription') {
|
if (artifact.type === 'subscription') {
|
||||||
const subName = artifact.source;
|
const subName = artifact.source;
|
||||||
const sub = findByName(allSubs, subName);
|
const sub = findByName(allSubs, subName);
|
||||||
@ -635,13 +558,6 @@ async function syncArtifacts() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (enabledCount === 0) {
|
|
||||||
$.info(
|
|
||||||
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subNames.length > 0) {
|
if (subNames.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
subNames.map(async (subName) => {
|
subNames.map(async (subName) => {
|
||||||
@ -690,47 +606,27 @@ async function syncArtifacts() {
|
|||||||
files[encodeURIComponent(artifact.name)] = {
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
};
|
||||||
|
|
||||||
valid.push(artifact.name);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`生成同步配置 ${artifact.name} 发生错误: ${
|
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||||
e.message ?? e
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
invalid.push(artifact.name);
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
$.info(`${valid.length} 个同步配置生成成功: ${valid.join(', ')}`);
|
if (invalid.length > 0) {
|
||||||
$.info(`${invalid.length} 个同步配置生成失败: ${invalid.join(', ')}`);
|
|
||||||
|
|
||||||
if (valid.length === 0) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`同步配置 ${invalid.join(', ')} 生成失败 详情请查看日志`,
|
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
delete body.history;
|
|
||||||
delete body.forks;
|
|
||||||
delete body.owner;
|
|
||||||
Object.values(body.files).forEach((file) => {
|
|
||||||
delete file.content;
|
|
||||||
});
|
|
||||||
$.info('上传配置响应:');
|
|
||||||
$.info(JSON.stringify(body, null, 2));
|
|
||||||
|
|
||||||
for (const artifact of allArtifacts) {
|
for (const artifact of allArtifacts) {
|
||||||
if (
|
if (artifact.sync) {
|
||||||
artifact.sync &&
|
|
||||||
artifact.source &&
|
|
||||||
valid.includes(artifact.name)
|
|
||||||
) {
|
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
// extract real url from gist
|
// extract real url from gist
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
@ -758,17 +654,9 @@ async function syncArtifacts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.info('上传配置成功');
|
$.info('全部订阅同步成功!');
|
||||||
|
|
||||||
if (invalid.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`同步配置成功 ${valid.length} 个, 失败 ${invalid.length} 个, 详情请查看日志`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$.info(`同步配置成功 ${valid.length} 个`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -778,7 +666,7 @@ async function syncAllArtifacts(_, res) {
|
|||||||
await syncArtifacts();
|
await syncArtifacts();
|
||||||
success(res);
|
success(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`同步配置失败,原因:${e.message ?? e}`);
|
$.error(`同步订阅失败,原因:${e.message ?? e}`);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
@ -855,16 +743,6 @@ async function syncArtifact(req, res) {
|
|||||||
});
|
});
|
||||||
artifact.updated = new Date().getTime();
|
artifact.updated = new Date().getTime();
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
delete body.history;
|
|
||||||
delete body.forks;
|
|
||||||
delete body.owner;
|
|
||||||
Object.values(body.files).forEach((file) => {
|
|
||||||
delete file.content;
|
|
||||||
});
|
|
||||||
$.info('上传配置响应:');
|
|
||||||
$.info(JSON.stringify(body, null, 2));
|
|
||||||
|
|
||||||
let files = body.files;
|
let files = body.files;
|
||||||
let isGitLab;
|
let isGitLab;
|
||||||
if (Array.isArray(files)) {
|
if (Array.isArray(files)) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SETTINGS_KEY, FILES_KEY, MODULES_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
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 { getPolicyDescriptor } from '@/utils';
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
@ -11,8 +11,6 @@ import {
|
|||||||
validCheck,
|
validCheck,
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { findByName } from '@/utils/database';
|
|
||||||
import { produceArtifact } from '@/restful/sync';
|
|
||||||
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
|
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
|
||||||
const clashPreprocessor = PROXY_PREPROCESSORS.find(
|
const clashPreprocessor = PROXY_PREPROCESSORS.find(
|
||||||
(processor) => processor.name === 'Clash Pre-processor',
|
(processor) => processor.name === 'Clash Pre-processor',
|
||||||
@ -132,53 +130,22 @@ export default async function download(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadUrlMatch = url
|
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
.split('#')[0]
|
// if (downloadUrlMatch) {
|
||||||
.match(/^\/api\/(file|module)\/(.+)/);
|
// let type = downloadUrlMatch?.[1];
|
||||||
if (downloadUrlMatch) {
|
// let name = downloadUrlMatch?.[2];
|
||||||
let type = '';
|
// if (name == null) {
|
||||||
try {
|
// throw new Error(`本地 ${type} URL 无效: ${url}`);
|
||||||
type = downloadUrlMatch?.[1];
|
// }
|
||||||
let name = downloadUrlMatch?.[2];
|
// name = decodeURIComponent(name);
|
||||||
if (name == null) {
|
// const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
||||||
throw new Error(`本地 ${type} URL 无效: ${url}`);
|
// const item = findByName($.read(key), name);
|
||||||
}
|
// if (!item) {
|
||||||
name = decodeURIComponent(name);
|
// throw new Error(`找不到本地 ${type}: ${name}`);
|
||||||
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
// }
|
||||||
const item = findByName($.read(key), name);
|
|
||||||
if (!item) {
|
|
||||||
throw new Error(`找不到 ${type}: ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'module') {
|
// return item.content;
|
||||||
return item.content;
|
// }
|
||||||
} else {
|
|
||||||
return await produceArtifact({
|
|
||||||
type: 'file',
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
$.error(
|
|
||||||
`Error when loading ${type}: ${
|
|
||||||
url.split('#')[0]
|
|
||||||
}.\n Reason: ${err}`,
|
|
||||||
);
|
|
||||||
throw new Error(`无法加载 ${type}: ${url}`);
|
|
||||||
}
|
|
||||||
} else if (url?.startsWith('/')) {
|
|
||||||
try {
|
|
||||||
const fs = eval(`require("fs")`);
|
|
||||||
return fs.readFileSync(url.split('#')[0], 'utf8');
|
|
||||||
} catch (err) {
|
|
||||||
$.error(
|
|
||||||
`Error when reading local file: ${
|
|
||||||
url.split('#')[0]
|
|
||||||
}.\n Reason: ${err}`,
|
|
||||||
);
|
|
||||||
throw new Error(`无法从该路径读取文本内容: ${url}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNode && tasks.has(id)) {
|
if (!isNode && tasks.has(id)) {
|
||||||
return tasks.get(id);
|
return tasks.get(id);
|
||||||
@ -306,25 +273,3 @@ export default async function download(
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadFile(url, file) {
|
|
||||||
const undici = eval("require('undici')");
|
|
||||||
const fs = eval("require('fs')");
|
|
||||||
const { pipeline } = eval("require('stream/promises')");
|
|
||||||
const { Agent, interceptors, request } = undici;
|
|
||||||
$.info(`Downloading file...\nURL: ${url}\nFile: ${file}`);
|
|
||||||
const { body, statusCode } = await request(url, {
|
|
||||||
dispatcher: new Agent().compose(
|
|
||||||
interceptors.redirect({
|
|
||||||
maxRedirections: 3,
|
|
||||||
throwOnRedirect: true,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (statusCode !== 200)
|
|
||||||
throw new Error(`Failed to download file from ${url}`);
|
|
||||||
const fileStream = fs.createWriteStream(file);
|
|
||||||
await pipeline(body, fileStream);
|
|
||||||
$.info(`File downloaded from ${url} to ${file}`);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
@ -49,7 +49,7 @@ export async function getFlowHeaders(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($arguments?.noFlow || !/^https?/.test(url)) {
|
if ($arguments?.noFlow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
@ -313,41 +313,3 @@ export function getRmainingDays(opt = {}) {
|
|||||||
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeFlowHeader(flowHeaders) {
|
|
||||||
try {
|
|
||||||
// 使用 Map 保持顺序并处理重复键
|
|
||||||
const kvMap = new Map();
|
|
||||||
|
|
||||||
flowHeaders
|
|
||||||
.split(';')
|
|
||||||
.map((p) => p.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.forEach((pair) => {
|
|
||||||
const eqIndex = pair.indexOf('=');
|
|
||||||
if (eqIndex === -1) return;
|
|
||||||
|
|
||||||
const key = pair.slice(0, eqIndex).trim();
|
|
||||||
const encodedValue = pair.slice(eqIndex + 1).trim();
|
|
||||||
|
|
||||||
// 只保留第一个出现的 key
|
|
||||||
if (!kvMap.has(key)) {
|
|
||||||
try {
|
|
||||||
// 解码 URI 组件并保留原始值作为 fallback
|
|
||||||
const decodedValue = decodeURIComponent(encodedValue);
|
|
||||||
kvMap.set(key, decodedValue);
|
|
||||||
} catch (e) {
|
|
||||||
kvMap.set(key, encodedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 拼接标准化字符串
|
|
||||||
return Array.from(kvMap.entries())
|
|
||||||
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`) // 重新编码保持兼容性
|
|
||||||
.join('; ');
|
|
||||||
} catch (e) {
|
|
||||||
$.error(`normalizeFlowHeader failed: ${e.message ?? e}`);
|
|
||||||
return flowHeaders;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,9 +18,7 @@ const ISOFlags = {
|
|||||||
'🇧🇬': ['BG', 'BGR'],
|
'🇧🇬': ['BG', 'BGR'],
|
||||||
'🇧🇭': ['BH', 'BHR'],
|
'🇧🇭': ['BH', 'BHR'],
|
||||||
'🇧🇴': ['BO', 'BOL'],
|
'🇧🇴': ['BO', 'BOL'],
|
||||||
'🇧🇳': ['BN', 'BRN'],
|
|
||||||
'🇧🇷': ['BR', 'BRA'],
|
'🇧🇷': ['BR', 'BRA'],
|
||||||
'🇧🇹': ['BT', 'BTN'],
|
|
||||||
'🇧🇾': ['BY', 'BLR'],
|
'🇧🇾': ['BY', 'BLR'],
|
||||||
'🇨🇦': ['CA', 'CAN'],
|
'🇨🇦': ['CA', 'CAN'],
|
||||||
'🇨🇭': ['CH', 'CHE'],
|
'🇨🇭': ['CH', 'CHE'],
|
||||||
@ -42,7 +40,6 @@ const ISOFlags = {
|
|||||||
'🇬🇪': ['GE', 'GEO'],
|
'🇬🇪': ['GE', 'GEO'],
|
||||||
'🇬🇷': ['GR', 'GRC'],
|
'🇬🇷': ['GR', 'GRC'],
|
||||||
'🇬🇹': ['GT', 'GTM'],
|
'🇬🇹': ['GT', 'GTM'],
|
||||||
'🇬🇺': ['GU', 'GUM'],
|
|
||||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||||
'🇭🇷': ['HR', 'HRV'],
|
'🇭🇷': ['HR', 'HRV'],
|
||||||
'🇭🇺': ['HU', 'HUN'],
|
'🇭🇺': ['HU', 'HUN'],
|
||||||
@ -62,15 +59,12 @@ const ISOFlags = {
|
|||||||
'🇮🇷': ['IR', 'IRN'],
|
'🇮🇷': ['IR', 'IRN'],
|
||||||
'🇮🇸': ['IS', 'ISL'],
|
'🇮🇸': ['IS', 'ISL'],
|
||||||
'🇮🇹': ['IT', 'ITA'],
|
'🇮🇹': ['IT', 'ITA'],
|
||||||
'🇱🇦': ['LA', 'LAO'],
|
|
||||||
'🇱🇰': ['LK', 'LKA'],
|
|
||||||
'🇱🇹': ['LT', 'LTU'],
|
'🇱🇹': ['LT', 'LTU'],
|
||||||
'🇱🇺': ['LU', 'LUX'],
|
'🇱🇺': ['LU', 'LUX'],
|
||||||
'🇱🇻': ['LV', 'LVA'],
|
'🇱🇻': ['LV', 'LVA'],
|
||||||
'🇲🇦': ['MA', 'MAR'],
|
'🇲🇦': ['MA', 'MAR'],
|
||||||
'🇲🇩': ['MD', 'MDA'],
|
'🇲🇩': ['MD', 'MDA'],
|
||||||
'🇳🇬': ['NG', 'NGA'],
|
'🇳🇬': ['NG', 'NGA'],
|
||||||
'🇲🇲': ['MM', 'MMR'],
|
|
||||||
'🇲🇰': ['MK', 'MKD'],
|
'🇲🇰': ['MK', 'MKD'],
|
||||||
'🇲🇳': ['MN', 'MNG'],
|
'🇲🇳': ['MN', 'MNG'],
|
||||||
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
||||||
@ -89,7 +83,6 @@ const ISOFlags = {
|
|||||||
'🇵🇷': ['PR', 'PRI'],
|
'🇵🇷': ['PR', 'PRI'],
|
||||||
'🇵🇹': ['PT', 'PRT'],
|
'🇵🇹': ['PT', 'PRT'],
|
||||||
'🇵🇾': ['PY', 'PRY'],
|
'🇵🇾': ['PY', 'PRY'],
|
||||||
'🇵🇬': ['PG', 'PNG'],
|
|
||||||
'🇷🇴': ['RO', 'ROU'],
|
'🇷🇴': ['RO', 'ROU'],
|
||||||
'🇷🇸': ['RS', 'SRB'],
|
'🇷🇸': ['RS', 'SRB'],
|
||||||
'🇷🇪': ['RE', 'REU'],
|
'🇷🇪': ['RE', 'REU'],
|
||||||
@ -149,10 +142,8 @@ export function getFlag(name) {
|
|||||||
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
|
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
|
||||||
'🇧🇭': ['Bahrain', '巴林'],
|
'🇧🇭': ['Bahrain', '巴林'],
|
||||||
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
||||||
'🇧🇳': ['Brunei', '文莱', '汶萊'],
|
|
||||||
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
||||||
'🇧🇴': ['Bolivia', '玻利维亚'],
|
'🇧🇴': ['Bolivia', '玻利维亚'],
|
||||||
'🇧🇹': ['Bhutan', '不丹', '不丹王国'],
|
|
||||||
'🇨🇦': [
|
'🇨🇦': [
|
||||||
'Canada',
|
'Canada',
|
||||||
'加拿大',
|
'加拿大',
|
||||||
@ -203,7 +194,6 @@ export function getFlag(name) {
|
|||||||
],
|
],
|
||||||
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
||||||
'🇬🇷': ['Greece', '希腊', '希臘'],
|
'🇬🇷': ['Greece', '希腊', '希臘'],
|
||||||
'🇬🇺': ['Guam', '关岛', '關島'],
|
|
||||||
'🇬🇹': ['Guatemala', '危地马拉'],
|
'🇬🇹': ['Guatemala', '危地马拉'],
|
||||||
'🇭🇰': [
|
'🇭🇰': [
|
||||||
'Hongkong',
|
'Hongkong',
|
||||||
@ -264,14 +254,11 @@ export function getFlag(name) {
|
|||||||
'🇮🇷': ['Iran', '伊朗'],
|
'🇮🇷': ['Iran', '伊朗'],
|
||||||
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
||||||
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
||||||
'🇱🇰': ['Sri Lanka', '斯里兰卡', '斯里蘭卡'],
|
|
||||||
'🇱🇦': ['Laos', '老挝', '老撾'],
|
|
||||||
'🇱🇹': ['Lithuania', '立陶宛'],
|
'🇱🇹': ['Lithuania', '立陶宛'],
|
||||||
'🇱🇺': ['Luxembourg', '卢森堡'],
|
'🇱🇺': ['Luxembourg', '卢森堡'],
|
||||||
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
|
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
|
||||||
'🇲🇦': ['Morocco', '摩洛哥'],
|
'🇲🇦': ['Morocco', '摩洛哥'],
|
||||||
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
|
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
|
||||||
'🇲🇲': ['Myanmar', '缅甸', '緬甸'],
|
|
||||||
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
|
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
|
||||||
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
|
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
|
||||||
'🇲🇳': ['Mongolia', '蒙古'],
|
'🇲🇳': ['Mongolia', '蒙古'],
|
||||||
@ -297,7 +284,6 @@ export function getFlag(name) {
|
|||||||
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
||||||
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
||||||
'🇵🇹': ['Portugal', '葡萄牙'],
|
'🇵🇹': ['Portugal', '葡萄牙'],
|
||||||
'🇵🇬': ['Papua New Guinea', '巴布亚新几内亚'],
|
|
||||||
'🇵🇾': ['Paraguay', '巴拉圭'],
|
'🇵🇾': ['Paraguay', '巴拉圭'],
|
||||||
'🇷🇴': ['Romania', '罗马尼亚'],
|
'🇷🇴': ['Romania', '罗马尼亚'],
|
||||||
'🇷🇸': ['Serbia', '塞尔维亚'],
|
'🇷🇸': ['Serbia', '塞尔维亚'],
|
||||||
|
@ -118,7 +118,6 @@ export default class Gist {
|
|||||||
.get('/gists?per_page=100&page=1')
|
.get('/gists?per_page=100&page=1')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const gists = JSON.parse(response.body);
|
const gists = JSON.parse(response.body);
|
||||||
$.info(`获取到当前 GitHub 用户的 gist: ${gists.length} 个`);
|
|
||||||
for (let g of gists) {
|
for (let g of gists) {
|
||||||
if (g.description === this.key) {
|
if (g.description === this.key) {
|
||||||
return g;
|
return g;
|
||||||
@ -280,7 +279,7 @@ export default class Gist {
|
|||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(`找不到 Sub-Store Gist (${this.key})`);
|
return Promise.reject('找不到 Sub-Store Gist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,42 +117,7 @@ function numberToString(value) {
|
|||||||
: BigInt(value).toString();
|
: BigInt(value).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidUUID(uuid) {
|
|
||||||
return (
|
|
||||||
typeof uuid === 'string' &&
|
|
||||||
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
|
||||||
uuid,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDateTime(date, format = 'YYYY-MM-DD_HH-mm-ss') {
|
|
||||||
const d = date instanceof Date ? date : new Date(date);
|
|
||||||
|
|
||||||
if (isNaN(d.getTime())) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const pad = (num) => String(num).padStart(2, '0');
|
|
||||||
|
|
||||||
const replacements = {
|
|
||||||
YYYY: d.getFullYear(),
|
|
||||||
MM: pad(d.getMonth() + 1),
|
|
||||||
DD: pad(d.getDate()),
|
|
||||||
HH: pad(d.getHours()),
|
|
||||||
mm: pad(d.getMinutes()),
|
|
||||||
ss: pad(d.getSeconds()),
|
|
||||||
};
|
|
||||||
|
|
||||||
return format.replace(
|
|
||||||
/YYYY|MM|DD|HH|mm|ss/g,
|
|
||||||
(match) => replacements[match],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
formatDateTime,
|
|
||||||
isValidUUID,
|
|
||||||
ipAddress,
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
|
@ -4,8 +4,6 @@ import {
|
|||||||
SCHEMA_VERSION_KEY,
|
SCHEMA_VERSION_KEY,
|
||||||
ARTIFACTS_KEY,
|
ARTIFACTS_KEY,
|
||||||
RULES_KEY,
|
RULES_KEY,
|
||||||
FILES_KEY,
|
|
||||||
TOKENS_KEY,
|
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
@ -57,17 +55,7 @@ function doMigrationV2() {
|
|||||||
const newRules = Object.values(rules);
|
const newRules = Object.values(rules);
|
||||||
$.write(newRules, RULES_KEY);
|
$.write(newRules, RULES_KEY);
|
||||||
|
|
||||||
// 5. migrate files
|
// 5. delete builtin rules
|
||||||
const files = $.read(FILES_KEY) || {};
|
|
||||||
const newFiles = Object.values(files);
|
|
||||||
$.write(newFiles, FILES_KEY);
|
|
||||||
|
|
||||||
// 6. migrate tokens
|
|
||||||
const tokens = $.read(TOKENS_KEY) || {};
|
|
||||||
const newTokens = Object.values(tokens);
|
|
||||||
$.write(newTokens, TOKENS_KEY);
|
|
||||||
|
|
||||||
// 7. delete builtin rules
|
|
||||||
delete $.cache.builtin;
|
delete $.cache.builtin;
|
||||||
$.info('Migration complete!');
|
$.info('Migration complete!');
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class ResourceCache {
|
|||||||
this._cleanup();
|
this._cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanup(prefix, expires) {
|
_cleanup() {
|
||||||
// clear obsolete cached resource
|
// clear obsolete cached resource
|
||||||
let clear = false;
|
let clear = false;
|
||||||
Object.entries(this.resourceCache).forEach((entry) => {
|
Object.entries(this.resourceCache).forEach((entry) => {
|
||||||
@ -35,11 +35,7 @@ class ResourceCache {
|
|||||||
$.delete(`#${id}`);
|
$.delete(`#${id}`);
|
||||||
clear = true;
|
clear = true;
|
||||||
}
|
}
|
||||||
if (
|
if (new Date().getTime() - updated.time > this.expires) {
|
||||||
new Date().getTime() - updated.time >
|
|
||||||
(expires ?? this.expires) ||
|
|
||||||
(prefix && id.startsWith(prefix))
|
|
||||||
) {
|
|
||||||
delete this.resourceCache[id];
|
delete this.resourceCache[id];
|
||||||
clear = true;
|
clear = true;
|
||||||
}
|
}
|
||||||
@ -56,15 +52,10 @@ class ResourceCache {
|
|||||||
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
|
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id, expires, remove) {
|
get(id) {
|
||||||
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||||
if (updated) {
|
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||||
if (new Date().getTime() - updated <= (expires ?? this.expires))
|
return this.resourceCache[id].data;
|
||||||
return this.resourceCache[id].data;
|
|
||||||
if (remove) {
|
|
||||||
delete this.resourceCache[id];
|
|
||||||
this._persist();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import gte from 'semver/functions/gte';
|
|
||||||
import coerce from 'semver/functions/coerce';
|
|
||||||
import $ from '@/core/app';
|
|
||||||
|
|
||||||
export function getUserAgentFromHeaders(headers) {
|
export function getUserAgentFromHeaders(headers) {
|
||||||
const keys = Object.keys(headers);
|
const keys = Object.keys(headers);
|
||||||
let UA = '';
|
let UA = '';
|
||||||
@ -47,7 +43,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) {
|
|||||||
return 'Clash';
|
return 'Clash';
|
||||||
} else if (ua.indexOf('v2ray') !== -1) {
|
} else if (ua.indexOf('v2ray') !== -1) {
|
||||||
return 'V2Ray';
|
return 'V2Ray';
|
||||||
} else if (ua.indexOf('sing-box') !== -1 || ua.indexOf('singbox') !== -1) {
|
} else if (ua.indexOf('sing-box') !== -1) {
|
||||||
return 'sing-box';
|
return 'sing-box';
|
||||||
} else if (accept.indexOf('application/json') === 0) {
|
} else if (accept.indexOf('application/json') === 0) {
|
||||||
return 'JSON';
|
return 'JSON';
|
||||||
@ -60,42 +56,3 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
||||||
return getPlatformFromUserAgent({ ua, UA, accept });
|
return getPlatformFromUserAgent({ ua, UA, accept });
|
||||||
}
|
}
|
||||||
export function shouldIncludeUnsupportedProxy(platform, ua) {
|
|
||||||
// try {
|
|
||||||
// const target = getPlatformFromUserAgent({
|
|
||||||
// UA: ua,
|
|
||||||
// ua: ua.toLowerCase(),
|
|
||||||
// });
|
|
||||||
// if (!['Stash', 'Egern', 'Loon'].includes(target)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// const coerceVersion = coerce(ua);
|
|
||||||
// $.log(JSON.stringify(coerceVersion, null, 2));
|
|
||||||
// const { version } = coerceVersion;
|
|
||||||
// if (
|
|
||||||
// platform === 'Stash' &&
|
|
||||||
// target === 'Stash' &&
|
|
||||||
// gte(version, '3.1.0')
|
|
||||||
// ) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// if (
|
|
||||||
// platform === 'Egern' &&
|
|
||||||
// target === 'Egern' &&
|
|
||||||
// gte(version, '1.29.0')
|
|
||||||
// ) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// // Loon 的 UA 不规范, version 取出来是 build
|
|
||||||
// if (
|
|
||||||
// platform === 'Loon' &&
|
|
||||||
// target === 'Loon' &&
|
|
||||||
// gte(version, '842.0.0')
|
|
||||||
// ) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// $.error(`获取版本号失败: ${e}`);
|
|
||||||
// }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
@ -34,6 +34,4 @@ export default {
|
|||||||
load,
|
load,
|
||||||
safeDump,
|
safeDump,
|
||||||
dump,
|
dump,
|
||||||
parse: safeLoad,
|
|
||||||
stringify: safeDump,
|
|
||||||
};
|
};
|
||||||
|
11
backend/src/vendor/express.js
vendored
11
backend/src/vendor/express.js
vendored
@ -17,14 +17,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
const express_ = eval(`require("express")`);
|
const express_ = eval(`require("express")`);
|
||||||
const bodyParser = eval(`require("body-parser")`);
|
const bodyParser = eval(`require("body-parser")`);
|
||||||
const app = express_();
|
const app = express_();
|
||||||
const limit = eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb';
|
app.use(bodyParser.json({ verify: rawBodySaver, limit: '1mb' }));
|
||||||
$.info(`[BACKEND] body JSON limit: ${limit}`);
|
|
||||||
app.use(
|
|
||||||
bodyParser.json({
|
|
||||||
verify: rawBodySaver,
|
|
||||||
limit,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
app.use(
|
app.use(
|
||||||
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
|
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
|
||||||
);
|
);
|
||||||
@ -38,7 +31,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
app.start = () => {
|
app.start = () => {
|
||||||
const listener = app.listen(port, host, () => {
|
const listener = app.listen(port, host, () => {
|
||||||
const { address, port } = listener.address();
|
const { address, port } = listener.address();
|
||||||
$.info(`[BACKEND] listening on ${address}:${port}`);
|
$.info(`[BACKEND] ${address}:${port}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return app;
|
return app;
|
||||||
|
187
backend/src/vendor/open-api.js
vendored
187
backend/src/vendor/open-api.js
vendored
@ -10,14 +10,6 @@ const isEgern = 'object' == typeof egern;
|
|||||||
const isLanceX = 'undefined' != typeof $native;
|
const isLanceX = 'undefined' != typeof $native;
|
||||||
const isGUIforCores = typeof $Plugins !== 'undefined';
|
const isGUIforCores = typeof $Plugins !== 'undefined';
|
||||||
|
|
||||||
function isPlainObject(obj) {
|
|
||||||
return (
|
|
||||||
obj !== null &&
|
|
||||||
typeof obj === 'object' &&
|
|
||||||
[null, Object.prototype].includes(Object.getPrototypeOf(obj))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OpenAPI {
|
export class OpenAPI {
|
||||||
constructor(name = 'untitled', debug = false) {
|
constructor(name = 'untitled', debug = false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -26,10 +18,6 @@ export class OpenAPI {
|
|||||||
this.http = HTTP();
|
this.http = HTTP();
|
||||||
this.env = ENV();
|
this.env = ENV();
|
||||||
|
|
||||||
if (isNode) {
|
|
||||||
const dotenv = eval(`require("dotenv")`);
|
|
||||||
dotenv.config();
|
|
||||||
}
|
|
||||||
this.node = (() => {
|
this.node = (() => {
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const fs = eval("require('fs')");
|
const fs = eval("require('fs')");
|
||||||
@ -70,50 +58,29 @@ export class OpenAPI {
|
|||||||
const basePath =
|
const basePath =
|
||||||
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
let rootPath = `${basePath}/root.json`;
|
let rootPath = `${basePath}/root.json`;
|
||||||
const backupRootPath = `${basePath}/root_${Date.now()}.json`;
|
|
||||||
|
|
||||||
this.log(`Root path: ${rootPath}`);
|
this.log(`Root path: ${rootPath}`);
|
||||||
if (this.node.fs.existsSync(rootPath)) {
|
if (!this.node.fs.existsSync(rootPath)) {
|
||||||
try {
|
|
||||||
this.root = JSON.parse(
|
|
||||||
this.node.fs.readFileSync(`${rootPath}`),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
this.node.fs.copyFileSync(rootPath, backupRootPath);
|
|
||||||
this.error(
|
|
||||||
`Failed to parse ${rootPath}: ${e.message}. Backup created at ${backupRootPath}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isPlainObject(this.root)) {
|
|
||||||
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
|
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
|
||||||
flag: 'w',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.root = {};
|
this.root = {};
|
||||||
|
} else {
|
||||||
|
this.root = JSON.parse(
|
||||||
|
this.node.fs.readFileSync(`${rootPath}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a json file with the given name if not exists
|
// create a json file with the given name if not exists
|
||||||
let fpath = `${basePath}/${this.name}.json`;
|
let fpath = `${basePath}/${this.name}.json`;
|
||||||
const backupPath = `${basePath}/${this.name}_${Date.now()}.json`;
|
|
||||||
|
|
||||||
this.log(`Data path: ${fpath}`);
|
this.log(`Data path: ${fpath}`);
|
||||||
if (this.node.fs.existsSync(fpath)) {
|
if (!this.node.fs.existsSync(fpath)) {
|
||||||
try {
|
|
||||||
this.cache = JSON.parse(
|
|
||||||
this.node.fs.readFileSync(`${fpath}`),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
this.node.fs.copyFileSync(fpath, backupPath);
|
|
||||||
this.error(
|
|
||||||
`Failed to parse ${fpath}: ${e.message}. Backup created at ${backupPath}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isPlainObject(this.cache)) {
|
|
||||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
||||||
flag: 'w',
|
flag: 'wx',
|
||||||
});
|
});
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
|
} else {
|
||||||
|
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,111 +337,45 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
opts: options.opts,
|
opts: options.opts,
|
||||||
});
|
});
|
||||||
} else if (isLoon || isSurge || isNode) {
|
} else if (isLoon || isSurge || isNode) {
|
||||||
worker = new Promise(async (resolve, reject) => {
|
worker = new Promise((resolve, reject) => {
|
||||||
|
const request = isNode
|
||||||
|
? eval("require('request')")
|
||||||
|
: $httpClient;
|
||||||
const body = options.body;
|
const body = options.body;
|
||||||
const opts = JSON.parse(JSON.stringify(options));
|
const opts = JSON.parse(JSON.stringify(options));
|
||||||
opts.body = body;
|
opts.body = body;
|
||||||
opts.timeout = opts.timeout || 8000;
|
|
||||||
if (opts.timeout) {
|
|
||||||
opts.timeout++;
|
|
||||||
if (isNaN(opts.timeout)) {
|
|
||||||
opts.timeout = 8000;
|
|
||||||
}
|
|
||||||
if (!isNode) {
|
|
||||||
let unit = 'ms';
|
|
||||||
// 这些客户端单位为 s
|
|
||||||
if (isSurge || isStash || isShadowRocket) {
|
|
||||||
opts.timeout = Math.ceil(opts.timeout / 1000);
|
|
||||||
unit = 's';
|
|
||||||
}
|
|
||||||
// Loon 为 ms
|
|
||||||
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNode) {
|
|
||||||
const undici = eval("require('undici')");
|
|
||||||
const {
|
|
||||||
ProxyAgent,
|
|
||||||
EnvHttpProxyAgent,
|
|
||||||
request,
|
|
||||||
interceptors,
|
|
||||||
} = undici;
|
|
||||||
const agentOpts = {
|
|
||||||
connect: {
|
|
||||||
rejectUnauthorized:
|
|
||||||
opts.strictSSL === false ||
|
|
||||||
opts.insecure === true
|
|
||||||
? false
|
|
||||||
: true,
|
|
||||||
},
|
|
||||||
bodyTimeout: opts.timeout,
|
|
||||||
headersTimeout: opts.timeout,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const url = new URL(opts.url);
|
|
||||||
if (url.username || url.password) {
|
|
||||||
opts.headers = {
|
|
||||||
...(opts.headers || {}),
|
|
||||||
Authorization: `Basic ${Buffer.from(
|
|
||||||
`${url.username || ''}:${
|
|
||||||
url.password || ''
|
|
||||||
}`,
|
|
||||||
).toString('base64')}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await request(opts.url, {
|
|
||||||
...opts,
|
|
||||||
method: method.toUpperCase(),
|
|
||||||
dispatcher: (opts.proxy
|
|
||||||
? new ProxyAgent({
|
|
||||||
...agentOpts,
|
|
||||||
uri: opts.proxy,
|
|
||||||
})
|
|
||||||
: new EnvHttpProxyAgent(agentOpts)
|
|
||||||
).compose(
|
|
||||||
interceptors.redirect({
|
|
||||||
maxRedirections: 3,
|
|
||||||
throwOnMaxRedirects: true,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
resolve({
|
|
||||||
statusCode: response.statusCode,
|
|
||||||
headers: response.headers,
|
|
||||||
body:
|
|
||||||
opts.encoding === null
|
|
||||||
? await response.body.arrayBuffer()
|
|
||||||
: await response.body.text(),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$httpClient[method.toLowerCase()](
|
|
||||||
opts,
|
|
||||||
(err, response, body) => {
|
|
||||||
// if (err) {
|
|
||||||
// console.log(err);
|
|
||||||
// } else {
|
|
||||||
// console.log({
|
|
||||||
// statusCode:
|
|
||||||
// response.status || response.statusCode,
|
|
||||||
// headers: response.headers,
|
|
||||||
// body,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (err) reject(err);
|
if (!isNode && opts.timeout) {
|
||||||
else
|
opts.timeout++;
|
||||||
resolve({
|
let unit = 'ms';
|
||||||
statusCode:
|
// 这些客户端单位为 s
|
||||||
response.status || response.statusCode,
|
if (isSurge || isStash || isShadowRocket) {
|
||||||
headers: response.headers,
|
opts.timeout = Math.ceil(opts.timeout / 1000);
|
||||||
body,
|
unit = 's';
|
||||||
});
|
}
|
||||||
},
|
// Loon 为 ms
|
||||||
);
|
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
|
||||||
}
|
}
|
||||||
|
request[method.toLowerCase()](opts, (err, response, body) => {
|
||||||
|
// if (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// } else {
|
||||||
|
// console.log({
|
||||||
|
// statusCode:
|
||||||
|
// response.status || response.statusCode,
|
||||||
|
// headers: response.headers,
|
||||||
|
// body,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (err) reject(err);
|
||||||
|
else
|
||||||
|
resolve({
|
||||||
|
statusCode: response.status || response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else if (isGUIforCores) {
|
} else if (isGUIforCores) {
|
||||||
worker = new Promise(async (resolve, reject) => {
|
worker = new Promise(async (resolve, reject) => {
|
||||||
|
@ -13,13 +13,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名
|
// 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名
|
||||||
// 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名
|
// 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名
|
||||||
// 7. `tls-fingerprint` 为 tls 指纹
|
// 7. `tls-fingerprint` 为 tls 指纹
|
||||||
// 8. `underlying-proxy` 为前置代理, 不同平台会自动转换
|
// 8. `underlying-proxy` 为前置代理
|
||||||
// 例如 $server['underlying-proxy'] = '名称'
|
|
||||||
// 只给 mihomo 输出的话, `dialer-proxy` 也行
|
|
||||||
// 只给 sing-box 输出的话, `detour` 也行
|
|
||||||
// 只给 Egern 输出的话, `prev_hop` 也行
|
|
||||||
// 只给 Shadowrocket 输出的话, `chain` 也行
|
|
||||||
// 输出到 Clash/Stash 时, 会过滤掉配置了前置代理的节点, 并提示使用对应的功能.
|
|
||||||
// 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除
|
// 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除
|
||||||
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
// 10. `sni` 在某些协议里会自动与 `servername` 转换
|
||||||
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
|
||||||
@ -28,13 +22,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||||
// 17. `block-quic` 支持 `auto`, `on`, `off`. 不同的平台不一定都支持, 会自动转换
|
|
||||||
|
|
||||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||||
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
|
|
||||||
// const fs = eval(`require("fs")`)
|
|
||||||
// // const path = eval(`require("path")`)
|
|
||||||
// fs.writeFileSync('/tmp/1.txt', $content, "utf8");
|
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@ -47,27 +36,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 先这样处理 encodeURIComponent('arg1=a&arg2=b')
|
// 先这样处理 encodeURIComponent('arg1=a&arg2=b')
|
||||||
// /api/file/foo?$options=arg1%3Da%26arg2%3Db
|
// /api/file/foo?$options=arg1%3Da%26arg2%3Db
|
||||||
|
|
||||||
// 默认会带上 _req 字段, 结构为
|
|
||||||
// {
|
|
||||||
// method,
|
|
||||||
// url,
|
|
||||||
// path,
|
|
||||||
// query,
|
|
||||||
// params,
|
|
||||||
// headers,
|
|
||||||
// body,
|
|
||||||
// }
|
|
||||||
// console.log($options)
|
// console.log($options)
|
||||||
|
|
||||||
// 若设置 $options._res.headers
|
|
||||||
// 则会在输出文件时设置响应头, 例如:
|
|
||||||
|
|
||||||
// $options._res = {
|
|
||||||
// headers: {
|
|
||||||
// 'X-Custom': '1'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// targetPlatform 为输出的目标平台
|
// targetPlatform 为输出的目标平台
|
||||||
|
|
||||||
// lodash
|
// lodash
|
||||||
@ -78,38 +48,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// scriptResourceCache 缓存
|
// scriptResourceCache 缓存
|
||||||
// 可参考 https://t.me/zhetengsha/1003
|
// 可参考 https://t.me/zhetengsha/1003
|
||||||
// const cache = scriptResourceCache
|
// const cache = scriptResourceCache
|
||||||
// 设置
|
// cache.set(id, data)
|
||||||
// cache.set('a:1', 1)
|
// cache.get(id)
|
||||||
// cache.set('a:2', 2)
|
|
||||||
// 获取
|
|
||||||
// cache.get('a:1')
|
|
||||||
// 支持第二个参数: 自定义过期时间
|
|
||||||
// 支持第三个参数: 是否删除过期项
|
|
||||||
// cache.get('a:2', 1000, true)
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
// cache._cleanup()
|
|
||||||
// 支持第一个参数: 匹配前缀的项也一起删除
|
|
||||||
// 支持第二个参数: 自定义过期时间
|
|
||||||
// cache._cleanup('a:', 1000)
|
|
||||||
|
|
||||||
// 关于缓存时长
|
|
||||||
|
|
||||||
// 拉取 Sub-Store 订阅时, 会自动拉取远程订阅
|
|
||||||
|
|
||||||
// 远程订阅缓存是 1 小时, 缓存的唯一 key 为 url+ user agent. 可通过前端的刷新按钮刷新缓存. 或使用参数 noCache 来禁用缓存. 例: 内部配置订阅链接时使用 http://a.com#noCache, 外部使用 sub-store 链接时使用 https://sub.store/download/1?noCache=true
|
|
||||||
|
|
||||||
// 当使用相关脚本时, 若在对应的脚本中使用参数开启缓存, 可设置持久化缓存 sub-store-csr-expiration-time 的值来自定义默认缓存时长, 默认为 172800000 (48 * 3600 * 1000, 即 48 小时)
|
|
||||||
|
|
||||||
// 🎈Loon 可在插件中设置
|
|
||||||
|
|
||||||
// 其他平台同理, 持久化缓存数据在 JSON 里
|
|
||||||
|
|
||||||
// 当配合脚本使用时, 可以在脚本的前面添加一个脚本操作, 实现保留 1 小时的缓存. 这样比较灵活
|
|
||||||
|
|
||||||
// async function operator() {
|
|
||||||
// scriptResourceCache._cleanup(undefined, 1 * 3600 * 1000);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ProxyUtils 为节点处理工具
|
// ProxyUtils 为节点处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/1066
|
// 可参考 https://t.me/zhetengsha/1066
|
||||||
@ -128,13 +68,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||||
// Gist, // Gist 类
|
// Gist, // Gist 类
|
||||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||||
// downloadFile, // 下载二进制文件, 见 backend/src/utils/download.js
|
|
||||||
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||||
// isValidUUID, // 辅助判断是否为有效的 UUID
|
|
||||||
// Buffer, // https://github.com/feross/buffer
|
|
||||||
// Base64, // https://github.com/dankogai/js-base64
|
|
||||||
// }
|
// }
|
||||||
// 为兼容 https://github.com/xishang0128/sparkle 的 JavaScript 覆写, 也可以直接使用 `b64d`(Base64 解码), `b64e`(Base64 编码), `Buffer`, `yaml`(简单兼容了下 `yaml.parse` 和 `yaml.stringify`)
|
|
||||||
|
|
||||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||||
// ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一
|
// ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 129 KiB |
Loading…
x
Reference in New Issue
Block a user