Compare commits

...

22 commits

Author SHA1 Message Date
2dust
45c987fd86 up 7.14.3
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-23 16:36:12 +08:00
2dust
7bec05ec23 Fix
https://github.com/2dust/v2rayN/issues/7819
2025-08-23 16:28:52 +08:00
2dust
606b216cd0 Press the Esc button to close the window
https://github.com/2dust/v2rayN/issues/7819
2025-08-23 16:23:30 +08:00
2dust
bb4f33559f Code clean 2025-08-21 19:55:17 +08:00
2dust
c7f3e53f28 Customize MenuFlyoutMaxHeight for desktop version
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-08-21 19:32:39 +08:00
JieXu
0035e836d7
Update build-linux.yml, Add RPM package for RHEL. (#7813)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Update build-linux.yml

* Update build-linux.yml

* Update build-linux.yml

* Update build-linux.yml

* Update package-rhel.sh

* Update package-rhel.sh. Change describe information

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh
2025-08-21 17:18:19 +08:00
2dust
e6da14f4a8 up 7.14.2
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-08-19 19:05:55 +08:00
2dust
f748f1849c Update Directory.Packages.props 2025-08-19 19:05:20 +08:00
DHR60
f8995b78f6
Passes srsName as third format argument (#7805) 2025-08-19 17:00:27 +08:00
JieXu
a861020828
Update package-rhel.sh (#7806) 2025-08-19 16:59:52 +08:00
DHR60
dc94962900
Fix tun (#7802)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-19 09:10:54 +08:00
JieXu
4a40b87bba
Update package-rhel.sh (#7799) 2025-08-19 09:09:52 +08:00
DHR60
4853e2348d
Fix dns (#7797) 2025-08-19 09:09:35 +08:00
2dust
e104f9f9b2 Rename Manager
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-18 20:09:58 +08:00
JieXu
876381a7fb
Create package-rhel.sh (#7770)
* Create package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh
2025-08-18 17:29:10 +08:00
Miheichev Aleksandr Sergeevich
4f711b1bd3
i18n(ru/zh-Hans/zh-Hant/hu/fa): translate TUN settings, unify MTU, use resx (#7787)
* feat(i18n,ui): externalize TUN settings labels, add translations

- Replace hard-coded labels "Auto Route", "Strict Route", "Stack",
  and "Mtu/mtu" with resource keys in both Avalonia and WPF views:
  - v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml
  - v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml
  - v2rayN/v2rayN/Views/AddServerWindow.xaml
  - v2rayN/v2rayN/Views/OptionSettingWindow.xaml
- Add new resource keys in ResUI:
  TbSettingsTunAutoRoute, TbSettingsTunStrictRoute,
  TbSettingsTunStack, TbSettingsTunMtu (unified casing as "MTU").
  Files:
  - v2rayN/ServiceLib/Resx/ResUI.resx
- Provide translations in:
  - v2rayN/ServiceLib/Resx/ResUI.ru.resx
  - v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
  - v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
  - v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx
  - v2rayN/ServiceLib/Resx/ResUI.hu.resx
- Normalize XML comments/whitespace in .resx files.
- Update submodule v2rayN/GlobalHotKeys to 5201dd5.

No breaking changes.

* i18n: TUN labels across locales; unify MTU

* chore: ignore local IDE/venv files

* chore(resx): regenerate ResUI.Designer with TUN string accessors

- Add strongly-typed accessors in ServiceLib.Resx.ResUI:
  - TbSettingsTunAutoRoute → "Auto Route"
  - TbSettingsTunStrictRoute → "Strict Route"
  - TbSettingsTunStack → "Stack"
  - TbSettingsTunMtu → "MTU"
- Keep auto-generated structure intact; normalize minor whitespace.

Refs: v2rayN/ServiceLib/Resx/ResUI.resx
No functional changes beyond exposing new i18n keys.

* chore(gitignore): ignore JetBrains Rider artifacts (.idea/, *.sln.iml)

---------

Co-authored-by: Aleksandr Miheichev <alexandr.gmail@tuta.com>
2025-08-18 17:28:59 +08:00
DHR60
89893c0945
Adds Xray and Singbox support config type (#7789)
* Adds Xray and Singbox config type support

* Unify multiline logical expression formatting
2025-08-18 17:28:49 +08:00
2dust
7b7fe0ef46 Refactoring GetRealPingTime
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-08-17 20:51:49 +08:00
2dust
f66226c103 Simple refactoring of CoreConfig generated code 2025-08-17 20:09:41 +08:00
2dust
d5c50ef27c Rename Manager 2025-08-17 17:31:55 +08:00
2dust
2060ac18fd Add Manager folder 2025-08-17 16:52:51 +08:00
2dust
c9c1cd8cbb Add Helper folder 2025-08-17 16:26:13 +08:00
127 changed files with 6467 additions and 5375 deletions

View file

@ -99,3 +99,37 @@ jobs:
tag: ${{ github.event.inputs.release_tag }} tag: ${{ github.event.inputs.release_tag }}
file_glob: true file_glob: true
prerelease: true prerelease: true
# release RHEL package
- name: Package RPM (RHEL-family)
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-rhel.sh
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
- name: Collect RPMs into workspace
if: github.event.inputs.release_tag != ''
run: |
mkdir -p "${{ github.workspace }}/dist/rpm"
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
# Rename to requested filenames
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
- name: Upload RPM artifacts
if: github.event.inputs.release_tag != ''
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-rpm
path: |
${{ github.workspace }}/dist/rpm/**/*.rpm
- name: Upload RPMs to release
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true

1
.gitignore vendored
View file

@ -397,4 +397,5 @@ FodyWeavers.xsd
*.msp *.msp
# JetBrains Rider # JetBrains Rider
.idea/
*.sln.iml *.sln.iml

815
package-rhel.sh Normal file
View file

@ -0,0 +1,815 @@
#!/usr/bin/env bash
set -euo pipefail
# ===== Require Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ====
if [[ -r /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
rhel|rocky|almalinux|centos|ubuntu|debian)
echo "[OK] Detected supported system: $NAME $VERSION_ID"
;;
*)
echo "[ERROR] Unsupported system: $NAME ($ID)."
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
exit 1
;;
esac
else
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
exit 1
fi
# ===== Config & Parse arguments =========================================================
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
WITH_CORE="both" # Default: bundle both xray+sing-box
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
# If the first argument starts with --, do not treat it as a version number
if [[ "${VERSION_ARG:-}" == --* ]]; then
VERSION_ARG=""
fi
# Take the first non --* argument as version, discard it
if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
# Parse remaining optional arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--with-core) WITH_CORE="${2:-both}"; shift 2;;
--autostart) AUTOSTART=1; shift;;
--xray-ver) XRAY_VER="${2:-}"; shift 2;;
--singbox-ver) SING_VER="${2:-}"; shift 2;;
--netcore) FORCE_NETCORE=1; shift;;
--arch) ARCH_OVERRIDE="${2:-}"; shift 2;;
--buildfrom) BUILD_FROM="${2:-}"; shift 2;;
*)
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
shift;;
esac
done
# Conflict: version number AND --buildfrom cannot be used together
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
exit 1
fi
# ===== Environment check + Dependencies ========================================
host_arch="$(uname -m)"
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
install_ok=0
case "$ID" in
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
rhel|rocky|almalinux|centos)
if command -v dnf >/dev/null 2>&1; then
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
install_ok=1
elif command -v yum >/dev/null 2>&1; then
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
install_ok=1
fi
;;
# ------------------------------ Ubuntu ----------------------------------------------
ubuntu)
sudo apt-get update
# Ensure 'universe' (Ubuntu) to get 'rpm'
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
sudo apt-get -y install software-properties-common || true
sudo add-apt-repository -y universe || true
sudo apt-get update
fi
# Base tools + rpm (provides rpmbuild)
sudo apt-get -y install curl unzip tar rsync rpm || true
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
exit 1
fi
# .NET SDK 8 (best effort via apt)
if ! command -v dotnet >/dev/null 2>&1; then
sudo apt-get -y install dotnet-sdk-8.0 || true
sudo apt-get -y install dotnet-sdk-8 || true
sudo apt-get -y install dotnet-sdk || true
fi
install_ok=1
;;
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
debian)
sudo apt-get update
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure 'rpm' is available from Debian repos."
exit 1
fi
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
if ! command -v dotnet >/dev/null 2>&1; then
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
export DOTNET_ROOT="$HOME/.dotnet"
if ! command -v dotnet >/dev/null 2>&1; then
echo "[ERROR] dotnet installation failed."
exit 1
fi
fi
install_ok=1
;;
esac
if [[ "$install_ok" -ne 1 ]]; then
echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:"
echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)"
fi
command -v curl >/dev/null
# Root directory = the script's location
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Git submodules (best effort)
if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
# ===== Locate project ================================================================
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
if [[ ! -f "$PROJECT" ]]; then
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
fi
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
# ===== Resolve GUI version & auto checkout ============================================
VERSION=""
choose_channel() {
# If --buildfrom provided, map it directly and skip interaction.
if [[ -n "${BUILD_FROM:-}" ]]; then
case "$BUILD_FROM" in
1) echo "latest"; return 0;;
2) echo "prerelease"; return 0;;
3) echo "keep"; return 0;;
*) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;;
esac
fi
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
local ch="latest" sel=""
if [[ -t 0 ]]; then
echo "[?] Choose v2rayN release channel:" >&2
echo " 1) Latest (stable) [default]" >&2
echo " 2) Pre-release (preview)" >&2
echo " 3) Keep current (do nothing)" >&2
printf "Enter 1, 2 or 3 [default 1]: " >&2
if read -r sel </dev/tty; then
case "${sel:-}" in
2) ch="prerelease" ;;
3) ch="keep" ;;
*) ch="latest" ;;
esac
else
ch="latest"
fi
else
ch="latest"
fi
echo "$ch"
}
get_latest_tag_latest() {
# Resolve /releases/latest → tag_name
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
}
get_latest_tag_prerelease() {
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
local json tag
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
# 1) Use jq if present
if command -v jq >/dev/null 2>&1; then
tag="$(printf '%s' "$json" \
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
| sed 's/^v//')" || true
fi
# 2) Fallback to sed/grep only
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
tag="$(printf '%s' "$json" \
| tr '\n' ' ' \
| sed 's/},[[:space:]]*{/\n/g' \
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
fi
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
printf '%s\n' "$tag"
}
git_try_checkout() {
# Try a series of refs and checkout when found.
local want="$1" ref=""
if git rev-parse --git-dir >/dev/null 2>&1; then
git fetch --tags --force --prune --depth=1 || true
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
ref="v${want}"
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
ref="${want}"
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
ref="${want}"
fi
if [[ -n "$ref" ]]; then
echo "[OK] Found ref '${ref}', checking out..."
git checkout -f "${ref}"
if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
return 0
fi
fi
return 1
}
if git rev-parse --git-dir >/dev/null 2>&1; then
if [[ -n "${VERSION_ARG:-}" ]]; then
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
if git_try_checkout "${VERSION_ARG#v}"; then
VERSION="${VERSION_ARG#v}"
else
echo "[WARN] Tag '${VERSION_ARG}' not found."
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
VERSION="${VERSION_ARG:-}"
if [[ -z "$VERSION" ]]; then
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
fi
VERSION="${VERSION#v}"
fi
echo "[*] GUI version resolved as: ${VERSION}"
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
download_xray() {
# Download Xray core and install to outdir/xray
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
mkdir -p "$outdir"
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
else
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
fi
echo "[+] Download xray: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$zipname"
unzip -q "$tmp/$zipname" -d "$tmp"
install -Dm755 "$tmp/xray" "$outdir/xray"
}
download_singbox() {
# Download sing-box core and install to outdir/sing-box
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
mkdir -p "$outdir"
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
else
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
fi
echo "[+] Download sing-box: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$tarname"
tar -C "$tmp" -xzf "$tmp/$tarname"
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
install -Dm755 "$bin" "$outdir/sing-box"
}
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
download_mihomo() {
# Download mihomo into outroot/bin/mihomo/mihomo
local outroot="$1"
local url=""
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
fi
echo "[+] Download mihomo: $url"
mkdir -p "$outroot/bin/mihomo"
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
chmod +x "$outroot/bin/mihomo/mihomo" || true
}
# Move geo files to a unified path: outroot/bin/xray/
unify_geo_layout() {
local outroot="$1"
mkdir -p "$outroot/bin/xray"
local srcs=( \
"$outroot/bin/geosite.dat" \
"$outroot/bin/geoip.dat" \
"$outroot/bin/geoip-only-cn-private.dat" \
"$outroot/bin/Country.mmdb" \
"$outroot/bin/geoip.metadb" \
)
for s in "${srcs[@]}"; do
if [[ -f "$s" ]]; then
mv -f "$s" "$outroot/bin/xray/$(basename "$s")"
fi
done
}
# Download geo/rule assets; then unify to bin/xray/
download_geo_assets() {
local outroot="$1"
local bin_dir="$outroot/bin"
local srss_dir="$bin_dir/srss"
mkdir -p "$bin_dir" "$srss_dir"
echo "[+] Download Xray Geo to ${bin_dir}"
curl -fsSL -o "$bin_dir/geosite.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
curl -fsSL -o "$bin_dir/geoip.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
curl -fsSL -o "$bin_dir/Country.mmdb" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
echo "[+] Download sing-box rule DB & rule-sets"
curl -fsSL -o "$bin_dir/geoip.metadb" \
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
for f in \
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
done
for f in \
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
done
# Unify to bin/xray/
unify_geo_layout "$outroot"
}
# Prefer the prebuilt v2rayN core bundle; then unify geo layout
download_v2rayn_bundle() {
local outroot="$1"
local url=""
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
fi
echo "[+] Try v2rayN bundle archive: $url"
local tmp zipname
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
if [[ -d "$tmp/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$tmp/bin/" "$outroot/bin/"
else
rsync -a "$tmp/" "$outroot/"
fi
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
# keep mihomo
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$nested_dir/bin/" "$outroot/bin/"
rm -rf "$nested_dir"
fi
# Unify to bin/xray/
unify_geo_layout "$outroot"
echo "[+] Bundle extracted to $outroot"
}
# ===== Build results collection for --arch all ========================================
BUILT_RPMS=() # Will collect absolute paths of built RPMs
BUILT_ALL=0 # Flag to know if we should print the final summary
# ===== Build (single-arch) function ====================================================
build_for_arch() {
# $1: target short arch: x64 | arm64
local short="$1"
local rid rpm_target archdir
case "$short" in
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;;
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;;
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;;
esac
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
# .NET publish (self-contained) for this RID
dotnet clean "$PROJECT" -c Release
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
dotnet restore "$PROJECT"
dotnet publish "$PROJECT" \
-c Release -r "$rid" \
-p:PublishSingleFile=false \
-p:SelfContained=true \
-p:IncludeNativeLibrariesForSelfExtract=true
# Per-arch variables (scoped)
local RID_DIR="$rid"
local PUBDIR
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
[[ -d "$PUBDIR" ]]
# Make RID_DIR visible to download helpers (they read this var)
export RID_DIR
# Per-arch working area
local PKGROOT="v2rayN-publish"
local WORKDIR
WORKDIR="$(mktemp -d)"
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
# rpmbuild topdir selection
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
rpmdev-setuptree
TOPDIR="${HOME}/rpmbuild"
SPECDIR="${TOPDIR}/SPECS"
SOURCEDIR="${TOPDIR}/SOURCES"
USE_TOPDIR_DEFINE=0
else
TOPDIR="${WORKDIR}/rpmbuild"
SPECDIR="${TOPDIR}/SPECS}"
SOURCEDIR="${TOPDIR}/SOURCES"
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
USE_TOPDIR_DEFINE=1
fi
# Stage publish content
mkdir -p "$WORKDIR/$PKGROOT"
cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/"
# Optional icon
local ICON_CANDIDATE
ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png"
[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true
# Prepare bin structure
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
# Bundle / cores per-arch
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
echo "[*] Using v2rayN bundle archive."
else
echo "[*] Bundle failed, fallback to separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
fi
else
echo "[*] --netcore specified: use separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
fi
# Tarball
mkdir -p "$SOURCEDIR"
tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT"
# SPEC
local SPECFILE="$SPECDIR/v2rayN.spec"
mkdir -p "$SPECDIR"
cat > "$SPECFILE" <<'SPEC'
%global debug_package %{nil}
%undefine _debuginfo_subpackages
%undefine _debugsource_packages
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
%global __requires_exclude ^liblttng-ust\.so\..*$
Name: v2rayN
Version: __VERSION__
Release: 1%{?dist}
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
License: GPL-3.0-only
URL: https://github.com/2dust/v2rayN
BugURL: https://github.com/2dust/v2rayN/issues
ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
%description
v2rayN Linux for Red Hat Enterprise Linux
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS
For more information, Please visit our website
https://github.com/2dust/v2rayN
%prep
%setup -q -n __PKGROOT__
%build
# no build
%install
install -dm0755 %{buildroot}/opt/v2rayN
cp -a * %{buildroot}/opt/v2rayN/
# Launcher (prefer native ELF first, then DLL fallback; also create Geo symlinks for the user)
install -dm0755 %{buildroot}%{_bindir}
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
#!/usr/bin/bash
set -euo pipefail
DIR="/opt/v2rayN"
# --- Symlink GEO files into user's XDG dir (first-run convenience) ---
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin"
SYS_XRAY_DIR="$DIR/bin/xray"
mkdir -p "$USR_GEO_DIR"
for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do
if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then
ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true
fi
done
# --- end GEO ---
# Prefer native apphost
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
# DLL fallback
for dll in v2rayN.Desktop.dll v2rayN.dll; do
if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi
done
echo "v2rayN launcher: no executable found in $DIR" >&2
ls -l "$DIR" >&2 || true
exit 1
EOF
chmod 0755 %{buildroot}%{_bindir}/v2rayn
# Desktop file
install -dm0755 %{buildroot}%{_datadir}/applications
cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF'
[Desktop Entry]
Type=Application
Name=v2rayN
Comment=v2rayN for Red Hat Enterprise Linux
Exec=v2rayn
Icon=v2rayn
Terminal=false
Categories=Network;
EOF
# Icon
if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then
install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
fi
%post
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
%postun
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
%files
%{_bindir}/v2rayn
/opt/v2rayN
%{_datadir}/applications/v2rayn.desktop
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
SPEC
# Autostart injection (inside %install) and %files entry
if [[ "$AUTOSTART" -eq 1 ]]; then
awk '
BEGIN{ins=0}
/^%post$/ && !ins {
print "# --- Autostart (.desktop) ---"
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
print "[Desktop Entry]"
print "Type=Application"
print "Name=v2rayN (Autostart)"
print "Exec=v2rayn"
print "X-GNOME-Autostart-enabled=true"
print "NoDisplay=false"
print "EOF"
ins=1
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
awk '
BEGIN{infiles=0; done=0}
/^%files$/ {infiles=1}
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
print
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
done=1
next
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
fi
# Replace placeholders
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
# NOTE: We define only __strip to point to the target-arch strip.
# DO NOT override __brp_strip (it must stay the brp script path).
local STRIP_ARGS=()
if [[ "$ID" == "ubuntu" ]]; then
local STRIP_BIN=""
if [[ "$short" == "x64" ]]; then
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
else
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
fi
if [[ -x "$STRIP_BIN" ]]; then
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
fi
fi
# Build RPM for this arch (force rpm --target to match compile arch)
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
else
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
fi
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
mkdir -p "$HOME/rpmbuild"
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
TOPDIR="$HOME/rpmbuild"
fi
echo "Build done for $short. RPM at:"
local f
for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do
[[ -e "$f" ]] || continue
echo " $f"
BUILT_RPMS+=("$f")
done
}
# ===== Arch selection and build orchestration =========================================
case "${ARCH_OVERRIDE:-}" in
"")
# No --arch: use host architecture
if [[ "$host_arch" == "aarch64" ]]; then
build_for_arch arm64
else
build_for_arch x64
fi
;;
x64|amd64)
build_for_arch x64
;;
arm64|aarch64)
build_for_arch arm64
;;
all)
BUILT_ALL=1
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
build_for_arch x64
build_for_arch arm64
;;
*)
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
exit 1
;;
esac
# ===== Final summary if building both arches ==========================================
if [[ "$BUILT_ALL" -eq 1 ]]; then
echo ""
echo "================ Build Summary (both architectures) ================"
if [[ "${#BUILT_RPMS[@]}" -gt 0 ]]; then
for rp in "${BUILT_RPMS[@]}"; do
echo "$rp"
done
else
echo "[WARN] No RPMs detected in summary (check build logs above)."
fi
echo "==================================================================="
fi

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.14.1</Version> <Version>7.14.3</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -5,10 +5,10 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.3" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.3" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.3" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.4" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.3" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.4" />
<PackageVersion Include="CliWrap" Version="3.9.0" /> <PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="4.0.3" /> <PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
@ -20,7 +20,7 @@
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" /> <PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
<PackageVersion Include="Splat.NLog" Version="15.4.1" /> <PackageVersion Include="Splat.NLog" Version="15.5.3" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />

