feat: add GUI support for AnyTLS/Mieru/Sudoku and AnyTLS URI parsing

This commit is contained in:
Slinetrac 2025-12-30 15:12:09 +08:00
parent c80c659180
commit 772b87e733
No known key found for this signature in database
5 changed files with 167 additions and 5 deletions

View File

@ -18,6 +18,7 @@
- 支持收起导航栏(导航栏右键菜单 / 界面设置)
- 允许将出站模式显示在托盘一级菜单
- 允许禁用在托盘中显示代理组
- 支持在「编辑节点」中直接导入 AnyTLS URI 配置
</details>
@ -31,5 +32,6 @@
- 改进托盘和窗口操作频率限制实现
- 使用「编辑节点」添加节点时,自动将节点添加到第一个 `select` 类型的代理组的第一位
- 隐藏侧边导航栏和悬浮跳转导航的滚动条
- 完善对 AnyTLS / Mieru / Sudoku 的 GUI 支持
</details>

8
pnpm-lock.yaml generated
View File

@ -130,7 +130,7 @@ importers:
version: 2.3.8(react@19.2.3)
tauri-plugin-mihomo-api:
specifier: github:clash-verge-rev/tauri-plugin-mihomo#main
version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/153f16f7b3f979aa130a2d3d7c39a52a39987288
version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a0e5095c1516229e8816096ae7017f950e6200dd
types-pac:
specifier: ^1.0.3
version: 1.0.3
@ -3876,8 +3876,8 @@ packages:
resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==}
engines: {node: '>=18'}
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/153f16f7b3f979aa130a2d3d7c39a52a39987288:
resolution: {tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/153f16f7b3f979aa130a2d3d7c39a52a39987288}
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a0e5095c1516229e8816096ae7017f950e6200dd:
resolution: {tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a0e5095c1516229e8816096ae7017f950e6200dd}
version: 0.1.0
terser@5.44.1:
@ -8398,7 +8398,7 @@ snapshots:
minizlib: 3.1.0
yallist: 5.0.0
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/153f16f7b3f979aa130a2d3d7c39a52a39987288:
tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/a0e5095c1516229e8816096ae7017f950e6200dd:
dependencies:
'@tauri-apps/api': 2.9.1

View File

