Update On Tue Feb 24 20:15:31 CET 2026

This commit is contained in:
github-action[bot]
2026-02-24 20:15:32 +01:00
parent b2811c9322
commit c3862442db
99 changed files with 2665 additions and 2748 deletions
+1
View File
@@ -1281,3 +1281,4 @@ Update On Fri Feb 20 20:00:01 CET 2026
Update On Sat Feb 21 19:48:30 CET 2026
Update On Sun Feb 22 19:49:09 CET 2026
Update On Mon Feb 23 20:25:31 CET 2026
Update On Tue Feb 24 20:15:23 CET 2026
+1 -3
View File
@@ -2,7 +2,7 @@
A cross-platform programmable network tool.
**Sponsor**: [Shiliew - A network app designed for those who value their time](https://www.txthinking.com/shiliew.html)
**Sponsor**: [Shiliew - Focuses on providing stable network services](https://www.txthinking.com/shiliew.html)
## Server
@@ -25,11 +25,9 @@ brook server -l :9999 -p hello
- [macOS](https://apps.apple.com/us/app/brook-network-tool/id1216002642)
- [About App Mode on macOS](https://www.txthinking.com/talks/articles/macos-app-mode-en.article)
- [Windows](https://github.com/txthinking/brook/releases/latest/download/Brook.msix)
- [How to install Brook on Windows](https://www.txthinking.com/talks/articles/msix-brook-en.article)
- [Linux](https://github.com/txthinking/brook/releases/latest/download/Brook.bin)
- [How to install Brook on Linux](https://www.txthinking.com/talks/articles/linux-app-brook-en.article)
- [OpenWrt](https://www.txthinking.com/talks/articles/brook-openwrt-one-en.article)
- [How to install Brook on OpenWrt](https://www.txthinking.com/talks/articles/brook-openwrt-en.article)
> You may want to use `brook link` to customize some parameters
+38 -54
View File
@@ -355,7 +355,7 @@ dependencies = [
"objc2-foundation 0.3.2",
"parking_lot",
"percent-encoding",
"windows-sys 0.52.0",
"windows-sys 0.60.2",
"wl-clipboard-rs",
"x11rb",
]
@@ -869,16 +869,6 @@ dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"serde",
"unty",
]
[[package]]
name = "bindgen"
version = "0.69.5"
@@ -1454,9 +1444,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.43"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -1542,7 +1532,6 @@ dependencies = [
"backon",
"base64 0.22.1",
"bimap",
"bincode 2.0.1",
"boa_engine",
"boa_utils",
"bumpalo",
@@ -1603,6 +1592,7 @@ dependencies = [
"parking_lot",
"percent-encoding",
"port_scanner",
"postcard",
"pretty_assertions",
"rand 0.9.2",
"rayon",
@@ -2852,7 +2842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -4511,7 +4501,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1700f6b8b9f00cdd675f32fbb3a5be882213140dfe045805273221ca266c43f8"
dependencies = [
"bincode 1.3.3",
"bincode",
"crossbeam-channel",
"fnv",
"libc",
@@ -4875,7 +4865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.53.3",
]
[[package]]
@@ -6368,9 +6358,9 @@ dependencies = [
[[package]]
name = "oxc_allocator"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cb3d0de8b7b87a52e7beafeb468c46c93076253dcac3db533afb94b3535896"
checksum = "e16d4295cf7888893b80ae70ff65c078ae3f9f52d5381cfc7eeffab089e07305"
dependencies = [
"allocator-api2",
"hashbrown 0.16.1",
@@ -6380,9 +6370,9 @@ dependencies = [
[[package]]
name = "oxc_ast"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c6aa4589029b2417a3d65f785d4b170629c202e575ab4a82127e9df4b90e4f"
checksum = "be755331a7de00100c60e03151663f26037a0dd720be238de57c036be03b4033"
dependencies = [
"bitflags 2.10.0",
"oxc_allocator",
@@ -6397,9 +6387,9 @@ dependencies = [
[[package]]
name = "oxc_ast_macros"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5953ad22a1d96f822a0b8bbf0b1d1dffba1e443444bd47a94b5efa956b68322f"
checksum = "a13a58adcfaadd4710b4f7d80ad422599ed5bb4956f4747d07e821c5897b16ef"
dependencies = [
"phf 0.13.1",
"proc-macro2",
@@ -6409,9 +6399,9 @@ dependencies = [
[[package]]
name = "oxc_ast_visit"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7d629ff31889443dd2b088cf3d7fbdd3b3179123c5111a0e93e408c400a932f"
checksum = "4e33ffb874949ea07fce9b686c2dba7e221c849e047232c04a84b13bae4496ef"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -6421,15 +6411,15 @@ dependencies = [
[[package]]
name = "oxc_data_structures"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d94ddeaf5d5e740c49fcd0eb8a2d40c7fa4038cd26f337f11e062a03520312"
checksum = "fd6c22a48542899e5f74162d55710ea2f95735c5d3a809196308b2dbf557f434"
[[package]]
name = "oxc_diagnostics"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287468f0f872e1a09bc81a2f6f1c05ecda4cb981274c00240c7dd5b3ba6340b9"
checksum = "fe5961a78ce2a24d288f5e7090f19ce49d062486e0d65e6140d01582198c94fd"
dependencies = [
"cow-utils",
"oxc-miette",
@@ -6438,9 +6428,9 @@ dependencies = [
[[package]]
name = "oxc_ecmascript"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3cbaa77c89b35825549a062a41753bd4a8e880956dccf3383dcd8e7f3bf246"
checksum = "e1fb3d121c372df31514f95d87c92693001739d2c9e56be37909499b5396faf1"
dependencies = [
"cow-utils",
"num-bigint",
@@ -6454,9 +6444,9 @@ dependencies = [
[[package]]
name = "oxc_estree"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "137da13e23d3848d25d4686f7fe73d1f7204c53c16adba856cb45e5c39f6f4fb"
checksum = "d38fc12975751e104dc53c369cba1598ff15aa8ca30aaac49e63937256316969"
[[package]]
name = "oxc_index"
@@ -6470,9 +6460,9 @@ dependencies = [
[[package]]
name = "oxc_parser"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3594e45daea7d4224221d28566bda905349f780266839794bfbd262dbfde0f11"
checksum = "341602ba5eb6629f7f90e49c1fce06bb8eed989412a4178acd8e7f48cf2c7f9d"
dependencies = [
"bitflags 2.10.0",
"cow-utils",
@@ -6493,9 +6483,9 @@ dependencies = [
[[package]]
name = "oxc_regular_expression"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5860fbe229c1d54556aa5701fd565fc55c85f9846aaa815a8498c6614fab06bb"
checksum = "8e810182cbde172aeada70acc45dae74f6773384e0d295cc27e6e377b1fc277c"
dependencies = [
"bitflags 2.10.0",
"oxc_allocator",
@@ -6509,9 +6499,9 @@ dependencies = [
[[package]]
name = "oxc_span"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76e84bd5f00dc82cf22484b31faec3d746dcae953fd926f36aed33b48c6940aa"
checksum = "b9999ef787b0b989b8c2b31669069d3bdca20d017ff34a7284ff9e983cf7b1d8"
dependencies = [
"compact_str",
"oxc-miette",
@@ -6523,9 +6513,9 @@ dependencies = [
[[package]]
name = "oxc_str"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11cbab64e0ec125ce0d763ab802b5396f23eca11b0bab5334d4e39f0e60d3b0a"
checksum = "a6fde66bc256ea0d09895c2a56a24f79e76abffd977f6c171516e42f1efdea51"
dependencies = [
"compact_str",
"hashbrown 0.16.1",
@@ -6535,9 +6525,9 @@ dependencies = [
[[package]]
name = "oxc_syntax"
version = "0.114.0"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ab18263d6fa008a34217a3710b79a3a269f223df98081c75eb816bf3a0cca"
checksum = "e77ea5bd4ea42ce05b2f51bcef8e46a4598cea5038ab25877a2d27601a90da83"
dependencies = [
"bitflags 2.10.0",
"cow-utils",
@@ -7288,7 +7278,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -8931,18 +8921,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -10531,12 +10521,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "ureq"
version = "2.12.1"
+8 -11
View File
@@ -56,7 +56,7 @@ itertools = "0.14" # swee
rayon = "1.10" # for iterator parallel processing
ambassador = "0.5.0" # for trait delegation
derive_builder = "0.20" # for builder pattern
strum = { version = "0.27", features = ["derive"] } # for enum string conversion
strum = { version = "0.28", features = ["derive"] } # for enum string conversion
atomic_enum = "0.3.0" # for atomic enum
enumflags2 = "0.7" # for enum flags
backon = { version = "1.0.1", features = ["tokio-sleep"] } # for backoff retry
@@ -100,10 +100,7 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
serde_yaml = { version = "0.10", package = "serde_yaml_ng", branch = "feat/specta-update", git = "https://github.com/libnyanpasu/serde-yaml-ng.git", features = [
"specta",
] }
bincode = { version = "2.0.1", default-features = false, features = [
"serde",
"std",
] }
postcard = { version = "1.1", features = ["alloc"] }
bytes = { version = "1", features = ["serde"] }
semver = "1.0"
@@ -172,12 +169,12 @@ display-info = "0.5.0" # should be removed after upgrading to tauri v2
# OXC (The Oxidation Compiler)
# We use it to parse and transpile the old script profile to esm based script profile
oxc_parser = "0.114"
oxc_allocator = "0.114"
oxc_span = "0.114"
oxc_ast = "0.114"
oxc_syntax = "0.114"
oxc_ast_visit = "0.114"
oxc_parser = "0.115"
oxc_allocator = "0.115"
oxc_span = "0.115"
oxc_ast = "0.115"
oxc_syntax = "0.115"
oxc_ast_visit = "0.115"
# Lua Integration
mlua = { version = "0.11", features = [
@@ -52,15 +52,14 @@ impl TryFrom<CacheFile<'static>> for (HeaderValue, Bytes) {
// TODO: use Reader instead of Vec
async fn read_cache_file(path: &Path) -> Result<(HeaderValue, Bytes)> {
let cache_file = tokio::fs::read(path).await?;
let (cache_file, _): (CacheFile<'static>, _) =
bincode::serde::decode_from_slice(&cache_file, bincode::config::standard())?;
let cache_file: CacheFile<'static> = postcard::from_bytes(&cache_file)?;
cache_file.try_into()
}
// TODO: use Writer instead of Vec
async fn write_cache_file(path: &Path, cache_file: &CacheFile<'_>) -> Result<()> {
let mut file = tokio::fs::File::create(path).await?;
let cache_file = bincode::serde::encode_to_vec(cache_file, bincode::config::standard())?;
let cache_file = postcard::to_allocvec(cache_file)?;
file.write_all(&cache_file).await?;
Ok(())
}
@@ -34,9 +34,9 @@
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tooltip": "1.2.8",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@tailwindcss/postcss": "4.2.0",
"@tailwindcss/postcss": "4.2.1",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.18",
"@tanstack/react-virtual": "3.13.19",
"@tanstack/router-zod-adapter": "1.81.5",
"@tauri-apps/api": "2.10.1",
"@types/json-schema": "7.0.15",
@@ -74,7 +74,7 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.441",
"@iconify/json": "2.2.442",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.90.21",
"@tanstack/react-router": "1.161.4",
+1 -1
View File
@@ -29,7 +29,7 @@
"react-error-boundary": "6.0.0",
"react-i18next": "15.7.4",
"react-use": "17.6.0",
"tailwindcss": "4.2.0",
"tailwindcss": "4.2.1",
"vite": "7.3.1",
"vite-tsconfig-paths": "6.1.1"
},
+2 -2
View File
@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.20",
"mihomo_alpha": "alpha-3752cb0",
"mihomo_alpha": "Not Found",
"clash_rs": "v0.9.4",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.4-alpha+sha.c39252b"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2026-02-22T22:22:22.101Z"
"updated_at": "2026-02-23T22:26:50.433Z"
}
+3 -3
View File
@@ -71,7 +71,7 @@
"knip": "5.85.0",
"lint-staged": "16.2.7",
"npm-run-all2": "8.0.4",
"oxlint": "1.49.0",
"oxlint": "1.50.0",
"postcss": "8.5.6",
"postcss-html": "1.8.1",
"postcss-import": "16.1.1",
@@ -87,11 +87,11 @@
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
"stylelint-order": "7.0.1",
"stylelint-scss": "7.0.0",
"tailwindcss": "4.2.0",
"tailwindcss": "4.2.1",
"tsx": "4.21.0",
"typescript": "5.9.3"
},
"packageManager": "pnpm@10.30.1",
"packageManager": "pnpm@10.30.2",
"engines": {
"node": "24.13.1"
},
+165 -165
View File
@@ -68,8 +68,8 @@ importers:
specifier: 8.0.4
version: 8.0.4
oxlint:
specifier: 1.49.0
version: 1.49.0
specifier: 1.50.0
version: 1.50.0
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -116,8 +116,8 @@ importers:
specifier: 7.0.0
version: 7.0.0(stylelint@17.3.0(typescript@5.9.3))
tailwindcss:
specifier: 4.2.0
version: 4.2.0
specifier: 4.2.1
version: 4.2.1
tsx:
specifier: 4.21.0
version: 4.21.0
@@ -234,14 +234,14 @@ importers:
specifier: 1.2.2
version: 1.2.2(@types/react@19.2.14)(react@19.2.4)
'@tailwindcss/postcss':
specifier: 4.2.0
version: 4.2.0
specifier: 4.2.1
version: 4.2.1
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-virtual':
specifier: 3.13.18
version: 3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
specifier: 3.13.19
version: 3.13.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-zod-adapter':
specifier: 1.81.5
version: 1.81.5(@tanstack/react-router@1.161.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)
@@ -349,8 +349,8 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.2.14)(react@19.2.4)
'@iconify/json':
specifier: 2.2.441
version: 2.2.441
specifier: 2.2.442
version: 2.2.442
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -520,8 +520,8 @@ importers:
specifier: 17.6.0
version: 17.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
tailwindcss:
specifier: 4.2.0
version: 4.2.0
specifier: 4.2.1
version: 4.2.1
vite:
specifier: 7.3.1
version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
@@ -1683,8 +1683,8 @@ packages:
prettier-plugin-ember-template-tag:
optional: true
'@iconify/json@2.2.441':
resolution: {integrity: sha512-SN7n/3vboWO7A2iTV/t5RP5Hp0dSJkwpeQ/FsksN3sV8BsV0WuAhJp0n4HmXoAD+Icu0/cM07a8ijt+qSDf5Cg==}
'@iconify/json@2.2.442':
resolution: {integrity: sha512-JvAEHLuOu9aS1Ch0kLCs6hlltRUFrs7JVD0Ic6Xfxj/JOkJCCwW0dRCHILEwkWFXrKTmDAP5XTUsCItsfYxdkg==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2407,124 +2407,124 @@ packages:
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.49.0':
resolution: {integrity: sha512-2WPoh/2oK9r/i2R4o4J18AOrm3HVlWiHZ8TnuCaS4dX8m5ZzRmHW0I3eLxEurQLHWVruhQN7fHgZnah+ag5iQg==}
'@oxlint/binding-android-arm-eabi@1.50.0':
resolution: {integrity: sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxlint/binding-android-arm64@1.49.0':
resolution: {integrity: sha512-YqJAGvNB11EzoKm1euVhZntb79alhMvWW/j12bYqdvVxn6xzEQWrEDCJg9BPo3A3tBCSUBKH7bVkAiCBqK/L1w==}
'@oxlint/binding-android-arm64@1.50.0':
resolution: {integrity: sha512-GeSuMoJWCVpovJi/e3xDSNgjeR8WEZ6MCXL6EtPiCIM2NTzv7LbflARINTXTJy2oFBYyvdf/l2PwHzYo6EdXvg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxlint/binding-darwin-arm64@1.49.0':
resolution: {integrity: sha512-WFocCRlvVkMhChCJ2qpJfp1Gj/IjvyjuifH9Pex8m8yHonxxQa3d8DZYreuDQU3T4jvSY8rqhoRqnpc61Nlbxw==}
'@oxlint/binding-darwin-arm64@1.50.0':
resolution: {integrity: sha512-w3SY5YtxGnxCHPJ8Twl3KmS9oja1gERYk3AMoZ7Hv8P43ZtB6HVfs02TxvarxfL214Tm3uzvc2vn+DhtUNeKnw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxlint/binding-darwin-x64@1.49.0':
resolution: {integrity: sha512-BN0KniwvehbUfYztOMwEDkYoojGm/narf5oJf+/ap+6PnzMeWLezMaVARNIS0j3OdMkjHTEP8s3+GdPJ7WDywQ==}
'@oxlint/binding-darwin-x64@1.50.0':
resolution: {integrity: sha512-hNfogDqy7tvmllXKBSlHo6k5x7dhTUVOHbMSE15CCAcXzmqf5883aPvBYPOq9AE7DpDUQUZ1kVE22YbiGW+tuw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxlint/binding-freebsd-x64@1.49.0':
resolution: {integrity: sha512-SnkAc/DPIY6joMCiP/+53Q+N2UOGMU6ULvbztpmvPJNF/jYPGhNbKtN982uj2Gs6fpbxYkmyj08QnpkD4fbHJA==}
'@oxlint/binding-freebsd-x64@1.50.0':
resolution: {integrity: sha512-ykZevOWEyu0nsxolA911ucxpEv0ahw8jfEeGWOwwb/VPoE4xoexuTOAiPNlWZNJqANlJl7yp8OyzCtXTUAxotw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxlint/binding-linux-arm-gnueabihf@1.49.0':
resolution: {integrity: sha512-6Z3EzRvpQVIpO7uFhdiGhdE8Mh3S2VWKLL9xuxVqD6fzPhyI3ugthpYXlCChXzO8FzcYIZ3t1+Kau+h2NY1hqA==}
'@oxlint/binding-linux-arm-gnueabihf@1.50.0':
resolution: {integrity: sha512-hif3iDk7vo5GGJ4OLCCZAf2vjnU9FztGw4L0MbQL0M2iY9LKFtDMMiQAHmkF0PQGQMVbTYtPdXCLKVgdkiqWXQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm-musleabihf@1.49.0':
resolution: {integrity: sha512-wdjXaQYAL/L25732mLlngfst4Jdmi/HLPVHb3yfCoP5mE3lO/pFFrmOJpqWodgv29suWY74Ij+RmJ/YIG5VuzQ==}
'@oxlint/binding-linux-arm-musleabihf@1.50.0':
resolution: {integrity: sha512-dVp9iSssiGAnTNey2Ruf6xUaQhdnvcFOJyRWd/mu5o2jVbFK15E5fbWGeFRfmuobu5QXuROtFga44+7DOS3PLg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm64-gnu@1.49.0':
resolution: {integrity: sha512-oSHpm8zmSvAG1BWUumbDRSg7moJbnwoEXKAkwDf/xTQJOzvbUknq95NVQdw/AduZr5dePftalB8rzJNGBogUMg==}
'@oxlint/binding-linux-arm64-gnu@1.50.0':
resolution: {integrity: sha512-1cT7yz2HA910CKA9NkH1ZJo50vTtmND2fkoW1oyiSb0j6WvNtJ0Wx2zoySfXWc/c+7HFoqRK5AbEoL41LOn9oA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-arm64-musl@1.49.0':
resolution: {integrity: sha512-xeqkMOARgGBlEg9BQuPDf6ZW711X6BT5qjDyeM5XNowCJeTSdmMhpePJjTEiVbbr3t21sIlK8RE6X5bc04nWyQ==}
'@oxlint/binding-linux-arm64-musl@1.50.0':
resolution: {integrity: sha512-++B3k/HEPFVlj89cOz8kWfQccMZB/aWL9AhsW7jPIkG++63Mpwb2cE9XOEsd0PATbIan78k2Gky+09uWM1d/gQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-ppc64-gnu@1.49.0':
resolution: {integrity: sha512-uvcqRO6PnlJGbL7TeePhTK5+7/JXbxGbN+C6FVmfICDeeRomgQqrfVjf0lUrVpUU8ii8TSkIbNdft3M+oNlOsQ==}
'@oxlint/binding-linux-ppc64-gnu@1.50.0':
resolution: {integrity: sha512-Z9b/KpFMkx66w3gVBqjIC1AJBTZAGoI9+U+K5L4QM0CB/G0JSNC1es9b3Y0Vcrlvtdn8A+IQTkYjd/Q0uCSaZw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-gnu@1.49.0':
resolution: {integrity: sha512-Dw1HkdXAwHNH+ZDserHP2RzXQmhHtpsYYI0hf8fuGAVCIVwvS6w1+InLxpPMY25P8ASRNiFN3hADtoh6lI+4lg==}
'@oxlint/binding-linux-riscv64-gnu@1.50.0':
resolution: {integrity: sha512-jvmuIw8wRSohsQlFNIST5uUwkEtEJmOQYr33bf/K2FrFPXHhM4KqGekI3ShYJemFS/gARVacQFgBzzJKCAyJjg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-musl@1.49.0':
resolution: {integrity: sha512-EPlMYaA05tJ9km/0dI9K57iuMq3Tw+nHst7TNIegAJZrBPtsOtYaMFZEaWj02HA8FI5QvSnRHMt+CI+RIhXJBQ==}
'@oxlint/binding-linux-riscv64-musl@1.50.0':
resolution: {integrity: sha512-x+UrN47oYNh90nmAAyql8eQaaRpHbDPu5guasDg10+OpszUQ3/1+1J6zFMmV4xfIEgTcUXG/oI5fxJhF4eWCNA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-s390x-gnu@1.49.0':
resolution: {integrity: sha512-yZiQL9qEwse34aMbnMb5VqiAWfDY+fLFuoJbHOuzB1OaJZbN1MRF9Nk+W89PIpGr5DNPDipwjZb8+Q7wOywoUQ==}
'@oxlint/binding-linux-s390x-gnu@1.50.0':
resolution: {integrity: sha512-i/JLi2ljLUIVfekMj4ISmdt+Hn11wzYUdRRrkVUYsCWw7zAy5xV7X9iA+KMyM156LTFympa7s3oKBjuCLoTAUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-gnu@1.49.0':
resolution: {integrity: sha512-CcCDwMMXSchNkhdgvhVn3DLZ4EnBXAD8o8+gRzahg+IdSt/72y19xBgShJgadIRF0TsRcV/MhDUMwL5N/W54aQ==}
'@oxlint/binding-linux-x64-gnu@1.50.0':
resolution: {integrity: sha512-/C7brhn6c6UUPccgSPCcpLQXcp+xKIW/3sji/5VZ8/OItL3tQ2U7KalHz887UxxSQeEOmd1kY6lrpuwFnmNqOA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-musl@1.49.0':
resolution: {integrity: sha512-u3HfKV8BV6t6UCCbN0RRiyqcymhrnpunVmLFI8sEa5S/EBu+p/0bJ3D7LZ2KT6PsBbrB71SWq4DeFrskOVgIZg==}
'@oxlint/binding-linux-x64-musl@1.50.0':
resolution: {integrity: sha512-oDR1f+bGOYU8LfgtEW8XtotWGB63ghtcxk5Jm6IDTCk++rTA/IRMsjOid2iMd+1bW+nP9Mdsmcdc7VbPD3+iyQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxlint/binding-openharmony-arm64@1.49.0':
resolution: {integrity: sha512-dRDpH9fw+oeUMpM4br0taYCFpW6jQtOuEIec89rOgDA1YhqwmeRcx0XYeCv7U48p57qJ1XZHeMGM9LdItIjfzA==}
'@oxlint/binding-openharmony-arm64@1.50.0':
resolution: {integrity: sha512-4CmRGPp5UpvXyu4jjP9Tey/SrXDQLRvZXm4pb4vdZBxAzbFZkCyh0KyRy4txld/kZKTJlW4TO8N1JKrNEk+mWw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxlint/binding-win32-arm64-msvc@1.49.0':
resolution: {integrity: sha512-6rrKe/wL9tn0qnOy76i1/0f4Dc3dtQnibGlU4HqR/brVHlVjzLSoaH0gAFnLnznh9yQ6gcFTBFOPrcN/eKPDGA==}
'@oxlint/binding-win32-arm64-msvc@1.50.0':
resolution: {integrity: sha512-Fq0M6vsGcFsSfeuWAACDhd5KJrO85ckbEfe1EGuBj+KPyJz7KeWte2fSFrFGmNKNXyhEMyx4tbgxiWRujBM2KQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxlint/binding-win32-ia32-msvc@1.49.0':
resolution: {integrity: sha512-CXHLWAtLs2xG/aVy1OZiYJzrULlq0QkYpI6cd7VKMrab+qur4fXVE/B1Bp1m0h1qKTj5/FTGg6oU4qaXMjS/ug==}
'@oxlint/binding-win32-ia32-msvc@1.50.0':
resolution: {integrity: sha512-qTdWR9KwY/vxJGhHVIZG2eBOhidOQvOwzDxnX+jhW/zIVacal1nAhR8GLkiywW8BIFDkQKXo/zOfT+/DY+ns/w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxlint/binding-win32-x64-msvc@1.49.0':
resolution: {integrity: sha512-VteIelt78kwzSglOozaQcs6BCS4Lk0j+QA+hGV0W8UeyaqQ3XpbZRhDU55NW1PPvCy1tg4VXsTlEaPovqto7nQ==}
'@oxlint/binding-win32-x64-msvc@1.50.0':
resolution: {integrity: sha512-682t7npLC4G2Ca+iNlI9fhAKTcFPYYXJjwoa88H4q+u5HHHlsnL/gHULapX3iqp+A8FIJbgdylL5KMYo2LaluQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -3377,69 +3377,69 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
'@tailwindcss/node@4.2.0':
resolution: {integrity: sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==}
'@tailwindcss/node@4.2.1':
resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
'@tailwindcss/oxide-android-arm64@4.2.0':
resolution: {integrity: sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==}
'@tailwindcss/oxide-android-arm64@4.2.1':
resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.2.0':
resolution: {integrity: sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==}
'@tailwindcss/oxide-darwin-arm64@4.2.1':
resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.2.0':
resolution: {integrity: sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==}
'@tailwindcss/oxide-darwin-x64@4.2.1':
resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.2.0':
resolution: {integrity: sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==}
'@tailwindcss/oxide-freebsd-x64@4.2.1':
resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0':
resolution: {integrity: sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==}
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.2.0':
resolution: {integrity: sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==}
'@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.2.0':
resolution: {integrity: sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==}
'@tailwindcss/oxide-linux-arm64-musl@4.2.1':
resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.2.0':
resolution: {integrity: sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==}
'@tailwindcss/oxide-linux-x64-gnu@4.2.1':
resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.2.0':
resolution: {integrity: sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==}
'@tailwindcss/oxide-linux-x64-musl@4.2.1':
resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.2.0':
resolution: {integrity: sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==}
'@tailwindcss/oxide-wasm32-wasi@4.2.1':
resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
@@ -3450,24 +3450,24 @@ packages:
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.2.0':
resolution: {integrity: sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==}
'@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.2.0':
resolution: {integrity: sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==}
'@tailwindcss/oxide-win32-x64-msvc@4.2.1':
resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.2.0':
resolution: {integrity: sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==}
'@tailwindcss/oxide@4.2.1':
resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==}
engines: {node: '>= 20'}
'@tailwindcss/postcss@4.2.0':
resolution: {integrity: sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==}
'@tailwindcss/postcss@4.2.1':
resolution: {integrity: sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==}
'@tanstack/history@1.161.4':
resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==}
@@ -3517,8 +3517,8 @@ packages:
react: '>=16.8'
react-dom: '>=16.8'
'@tanstack/react-virtual@3.13.18':
resolution: {integrity: sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==}
'@tanstack/react-virtual@3.13.19':
resolution: {integrity: sha512-KzwmU1IbE0IvCZSm6OXkS+kRdrgW2c2P3Ho3NC+zZXWK6oObv/L+lcV/2VuJ+snVESRlMJ+w/fg4WXI/JzoNGQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -3586,8 +3586,8 @@ packages:
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
'@tanstack/virtual-core@3.13.18':
resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==}
'@tanstack/virtual-core@3.13.19':
resolution: {integrity: sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g==}
'@tanstack/virtual-core@3.13.9':
resolution: {integrity: sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==}
@@ -6084,8 +6084,8 @@ packages:
oxc-resolver@11.16.1:
resolution: {integrity: sha512-eN5tLRx2oyyNpPyWWItuPiNLY/EpGlqXVJll+Uptds4owQ4UQK3T8rPlR+wl8xBKvQZM4drFC1nKVLeZDUWpQg==}
oxlint@1.49.0:
resolution: {integrity: sha512-YZffp0gM+63CJoRhHjtjRnwKtAgUnXM6j63YQ++aigji2NVvLGsUlrXo9gJUXZOdcbfShLYtA6RuTu8GZ4lzOQ==}
oxlint@1.50.0:
resolution: {integrity: sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -7095,8 +7095,8 @@ packages:
tailwind-merge@3.5.0:
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
tailwindcss@4.2.0:
resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==}
tailwindcss@4.2.1:
resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
tapable@2.3.0:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
@@ -8887,7 +8887,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.441':
'@iconify/json@2.2.442':
dependencies:
'@iconify/types': 2.0.0
pathe: 2.0.3
@@ -9619,61 +9619,61 @@ snapshots:
'@oxc-resolver/binding-win32-x64-msvc@11.16.1':
optional: true
'@oxlint/binding-android-arm-eabi@1.49.0':
'@oxlint/binding-android-arm-eabi@1.50.0':
optional: true
'@oxlint/binding-android-arm64@1.49.0':
'@oxlint/binding-android-arm64@1.50.0':
optional: true
'@oxlint/binding-darwin-arm64@1.49.0':
'@oxlint/binding-darwin-arm64@1.50.0':
optional: true
'@oxlint/binding-darwin-x64@1.49.0':
'@oxlint/binding-darwin-x64@1.50.0':
optional: true
'@oxlint/binding-freebsd-x64@1.49.0':
'@oxlint/binding-freebsd-x64@1.50.0':
optional: true
'@oxlint/binding-linux-arm-gnueabihf@1.49.0':
'@oxlint/binding-linux-arm-gnueabihf@1.50.0':
optional: true
'@oxlint/binding-linux-arm-musleabihf@1.49.0':
'@oxlint/binding-linux-arm-musleabihf@1.50.0':
optional: true
'@oxlint/binding-linux-arm64-gnu@1.49.0':
'@oxlint/binding-linux-arm64-gnu@1.50.0':
optional: true
'@oxlint/binding-linux-arm64-musl@1.49.0':
'@oxlint/binding-linux-arm64-musl@1.50.0':
optional: true
'@oxlint/binding-linux-ppc64-gnu@1.49.0':
'@oxlint/binding-linux-ppc64-gnu@1.50.0':
optional: true
'@oxlint/binding-linux-riscv64-gnu@1.49.0':
'@oxlint/binding-linux-riscv64-gnu@1.50.0':
optional: true
'@oxlint/binding-linux-riscv64-musl@1.49.0':
'@oxlint/binding-linux-riscv64-musl@1.50.0':
optional: true
'@oxlint/binding-linux-s390x-gnu@1.49.0':
'@oxlint/binding-linux-s390x-gnu@1.50.0':
optional: true
'@oxlint/binding-linux-x64-gnu@1.49.0':
'@oxlint/binding-linux-x64-gnu@1.50.0':
optional: true
'@oxlint/binding-linux-x64-musl@1.49.0':
'@oxlint/binding-linux-x64-musl@1.50.0':
optional: true
'@oxlint/binding-openharmony-arm64@1.49.0':
'@oxlint/binding-openharmony-arm64@1.50.0':
optional: true
'@oxlint/binding-win32-arm64-msvc@1.49.0':
'@oxlint/binding-win32-arm64-msvc@1.50.0':
optional: true
'@oxlint/binding-win32-ia32-msvc@1.49.0':
'@oxlint/binding-win32-ia32-msvc@1.50.0':
optional: true
'@oxlint/binding-win32-x64-msvc@1.49.0':
'@oxlint/binding-win32-x64-msvc@1.50.0':
optional: true
'@parcel/watcher-android-arm64@2.4.1':
@@ -10432,7 +10432,7 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
'@tailwindcss/node@4.2.0':
'@tailwindcss/node@4.2.1':
dependencies:
'@jridgewell/remapping': 2.3.5
enhanced-resolve: 5.19.0
@@ -10440,66 +10440,66 @@ snapshots:
lightningcss: 1.31.1
magic-string: 0.30.21
source-map-js: 1.2.1
tailwindcss: 4.2.0
tailwindcss: 4.2.1
'@tailwindcss/oxide-android-arm64@4.2.0':
'@tailwindcss/oxide-android-arm64@4.2.1':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.2.0':
'@tailwindcss/oxide-darwin-arm64@4.2.1':
optional: true
'@tailwindcss/oxide-darwin-x64@4.2.0':
'@tailwindcss/oxide-darwin-x64@4.2.1':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.2.0':
'@tailwindcss/oxide-freebsd-x64@4.2.1':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0':
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.2.0':
'@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.2.0':
'@tailwindcss/oxide-linux-arm64-musl@4.2.1':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.2.0':
'@tailwindcss/oxide-linux-x64-gnu@4.2.1':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.2.0':
'@tailwindcss/oxide-linux-x64-musl@4.2.1':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.2.0':
'@tailwindcss/oxide-wasm32-wasi@4.2.1':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.2.0':
'@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.2.0':
'@tailwindcss/oxide-win32-x64-msvc@4.2.1':
optional: true
'@tailwindcss/oxide@4.2.0':
'@tailwindcss/oxide@4.2.1':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.2.0
'@tailwindcss/oxide-darwin-arm64': 4.2.0
'@tailwindcss/oxide-darwin-x64': 4.2.0
'@tailwindcss/oxide-freebsd-x64': 4.2.0
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.0
'@tailwindcss/oxide-linux-arm64-gnu': 4.2.0
'@tailwindcss/oxide-linux-arm64-musl': 4.2.0
'@tailwindcss/oxide-linux-x64-gnu': 4.2.0
'@tailwindcss/oxide-linux-x64-musl': 4.2.0
'@tailwindcss/oxide-wasm32-wasi': 4.2.0
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.0
'@tailwindcss/oxide-win32-x64-msvc': 4.2.0
'@tailwindcss/oxide-android-arm64': 4.2.1
'@tailwindcss/oxide-darwin-arm64': 4.2.1
'@tailwindcss/oxide-darwin-x64': 4.2.1
'@tailwindcss/oxide-freebsd-x64': 4.2.1
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1
'@tailwindcss/oxide-linux-arm64-gnu': 4.2.1
'@tailwindcss/oxide-linux-arm64-musl': 4.2.1
'@tailwindcss/oxide-linux-x64-gnu': 4.2.1
'@tailwindcss/oxide-linux-x64-musl': 4.2.1
'@tailwindcss/oxide-wasm32-wasi': 4.2.1
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
'@tailwindcss/postcss@4.2.0':
'@tailwindcss/postcss@4.2.1':
dependencies:
'@alloc/quick-lru': 5.2.0
'@tailwindcss/node': 4.2.0
'@tailwindcss/oxide': 4.2.0
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
postcss: 8.5.6
tailwindcss: 4.2.0
tailwindcss: 4.2.1
'@tanstack/history@1.161.4': {}
@@ -10549,9 +10549,9 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@tanstack/react-virtual@3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
'@tanstack/react-virtual@3.13.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@tanstack/virtual-core': 3.13.18
'@tanstack/virtual-core': 3.13.19
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
@@ -10637,7 +10637,7 @@ snapshots:
'@tanstack/table-core@8.21.3': {}
'@tanstack/virtual-core@3.13.18': {}
'@tanstack/virtual-core@3.13.19': {}
'@tanstack/virtual-core@3.13.9': {}
@@ -13338,27 +13338,27 @@ snapshots:
'@oxc-resolver/binding-win32-ia32-msvc': 11.16.1
'@oxc-resolver/binding-win32-x64-msvc': 11.16.1
oxlint@1.49.0:
oxlint@1.50.0:
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.49.0
'@oxlint/binding-android-arm64': 1.49.0
'@oxlint/binding-darwin-arm64': 1.49.0
'@oxlint/binding-darwin-x64': 1.49.0
'@oxlint/binding-freebsd-x64': 1.49.0
'@oxlint/binding-linux-arm-gnueabihf': 1.49.0
'@oxlint/binding-linux-arm-musleabihf': 1.49.0
'@oxlint/binding-linux-arm64-gnu': 1.49.0
'@oxlint/binding-linux-arm64-musl': 1.49.0
'@oxlint/binding-linux-ppc64-gnu': 1.49.0
'@oxlint/binding-linux-riscv64-gnu': 1.49.0
'@oxlint/binding-linux-riscv64-musl': 1.49.0
'@oxlint/binding-linux-s390x-gnu': 1.49.0
'@oxlint/binding-linux-x64-gnu': 1.49.0
'@oxlint/binding-linux-x64-musl': 1.49.0
'@oxlint/binding-openharmony-arm64': 1.49.0
'@oxlint/binding-win32-arm64-msvc': 1.49.0
'@oxlint/binding-win32-ia32-msvc': 1.49.0
'@oxlint/binding-win32-x64-msvc': 1.49.0
'@oxlint/binding-android-arm-eabi': 1.50.0
'@oxlint/binding-android-arm64': 1.50.0
'@oxlint/binding-darwin-arm64': 1.50.0
'@oxlint/binding-darwin-x64': 1.50.0
'@oxlint/binding-freebsd-x64': 1.50.0
'@oxlint/binding-linux-arm-gnueabihf': 1.50.0
'@oxlint/binding-linux-arm-musleabihf': 1.50.0
'@oxlint/binding-linux-arm64-gnu': 1.50.0
'@oxlint/binding-linux-arm64-musl': 1.50.0
'@oxlint/binding-linux-ppc64-gnu': 1.50.0
'@oxlint/binding-linux-riscv64-gnu': 1.50.0
'@oxlint/binding-linux-riscv64-musl': 1.50.0
'@oxlint/binding-linux-s390x-gnu': 1.50.0
'@oxlint/binding-linux-x64-gnu': 1.50.0
'@oxlint/binding-linux-x64-musl': 1.50.0
'@oxlint/binding-openharmony-arm64': 1.50.0
'@oxlint/binding-win32-arm64-msvc': 1.50.0
'@oxlint/binding-win32-ia32-msvc': 1.50.0
'@oxlint/binding-win32-x64-msvc': 1.50.0
p-retry@7.1.1:
dependencies:
@@ -14316,7 +14316,7 @@ snapshots:
tailwind-merge@3.5.0: {}
tailwindcss@4.2.0: {}
tailwindcss@4.2.1: {}
tapable@2.3.0: {}
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-5.10 = .249
LINUX_KERNEL_HASH-5.10.249 = b990ed34261149542f932e5eaf1ba58535c5d48c147ae36bc2747c65e54d1046
LINUX_VERSION-5.10 = .251
LINUX_KERNEL_HASH-5.10.251 = e6857625fee3b587b0279b445adc3940a5c40723385fa1055ac7af16ff4b4c01
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-5.15 = .199
LINUX_KERNEL_HASH-5.15.199 = 01c0d3ae4c138a51891cb54d21374259fd07bfe1076e06362ec0ae0a822fbd2e
LINUX_VERSION-5.15 = .201
LINUX_KERNEL_HASH-5.15.201 = 4f2afffbeddaad6b8527d41a3e3a82646d3cf5dfd0acbb6c4e8a99fc70461b96
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-6.1 = .162
LINUX_KERNEL_HASH-6.1.162 = 9766422a49b9a24ea911ebc0b4e7d1f3d84fba3bc374387ae3c4bc60bec6d089
LINUX_VERSION-6.1 = .164
LINUX_KERNEL_HASH-6.1.164 = 33bf087f7bbf7f626873dd7d955eb44182a93695db41f5f89a6bd3d233a39d1c
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-6.12 = .69
LINUX_KERNEL_HASH-6.12.69 = 4b493657f218703239c4f22415f027b3644949bf2761abd18b849f0aad5f7665
LINUX_VERSION-6.12 = .74
LINUX_KERNEL_HASH-6.12.74 = 3b56eeb1dc9a437f189ca56b823be3769994f59a4ea0895b08ec0d20acaca13e
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-6.6 = .123
LINUX_KERNEL_HASH-6.6.123 = a616d4b5a18a9097e2df8e03dfd2ba366ddfc1321b16d648651dc512b3e945b1
LINUX_VERSION-6.6 = .127
LINUX_KERNEL_HASH-6.6.127 = a7cd9c97b4f0b31cc030bcdc60abe5434fffb2556e293f7438ce7909dff8c9fe
+2 -2
View File
@@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \
sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1))))
VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER))
VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.3)
VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.5)
VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE))
VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION))
VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO))
VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.3)
VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.5)
VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST))
VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt)
+1 -1
View File
@@ -190,7 +190,7 @@ if VERSIONOPT
config VERSION_REPO
string
prompt "Release repository"
default "https://downloads.openwrt.org/releases/24.10.3"
default "https://downloads.openwrt.org/releases/24.10.5"
help
This is the repository address embedded in the image, it defaults
to the trunk snapshot repo; the url may contain the following placeholders:
@@ -8,12 +8,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=linux-firmware
PKG_VERSION:=20251111
PKG_VERSION:=20260221
PKG_RELEASE:=1
PKG_SOURCE_URL:=@KERNEL/linux/kernel/firmware
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_HASH:=ab57a1526595090bb4874c35335e2252288dcbea546eff491654b2313438a47d
PKG_HASH:=bd19acc4c1a02548e09d3df67f987fe6e378df735bab138c1d9e917962056d94
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
+14 -2
View File
@@ -4,7 +4,8 @@ PKG_DRIVERS += \
rtl8xxxu rtw88 rtw88-pci rtw88-usb rtw88-sdio rtw88-8821c rtw88-8822b rtw88-8822c \
rtw88-8723d rtw88-8821ce rtw88-8821cu rtw88-8822be rtw88-8822bu \
rtw88-8822ce rtw88-8822cu rtw88-8723de rtw88-8723ds rtw88-8723du \
rtw89 rtw89-pci rtw89-8851be rtw89-8852ae rtw89-8852be rtw89-8852ce
rtw89 rtw89-pci rtw89-8851be rtw89-8852ae rtw89-8852b-common \
rtw89-8852be rtw89-8852ce
config-$(call config_package,rtlwifi) += RTL_CARDS RTLWIFI
config-$(call config_package,rtlwifi-pci) += RTLWIFI_PCI
@@ -51,6 +52,7 @@ config-$(call config_package,rtw89) += RTW89 RTW89_CORE
config-$(call config_package,rtw89-pci) += RTW89_PCI
config-$(call config_package,rtw89-8851be) += RTW89_8851B RTW89_8851BE
config-$(call config_package,rtw89-8852ae) += RTW89_8852A RTW89_8852AE
config-$(call config_package,rtw89-8852b-common) += RTW89_8852B_COMMON
config-$(call config_package,rtw89-8852be) += RTW89_8852B RTW89_8852BE
config-$(call config_package,rtw89-8852ce) += RTW89_8852C RTW89_8852CE
config-$(CONFIG_PACKAGE_RTW89_DEBUG) += RTW89_DEBUG
@@ -431,10 +433,20 @@ define KernelPackage/rtw89-8852ae
AUTOLOAD:=$(call AutoProbe,rtw89_8852ae)
endef
define KernelPackage/rtw89-8852b-common
$(call KernelPackage/mac80211/Default)
TITLE:=Realtek RTL8852B family support
DEPENDS+= +kmod-rtw89-pci
FILES:= \
$(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852b_common.ko
AUTOLOAD:=$(call AutoProbe,rtw89_8852b_common)
HIDDEN:=1
endef
define KernelPackage/rtw89-8852be
$(call KernelPackage/mac80211/Default)
TITLE:=Realtek RTL8852BE support
DEPENDS+= +kmod-rtw89-pci +rtl8852be-firmware
DEPENDS+= +kmod-rtw89-8852b-common +rtl8852be-firmware
FILES:= \
$(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852b.ko \
$(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852be.ko
@@ -49,12 +49,12 @@ sed -i '/check_signature/d' /etc/opkg.conf
sed -i '/REDIRECT --to-ports 53/d' /etc/firewall.user
sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release
echo "DISTRIB_REVISION='R25.10.10'" >> /etc/openwrt_release
echo "DISTRIB_REVISION='R26.02.20'" >> /etc/openwrt_release
sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release
echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release
sed -i '/OPENWRT_RELEASE/d' /usr/lib/os-release
echo 'OPENWRT_RELEASE="LEDE R25.10.10"' >> /usr/lib/os-release
echo 'OPENWRT_RELEASE="LEDE R26.02.20"' >> /usr/lib/os-release
sed -i '/log-facility/d' /etc/dnsmasq.conf
echo "log-facility=/dev/null" >> /etc/dnsmasq.conf
@@ -142,10 +142,10 @@ class BoxService(private val service: Service, private val platformInterface: Pl
val appList = Settings.getEffectivePerAppProxyList()
if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) {
includePackage =
PlatformInterfaceWrapper.StringArray(appList.iterator())
PlatformInterfaceWrapper.StringArray((appList + Application.application.packageName).iterator())
} else {
excludePackage =
PlatformInterfaceWrapper.StringArray(appList.iterator())
PlatformInterfaceWrapper.StringArray((appList - Application.application.packageName).iterator())
}
}
},
@@ -224,9 +224,9 @@ class BoxService(private val service: Service, private val platformInterface: Pl
if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) {
val appList = Settings.getEffectivePerAppProxyList()
if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) {
includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())
includePackage = PlatformInterfaceWrapper.StringArray((appList + Application.application.packageName).iterator())
} else {
excludePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())
excludePackage = PlatformInterfaceWrapper.StringArray((appList - Application.application.packageName).iterator())
}
}
},
+2 -2
View File
@@ -1,5 +1,5 @@
VERSION_CODE=623
VERSION_NAME=1.13.0-rc.6
VERSION_CODE=624
VERSION_NAME=1.13.0-rc.7
GO_VERSION=go1.25.7
@@ -128,6 +128,9 @@ struct CodeEditTextView: NSViewRepresentable {
guard let controller = context.coordinator.controller else { return }
if controller.text != text {
controller.text = text
// setText() creates a new Highlighter but doesn't trigger initial highlighting.
// Re-setting the language forces the highlighter to invalidate and re-highlight.
controller.language = .json
}
if controller.configuration.behavior.isEditable != isEditable {
controller.configuration = makeConfiguration(isEditable: isEditable)
+3 -2
View File
@@ -2,9 +2,9 @@
icon: material/alert-decagram
---
#### 1.13.0-rc.6
#### 1.13.0-rc.7
* Fixes and improvements
* Add advertise tags support for Tailscale endpoint
Important changes since 1.12:
@@ -136,6 +136,7 @@ See [Dial Fields](/configuration/shared/dial/#bind_address_no_port).
Tailscale endpoint can now create a system TUN interface to handle traffic directly.
New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections.
New `advertise_tags` option for ACL tag advertisement.
See [Tailscale endpoint](/configuration/endpoint/tailscale/).
@@ -9,6 +9,7 @@ icon: material/new-box
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
:material-plus: [advertise_tags](#advertise_tags)
!!! question "Since sing-box 1.12.0"
@@ -28,6 +29,7 @@ icon: material/new-box
"exit_node_allow_lan_access": false,
"advertise_routes": [],
"advertise_exit_node": false,
"advertise_tags": [],
"relay_server_port": 0,
"relay_server_static_endpoints": [],
"system_interface": false,
@@ -102,6 +104,14 @@ Example: `["192.168.1.1/24"]`
Indicates whether the node should advertise itself as an exit node.
#### advertise_tags
!!! question "Since sing-box 1.13.0"
Tags to advertise for this node, for ACL enforcement purposes.
Example: `["tag:server"]`
#### relay_server_port
!!! question "Since sing-box 1.13.0"
@@ -9,6 +9,7 @@ icon: material/new-box
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
:material-plus: [advertise_tags](#advertise_tags)
!!! question "自 sing-box 1.12.0 起"
@@ -28,6 +29,7 @@ icon: material/new-box
"exit_node_allow_lan_access": false,
"advertise_routes": [],
"advertise_exit_node": false,
"advertise_tags": [],
"relay_server_port": 0,
"relay_server_static_endpoints": [],
"system_interface": false,
@@ -101,6 +103,14 @@ icon: material/new-box
指示节点是否应将自己通告为出口节点。
#### advertise_tags
!!! question "自 sing-box 1.13.0 起"
为此节点通告的标签,用于 ACL 执行。
示例:`["tag:server"]`
#### relay_server_port
!!! question "自 sing-box 1.13.0 起"
+3 -3
View File
@@ -3,7 +3,7 @@ module github.com/sagernet/sing-box
go 1.24.7
require (
github.com/anthropics/anthropic-sdk-go v1.19.0
github.com/anthropics/anthropic-sdk-go v1.26.0
github.com/anytls/sing-anytls v0.0.11
github.com/caddyserver/certmagic v0.25.0
github.com/coder/websocket v1.8.14
@@ -22,7 +22,7 @@ require (
github.com/metacubex/utls v1.8.4
github.com/mholt/acmez/v3 v3.1.4
github.com/miekg/dns v1.1.69
github.com/openai/openai-go/v3 v3.15.0
github.com/openai/openai-go/v3 v3.23.0
github.com/oschwald/maxminddb-golang v1.13.1
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
@@ -43,7 +43,7 @@ require (
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
+10 -8
View File
@@ -8,8 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=
github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
@@ -38,6 +38,8 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
@@ -126,8 +128,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo=
github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/openai/openai-go/v3 v3.23.0 h1:FRFwTcB4FoWFtIunTY/8fgHvzSHgqbfWjiCwOMVrsvw=
github.com/openai/openai-go/v3 v3.23.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
@@ -236,8 +238,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f h1:3B8auqVameLvPhWxzw5S8QWdLTv19o0M8LYxPOXNm0g=
github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8=
github.com/sagernet/sing-tun v0.8.0-beta.18 h1:C6oHxP9BNBVEVdC9ABMTXmKej9mUVtcuw2v+IiBS8yw=
github.com/sagernet/sing-tun v0.8.0-beta.18/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
@@ -246,8 +246,8 @@ github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1h
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8=
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
@@ -380,6 +380,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+17 -16
View File
@@ -12,22 +12,23 @@ import (
type TailscaleEndpointOptions struct {
DialerOptions
StateDirectory string `json:"state_directory,omitempty"`
AuthKey string `json:"auth_key,omitempty"`
ControlURL string `json:"control_url,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
Hostname string `json:"hostname,omitempty"`
AcceptRoutes bool `json:"accept_routes,omitempty"`
ExitNode string `json:"exit_node,omitempty"`
ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"`
AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"`
AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"`
RelayServerPort *uint16 `json:"relay_server_port,omitempty"`
RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"`
SystemInterface bool `json:"system_interface,omitempty"`
SystemInterfaceName string `json:"system_interface_name,omitempty"`
SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
StateDirectory string `json:"state_directory,omitempty"`
AuthKey string `json:"auth_key,omitempty"`
ControlURL string `json:"control_url,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
Hostname string `json:"hostname,omitempty"`
AcceptRoutes bool `json:"accept_routes,omitempty"`
ExitNode string `json:"exit_node,omitempty"`
ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"`
AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"`
AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"`
AdvertiseTags badoption.Listable[string] `json:"advertise_tags,omitempty"`
RelayServerPort *uint16 `json:"relay_server_port,omitempty"`
RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"`
SystemInterface bool `json:"system_interface,omitempty"`
SystemInterfaceName string `json:"system_interface_name,omitempty"`
SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
}
type TailscaleDNSServerOptions struct {
+11 -9
View File
@@ -97,6 +97,7 @@ type Endpoint struct {
exitNodeAllowLANAccess bool
advertiseRoutes []netip.Prefix
advertiseExitNode bool
advertiseTags []string
relayServerPort *uint16
relayServerStaticEndpoints []netip.AddrPort
@@ -244,6 +245,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess,
advertiseRoutes: options.AdvertiseRoutes,
advertiseExitNode: options.AdvertiseExitNode,
advertiseTags: options.AdvertiseTags,
relayServerPort: options.RelayServerPort,
relayServerStaticEndpoints: options.RelayServerStaticEndpoints,
udpTimeout: udpTimeout,
@@ -359,25 +361,25 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
localBackend := t.server.ExportLocalBackend()
perfs := &ipn.MaskedPrefs{
Prefs: ipn.Prefs{
RouteAll: t.acceptRoutes,
RouteAll: t.acceptRoutes,
AdvertiseRoutes: t.advertiseRoutes,
AdvertiseTags: t.advertiseTags,
},
RouteAllSet: true,
ExitNodeIPSet: true,
AdvertiseRoutesSet: true,
}
if len(t.advertiseRoutes) > 0 {
perfs.AdvertiseRoutes = t.advertiseRoutes
RouteAllSet: true,
ExitNodeIPSet: true,
AdvertiseRoutesSet: true,
AdvertiseTagsSet: true,
RelayServerPortSet: true,
RelayServerStaticEndpointsSet: true,
}
if t.advertiseExitNode {
perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...)
}
if t.relayServerPort != nil {
perfs.RelayServerPort = t.relayServerPort
perfs.RelayServerPortSet = true
}
if len(t.relayServerStaticEndpoints) > 0 {
perfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints
perfs.RelayServerStaticEndpointsSet = true
}
_, err = localBackend.EditPrefs(perfs)
if err != nil {
+6
View File
@@ -425,6 +425,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
usage.OutputTokens,
usage.CacheReadInputTokens,
usage.CacheCreationInputTokens,
usage.CacheCreation.Ephemeral5mInputTokens,
usage.CacheCreation.Ephemeral1hInputTokens,
username,
)
}
@@ -485,6 +487,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
accumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens
accumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens
accumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens
accumulatedUsage.CacheCreation.Ephemeral5mInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral5mInputTokens
accumulatedUsage.CacheCreation.Ephemeral1hInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral1hInputTokens
}
case "message_delta":
messageDelta := event.AsMessageDelta()
@@ -519,6 +523,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
accumulatedUsage.OutputTokens,
accumulatedUsage.CacheReadInputTokens,
accumulatedUsage.CacheCreationInputTokens,
accumulatedUsage.CacheCreation.Ephemeral5mInputTokens,
accumulatedUsage.CacheCreation.Ephemeral1hInputTokens,
username,
)
}
+203 -108
View File
@@ -13,12 +13,14 @@ import (
)
type UsageStats struct {
RequestCount int `json:"request_count"`
MessagesCount int `json:"messages_count"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
RequestCount int `json:"request_count"`
MessagesCount int `json:"messages_count"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"`
CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"`
}
type CostCombination struct {
@@ -41,13 +43,15 @@ type AggregatedUsage struct {
}
type UsageStatsJSON struct {
RequestCount int `json:"request_count"`
MessagesCount int `json:"messages_count"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CostUSD float64 `json:"cost_usd"`
RequestCount int `json:"request_count"`
MessagesCount int `json:"messages_count"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadInputTokens int64 `json:"cache_read_input_tokens"`
CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"`
CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"`
CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"`
CostUSD float64 `json:"cost_usd"`
}
type CostCombinationJSON struct {
@@ -69,10 +73,11 @@ type AggregatedUsageJSON struct {
}
type ModelPricing struct {
InputPrice float64
OutputPrice float64
CacheReadPrice float64
CacheWritePrice float64
InputPrice float64
OutputPrice float64
CacheReadPrice float64
CacheWritePrice5Minute float64
CacheWritePrice1Hour float64
}
type modelFamily struct {
@@ -82,143 +87,205 @@ type modelFamily struct {
}
var (
opus4Pricing = ModelPricing{
InputPrice: 15.0,
OutputPrice: 75.0,
CacheReadPrice: 1.5,
CacheWritePrice: 18.75,
opus46StandardPricing = ModelPricing{
InputPrice: 5.0,
OutputPrice: 25.0,
CacheReadPrice: 0.5,
CacheWritePrice5Minute: 6.25,
CacheWritePrice1Hour: 10.0,
}
sonnet4StandardPricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice: 3.75,
}
sonnet4PremiumPricing = ModelPricing{
InputPrice: 6.0,
OutputPrice: 22.5,
CacheReadPrice: 0.6,
CacheWritePrice: 7.5,
}
haiku4Pricing = ModelPricing{
InputPrice: 1.0,
OutputPrice: 5.0,
CacheReadPrice: 0.1,
CacheWritePrice: 1.25,
}
haiku35Pricing = ModelPricing{
InputPrice: 0.8,
OutputPrice: 4.0,
CacheReadPrice: 0.08,
CacheWritePrice: 1.0,
}
sonnet35Pricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice: 3.75,
opus46PremiumPricing = ModelPricing{
InputPrice: 10.0,
OutputPrice: 37.5,
CacheReadPrice: 1.0,
CacheWritePrice5Minute: 12.5,
CacheWritePrice1Hour: 20.0,
}
opus45Pricing = ModelPricing{
InputPrice: 5.0,
OutputPrice: 25.0,
CacheReadPrice: 0.5,
CacheWritePrice: 6.25,
InputPrice: 5.0,
OutputPrice: 25.0,
CacheReadPrice: 0.5,
CacheWritePrice5Minute: 6.25,
CacheWritePrice1Hour: 10.0,
}
opus4Pricing = ModelPricing{
InputPrice: 15.0,
OutputPrice: 75.0,
CacheReadPrice: 1.5,
CacheWritePrice5Minute: 18.75,
CacheWritePrice1Hour: 30.0,
}
sonnet46StandardPricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice5Minute: 3.75,
CacheWritePrice1Hour: 6.0,
}
sonnet46PremiumPricing = ModelPricing{
InputPrice: 6.0,
OutputPrice: 22.5,
CacheReadPrice: 0.6,
CacheWritePrice5Minute: 7.5,
CacheWritePrice1Hour: 12.0,
}
sonnet45StandardPricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice: 3.75,
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice5Minute: 3.75,
CacheWritePrice1Hour: 6.0,
}
sonnet45PremiumPricing = ModelPricing{
InputPrice: 6.0,
OutputPrice: 22.5,
CacheReadPrice: 0.6,
CacheWritePrice: 7.5,
InputPrice: 6.0,
OutputPrice: 22.5,
CacheReadPrice: 0.6,
CacheWritePrice5Minute: 7.5,
CacheWritePrice1Hour: 12.0,
}
sonnet4StandardPricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice5Minute: 3.75,
CacheWritePrice1Hour: 6.0,
}
sonnet4PremiumPricing = ModelPricing{
InputPrice: 6.0,
OutputPrice: 22.5,
CacheReadPrice: 0.6,
CacheWritePrice5Minute: 7.5,
CacheWritePrice1Hour: 12.0,
}
sonnet37Pricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice5Minute: 3.75,
CacheWritePrice1Hour: 6.0,
}
sonnet35Pricing = ModelPricing{
InputPrice: 3.0,
OutputPrice: 15.0,
CacheReadPrice: 0.3,
CacheWritePrice5Minute: 3.75,
CacheWritePrice1Hour: 6.0,
}
haiku45Pricing = ModelPricing{
InputPrice: 1.0,
OutputPrice: 5.0,
CacheReadPrice: 0.1,
CacheWritePrice: 1.25,
InputPrice: 1.0,
OutputPrice: 5.0,
CacheReadPrice: 0.1,
CacheWritePrice5Minute: 1.25,
CacheWritePrice1Hour: 2.0,
}
haiku4Pricing = ModelPricing{
InputPrice: 1.0,
OutputPrice: 5.0,
CacheReadPrice: 0.1,
CacheWritePrice5Minute: 1.25,
CacheWritePrice1Hour: 2.0,
}
haiku35Pricing = ModelPricing{
InputPrice: 0.8,
OutputPrice: 4.0,
CacheReadPrice: 0.08,
CacheWritePrice5Minute: 1.0,
CacheWritePrice1Hour: 1.6,
}
haiku3Pricing = ModelPricing{
InputPrice: 0.25,
OutputPrice: 1.25,
CacheReadPrice: 0.03,
CacheWritePrice: 0.3,
InputPrice: 0.25,
OutputPrice: 1.25,
CacheReadPrice: 0.03,
CacheWritePrice5Minute: 0.3,
CacheWritePrice1Hour: 0.5,
}
opus3Pricing = ModelPricing{
InputPrice: 15.0,
OutputPrice: 75.0,
CacheReadPrice: 1.5,
CacheWritePrice: 18.75,
InputPrice: 15.0,
OutputPrice: 75.0,
CacheReadPrice: 1.5,
CacheWritePrice5Minute: 18.75,
CacheWritePrice1Hour: 30.0,
}
modelFamilies = []modelFamily{
{
pattern: regexp.MustCompile(`^claude-opus-4-5-`),
pattern: regexp.MustCompile(`^claude-opus-4-6(?:-|$)`),
standardPricing: opus46StandardPricing,
premiumPricing: &opus46PremiumPricing,
},
{
pattern: regexp.MustCompile(`^claude-opus-4-5(?:-|$)`),
standardPricing: opus45Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`),
pattern: regexp.MustCompile(`^claude-(?:opus-4(?:-|$)|4-opus-)`),
standardPricing: opus4Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`),
pattern: regexp.MustCompile(`^claude-(?:opus-3(?:-|$)|3-opus-)`),
standardPricing: opus3Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`),
pattern: regexp.MustCompile(`^claude-(?:sonnet-4-6(?:-|$)|4-6-sonnet-)`),
standardPricing: sonnet46StandardPricing,
premiumPricing: &sonnet46PremiumPricing,
},
{
pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5(?:-|$)|4-5-sonnet-)`),
standardPricing: sonnet45StandardPricing,
premiumPricing: &sonnet45PremiumPricing,
},
{
pattern: regexp.MustCompile(`^claude-3-7-sonnet-`),
pattern: regexp.MustCompile(`^claude-(?:sonnet-4(?:-|$)|4-sonnet-)`),
standardPricing: sonnet4StandardPricing,
premiumPricing: &sonnet4PremiumPricing,
},
{
pattern: regexp.MustCompile(`^claude-(?:sonnet-4-|4-sonnet-)`),
standardPricing: sonnet4StandardPricing,
premiumPricing: &sonnet4PremiumPricing,
pattern: regexp.MustCompile(`^claude-3-7-sonnet(?:-|$)`),
standardPricing: sonnet37Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-3-5-sonnet-`),
pattern: regexp.MustCompile(`^claude-3-5-sonnet(?:-|$)`),
standardPricing: sonnet35Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`),
pattern: regexp.MustCompile(`^claude-(?:haiku-4-5(?:-|$)|4-5-haiku-)`),
standardPricing: haiku45Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-haiku-4-`),
pattern: regexp.MustCompile(`^claude-haiku-4(?:-|$)`),
standardPricing: haiku4Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-3-5-haiku-`),
pattern: regexp.MustCompile(`^claude-3-5-haiku(?:-|$)`),
standardPricing: haiku35Pricing,
premiumPricing: nil,
},
{
pattern: regexp.MustCompile(`^claude-3-haiku-`),
pattern: regexp.MustCompile(`^claude-3-haiku(?:-|$)`),
standardPricing: haiku3Pricing,
premiumPricing: nil,
},
@@ -243,10 +310,20 @@ func getPricing(model string, contextWindow int) ModelPricing {
func calculateCost(stats UsageStats, model string, contextWindow int) float64 {
pricing := getPricing(model, contextWindow)
cacheCreationCost := 0.0
if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 {
cacheCreationCost =
float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute +
float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour
} else {
// Backward compatibility for usage files generated before TTL split tracking.
cacheCreationCost = float64(stats.CacheCreationInputTokens) * pricing.CacheWritePrice5Minute
}
cost := (float64(stats.InputTokens)*pricing.InputPrice +
float64(stats.OutputTokens)*pricing.OutputPrice +
float64(stats.CacheReadInputTokens)*pricing.CacheReadPrice +
float64(stats.CacheCreationInputTokens)*pricing.CacheWritePrice) / 1_000_000
cacheCreationCost) / 1_000_000
return math.Round(cost*100) / 100
}
@@ -273,13 +350,15 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
Model: combo.Model,
ContextWindow: combo.ContextWindow,
Total: UsageStatsJSON{
RequestCount: combo.Total.RequestCount,
MessagesCount: combo.Total.MessagesCount,
InputTokens: combo.Total.InputTokens,
OutputTokens: combo.Total.OutputTokens,
CacheReadInputTokens: combo.Total.CacheReadInputTokens,
CacheCreationInputTokens: combo.Total.CacheCreationInputTokens,
CostUSD: totalCost,
RequestCount: combo.Total.RequestCount,
MessagesCount: combo.Total.MessagesCount,
InputTokens: combo.Total.InputTokens,
OutputTokens: combo.Total.OutputTokens,
CacheReadInputTokens: combo.Total.CacheReadInputTokens,
CacheCreationInputTokens: combo.Total.CacheCreationInputTokens,
CacheCreation5MinuteInputTokens: combo.Total.CacheCreation5MinuteInputTokens,
CacheCreation1HourInputTokens: combo.Total.CacheCreation1HourInputTokens,
CostUSD: totalCost,
},
ByUser: make(map[string]UsageStatsJSON),
}
@@ -289,13 +368,15 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
result.Costs.ByUser[user] += userCost
comboJSON.ByUser[user] = UsageStatsJSON{
RequestCount: userStats.RequestCount,
MessagesCount: userStats.MessagesCount,
InputTokens: userStats.InputTokens,
OutputTokens: userStats.OutputTokens,
CacheReadInputTokens: userStats.CacheReadInputTokens,
CacheCreationInputTokens: userStats.CacheCreationInputTokens,
CostUSD: userCost,
RequestCount: userStats.RequestCount,
MessagesCount: userStats.MessagesCount,
InputTokens: userStats.InputTokens,
OutputTokens: userStats.OutputTokens,
CacheReadInputTokens: userStats.CacheReadInputTokens,
CacheCreationInputTokens: userStats.CacheCreationInputTokens,
CacheCreation5MinuteInputTokens: userStats.CacheCreation5MinuteInputTokens,
CacheCreation1HourInputTokens: userStats.CacheCreation1HourInputTokens,
CostUSD: userCost,
}
}
@@ -367,7 +448,13 @@ func (u *AggregatedUsage) Save() error {
return err
}
func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCount int, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens int64, user string) error {
func (u *AggregatedUsage) AddUsage(
model string,
contextWindow int,
messagesCount int,
inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,
user string,
) error {
if model == "" {
return E.New("model cannot be empty")
}
@@ -400,6 +487,10 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun
combo = &u.Combinations[len(u.Combinations)-1]
}
if cacheCreationTokens == 0 {
cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens
}
// Update total stats
combo.Total.RequestCount++
combo.Total.MessagesCount += messagesCount
@@ -407,6 +498,8 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun
combo.Total.OutputTokens += outputTokens
combo.Total.CacheReadInputTokens += cacheReadTokens
combo.Total.CacheCreationInputTokens += cacheCreationTokens
combo.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
combo.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens
// Update per-user stats if user is specified
if user != "" {
@@ -417,6 +510,8 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun
userStats.OutputTokens += outputTokens
userStats.CacheReadInputTokens += cacheReadTokens
userStats.CacheCreationInputTokens += cacheCreationTokens
userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens
combo.ByUser[user] = userStats
}
+15 -5
View File
@@ -406,7 +406,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
isChatCompletions := path == "/v1/chat/completions"
mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
isStreaming := err == nil && mediaType == "text/event-stream"
if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" {
isStreaming = true
}
if !isStreaming {
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
@@ -414,13 +416,14 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
return
}
var responseModel string
var responseModel, serviceTier string
var inputTokens, outputTokens, cachedTokens int64
if isChatCompletions {
var chatCompletion openai.ChatCompletion
if json.Unmarshal(bodyBytes, &chatCompletion) == nil {
responseModel = chatCompletion.Model
serviceTier = string(chatCompletion.ServiceTier)
inputTokens = chatCompletion.Usage.PromptTokens
outputTokens = chatCompletion.Usage.CompletionTokens
cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens
@@ -429,6 +432,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
var responsesResponse responses.Response
if json.Unmarshal(bodyBytes, &responsesResponse) == nil {
responseModel = string(responsesResponse.Model)
serviceTier = string(responsesResponse.ServiceTier)
inputTokens = responsesResponse.Usage.InputTokens
outputTokens = responsesResponse.Usage.OutputTokens
cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens
@@ -440,7 +444,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
responseModel = requestModel
}
if responseModel != "" {
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username)
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username)
}
}
@@ -455,7 +459,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
}
var inputTokens, outputTokens, cachedTokens int64
var responseModel string
var responseModel, serviceTier string
buffer := make([]byte, buf.BufferSize)
var leftover []byte
@@ -490,6 +494,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
if chatChunk.Model != "" {
responseModel = chatChunk.Model
}
if chatChunk.ServiceTier != "" {
serviceTier = string(chatChunk.ServiceTier)
}
if chatChunk.Usage.PromptTokens > 0 {
inputTokens = chatChunk.Usage.PromptTokens
cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens
@@ -506,6 +513,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
if string(completedEvent.Response.Model) != "" {
responseModel = string(completedEvent.Response.Model)
}
if completedEvent.Response.ServiceTier != "" {
serviceTier = string(completedEvent.Response.ServiceTier)
}
if completedEvent.Response.Usage.InputTokens > 0 {
inputTokens = completedEvent.Response.Usage.InputTokens
cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens
@@ -534,7 +544,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
if inputTokens > 0 || outputTokens > 0 {
if responseModel != "" {
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username)
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username)
}
}
return
+514 -37
View File
@@ -5,6 +5,7 @@ import (
"math"
"os"
"regexp"
"strings"
"sync"
"time"
@@ -42,9 +43,10 @@ func (u *UsageStats) UnmarshalJSON(data []byte) error {
}
type CostCombination struct {
Model string `json:"model"`
Total UsageStats `json:"total"`
ByUser map[string]UsageStats `json:"by_user"`
Model string `json:"model"`
ServiceTier string `json:"service_tier,omitempty"`
Total UsageStats `json:"total"`
ByUser map[string]UsageStats `json:"by_user"`
}
type AggregatedUsage struct {
@@ -68,9 +70,10 @@ type UsageStatsJSON struct {
}
type CostCombinationJSON struct {
Model string `json:"model"`
Total UsageStatsJSON `json:"total"`
ByUser map[string]UsageStatsJSON `json:"by_user"`
Model string `json:"model"`
ServiceTier string `json:"service_tier,omitempty"`
Total UsageStatsJSON `json:"total"`
ByUser map[string]UsageStatsJSON `json:"by_user"`
}
type CostsSummaryJSON struct {
@@ -95,7 +98,123 @@ type modelFamily struct {
pricing ModelPricing
}
const (
serviceTierAuto = "auto"
serviceTierDefault = "default"
serviceTierFlex = "flex"
serviceTierPriority = "priority"
serviceTierScale = "scale"
)
var (
gpt52Pricing = ModelPricing{
InputPrice: 1.75,
OutputPrice: 14.0,
CachedInputPrice: 0.175,
}
gpt5Pricing = ModelPricing{
InputPrice: 1.25,
OutputPrice: 10.0,
CachedInputPrice: 0.125,
}
gpt5MiniPricing = ModelPricing{
InputPrice: 0.25,
OutputPrice: 2.0,
CachedInputPrice: 0.025,
}
gpt5NanoPricing = ModelPricing{
InputPrice: 0.05,
OutputPrice: 0.4,
CachedInputPrice: 0.005,
}
gpt52CodexPricing = ModelPricing{
InputPrice: 1.75,
OutputPrice: 14.0,
CachedInputPrice: 0.175,
}
gpt51CodexPricing = ModelPricing{
InputPrice: 1.25,
OutputPrice: 10.0,
CachedInputPrice: 0.125,
}
gpt51CodexMiniPricing = ModelPricing{
InputPrice: 0.25,
OutputPrice: 2.0,
CachedInputPrice: 0.025,
}
gpt52ProPricing = ModelPricing{
InputPrice: 21.0,
OutputPrice: 168.0,
CachedInputPrice: 21.0,
}
gpt5ProPricing = ModelPricing{
InputPrice: 15.0,
OutputPrice: 120.0,
CachedInputPrice: 15.0,
}
gpt52FlexPricing = ModelPricing{
InputPrice: 0.875,
OutputPrice: 7.0,
CachedInputPrice: 0.0875,
}
gpt5FlexPricing = ModelPricing{
InputPrice: 0.625,
OutputPrice: 5.0,
CachedInputPrice: 0.0625,
}
gpt5MiniFlexPricing = ModelPricing{
InputPrice: 0.125,
OutputPrice: 1.0,
CachedInputPrice: 0.0125,
}
gpt5NanoFlexPricing = ModelPricing{
InputPrice: 0.025,
OutputPrice: 0.2,
CachedInputPrice: 0.0025,
}
gpt52PriorityPricing = ModelPricing{
InputPrice: 3.5,
OutputPrice: 28.0,
CachedInputPrice: 0.35,
}
gpt5PriorityPricing = ModelPricing{
InputPrice: 2.5,
OutputPrice: 20.0,
CachedInputPrice: 0.25,
}
gpt5MiniPriorityPricing = ModelPricing{
InputPrice: 0.45,
OutputPrice: 3.6,
CachedInputPrice: 0.045,
}
gpt52CodexPriorityPricing = ModelPricing{
InputPrice: 3.5,
OutputPrice: 28.0,
CachedInputPrice: 0.35,
}
gpt51CodexPriorityPricing = ModelPricing{
InputPrice: 2.5,
OutputPrice: 20.0,
CachedInputPrice: 0.25,
}
gpt4oPricing = ModelPricing{
InputPrice: 2.5,
OutputPrice: 10.0,
@@ -111,7 +230,19 @@ var (
gpt4oAudioPricing = ModelPricing{
InputPrice: 2.5,
OutputPrice: 10.0,
CachedInputPrice: 1.25,
CachedInputPrice: 2.5,
}
gpt4oMiniAudioPricing = ModelPricing{
InputPrice: 0.15,
OutputPrice: 0.6,
CachedInputPrice: 0.15,
}
gptAudioMiniPricing = ModelPricing{
InputPrice: 0.6,
OutputPrice: 2.4,
CachedInputPrice: 0.6,
}
o1Pricing = ModelPricing{
@@ -120,6 +251,12 @@ var (
CachedInputPrice: 7.5,
}
o1ProPricing = ModelPricing{
InputPrice: 150.0,
OutputPrice: 600.0,
CachedInputPrice: 150.0,
}
o1MiniPricing = ModelPricing{
InputPrice: 1.1,
OutputPrice: 4.4,
@@ -135,13 +272,55 @@ var (
o3Pricing = ModelPricing{
InputPrice: 2.0,
OutputPrice: 8.0,
CachedInputPrice: 1.0,
CachedInputPrice: 0.5,
}
o3ProPricing = ModelPricing{
InputPrice: 20.0,
OutputPrice: 80.0,
CachedInputPrice: 20.0,
}
o3DeepResearchPricing = ModelPricing{
InputPrice: 10.0,
OutputPrice: 40.0,
CachedInputPrice: 2.5,
}
o4MiniPricing = ModelPricing{
InputPrice: 1.1,
OutputPrice: 4.4,
CachedInputPrice: 0.55,
CachedInputPrice: 0.275,
}
o4MiniDeepResearchPricing = ModelPricing{
InputPrice: 2.0,
OutputPrice: 8.0,
CachedInputPrice: 0.5,
}
o3FlexPricing = ModelPricing{
InputPrice: 1.0,
OutputPrice: 4.0,
CachedInputPrice: 0.25,
}
o4MiniFlexPricing = ModelPricing{
InputPrice: 0.55,
OutputPrice: 2.2,
CachedInputPrice: 0.138,
}
o3PriorityPricing = ModelPricing{
InputPrice: 3.5,
OutputPrice: 14.0,
CachedInputPrice: 0.875,
}
o4MiniPriorityPricing = ModelPricing{
InputPrice: 2.0,
OutputPrice: 8.0,
CachedInputPrice: 0.5,
}
gpt41Pricing = ModelPricing{
@@ -162,69 +341,358 @@ var (
CachedInputPrice: 0.025,
}
modelFamilies = []modelFamily{
gpt41PriorityPricing = ModelPricing{
InputPrice: 3.5,
OutputPrice: 14.0,
CachedInputPrice: 0.875,
}
gpt41MiniPriorityPricing = ModelPricing{
InputPrice: 0.7,
OutputPrice: 2.8,
CachedInputPrice: 0.175,
}
gpt41NanoPriorityPricing = ModelPricing{
InputPrice: 0.2,
OutputPrice: 0.8,
CachedInputPrice: 0.05,
}
gpt4oPriorityPricing = ModelPricing{
InputPrice: 4.25,
OutputPrice: 17.0,
CachedInputPrice: 2.125,
}
gpt4oMiniPriorityPricing = ModelPricing{
InputPrice: 0.25,
OutputPrice: 1.0,
CachedInputPrice: 0.125,
}
standardModelFamilies = []modelFamily{
{
pattern: regexp.MustCompile(`^gpt-4\.1-nano`),
pricing: gpt41NanoPricing,
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
pricing: gpt52CodexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1-mini`),
pricing: gpt41MiniPricing,
pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`),
pricing: gpt51CodexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1`),
pricing: gpt41Pricing,
pattern: regexp.MustCompile(`^gpt-5\.1-codex-mini(?:$|-)`),
pricing: gpt51CodexMiniPricing,
},
{
pattern: regexp.MustCompile(`^o4-mini`),
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
pricing: gpt51CodexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
pricing: gpt51CodexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.2-chat-latest$`),
pricing: gpt52Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1-chat-latest$`),
pricing: gpt5Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-chat-latest$`),
pricing: gpt5Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.2-pro(?:$|-)`),
pricing: gpt52ProPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-pro(?:$|-)`),
pricing: gpt5ProPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),
pricing: gpt5MiniPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`),
pricing: gpt5NanoPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`),
pricing: gpt52Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`),
pricing: gpt5Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-5(?:$|-)`),
pricing: gpt5Pricing,
},
{
pattern: regexp.MustCompile(`^o4-mini-deep-research(?:$|-)`),
pricing: o4MiniDeepResearchPricing,
},
{
pattern: regexp.MustCompile(`^o4-mini(?:$|-)`),
pricing: o4MiniPricing,
},
{
pattern: regexp.MustCompile(`^o3-mini`),
pattern: regexp.MustCompile(`^o3-pro(?:$|-)`),
pricing: o3ProPricing,
},
{
pattern: regexp.MustCompile(`^o3-deep-research(?:$|-)`),
pricing: o3DeepResearchPricing,
},
{
pattern: regexp.MustCompile(`^o3-mini(?:$|-)`),
pricing: o3MiniPricing,
},
{
pattern: regexp.MustCompile(`^o3`),
pattern: regexp.MustCompile(`^o3(?:$|-)`),
pricing: o3Pricing,
},
{
pattern: regexp.MustCompile(`^o1-mini`),
pattern: regexp.MustCompile(`^o1-pro(?:$|-)`),
pricing: o1ProPricing,
},
{
pattern: regexp.MustCompile(`^o1-mini(?:$|-)`),
pricing: o1MiniPricing,
},
{
pattern: regexp.MustCompile(`^o1`),
pattern: regexp.MustCompile(`^o1(?:$|-)`),
pricing: o1Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o-audio`),
pattern: regexp.MustCompile(`^gpt-4o-mini-audio(?:$|-)`),
pricing: gpt4oMiniAudioPricing,
},
{
pattern: regexp.MustCompile(`^gpt-audio-mini(?:$|-)`),
pricing: gptAudioMiniPricing,
},
{
pattern: regexp.MustCompile(`^(?:gpt-4o-audio|gpt-audio)(?:$|-)`),
pricing: gpt4oAudioPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o-mini`),
pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`),
pricing: gpt41NanoPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`),
pricing: gpt41MiniPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`),
pricing: gpt41Pricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`),
pricing: gpt4oMiniPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o`),
pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`),
pricing: gpt4oPricing,
},
{
pattern: regexp.MustCompile(`^chatgpt-4o`),
pattern: regexp.MustCompile(`^chatgpt-4o(?:$|-)`),
pricing: gpt4oPricing,
},
}
flexModelFamilies = []modelFamily{
{
pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),
pricing: gpt5MiniFlexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`),
pricing: gpt5NanoFlexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`),
pricing: gpt52FlexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`),
pricing: gpt5FlexPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5(?:$|-)`),
pricing: gpt5FlexPricing,
},
{
pattern: regexp.MustCompile(`^o4-mini(?:$|-)`),
pricing: o4MiniFlexPricing,
},
{
pattern: regexp.MustCompile(`^o3(?:$|-)`),
pricing: o3FlexPricing,
},
}
priorityModelFamilies = []modelFamily{
{
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
pricing: gpt52CodexPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`),
pricing: gpt51CodexPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
pricing: gpt51CodexPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
pricing: gpt51CodexPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`),
pricing: gpt5MiniPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`),
pricing: gpt52PriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`),
pricing: gpt5PriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-5(?:$|-)`),
pricing: gpt5PriorityPricing,
},
{
pattern: regexp.MustCompile(`^o4-mini(?:$|-)`),
pricing: o4MiniPriorityPricing,
},
{
pattern: regexp.MustCompile(`^o3(?:$|-)`),
pricing: o3PriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`),
pricing: gpt41NanoPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`),
pricing: gpt41MiniPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`),
pricing: gpt41PriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`),
pricing: gpt4oMiniPriorityPricing,
},
{
pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`),
pricing: gpt4oPriorityPricing,
},
}
)
func getPricing(model string) ModelPricing {
func modelFamiliesForTier(serviceTier string) []modelFamily {
switch serviceTier {
case serviceTierFlex:
return flexModelFamilies
case serviceTierPriority:
return priorityModelFamilies
default:
return standardModelFamilies
}
}
func findPricingInFamilies(model string, modelFamilies []modelFamily) (ModelPricing, bool) {
for _, family := range modelFamilies {
if family.pattern.MatchString(model) {
return family.pricing
return family.pricing, true
}
}
return ModelPricing{}, false
}
func normalizeServiceTier(serviceTier string) string {
switch strings.ToLower(strings.TrimSpace(serviceTier)) {
case "", serviceTierAuto, serviceTierDefault:
return serviceTierDefault
case serviceTierFlex:
return serviceTierFlex
case serviceTierPriority:
return serviceTierPriority
case serviceTierScale:
// Scale-tier requests are prepaid differently and not listed in this usage file.
return serviceTierDefault
default:
return serviceTierDefault
}
}
func getPricing(model string, serviceTier string) ModelPricing {
normalizedServiceTier := normalizeServiceTier(serviceTier)
modelFamilies := modelFamiliesForTier(normalizedServiceTier)
if pricing, found := findPricingInFamilies(model, modelFamilies); found {
return pricing
}
normalizedModel := normalizeGPT5Model(model)
if normalizedModel != model {
if pricing, found := findPricingInFamilies(normalizedModel, modelFamilies); found {
return pricing
}
}
if normalizedServiceTier != serviceTierDefault {
if pricing, found := findPricingInFamilies(model, standardModelFamilies); found {
return pricing
}
if normalizedModel != model {
if pricing, found := findPricingInFamilies(normalizedModel, standardModelFamilies); found {
return pricing
}
}
}
return gpt4oPricing
}
func calculateCost(stats UsageStats, model string) float64 {
pricing := getPricing(model)
func normalizeGPT5Model(model string) string {
if !strings.HasPrefix(model, "gpt-5.") {
return model
}
switch {
case strings.Contains(model, "-codex-mini"):
return "gpt-5.1-codex-mini"
case strings.Contains(model, "-codex-max"):
return "gpt-5.1-codex-max"
case strings.Contains(model, "-codex"):
return "gpt-5.2-codex"
case strings.Contains(model, "-chat-latest"):
return "gpt-5.2-chat-latest"
case strings.Contains(model, "-pro"):
return "gpt-5.2-pro"
case strings.Contains(model, "-mini"):
return "gpt-5-mini"
case strings.Contains(model, "-nano"):
return "gpt-5-nano"
default:
return "gpt-5.2"
}
}
func calculateCost(stats UsageStats, model string, serviceTier string) float64 {
pricing := getPricing(model, serviceTier)
regularInputTokens := stats.InputTokens - stats.CachedTokens
if regularInputTokens < 0 {
@@ -252,12 +720,13 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
}
for i, combo := range u.Combinations {
totalCost := calculateCost(combo.Total, combo.Model)
totalCost := calculateCost(combo.Total, combo.Model, combo.ServiceTier)
result.Costs.TotalUSD += totalCost
comboJSON := CostCombinationJSON{
Model: combo.Model,
Model: combo.Model,
ServiceTier: combo.ServiceTier,
Total: UsageStatsJSON{
RequestCount: combo.Total.RequestCount,
InputTokens: combo.Total.InputTokens,
@@ -269,7 +738,7 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
}
for user, userStats := range combo.ByUser {
userCost := calculateCost(userStats, combo.Model)
userCost := calculateCost(userStats, combo.Model, combo.ServiceTier)
result.Costs.ByUser[user] += userCost
comboJSON.ByUser[user] = UsageStatsJSON{
@@ -318,6 +787,7 @@ func (u *AggregatedUsage) Load() error {
u.Combinations = temp.Combinations
for i := range u.Combinations {
u.Combinations[i].ServiceTier = normalizeServiceTier(u.Combinations[i].ServiceTier)
if u.Combinations[i].ByUser == nil {
u.Combinations[i].ByUser = make(map[string]UsageStats)
}
@@ -349,11 +819,13 @@ func (u *AggregatedUsage) Save() error {
return err
}
func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error {
func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error {
if model == "" {
return E.New("model cannot be empty")
}
normalizedServiceTier := normalizeServiceTier(serviceTier)
u.mutex.Lock()
defer u.mutex.Unlock()
@@ -361,7 +833,11 @@ func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cach
var combo *CostCombination
for i := range u.Combinations {
if u.Combinations[i].Model == model {
comboServiceTier := normalizeServiceTier(u.Combinations[i].ServiceTier)
if u.Combinations[i].ServiceTier != comboServiceTier {
u.Combinations[i].ServiceTier = comboServiceTier
}
if u.Combinations[i].Model == model && comboServiceTier == normalizedServiceTier {
combo = &u.Combinations[i]
break
}
@@ -369,9 +845,10 @@ func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cach
if combo == nil {
newCombo := CostCombination{
Model: model,
Total: UsageStats{},
ByUser: make(map[string]UsageStats),
Model: model,
ServiceTier: normalizedServiceTier,
Total: UsageStats{},
ByUser: make(map[string]UsageStats),
}
u.Combinations = append(u.Combinations, newCombo)
combo = &u.Combinations[len(u.Combinations)-1]
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
PKG_VERSION:=2.7.0
PKG_VERSION:=2.7.1
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)?
PKG_HASH:=3cac6adeccdac7cc8b353948e3cead1eb8606aa8ce74dac4853d821a22ceeba7
PKG_HASH:=2b8e42e965eb1b5215efe58f06a1416b2379b025c79adc89c620c56488b4f08d
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION)
PKG_LICENSE:=MIT
+2 -2
View File
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=838ab094bc01b9bafc849ce70c6f439dcb158d0c0dd41441ddb3c38d4d9ef563
endef
GEOSITE_VER:=20260220102217
GEOSITE_VER:=20260224084600
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=f1f70d67c7b971313140859c62d087b6c34c03041a7ba6ad3d9f06a217bcfdcf
HASH:=8a681eee7973fce2bfd68b698d868efec60cd77e1b5a34562514dc0a148cc9d5
endef
GEOSITE_IRAN_VER:=202602230057
-2
View File
@@ -15,7 +15,6 @@ public class Global
public const string CoreConfigFileName = "config.json";
public const string CorePreConfigFileName = "configPre.json";
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string NamespaceSample = "ServiceLib.Sample.";
@@ -88,7 +87,6 @@ public class Global
public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxEchDNSTag = "ech_dns";
public const int Hysteria2DefaultHopInt = 10;
@@ -254,6 +254,7 @@ public static class ConfigHandler
item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery;
item.Finalmask = profileItem.Finalmask;
item.ProtoExtra = profileItem.ProtoExtra;
}
@@ -1122,6 +1123,7 @@ public static class ConfigHandler
&& AreEqual(o.Fingerprint, n.Fingerprint)
&& AreEqual(o.PublicKey, n.PublicKey)
&& AreEqual(o.ShortId, n.ShortId)
&& AreEqual(o.Finalmask, n.Finalmask)
&& (!remarks || o.Remarks == n.Remarks);
static bool AreEqual(string? a, string? b)
@@ -1231,26 +1233,16 @@ public static class ConfigHandler
/// <param name="node">Server node that might need pre-SOCKS</param>
/// <param name="coreType">Core type being used</param>
/// <returns>A SOCKS profile item or null if not needed</returns>
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType())
{
var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList();
if (lstAddresses.Count > 0)
{
tun2SocksAddress = Utils.List2String(lstAddresses);
}
}
itemSocks = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
SpiderX = tun2SocksAddress, // Tun2SocksAddress
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
@@ -1265,7 +1257,6 @@ public static class ConfigHandler
Port = node.PreSocksPort.Value,
};
}
await Task.CompletedTask;
return itemSocks;
}
@@ -7,27 +7,27 @@ public static class CoreConfigHandler
{
private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
public static async Task<RetResult> GenerateClientConfig(CoreConfigContext context, string? fileName)
{
var config = AppManager.Instance.Config;
var result = new RetResult();
var node = context.Node;
if (node.ConfigType == EConfigType.Custom)
{
result = node.CoreType switch
{
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
}
if (result.Success != true)
{
@@ -93,13 +93,21 @@ public static class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
{
var result = new RetResult();
var context = await BuildCoreConfigContext(config, new());
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty())
.Select(serverTestItem => serverTestItem.IndexId);
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
foreach (var node in nodes)
{
await FillNodeContext(context, node, false);
}
if (coreType == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds);
}
else if (coreType == ECoreType.Xray)
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds);
}
if (result.Success != true)
{
@@ -109,20 +117,21 @@ public static class CoreConfigHandler
return result;
}
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName)
{
var result = new RetResult();
var node = context.Node;
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port;
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port);
}
if (result.Success != true)
{
@@ -132,4 +141,127 @@ public static class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
public static async Task<CoreConfigContext> BuildCoreConfigContext(Config config, ProfileItem node)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
var context = new CoreConfigContext()
{
Node = node,
AllProxiesMap = [],
AppConfig = config,
FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType),
IsTunEnabled = config.TunModeItem.EnableTun,
SimpleDnsItem = config.SimpleDNSItem,
ProtectDomainList = [],
ProtectSocksPort = 0,
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
};
context = context with
{
Node = await FillNodeContext(context, node)
};
if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true))
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet);
foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
{
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
if (ruleOutboundNode != null)
{
var ruleOutboundNodeAct = await FillNodeContext(context, ruleOutboundNode, false);
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = ruleOutboundNodeAct;
}
}
}
return context;
}
private static async Task<ProfileItem> FillNodeContext(CoreConfigContext context, ProfileItem node, bool includeSubChain = true)
{
if (node.IndexId.IsNullOrEmpty())
{
return node;
}
var newItems = new List<ProfileItem> { node };
if (node.ConfigType.IsGroupType())
{
var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node);
foreach (var childItem in groupChildList.Where(childItem => !context.AllProxiesMap.ContainsKey(childItem.IndexId)))
{
await FillNodeContext(context, childItem, false);
}
node.SetProtocolExtra(node.GetProtocolExtra() with
{
ChildItems = Utils.List2String(groupChildList.Select(n => n.IndexId).ToList()),
});
newItems.AddRange(groupChildList);
}
context.AllProxiesMap[node.IndexId] = node;
foreach (var item in newItems)
{
var address = item.Address;
if (Utils.IsDomain(address))
{
context.ProtectDomainList.Add(address);
}
if (item.EchConfigList.IsNullOrEmpty())
{
continue;
}
var echQuerySni = item.Sni;
if (item.StreamSecurity == Global.StreamSecurity
&& item.EchConfigList?.Contains("://") == true)
{
var idx = item.EchConfigList.IndexOf('+');
echQuerySni = idx > 0 ? item.EchConfigList[..idx] : item.Sni;
}
if (!Utils.IsDomain(echQuerySni))
{
continue;
}
context.ProtectDomainList.Add(echQuerySni);
}
if (!includeSubChain || node.Subid.IsNullOrEmpty())
{
return node;
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem == null)
{
return node;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (prevNode is null && nextNode is null)
{
return node;
}
var prevNodeAct = prevNode is null ? null : await FillNodeContext(context, prevNode, false);
var nextNodeAct = nextNode is null ? null : await FillNodeContext(context, nextNode, false);
// Build new proxy chain node
var chainNode = new ProfileItem()
{
IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain,
CoreType = node.CoreType ?? ECoreType.Xray,
};
List<string?> childItems = [prevNodeAct?.IndexId, node.IndexId, nextNodeAct?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with
{
GroupType = chainNode.ConfigType.ToString(),
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
};
chainNode.SetProtocolExtra(chainExtraItem);
context.AllProxiesMap[chainNode.IndexId] = chainNode;
return chainNode;
}
}
@@ -73,6 +73,19 @@ public class BaseFmt
{
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
}
if (item.Finalmask.IsNotEmpty())
{
var node = JsonUtils.ParseJson(item.Finalmask);
var finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: item.Finalmask;
dicQuery.Add("fm", Utils.UrlEncode(finalmask));
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@@ -214,6 +227,24 @@ public class BaseFmt
item.EchConfigList = GetQueryDecoded(query, "ech");
item.CertSha = GetQueryDecoded(query, "pcs");
var finalmaskDecoded = GetQueryDecoded(query, "fm");
if (finalmaskDecoded.IsNotEmpty())
{
var node = JsonUtils.ParseJson(finalmaskDecoded);
item.Finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: finalmaskDecoded;
}
else
{
item.Finalmask = string.Empty;
}
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{
item.AllowInsecure = Global.AllowInsecure.First();
@@ -230,6 +230,18 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<ProfileItem>> GetProfileItemsByIndexIds(IEnumerable<string> indexIds)
{
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (ids.Count == 0)
{
return [];
}
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => ids.Contains(it.IndexId))
.ToListAsync();
}
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{
if (remarks.IsNullOrEmpty())
@@ -215,7 +215,7 @@ public class CertPemManager
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
@@ -262,7 +262,7 @@ public class CertPemManager
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
@@ -280,11 +280,7 @@ public class CertPemManager
var chain = new X509Chain();
chain.Build(certChain);
foreach (var element in chain.ChainElements)
{
var pem = ExportCertToPem(element.Certificate);
pemList.Add(pem);
}
pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate)));
return (pemList, null);
}
@@ -66,7 +66,9 @@ public class CoreManager
}
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
context = context with { IsTunEnabled = _config.TunModeItem.EnableTun };
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
@@ -85,8 +87,8 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice();
}
await CoreStart(node);
await CoreStartPreService(node);
await CoreStart(context);
await CoreStartPreService(context);
if (_processService != null)
{
await UpdateFunc(true, $"{node.GetSummary()}");
@@ -122,7 +124,8 @@ public class CoreManager
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
if (result.Success != true)
{
return null;
@@ -165,8 +168,9 @@ public class CoreManager
#region Private
private async Task CoreStart(ProfileItem node)
private async Task CoreStart(CoreConfigContext context)
{
var node = context.Node;
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
@@ -179,17 +183,20 @@ public class CoreManager
_processService = proc;
}
private async Task CoreStartPreService(ProfileItem node)
private async Task CoreStartPreService(CoreConfigContext context)
{
var node = context.Node;
if (_processService != null && !_processService.HasExited)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
var itemSocks = ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
{
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
var itemSocksContext = await CoreConfigHandler.BuildCoreConfigContext(_config, itemSocks);
itemSocksContext.ProtectDomainList.AddRangeSafe(context.ProtectDomainList);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocksContext, fileName);
if (result.Success)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
@@ -79,7 +79,7 @@ public class GroupProfileManager
{
if (protocolExtra == null)
{
return new();
return [];
}
var items = new List<ProfileItem>();
@@ -93,27 +93,44 @@ public class GroupProfileManager
{
if (extra == null || extra.ChildItems.IsNullOrEmpty())
{
return new();
return [];
}
var childProfiles = (await Task.WhenAll(
(Utils.String2List(extra.ChildItems) ?? new())
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles ?? new();
var childProfileIds = Utils.String2List(extra.ChildItems)
?.Where(p => !string.IsNullOrEmpty(p))
.ToList() ?? [];
if (childProfileIds.Count == 0)
{
return [];
}
var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds);
if (childProfiles == null || childProfiles.Count == 0)
{
return [];
}
var profileMap = childProfiles
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
.GroupBy(p => p!.IndexId!)
.ToDictionary(g => g.Key, g => g.First());
var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}
return ordered;
}
private static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.SubChildItems.IsNullOrEmpty())
{
return new();
return [];
}
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
@@ -123,59 +140,30 @@ public class GroupProfileManager
!p.ConfigType.IsComplexType() &&
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
)
.ToList() ?? new();
.ToList() ?? [];
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(ProfileItem profileItem)
public static async Task<List<ProfileItem>> GetAllChildProfileItems(ProfileItem profileItem)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var child in childItems)
{
if (!child.IsComplex())
{
childAddresses.Add(child.Address);
}
else if (child.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(child);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
var allChildItems = new List<ProfileItem>();
var visited = new HashSet<string>();
await CollectChildItems(profileItem, allChildItems, visited);
return allChildItems;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(ProfileItem profileItem)
private static async Task CollectChildItems(ProfileItem profileItem, List<ProfileItem> allChildItems, HashSet<string> visited)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var childNode in childItems)
foreach (var child in childItems.Where(child => visited.Add(child.IndexId)))
{
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
allChildItems.Add(child);
if (child.ConfigType.IsGroupType())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
await CollectChildItems(child, allChildItems, visited);
}
}
return childAddresses;
}
}
@@ -0,0 +1,17 @@
namespace ServiceLib.Models;
public record CoreConfigContext
{
public required ProfileItem Node { get; init; }
public RoutingItem? RoutingItem { get; init; }
public DNSItem? RawDnsItem { get; init; }
public SimpleDNSItem SimpleDnsItem { get; init; } = new();
public Dictionary<string, ProfileItem> AllProxiesMap { get; init; } = new();
public Config AppConfig { get; init; } = new();
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
// TUN Compatibility
public bool IsTunEnabled { get; init; } = false;
public HashSet<string> ProtectDomainList { get; init; } = new();
public int ProtectSocksPort { get; init; } = 0;
}
@@ -178,6 +178,7 @@ public class ProfileItem
public string CertSha { get; set; }
public string EchConfigList { get; set; }
public string EchForceQuery { get; set; }
public string Finalmask { get; set; }
public string ProtoExtra { get; set; }
@@ -255,9 +255,8 @@ public class Server4Sbox : BaseServer4Sbox
// public List<string>? path { get; set; } // hosts
public Dictionary<string, List<string>>? predefined { get; set; }
// Deprecated
// Deprecated in sing-box 1.12.0 , kept for backward compatibility
public string? address { get; set; }
public string? address_resolver { get; set; }
public string? address_strategy { get; set; }
public string? strategy { get; set; }
@@ -341,7 +341,7 @@ public class StreamSettings4Ray
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public FinalMask4Ray? finalmask { get; set; }
public Finalmask4Ray? finalmask { get; set; }
public Sockopt4Ray? sockopt { get; set; }
}
@@ -476,7 +476,7 @@ public class HysteriaUdpHop4Ray
public string? interval { get; set; }
}
public class FinalMask4Ray
public class Finalmask4Ray
{
public List<Mask4Ray>? tcp { get; set; }
public List<Mask4Ray>? udp { get; set; }
@@ -485,7 +485,7 @@ public class FinalMask4Ray
public class Mask4Ray
{
public string type { get; set; }
public MaskSettings4Ray? settings { get; set; }
public object? settings { get; set; }
}
public class MaskSettings4Ray
+9
View File
@@ -2916,6 +2916,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Finalmask 的本地化字符串。
/// </summary>
public static string TbFinalmask {
get {
return ResourceManager.GetString("TbFinalmask", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
+3
View File
@@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1668,4 +1668,7 @@
<data name="menuServerListPreview" xml:space="preserve">
<value>子配置项预览</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1668,4 +1668,7 @@
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>
@@ -1,4 +1,4 @@
{
{
"log": {
"access": "Vaccess.log",
"error": "Verror.log",
@@ -6,34 +6,6 @@
},
"inbounds": [],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [{
"address": "",
"port": 0,
"users": [{
"id": "",
"security": "auto"
}]
}],
"servers": [{
"address": "",
"method": "",
"ota": false,
"password": "",
"port": 0,
"level": 1
}]
},
"streamSettings": {
"network": "tcp"
},
"mux": {
"enabled": false
}
},
{
"protocol": "freedom",
"tag": "direct"
@@ -5,19 +5,12 @@
},
"inbounds": [],
"outbounds": [
{
"type": "vless",
"tag": "proxy",
"server": "",
"server_port": 443
},
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
]
"rules": []
}
}
@@ -1,43 +1,34 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config)
public partial class CoreConfigSingboxService(CoreConfigContext context)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigSingboxService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private SingboxConfig _coreConfig = new();
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public RetResult GenerateClientConfigContent()
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
@@ -45,44 +36,31 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
GenLog();
await GenInbounds(singboxConfig);
GenInbounds();
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
GenOutbounds();
await GenMoreOutbounds(node, singboxConfig);
GenRouting();
await GenRouting(singboxConfig);
GenDns();
await GenDns(node, singboxConfig);
GenExperimental();
await GenExperimental(singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ConvertGeo2Ruleset();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
ret.Data = ApplyFullConfigTemplate();
return ret;
}
catch (Exception ex)
@@ -93,17 +71,11 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
@@ -114,8 +86,8 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
@@ -133,10 +105,10 @@ public partial class CoreConfigSingboxService(Config config)
Logging.SaveLog(_tag, ex);
}
await GenLog(singboxConfig);
//GenDns(new(), singboxConfig);
singboxConfig.inbounds.Clear();
singboxConfig.outbounds.RemoveAt(0);
GenLog();
GenMinimizedDns();
_coreConfig.inbounds.Clear();
_coreConfig.outbounds.RemoveAt(0);
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@@ -150,7 +122,7 @@ public partial class CoreConfigSingboxService(Config config)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
var item = context.AllProxiesMap.GetValueOrDefault(it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid())
{
continue;
@@ -190,26 +162,11 @@ public partial class CoreConfigSingboxService(Config config)
type = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.type + inbound.listen_port.ToString();
singboxConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
//outbound
var server = await GenServer(item);
if (server is null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
var tag = Global.ProxyTag + inbound.listen_port.ToString();
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag);
FillRangeProxy(serverList, _coreConfig, false);
//rule
Rule4Sbox rule = new()
@@ -217,25 +174,11 @@ public partial class CoreConfigSingboxService(Config config)
inbound = new List<string> { inbound.tag },
outbound = tag
};
singboxConfig.route.rules.Add(rule);
_coreConfig.route.rules.Add(rule);
}
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (rawDNSItem != null && rawDNSItem.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -246,20 +189,20 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public RetResult GenerateClientSpeedtestConfig(int port)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
@@ -272,44 +215,20 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig);
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, item);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
GenLog();
GenOutbounds();
GenMinimizedDns();
singboxConfig.route.rules.Clear();
singboxConfig.inbounds.Clear();
singboxConfig.inbounds.Add(new()
_coreConfig.route.rules.Clear();
_coreConfig.inbounds.Clear();
_coreConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback,
@@ -319,202 +238,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
try
{
if (node == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
var addressFileName = node.Address;
if (addressFileName.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
if (!File.Exists(addressFileName))
{
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "1";
return ret;
}
if (node.Address == Global.CoreMultipleLoadConfigFileName)
{
var txtFile = File.ReadAllText(addressFileName);
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
if (singboxConfig == null)
{
File.Copy(addressFileName, fileName);
}
else
{
await GenInbounds(singboxConfig);
await GenExperimental(singboxConfig);
var content = JsonUtils.Serialize(singboxConfig, true);
await File.WriteAllTextAsync(fileName, content);
}
}
else
{
File.Copy(addressFileName, fileName);
}
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "2";
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
private string ApplyFullConfigTemplate()
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate is not { Enabled: true })
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
// Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds)
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : [];
foreach (var outbound in _coreConfig.outbounds)
{
if (outbound.type.ToLower() is "direct" or "block")
{
@@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0)
{
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints)
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : [];
foreach (var endpoint in _coreConfig.endpoints)
{
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
{
@@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
}
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
return JsonUtils.Serialize(fullConfigTemplateNode);
}
}
@@ -2,25 +2,25 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
private void GenDns()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
var item = context.RawDnsItem;
if (item is { Enabled: true })
{
return await GenDnsCompatible(node, singboxConfig);
GenDnsCustom();
return;
}
var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(node, singboxConfig, simpleDNSItem);
await GenDnsRules(node, singboxConfig, simpleDNSItem);
GenDnsServers();
GenDnsRules();
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true;
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.independent_cache = true;
// final dns
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
var useDirectDns = false;
if (routing != null)
{
@@ -32,35 +32,34 @@ public partial class CoreConfigSingboxService
lastRule.Network == "tcp,udp" ||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
}
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
var simpleDnsItem = context.SimpleDnsItem;
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
singboxConfig.dns.rules.Add(new()
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxFakeDNSTag,
query_type = new List<int> { 1, 28 }, // A and AAAA
rewrite_ttl = 1,
});
}
await GenOutboundDnsRule(node, singboxConfig);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private void GenDnsServers()
{
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
var simpleDnsItem = context.SimpleDnsItem;
var finalDns = GenBootstrapDns();
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxLocalDNSTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
@@ -71,12 +70,12 @@ public partial class CoreConfigSingboxService
type = "hosts",
predefined = new(),
};
if (simpleDNSItem.AddCommonHosts == true)
if (simpleDnsItem.AddCommonHosts == true)
{
hostsDns.predefined = Global.PredefinedHosts;
}
if (simpleDNSItem.UseSystemHosts == true)
if (simpleDnsItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0)
@@ -88,9 +87,9 @@ public partial class CoreConfigSingboxService
}
}
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
hostsDns.predefined[kvp.Key] = kvp.Value.Where(s => Utils.IsIpAddress(s)).ToList();
hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList();
}
foreach (var host in hostsDns.predefined)
@@ -109,14 +108,14 @@ public partial class CoreConfigSingboxService
}
}
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= [];
singboxConfig.dns.servers.Add(remoteDns);
singboxConfig.dns.servers.Add(directDns);
singboxConfig.dns.servers.Add(hostsDns);
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(remoteDns);
_coreConfig.dns.servers.Add(directDns);
_coreConfig.dns.servers.Add(hostsDns);
// fake ip
if (simpleDNSItem.FakeIP == true)
if (simpleDnsItem.FakeIP == true)
{
var fakeip = new Server4Sbox
{
@@ -125,68 +124,50 @@ public partial class CoreConfigSingboxService
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
};
singboxConfig.dns.servers.Add(fakeip);
_coreConfig.dns.servers.Add(fakeip);
}
// ech
var (_, dnsServer) = ParseEchParam(node?.EchConfigList);
if (dnsServer is not null)
{
dnsServer.tag = Global.SingboxEchDNSTag;
if (dnsServer.server is not null
&& hostsDns.predefined.ContainsKey(dnsServer.server))
{
dnsServer.domain_resolver = Global.SingboxHostsDNSTag;
}
else
{
dnsServer.domain_resolver = Global.SingboxLocalDNSTag;
}
singboxConfig.dns.servers.Add(dnsServer);
}
else if (node?.ConfigType.IsGroupType() == true)
{
var echDnsObject = JsonUtils.DeepCopy(directDns);
echDnsObject.tag = Global.SingboxEchDNSTag;
singboxConfig.dns.servers.Add(echDnsObject);
}
return await Task.FromResult(0);
}
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
private Server4Sbox GenBootstrapDns()
{
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(finalDns);
return await Task.FromResult(finalDns);
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(finalDns);
return finalDns;
}
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private void GenDnsRules()
{
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
var simpleDnsItem = context.SimpleDnsItem;
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
singboxConfig.dns.rules.AddRange(new[]
_coreConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(),
},
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy),
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom),
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = ERuleMode.Direct.ToString()
}
});
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
var predefined = kvp.Value.First();
if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined))
@@ -197,7 +178,7 @@ public partial class CoreConfigSingboxService
{
// xray syntactic sugar for predefined
// etc. #0 -> NOERROR
singboxConfig.dns.rules.Add(new()
_coreConfig.dns.rules.Add(new()
{
query_type = [1, 28],
domain = [kvp.Key],
@@ -225,38 +206,13 @@ public partial class CoreConfigSingboxService
};
if (ParseV2Domain(kvp.Key, rule))
{
singboxConfig.dns.rules.Add(rule);
_coreConfig.dns.rules.Add(rule);
}
}
var (ech, _) = ParseEchParam(node?.EchConfigList);
if (ech is not null)
if (simpleDnsItem.BlockBindingQuery == true)
{
var echDomain = ech.query_server_name ?? node?.Sni;
singboxConfig.dns.rules.Add(new()
{
query_type = [64, 65],
server = Global.SingboxEchDNSTag,
domain = echDomain is not null ? new List<string> { echDomain } : null,
});
}
else if (node?.ConfigType.IsGroupType() == true)
{
var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList();
if (queryServerNames.Count > 0)
{
singboxConfig.dns.rules.Add(new()
{
query_type = [64, 65],
server = Global.SingboxEchDNSTag,
domain = queryServerNames,
});
}
}
if (simpleDNSItem.BlockBindingQuery == true)
{
singboxConfig.dns.rules.Add(new()
_coreConfig.dns.rules.Add(new()
{
query_type = [64, 65],
action = "predefined",
@@ -264,7 +220,7 @@ public partial class CoreConfigSingboxService
});
}
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;
@@ -284,13 +240,13 @@ public partial class CoreConfigSingboxService
]
};
singboxConfig.dns.rules.Add(rule4Fake);
_coreConfig.dns.rules.Add(rule4Fake);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing == null)
{
return 0;
return;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
@@ -298,9 +254,9 @@ public partial class CoreConfigSingboxService
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{
var ipItems = simpleDNSItem.DirectExpectedIPs
var ipItems = simpleDnsItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
@@ -348,7 +304,7 @@ public partial class CoreConfigSingboxService
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom);
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{
@@ -373,31 +329,46 @@ public partial class CoreConfigSingboxService
}
else
{
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
var rule4Fake = JsonUtils.DeepCopy(rule);
rule4Fake.server = Global.SingboxFakeDNSTag;
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
rule4Fake.rewrite_ttl = 1;
singboxConfig.dns.rules.Add(rule4Fake);
_coreConfig.dns.rules.Add(rule4Fake);
}
rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy);
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
}
singboxConfig.dns.rules.Add(rule);
_coreConfig.dns.rules.Add(rule);
}
return 0;
}
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
private void GenMinimizedDns()
{
GenDnsServers();
foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList())
{
_coreConfig.dns.servers.Remove(server);
}
_coreConfig.dns ??= new();
_coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.Clear();
_coreConfig.dns.final = Global.SingboxDirectDNSTag;
_coreConfig.route.default_domain_resolver = new()
{
server = Global.SingboxDirectDNSTag,
};
}
private void GenDnsCustom()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
var item = context.RawDnsItem;
var strDNS = string.Empty;
if (_config.TunModeItem.EnableTun)
if (context.IsTunEnabled)
{
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
}
@@ -409,61 +380,33 @@ public partial class CoreConfigSingboxService
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null)
{
return 0;
return;
}
singboxConfig.dns = dns4Sbox;
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
_coreConfig.dns = dns4Sbox;
if (dns4Sbox.servers?.Count > 0 &&
dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomainsCompatible(singboxConfig, item);
GenDnsProtectCustom();
}
else
{
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
GenDnsProtectCustomLegacy();
}
await GenOutboundDnsRule(node, singboxConfig);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
private void GenDnsProtectCustom()
{
var dns4Sbox = singboxConfig.dns ?? new();
var dnsItem = context.RawDnsItem;
var dns4Sbox = _coreConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag;
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag;
dns4Sbox.servers.Add(new()
{
tag = tag,
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
detour = Global.DirectTag,
strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom,
});
dns4Sbox.rules.Insert(0, new()
{
server = tag,
@@ -475,56 +418,41 @@ public partial class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString()
});
var lstDomain = singboxConfig.outbounds
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
.Select(t => t.server)
.Distinct()
.ToList();
if (lstDomain != null && lstDomain.Count > 0)
{
dns4Sbox.rules.Insert(0, new()
{
server = tag,
domain = lstDomain
});
}
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
_coreConfig.dns = dns4Sbox;
}
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig)
private void GenDnsProtectCustomLegacy()
{
if (node == null)
{
return 0;
}
GenDnsProtectCustom();
List<string> domain = new();
if (Utils.IsDomain(node.Address)) // normal outbound
_coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag);
var dnsItem = context.RawDnsItem;
var localDnsServer = new Server4Sbox()
{
domain.Add(node.Address);
}
if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress
{
domain.AddRange(Utils.String2List(node.SpiderX)
.Where(Utils.IsDomain)
.Distinct()
.ToList());
}
if (domain.Count == 0)
{
return 0;
}
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress)
? Global.DomainPureIPDNSAddress.FirstOrDefault()
: dnsItem?.DomainDNSAddress,
tag = Global.SingboxLocalDNSTag,
detour = Global.DirectTag,
};
_coreConfig.dns?.servers?.Add(localDnsServer);
}
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
private Rule4Sbox BuildProtectDomainRule()
{
return new()
{
server = Global.SingboxLocalDNSTag,
domain = domain,
});
return await Task.FromResult(0);
domain = context.ProtectDomainList.ToList(),
};
}
private static Server4Sbox? ParseDnsAddress(string address)
@@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
private void GenInbounds()
{
try
{
var listen = "0.0.0.0";
singboxConfig.inbounds = [];
_coreConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box))
@@ -18,23 +18,23 @@ public partial class CoreConfigSingboxService
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
};
singboxConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
singboxConfig.inbounds.Add(inbound2);
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
_coreConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
inbound3.listen = listen;
singboxConfig.inbounds.Add(inbound3);
_coreConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
@@ -71,17 +71,16 @@ public partial class CoreConfigSingboxService
tunInbound.address = ["172.18.0.1/30"];
}
singboxConfig.inbounds.Add(tunInbound);
_coreConfig.inbounds.Add(tunInbound);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
{
var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString();
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenLog(SingboxConfig singboxConfig)
private void GenLog()
{
try
{
@@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService
case "debug":
case "info":
case "error":
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
_coreConfig.log.level = _config.CoreBasicItem.Loglevel;
break;
case "warning":
singboxConfig.log.level = "warn";
_coreConfig.log.level = "warn";
break;
default:
@@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService
}
if (_config.CoreBasicItem.Loglevel == Global.None)
{
singboxConfig.log.disabled = true;
_coreConfig.log.disabled = true;
}
if (_config.CoreBasicItem.LogEnabled)
{
var dtNow = DateTime.Now;
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}
File diff suppressed because it is too large Load Diff
@@ -2,23 +2,23 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenRouting(SingboxConfig singboxConfig)
private void GenRouting()
{
try
{
singboxConfig.route.final = Global.ProxyTag;
var simpleDnsItem = _config.SimpleDNSItem;
_coreConfig.route.final = Global.ProxyTag;
var simpleDnsItem = context.SimpleDnsItem;
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
var rawDNSItem = context.RawDnsItem;
if (rawDNSItem is { Enabled: true })
{
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
}
singboxConfig.route.default_domain_resolver = new()
_coreConfig.route.default_domain_resolver = new()
{
server = defaultDomainResolverTag,
strategy = directDnsStrategy
@@ -26,23 +26,23 @@ public partial class CoreConfigSingboxService
if (_config.TunModeItem.EnableTun)
{
singboxConfig.route.auto_detect_interface = true;
_coreConfig.route.auto_detect_interface = true;
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
if (tunRules != null)
{
singboxConfig.route.rules.AddRange(tunRules);
_coreConfig.route.rules.AddRange(tunRules);
}
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
singboxConfig.route.rules.Add(new()
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
_coreConfig.route.rules.Add(new()
{
port = new() { 53 },
port = [53],
action = "hijack-dns",
process_name = lstDnsExe
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
process_name = lstDirectExe
@@ -51,66 +51,62 @@ public partial class CoreConfigSingboxService
if (_config.Inbound.First().SniffingEnabled)
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
action = "sniff"
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
protocol = new() { "dns" },
protocol = ["dns"],
action = "hijack-dns"
});
}
else
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
port = new() { 53 },
network = new() { "udp" },
port = [53],
network = ["udp"],
action = "hijack-dns"
});
}
var hostsDomains = new List<string>();
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (dnsItem == null || !dnsItem.Enabled)
if (rawDNSItem is not { Enabled: true })
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts);
hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key));
if (simpleDnsItem.UseSystemHosts == true)
{
var systemHostsMap = Utils.GetSystemHosts();
foreach (var kvp in systemHostsMap)
{
hostsDomains.Add(kvp.Key);
}
hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key));
}
}
if (hostsDomains.Count > 0)
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
action = "resolve",
domain = hostsDomains,
});
}
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
clash_mode = ERuleMode.Direct.ToString()
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.ProxyTag,
clash_mode = ERuleMode.Global.ToString()
});
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
var routing = context.RoutingItem;
if (routing.DomainStrategy4Singbox.IsNotEmpty())
{
domainStrategy = defaultRouting.DomainStrategy4Singbox;
domainStrategy = routing.DomainStrategy4Singbox;
}
var resolveRule = new Rule4Sbox
{
@@ -119,10 +115,9 @@ public partial class CoreConfigSingboxService
};
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
{
singboxConfig.route.rules.Add(resolveRule);
_coreConfig.route.rules.Add(resolveRule);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null)
{
@@ -139,7 +134,7 @@ public partial class CoreConfigSingboxService
continue;
}
await GenRoutingUserRule(item1, singboxConfig);
GenRoutingUserRule(item1);
if (item1.Ip?.Count > 0)
{
@@ -149,10 +144,10 @@ public partial class CoreConfigSingboxService
}
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
{
singboxConfig.route.rules.Add(resolveRule);
_coreConfig.route.rules.Add(resolveRule);
foreach (var item2 in ipRules)
{
await GenRoutingUserRule(item2, singboxConfig);
GenRoutingUserRule(item2);
}
}
}
@@ -160,10 +155,9 @@ public partial class CoreConfigSingboxService
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
{
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -187,20 +181,22 @@ public partial class CoreConfigSingboxService
}
}
lstDnsExe = new List<string>(dnsExeSet);
lstDirectExe = new List<string>(directExeSet);
var lstDnsExe = new List<string>(dnsExeSet);
var lstDirectExe = new List<string>(directExeSet);
return (lstDnsExe, lstDirectExe);
}
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
private void GenRoutingUserRule(RulesItem? item)
{
try
{
if (item == null)
{
return 0;
return;
}
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules;
item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag);
var rules = _coreConfig.route.rules;
var rule = new Rule4Sbox();
if (item.OutboundTag == "block")
@@ -326,10 +322,9 @@ public partial class CoreConfigSingboxService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private bool ParseV2Domain(string domain, Rule4Sbox rule)
private static bool ParseV2Domain(string domain, Rule4Sbox rule)
{
if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{
@@ -368,7 +363,7 @@ public partial class CoreConfigSingboxService
return true;
}
private bool ParseV2Address(string address, Rule4Sbox rule)
private static bool ParseV2Address(string address, Rule4Sbox rule)
{
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
{
@@ -401,14 +396,14 @@ public partial class CoreConfigSingboxService
return true;
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
private string GenRoutingUserRuleOutbound(string outboundTag)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
@@ -418,39 +413,15 @@ public partial class CoreConfigSingboxService
}
var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag))
|| (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag))))
{
return tag;
}
if (node.ConfigType.IsGroupType())
{
var ret = await GenGroupOutbound(node, singboxConfig, tag);
if (ret == 0)
{
return tag;
}
return Global.ProxyTag;
}
var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
FillRangeProxy(proxyOutbounds, _coreConfig, false);
var server = await GenServer(node);
if (server is null)
{
return Global.ProxyTag;
}
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
return server.tag;
return tag;
}
}
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
private void ConvertGeo2Ruleset()
{
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{
@@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService
var ruleSets = new List<string>();
//convert route geosite & geoip to ruleset
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null;
AddRuleSets(ruleSets, rule.rule_set);
}
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
@@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService
}
//convert dns geosite & geoip to ruleset
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null;
}
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
rule.geoip = null;
}
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
{
AddRuleSets(ruleSets, dnsRule.rule_set);
}
//rules in rules
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
{
foreach (var item2 in item ?? [])
{
@@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService
//load custom ruleset file
List<Ruleset4Sbox> customRulesets = [];
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
{
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
@@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService
var localSrss = Utils.GetBinPath("srss");
//Add ruleset srs
singboxConfig.route.rule_set = [];
_coreConfig.route.rule_set = [];
foreach (var item in new HashSet<string>(ruleSets))
{
if (item.IsNullOrEmpty())
@@ -113,9 +113,7 @@ public partial class CoreConfigSingboxService
};
}
}
singboxConfig.route.rule_set.Add(customRuleset);
_coreConfig.route.rule_set.Add(customRuleset);
}
return 0;
}
}
@@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
private void GenExperimental()
{
//if (_config.guiItem.enableStatistics)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
_coreConfig.experimental ??= new Experimental4Sbox();
_coreConfig.experimental.clash_api = new Clash_Api4Sbox()
{
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
};
@@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
_coreConfig.experimental ??= new Experimental4Sbox();
_coreConfig.experimental.cache_file = new CacheFile4Sbox()
{
enabled = true,
path = Utils.GetBinPath("cache.db"),
store_fakeip = _config.SimpleDNSItem.FakeIP == true
store_fakeip = context.SimpleDnsItem.FakeIP == true
};
}
return await Task.FromResult(0);
}
}
@@ -1,44 +1,35 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService(Config config)
public partial class CoreConfigV2rayService(CoreConfigContext context)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigV2rayService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private V2rayConfig _coreConfig = new();
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public RetResult GenerateClientConfigContent()
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
if (_node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
@@ -46,30 +37,28 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
GenLog();
await GenInbounds(v2rayConfig);
GenInbounds();
await GenOutbound(node, v2rayConfig.outbounds.First());
GenOutbounds();
await GenMoreOutbounds(node, v2rayConfig);
GenRouting();
await GenRouting(v2rayConfig);
GenDns();
await GenDns(node, v2rayConfig);
await GenStatistic(v2rayConfig);
GenStatistic();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
ret.Data = ApplyFullConfigTemplate();
return ret;
}
catch (Exception ex)
@@ -80,18 +69,11 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
@@ -102,172 +84,8 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
//add rule
var rules = v2rayConfig.routing.rules;
if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0))
{
var balancerTagSet = v2rayConfig.routing.balancers
.Select(b => b.tag)
.ToHashSet();
foreach (var rule in rules)
{
if (rule.outboundTag == null)
{
continue;
}
if (balancerTagSet.Contains(rule.outboundTag))
{
rule.balancerTag = rule.outboundTag;
rule.outboundTag = null;
continue;
}
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
if (balancerTagSet.Contains(outboundWithSuffix))
{
rule.balancerTag = outboundWithSuffix;
rule.outboundTag = null;
}
}
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
v2rayConfig.routing.rules.Add(new()
{
ip = ["0.0.0.0/0", "::/0"],
balancerTag = defaultBalancerTag,
type = "field"
});
}
else
{
v2rayConfig.routing.rules.Add(new()
{
network = "tcp,udp",
balancerTag = defaultBalancerTag,
type = "field"
});
}
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
@@ -285,10 +103,10 @@ public partial class CoreConfigV2rayService(Config config)
Logging.SaveLog(_tag, ex);
}
await GenLog(v2rayConfig);
v2rayConfig.inbounds.Clear();
v2rayConfig.outbounds.Clear();
v2rayConfig.routing.rules.Clear();
GenLog();
_coreConfig.inbounds.Clear();
_coreConfig.outbounds.Clear();
_coreConfig.routing.rules.Clear();
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@@ -302,7 +120,7 @@ public partial class CoreConfigV2rayService(Config config)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
var item = context.AllProxiesMap.GetValueOrDefault(it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid())
{
continue;
@@ -342,27 +160,40 @@ public partial class CoreConfigV2rayService(Config config)
protocol = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.protocol + inbound.port.ToString();
v2rayConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
var tag = Global.ProxyTag + inbound.port.ToString();
var isBalancer = false;
//outbound
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(item, outbound);
outbound.tag = Global.ProxyTag + inbound.port.ToString();
v2rayConfig.outbounds.Add(outbound);
var proxyOutbounds =
new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag);
_coreConfig.outbounds.AddRange(proxyOutbounds);
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
{
isBalancer = true;
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
//rule
RulesItem4Ray rule = new()
{
inboundTag = new List<string> { inbound.tag },
outboundTag = outbound.tag,
outboundTag = tag,
type = "field"
};
v2rayConfig.routing.rules.Add(rule);
if (isBalancer)
{
rule.balancerTag = tag;
rule.outboundTag = null;
}
_coreConfig.routing.rules.Add(rule);
}
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -373,21 +204,21 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public RetResult GenerateClientSpeedtestConfig(int port)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
if (_node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
@@ -398,20 +229,19 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
await GenOutbound(node, v2rayConfig.outbounds.First());
await GenMoreOutbounds(node, v2rayConfig);
GenLog();
GenOutbounds();
v2rayConfig.routing.rules.Clear();
v2rayConfig.inbounds.Clear();
v2rayConfig.inbounds.Add(new()
_coreConfig.routing.rules.Clear();
_coreConfig.inbounds.Clear();
_coreConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.socks}{port}",
listen = Global.Loopback,
@@ -421,7 +251,7 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{
// Collect all existing subject selectors from both observatories
var subjectSelectors = new List<string>();
subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []);
subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []);
subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []);
subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []);
// Case 1: exact match already exists -> nothing to do
if (subjectSelectors.Any(baseTagName.StartsWith))
{
return await Task.FromResult(0);
return;
}
// Case 2: prefix match exists -> reuse it and move to the first position
@@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService
{
baseTagName = matched;
if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
{
v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName);
v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
_coreConfig.burstObservatory.subjectSelector.Remove(baseTagName);
_coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
}
if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
{
v2rayConfig.observatory.subjectSelector.Remove(baseTagName);
v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName);
_coreConfig.observatory.subjectSelector.Remove(baseTagName);
_coreConfig.observatory.subjectSelector.Insert(0, baseTagName);
}
return await Task.FromResult(0);
return;
}
// Case 3: need to create or insert based on multipleLoad type
if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
{
if (v2rayConfig.burstObservatory is null)
if (_coreConfig.burstObservatory is null)
{
// Create new burst observatory with default ping config
v2rayConfig.burstObservatory = new BurstObservatory4Ray
_coreConfig.burstObservatory = new BurstObservatory4Ray
{
subjectSelector = [baseTagName],
pingConfig = new()
@@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService
}
else
{
v2rayConfig.burstObservatory.subjectSelector ??= new();
v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName);
_coreConfig.burstObservatory.subjectSelector ??= new();
_coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
}
}
else if (multipleLoad is EMultipleLoad.LeastPing)
{
if (v2rayConfig.observatory is null)
if (_coreConfig.observatory is null)
{
// Create new observatory with default probe config
v2rayConfig.observatory = new Observatory4Ray
_coreConfig.observatory = new Observatory4Ray
{
subjectSelector = [baseTagName],
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
@@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService
}
else
{
v2rayConfig.observatory.subjectSelector ??= new();
v2rayConfig.observatory.subjectSelector.Add(baseTagName);
_coreConfig.observatory.subjectSelector ??= new();
_coreConfig.observatory.subjectSelector.Add(baseTagName);
}
}
return await Task.FromResult(0);
}
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
{
var strategyType = multipleLoad switch
{
@@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService
},
tag = balancerTag,
};
v2rayConfig.routing.balancers ??= new();
v2rayConfig.routing.balancers.Add(balancer);
return await Task.FromResult(balancerTag);
_coreConfig.routing.balancers ??= new();
_coreConfig.routing.balancers.Add(balancer);
}
}
@@ -2,35 +2,39 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig)
private string ApplyFullConfigTemplate()
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
{
return JsonUtils.Serialize(v2rayConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(v2rayConfig);
return JsonUtils.Serialize(_coreConfig);
}
// Handle balancer and rules modifications (for multiple load scenarios)
if (v2rayConfig.routing?.balancers?.Count > 0)
if (_coreConfig.routing?.balancers?.Count > 0)
{
var balancer = v2rayConfig.routing.balancers.First();
var balancer =
_coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null);
// Modify existing rules in custom config
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
if (rulesNode != null)
if (balancer != null)
{
foreach (var rule in rulesNode.AsArray())
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
if (rulesNode != null)
{
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
foreach (var rule in rulesNode.AsArray())
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
}
}
}
}
@@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService
// Handle balancers - append instead of override
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers)
{
foreach (var balancerNode in newBalancers)
{
@@ -54,33 +58,33 @@ public partial class CoreConfigV2rayService
}
else
{
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers));
}
}
if (v2rayConfig.observatory != null)
if (_coreConfig.observatory != null)
{
if (fullConfigTemplateNode["observatory"] == null)
{
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory));
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory));
}
else
{
var subjectSelector = v2rayConfig.observatory.subjectSelector;
var subjectSelector = _coreConfig.observatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
}
}
if (v2rayConfig.burstObservatory != null)
if (_coreConfig.burstObservatory != null)
{
if (fullConfigTemplateNode["burstObservatory"] == null)
{
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory));
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory));
}
else
{
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector;
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
}
@@ -88,7 +92,7 @@ public partial class CoreConfigV2rayService
var customOutboundsNode = new JsonArray();
foreach (var outbound in v2rayConfig.outbounds)
foreach (var outbound in _coreConfig.outbounds)
{
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
{
@@ -97,8 +101,8 @@ public partial class CoreConfigV2rayService
continue;
}
}
else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
&& ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true))
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
{
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
?? outbound.settings?.vnext?.FirstOrDefault()?.address
@@ -123,6 +127,6 @@ public partial class CoreConfigV2rayService
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
return JsonUtils.Serialize(fullConfigTemplateNode);
}
}
@@ -2,46 +2,45 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
private void GenDns()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
var item = context.RawDnsItem;
if (item is { Enabled: true })
{
var result = await GenDnsCompatible(node, v2rayConfig);
GenDnsCustom();
if (v2rayConfig.routing.domainStrategy != Global.IPIfNonMatch)
if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch)
{
return result;
return;
}
// DNS routing
var dnsObj = JsonUtils.SerializeToNode(v2rayConfig.dns);
var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
if (dnsObj == null)
{
return result;
return;
}
dnsObj["tag"] = Global.DnsTag;
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
v2rayConfig.routing.rules.Add(new RulesItem4Ray
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
return result;
return;
}
var simpleDnsItem = _config.SimpleDNSItem;
var dnsItem = v2rayConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
var simpleDnsItem = context.SimpleDnsItem;
var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy
if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs)
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new()
@@ -59,23 +58,23 @@ public partial class CoreConfigV2rayService
var xraySupportConfigTypeNames = Global.XraySupportConfigType
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
.ToHashSet();
v2rayConfig.outbounds
_coreConfig.outbounds
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
.ToList()
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
}
await GenDnsServers(node, dnsItem, simpleDnsItem);
await GenDnsHosts(dnsItem, simpleDnsItem);
FillDnsServers(dnsItem);
FillDnsHosts(dnsItem);
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing
dnsItem.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
@@ -83,17 +82,17 @@ public partial class CoreConfigV2rayService
});
}
v2rayConfig.dns = dnsItem;
_coreConfig.dns = dnsItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(ProfileItem? node, Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem)
private void FillDnsServers(Dns4Ray dnsItem)
{
var simpleDNSItem = context.SimpleDnsItem;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
@@ -197,9 +196,9 @@ public partial class CoreConfigV2rayService
}
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
List<RulesItem>? rules = null;
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
rules = JsonUtils.Deserialize<List<RulesItem>>(routing?.RuleSet) ?? [];
foreach (var item in rules)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
@@ -246,27 +245,9 @@ public partial class CoreConfigV2rayService
}
}
if (Utils.IsDomain(node?.Address))
if (context.ProtectDomainList.Count > 0)
{
directDomainList.Add(node.Address);
}
if (node?.Subid is not null)
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
{
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
if (profileNode is not null
&& Global.XraySupportConfigType.Contains(profileNode.ConfigType)
&& Utils.IsDomain(profileNode.Address))
{
directDomainList.Add(profileNode.Address);
}
}
}
directDomainList.AddRange(context.ProtectDomainList);
}
dnsItem.servers ??= [];
@@ -300,15 +281,14 @@ public partial class CoreConfigV2rayService
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
dnsItem.servers.AddRange(defaultDnsServers);
return 0;
}
private async Task<int> GenDnsHosts(Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem)
private void FillDnsHosts(Dns4Ray dnsItem)
{
var simpleDNSItem = context.SimpleDnsItem;
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
{
return await Task.FromResult(0);
return;
}
dnsItem.hosts ??= new Dictionary<string, object>();
if (simpleDNSItem.AddCommonHosts == true)
@@ -337,14 +317,13 @@ public partial class CoreConfigV2rayService
{
dnsItem.hosts[kvp.Key] = kvp.Value;
}
return await Task.FromResult(0);
}
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
private void GenDnsCustom()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
var item = context.RawDnsItem;
var normalDNS = item?.NormalDNS;
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
if (normalDNS.IsNullOrEmpty())
@@ -355,7 +334,7 @@ public partial class CoreConfigV2rayService
//Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty())
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new();
@@ -410,63 +389,37 @@ public partial class CoreConfigV2rayService
}
}
await GenDnsDomainsCompatible(node, obj, item);
FillDnsDomainsCustom(obj);
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem)
private void FillDnsDomainsCustom(JsonNode dns)
{
if (node == null)
{
return 0;
}
var servers = dns["servers"];
if (servers != null)
if (servers == null)
{
var domainList = new List<string>();
if (Utils.IsDomain(node.Address))
{
domainList.Add(node.Address);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
// Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
&& Utils.IsDomain(prevNode.Address))
{
domainList.Add(prevNode.Address);
}
// Next proxy
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
&& Utils.IsDomain(nextNode.Address))
{
domainList.Add(nextNode.Address);
}
}
if (domainList.Count > 0)
{
var dnsServer = new DnsServer4Ray()
{
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
skipFallback = true,
domains = domainList
};
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
return;
}
return await Task.FromResult(0);
var domainList = context.ProtectDomainList;
if (domainList.Count <= 0)
{
return;
}
var dnsItem = context.RawDnsItem;
var dnsServer = new DnsServer4Ray()
{
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
skipFallback = true,
domains = domainList.ToList(),
};
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
}
@@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
private void GenInbounds()
{
try
{
var listen = "0.0.0.0";
v2rayConfig.inbounds = [];
_coreConfig.inbounds = [];
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
v2rayConfig.inbounds.Add(inbound);
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
_coreConfig.inbounds.Add(inbound);
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
v2rayConfig.inbounds.Add(inbound2);
var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
_coreConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
inbound3.listen = listen;
v2rayConfig.inbounds.Add(inbound3);
_coreConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{
inbound3.settings.auth = "password";
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
inbound3.settings.accounts = new List<AccountsItem4Ray> { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
}
}
else
@@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
{
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
if (result.IsNullOrEmpty())
@@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenLog(V2rayConfig v2rayConfig)
private void GenLog()
{
try
{
if (_config.CoreBasicItem.LogEnabled)
{
var dtNow = DateTime.Now;
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
_coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
}
else
{
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = null;
v2rayConfig.log.error = null;
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
_coreConfig.log.access = null;
_coreConfig.log.error = null;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}
@@ -2,13 +2,94 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound)
private void GenOutbounds()
{
var proxyOutboundList = BuildAllProxyOutbounds();
_coreConfig.outbounds.InsertRange(0, proxyOutboundList);
if (proxyOutboundList.Count(n => n.tag.StartsWith(Global.ProxyTag)) > 1)
{
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad);
GenBalancer(multipleLoad);
}
}
private List<Outbounds4Ray> BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag)
{
var proxyOutboundList = new List<Outbounds4Ray>();
if (_node.ConfigType.IsGroupType())
{
proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName));
}
else
{
proxyOutboundList.Add(BuildProxyOutbound(baseTagName));
}
if (_config.CoreBasicItem.EnableFragment)
{
var fragmentOutbound = new Outbounds4Ray
{
protocol = "freedom",
tag = $"frag-{Global.ProxyTag}",
settings = new()
{
fragment = new()
{
packets = _config.Fragment4RayItem?.Packets,
length = _config.Fragment4RayItem?.Length,
interval = _config.Fragment4RayItem?.Interval
}
}
};
var actOutboundWithTlsList =
proxyOutboundList.Where(n => n.streamSettings?.security.IsNullOrEmpty() == false
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true));
foreach (var outbound in actOutboundWithTlsList)
{
var fragmentOutboundClone = JsonUtils.DeepCopy(fragmentOutbound);
fragmentOutboundClone.tag = $"frag-{outbound.tag}";
outbound.streamSettings.sockopt = new()
{
dialerProxy = fragmentOutboundClone.tag
};
proxyOutboundList.Add(fragmentOutboundClone);
}
}
return proxyOutboundList;
}
private List<Outbounds4Ray> BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag)
{
var proxyOutboundList = new List<Outbounds4Ray>();
switch (_node.ConfigType)
{
case EConfigType.PolicyGroup:
proxyOutboundList.AddRange(BuildOutboundsList(baseTagName));
break;
case EConfigType.ProxyChain:
proxyOutboundList.AddRange(BuildChainOutboundsList(baseTagName));
break;
}
return proxyOutboundList;
}
private Outbounds4Ray BuildProxyOutbound(string baseTagName = Global.ProxyTag)
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
FillOutbound(outbound);
outbound.tag = baseTagName;
return outbound;
}
private void FillOutbound(Outbounds4Ray outbound)
{
try
{
var protocolExtra = node.GetProtocolExtra();
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType)
var protocolExtra = _node.GetProtocolExtra();
var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (_node.ConfigType)
{
case EConfigType.VMess:
{
@@ -22,8 +103,8 @@ public partial class CoreConfigV2rayService
{
vnextItem = outbound.settings.vnext.First();
}
vnextItem.address = node.Address;
vnextItem.port = node.Port;
vnextItem.address = _node.Address;
vnextItem.port = _node.Port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
@@ -36,7 +117,7 @@ public partial class CoreConfigV2rayService
usersItem = vnextItem.users.First();
}
usersItem.id = node.Password;
usersItem.id = _node.Password;
usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity))
@@ -48,7 +129,7 @@ public partial class CoreConfigV2rayService
usersItem.security = Global.DefaultSecurity;
}
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
FillOutboundMux(outbound, muxEnabled, muxEnabled);
outbound.settings.servers = null;
break;
@@ -65,16 +146,16 @@ public partial class CoreConfigV2rayService
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.password = node.Password;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod)
serversItem.address = _node.Address;
serversItem.port = _node.Port;
serversItem.password = _node.Password;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod)
? protocolExtra.SsMethod : "none";
serversItem.ota = false;
serversItem.level = 1;
await GenOutboundMux(node, outbound);
FillOutboundMux(outbound);
outbound.settings.vnext = null;
break;
@@ -92,25 +173,25 @@ public partial class CoreConfigV2rayService
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.address = _node.Address;
serversItem.port = _node.Port;
serversItem.method = null;
serversItem.password = null;
if (node.Username.IsNotEmpty()
&& node.Password.IsNotEmpty())
if (_node.Username.IsNotEmpty()
&& _node.Password.IsNotEmpty())
{
SocksUsersItem4Ray socksUsersItem = new()
{
user = node.Username ?? "",
pass = node.Password,
user = _node.Username ?? "",
pass = _node.Password,
level = 1
};
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
}
await GenOutboundMux(node, outbound);
FillOutboundMux(outbound);
outbound.settings.vnext = null;
break;
@@ -127,8 +208,8 @@ public partial class CoreConfigV2rayService
{
vnextItem = outbound.settings.vnext.First();
}
vnextItem.address = node.Address;
vnextItem.port = node.Port;
vnextItem.address = _node.Address;
vnextItem.port = _node.Port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
@@ -140,7 +221,7 @@ public partial class CoreConfigV2rayService
{
usersItem = vnextItem.users.First();
}
usersItem.id = node.Password;
usersItem.id = _node.Password;
usersItem.email = Global.UserEMail;
usersItem.encryption = protocolExtra.VlessEncryption;
@@ -150,7 +231,7 @@ public partial class CoreConfigV2rayService
}
else
{
await GenOutboundMux(node, outbound, false, muxEnabled);
FillOutboundMux(outbound, false, muxEnabled);
}
outbound.settings.servers = null;
break;
@@ -167,14 +248,14 @@ public partial class CoreConfigV2rayService
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.password = node.Password;
serversItem.address = _node.Address;
serversItem.port = _node.Port;
serversItem.password = _node.Password;
serversItem.ota = false;
serversItem.level = 1;
await GenOutboundMux(node, outbound);
FillOutboundMux(outbound);
outbound.settings.vnext = null;
break;
@@ -184,8 +265,8 @@ public partial class CoreConfigV2rayService
outbound.settings = new()
{
version = 2,
address = node.Address,
port = node.Port,
address = _node.Address,
port = _node.Port,
};
outbound.settings.vnext = null;
outbound.settings.servers = null;
@@ -193,7 +274,7 @@ public partial class CoreConfigV2rayService
}
case EConfigType.WireGuard:
{
var address = node.Address;
var address = _node.Address;
if (Utils.IsIpv6(address))
{
address = $"[{address}]";
@@ -201,12 +282,12 @@ public partial class CoreConfigV2rayService
var peer = new WireguardPeer4Ray
{
publicKey = protocolExtra.WgPublicKey ?? "",
endpoint = address + ":" + node.Port.ToString()
endpoint = address + ":" + _node.Port.ToString()
};
var setting = new Outboundsettings4Ray
{
address = Utils.String2List(protocolExtra.WgInterfaceAddress),
secretKey = node.Password,
secretKey = _node.Password,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(),
peers = [peer]
@@ -218,21 +299,20 @@ public partial class CoreConfigV2rayService
}
}
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
if (node.ConfigType == EConfigType.Hysteria2)
outbound.protocol = Global.ProtocolTypes[_node.ConfigType];
if (_node.ConfigType == EConfigType.Hysteria2)
{
outbound.protocol = "hysteria";
}
await GenBoundStreamSettings(node, outbound);
FillBoundStreamSettings(outbound);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
private void FillOutboundMux(Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
{
try
{
@@ -255,48 +335,40 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound)
private void FillBoundStreamSettings(Outbounds4Ray outbound)
{
try
{
var streamSettings = outbound.streamSettings;
var network = node.GetNetwork();
if (node.ConfigType == EConfigType.Hysteria2)
var network = _node.GetNetwork();
if (_node.ConfigType == EConfigType.Hysteria2)
{
network = "hysteria";
}
streamSettings.network = network;
var host = node.RequestHost.TrimEx();
var path = node.Path.TrimEx();
var sni = node.Sni.TrimEx();
var host = _node.RequestHost.TrimEx();
var path = _node.Path.TrimEx();
var sni = _node.Sni.TrimEx();
var useragent = "";
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
{
try
{
useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent];
}
catch (KeyNotFoundException)
{
useragent = _config.CoreBasicItem.DefUserAgent;
}
useragent = Global.UserAgentTexts.GetValueOrDefault(_config.CoreBasicItem.DefUserAgent, _config.CoreBasicItem.DefUserAgent);
}
//if tls
if (node.StreamSecurity == Global.StreamSecurity)
if (_node.StreamSecurity == Global.StreamSecurity)
{
streamSettings.security = node.StreamSecurity;
streamSettings.security = _node.StreamSecurity;
TlsSettings4Ray tlsSettings = new()
{
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(),
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
echConfigList = node.EchConfigList.NullIfEmpty(),
echForceQuery = node.EchForceQuery.NullIfEmpty()
allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure),
alpn = _node.GetAlpn(),
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
echConfigList = _node.EchConfigList.NullIfEmpty(),
echForceQuery = _node.EchForceQuery.NullIfEmpty()
};
if (sni.IsNotEmpty())
{
@@ -306,7 +378,7 @@ public partial class CoreConfigV2rayService
{
tlsSettings.serverName = Utils.String2List(host)?.First();
}
var certs = CertPemManager.ParsePemChain(node.Cert);
var certs = CertPemManager.ParsePemChain(_node.Cert);
if (certs.Count > 0)
{
var certsettings = new List<CertificateSettings4Ray>();
@@ -323,27 +395,27 @@ public partial class CoreConfigV2rayService
tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false;
}
else if (!node.CertSha.IsNullOrEmpty())
else if (!_node.CertSha.IsNullOrEmpty())
{
tlsSettings.pinnedPeerCertSha256 = node.CertSha;
tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
tlsSettings.allowInsecure = false;
}
streamSettings.tlsSettings = tlsSettings;
}
//if Reality
if (node.StreamSecurity == Global.StreamSecurityReality)
if (_node.StreamSecurity == Global.StreamSecurityReality)
{
streamSettings.security = node.StreamSecurity;
streamSettings.security = _node.StreamSecurity;
TlsSettings4Ray realitySettings = new()
{
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
serverName = sni,
publicKey = node.PublicKey,
shortId = node.ShortId,
spiderX = node.SpiderX,
mldsa65Verify = node.Mldsa65Verify,
publicKey = _node.PublicKey,
shortId = _node.ShortId,
spiderX = _node.SpiderX,
mldsa65Verify = _node.Mldsa65Verify,
show = false,
};
@@ -367,14 +439,14 @@ public partial class CoreConfigV2rayService
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
streamSettings.finalmask ??= new();
if (Global.KcpHeaderMaskMap.TryGetValue(node.HeaderType, out var header))
if (Global.KcpHeaderMaskMap.TryGetValue(_node.HeaderType, out var header))
{
streamSettings.finalmask.udp =
[
new Mask4Ray
{
type = header,
settings = node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null
settings = _node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null
}
];
}
@@ -444,17 +516,17 @@ public partial class CoreConfigV2rayService
{
xhttpSettings.host = host;
}
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType))
if (_node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(_node.HeaderType))
{
xhttpSettings.mode = node.HeaderType;
xhttpSettings.mode = _node.HeaderType;
}
if (node.Extra.IsNotEmpty())
if (_node.Extra.IsNotEmpty())
{
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra);
xhttpSettings.extra = JsonUtils.ParseJson(_node.Extra);
}
streamSettings.xhttpSettings = xhttpSettings;
await GenOutboundMux(node, outbound);
FillOutboundMux(outbound);
break;
//h2
@@ -478,11 +550,11 @@ public partial class CoreConfigV2rayService
key = path,
header = new Header4Ray
{
type = node.HeaderType
type = _node.HeaderType
}
};
streamSettings.quicSettings = quicsettings;
if (node.StreamSecurity == Global.StreamSecurity)
if (_node.StreamSecurity == Global.StreamSecurity)
{
if (sni.IsNotEmpty())
{
@@ -490,7 +562,7 @@ public partial class CoreConfigV2rayService
}
else
{
streamSettings.tlsSettings.serverName = node.Address;
streamSettings.tlsSettings.serverName = _node.Address;
}
}
break;
@@ -500,7 +572,7 @@ public partial class CoreConfigV2rayService
{
authority = host.NullIfEmpty(),
serviceName = path,
multiMode = node.HeaderType == Global.GrpcMultiMode,
multiMode = _node.HeaderType == Global.GrpcMultiMode,
idle_timeout = _config.GrpcItem.IdleTimeout,
health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
permit_without_stream = _config.GrpcItem.PermitWithoutStream,
@@ -510,7 +582,7 @@ public partial class CoreConfigV2rayService
break;
case "hysteria":
var protocolExtra = node.GetProtocolExtra();
var protocolExtra = _node.GetProtocolExtra();
var ports = protocolExtra?.Ports;
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
? su
@@ -536,7 +608,7 @@ public partial class CoreConfigV2rayService
streamSettings.hysteriaSettings = new()
{
version = 2,
auth = node.Password,
auth = _node.Password,
up = upMbps > 0 ? $"{upMbps}mbps" : null,
down = downMbps > 0 ? $"{downMbps}mbps" : null,
udphop = udpHop,
@@ -557,13 +629,13 @@ public partial class CoreConfigV2rayService
default:
//tcp
if (node.HeaderType == Global.TcpHeaderHttp)
if (_node.HeaderType == Global.TcpHeaderHttp)
{
TcpSettings4Ray tcpSettings = new()
{
header = new Header4Ray
{
type = node.HeaderType
type = _node.HeaderType
}
};
@@ -587,415 +659,142 @@ public partial class CoreConfigV2rayService
}
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (!node.ConfigType.IsGroupType())
if (!_node.Finalmask.IsNullOrEmpty())
{
return -1;
}
var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, v2rayConfig, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName);
break;
default:
break;
}
//add balancers
if (node.ConfigType == EConfigType.PolicyGroup)
{
var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
await GenObservatory(v2rayConfig, multipleLoad, baseTagName);
await GenBalancer(v2rayConfig, multipleLoad, baseTagName);
streamSettings.finalmask = JsonUtils.Deserialize<Finalmask4Ray>(_node.Finalmask);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
private List<Outbounds4Ray> BuildOutboundsList(string baseTagName = Global.ProxyTag)
{
//fragment proxy
if (_config.CoreBasicItem.EnableFragment
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
var nodes = new List<ProfileItem>();
foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? [])
{
var fragmentOutbound = new Outbounds4Ray
if (context.AllProxiesMap.TryGetValue(nodeId, out var node))
{
protocol = "freedom",
tag = $"frag-{Global.ProxyTag}",
settings = new()
{
fragment = new()
{
packets = _config.Fragment4RayItem?.Packets,
length = _config.Fragment4RayItem?.Length,
interval = _config.Fragment4RayItem?.Interval
}
}
};
v2rayConfig.outbounds.Add(fragmentOutbound);
v2rayConfig.outbounds.First().streamSettings.sockopt = new()
{
dialerProxy = fragmentOutbound.tag
};
return 0;
}
if (node.Subid.IsNullOrEmpty())
{
return 0;
}
try
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is null)
{
return 0;
}
//current proxy
var outbound = v2rayConfig.outbounds.First();
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
//Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag;
v2rayConfig.outbounds.Add(prevOutbound);
}
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
if (nextOutbound is not null)
{
v2rayConfig.outbounds.Insert(0, nextOutbound);
nodes.Add(node);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenOutboundsListWithChain(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{
try
{
// Get template and initialize list
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (txtOutbound.IsNullOrEmpty())
{
return 0;
}
var resultOutbounds = new List<Outbounds4Ray>();
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
// Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
var prevIndex = 0; // Index for prev outbounds
// Process nodes
var index = 0;
foreach (var node in nodes)
{
index++;
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
}
var childBaseTagName = $"{baseTagName}-{index}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
continue;
}
// Handle proxy chain
string? prevTag = null;
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextOutbound != null)
{
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
// current proxy
await GenOutbound(node, currentOutbound);
currentOutbound.tag = $"{baseTagName}-{index}";
if (!node.Subid.IsNullOrEmpty())
{
if (prevProxyTags.TryGetValue(node.Subid, out var value))
{
prevTag = value; // maybe null
}
else
{
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevTag = $"prev-{baseTagName}-{++prevIndex}";
prevOutbound.tag = prevTag;
prevOutbounds.Add(prevOutbound);
}
prevProxyTags[node.Subid] = prevTag;
}
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
if (!nextProxyCache.ContainsKey(node.Subid))
{
nextProxyCache[node.Subid] = nextOutbound;
}
}
if (nextOutbound is not null)
{
resultOutbounds.Add(nextOutbound);
}
resultOutbounds.Add(currentOutbound);
}
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
if (baseTagName == Global.ProxyTag)
{
resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(prevOutbounds);
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
/// <summary>
/// Generates a chained outbound configuration for the given subItem and outbound.
/// The outbound's tag must be set before calling this method.
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
/// </summary>
/// <param name="subItem">The subscription item containing proxy chain information.</param>
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
/// <returns>
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
/// </returns>
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (!prevOutboundTag.IsNullOrEmpty())
{
outbound.streamSettings.sockopt = new()
{
dialerProxy = prevOutboundTag
};
}
// Next proxy
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& Global.XraySupportConfigType.Contains(nextNode.ConfigType))
{
if (nextOutbound == null)
{
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
}
nextOutbound.tag = outbound.tag;
outbound.tag = $"mid-{outbound.tag}";
nextOutbound.streamSettings.sockopt = new()
{
dialerProxy = outbound.tag
};
}
return nextOutbound;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return null;
}
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{
var resultOutbounds = new List<Outbounds4Ray>();
for (var i = 0; i < nodes.Count; i++)
{
var node = nodes[i];
if (node == null)
{
continue;
}
var currentTag = $"{baseTagName}-{i + 1}";
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
}
var childBaseTagName = $"{baseTagName}-{i + 1}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag);
resultOutbounds.AddRange(childProfiles);
continue;
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (txtOutbound.IsNullOrEmpty())
{
break;
}
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var result = await GenOutbound(node, outbound);
if (result != 0)
{
break;
}
outbound.tag = baseTagName + (i + 1).ToString();
var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound();
outbound.tag = currentTag;
resultOutbounds.Add(outbound);
}
if (baseTagName == Global.ProxyTag)
{
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
return await Task.FromResult(0);
return resultOutbounds;
}
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
private List<Outbounds4Ray> BuildChainOutboundsList(string baseTagName = Global.ProxyTag)
{
var nodes = new List<ProfileItem>();
foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? [])
{
if (context.AllProxiesMap.TryGetValue(nodeId, out var node))
{
nodes.Add(node);
}
}
// Based on actual network flow instead of data packets
var nodesReverse = nodes.AsEnumerable().Reverse().ToList();
var resultOutbounds = new List<Outbounds4Ray>();
for (var i = 0; i < nodesReverse.Count; i++)
{
var node = nodesReverse[i];
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (txtOutbound.IsNullOrEmpty())
var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}";
var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null;
if (node.ConfigType.IsGroupType())
{
break;
var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag);
if (!dialerProxyTag.IsNullOrEmpty())
{
var chainEndNodes =
childProfiles.Where(n => n?.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true);
foreach (var chainEndNode in chainEndNodes)
{
chainEndNode.streamSettings.sockopt = new()
{
dialerProxy = dialerProxyTag
};
}
}
if (i != 0)
{
var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList();
if (chainStartNodes.Count == 1)
{
foreach (var existedChainEndNode in resultOutbounds.Where(n => n.streamSettings?.sockopt?.dialerProxy == currentTag))
{
existedChainEndNode.streamSettings.sockopt = new()
{
dialerProxy = chainStartNodes.First().tag
};
}
}
else if (chainStartNodes.Count > 1)
{
var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds);
resultOutbounds.Clear();
var j = 0;
foreach (var chainStartNode in chainStartNodes)
{
var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes);
foreach (var existedChainNode in existedChainNodesClone)
{
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
existedChainNode.tag = cloneTag;
}
for (var k = 0; k < existedChainNodesClone.Count; k++)
{
var existedChainNode = existedChainNodesClone[k];
var previousDialerProxyTag = existedChainNode.streamSettings?.sockopt?.dialerProxy;
var nextTag = k + 1 < existedChainNodesClone.Count
? existedChainNodesClone[k + 1].tag
: chainStartNode.tag;
existedChainNode.streamSettings.sockopt = new()
{
dialerProxy = (previousDialerProxyTag == currentTag)
? chainStartNode.tag
: nextTag
};
resultOutbounds.Add(existedChainNode);
}
j++;
}
}
}
resultOutbounds.AddRange(childProfiles);
continue;
}
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var result = await GenOutbound(node, outbound);
var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound();
if (result != 0)
{
break;
}
outbound.tag = currentTag;
if (i == 0)
{
outbound.tag = baseTagName;
}
else
{
// avoid v2ray observe
outbound.tag = "chain-" + baseTagName + i.ToString();
}
if (i != nodesReverse.Count - 1)
if (!dialerProxyTag.IsNullOrEmpty())
{
outbound.streamSettings.sockopt = new()
{
dialerProxy = "chain-" + baseTagName + (i + 1).ToString()
dialerProxy = dialerProxyTag
};
}
resultOutbounds.Add(outbound);
}
if (baseTagName == Global.ProxyTag)
{
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
return await Task.FromResult(0);
return resultOutbounds;
}
}
@@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
private void GenRouting()
{
try
{
if (v2rayConfig.routing?.rules != null)
if (_coreConfig.routing?.rules != null)
{
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
_coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing != null)
{
if (routing.DomainStrategy.IsNotEmpty())
{
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
_coreConfig.routing.domainStrategy = routing.DomainStrategy;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules)
@@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService
}
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
GenRoutingUserRule(item2);
}
}
var balancerTagList = _coreConfig.routing.balancers
?.Select(p => p.tag)
.ToList() ?? [];
if (balancerTagList.Count > 0)
{
foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix)))
{
rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix;
rulesItem.outboundTag = null;
}
}
}
@@ -40,95 +51,94 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
private void GenRoutingUserRule(RulesItem4Ray? userRule)
{
try
{
if (rule == null)
if (userRule == null)
{
return 0;
return;
}
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag);
if (rule.port.IsNullOrEmpty())
if (userRule.port.IsNullOrEmpty())
{
rule.port = null;
userRule.port = null;
}
if (rule.network.IsNullOrEmpty())
if (userRule.network.IsNullOrEmpty())
{
rule.network = null;
userRule.network = null;
}
if (rule.domain?.Count == 0)
if (userRule.domain?.Count == 0)
{
rule.domain = null;
userRule.domain = null;
}
if (rule.ip?.Count == 0)
if (userRule.ip?.Count == 0)
{
rule.ip = null;
userRule.ip = null;
}
if (rule.protocol?.Count == 0)
if (userRule.protocol?.Count == 0)
{
rule.protocol = null;
userRule.protocol = null;
}
if (rule.inboundTag?.Count == 0)
if (userRule.inboundTag?.Count == 0)
{
rule.inboundTag = null;
userRule.inboundTag = null;
}
if (rule.process?.Count == 0)
if (userRule.process?.Count == 0)
{
rule.process = null;
userRule.process = null;
}
var hasDomainIp = false;
if (rule.domain?.Count > 0)
if (userRule.domain?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.ip = null;
it.process = null;
it.type = "field";
for (var k = it.domain.Count - 1; k >= 0; k--)
{
if (it.domain[k].StartsWith("#"))
if (it.domain[k].StartsWith('#'))
{
it.domain.RemoveAt(k);
}
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
}
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (rule.ip?.Count > 0)
if (userRule.ip?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.domain = null;
it.process = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (rule.process?.Count > 0)
if (userRule.process?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.domain = null;
it.ip = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (!hasDomainIp)
{
if (rule.port.IsNotEmpty()
|| rule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0
|| rule.network != null
if (userRule.port.IsNotEmpty()
|| userRule.protocol?.Count > 0
|| userRule.inboundTag?.Count > 0
|| userRule.network != null
)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.type = "field";
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
}
}
}
@@ -136,17 +146,16 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
private string GenRoutingUserRuleOutbound(string outboundTag)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
@@ -156,27 +165,20 @@ public partial class CoreConfigV2rayService
}
var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (v2rayConfig.outbounds.Any(p => p.tag == tag))
if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag)))
{
return tag;
}
if (node.ConfigType.IsGroupType())
var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
_coreConfig.outbounds.AddRange(proxyOutbounds);
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
{
var ret = await GenGroupOutbound(node, v2rayConfig, tag);
if (ret == 0)
{
return tag;
}
return Global.ProxyTag;
var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(node, outbound);
outbound.tag = tag;
v2rayConfig.outbounds.Add(outbound);
return outbound.tag;
return tag;
}
}
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
private void GenStatistic()
{
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{
@@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService
Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new();
v2rayConfig.stats = new Stats4Ray();
_coreConfig.stats = new Stats4Ray();
apiObj.tag = tag;
v2rayConfig.metrics = apiObj;
_coreConfig.metrics = apiObj;
policySystemSetting.statsOutboundDownlink = true;
policySystemSetting.statsOutboundUplink = true;
policyObj.system = policySystemSetting;
v2rayConfig.policy = policyObj;
_coreConfig.policy = policyObj;
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
if (!_coreConfig.inbounds.Exists(item => item.tag == tag))
{
Inbounds4Ray apiInbound = new();
Inboundsettings4Ray apiInboundSettings = new();
@@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService
apiInbound.protocol = Global.InboundAPIProtocol;
apiInboundSettings.address = Global.Loopback;
apiInbound.settings = apiInboundSettings;
v2rayConfig.inbounds.Add(apiInbound);
_coreConfig.inbounds.Add(apiInbound);
}
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag))
{
RulesItem4Ray apiRoutingRule = new()
{
@@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService
type = "field"
};
v2rayConfig.routing.rules.Add(apiRoutingRule);
_coreConfig.routing.rules.Add(apiRoutingRule);
}
}
return await Task.FromResult(0);
}
}
@@ -247,11 +247,6 @@ public class AddServerViewModel : MyReactiveObject
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
@@ -277,11 +272,6 @@ public class AddServerViewModel : MyReactiveObject
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
@@ -801,7 +801,8 @@ public class ProfilesViewModel : MyReactiveObject
if (blClipboard)
{
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, null);
if (result.Success != true)
{
NoticeManager.Instance.Enqueue(result.Msg);
@@ -824,7 +825,8 @@ public class ProfilesViewModel : MyReactiveObject
{
return;
}
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
{
NoticeManager.Instance.Enqueue(result.Msg);
@@ -32,7 +32,7 @@
IsCancel="True" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid
Grid.Row="0"
@@ -722,11 +722,51 @@
Text="{x:Static resx:ResUI.TbPath}" />
</Grid>
<Separator Grid.Row="5" Margin="{StaticResource MarginTb8}" />
<Grid
x:Name="gridFinalmask"
Grid.Row="5"
ColumnDefinitions="300,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFinalmask}" />
<Button
Grid.Row="0"
Grid.Column="1"
Margin="{StaticResource MarginLr4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="IconButton">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBox
x:Name="txtFinalmask"
Width="400"
Margin="{StaticResource Margin4}"
AcceptsReturn="True"
Classes="TextArea"
TextWrapping="NoWrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
<Separator Grid.Row="6" Margin="{StaticResource MarginTb8}" />
<Grid
x:Name="gridTls"
Grid.Row="6"
Grid.Row="7"
ColumnDefinitions="300,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -745,7 +785,7 @@
</Grid>
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
Grid.Row="8"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
@@ -908,7 +948,7 @@
</Grid>
<Grid
x:Name="gridRealityMore"
Grid.Row="7"
Grid.Row="8"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
@@ -996,7 +1036,7 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</Grid>
<Separator Grid.Row="8" Margin="{StaticResource MarginTb8}" />
<Separator Grid.Row="9" Margin="{StaticResource MarginTb8}" />
</Grid>
</ScrollViewer>
</DockPanel>
@@ -78,6 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
gridFinalmask.IsVisible = false;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
break;
@@ -95,6 +96,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridAnytls.IsVisible = true;
lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbCoreType.IsEnabled = false;
gridFinalmask.IsVisible = false;
break;
}
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@@ -194,6 +196,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
@@ -60,6 +60,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
@@ -928,12 +930,47 @@
Text="{x:Static resx:ResUI.TbPath}" />
</Grid>
<Grid x:Name="gridFinalmask" Grid.Row="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFinalmask}" />
<materialDesign:PopupBox
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
StaysOpen="True"
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
<StackPanel>
<TextBox
x:Name="txtFinalmask"
Width="400"
Margin="{StaticResource Margin4}"
AcceptsReturn="True"
HorizontalScrollBarVisibility="Auto"
MaxLines="8"
MinLines="4"
Style="{StaticResource MyOutlinedTextBox}"
TextWrapping="NoWrap"
VerticalScrollBarVisibility="Auto" />
</StackPanel>
</materialDesign:PopupBox>
</Grid>
<Separator
Grid.Row="5"
Grid.Row="6"
Margin="0,2"
Style="{DynamicResource MaterialDesignSeparator}" />
<Grid x:Name="gridTls" Grid.Row="6">
<Grid x:Name="gridTls" Grid.Row="7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
@@ -963,7 +1000,7 @@
</Grid>
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
Grid.Row="8"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -1152,7 +1189,7 @@
</Grid>
<Grid
x:Name="gridRealityMore"
Grid.Row="7"
Grid.Row="8"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -1264,7 +1301,7 @@
Style="{StaticResource DefTextBox}" />
</Grid>
<Separator
Grid.Row="8"
Grid.Row="9"
Margin="0,2"
Style="{DynamicResource MaterialDesignSeparator}" />
</Grid>
@@ -73,6 +73,7 @@ public partial class AddServerWindow
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
gridFinalmask.Visibility = Visibility.Collapsed;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
break;
@@ -90,6 +91,7 @@ public partial class AddServerWindow
gridAnytls.Visibility = Visibility.Visible;
cmbCoreType.IsEnabled = false;
lstStreamSecurity.Add(Global.StreamSecurityReality);
gridFinalmask.Visibility = Visibility.Collapsed;
break;
}
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@@ -190,6 +192,8 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 24
targetSdk = 36
versionCode = 710
versionName = "2.0.10"
versionCode = 711
versionName = "2.0.11"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
@@ -51,6 +51,7 @@ object AppConfig {
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val PREF_DOUBLE_COLUMN_DISPLAY = "pref_double_column_display"
const val PREF_GROUP_ALL_DISPLAY = "pref_group_all_display"
const val PREF_LANGUAGE = "pref_language"
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
@@ -76,7 +77,6 @@ object AppConfig {
/** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
/** Protocol identifiers. */
const val PROTOCOL_FREEDOM = "freedom"
@@ -11,6 +11,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.SubscriptionCache
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
@@ -237,23 +238,77 @@ object AngConfigManager {
}
val subItem = MmkvManager.decodeSubscription(subid)
var count = 0
// Parse all configs first (no I/O during parsing)
val configs = mutableListOf<ProfileItem>()
servers.lines()
.distinct()
.reversed()
.forEach {
val resId = parseConfig(it, subid, subItem, removedSelectedServer)
if (resId == 0) {
count++
val config = parseConfig(it, subid, subItem)
if (config != null) {
configs.add(config)
}
}
return count
// Batch save all parsed configs (only one serverList read/write)
if (configs.isNotEmpty()) {
val keys = batchSaveConfigs(configs, subid)
// Handle removed selected server
removedSelectedServer?.let { removed ->
val matchKey = keys.find { key ->
val savedConfig = MmkvManager.decodeServerConfig(key)
savedConfig != null &&
savedConfig.server == removed.server &&
savedConfig.serverPort == removed.serverPort
}
matchKey?.let { MmkvManager.setSelectServer(it) }
}
}
return configs.size
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to parse batch config", e)
}
return 0
}
/**
* Batch save configurations to reduce serverList read/write operations.
* Reads serverList once, saves all configs, then writes serverList once.
*
* @param configs The list of ProfileItem to save.
* @param subid The subscription ID.
* @return The list of generated keys.
*/
private fun batchSaveConfigs(configs: List<ProfileItem>, subid: String): List<String> {
val keys = mutableListOf<String>()
// Read serverList once
val serverList = MmkvManager.decodeServerList(subid)
var needSetSelected = MmkvManager.getSelectServer().isNullOrBlank()
configs.forEach { config ->
val key = Utils.getUuid()
// Save profile directly without updating serverList
MmkvManager.encodeProfileDirect(key, JsonUtil.toJson(config))
if (!serverList.contains(key)) {
serverList.add(0, key)
if (needSetSelected) {
MmkvManager.setSelectServer(key)
needSetSelected = false
}
}
keys.add(key)
}
// Write serverList once
MmkvManager.encodeServerList(serverList, subid)
return keys
}
/**
* Parses a custom configuration server.
*
@@ -319,22 +374,21 @@ object AngConfigManager {
/**
* Parses the configuration from a QR code or string.
* Only parses and returns ProfileItem, does not save.
*
* @param str The configuration string.
* @param subid The subscription ID.
* @param subItem The subscription item.
* @param removedSelectedServer The removed selected server.
* @return The result code.
* @return The parsed ProfileItem or null if parsing fails or filtered out.
*/
private fun parseConfig(
str: String?,
subid: String,
subItem: SubscriptionItem?,
removedSelectedServer: ProfileItem?
): Int {
subItem: SubscriptionItem?
): ProfileItem? {
try {
if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data
return null
}
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
@@ -356,28 +410,24 @@ object AngConfigManager {
}
if (config == null) {
return R.string.toast_incorrect_protocol
return null
}
//filter
if (subItem?.filter != null && subItem.filter?.isNotEmpty() == true && config.remarks.isNotEmpty()) {
val matched = Regex(pattern = subItem.filter ?: "")
// Apply filter
if (subItem?.filter.isNotNullEmpty() && config.remarks.isNotNullEmpty()) {
val matched = Regex(pattern = subItem?.filter.orEmpty())
.containsMatchIn(input = config.remarks)
if (!matched) return -1
if (!matched) return null
}
config.subscriptionId = subid
config.description = generateDescription(config)
val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null &&
config.server == removedSelectedServer.server && config.serverPort == removedSelectedServer.serverPort
) {
MmkvManager.setSelectServer(guid)
}
return config
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to parse config", e)
return -1
return null
}
return 0
}
/**
@@ -225,6 +225,16 @@ object MmkvManager {
return key
}
/**
* Encodes the server configuration directly without updating serverList.
*
* @param key The server GUID.
* @param configJson The server configuration JSON string.
*/
fun encodeProfileDirect(key: String, configJson: String) {
profileFullStorage.encode(key, configJson)
}
/**
* Removes the server configuration.
*
@@ -79,5 +79,6 @@ class MmkvPreferenceDataStore : PreferenceDataStore() {
}
// Notify listeners that require service restart or reinit
SettingsChangeManager.makeRestartService()
SettingsChangeManager.makeSetupGroupTab()
}
}
@@ -6,7 +6,7 @@ import android.os.IBinder
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.V2RayNativeManager
import com.v2ray.ang.util.MessageUtil
import java.util.Collections
@@ -54,8 +54,14 @@ class V2RayTestService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.getIntExtra("key", 0)) {
MSG_MEASURE_CONFIG -> {
val guidsList = intent.serializable<ArrayList<String>>("content")
if (guidsList != null && guidsList.isNotEmpty()) {
val subscriptionId = intent.getStringExtra("content").orEmpty()
val guidsList = if (subscriptionId.isEmpty()) {
MmkvManager.decodeAllServerList()
} else {
MmkvManager.decodeServerList(subscriptionId)
}
if (guidsList.isNotEmpty()) {
lateinit var worker: RealPingWorkerService
worker = RealPingWorkerService(this, guidsList) { status ->
// notify UI and remove the worker from active list when finished
@@ -37,8 +37,6 @@ import java.util.Collections
class MainViewModel(application: Application) : AndroidViewModel(application) {
private var serverList = mutableListOf<String>() // MmkvManager.decodeServerList()
var subscriptionId: String = MmkvManager.decodeSettingsString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
//var keywordFilter: String = MmkvManager.MmkvManager.decodeSettingsString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
var keywordFilter = ""
val serversCache = mutableListOf<ServersCache>()
val isRunning by lazy { MutableLiveData<Boolean>() }
@@ -149,6 +147,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
@Synchronized
fun updateCache() {
serversCache.clear()
val kw = keywordFilter.trim().lowercase()
for (guid in serverList) {
val profile = MmkvManager.decodeServerConfig(guid) ?: continue
// var profile = MmkvManager.decodeProfileConfig(guid)
@@ -167,8 +166,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
// if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) {
// continue
// }
if (kw.isEmpty()) {
serversCache.add(ServersCache(guid, profile))
continue
}
if (keywordFilter.isEmpty() || profile.remarks.lowercase().contains(keywordFilter.lowercase())) {
val remarks = profile.remarks.lowercase()
val description = profile.description.orEmpty().lowercase()
val server = profile.server.orEmpty().lowercase()
if (remarks.contains(kw) || description.contains(kw) || server.contains(kw)) {
serversCache.add(ServersCache(guid, profile))
}
}
@@ -240,13 +247,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
updateListAction.value = -1
val serversCopy = serversCache.toList()
viewModelScope.launch(Dispatchers.Default) {
val guids = ArrayList<String>(serversCopy.map { it.guid })
if (guids.isEmpty()) {
if (serversCache.isEmpty()) {
return@launch
}
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, guids)
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, subscriptionId)
}
}
@@ -283,7 +288,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
val groups = mutableListOf<GroupMapItem>()
if (subscriptions.count() > 1) {
if (subscriptions.size > 1
&& MmkvManager.decodeSettingsBool(AppConfig.PREF_GROUP_ALL_DISPLAY)
) {
groups.add(
GroupMapItem(
id = "",
@@ -430,7 +437,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
return
}
keywordFilter = keyword
MmkvManager.encodeSettings(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
reloadServerList()
}
@@ -234,6 +234,8 @@
<string name="title_pref_double_column_display">Enable double column display</string>
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">ملاحظات</string>
<string name="summary_pref_feedback">ملاحظات التحسينات أو الأخطاء إلى GitHub</string>
@@ -234,6 +234,8 @@
<string name="title_pref_double_column_display">Enable double column display</string>
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">মতামত</string>
<string name="summary_pref_feedback">মতামত উন্নয়ন বা বাগগুলি GitHub-এ পাঠান</string>
@@ -234,6 +234,8 @@
<string name="title_pref_double_column_display">ره وندن نشووݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ره وستن، وا برنومه ن ز نۊ ره ونین.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا گوزارش موشکلا من Github</string>
@@ -232,6 +232,8 @@
<string name="title_pref_double_column_display">فعال کردن نمایش دو ستون</string>
<string name="summary_pref_double_column_display">لیست نمایه در دو ستون نمایش داده می شود و امکان نمایش محتوای بیشتری را بر روی صفحه نمایش می دهد. برای اجرا باید برنامه را مجددا راه اندازی کنید.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">بازخورد</string>
<string name="summary_pref_feedback">بازخورد یا گزارش اشکالات در گیت‌ هاب</string>
@@ -233,6 +233,8 @@
<string name="title_pref_double_column_display">Профили в два столбца</string>
<string name="summary_pref_double_column_display">Список профилей отображается двумя столбцами, что позволяет показать больше информации на экране. Требуется перезапуск приложения.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">Обратная связь</string>
<string name="summary_pref_feedback">Предложить улучшение или сообщить об ошибке на GitHub</string>
@@ -234,6 +234,8 @@
<string name="title_pref_double_column_display">Enable double column display</string>
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">Phản hồi lỗi</string>
<string name="summary_pref_feedback">Phản hồi cải tiến hoặc lỗi lên GitHub</string>
@@ -231,6 +231,9 @@
<string name="title_pref_double_column_display">启用双列显示</string>
<string name="summary_pref_double_column_display">配置列表以双列显示,允许在屏幕上显示更多内容。需要重启应用生效。</string>
<string name="title_pref_group_all_display">启用显示所有组</string>
<string name="summary_pref_group_all_display">添加一个额外的“所有选项卡”页面</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">反馈</string>
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
@@ -232,6 +232,9 @@
<string name="title_pref_double_column_display">啟用雙列顯示</string>
<string name="summary_pref_double_column_display">設定檔清單以雙列顯示,允許在螢幕上顯示更多內容。需要重啟應用生效。</string>
<string name="title_pref_group_all_display">啟用顯示所有群組</string>
<string name="summary_pref_group_all_display">新增一個額外的「所有選項卡」頁面</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">意見回饋</string>
<string name="summary_pref_feedback">前往 GitHub 回報錯誤</string>
@@ -237,6 +237,9 @@
<string name="title_pref_double_column_display">Enable double column display</string>
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">Feedback</string>
<string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string>
@@ -23,6 +23,11 @@
android:summary="@string/summary_pref_double_column_display"
android:title="@string/title_pref_double_column_display" />
<CheckBoxPreference
android:key="pref_group_all_display"
android:summary="@string/summary_pref_group_all_display"
android:title="@string/title_pref_group_all_display" />
<ListPreference
android:defaultValue="auto"
android:entries="@array/language_select"