View file

@ -4,7 +4,7 @@ using ZXing.SkiaSharp;
namespace ServiceLib.Common; namespace ServiceLib.Common;
public class QRCodeHelper public class QRCodeUtils
{ {
public static byte[]? GenQRCode(string? url) public static byte[]? GenQRCode(string? url)
{ {

View file

@ -289,6 +289,31 @@ public class Global
"sing_box" "sing_box"
]; ];
public static readonly HashSet<EConfigType> XraySupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly List<string> DomainStrategies = public static readonly List<string> DomainStrategies =
[ [
AsIs, AsIs,

View file

@ -1,7 +1,9 @@
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;
global using ServiceLib.Enums; global using ServiceLib.Enums;
global using ServiceLib.Handler; global using ServiceLib.Handler;
global using ServiceLib.Helper;
global using ServiceLib.Manager;
global using ServiceLib.Handler.Fmt; global using ServiceLib.Handler.Fmt;
global using ServiceLib.Services; global using ServiceLib.Services;
global using ServiceLib.Services.Statistics; global using ServiceLib.Services.Statistics;

View file

@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
public class ConfigHandler public static class ConfigHandler
{ {
private static readonly string _configRes = Global.ConfigFileName; private static readonly string _configRes = Global.ConfigFileName;
private static readonly string _tag = "ConfigHandler"; private static readonly string _tag = "ConfigHandler";
@ -216,7 +216,7 @@ public class ConfigHandler
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns> /// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
public static async Task<int> AddServer(Config config, ProfileItem profileItem) public static async Task<int> AddServer(Config config, ProfileItem profileItem)
{ {
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
if (item is null) if (item is null)
{ {
item = profileItem; item = profileItem;
@ -336,7 +336,7 @@ public class ConfigHandler
{ {
foreach (var it in indexes) foreach (var it in indexes)
{ {
var item = await AppHandler.Instance.GetProfileItem(it.IndexId); var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null) if (item is null)
{ {
continue; continue;
@ -418,7 +418,7 @@ public class ConfigHandler
/// <returns>The default profile item or null if none exists</returns> /// <returns>The default profile item or null if none exists</returns>
public static async Task<ProfileItem?> GetDefaultServer(Config config) public static async Task<ProfileItem?> GetDefaultServer(Config config)
{ {
var item = await AppHandler.Instance.GetProfileItem(config.IndexId); var item = await AppManager.Instance.GetProfileItem(config.IndexId);
if (item is null) if (item is null)
{ {
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(); var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
@ -449,7 +449,7 @@ public class ConfigHandler
for (int i = 0; i < lstProfile.Count; i++) for (int i = 0; i < lstProfile.Count; i++)
{ {
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
} }
var sort = 0; var sort = 0;
@ -461,7 +461,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1; sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1;
break; break;
} }
@ -471,7 +471,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1; sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
break; break;
} }
@ -482,7 +482,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1; sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
break; break;
} }
@ -492,7 +492,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1; sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1;
break; break;
} }
@ -501,7 +501,7 @@ public class ConfigHandler
break; break;
} }
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort); ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort);
return await Task.FromResult(0); return await Task.FromResult(0);
} }
@ -559,7 +559,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem) public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
{ {
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
if (item is null) if (item is null)
{ {
item = profileItem; item = profileItem;
@ -601,7 +601,7 @@ public class ConfigHandler
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.Security = profileItem.Security.TrimEx();
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
{ {
return -1; return -1;
} }
@ -829,13 +829,13 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc) public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
{ {
var lstModel = await AppHandler.Instance.ProfileItems(subId, ""); var lstModel = await AppManager.Instance.ProfileItems(subId, "");
if (lstModel.Count <= 0) if (lstModel.Count <= 0)
{ {
return -1; return -1;
} }
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? []; var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
var lstProfile = (from t in lstModel var lstProfile = (from t in lstModel
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
from t22 in t2b.DefaultIfEmpty() from t22 in t2b.DefaultIfEmpty()
@ -905,7 +905,7 @@ public class ConfigHandler
for (var i = 0; i < lstProfile.Count; i++) for (var i = 0; i < lstProfile.Count; i++)
{ {
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
} }
switch (name) switch (name)
{ {
@ -914,7 +914,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10; var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Delay <= 0)) foreach (var item in lstProfile.Where(item => item.Delay <= 0))
{ {
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
} }
break; break;
@ -924,7 +924,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10; var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Speed <= 0)) foreach (var item in lstProfile.Where(item => item.Speed <= 0))
{ {
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
} }
break; break;
@ -982,7 +982,7 @@ public class ConfigHandler
/// <returns>Tuple with total count and remaining count after deduplication</returns> /// <returns>Tuple with total count and remaining count after deduplication</returns>
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId) public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
{ {
var lstProfile = await AppHandler.Instance.ProfileItems(subId); var lstProfile = await AppManager.Instance.ProfileItems(subId);
if (lstProfile == null) if (lstProfile == null)
{ {
return new Tuple<int, int>(0, 0); return new Tuple<int, int>(0, 0);
@ -1052,15 +1052,15 @@ public class ConfigHandler
if (profileItem.IndexId.IsNullOrEmpty()) if (profileItem.IndexId.IsNullOrEmpty())
{ {
profileItem.IndexId = Utils.GetGuid(false); profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExHandler.Instance.GetMaxSort(); maxSort = ProfileExManager.Instance.GetMaxSort();
} }
if (!toFile && maxSort < 0) if (!toFile && maxSort < 0)
{ {
maxSort = ProfileExHandler.Instance.GetMaxSort(); maxSort = ProfileExManager.Instance.GetMaxSort();
} }
if (maxSort > 0) if (maxSort > 0)
{ {
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1); ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
} }
if (toFile) if (toFile)
@ -1120,7 +1120,7 @@ public class ConfigHandler
{ {
try try
{ {
var item = await AppHandler.Instance.GetProfileItem(indexId); var item = await AppManager.Instance.GetProfileItem(indexId);
if (item == null) if (item == null)
{ {
return 0; return 0;
@ -1165,7 +1165,7 @@ public class ConfigHandler
return result; return result;
} }
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new(); var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId; profileItem.IndexId = indexId;
if (coreType == ECoreType.Xray) if (coreType == ECoreType.Xray)
{ {
@ -1211,7 +1211,7 @@ public class ConfigHandler
ConfigType = EConfigType.SOCKS, ConfigType = EConfigType.SOCKS,
Address = Global.Loopback, Address = Global.Loopback,
Sni = node.Address, //Tun2SocksAddress Sni = node.Address, //Tun2SocksAddress
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
}; };
} }
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
@ -1238,12 +1238,12 @@ public class ConfigHandler
/// <returns>Number of removed servers or -1 if failed</returns> /// <returns>Number of removed servers or -1 if failed</returns>
public static async Task<int> RemoveInvalidServerResult(Config config, string subid) public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
{ {
var lstModel = await AppHandler.Instance.ProfileItems(subid, ""); var lstModel = await AppManager.Instance.ProfileItems(subid, "");
if (lstModel is { Count: <= 0 }) if (lstModel is { Count: <= 0 })
{ {
return -1; return -1;
} }
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
var lstProfile = (from t in lstModel var lstProfile = (from t in lstModel
join t2 in lstProfileExs on t.IndexId equals t2.IndexId join t2 in lstProfileExs on t.IndexId equals t2.IndexId
where t2.Delay == -1 where t2.Delay == -1
@ -1279,7 +1279,7 @@ public class ConfigHandler
if (isSub && subid.IsNotEmpty()) if (isSub && subid.IsNotEmpty())
{ {
await RemoveServersViaSubid(config, subid, isSub); await RemoveServersViaSubid(config, subid, isSub);
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? ""; subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
} }
var countServers = 0; var countServers = 0;
@ -1363,7 +1363,7 @@ public class ConfigHandler
return -1; return -1;
} }
var subItem = await AppHandler.Instance.GetSubItem(subid); var subItem = await AppManager.Instance.GetSubItem(subid);
var subRemarks = subItem?.Remarks; var subRemarks = subItem?.Remarks;
var preSocksPort = subItem?.PreSocksPort; var preSocksPort = subItem?.PreSocksPort;
@ -1519,7 +1519,7 @@ public class ConfigHandler
ProfileItem? activeProfile = null; ProfileItem? activeProfile = null;
if (isSub && subid.IsNotEmpty()) if (isSub && subid.IsNotEmpty())
{ {
lstOriSub = await AppHandler.Instance.ProfileItems(subid); lstOriSub = await AppManager.Instance.ProfileItems(subid);
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId); activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
} }
@ -1551,7 +1551,7 @@ public class ConfigHandler
//Select active node //Select active node
if (activeProfile != null) if (activeProfile != null)
{ {
var lstSub = await AppHandler.Instance.ProfileItems(subid); var lstSub = await AppManager.Instance.ProfileItems(subid);
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true)); var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
if (existItem != null) if (existItem != null)
{ {
@ -1562,13 +1562,13 @@ public class ConfigHandler
//Keep the last traffic statistics //Keep the last traffic statistics
if (lstOriSub != null) if (lstOriSub != null)
{ {
var lstSub = await AppHandler.Instance.ProfileItems(subid); var lstSub = await AppManager.Instance.ProfileItems(subid);
foreach (var item in lstSub) foreach (var item in lstSub)
{ {
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true)); var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
if (existItem != null) if (existItem != null)
{ {
await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
} }
} }
} }
@ -1608,7 +1608,7 @@ public class ConfigHandler
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{ {
//TODO Temporary reminder to be removed later //TODO Temporary reminder to be removed later
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return -1; //return -1;
} }
@ -1626,7 +1626,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddSubItem(Config config, SubItem subItem) public static async Task<int> AddSubItem(Config config, SubItem subItem)
{ {
var item = await AppHandler.Instance.GetSubItem(subItem.Id); var item = await AppManager.Instance.GetSubItem(subItem.Id);
if (item is null) if (item is null)
{ {
item = subItem; item = subItem;
@ -1658,7 +1658,7 @@ public class ConfigHandler
var maxSort = 0; var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0) if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{ {
var lstSubs = (await AppHandler.Instance.SubItems()); var lstSubs = (await AppManager.Instance.SubItems());
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0; maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
} }
item.Sort = maxSort + 1; item.Sort = maxSort + 1;
@ -1712,7 +1712,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> DeleteSubItem(Config config, string id) public static async Task<int> DeleteSubItem(Config config, string id)
{ {
var item = await AppHandler.Instance.GetSubItem(id); var item = await AppManager.Instance.GetSubItem(id);
if (item is null) if (item is null)
{ {
return 0; return 0;
@ -1896,7 +1896,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem) public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
{ {
var items = await AppHandler.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true)) if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
{ {
return -1; return -1;
@ -1976,7 +1976,7 @@ public class ConfigHandler
if (template == null) if (template == null)
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
var items = await AppHandler.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count; var maxSort = items.Count;
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0) if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
{ {
@ -2023,14 +2023,14 @@ public class ConfigHandler
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
{ {
var ver = "V3-"; var ver = "V3-";
var items = await AppHandler.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
//TODO Temporary code to be removed later //TODO Temporary code to be removed later
var lockItem = items?.FirstOrDefault(t => t.Locked == true); var lockItem = items?.FirstOrDefault(t => t.Locked == true);
if (lockItem != null) if (lockItem != null)
{ {
await ConfigHandler.RemoveRoutingItem(lockItem); await ConfigHandler.RemoveRoutingItem(lockItem);
items = await AppHandler.Instance.RoutingItems(); items = await AppManager.Instance.RoutingItems();
} }
if (!blImportAdvancedRules && items.Count > 0) if (!blImportAdvancedRules && items.Count > 0)
@ -2107,7 +2107,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> InitBuiltinDNS(Config config) public static async Task<int> InitBuiltinDNS(Config config)
{ {
var items = await AppHandler.Instance.DNSItems(); var items = await AppManager.Instance.DNSItems();
// Check existing DNS items and disable those with empty NormalDNS // Check existing DNS items and disable those with empty NormalDNS
var needsUpdate = false; var needsUpdate = false;
@ -2185,7 +2185,7 @@ public class ConfigHandler
/// <returns>DNS item with configuration from the URL</returns> /// <returns>DNS item with configuration from the URL</returns>
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url) public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
{ {
var currentItem = await AppHandler.Instance.GetDNSItem(type); var currentItem = await AppManager.Instance.GetDNSItem(type);
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, ""); var templateContent = await downloadHandle.TryDownloadString(url, true, "");
@ -2247,7 +2247,7 @@ public class ConfigHandler
public static async Task<int> InitBuiltinFullConfigTemplate(Config config) public static async Task<int> InitBuiltinFullConfigTemplate(Config config)
{ {
var items = await AppHandler.Instance.FullConfigTemplateItem(); var items = await AppManager.Instance.FullConfigTemplateItem();
if (items.Count <= 0) if (items.Count <= 0)
{ {
var item = new FullConfigTemplateItem() var item = new FullConfigTemplateItem()

View file

@ -1,27 +1,28 @@
using System.Net;
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
public class ConnectionHandler public static class ConnectionHandler
{ {
private static readonly Lazy<ConnectionHandler> _instance = new(() => new()); private static readonly string _tag = "ConnectionHandler";
public static ConnectionHandler Instance => _instance.Value;
public async Task<string> RunAvailabilityCheck() public static async Task<string> RunAvailabilityCheck()
{ {
var downloadHandle = new DownloadService(); var time = await GetRealPingTime();
var time = await downloadHandle.RunAvailabilityCheck(null); var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
var ip = time > 0 ? await GetIPInfo(downloadHandle) ?? Global.None : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip); return string.Format(ResUI.TestMeOutput, time, ip);
} }
private async Task<string?> GetIPInfo(DownloadService downloadHandle) private static async Task<string?> GetIPInfo()
{ {
var url = AppHandler.Instance.Config.SpeedTestItem.IPAPIUrl; var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
if (url.IsNullOrEmpty()) if (url.IsNullOrEmpty())
{ {
return null; return null;
} }
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, true, ""); var result = await downloadHandle.TryDownloadString(url, true, "");
if (result == null) if (result == null)
{ {
@ -39,4 +40,31 @@ public class ConnectionHandler
return $"({country ?? "unknown"}) {ip}"; return $"({country ?? "unknown"}) {ip}";
} }
private static async Task<int> GetRealPingTime()
{
var responseTime = -1;
try
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
for (var i = 0; i < 2; i++)
{
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
if (responseTime > 0)
{
break;
}
await Task.Delay(500);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return -1;
}
return responseTime;
}
} }

View file

@ -3,13 +3,13 @@ namespace ServiceLib.Handler;
/// <summary> /// <summary>
/// Core configuration file processing class /// Core configuration file processing class
/// </summary> /// </summary>
public class CoreConfigHandler public static class CoreConfigHandler
{ {
private static readonly string _tag = "CoreConfigHandler"; private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName) public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
{ {
var config = AppHandler.Instance.Config; var config = AppManager.Instance.Config;
var result = new RetResult(); var result = new RetResult();
if (node.ConfigType == EConfigType.Custom) if (node.ConfigType == EConfigType.Custom)
@ -21,7 +21,7 @@ public class CoreConfigHandler
_ => await GenerateClientCustomConfig(node, fileName) _ => await GenerateClientCustomConfig(node, fileName)
}; };
} }
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{ {
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
} }
@ -112,11 +112,11 @@ public class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
{ {
var result = new RetResult(); var result = new RetResult();
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum); var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port; testItem.Port = port;
if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{ {
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
} }

View file

@ -1,5 +1,3 @@
using SkiaSharp;
namespace ServiceLib.Handler.Fmt; namespace ServiceLib.Handler.Fmt;
public class HtmlPageFmt : BaseFmt public class HtmlPageFmt : BaseFmt

View file

@ -1,11 +1,11 @@
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
public class SubscriptionHandler public static class SubscriptionHandler
{ {
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Action<bool, string> updateFunc) public static async Task UpdateProcess(Config config, string subId, bool blProxy, Action<bool, string> updateFunc)
{ {
updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart); updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
var subItem = await AppHandler.Instance.SubItems(); var subItem = await AppManager.Instance.SubItems();
if (subItem is not { Count: > 0 }) if (subItem is not { Count: > 0 })
{ {

View file

@ -1,6 +1,6 @@
namespace ServiceLib.Handler.SysProxy; namespace ServiceLib.Handler.SysProxy;
public class ProxySettingLinux public static class ProxySettingLinux
{ {
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";

View file

@ -1,6 +1,6 @@
namespace ServiceLib.Handler.SysProxy; namespace ServiceLib.Handler.SysProxy;
public class ProxySettingOSX public static class ProxySettingOSX
{ {
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";

View file

@ -3,7 +3,7 @@ using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionO
namespace ServiceLib.Handler.SysProxy; namespace ServiceLib.Handler.SysProxy;
public class ProxySettingWindows public static class ProxySettingWindows
{ {
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings"; private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";

View file

@ -15,7 +15,7 @@ public static class SysProxyHandler
try try
{ {
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
if (port <= 0) if (port <= 0)
{ {
@ -56,7 +56,7 @@ public static class SysProxyHandler
if (type != ESysProxyType.Pac && Utils.IsWindows()) if (type != ESysProxyType.Pac && Utils.IsWindows())
{ {
PacHandler.Instance.Stop(); PacManager.Instance.Stop();
} }
} }
catch (Exception ex) catch (Exception ex)
@ -90,8 +90,8 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port) private static async Task SetWindowsProxyPac(int port)
{ {
var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac); var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacHandler.Instance.StartAsync(Utils.GetConfigPath(), port, portPac); await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4); ProxySettingWindows.SetProxy(strProxy, "", 4);
} }

View file

@ -1,7 +1,7 @@
using System.Net; using System.Net;
using Downloader; using Downloader;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
public class DownloaderHelper public class DownloaderHelper
{ {

View file

@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
/// <summary> /// <summary>
/// </summary> /// </summary>
@ -202,4 +204,35 @@ public class HttpClientHelper
} }
} while (isMoreToRead); } while (isMoreToRead);
} }
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch //(Exception ex)
{
//Utile.SaveLog(ex.Message, ex);
}
return responseTime;
}
} }

View file

@ -1,7 +1,7 @@
using System.Collections; using System.Collections;
using SQLite; using SQLite;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
public sealed class SQLiteHelper public sealed class SQLiteHelper
{ {

View file

@ -1,15 +1,15 @@
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public sealed class AppHandler public sealed class AppManager
{ {
#region Property #region Property
private static readonly Lazy<AppHandler> _instance = new(() => new()); private static readonly Lazy<AppManager> _instance = new(() => new());
private Config _config; private Config _config;
private int? _statePort; private int? _statePort;
private int? _statePort2; private int? _statePort2;
private Job? _processJob; private Job? _processJob;
public static AppHandler Instance => _instance.Value; public static AppManager Instance => _instance.Value;
public Config Config => _config; public Config Config => _config;
public int StatePort public int StatePort
@ -97,7 +97,7 @@ public sealed class AppHandler
return localPort + (int)protocol; return localPort + (int)protocol;
} }
public void AddProcess(IntPtr processHandle) public void AddProcess(nint processHandle)
{ {
if (Utils.IsWindows()) if (Utils.IsWindows())
{ {

View file

@ -1,11 +1,11 @@
using static ServiceLib.Models.ClashProxies; using static ServiceLib.Models.ClashProxies;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public sealed class ClashApiHandler public sealed class ClashApiManager
{ {
private static readonly Lazy<ClashApiHandler> instance = new(() => new()); private static readonly Lazy<ClashApiManager> instance = new(() => new());
public static ClashApiHandler Instance => instance.Value; public static ClashApiManager Instance => instance.Value;
private static readonly string _tag = "ClashApiHandler"; private static readonly string _tag = "ClashApiHandler";
private Dictionary<string, ProxiesItem>? _proxies; private Dictionary<string, ProxiesItem>? _proxies;
@ -65,7 +65,7 @@ public sealed class ClashApiHandler
return; return;
} }
var urlBase = $"{GetApiUrl()}/proxies"; var urlBase = $"{GetApiUrl()}/proxies";
urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl; urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var tasks = new List<Task>(); var tasks = new List<Task>();
foreach (var it in lstProxy) foreach (var it in lstProxy)
@ -182,6 +182,6 @@ public sealed class ClashApiHandler
private string GetApiUrl() private string GetApiUrl()
{ {
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}"; return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
} }
} }

View file

@ -3,12 +3,12 @@ using System.Text;
using CliWrap; using CliWrap;
using CliWrap.Buffered; using CliWrap.Buffered;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class CoreAdminHandler public class CoreAdminManager
{ {
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new()); private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
public static CoreAdminHandler Instance => _instance.Value; public static CoreAdminManager Instance => _instance.Value;
private Config _config; private Config _config;
private Action<bool, string>? _updateFunc; private Action<bool, string>? _updateFunc;
private int _linuxSudoPid = -1; private int _linuxSudoPid = -1;
@ -72,7 +72,7 @@ public class CoreAdminHandler
proc.BeginErrorReadLine(); proc.BeginErrorReadLine();
await Task.Delay(10); await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd);
await Task.Delay(100); await Task.Delay(100);
if (proc is null or { HasExited: true }) if (proc is null or { HasExited: true })
@ -103,7 +103,7 @@ public class CoreAdminHandler
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" }; var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
var result = await Cli.Wrap(Global.LinuxBash) var result = await Cli.Wrap(Global.LinuxBash)
.WithArguments(arg) .WithArguments(arg)
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd)) .WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
.ExecuteBufferedAsync(); .ExecuteBufferedAsync();
UpdateFunc(false, result.StandardOutput.ToString()); UpdateFunc(false, result.StandardOutput.ToString());

View file

@ -1,12 +1,12 @@
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public sealed class CoreInfoHandler public sealed class CoreInfoManager
{ {
private static readonly Lazy<CoreInfoHandler> _instance = new(() => new()); private static readonly Lazy<CoreInfoManager> _instance = new(() => new());
private List<CoreInfo>? _coreInfo; private List<CoreInfo>? _coreInfo;
public static CoreInfoHandler Instance => _instance.Value; public static CoreInfoManager Instance => _instance.Value;
public CoreInfoHandler() public CoreInfoManager()
{ {
InitCoreInfo(); InitCoreInfo();
} }

View file

@ -1,15 +1,15 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
/// <summary> /// <summary>
/// Core process processing class /// Core process processing class
/// </summary> /// </summary>
public class CoreHandler public class CoreManager
{ {
private static readonly Lazy<CoreHandler> _instance = new(() => new()); private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreHandler Instance => _instance.Value; public static CoreManager Instance => _instance.Value;
private Config _config; private Config _config;
private Process? _process; private Process? _process;
private Process? _processPre; private Process? _processPre;
@ -39,7 +39,7 @@ public class CoreHandler
if (Utils.IsNonWindows()) if (Utils.IsNonWindows())
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo) foreach (var it in coreInfo)
{ {
if (it.CoreType == ECoreType.v2rayN) if (it.CoreType == ECoreType.v2rayN)
@ -114,7 +114,7 @@ public class CoreHandler
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
UpdateFunc(false, configPath); UpdateFunc(false, configPath);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false); var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null) if (proc is null)
{ {
@ -126,7 +126,7 @@ public class CoreHandler
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem) public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
{ {
var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId); var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
if (node is null) if (node is null)
{ {
return -1; return -1;
@ -140,8 +140,8 @@ public class CoreHandler
return -1; return -1;
} }
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false); var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null) if (proc is null)
{ {
@ -157,7 +157,7 @@ public class CoreHandler
{ {
if (_linuxSudo) if (_linuxSudo)
{ {
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo(); await CoreAdminManager.Instance.KillProcessAsLinuxSudo();
_linuxSudo = false; _linuxSudo = false;
} }
@ -183,8 +183,8 @@ public class CoreHandler
private async Task CoreStart(ProfileItem node) private async Task CoreStart(ProfileItem node)
{ {
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
@ -199,7 +199,7 @@ public class CoreHandler
{ {
if (_process != null && !_process.HasExited) if (_process != null && !_process.HasExited)
{ {
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null) if (itemSocks != null)
{ {
@ -208,7 +208,7 @@ public class CoreHandler
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success) if (result.Success)
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null) if (proc is null)
{ {
@ -231,7 +231,7 @@ public class CoreHandler
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
{ {
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg); var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty()) if (fileName.IsNullOrEmpty())
{ {
UpdateFunc(false, msg); UpdateFunc(false, msg);
@ -246,8 +246,8 @@ public class CoreHandler
&& Utils.IsNonWindows()) && Utils.IsNonWindows())
{ {
_linuxSudo = true; _linuxSudo = true;
await CoreAdminHandler.Instance.Init(_config, _updateFunc); await CoreAdminManager.Instance.Init(_config, _updateFunc);
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath); return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
} }
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog); return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
@ -299,7 +299,7 @@ public class CoreHandler
} }
await Task.Delay(100); await Task.Delay(100);
AppHandler.Instance.AddProcess(proc.Handle); AppManager.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true }) if (proc is null or { HasExited: true })
{ {
throw new Exception(ResUI.FailedToRunCore); throw new Exception(ResUI.FailedToRunCore);

View file

@ -1,11 +1,11 @@
using ReactiveUI; using ReactiveUI;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class NoticeHandler public class NoticeManager
{ {
private static readonly Lazy<NoticeHandler> _instance = new(() => new()); private static readonly Lazy<NoticeManager> _instance = new(() => new());
public static NoticeHandler Instance => _instance.Value; public static NoticeManager Instance => _instance.Value;
public void Enqueue(string? content) public void Enqueue(string? content)
{ {

View file

@ -1,12 +1,12 @@
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class PacHandler public class PacManager
{ {
private static readonly Lazy<PacHandler> _instance = new(() => new PacHandler()); private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
public static PacHandler Instance => _instance.Value; public static PacManager Instance => _instance.Value;
private string _configPath; private string _configPath;
private int _httpPort; private int _httpPort;

View file

@ -2,17 +2,17 @@ using System.Collections.Concurrent;
//using System.Reactive.Linq; //using System.Reactive.Linq;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class ProfileExHandler public class ProfileExManager
{ {
private static readonly Lazy<ProfileExHandler> _instance = new(() => new()); private static readonly Lazy<ProfileExManager> _instance = new(() => new());
private ConcurrentBag<ProfileExItem> _lstProfileEx = []; private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
private readonly Queue<string> _queIndexIds = new(); private readonly Queue<string> _queIndexIds = new();
public static ProfileExHandler Instance => _instance.Value; public static ProfileExManager Instance => _instance.Value;
private static readonly string _tag = "ProfileExHandler"; private static readonly string _tag = "ProfileExHandler";
public ProfileExHandler() public ProfileExManager()
{ {
//Init(); //Init();
} }

View file

@ -1,9 +1,9 @@
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class StatisticsHandler public class StatisticsManager
{ {
private static readonly Lazy<StatisticsHandler> instance = new(() => new()); private static readonly Lazy<StatisticsManager> instance = new(() => new());
public static StatisticsHandler Instance => instance.Value; public static StatisticsManager Instance => instance.Value;
private Config _config; private Config _config;
private ServerStatItem? _serverStatItem; private ServerStatItem? _serverStatItem;
@ -91,7 +91,7 @@ public class StatisticsHandler
{ {
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
long ticks = DateTime.Now.Date.Ticks; var ticks = DateTime.Now.Date.Ticks;
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}"); await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync(); _lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
@ -128,7 +128,7 @@ public class StatisticsHandler
private async Task GetServerStatItem(string indexId) private async Task GetServerStatItem(string indexId)
{ {
long ticks = DateTime.Now.Date.Ticks; var ticks = DateTime.Now.Date.Ticks;
if (_serverStatItem != null && _serverStatItem.IndexId != indexId) if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
{ {
_serverStatItem = null; _serverStatItem = null;

View file

@ -1,9 +1,9 @@
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public class TaskHandler public class TaskManager
{ {
private static readonly Lazy<TaskHandler> _instance = new(() => new()); private static readonly Lazy<TaskManager> _instance = new(() => new());
public static TaskHandler Instance => _instance.Value; public static TaskManager Instance => _instance.Value;
public void RegUpdateTask(Config config, Action<bool, string> updateFunc) public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
{ {
@ -29,7 +29,7 @@ public class TaskHandler
//Logging.SaveLog("Execute save config"); //Logging.SaveLog("Execute save config");
await ConfigHandler.SaveConfig(config); await ConfigHandler.SaveConfig(config);
await ProfileExHandler.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
} }
//Execute once 1 hour //Execute once 1 hour
@ -52,7 +52,7 @@ public class TaskHandler
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc) private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
{ {
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
var lstSubs = (await AppHandler.Instance.SubItems())? var lstSubs = (await AppManager.Instance.SubItems())?
.Where(t => t.AutoUpdateInterval > 0) .Where(t => t.AutoUpdateInterval > 0)
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60) .Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
.ToList(); .ToList();
@ -66,7 +66,7 @@ public class TaskHandler
foreach (var item in lstSubs) foreach (var item in lstSubs)
{ {
await SubscriptionHandler.UpdateProcess(config, item.Id, true, (bool success, string msg) => await SubscriptionHandler.UpdateProcess(config, item.Id, true, (success, msg) =>
{ {
updateFunc?.Invoke(success, msg); updateFunc?.Invoke(success, msg);
if (success) if (success)
@ -87,7 +87,7 @@ public class TaskHandler
Logging.SaveLog("Execute update geo files"); Logging.SaveLog("Execute update geo files");
var updateHandle = new UpdateService(); var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) => await updateHandle.UpdateGeoFileAll(config, (success, msg) =>
{ {
updateFunc?.Invoke(false, msg); updateFunc?.Invoke(false, msg);
}); });

View file

@ -1,12 +1,12 @@
using System.Net; using System.Net;
using WebDav; using WebDav;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public sealed class WebDavHandler public sealed class WebDavManager
{ {
private static readonly Lazy<WebDavHandler> _instance = new(() => new()); private static readonly Lazy<WebDavManager> _instance = new(() => new());
public static WebDavHandler Instance => _instance.Value; public static WebDavManager Instance => _instance.Value;
private readonly Config? _config; private readonly Config? _config;
private WebDavClient? _client; private WebDavClient? _client;
@ -15,9 +15,9 @@ public sealed class WebDavHandler
private readonly string _webFileName = "backup.zip"; private readonly string _webFileName = "backup.zip";
private readonly string _tag = "WebDav--"; private readonly string _tag = "WebDav--";
public WebDavHandler() public WebDavManager()
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
} }
private async Task<bool> GetClient() private async Task<bool> GetClient()

View file

@ -3732,6 +3732,34 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Auto Route 的本地化字符串。
/// </summary>
public static string TbSettingsTunAutoRoute {
get { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); }
}
/// <summary>
/// 查找类似 Strict Route 的本地化字符串。
/// </summary>
public static string TbSettingsTunStrictRoute {
get { return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture); }
}
/// <summary>
/// 查找类似 Stack 的本地化字符串。
/// </summary>
public static string TbSettingsTunStack {
get { return ResourceManager.GetString("TbSettingsTunStack", resourceCulture); }
}
/// <summary>
/// 查找类似 MTU 的本地化字符串。
/// </summary>
public static string TbSettingsTunMtu {
get { return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture); }
}
/// <summary> /// <summary>
/// 查找类似 Tun Mode settings 的本地化字符串。 /// 查找类似 Tun Mode settings 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1059,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value> <value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>مسیریابی خودکار</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>مسیریابی سخت‌گیرانه</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>پشته شبکه</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>فعال سازی additional Inbound</value> <value>فعال سازی additional Inbound</value>
</data> </data>

View file

@ -1059,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value> <value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Automatikus útválasztás</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>Szigorú útválasztás</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Hálózati verem</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>További bejövő engedélyezése</value> <value>További bejövő engedélyezése</value>
</data> </data>

View file

@ -1059,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Please make sure the Configuration remarks exist and are unique</value> <value>Please make sure the Configuration remarks exist and are unique</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Auto Route</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>Strict Route</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Stack</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Enable additional Inbound</value> <value>Enable additional Inbound</value>
</data> </data>

View file

@ -1059,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Убедитесь, что примечание существует и является уникальным</value> <value>Убедитесь, что примечание существует и является уникальным</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Автоматическая маршрутизация</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>Строгая маршрутизация</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Сетевой стек</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Включить дополнительный входящий канал</value> <value>Включить дополнительный входящий канал</value>
</data> </data>

View file

@ -1056,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置文件别名存在并唯一</value> <value>请确保配置文件别名存在并唯一</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自动路由</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>严格路由</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>协议栈</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>启用额外监听端口</value> <value>启用额外监听端口</value>
</data> </data>

View file

@ -1056,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保設定檔別名存在並且唯一</value> <value>請確保設定檔別名存在並且唯一</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自動路由</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>嚴格路由</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>協定堆疊</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>啟用額外偵聽連接埠</value> <value>啟用額外偵聽連接埠</value>
</data> </data>

View file

@ -73,12 +73,12 @@ public class CoreConfigClashService
} }
//mixed-port //mixed-port
fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
//log-level //log-level
fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel); fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel);
//external-controller //external-controller
fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}"; fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
//allow-lan //allow-lan
if (_config.Inbound.First().AllowLANConn) if (_config.Inbound.First().AllowLANConn)
{ {
@ -139,7 +139,7 @@ public class CoreConfigClashService
return ret; return ret;
} }
ClashApiHandler.Instance.ProfileContent = fileContent; ClashApiManager.Instance.ProfileContent = fileContent;
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}"); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
ret.Success = true; ret.Success = true;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,522 @@
using System.Net;
using System.Net.NetworkInformation;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigSingboxService";
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
await GenInbounds(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);
await GenRouting(singboxConfig);
await GenDns(node, singboxConfig);
await GenExperimental(singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
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> 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);
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;
}
List<IPEndPoint> lstIpEndPoints = new();
List<TcpConnectionInformation> lstTcpConns = new();
try
{
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await GenLog(singboxConfig);
//GenDns(new(), singboxConfig);
singboxConfig.inbounds.Clear();
singboxConfig.outbounds.RemoveAt(0);
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
foreach (var it in selecteds)
{
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
//find unused port
var port = initPort;
for (var k = initPort; k < Global.MaxPort; k++)
{
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
{
continue;
}
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
{
continue;
}
//found
port = k;
initPort = port + 1;
break;
}
//Port In Used
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
{
continue;
}
it.Port = port;
it.AllowTest = true;
//inbound
Inbound4Sbox inbound = new()
{
listen = Global.Loopback,
listen_port = port,
type = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.type + inbound.listen_port.ToString();
singboxConfig.inbounds.Add(inbound);
//outbound
if (item is null)
{
continue;
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS
&& !Global.Flows.Contains(item.Flow))
{
continue;
}
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
continue;
}
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);
}
//rule
Rule4Sbox rule = new()
{
inbound = new List<string> { inbound.tag },
outbound = tag
};
singboxConfig.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.SingboxFinalResolverTag
};
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> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node is not { Port: > 0 })
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == 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.SingboxFinalResolverTag
};
singboxConfig.route.rules.Clear();
singboxConfig.inbounds.Clear();
singboxConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback,
listen_port = port,
type = EInboundProtocol.mixed.ToString(),
});
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(List<ProfileItem> 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);
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;
}
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
singboxConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenOutboundsList(proxyProfiles, singboxConfig);
await GenDns(null, 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;
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
#endregion public gen function
}

View file

@ -0,0 +1,63 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
{
return JsonUtils.Serialize(singboxConfig);
}
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(singboxConfig);
}
// Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds)
{
if (outbound.type.ToLower() is "direct" or "block")
{
if (fullConfigTemplate.AddProxyOnly == true)
{
continue;
}
}
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
{
outbound.detour = fullConfigTemplate.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
{
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints)
{
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
{
endpoint.detour = fullConfigTemplate.ProxyDetour;
}
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
}
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
}
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
}
}

View file

@ -0,0 +1,506 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
{
return await GenDnsCompatible(node, singboxConfig);
}
var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(singboxConfig, simpleDNSItem);
await GenDnsRules(singboxConfig, simpleDNSItem);
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true;
// final dns
var routing = await ConfigHandler.GetDefaultRouting(_config);
var useDirectDns = false;
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
useDirectDns = rules?.LastOrDefault() is { } lastRule &&
lastRule.OutboundTag == Global.DirectTag &&
(lastRule.Port == "0-65535" ||
lastRule.Network == "tcp,udp" ||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
}
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
// Tun2SocksAddress
if (node != null && Utils.IsDomain(node.Address))
{
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{
server = Global.SingboxOutboundResolverTag,
domain = [node.Address],
});
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxFinalResolverTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxFinalResolverTag;
var resolverDns = ParseDnsAddress(simpleDNSItem.SingboxOutboundsResolveDNS);
resolverDns.tag = Global.SingboxOutboundResolverTag;
resolverDns.domain_resolver = Global.SingboxFinalResolverTag;
var hostsDns = new Server4Sbox
{
tag = Global.SingboxHostsDNSTag,
type = "hosts",
predefined = new(),
};
if (simpleDNSItem.AddCommonHosts == true)
{
hostsDns.predefined = Global.PredefinedHosts;
}
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = simpleDNSItem.Hosts?
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Where(line => !string.IsNullOrWhiteSpace(line))
.Where(line => line.Contains(' '))
.ToDictionary(
line =>
{
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
return parts[0];
},
line =>
{
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
var values = parts.Skip(1).ToList();
return values;
}
) ?? new Dictionary<string, List<string>>();
foreach (var kvp in userHostsMap)
{
hostsDns.predefined[kvp.Key] = kvp.Value;
}
}
if (simpleDNSItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts.Count > 0)
{
foreach (var host in systemHosts)
{
if (hostsDns.predefined[host.Key] != null)
{
continue;
}
hostsDns.predefined[host.Key] = new List<string> { host.Value };
}
}
}
foreach (var host in hostsDns.predefined)
{
if (finalDns.server == host.Key)
{
finalDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (remoteDns.server == host.Key)
{
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (resolverDns.server == host.Key)
{
resolverDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (directDns.server == host.Key)
{
directDns.domain_resolver = Global.SingboxHostsDNSTag;
}
}
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(remoteDns);
singboxConfig.dns.servers.Add(directDns);
singboxConfig.dns.servers.Add(resolverDns);
singboxConfig.dns.servers.Add(hostsDns);
// fake ip
if (simpleDNSItem.FakeIP == true)
{
var fakeip = new Server4Sbox
{
tag = Global.SingboxFakeDNSTag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
};
singboxConfig.dns.servers.Add(fakeip);
}
return await Task.FromResult(0);
}
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
{
var finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS);
finalDns.tag = Global.SingboxFinalResolverTag;
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(finalDns);
return await Task.FromResult(finalDns);
}
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
clash_mode = ERuleMode.Direct.ToString()
}
});
if (simpleDNSItem.BlockBindingQuery == true)
{
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
action = "predefined",
rcode = "NOTIMP"
});
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
return 0;
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
{
var ipItems = simpleDNSItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToList();
foreach (var ip in ipItems)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
{
expectedIPsRegions.Add(region);
regionNames.Add(region);
regionNames.Add($"geolocation-{region}");
regionNames.Add($"tld-{region}");
}
}
else
{
expectedIPCidr.Add(ip);
}
}
}
foreach (var item in rules)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
continue;
}
var rule = new Rule4Sbox();
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
if (validDomains <= 0)
{
continue;
}
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct;
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{
var geositeSet = new HashSet<string>(rule.geosite);
if (regionNames.Intersect(geositeSet).Any())
{
if (expectedIPsRegions.Count > 0)
{
rule.geoip = expectedIPsRegions;
}
if (expectedIPCidr.Count > 0)
{
rule.ip_cidr = expectedIPCidr;
}
}
}
}
else if (item.OutboundTag == Global.BlockTag)
{
rule.action = "predefined";
rule.rcode = "NXDOMAIN";
}
else
{
if (simpleDNSItem.FakeIP == true)
{
var rule4Fake = JsonUtils.DeepCopy(rule);
rule4Fake.server = Global.SingboxFakeDNSTag;
singboxConfig.dns.rules.Add(rule4Fake);
}
rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy;
}
singboxConfig.dns.rules.Add(rule);
}
return 0;
}
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
var strDNS = string.Empty;
if (_config.TunModeItem.EnableTun)
{
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
}
else
{
strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS;
}
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null)
{
return 0;
}
singboxConfig.dns = dns4Sbox;
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomainsCompatible(singboxConfig, item);
}
else
{
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
}
// Tun2SocksAddress
if (node != null && Utils.IsDomain(node.Address))
{
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{
server = Global.SingboxFinalResolverTag,
domain = [node.Address],
});
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxFinalResolverTag;
var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(localDnsAddress);
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.SingboxFinalResolverTag;
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,
clash_mode = ERuleMode.Direct.ToString()
});
dns4Sbox.rules.Insert(0, new()
{
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
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
});
}
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private static Server4Sbox? ParseDnsAddress(string address)
{
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(addressFirst))
{
return null;
}
var server = new Server4Sbox();
if (addressFirst is "local" or "localhost")
{
server.type = "local";
return server;
}
if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
{
var interface_name = addressFirst.Substring(7);
server.type = "dhcp";
server.Interface = interface_name == "auto" ? null : interface_name;
return server;
}
if (!addressFirst.Contains("://"))
{
// udp dns
server.type = "udp";
server.server = addressFirst;
return server;
}
try
{
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(addressFirst);
server.server = uri.Host;
if (!uri.IsDefaultPort)
{
server.server_port = uri.Port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
server.path = uri.AbsolutePath;
}
}
catch (UriFormatException)
{
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
if (protocolEndIndex > 0)
{
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var remaining = addressFirst.Substring(protocolEndIndex + 3);
var portIndex = remaining.IndexOf(':');
var pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
server.server = remaining.Substring(0, portIndex);
var portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out var parsedPort))
{
server.server_port = parsedPort;
}
}
else if (pathIndex > 0)
{
server.server = remaining.Substring(0, pathIndex);
}
else
{
server.server = remaining;
}
if (pathIndex > 0 && (server.type == "https" || server.type == "h3"))
{
server.path = remaining.Substring(pathIndex);
}
}
}
return server;
}
}

View file

@ -0,0 +1,92 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
{
try
{
var listen = "0.0.0.0";
singboxConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun
|| _config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box)
{
var inbound = new Inbound4Sbox()
{
type = EInboundProtocol.mixed.ToString(),
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
};
singboxConfig.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);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
inbound3.listen = listen;
singboxConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{
inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } };
}
}
else
{
inbound.listen = listen;
}
}
}
if (_config.TunModeItem.EnableTun)
{
if (_config.TunModeItem.Mtu <= 0)
{
_config.TunModeItem.Mtu = Global.TunMtus.First();
}
if (_config.TunModeItem.Stack.IsNullOrEmpty())
{
_config.TunModeItem.Stack = Global.TunStacks.First();
}
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack;
if (_config.TunModeItem.EnableIPv6Address == false)
{
tunInbound.address = ["172.18.0.1/30"];
}
singboxConfig.inbounds.Add(tunInbound);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
{
var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString();
inbound.listen_port = inItem.listen_port + (int)protocol;
inbound.type = EInboundProtocol.mixed.ToString();
return inbound;
}
}

View file

@ -0,0 +1,40 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenLog(SingboxConfig singboxConfig)
{
try
{
switch (_config.CoreBasicItem.Loglevel)
{
case "debug":
case "info":
case "error":
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
break;
case "warning":
singboxConfig.log.level = "warn";
break;
default:
break;
}
if (_config.CoreBasicItem.Loglevel == Global.None)
{
singboxConfig.log.disabled = true;
}
if (_config.CoreBasicItem.LogEnabled)
{
var dtNow = DateTime.Now;
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}

View file

@ -0,0 +1,577 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenOutbound(ProfileItem node, Outbound4Sbox outbound)
{
try
{
outbound.server = node.Address;
outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType];
switch (node.ConfigType)
{
case EConfigType.VMess:
{
outbound.uuid = node.Id;
outbound.alter_id = node.AlterId;
if (Global.VmessSecurities.Contains(node.Security))
{
outbound.security = node.Security;
}
else
{
outbound.security = Global.DefaultSecurity;
}
await GenOutboundMux(node, outbound);
break;
}
case EConfigType.Shadowsocks:
{
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
outbound.password = node.Id;
await GenOutboundMux(node, outbound);
break;
}
case EConfigType.SOCKS:
{
outbound.version = "5";
if (node.Security.IsNotEmpty()
&& node.Id.IsNotEmpty())
{
outbound.username = node.Security;
outbound.password = node.Id;
}
break;
}
case EConfigType.HTTP:
{
if (node.Security.IsNotEmpty()
&& node.Id.IsNotEmpty())
{
outbound.username = node.Security;
outbound.password = node.Id;
}
break;
}
case EConfigType.VLESS:
{
outbound.uuid = node.Id;
outbound.packet_encoding = "xudp";
if (node.Flow.IsNullOrEmpty())
{
await GenOutboundMux(node, outbound);
}
else
{
outbound.flow = node.Flow;
}
break;
}
case EConfigType.Trojan:
{
outbound.password = node.Id;
await GenOutboundMux(node, outbound);
break;
}
case EConfigType.Hysteria2:
{
outbound.password = node.Id;
if (node.Path.IsNotEmpty())
{
outbound.obfs = new()
{
type = "salamander",
password = node.Path.TrimEx(),
};
}
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null;
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null;
if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
{
outbound.server_port = null;
outbound.server_ports = node.Ports.Split(',')
.Select(p => p.Trim())
.Where(p => p.IsNotEmpty())
.Select(p =>
{
var port = p.Replace('-', ':');
return port.Contains(':') ? port : $"{port}:{port}";
})
.ToList();
outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null;
}
break;
}
case EConfigType.TUIC:
{
outbound.uuid = node.Id;
outbound.password = node.Security;
outbound.congestion_control = node.HeaderType;
break;
}
case EConfigType.Anytls:
{
outbound.password = node.Id;
break;
}
}
await GenOutboundTls(node, outbound);
await GenOutboundTransport(node, outbound);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint)
{
try
{
endpoint.address = Utils.String2List(node.RequestHost);
endpoint.type = Global.ProtocolTypes[node.ConfigType];
switch (node.ConfigType)
{
case EConfigType.WireGuard:
{
var peer = new Peer4Sbox
{
public_key = node.PublicKey,
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
address = node.Address,
port = node.Port,
// TODO default ["0.0.0.0/0", "::/0"]
allowed_ips = new() { "0.0.0.0/0", "::/0" },
};
endpoint.private_key = node.Id;
endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
endpoint.peers = new() { peer };
break;
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<BaseServer4Sbox?> GenServer(ProfileItem node)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (node.ConfigType == EConfigType.WireGuard)
{
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
await GenEndpoint(node, endpoint);
return endpoint;
}
else
{
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(node, outbound);
return outbound;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
{
var mux = new Multiplex4Sbox()
{
enabled = true,
protocol = _config.Mux4SboxItem.Protocol,
max_connections = _config.Mux4SboxItem.MaxConnections,
padding = _config.Mux4SboxItem.Padding,
};
outbound.multiplex = mux;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
{
try
{
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
{
var server_name = string.Empty;
if (node.Sni.IsNotEmpty())
{
server_name = node.Sni;
}
else if (node.RequestHost.IsNotEmpty())
{
server_name = Utils.String2List(node.RequestHost)?.First();
}
var tls = new Tls4Sbox()
{
enabled = true,
record_fragment = _config.CoreBasicItem.EnableFragment,
server_name = server_name,
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(),
};
if (node.Fingerprint.IsNotEmpty())
{
tls.utls = new Utls4Sbox()
{
enabled = true,
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
};
}
if (node.StreamSecurity == Global.StreamSecurityReality)
{
tls.reality = new Reality4Sbox()
{
enabled = true,
public_key = node.PublicKey,
short_id = node.ShortId
};
tls.insecure = false;
}
outbound.tls = tls;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var transport = new Transport4Sbox();
switch (node.GetNetwork())
{
case nameof(ETransport.h2):
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
break;
case nameof(ETransport.tcp): //http
if (node.HeaderType == Global.TcpHeaderHttp)
{
if (node.ConfigType == EConfigType.Shadowsocks)
{
outbound.plugin = "obfs-local";
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
}
else
{
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
}
}
break;
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
if (node.RequestHost.IsNotEmpty())
{
transport.headers = new()
{
Host = node.RequestHost
};
}
break;
case nameof(ETransport.httpupgrade):
transport.type = nameof(ETransport.httpupgrade);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
break;
case nameof(ETransport.quic):
transport.type = nameof(ETransport.quic);
break;
case nameof(ETransport.grpc):
transport.type = nameof(ETransport.grpc);
transport.service_name = node.Path;
transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s");
transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s");
transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream;
break;
default:
break;
}
if (transport.type != null)
{
outbound.transport = transport;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
{
if (node.Subid.IsNullOrEmpty())
{
return 0;
}
try
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is null)
{
return 0;
}
//current proxy
BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag, null);
outbound ??= singboxConfig.outbounds.First();
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
//Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
{
prevOutboundTag = $"prev-{Global.ProxyTag}";
var prevServer = await GenServer(prevNode);
prevServer.tag = prevOutboundTag;
if (prevServer is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (prevServer is Outbound4Sbox outboundPrev)
{
singboxConfig.outbounds.Add(outboundPrev);
}
}
var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
if (nextServer is not null)
{
if (nextServer is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Insert(0, endpoint);
}
else if (nextServer is Outbound4Sbox outboundNext)
{
singboxConfig.outbounds.Insert(0, outboundNext);
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
{
try
{
// Get outbound template and initialize lists
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (txtOutbound.IsNullOrEmpty())
{
return 0;
}
var resultOutbounds = new List<Outbound4Sbox>();
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints
var proxyTags = new List<string>(); // For selector and urltest outbounds
// Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
var prevIndex = 0; // Index for prev outbounds
// Process each node
var index = 0;
foreach (var node in nodes)
{
index++;
// Handle proxy chain
string? prevTag = null;
var currentServer = await GenServer(node);
var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextServer != null)
{
nextServer = JsonUtils.DeepCopy(nextServer);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
// current proxy
currentServer.tag = $"{Global.ProxyTag}-{index}";
proxyTags.Add(currentServer.tag);
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.SingboxSupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
prevOutbound.tag = prevTag;
prevOutbounds.Add(prevOutbound);
}
prevProxyTags[node.Subid] = prevTag;
}
nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
if (!nextProxyCache.ContainsKey(node.Subid))
{
nextProxyCache[node.Subid] = nextServer;
}
}
if (nextServer is not null)
{
if (nextServer is Endpoints4Sbox nextEndpoint)
{
resultEndpoints.Add(nextEndpoint);
}
else if (nextServer is Outbound4Sbox nextOutbound)
{
resultOutbounds.Add(nextOutbound);
}
}
if (currentServer is Endpoints4Sbox currentEndpoint)
{
resultEndpoints.Add(currentEndpoint);
}
else if (currentServer is Outbound4Sbox currentOutbound)
{
resultOutbounds.Add(currentOutbound);
}
}
// Add urltest outbound (auto selection based on latency)
if (proxyTags.Count > 0)
{
var outUrltest = new Outbound4Sbox
{
type = "urltest",
tag = $"{Global.ProxyTag}-auto",
outbounds = proxyTags,
interrupt_exist_connections = false,
};
// Add selector outbound (manual selection)
var outSelector = new Outbound4Sbox
{
type = "selector",
tag = Global.ProxyTag,
outbounds = JsonUtils.DeepCopy(proxyTags),
interrupt_exist_connections = false,
};
outSelector.outbounds.Insert(0, outUrltest.tag);
// Insert these at the beginning
resultOutbounds.Insert(0, outUrltest);
resultOutbounds.Insert(0, outSelector);
}
// Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds
resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(singboxConfig.outbounds);
singboxConfig.outbounds = resultOutbounds;
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
resultEndpoints.AddRange(singboxConfig.endpoints);
singboxConfig.endpoints = resultEndpoints;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (!prevOutboundTag.IsNullOrEmpty())
{
outbound.detour = prevOutboundTag;
}
// Next proxy
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType))
{
nextOutbound ??= await GenServer(nextNode);
nextOutbound.tag = outbound.tag;
outbound.tag = $"mid-{outbound.tag}";
nextOutbound.detour = outbound.tag;
}
return nextOutbound;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return null;
}
}

View file

@ -0,0 +1,365 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenRouting(SingboxConfig singboxConfig)
{
try
{
singboxConfig.route.final = Global.ProxyTag;
var item = _config.SimpleDNSItem;
var defaultDomainResolverTag = Global.SingboxOutboundResolverTag;
var directDNSStrategy = item.SingboxStrategy4Direct.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : item.SingboxStrategy4Direct;
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (rawDNSItem != null && rawDNSItem.Enabled == true)
{
defaultDomainResolverTag = Global.SingboxFinalResolverTag;
directDNSStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : rawDNSItem.DomainStrategy4Freedom;
}
singboxConfig.route.default_domain_resolver = new()
{
server = defaultDomainResolverTag,
strategy = directDNSStrategy
};
if (_config.TunModeItem.EnableTun)
{
singboxConfig.route.auto_detect_interface = true;
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
if (tunRules != null)
{
singboxConfig.route.rules.AddRange(tunRules);
}
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
singboxConfig.route.rules.Add(new()
{
port = new() { 53 },
action = "hijack-dns",
process_name = lstDnsExe
});
singboxConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
process_name = lstDirectExe
});
}
if (_config.Inbound.First().SniffingEnabled)
{
singboxConfig.route.rules.Add(new()
{
action = "sniff"
});
singboxConfig.route.rules.Add(new()
{
protocol = new() { "dns" },
action = "hijack-dns"
});
}
else
{
singboxConfig.route.rules.Add(new()
{
port = new() { 53 },
network = new() { "udp" },
action = "hijack-dns"
});
}
singboxConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
clash_mode = ERuleMode.Direct.ToString()
});
singboxConfig.route.rules.Add(new()
{
outbound = Global.ProxyTag,
clash_mode = ERuleMode.Global.ToString()
});
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
{
domainStrategy = defaultRouting.DomainStrategy4Singbox;
}
var resolveRule = new Rule4Sbox
{
action = "resolve",
strategy = domainStrategy
};
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
{
singboxConfig.route.rules.Add(resolveRule);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item1 in rules ?? [])
{
if (item1.Enabled)
{
await GenRoutingUserRule(item1, singboxConfig);
if (item1.Ip != null && item1.Ip.Count > 0)
{
ipRules.Add(item1);
}
}
}
}
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
{
singboxConfig.route.rules.Add(resolveRule);
foreach (var item2 in ipRules)
{
await GenRoutingUserRule(item2, singboxConfig);
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
{
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
foreach (var coreConfig in coreInfoResult)
{
if (coreConfig.CoreType == ECoreType.v2rayN)
{
continue;
}
foreach (var baseExeName in coreConfig.CoreExes)
{
if (coreConfig.CoreType != ECoreType.sing_box)
{
dnsExeSet.Add(Utils.GetExeName(baseExeName));
}
directExeSet.Add(Utils.GetExeName(baseExeName));
}
}
lstDnsExe = new List<string>(dnsExeSet);
lstDirectExe = new List<string>(directExeSet);
}
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
{
try
{
if (item == null)
{
return 0;
}
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules;
var rule = new Rule4Sbox();
if (item.OutboundTag == "block")
{
rule.action = "reject";
}
else
{
rule.outbound = item.OutboundTag;
}
if (item.Port.IsNotEmpty())
{
var portRanges = item.Port.Split(',').Where(it => it.Contains('-')).Select(it => it.Replace("-", ":")).ToList();
var ports = item.Port.Split(',').Where(it => !it.Contains('-')).Select(it => it.ToInt()).ToList();
rule.port_range = portRanges.Count > 0 ? portRanges : null;
rule.port = ports.Count > 0 ? ports : null;
}
if (item.Network.IsNotEmpty())
{
rule.network = Utils.String2List(item.Network);
}
if (item.Protocol?.Count > 0)
{
rule.protocol = item.Protocol;
}
if (item.InboundTag?.Count >= 0)
{
rule.inbound = item.InboundTag;
}
var rule1 = JsonUtils.DeepCopy(rule);
var rule2 = JsonUtils.DeepCopy(rule);
var rule3 = JsonUtils.DeepCopy(rule);
var hasDomainIp = false;
if (item.Domain?.Count > 0)
{
var countDomain = 0;
foreach (var it in item.Domain)
{
if (ParseV2Domain(it, rule1))
countDomain++;
}
if (countDomain > 0)
{
rules.Add(rule1);
hasDomainIp = true;
}
}
if (item.Ip?.Count > 0)
{
var countIp = 0;
foreach (var it in item.Ip)
{
if (ParseV2Address(it, rule2))
countIp++;
}
if (countIp > 0)
{
rules.Add(rule2);
hasDomainIp = true;
}
}
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
{
rule3.process_name = item.Process;
rules.Add(rule3);
hasDomainIp = true;
}
if (!hasDomainIp
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
{
rules.Add(rule);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private bool ParseV2Domain(string domain, Rule4Sbox rule)
{
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{
return false;
}
else if (domain.StartsWith("geosite:"))
{
rule.geosite ??= [];
rule.geosite?.Add(domain.Substring(8));
}
else if (domain.StartsWith("regexp:"))
{
rule.domain_regex ??= [];
rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7));
}
else if (domain.StartsWith("domain:"))
{
rule.domain ??= [];
rule.domain_suffix ??= [];
rule.domain?.Add(domain.Substring(7));
rule.domain_suffix?.Add("." + domain.Substring(7));
}
else if (domain.StartsWith("full:"))
{
rule.domain ??= [];
rule.domain?.Add(domain.Substring(5));
}
else if (domain.StartsWith("keyword:"))
{
rule.domain_keyword ??= [];
rule.domain_keyword?.Add(domain.Substring(8));
}
else
{
rule.domain_keyword ??= [];
rule.domain_keyword?.Add(domain);
}
return true;
}
private bool ParseV2Address(string address, Rule4Sbox rule)
{
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
{
return false;
}
else if (address.Equals("geoip:private"))
{
rule.ip_is_private = true;
}
else if (address.StartsWith("geoip:"))
{
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
}
else if (address.Equals("geoip:!private"))
{
rule.ip_is_private = false;
}
else if (address.StartsWith("geoip:!"))
{
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
rule.invert = true;
}
else
{
rule.ip_cidr ??= new();
rule.ip_cidr?.Add(address);
}
return true;
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{
return Global.ProxyTag;
}
var server = await GenServer(node);
if (server is null)
{
return Global.ProxyTag;
}
server.tag = Global.ProxyTag + node.IndexId.ToString();
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;
}
}

View file

@ -0,0 +1,119 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
{
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{
if (rule_set != null)
ruleSets.AddRange(rule_set);
}
var geosite = "geosite";
var geoip = "geoip";
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() ?? [])
{
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() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
rule.geoip = null;
AddRuleSets(ruleSets, rule.rule_set);
}
//convert dns geosite & geoip to ruleset
foreach (var rule in singboxConfig.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() ?? [])
{
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() ?? [])
{
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 item2 in item ?? [])
{
AddRuleSets(ruleSets, item2.rule_set);
}
}
//load custom ruleset file
List<Ruleset4Sbox> customRulesets = [];
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
{
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
if (result.IsNotEmpty())
{
customRulesets = (JsonUtils.Deserialize<List<Ruleset4Sbox>>(result) ?? [])
.Where(t => t.tag != null)
.Where(t => t.type != null)
.Where(t => t.format != null)
.ToList();
}
}
//Local srs files address
var localSrss = Utils.GetBinPath("srss");
//Add ruleset srs
singboxConfig.route.rule_set = [];
foreach (var item in new HashSet<string>(ruleSets))
{
if (item.IsNullOrEmpty())
{ continue; }
var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item));
if (customRuleset is null)
{
var pathSrs = Path.Combine(localSrss, $"{item}.srs");
if (File.Exists(pathSrs))
{
customRuleset = new()
{
type = "local",
format = "binary",
tag = item,
path = pathSrs
};
}
else
{
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
? Global.SingboxRulesetUrl
: _config.ConstItem.SrsSourceUrl;
customRuleset = new()
{
type = "remote",
format = "binary",
tag = item,
url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item),
download_detour = Global.ProxyTag
};
}
}
singboxConfig.route.rule_set.Add(customRuleset);
}
return 0;
}
}

View file

@ -0,0 +1,29 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
{
//if (_config.guiItem.enableStatistics)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
{
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
};
}
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
{
enabled = true,
path = Utils.GetBinPath("cache.db"),
store_fakeip = _config.SimpleDNSItem.FakeIP == true
};
}
return await Task.FromResult(0);
}
}

View file

@ -0,0 +1,411 @@
using System.Net;
using System.Net.NetworkInformation;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService(Config config)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigV2rayService";
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
await GenOutbound(node, v2rayConfig.outbounds.First());
await GenMoreOutbounds(node, v2rayConfig);
await GenRouting(v2rayConfig);
await GenDns(node, v2rayConfig);
await GenStatistic(v2rayConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
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> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
string 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;
}
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
v2rayConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenOutboundsList(proxyProfiles, v2rayConfig);
//add balancers
await GenBalancer(v2rayConfig, multipleLoad);
var balancer = v2rayConfig.routing.balancers.First();
//add rule
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList();
if (rules?.Count > 0)
{
foreach (var rule in rules)
{
rule.outboundTag = null;
rule.balancerTag = balancer.tag;
}
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
v2rayConfig.routing.rules.Add(new()
{
ip = ["0.0.0.0/0", "::/0"],
balancerTag = balancer.tag,
type = "field"
});
}
else
{
v2rayConfig.routing.rules.Add(new()
{
network = "tcp,udp",
balancerTag = balancer.tag,
type = "field"
});
}
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
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)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
List<IPEndPoint> lstIpEndPoints = new();
List<TcpConnectionInformation> lstTcpConns = new();
try
{
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await GenLog(v2rayConfig);
v2rayConfig.inbounds.Clear();
v2rayConfig.outbounds.Clear();
v2rayConfig.routing.rules.Clear();
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
foreach (var it in selecteds)
{
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
//find unused port
var port = initPort;
for (var k = initPort; k < Global.MaxPort; k++)
{
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
{
continue;
}
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
{
continue;
}
//found
port = k;
initPort = port + 1;
break;
}
//Port In Used
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
{
continue;
}
it.Port = port;
it.AllowTest = true;
//outbound
if (item is null)
{
continue;
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInXray.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS
&& !Global.Flows.Contains(item.Flow))
{
continue;
}
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
continue;
}
//inbound
Inbounds4Ray inbound = new()
{
listen = Global.Loopback,
port = port,
protocol = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.protocol + inbound.port.ToString();
v2rayConfig.inbounds.Add(inbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(item, outbound);
outbound.tag = Global.ProxyTag + inbound.port.ToString();
v2rayConfig.outbounds.Add(outbound);
//rule
RulesItem4Ray rule = new()
{
inboundTag = new List<string> { inbound.tag },
outboundTag = outbound.tag,
type = "field"
};
v2rayConfig.routing.rules.Add(rule);
}
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node is not { Port: > 0 })
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
await GenOutbound(node, v2rayConfig.outbounds.First());
await GenMoreOutbounds(node, v2rayConfig);
v2rayConfig.routing.rules.Clear();
v2rayConfig.inbounds.Clear();
v2rayConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.socks}{port}",
listen = Global.Loopback,
port = port,
protocol = EInboundProtocol.mixed.ToString(),
});
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
#endregion public gen function
}

View file

@ -0,0 +1,50 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
{
if (multipleLoad == EMultipleLoad.LeastPing)
{
var observatory = new Observatory4Ray
{
subjectSelector = [Global.ProxyTag],
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
probeInterval = "3m",
enableConcurrency = true,
};
v2rayConfig.observatory = observatory;
}
else if (multipleLoad == EMultipleLoad.LeastLoad)
{
var burstObservatory = new BurstObservatory4Ray
{
subjectSelector = [Global.ProxyTag],
pingConfig = new()
{
destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
interval = "5m",
timeout = "30s",
sampling = 2,
}
};
v2rayConfig.burstObservatory = burstObservatory;
}
var strategyType = multipleLoad switch
{
EMultipleLoad.Random => "random",
EMultipleLoad.RoundRobin => "roundRobin",
EMultipleLoad.LeastPing => "leastPing",
EMultipleLoad.LeastLoad => "leastLoad",
_ => "roundRobin",
};
var balancer = new BalancersItem4Ray
{
selector = [Global.ProxyTag],
strategy = new() { type = strategyType },
tag = $"{Global.ProxyTag}-round",
};
v2rayConfig.routing.balancers = [balancer];
return await Task.FromResult(0);
}
}

View file

@ -0,0 +1,86 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
{
return JsonUtils.Serialize(v2rayConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(v2rayConfig);
}
// Handle balancer and rules modifications (for multiple load scenarios)
if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0)
{
var balancer = v2rayConfig.routing.balancers.First();
// Modify existing rules in custom config
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
if (rulesNode != null)
{
foreach (var rule in rulesNode.AsArray())
{
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
}
}
}
// Ensure routing node exists
if (fullConfigTemplateNode["routing"] == null)
{
fullConfigTemplateNode["routing"] = new JsonObject();
}
// Handle balancers - append instead of override
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
{
foreach (var balancerNode in newBalancers)
{
customBalancersNode.Add(balancerNode?.DeepClone());
}
}
}
else
{
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
}
}
// Handle outbounds - append instead of override
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in v2rayConfig.outbounds)
{
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
{
if (fullConfigTemplate.AddProxyOnly == true)
{
continue;
}
}
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty)))
{
outbound.streamSettings ??= new StreamSettings4Ray();
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
}
}

View file

@ -0,0 +1,422 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
if (item != null && item.Enabled == true)
{
var result = await GenDnsCompatible(node, v2rayConfig);
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
}
return result;
}
var simpleDNSItem = _config.SimpleDNSItem;
var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom;
//Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty())
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new()
{
domainStrategy = domainStrategy4Freedom,
userLevel = 0
};
}
}
await GenDnsServers(node, v2rayConfig, simpleDNSItem);
await GenDnsHosts(v2rayConfig, simpleDNSItem);
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
{
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
.Select(addr => addr.Trim())
.Where(addr => !string.IsNullOrEmpty(addr))
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
.Distinct()
.ToList() ?? new List<string> { defaultAddress };
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var dnsServer = new DnsServer4Ray
{
address = dnsAddress,
skipFallback = true,
domains = domains.Count > 0 ? domains : null,
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
};
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
var directDomainList = new List<string>();
var directGeositeList = new List<string>();
var proxyDomainList = new List<string>();
var proxyGeositeList = new List<string>();
var expectedDomainList = new List<string>();
var expectedIPs = new List<string>();
var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
{
expectedIPs = simpleDNSItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToList();
foreach (var ip in expectedIPs)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
{
regionNames.Add($"geosite:{region}");
regionNames.Add($"geosite:geolocation-{region}");
regionNames.Add($"geosite:tld-{region}");
}
}
}
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
List<RulesItem>? rules = null;
if (routing != null)
{
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
foreach (var item in rules)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
continue;
}
foreach (var domain in item.Domain)
{
if (domain.StartsWith('#'))
continue;
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
{
if (normalizedDomain.StartsWith("geosite:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
else
{
directDomainList.Add(normalizedDomain);
}
}
else if (item.OutboundTag != Global.BlockTag)
{
if (normalizedDomain.StartsWith("geosite:"))
{
proxyGeositeList.Add(normalizedDomain);
}
else
{
proxyDomainList.Add(normalizedDomain);
}
}
}
}
}
if (Utils.IsDomain(node?.Address))
{
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);
}
}
}
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.servers ??= new List<object>();
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
{
if (domains.Count > 0)
{
foreach (var dnsAddress in dnsAddresses)
{
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
}
}
}
AddDnsServers(remoteDNSAddress, proxyDomainList);
AddDnsServers(directDNSAddress, directDomainList);
AddDnsServers(remoteDNSAddress, proxyGeositeList);
AddDnsServers(directDNSAddress, directGeositeList);
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
var useDirectDns = rules?.LastOrDefault() is { } lastRule
&& lastRule.OutboundTag == Global.DirectTag
&& (lastRule.Port == "0-65535"
|| lastRule.Network == "tcp,udp"
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
return 0;
}
private async Task<int> GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
{
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
{
return await Task.FromResult(0);
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
if (simpleDNSItem.AddCommonHosts == true)
{
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
kvp => kvp.Key,
kvp => (object)kvp.Value
);
}
if (simpleDNSItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts.Count > 0)
{
var normalHost = v2rayConfig.dns.hosts;
if (normalHost != null)
{
foreach (var host in systemHosts)
{
if (normalHost[host.Key] != null)
{
continue;
}
normalHost[host.Key] = new List<string> { host.Value };
}
}
}
}
var userHostsMap = simpleDNSItem.Hosts?
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Where(line => !string.IsNullOrWhiteSpace(line))
.Where(line => line.Contains(' '))
.ToDictionary(
line =>
{
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
return parts[0];
},
line =>
{
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
var values = parts.Skip(1).ToList();
return values;
}
);
if (userHostsMap != null)
{
foreach (var kvp in userHostsMap)
{
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
}
}
return await Task.FromResult(0);
}
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
var normalDNS = item?.NormalDNS;
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
if (normalDNS.IsNullOrEmpty())
{
normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName);
}
//Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty())
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new();
outbound.settings.domainStrategy = domainStrategy4Freedom;
outbound.settings.userLevel = 0;
}
}
var obj = JsonUtils.ParseJson(normalDNS);
if (obj is null)
{
List<string> servers = [];
string[] arrDNS = normalDNS.Split(',');
foreach (string str in arrDNS)
{
servers.Add(str);
}
obj = JsonUtils.ParseJson("{}");
obj["servers"] = JsonUtils.SerializeToNode(servers);
}
// Append to dns settings
if (item.UseSystemHosts)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts.Count > 0)
{
var normalHost1 = obj["hosts"];
if (normalHost1 != null)
{
foreach (var host in systemHosts)
{
if (normalHost1[host.Key] != null)
continue;
normalHost1[host.Key] = host.Value;
}
}
}
}
var normalHost = obj["hosts"];
if (normalHost != null)
{
foreach (var hostProp in normalHost.AsObject().ToList())
{
if (hostProp.Value is JsonValue value && value.TryGetValue<string>(out var ip))
{
normalHost[hostProp.Key] = new JsonArray(ip);
}
}
}
await GenDnsDomainsCompatible(node, obj, item);
v2rayConfig.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)
{
if (node == null)
{
return 0;
}
var servers = dns["servers"];
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 await Task.FromResult(0);
}
}