@ -828,6 +828,9 @@ export const GroupsEditorViewer = (props: Props) => {
"Hysteria2",
"WireGuard",
"Tuic",
"Mieru",
"AnyTLS",
"Sudoku",
"Relay",
"Selector",
"Fallback",

75
src/types/global.d.ts vendored
View File

@ -429,6 +429,16 @@ type CipherType =
| "aez-384"
| "deoxys-ii-256-128"
| "rc4-md5";
type MieruTransport = "TCP" | "UDP";
type MieruMultiplexing =
| "MULTIPLEXING_OFF"
| "MULTIPLEXING_LOW"
| "MULTIPLEXING_MIDDLE"
| "MULTIPLEXING_HIGH";
type SudokuAeadMethod = "chacha20-poly1305" | "aes-128-gcm" | "none";
type SudokuTableType = "prefer_ascii" | "prefer_entropy";
type SudokuHttpMaskMode = "legacy" | "stream" | "poll" | "auto";
type SudokuHttpMaskStrategy = "random" | "post" | "websocket";
// base
interface IProxyBaseConfig {
tfo?: boolean;
@ -513,6 +523,29 @@ interface IProxyTrojanConfig extends IProxyBaseConfig {
};
"client-fingerprint"?: ClientFingerprint;
}
// anytls
interface IProxyAnyTLSConfig extends IProxyBaseConfig {
name: string;
type: "anytls";
server?: string;
port?: number;
password?: string;
alpn?: string[];
sni?: string;
"client-fingerprint"?: ClientFingerprint;
"skip-cert-verify"?: boolean;
fingerprint?: string;
certificate?: string;
"private-key"?: string;
"ech-opts"?: {
enable?: boolean;
config?: string;
};
udp?: boolean;
"idle-session-check-interval"?: number;
"idle-session-timeout"?: number;
"min-idle-session"?: number;
}
// tuic
interface IProxyTuicConfig extends IProxyBaseConfig {
name: string;
@ -546,6 +579,20 @@ interface IProxyTuicConfig extends IProxyBaseConfig {
"udp-over-stream"?: boolean;
"udp-over-stream-version"?: number;
}
// mieru
interface IProxyMieruConfig extends IProxyBaseConfig {
name: string;
type: "mieru";
server?: string;
port?: number;
"port-range"?: string;
transport?: MieruTransport;
udp?: boolean;
username?: string;
password?: string;
multiplexing?: MieruMultiplexing;
"handshake-mode"?: string;
}
// vless
interface IProxyVlessConfig extends IProxyBaseConfig {
name: string;
@ -714,6 +761,26 @@ interface IProxyShadowsocksConfig extends IProxyBaseConfig {
"client-fingerprint"?: ClientFingerprint;
smux?: boolean;
}
// sudoku
interface IProxySudokuConfig extends IProxyBaseConfig {
name: string;
type: "sudoku";
server?: string;
port?: number;
key?: string;
"aead-method"?: SudokuAeadMethod;
"padding-min"?: number;
"padding-max"?: number;
"table-type"?: SudokuTableType;
"enable-pure-downlink"?: boolean;
"http-mask"?: boolean;
"http-mask-mode"?: SudokuHttpMaskMode;
"http-mask-tls"?: boolean;
"http-mask-host"?: string;
"http-mask-strategy"?: SudokuHttpMaskStrategy;
"custom-table"?: string;
"custom-tables"?: string[];
}
// shadowsocksR
interface IProxyshadowsocksRConfig extends IProxyBaseConfig {
name: string;
@ -765,13 +832,16 @@ interface IProxyConfig
IProxySocks5Config,
IProxySshConfig,
IProxyTrojanConfig,
IProxyAnyTLSConfig,
IProxyTuicConfig,
IProxyMieruConfig,
IProxyVlessConfig,
IProxyVmessConfig,
IProxyWireguardConfig,
IProxyHysteriaConfig,
IProxyHysteria2Config,
IProxyShadowsocksConfig,
IProxySudokuConfig,
IProxyshadowsocksRConfig,
IProxySmuxConfig,
IProxySnellConfig {
@ -783,6 +853,7 @@ interface IProxyConfig
| "snell"
| "http"
| "trojan"
| "anytls"
| "hysteria"
| "hysteria2"
| "tuic"
@ -790,7 +861,9 @@ interface IProxyConfig
| "ssh"
| "socks5"
| "vmess"
| "vless";
| "vless"
| "mieru"
| "sudoku";
}
interface IVergeConfig {

View File

@ -8,6 +8,7 @@ const URI_PARSERS: Record<string, UriParser> = {
vmess: URI_VMESS,
vless: URI_VLESS,
trojan: URI_Trojan,
anytls: URI_AnyTLS,
hysteria2: URI_Hysteria2,
hy2: URI_Hysteria2,
hysteria: URI_Hysteria,
@ -1071,6 +1072,89 @@ function URI_Trojan(line: string): IProxyTrojanConfig {
return proxy;
}
function URI_AnyTLS(line: string): IProxyAnyTLSConfig {
const afterScheme = stripUriScheme(line, "anytls", "Invalid anytls uri");
if (!afterScheme) {
throw new Error("Invalid anytls uri");
}
const {
auth: authRaw,
host: server,
port,
query: addons,
fragment: nameRaw,
} = parseUrlLike(afterScheme, {
errorMessage: "Invalid anytls uri",
});
if (!server) {
throw new Error("Invalid anytls uri");
}
const portNum = parsePortOrDefault(port, 443);
const auth = safeDecodeURIComponent(authRaw) ?? authRaw;
const decodedName = decodeAndTrim(nameRaw);
const name = decodedName ?? `AnyTLS ${server}:${portNum}`;
const proxy: IProxyAnyTLSConfig = {
type: "anytls",
name,
server,
port: portNum,
udp: true,
};
if (auth) {
const [username, password] = splitOnce(auth, ":");
proxy.password = password ?? username;
}
const params = parseQueryStringNormalized(addons);
if (params.sni) {
proxy.sni = params.sni;
}
if (params.alpn) {
const alpn = params.alpn
.split(",")
.map((item) => item.trim())
.filter(Boolean);
if (alpn.length > 0) {
proxy.alpn = alpn;
}
}
const fingerprint = params.fingerprint ?? params.hpkp;
if (fingerprint) {
proxy.fingerprint = fingerprint;
}
const clientFingerprint = params["client-fingerprint"] ?? params.fp;
if (clientFingerprint) {
proxy["client-fingerprint"] = clientFingerprint as ClientFingerprint;
}
if (Object.prototype.hasOwnProperty.call(params, "skip-cert-verify")) {
proxy["skip-cert-verify"] = parseBoolOrPresence(params["skip-cert-verify"]);
} else if (Object.prototype.hasOwnProperty.call(params, "insecure")) {
proxy["skip-cert-verify"] = parseBoolOrPresence(params.insecure);
}
if (Object.prototype.hasOwnProperty.call(params, "udp")) {
proxy.udp = parseBoolOrPresence(params.udp);
}
const idleCheck = parseInteger(params["idle-session-check-interval"]);
if (idleCheck !== undefined) {
proxy["idle-session-check-interval"] = idleCheck;
}
const idleTimeout = parseInteger(params["idle-session-timeout"]);
if (idleTimeout !== undefined) {
proxy["idle-session-timeout"] = idleTimeout;
}
const minIdle = parseInteger(params["min-idle-session"]);
if (minIdle !== undefined) {
proxy["min-idle-session"] = minIdle;
}
return proxy;
}
function URI_Hysteria2(line: string): IProxyHysteria2Config {
const afterScheme = stripUriScheme(
line,