View file

@ -0,0 +1,72 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
{
try
{
var listen = "0.0.0.0";
v2rayConfig.inbounds = [];
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
v2rayConfig.inbounds.Add(inbound);
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
v2rayConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
inbound3.listen = listen;
v2rayConfig.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 } };
}
}
else
{
inbound.listen = listen;
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
{
string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
if (result.IsNullOrEmpty())
{
return new();
}
var inbound = JsonUtils.Deserialize<Inbounds4Ray>(result);
if (inbound == null)
{
return new();
}
inbound.tag = protocol.ToString();
inbound.port = inItem.LocalPort + (int)protocol;
inbound.protocol = EInboundProtocol.mixed.ToString();
inbound.settings.udp = inItem.UdpEnabled;
inbound.sniffing.enabled = inItem.SniffingEnabled;
inbound.sniffing.destOverride = inItem.DestOverride;
inbound.sniffing.routeOnly = inItem.RouteOnly;
return inbound;
}
}

View file

@ -0,0 +1,29 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenLog(V2rayConfig v2rayConfig)
{
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");
}
else
{
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = null;
v2rayConfig.log.error = null;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}

View file

@ -0,0 +1,695 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound)
{
try
{
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType)
{
case EConfigType.VMess:
{
VnextItem4Ray vnextItem;
if (outbound.settings.vnext.Count <= 0)
{
vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext.First();
}
vnextItem.address = node.Address;
vnextItem.port = node.Port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users.First();
}
usersItem.id = node.Id;
usersItem.alterId = node.AlterId;
usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(node.Security))
{
usersItem.security = node.Security;
}
else
{
usersItem.security = Global.DefaultSecurity;
}
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
outbound.settings.servers = null;
break;
}
case EConfigType.Shadowsocks:
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.password = node.Id;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none";
serversItem.ota = false;
serversItem.level = 1;
await GenOutboundMux(node, outbound);
outbound.settings.vnext = null;
break;
}
case EConfigType.SOCKS:
case EConfigType.HTTP:
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.method = null;
serversItem.password = null;
if (node.Security.IsNotEmpty()
&& node.Id.IsNotEmpty())
{
SocksUsersItem4Ray socksUsersItem = new()
{
user = node.Security,
pass = node.Id,
level = 1
};
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
}
await GenOutboundMux(node, outbound);
outbound.settings.vnext = null;
break;
}
case EConfigType.VLESS:
{
VnextItem4Ray vnextItem;
if (outbound.settings.vnext?.Count <= 0)
{
vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext.First();
}
vnextItem.address = node.Address;
vnextItem.port = node.Port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users.First();
}
usersItem.id = node.Id;
usersItem.email = Global.UserEMail;
usersItem.encryption = node.Security;
if (node.Flow.IsNullOrEmpty())
{
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
}
else
{
usersItem.flow = node.Flow;
await GenOutboundMux(node, outbound, false, muxEnabled);
}
outbound.settings.servers = null;
break;
}
case EConfigType.Trojan:
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers.First();
}
serversItem.address = node.Address;
serversItem.port = node.Port;
serversItem.password = node.Id;
serversItem.ota = false;
serversItem.level = 1;
await GenOutboundMux(node, outbound);
outbound.settings.vnext = null;
break;
}
case EConfigType.WireGuard:
{
var peer = new WireguardPeer4Ray
{
publicKey = node.PublicKey,
endpoint = node.Address + ":" + node.Port.ToString()
};
var setting = new Outboundsettings4Ray
{
address = Utils.String2List(node.RequestHost),
secretKey = node.Id,
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(),
peers = new List<WireguardPeer4Ray> { peer }
};
outbound.settings = setting;
outbound.settings.vnext = null;
outbound.settings.servers = null;
break;
}
}
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
await GenBoundStreamSettings(node, 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)
{
try
{
outbound.mux.enabled = false;
outbound.mux.concurrency = -1;
if (enabledTCP)
{
outbound.mux.enabled = true;
outbound.mux.concurrency = _config.Mux4RayItem.Concurrency;
}
else if (enabledUDP)
{
outbound.mux.enabled = true;
outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency;
outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound)
{
try
{
var streamSettings = outbound.streamSettings;
streamSettings.network = node.GetNetwork();
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;
}
}
//if tls
if (node.StreamSecurity == Global.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
};
if (sni.IsNotEmpty())
{
tlsSettings.serverName = sni;
}
else if (host.IsNotEmpty())
{
tlsSettings.serverName = Utils.String2List(host)?.First();
}
streamSettings.tlsSettings = tlsSettings;
}
//if Reality
if (node.StreamSecurity == Global.StreamSecurityReality)
{
streamSettings.security = node.StreamSecurity;
TlsSettings4Ray realitySettings = new()
{
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
serverName = sni,
publicKey = node.PublicKey,
shortId = node.ShortId,
spiderX = node.SpiderX,
mldsa65Verify = node.Mldsa65Verify,
show = false,
};
streamSettings.realitySettings = realitySettings;
}
//streamSettings
switch (node.GetNetwork())
{
case nameof(ETransport.kcp):
KcpSettings4Ray kcpSettings = new()
{
mtu = _config.KcpItem.Mtu,
tti = _config.KcpItem.Tti
};
kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity;
kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity;
kcpSettings.congestion = _config.KcpItem.Congestion;
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
kcpSettings.header = new Header4Ray
{
type = node.HeaderType,
domain = host.IsNullOrEmpty() ? null : host
};
if (path.IsNotEmpty())
{
kcpSettings.seed = path;
}
streamSettings.kcpSettings = kcpSettings;
break;
//ws
case nameof(ETransport.ws):
WsSettings4Ray wsSettings = new();
wsSettings.headers = new Headers4Ray();
if (host.IsNotEmpty())
{
wsSettings.host = host;
wsSettings.headers.Host = host;
}
if (path.IsNotEmpty())
{
wsSettings.path = path;
}
if (useragent.IsNotEmpty())
{
wsSettings.headers.UserAgent = useragent;
}
streamSettings.wsSettings = wsSettings;
break;
//httpupgrade
case nameof(ETransport.httpupgrade):
HttpupgradeSettings4Ray httpupgradeSettings = new();
if (path.IsNotEmpty())
{
httpupgradeSettings.path = path;
}
if (host.IsNotEmpty())
{
httpupgradeSettings.host = host;
}
streamSettings.httpupgradeSettings = httpupgradeSettings;
break;
//xhttp
case nameof(ETransport.xhttp):
streamSettings.network = ETransport.xhttp.ToString();
XhttpSettings4Ray xhttpSettings = new();
if (path.IsNotEmpty())
{
xhttpSettings.path = path;
}
if (host.IsNotEmpty())
{
xhttpSettings.host = host;
}
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType))
{
xhttpSettings.mode = node.HeaderType;
}
if (node.Extra.IsNotEmpty())
{
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra);
}
streamSettings.xhttpSettings = xhttpSettings;
await GenOutboundMux(node, outbound);
break;
//h2
case nameof(ETransport.h2):
HttpSettings4Ray httpSettings = new();
if (host.IsNotEmpty())
{
httpSettings.host = Utils.String2List(host);
}
httpSettings.path = path;
streamSettings.httpSettings = httpSettings;
break;
//quic
case nameof(ETransport.quic):
QuicSettings4Ray quicsettings = new()
{
security = host,
key = path,
header = new Header4Ray
{
type = node.HeaderType
}
};
streamSettings.quicSettings = quicsettings;
if (node.StreamSecurity == Global.StreamSecurity)
{
if (sni.IsNotEmpty())
{
streamSettings.tlsSettings.serverName = sni;
}
else
{
streamSettings.tlsSettings.serverName = node.Address;
}
}
break;
case nameof(ETransport.grpc):
GrpcSettings4Ray grpcSettings = new()
{
authority = host.IsNullOrEmpty() ? null : host,
serviceName = path,
multiMode = node.HeaderType == Global.GrpcMultiMode,
idle_timeout = _config.GrpcItem.IdleTimeout,
health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
permit_without_stream = _config.GrpcItem.PermitWithoutStream,
initial_windows_size = _config.GrpcItem.InitialWindowsSize,
};
streamSettings.grpcSettings = grpcSettings;
break;
default:
//tcp
if (node.HeaderType == Global.TcpHeaderHttp)
{
TcpSettings4Ray tcpSettings = new()
{
header = new Header4Ray
{
type = node.HeaderType
}
};
//request Host
string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName);
string[] arrHost = host.Split(',');
string host2 = string.Join(",".AppendQuotes(), arrHost);
request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}");
request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}");
//Path
string pathHttp = @"/";
if (path.IsNotEmpty())
{
string[] arrPath = path.Split(',');
pathHttp = string.Join(",".AppendQuotes(), arrPath);
}
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
tcpSettings.header.request = JsonUtils.Deserialize<object>(request);
streamSettings.tcpSettings = tcpSettings;
}
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
{
//fragment proxy
if (_config.CoreBasicItem.EnableFragment
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
{
var fragmentOutbound = new Outbounds4Ray
{
protocol = "freedom",
tag = $"{Global.ProxyTag}3",
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);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
{
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
int prevIndex = 0; // Index for prev outbounds
// Process nodes
int index = 0;
foreach (var node in nodes)
{
index++;
// 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 = $"{Global.ProxyTag}-{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-{Global.ProxyTag}-{++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
resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = 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;
}
}

View file

@ -0,0 +1,142 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
{
try
{
if (v2rayConfig.routing?.rules != null)
{
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing != null)
{
if (routing.DomainStrategy.IsNotEmpty())
{
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules)
{
if (item.Enabled)
{
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
{
try
{
if (rule == null)
{
return 0;
}
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
if (rule.port.IsNullOrEmpty())
{
rule.port = null;
}
if (rule.network.IsNullOrEmpty())
{
rule.network = null;
}
if (rule.domain?.Count == 0)
{
rule.domain = null;
}
if (rule.ip?.Count == 0)
{
rule.ip = null;
}
if (rule.protocol?.Count == 0)
{
rule.protocol = null;
}
if (rule.inboundTag?.Count == 0)
{
rule.inboundTag = null;
}
var hasDomainIp = false;
if (rule.domain?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
it.ip = null;
it.type = "field";
for (var k = it.domain.Count - 1; k >= 0; k--)
{
if (it.domain[k].StartsWith("#"))
{
it.domain.RemoveAt(k);
}
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
}
v2rayConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (rule.ip?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
it.domain = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (!hasDomainIp)
{
if (rule.port.IsNotEmpty()
|| rule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0
|| rule.network != null
)
{
var it = JsonUtils.DeepCopy(rule);
it.type = "field";
v2rayConfig.routing.rules.Add(it);
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| !Global.XraySupportConfigType.Contains(node.ConfigType))
{
return Global.ProxyTag;
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(node, outbound);
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
v2rayConfig.outbounds.Add(outbound);
return outbound.tag;
}
}

View file

@ -0,0 +1,51 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
{
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{
string tag = EInboundProtocol.api.ToString();
Metrics4Ray apiObj = new();
Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new();
v2rayConfig.stats = new Stats4Ray();
apiObj.tag = tag;
v2rayConfig.metrics = apiObj;
policySystemSetting.statsOutboundDownlink = true;
policySystemSetting.statsOutboundUplink = true;
policyObj.system = policySystemSetting;
v2rayConfig.policy = policyObj;
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
{
Inbounds4Ray apiInbound = new();
Inboundsettings4Ray apiInboundSettings = new();
apiInbound.tag = tag;
apiInbound.listen = Global.Loopback;
apiInbound.port = AppManager.Instance.StatePort;
apiInbound.protocol = Global.InboundAPIProtocol;
apiInboundSettings.address = Global.Loopback;
apiInbound.settings = apiInboundSettings;
v2rayConfig.inbounds.Add(apiInbound);
}
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
{
RulesItem4Ray apiRoutingRule = new()
{
inboundTag = new List<string> { tag },
outboundTag = tag,
type = "field"
};
v2rayConfig.routing.rules.Add(apiRoutingRule);
}
}
return await Task.FromResult(0);
}
}

View file

@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Sockets; using System.Net.Sockets;
@ -20,7 +19,7 @@ public class DownloadService
{ {
try try
{ {
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
var progress = new Progress<string>(); var progress = new Progress<string>();
progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}"); progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}");
@ -45,7 +44,7 @@ public class DownloadService
{ {
try try
{ {
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}")); UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
var progress = new Progress<double>(); var progress = new Progress<double>();
@ -72,7 +71,7 @@ public class DownloadService
public async Task<string?> UrlRedirectAsync(string url, bool blProxy) public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
{ {
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
var webRequestHandler = new SocketsHttpHandler var webRequestHandler = new SocketsHttpHandler
{ {
AllowAutoRedirect = false, AllowAutoRedirect = false,
@ -142,7 +141,7 @@ public class DownloadService
{ {
try try
{ {
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
var webProxy = await GetWebProxy(blProxy); var webProxy = await GetWebProxy(blProxy);
var client = new HttpClient(new SocketsHttpHandler() var client = new HttpClient(new SocketsHttpHandler()
{ {
@ -187,7 +186,7 @@ public class DownloadService
{ {
try try
{ {
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
var webProxy = await GetWebProxy(blProxy); var webProxy = await GetWebProxy(blProxy);
@ -210,70 +209,13 @@ public class DownloadService
return null; return null;
} }
public async Task<int> RunAvailabilityCheck(IWebProxy? webProxy)
{
var responseTime = -1;
try
{
webProxy ??= await GetWebProxy(true);
var config = AppHandler.Instance.Config;
for (var i = 0; i < 2; i++)
{
responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
if (responseTime > 0)
{
break;
}
await Task.Delay(500);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return -1;
}
return responseTime;
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch //(Exception ex)
{
//Utile.SaveLog(ex.Message, ex);
}
return responseTime;
}
private async Task<WebProxy?> GetWebProxy(bool blProxy) private async Task<WebProxy?> GetWebProxy(bool blProxy)
{ {
if (!blProxy) if (!blProxy)
{ {
return null; return null;
} }
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
if (await SocketCheck(Global.Loopback, port) == false) if (await SocketCheck(Global.Loopback, port) == false)
{ {
return null; return null;

View file

@ -23,7 +23,7 @@ public class SpeedtestService
Task.Run(async () => Task.Run(async () =>
{ {
await RunAsync(actionType, selecteds); await RunAsync(actionType, selecteds);
await ProfileExHandler.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
UpdateFunc("", ResUI.SpeedtestingCompleted); UpdateFunc("", ResUI.SpeedtestingCompleted);
}); });
} }
@ -98,18 +98,18 @@ public class SpeedtestService
case ESpeedActionType.Tcping: case ESpeedActionType.Tcping:
case ESpeedActionType.Realping: case ESpeedActionType.Realping:
UpdateFunc(it.IndexId, ResUI.Speedtesting, ""); UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
break; break;
case ESpeedActionType.Speedtest: case ESpeedActionType.Speedtest:
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait); UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
break; break;
case ESpeedActionType.Mixedtest: case ESpeedActionType.Mixedtest:
UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait); UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
break; break;
} }
} }
@ -132,7 +132,7 @@ public class SpeedtestService
{ {
var responseTime = await GetTcpingTime(it.Address, it.Port); var responseTime = await GetTcpingTime(it.Address, it.Port);
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
UpdateFunc(it.IndexId, responseTime.ToString()); UpdateFunc(it.IndexId, responseTime.ToString());
} }
catch (Exception ex) catch (Exception ex)
@ -191,15 +191,13 @@ public class SpeedtestService
var pid = -1; var pid = -1;
try try
{ {
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(selecteds); pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds);
if (pid < 0) if (pid < 0)
{ {
return false; return false;
} }
await Task.Delay(1000); await Task.Delay(1000);
var downloadHandle = new DownloadService();
List<Task> tasks = new(); List<Task> tasks = new();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
@ -213,7 +211,7 @@ public class SpeedtestService
} }
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(async () =>
{ {
await DoRealPing(downloadHandle, it); await DoRealPing(it);
})); }));
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
@ -255,7 +253,7 @@ public class SpeedtestService
var pid = -1; var pid = -1;
try try
{ {
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it); pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
if (pid < 0) if (pid < 0)
{ {
UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
@ -263,7 +261,7 @@ public class SpeedtestService
else else
{ {
await Task.Delay(1000); await Task.Delay(1000);
var delay = await DoRealPing(downloadHandle, it); var delay = await DoRealPing(it);
if (blSpeedTest) if (blSpeedTest)
{ {
if (delay > 0) if (delay > 0)
@ -294,12 +292,12 @@ public class SpeedtestService
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
private async Task<int> DoRealPing(DownloadService downloadHandle, ServerTestItem it) private async Task<int> DoRealPing(ServerTestItem it)
{ {
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await downloadHandle.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
UpdateFunc(it.IndexId, responseTime.ToString()); UpdateFunc(it.IndexId, responseTime.ToString());
return responseTime; return responseTime;
} }
@ -316,7 +314,7 @@ public class SpeedtestService
decimal.TryParse(msg, out var dec); decimal.TryParse(msg, out var dec);
if (dec > 0) if (dec > 0)
{ {
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, dec); ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec);
} }
UpdateFunc(it.IndexId, "", msg); UpdateFunc(it.IndexId, "", msg);
}); });
@ -358,8 +356,8 @@ public class SpeedtestService
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = new(); List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{ {
@ -378,7 +376,7 @@ public class SpeedtestService
_updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed }); _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
if (indexId.IsNotEmpty() && speed.IsNotEmpty()) if (indexId.IsNotEmpty() && speed.IsNotEmpty())
{ {
ProfileExHandler.Instance.SetTestMessage(indexId, speed); ProfileExManager.Instance.SetTestMessage(indexId, speed);
} }
} }
} }

View file

@ -9,7 +9,7 @@ public class StatisticsSingboxService
private bool _exitFlag; private bool _exitFlag;
private ClientWebSocket? webSocket; private ClientWebSocket? webSocket;
private Action<ServerSpeedItem>? _updateFunc; private Action<ServerSpeedItem>? _updateFunc;
private string Url => $"ws://{Global.Loopback}:{AppHandler.Instance.StatePort2}/traffic"; private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
private static readonly string _tag = "StatisticsSingboxService"; private static readonly string _tag = "StatisticsSingboxService";
public StatisticsSingboxService(Config config, Action<ServerSpeedItem> updateFunc) public StatisticsSingboxService(Config config, Action<ServerSpeedItem> updateFunc)

View file

@ -7,7 +7,7 @@ public class StatisticsXrayService
private readonly Config _config; private readonly Config _config;
private bool _exitFlag; private bool _exitFlag;
private Action<ServerSpeedItem>? _updateFunc; private Action<ServerSpeedItem>? _updateFunc;
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort}/debug/vars"; private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
public StatisticsXrayService(Config config, Action<ServerSpeedItem> updateFunc) public StatisticsXrayService(Config config, Action<ServerSpeedItem> updateFunc)
{ {

View file

@ -135,7 +135,7 @@ public class UpdateService
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease) private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var tagName = string.Empty; var tagName = string.Empty;
if (preRelease) if (preRelease)
{ {
@ -169,7 +169,7 @@ public class UpdateService
{ {
try try
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
string filePath = string.Empty; string filePath = string.Empty;
foreach (var name in coreInfo.CoreExes) foreach (var name in coreInfo.CoreExes)
{ {
@ -221,7 +221,7 @@ public class UpdateService
{ {
try try
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty; var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
SemanticVersion curVersion; SemanticVersion curVersion;
string message; string message;
@ -379,7 +379,7 @@ public class UpdateService
var geoSiteFiles = new List<string>(); var geoSiteFiles = new List<string>();
//Collect used files list //Collect used files list
var routingItems = await AppHandler.Instance.RoutingItems(); var routingItems = await AppManager.Instance.RoutingItems();
foreach (var routing in routingItems) foreach (var routing in routingItems)
{ {
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
@ -434,7 +434,7 @@ public class UpdateService
var fileName = $"{type}-{srsName}.srs"; var fileName = $"{type}-{srsName}.srs";
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName); var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
var url = string.Format(srsUrl, type, $"{type}-{srsName}"); var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
await DownloadGeoFile(url, fileName, targetPath, updateFunc); await DownloadGeoFile(url, fileName, targetPath, updateFunc);
} }

View file

@ -19,7 +19,7 @@ public class AddServer2ViewModel : MyReactiveObject
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView) public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () => BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
@ -45,25 +45,25 @@ public class AddServer2ViewModel : MyReactiveObject
var remarks = SelectedSource.Remarks; var remarks = SelectedSource.Remarks;
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return; return;
} }
if (SelectedSource.Address.IsNullOrEmpty()) if (SelectedSource.Address.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
return; return;
} }
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0) if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
@ -74,12 +74,12 @@ public class AddServer2ViewModel : MyReactiveObject
return; return;
} }
var item = await AppHandler.Instance.GetProfileItem(SelectedSource.IndexId); var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
item ??= SelectedSource; item ??= SelectedSource;
item.Address = fileName; item.Address = fileName;
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0) if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer); NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
if (item.IndexId.IsNotEmpty()) if (item.IndexId.IsNotEmpty())
{ {
SelectedSource = JsonUtils.DeepCopy(item); SelectedSource = JsonUtils.DeepCopy(item);
@ -88,7 +88,7 @@ public class AddServer2ViewModel : MyReactiveObject
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.FailedImportedCustomServer); NoticeManager.Instance.Enqueue(ResUI.FailedImportedCustomServer);
} }
} }
@ -97,7 +97,7 @@ public class AddServer2ViewModel : MyReactiveObject
var address = SelectedSource.Address; var address = SelectedSource.Address;
if (address.IsNullOrEmpty()) if (address.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
return; return;
} }
@ -108,7 +108,7 @@ public class AddServer2ViewModel : MyReactiveObject
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.FailedReadConfiguration); NoticeManager.Instance.Enqueue(ResUI.FailedReadConfiguration);
} }
await Task.CompletedTask; await Task.CompletedTask;
} }

View file

@ -16,7 +16,7 @@ public class AddServerViewModel : MyReactiveObject
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView) public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
@ -43,32 +43,32 @@ public class AddServerViewModel : MyReactiveObject
{ {
if (SelectedSource.Remarks.IsNullOrEmpty()) if (SelectedSource.Remarks.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return; return;
} }
if (SelectedSource.Address.IsNullOrEmpty()) if (SelectedSource.Address.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddress); NoticeManager.Instance.Enqueue(ResUI.FillServerAddress);
return; return;
} }
var port = SelectedSource.Port.ToString(); var port = SelectedSource.Port.ToString();
if (port.IsNullOrEmpty() || !Utils.IsNumeric(port) if (port.IsNullOrEmpty() || !Utils.IsNumeric(port)
|| SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort) || SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort)
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectServerPort); NoticeManager.Instance.Enqueue(ResUI.FillCorrectServerPort);
return; return;
} }
if (SelectedSource.ConfigType == EConfigType.Shadowsocks) if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
{ {
if (SelectedSource.Id.IsNullOrEmpty()) if (SelectedSource.Id.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillPassword); NoticeManager.Instance.Enqueue(ResUI.FillPassword);
return; return;
} }
if (SelectedSource.Security.IsNullOrEmpty()) if (SelectedSource.Security.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectEncryption); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
return; return;
} }
} }
@ -76,7 +76,7 @@ public class AddServerViewModel : MyReactiveObject
{ {
if (SelectedSource.Id.IsNullOrEmpty()) if (SelectedSource.Id.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillUUID); NoticeManager.Instance.Enqueue(ResUI.FillUUID);
return; return;
} }
} }
@ -84,12 +84,12 @@ public class AddServerViewModel : MyReactiveObject
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
} }

View file

@ -22,7 +22,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () => WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
@ -52,14 +52,14 @@ public class BackupAndRestoreViewModel : MyReactiveObject
_config.WebDavItem = SelectedSource; _config.WebDavItem = SelectedSource;
_ = await ConfigHandler.SaveConfig(_config); _ = await ConfigHandler.SaveConfig(_config);
var result = await WebDavHandler.Instance.CheckConnection(); var result = await WebDavManager.Instance.CheckConnection();
if (result) if (result)
{ {
DisplayOperationMsg(ResUI.OperationSuccess); DisplayOperationMsg(ResUI.OperationSuccess);
} }
else else
{ {
DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); DisplayOperationMsg(WebDavManager.Instance.GetLastError());
} }
} }
@ -70,7 +70,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
var result = await CreateZipFileFromDirectory(fileName); var result = await CreateZipFileFromDirectory(fileName);
if (result) if (result)
{ {
var result2 = await WebDavHandler.Instance.PutFile(fileName); var result2 = await WebDavManager.Instance.PutFile(fileName);
if (result2) if (result2)
{ {
DisplayOperationMsg(ResUI.OperationSuccess); DisplayOperationMsg(ResUI.OperationSuccess);
@ -78,21 +78,21 @@ public class BackupAndRestoreViewModel : MyReactiveObject
} }
} }
DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); DisplayOperationMsg(WebDavManager.Instance.GetLastError());
} }
private async Task RemoteRestore() private async Task RemoteRestore()
{ {
DisplayOperationMsg(); DisplayOperationMsg();
var fileName = Utils.GetTempPath(Utils.GetGuid()); var fileName = Utils.GetTempPath(Utils.GetGuid());
var result = await WebDavHandler.Instance.GetRawFile(fileName); var result = await WebDavManager.Instance.GetRawFile(fileName);
if (result) if (result)
{ {
await LocalRestore(fileName); await LocalRestore(fileName);
return; return;
} }
DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); DisplayOperationMsg(WebDavManager.Instance.GetLastError());
} }
public async Task<bool> LocalBackup(string fileName) public async Task<bool> LocalBackup(string fileName)
@ -105,7 +105,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
} }
else else
{ {
DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); DisplayOperationMsg(WebDavManager.Instance.GetLastError());
} }
return result; return result;
@ -158,7 +158,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
} }
else else
{ {
DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); DisplayOperationMsg(WebDavManager.Instance.GetLastError());
} }
} }

View file

@ -21,7 +21,7 @@ public class CheckUpdateViewModel : MyReactiveObject
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () => CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () =>

View file

@ -26,7 +26,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh; AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
@ -58,7 +58,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
private async Task GetClashConnections() private async Task GetClashConnections()
{ {
var ret = await ClashApiHandler.Instance.GetClashConnectionsAsync(); var ret = await ClashApiManager.Instance.GetClashConnectionsAsync();
if (ret == null) if (ret == null)
{ {
return; return;
@ -118,7 +118,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
{ {
_connectionItems.Clear(); _connectionItems.Clear();
} }
await ClashApiHandler.Instance.ClashConnectionClose(id); await ClashApiManager.Instance.ClashConnectionClose(id);
await GetClashConnections(); await GetClashConnections();
} }

View file

@ -43,7 +43,7 @@ public class ClashProxiesViewModel : MyReactiveObject
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () => ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
@ -152,13 +152,13 @@ public class ClashProxiesViewModel : MyReactiveObject
{ {
{ "mode", mode.ToString().ToLower() } { "mode", mode.ToString().ToLower() }
}; };
await ClashApiHandler.Instance.ClashConfigUpdate(headers); await ClashApiManager.Instance.ClashConfigUpdate(headers);
} }
} }
private async Task GetClashProxies(bool refreshUI) private async Task GetClashProxies(bool refreshUI)
{ {
var ret = await ClashApiHandler.Instance.GetClashProxiesAsync(); var ret = await ClashApiManager.Instance.GetClashProxiesAsync();
if (ret?.Item1 == null || ret.Item2 == null) if (ret?.Item1 == null || ret.Item2 == null)
{ {
return; return;
@ -182,7 +182,7 @@ public class ClashProxiesViewModel : MyReactiveObject
var selectedName = SelectedGroup?.Name; var selectedName = SelectedGroup?.Name;
_proxyGroups.Clear(); _proxyGroups.Clear();
var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups(); var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
if (proxyGroups != null && proxyGroups.Count > 0) if (proxyGroups != null && proxyGroups.Count > 0)
{ {
foreach (var it in proxyGroups) foreach (var it in proxyGroups)
@ -352,11 +352,11 @@ public class ClashProxiesViewModel : MyReactiveObject
var selectedProxy = TryGetProxy(name); var selectedProxy = TryGetProxy(name);
if (selectedProxy == null || selectedProxy.type != "Selector") if (selectedProxy == null || selectedProxy.type != "Selector")
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return; return;
} }
await ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode); await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
selectedProxy.now = nameNode; selectedProxy.now = nameNode;
var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name); var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
@ -368,12 +368,12 @@ public class ClashProxiesViewModel : MyReactiveObject
SelectedGroup = group2; SelectedGroup = group2;
} }
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
private async Task ProxiesDelayTest(bool blAll = true) private async Task ProxiesDelayTest(bool blAll = true)
{ {
ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) => ClashApiManager.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
{ {
if (item == null || result.IsNullOrEmpty()) if (item == null || result.IsNullOrEmpty())
{ {

View file

@ -38,7 +38,7 @@ public class DNSSettingViewModel : MyReactiveObject
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync); SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
@ -60,7 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject
private async Task Init() private async Task Init()
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
var item = _config.SimpleDNSItem; var item = _config.SimpleDNSItem;
UseSystemHosts = item.UseSystemHosts; UseSystemHosts = item.UseSystemHosts;
AddCommonHosts = item.AddCommonHosts; AddCommonHosts = item.AddCommonHosts;
@ -76,14 +76,14 @@ public class DNSSettingViewModel : MyReactiveObject
Hosts = item.Hosts; Hosts = item.Hosts;
DirectExpectedIPs = item.DirectExpectedIPs; DirectExpectedIPs = item.DirectExpectedIPs;
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
RayCustomDNSEnableCompatible = item1.Enabled; RayCustomDNSEnableCompatible = item1.Enabled;
UseSystemHostsCompatible = item1.UseSystemHosts; UseSystemHostsCompatible = item1.UseSystemHosts;
DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty; DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty;
DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty; DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty;
NormalDNSCompatible = item1?.NormalDNS ?? string.Empty; NormalDNSCompatible = item1?.NormalDNS ?? string.Empty;
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
SBCustomDNSEnableCompatible = item2.Enabled; SBCustomDNSEnableCompatible = item2.Enabled;
DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty; DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty;
DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty; DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty;
@ -117,7 +117,7 @@ public class DNSSettingViewModel : MyReactiveObject
{ {
if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}')) if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}'))
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
return; return;
} }
} }
@ -127,7 +127,7 @@ public class DNSSettingViewModel : MyReactiveObject
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(NormalDNS2Compatible); var obj2 = JsonUtils.Deserialize<Dns4Sbox>(NormalDNS2Compatible);
if (obj2 == null) if (obj2 == null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
return; return;
} }
} }
@ -136,12 +136,12 @@ public class DNSSettingViewModel : MyReactiveObject
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(TunDNS2Compatible); var obj2 = JsonUtils.Deserialize<Dns4Sbox>(TunDNS2Compatible);
if (obj2 == null) if (obj2 == null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
return; return;
} }
} }
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
item1.Enabled = RayCustomDNSEnableCompatible; item1.Enabled = RayCustomDNSEnableCompatible;
item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible; item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible;
item1.DomainDNSAddress = DomainDNSAddressCompatible; item1.DomainDNSAddress = DomainDNSAddressCompatible;
@ -149,7 +149,7 @@ public class DNSSettingViewModel : MyReactiveObject
item1.NormalDNS = NormalDNSCompatible; item1.NormalDNS = NormalDNSCompatible;
await ConfigHandler.SaveDNSItems(_config, item1); await ConfigHandler.SaveDNSItems(_config, item1);
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
item2.Enabled = SBCustomDNSEnableCompatible; item2.Enabled = SBCustomDNSEnableCompatible;
item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible; item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible;
item2.DomainDNSAddress = DomainDNSAddress2Compatible; item2.DomainDNSAddress = DomainDNSAddress2Compatible;

View file

@ -41,7 +41,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
@ -53,13 +53,13 @@ public class FullConfigTemplateViewModel : MyReactiveObject
private async Task Init() private async Task Init()
{ {
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray); var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
EnableFullConfigTemplate4Ray = item?.Enabled ?? false; EnableFullConfigTemplate4Ray = item?.Enabled ?? false;
FullConfigTemplate4Ray = item?.Config ?? string.Empty; FullConfigTemplate4Ray = item?.Config ?? string.Empty;
AddProxyOnly4Ray = item?.AddProxyOnly ?? false; AddProxyOnly4Ray = item?.AddProxyOnly ?? false;
ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty; ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty;
var item2 = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); var item2 = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
EnableFullConfigTemplate4Singbox = item2?.Enabled ?? false; EnableFullConfigTemplate4Singbox = item2?.Enabled ?? false;
FullConfigTemplate4Singbox = item2?.Config ?? string.Empty; FullConfigTemplate4Singbox = item2?.Config ?? string.Empty;
FullTunConfigTemplate4Singbox = item2?.TunConfig ?? string.Empty; FullTunConfigTemplate4Singbox = item2?.TunConfig ?? string.Empty;
@ -75,13 +75,13 @@ public class FullConfigTemplateViewModel : MyReactiveObject
if (!await SaveSingboxConfigAsync()) if (!await SaveSingboxConfigAsync())
return; return;
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_ = _updateView?.Invoke(EViewAction.CloseWindow, null); _ = _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
private async Task<bool> SaveXrayConfigAsync() private async Task<bool> SaveXrayConfigAsync()
{ {
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray); var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
item.Enabled = EnableFullConfigTemplate4Ray; item.Enabled = EnableFullConfigTemplate4Ray;
item.Config = null; item.Config = null;
@ -96,7 +96,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
private async Task<bool> SaveSingboxConfigAsync() private async Task<bool> SaveSingboxConfigAsync()
{ {
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
item.Enabled = EnableFullConfigTemplate4Singbox; item.Enabled = EnableFullConfigTemplate4Singbox;
item.Config = null; item.Config = null;
item.TunConfig = null; item.TunConfig = null;

View file

@ -11,7 +11,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys); _globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
@ -58,7 +58,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
} }

View file

@ -71,7 +71,7 @@ public class MainWindowViewModel : MyReactiveObject
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
#region WhenAnyValue && ReactiveCommand #region WhenAnyValue && ReactiveCommand
@ -178,7 +178,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
}); });
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () => RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
@ -226,13 +226,13 @@ public class MainWindowViewModel : MyReactiveObject
await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
await ProfileExHandler.Instance.Init(); await ProfileExManager.Instance.Init();
await CoreHandler.Instance.Init(_config, UpdateHandler); await CoreManager.Instance.Init(_config, UpdateHandler);
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{ {
await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler); await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
} }
BlReloadEnabled = true; BlReloadEnabled = true;
@ -247,16 +247,16 @@ public class MainWindowViewModel : MyReactiveObject
private void UpdateHandler(bool notify, string msg) private void UpdateHandler(bool notify, string msg)
{ {
NoticeHandler.Instance.SendMessage(msg); NoticeManager.Instance.SendMessage(msg);
if (notify) if (notify)
{ {
NoticeHandler.Instance.Enqueue(msg); NoticeManager.Instance.Enqueue(msg);
} }
} }
private void UpdateTaskHandler(bool success, string msg) private void UpdateTaskHandler(bool success, string msg)
{ {
NoticeHandler.Instance.SendMessageEx(msg); NoticeManager.Instance.SendMessageEx(msg);
if (success) if (success)
{ {
var indexIdOld = _config.IndexId; var indexIdOld = _config.IndexId;
@ -303,10 +303,10 @@ public class MainWindowViewModel : MyReactiveObject
MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString()); MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString());
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await ProfileExHandler.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
await StatisticsHandler.Instance.SaveTo(); await StatisticsManager.Instance.SaveTo();
await CoreHandler.Instance.CoreStop(); await CoreManager.Instance.CoreStop();
StatisticsHandler.Instance.Close(); StatisticsManager.Instance.Close();
Logging.SaveLog("MyAppExitAsync End"); Logging.SaveLog("MyAppExitAsync End");
} }
@ -324,7 +324,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
if (!Utils.UpgradeAppExists(out var upgradeFileName)) if (!Utils.UpgradeAppExists(out var upgradeFileName))
{ {
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip); NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
Logging.SaveLog("UpgradeApp does not exist"); Logging.SaveLog("UpgradeApp does not exist");
return; return;
} }
@ -404,11 +404,11 @@ public class MainWindowViewModel : MyReactiveObject
{ {
RefreshSubscriptions(); RefreshSubscriptions();
RefreshServers(); RefreshServers();
NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
@ -420,7 +420,7 @@ public class MainWindowViewModel : MyReactiveObject
public async Task ScanScreenResult(byte[]? bytes) public async Task ScanScreenResult(byte[]? bytes)
{ {
var result = QRCodeHelper.ParseBarcode(bytes); var result = QRCodeUtils.ParseBarcode(bytes);
await AddScanResultAsync(result); await AddScanResultAsync(result);
} }
@ -437,7 +437,7 @@ public class MainWindowViewModel : MyReactiveObject
return; return;
} }
var result = QRCodeHelper.ParseBarcode(fileName); var result = QRCodeUtils.ParseBarcode(fileName);
await AddScanResultAsync(result); await AddScanResultAsync(result);
} }
@ -445,7 +445,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound); NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
} }
else else
{ {
@ -454,11 +454,11 @@ public class MainWindowViewModel : MyReactiveObject
{ {
RefreshSubscriptions(); RefreshSubscriptions();
RefreshServers(); RefreshServers();
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan); NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
} }
@ -531,7 +531,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task ClearServerStatistics() private async Task ClearServerStatistics()
{ {
await StatisticsHandler.Instance.ClearAllServerStatistics(); await StatisticsManager.Instance.ClearAllServerStatistics();
RefreshServers(); RefreshServers();
} }
@ -602,13 +602,13 @@ public class MainWindowViewModel : MyReactiveObject
private async Task LoadCore() private async Task LoadCore()
{ {
var node = await ConfigHandler.GetDefaultServer(_config); var node = await ConfigHandler.GetDefaultServer(_config);
await CoreHandler.Instance.LoadCore(node); await CoreManager.Instance.LoadCore(node);
} }
public async Task CloseCore() public async Task CloseCore()
{ {
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await CoreHandler.Instance.CoreStop(); await CoreManager.Instance.CoreStop();
} }
private async Task AutoHideStartup() private async Task AutoHideStartup()

View file

@ -20,7 +20,7 @@ public class MsgViewModel : MyReactiveObject
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;

View file

@ -109,7 +109,7 @@ public class OptionSettingViewModel : MyReactiveObject
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
@ -276,7 +276,7 @@ public class OptionSettingViewModel : MyReactiveObject
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
|| localPort <= 0 || localPort >= Global.MaxPort) || localPort <= 0 || localPort >= Global.MaxPort)
{ {
NoticeHandler.Instance.Enqueue(ResUI.FillLocalListeningPort); NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return; return;
} }
var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics
@ -369,14 +369,14 @@ public class OptionSettingViewModel : MyReactiveObject
if (await ConfigHandler.SaveConfig(_config) == 0) if (await ConfigHandler.SaveConfig(_config) == 0)
{ {
await AutoStartupHandler.UpdateTask(_config); await AutoStartupHandler.UpdateTask(_config);
AppHandler.Instance.Reset(); AppManager.Instance.Reset();
NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }

View file

@ -100,7 +100,7 @@ public class ProfilesViewModel : MyReactiveObject
public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
#region WhenAnyValue && ReactiveCommand #region WhenAnyValue && ReactiveCommand
@ -284,8 +284,8 @@ public class ProfilesViewModel : MyReactiveObject
{ {
if (result.IndexId.IsNullOrEmpty()) if (result.IndexId.IsNullOrEmpty())
{ {
NoticeHandler.Instance.SendMessageEx(result.Delay); NoticeManager.Instance.SendMessageEx(result.Delay);
NoticeHandler.Instance.Enqueue(result.Delay); NoticeManager.Instance.Enqueue(result.Delay);
return; return;
} }
var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId); var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
@ -403,7 +403,7 @@ public class ProfilesViewModel : MyReactiveObject
_subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); _subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
foreach (var item in await AppHandler.Instance.SubItems()) foreach (var item in await AppManager.Instance.SubItems())
{ {
_subItems.Add(item); _subItems.Add(item);
} }
@ -419,12 +419,12 @@ public class ProfilesViewModel : MyReactiveObject
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter) private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
{ {
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, filter); var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, filter);
await ConfigHandler.SetDefaultServer(_config, lstModel); await ConfigHandler.SetDefaultServer(_config, lstModel);
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? []; var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
lstModel = (from t in lstModel lstModel = (from t in lstModel
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
from t22 in t2b.DefaultIfEmpty() from t22 in t2b.DefaultIfEmpty()
@ -474,7 +474,7 @@ public class ProfilesViewModel : MyReactiveObject
{ {
foreach (var profile in orderProfiles) foreach (var profile in orderProfiles)
{ {
var item = await AppHandler.Instance.GetProfileItem(profile.IndexId); var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
if (item is not null) if (item is not null)
{ {
lstSelected.Add(item); lstSelected.Add(item);
@ -495,10 +495,10 @@ public class ProfilesViewModel : MyReactiveObject
{ {
return; return;
} }
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
eConfigType = item.ConfigType; eConfigType = item.ConfigType;
@ -536,7 +536,7 @@ public class ProfilesViewModel : MyReactiveObject
var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId); var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId);
await ConfigHandler.RemoveServers(_config, lstSelected); await ConfigHandler.RemoveServers(_config, lstSelected);
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
if (lstSelected.Count == _profileItems.Count) if (lstSelected.Count == _profileItems.Count)
{ {
_profileItems.Clear(); _profileItems.Clear();
@ -556,7 +556,7 @@ public class ProfilesViewModel : MyReactiveObject
RefreshServers(); RefreshServers();
Reload(); Reload();
} }
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
} }
private async Task CopyServer() private async Task CopyServer()
@ -569,7 +569,7 @@ public class ProfilesViewModel : MyReactiveObject
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0) if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
{ {
RefreshServers(); RefreshServers();
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
} }
@ -592,10 +592,10 @@ public class ProfilesViewModel : MyReactiveObject
{ {
return; return;
} }
var item = await AppHandler.Instance.GetProfileItem(indexId); var item = await AppManager.Instance.GetProfileItem(indexId);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
@ -621,10 +621,10 @@ public class ProfilesViewModel : MyReactiveObject
public async Task ShareServerAsync() public async Task ShareServerAsync()
{ {
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
var url = FmtHandler.GetShareUri(item); var url = FmtHandler.GetShareUri(item);
@ -647,7 +647,7 @@ public class ProfilesViewModel : MyReactiveObject
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad); var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
if (ret.Success != true) if (ret.Success != true)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return; return;
} }
if (ret?.Data?.ToString() == _config.IndexId) if (ret?.Data?.ToString() == _config.IndexId)
@ -682,7 +682,7 @@ public class ProfilesViewModel : MyReactiveObject
{ {
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId); var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
RefreshServers(); RefreshServers();
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count)); NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
} }
//move server //move server
@ -700,7 +700,7 @@ public class ProfilesViewModel : MyReactiveObject
} }
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id); await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
RefreshServers(); RefreshServers();
SelectedMoveToGroup = null; SelectedMoveToGroup = null;
@ -712,7 +712,7 @@ public class ProfilesViewModel : MyReactiveObject
var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId); var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
@ -762,10 +762,10 @@ public class ProfilesViewModel : MyReactiveObject
private async Task Export2ClientConfigAsync(bool blClipboard) private async Task Export2ClientConfigAsync(bool blClipboard)
{ {
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
if (blClipboard) if (blClipboard)
@ -773,12 +773,12 @@ public class ProfilesViewModel : MyReactiveObject
var result = await CoreConfigHandler.GenerateClientConfig(item, null); var result = await CoreConfigHandler.GenerateClientConfig(item, null);
if (result.Success != true) if (result.Success != true)
{ {
NoticeHandler.Instance.Enqueue(result.Msg); NoticeManager.Instance.Enqueue(result.Msg);
} }
else else
{ {
await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data); await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data);
NoticeHandler.Instance.SendMessage(ResUI.OperationSuccess); NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
} }
} }
else else
@ -796,11 +796,11 @@ public class ProfilesViewModel : MyReactiveObject
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
if (result.Success != true) if (result.Success != true)
{ {
NoticeHandler.Instance.Enqueue(result.Msg); NoticeManager.Instance.Enqueue(result.Msg);
} }
else else
{ {
NoticeHandler.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName)); NoticeManager.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName));
} }
} }
@ -833,7 +833,7 @@ public class ProfilesViewModel : MyReactiveObject
{ {
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
} }
NoticeHandler.Instance.SendMessage(ResUI.BatchExportURLSuccessfully); NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
} }
} }
@ -850,7 +850,7 @@ public class ProfilesViewModel : MyReactiveObject
} }
else else
{ {
item = await AppHandler.Instance.GetSubItem(_config.SubIndexId); item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
if (item is null) if (item is null)
{ {
return; return;

View file

@ -28,7 +28,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView) public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
@ -83,7 +83,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
if (!hasRule) if (!hasRule)
{ {
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process")); NoticeManager.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process"));
return; return;
} }
//NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); //NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);

View file

@ -37,7 +37,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView) public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
var canEditRemove = this.WhenAnyValue( var canEditRemove = this.WhenAnyValue(
@ -151,7 +151,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
{ {
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return; return;
} }
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
@ -174,7 +174,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
{ {
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return; return;
} }
@ -205,7 +205,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
{ {
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return; return;
} }
@ -226,7 +226,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
string remarks = SelectedRouting.Remarks; string remarks = SelectedRouting.Remarks;
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return; return;
} }
var item = SelectedRouting; var item = SelectedRouting;
@ -239,12 +239,12 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
if (await ConfigHandler.SaveRoutingItem(_config, item) == 0) if (await ConfigHandler.SaveRoutingItem(_config, item) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
@ -266,7 +266,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
if (ret == 0) if (ret == 0)
{ {
RefreshRulesItems(); RefreshRulesItems();
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
} }
@ -281,7 +281,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
if (ret == 0) if (ret == 0)
{ {
RefreshRulesItems(); RefreshRulesItems();
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
} }
@ -290,7 +290,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
var url = SelectedRouting.Url; var url = SelectedRouting.Url;
if (url.IsNullOrEmpty()) if (url.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.MsgNeedUrl); NoticeManager.Instance.Enqueue(ResUI.MsgNeedUrl);
return; return;
} }
@ -300,7 +300,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
if (ret == 0) if (ret == 0)
{ {
RefreshRulesItems(); RefreshRulesItems();
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
} }
} }

View file

@ -35,7 +35,7 @@ public class RoutingSettingViewModel : MyReactiveObject
public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
var canEditRemove = this.WhenAnyValue( var canEditRemove = this.WhenAnyValue(
@ -84,7 +84,7 @@ public class RoutingSettingViewModel : MyReactiveObject
{ {
_routingItems.Clear(); _routingItems.Clear();
var routings = await AppHandler.Instance.RoutingItems(); var routings = await AppManager.Instance.RoutingItems();
foreach (var item in routings) foreach (var item in routings)
{ {
var it = new RoutingItemModel() var it = new RoutingItemModel()
@ -109,12 +109,12 @@ public class RoutingSettingViewModel : MyReactiveObject
if (await ConfigHandler.SaveConfig(_config) == 0) if (await ConfigHandler.SaveConfig(_config) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
@ -129,7 +129,7 @@ public class RoutingSettingViewModel : MyReactiveObject
} }
else else
{ {
item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
if (item is null) if (item is null)
{ {
return; return;
@ -146,7 +146,7 @@ public class RoutingSettingViewModel : MyReactiveObject
{ {
if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty()) if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return; return;
} }
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
@ -155,7 +155,7 @@ public class RoutingSettingViewModel : MyReactiveObject
} }
foreach (var it in SelectedSources ?? [SelectedSource]) foreach (var it in SelectedSources ?? [SelectedSource])
{ {
var item = await AppHandler.Instance.GetRoutingItem(it?.Id); var item = await AppManager.Instance.GetRoutingItem(it?.Id);
if (item != null) if (item != null)
{ {
await ConfigHandler.RemoveRoutingItem(item); await ConfigHandler.RemoveRoutingItem(item);
@ -168,10 +168,10 @@ public class RoutingSettingViewModel : MyReactiveObject
public async Task RoutingAdvancedSetDefault() public async Task RoutingAdvancedSetDefault()
{ {
var item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); var item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
if (item is null) if (item is null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return; return;
} }

View file

@ -100,7 +100,7 @@ public class StatusBarViewModel : MyReactiveObject
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
SelectedRouting = new(); SelectedRouting = new();
SelectedServer = new(); SelectedServer = new();
RunningServerToolTipText = "-"; RunningServerToolTipText = "-";
@ -228,7 +228,7 @@ public class StatusBarViewModel : MyReactiveObject
private async Task CopyProxyCmdToClipboard() private async Task CopyProxyCmdToClipboard()
{ {
var cmd = Utils.IsWindows() ? "set" : "export"; var cmd = Utils.IsWindows() ? "set" : "export";
var address = $"{Global.Loopback}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"; var address = $"{Global.Loopback}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}";
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}");
@ -283,7 +283,7 @@ public class StatusBarViewModel : MyReactiveObject
private async Task RefreshServersMenu() private async Task RefreshServersMenu()
{ {
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, ""); var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
_servers.Clear(); _servers.Clear();
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
@ -334,9 +334,9 @@ public class StatusBarViewModel : MyReactiveObject
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting); _updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
var msg = await Task.Run(ConnectionHandler.Instance.RunAvailabilityCheck); var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
NoticeHandler.Instance.SendMessageEx(msg); NoticeManager.Instance.SendMessageEx(msg);
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg); _updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
} }
@ -355,7 +355,7 @@ public class StatusBarViewModel : MyReactiveObject
} }
_config.SystemProxyItem.SysProxyType = type; _config.SystemProxyItem.SysProxyType = type;
await ChangeSystemProxyAsync(type, true); await ChangeSystemProxyAsync(type, true);
NoticeHandler.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}"); NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
@ -381,7 +381,7 @@ public class StatusBarViewModel : MyReactiveObject
_routingItems.Clear(); _routingItems.Clear();
BlRouting = true; BlRouting = true;
var routings = await AppHandler.Instance.RoutingItems(); var routings = await AppManager.Instance.RoutingItems();
foreach (var item in routings) foreach (var item in routings)
{ {
_routingItems.Add(item); _routingItems.Add(item);
@ -404,7 +404,7 @@ public class StatusBarViewModel : MyReactiveObject
return; return;
} }
var item = await AppHandler.Instance.GetRoutingItem(SelectedRouting?.Id); var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id);
if (item is null) if (item is null)
{ {
return; return;
@ -412,7 +412,7 @@ public class StatusBarViewModel : MyReactiveObject
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
{ {
NoticeHandler.Instance.SendMessageEx(ResUI.TipChangeRouting); NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
Locator.Current.GetService<MainWindowViewModel>()?.Reload(); Locator.Current.GetService<MainWindowViewModel>()?.Reload();
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
} }
@ -471,11 +471,11 @@ public class StatusBarViewModel : MyReactiveObject
} }
else if (Utils.IsLinux()) else if (Utils.IsLinux())
{ {
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty(); return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
} }
else if (Utils.IsOSX()) else if (Utils.IsOSX())
{ {
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty(); return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
} }
return false; return false;
} }
@ -487,10 +487,10 @@ public class StatusBarViewModel : MyReactiveObject
public async Task InboundDisplayStatus() public async Task InboundDisplayStatus()
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append($"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"); sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
if (_config.Inbound.First().SecondLocalPortEnabled) if (_config.Inbound.First().SecondLocalPortEnabled)
{ {
sb.Append($",{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks2)}"); sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}");
} }
sb.Append(']'); sb.Append(']');
InboundDisplay = $"{ResUI.LabLocal}:{sb}"; InboundDisplay = $"{ResUI.LabLocal}:{sb}";
@ -498,8 +498,8 @@ public class StatusBarViewModel : MyReactiveObject
if (_config.Inbound.First().AllowLANConn) if (_config.Inbound.First().AllowLANConn)
{ {
var lan = _config.Inbound.First().NewPort4LAN var lan = _config.Inbound.First().NewPort4LAN
? $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks3)}]" ? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
: $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}]"; : $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]";
InboundLanDisplay = $"{ResUI.LabLAN}:{lan}"; InboundLanDisplay = $"{ResUI.LabLAN}:{lan}";
} }
else else

View file

@ -13,7 +13,7 @@ public class SubEditViewModel : MyReactiveObject
public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView) public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
@ -29,7 +29,7 @@ public class SubEditViewModel : MyReactiveObject
var remarks = SelectedSource.Remarks; var remarks = SelectedSource.Remarks;
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())
{ {
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return; return;
} }
@ -39,25 +39,25 @@ public class SubEditViewModel : MyReactiveObject
var uri = Utils.TryUri(url); var uri = Utils.TryUri(url);
if (uri == null) if (uri == null)
{ {
NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip); NoticeManager.Instance.Enqueue(ResUI.InvalidUrlTip);
return; return;
} }
//Do not allow http protocol //Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{ {
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return; //return;
} }
} }
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0) if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null); _updateView?.Invoke(EViewAction.CloseWindow, null);
} }
else else
{ {
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
} }

View file

@ -24,7 +24,7 @@ public class SubSettingViewModel : MyReactiveObject
public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
var canEditRemove = this.WhenAnyValue( var canEditRemove = this.WhenAnyValue(
@ -61,7 +61,7 @@ public class SubSettingViewModel : MyReactiveObject
public async Task RefreshSubItems() public async Task RefreshSubItems()
{ {
_subItems.Clear(); _subItems.Clear();
_subItems.AddRange(await AppHandler.Instance.SubItems()); _subItems.AddRange(await AppManager.Instance.SubItems());
} }
public async Task EditSubAsync(bool blNew) public async Task EditSubAsync(bool blNew)
@ -73,7 +73,7 @@ public class SubSettingViewModel : MyReactiveObject
} }
else else
{ {
item = await AppHandler.Instance.GetSubItem(SelectedSource?.Id); item = await AppManager.Instance.GetSubItem(SelectedSource?.Id);
if (item is null) if (item is null)
{ {
return; return;
@ -98,7 +98,7 @@ public class SubSettingViewModel : MyReactiveObject
await ConfigHandler.DeleteSubItem(_config, it.Id); await ConfigHandler.DeleteSubItem(_config, it.Id);
} }
await RefreshSubItems(); await RefreshSubItems();
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
IsModified = true; IsModified = true;
} }
} }

View file

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using ServiceLib.Manager;
using Splat; using Splat;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
using v2rayN.Desktop.Views; using v2rayN.Desktop.Views;
@ -25,7 +26,7 @@ public partial class App : Application
{ {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
AppHandler.Instance.InitComponents(); AppManager.Instance.InitComponents();
desktop.Exit += OnExit; desktop.Exit += OnExit;
desktop.MainWindow = new MainWindow(); desktop.MainWindow = new MainWindow();

View file

@ -10,6 +10,7 @@
<x:Double x:Key="IconButtonWidth">32</x:Double> <x:Double x:Key="IconButtonWidth">32</x:Double>
<x:Double x:Key="IconButtonHeight">32</x:Double> <x:Double x:Key="IconButtonHeight">32</x:Double>
<x:Double x:Key="MenuFlyoutMaxHeight">1000</x:Double>
<Thickness x:Key="Margin2">2</Thickness> <Thickness x:Key="Margin2">2</Thickness>
<Thickness x:Key="MarginLr4">4,0</Thickness> <Thickness x:Key="MarginLr4">4,0</Thickness>

View file

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ServiceLib.Manager;
namespace v2rayN.Desktop.Base; namespace v2rayN.Desktop.Base;
@ -20,7 +21,7 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
{ {
try try
{ {
var sizeItem = ConfigHandler.GetWindowSizeItem(AppHandler.Instance.Config, GetType().Name); var sizeItem = ConfigHandler.GetWindowSizeItem(AppManager.Instance.Config, GetType().Name);
if (sizeItem == null) if (sizeItem == null)
{ {
return; return;
@ -45,7 +46,7 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
base.OnClosed(e); base.OnClosed(e);
try try
{ {
ConfigHandler.SaveWindowSizeItem(AppHandler.Instance.Config, GetType().Name, Width, Height); ConfigHandler.SaveWindowSizeItem(AppManager.Instance.Config, GetType().Name, Width, Height);
} }
catch { } catch { }
} }

View file

@ -4,12 +4,12 @@ using Avalonia.ReactiveUI;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using GlobalHotKeys; using GlobalHotKeys;
namespace v2rayN.Desktop.Handler; namespace v2rayN.Desktop.Manager;
public sealed class HotkeyHandler public sealed class HotkeyManager
{ {
private static readonly Lazy<HotkeyHandler> _instance = new(() => new()); private static readonly Lazy<HotkeyManager> _instance = new(() => new());
public static HotkeyHandler Instance = _instance.Value; public static HotkeyManager Instance = _instance.Value;
private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new(); private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new();
private HotKeyManager? _hotKeyManager; private HotKeyManager? _hotKeyManager;

View file

@ -1,5 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
namespace v2rayN.Desktop; namespace v2rayN.Desktop;
@ -46,7 +47,7 @@ internal class Program
} }
} }
if (!AppHandler.Instance.InitApp()) if (!AppManager.Instance.InitApp())
{ {
return false; return false;
} }
@ -62,6 +63,6 @@ internal class Program
.WithFontByDefault() .WithFontByDefault()
.LogToTrace() .LogToTrace()
.UseReactiveUI() .UseReactiveUI()
.With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock }); .With(new MacOSPlatformOptions { ShowInDock = AppManager.Instance.Config.UiItem.MacOSShowInDock });
} }
} }

View file

@ -8,6 +8,7 @@ using Avalonia.Styling;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Semi.Avalonia; using Semi.Avalonia;
using ServiceLib.Manager;
namespace v2rayN.Desktop.ViewModels; namespace v2rayN.Desktop.ViewModels;
@ -21,7 +22,7 @@ public class ThemeSettingViewModel : MyReactiveObject
public ThemeSettingViewModel() public ThemeSettingViewModel()
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
BindingUI(); BindingUI();
RestoreUI(); RestoreUI();
@ -74,7 +75,7 @@ public class ThemeSettingViewModel : MyReactiveObject
_config.UiItem.CurrentLanguage = CurrentLanguage; _config.UiItem.CurrentLanguage = CurrentLanguage;
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);
ConfigHandler.SaveConfig(_config); ConfigHandler.SaveConfig(_config);
NoticeHandler.Instance.Enqueue(ResUI.NeedRebootTips); NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips);
} }
}); });
} }

View file

@ -523,7 +523,7 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Mtu" /> Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
<TextBox <TextBox
x:Name="txtShortId9" x:Name="txtShortId9"
Grid.Row="5" Grid.Row="5"

View file

@ -2,6 +2,7 @@ using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -50,7 +51,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
gridSs.IsVisible = true; gridSs.IsVisible = true;
cmbSecurity3.ItemsSource = AppHandler.Instance.GetShadowsocksSecurities(profileItem); cmbSecurity3.ItemsSource = AppManager.Instance.GetShadowsocksSecurities(profileItem);
break; break;
case EConfigType.SOCKS: case EConfigType.SOCKS:

View file

@ -2,6 +2,7 @@ using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -14,7 +15,8 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
{ {
InitializeComponent(); InitializeComponent();
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new DNSSettingViewModel(UpdateViewHandler); ViewModel = new DNSSettingViewModel(UpdateViewHandler);
@ -99,4 +101,9 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
{ {
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/dns/"); ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/dns/");
} }
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
btnCancel.Focus();
}
} }

View file

@ -1,6 +1,7 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -13,7 +14,8 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
{ {
InitializeComponent(); InitializeComponent();
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new FullConfigTemplateViewModel(UpdateViewHandler); ViewModel = new FullConfigTemplateViewModel(UpdateViewHandler);
@ -48,4 +50,8 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
{ {
ProcUtils.ProcessStart("https://github.com/2dust/v2rayN/wiki/Description-of-some-ui#%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF%E8%AE%BE%E7%BD%AE"); ProcUtils.ProcessStart("https://github.com/2dust/v2rayN/wiki/Description-of-some-ui#%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF%E8%AE%BE%E7%BD%AE");
} }
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
btnCancel.Focus();
}
} }

View file

@ -5,7 +5,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
using v2rayN.Desktop.Handler; using v2rayN.Desktop.Manager;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -21,8 +21,9 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
btnReset.Click += btnReset_Click; btnReset.Click += btnReset_Click;
HotkeyHandler.Instance.IsPause = true; HotkeyManager.Instance.IsPause = true;
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false; Loaded += Window_Loaded;
this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false;
btnCancel.Click += (s, e) => this.Close(); btnCancel.Click += (s, e) => this.Close();
this.WhenActivated(disposables => this.WhenActivated(disposables =>
@ -134,4 +135,8 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
return res.ToString(); return res.ToString();
} }
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
btnCancel.Focus();
}
} }

View file

@ -9,10 +9,11 @@ using Avalonia.Threading;
using DialogHostAvalonia; using DialogHostAvalonia;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using Splat; using Splat;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
using v2rayN.Desktop.Handler; using v2rayN.Desktop.Manager;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -28,7 +29,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
InitializeComponent(); InitializeComponent();
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight }; _manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight };
this.KeyDown += MainWindow_KeyDown; this.KeyDown += MainWindow_KeyDown;
@ -142,7 +143,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false); ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
HotkeyHandler.Instance.Init(_config, OnHotkeyHandler); HotkeyManager.Instance.Init(_config, OnHotkeyHandler);
} }
else else
{ {
@ -234,7 +235,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
StorageUI(); StorageUI();
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
HotkeyHandler.Instance.Dispose(); HotkeyManager.Instance.Dispose();
desktop.Shutdown(); desktop.Shutdown();
} }
break; break;
@ -351,7 +352,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
//ShowHideWindow(false); //ShowHideWindow(false);
NoticeHandler.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)"); NoticeManager.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)");
await Task.CompletedTask; await Task.CompletedTask;
//if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) //if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
//{ //{
@ -477,7 +478,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
private void AddHelpMenuItem() private void AddHelpMenuItem()
{ {
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo foreach (var it in coreInfo
.Where(t => t.CoreType != ECoreType.v2fly .Where(t => t.CoreType != ECoreType.v2fly
&& t.CoreType != ECoreType.hysteria)) && t.CoreType != ECoreType.hysteria))

View file

@ -736,7 +736,7 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Auto Route" /> Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" />
<ToggleSwitch <ToggleSwitch
x:Name="togAutoRoute" x:Name="togAutoRoute"
Grid.Row="2" Grid.Row="2"
@ -749,7 +749,7 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Strict Route" /> Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" />
<ToggleSwitch <ToggleSwitch
x:Name="togStrictRoute" x:Name="togStrictRoute"
Grid.Row="3" Grid.Row="3"
@ -762,7 +762,7 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Stack" /> Text="{x:Static resx:ResUI.TbSettingsTunStack}" />
<ComboBox <ComboBox
x:Name="cmbStack" x:Name="cmbStack"
Grid.Row="4" Grid.Row="4"
@ -776,7 +776,7 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Mtu" /> Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
<ComboBox <ComboBox
x:Name="cmbMtu" x:Name="cmbMtu"
Grid.Row="5" Grid.Row="5"

View file

@ -1,6 +1,8 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@ -13,8 +15,9 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => this.Close(); btnCancel.Click += (s, e) => this.Close();
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
ViewModel = new OptionSettingViewModel(UpdateViewHandler); ViewModel = new OptionSettingViewModel(UpdateViewHandler);
@ -208,4 +211,8 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList(); ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
} }
} }
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
btnCancel.Focus();
}
} }

View file

@ -7,6 +7,7 @@ using Avalonia.Threading;
using DialogHostAvalonia; using DialogHostAvalonia;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using ServiceLib.Manager;
using Splat; using Splat;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
@ -26,7 +27,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{ {
InitializeComponent(); InitializeComponent();
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
_window = window; _window = window;
menuSelectAll.Click += menuSelectAll_Click; menuSelectAll.Click += menuSelectAll_Click;

View file

@ -23,7 +23,7 @@ public partial class QrcodeView : UserControl
private Bitmap? GetQRCode(string? url) private Bitmap? GetQRCode(string? url)
{ {
var bytes = QRCodeHelper.GenQRCode(url); var bytes = QRCodeUtils.GenQRCode(url);
return ByteToBitmap(bytes); return ByteToBitmap(bytes);
} }

View file

@ -17,6 +17,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded;
this.Closing += RoutingSettingWindow_Closing; this.Closing += RoutingSettingWindow_Closing;
btnCancel.Click += (s, e) => this.Close(); btnCancel.Click += (s, e) => this.Close();
this.KeyDown += RoutingSettingWindow_KeyDown; this.KeyDown += RoutingSettingWindow_KeyDown;
@ -134,4 +135,8 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
} }
} }
} }
private void Window_Loaded(object? sender, RoutedEventArgs e)
{
btnCancel.Focus();
}
} }

Some files were not shown because too many files have changed in this diff Show more