mirror of
https://github.com/2dust/v2rayN.git
synced 2025-08-23 19:36:55 +00:00
Compare commits
No commits in common. "master" and "7.14.1" have entirely different histories.
127 changed files with 5375 additions and 6467 deletions
36
.github/workflows/build-linux.yml
vendored
36
.github/workflows/build-linux.yml
vendored
|
@ -98,38 +98,4 @@ jobs:
|
|||
file: ${{ github.workspace }}/v2rayN*.zip
|
||||
tag: ${{ github.event.inputs.release_tag }}
|
||||
file_glob: 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
|
||||
prerelease: true
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -397,5 +397,4 @@ FodyWeavers.xsd
|
|||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
*.sln.iml
|
815
package-rhel.sh
815
package-rhel.sh
|
@ -1,815 +0,0 @@
|
|||
#!/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
|
|
@ -1,7 +1,7 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.14.3</Version>
|
||||
<Version>7.14.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.4" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.4" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.4" />
|
||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.4" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.3" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.3" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.3" />
|
||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.3" />
|
||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
|
@ -20,7 +20,7 @@
|
|||
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
||||
<PackageVersion Include="Splat.NLog" Version="15.5.3" />
|
||||
<PackageVersion Include="Splat.NLog" Version="15.4.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Net;
|
||||
using Downloader;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public class DownloaderHelper
|
||||
{
|
|
@ -1,10 +1,8 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
|
@ -204,35 +202,4 @@ public class HttpClientHelper
|
|||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using ZXing.SkiaSharp;
|
|||
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public class QRCodeUtils
|
||||
public class QRCodeHelper
|
||||
{
|
||||
public static byte[]? GenQRCode(string? url)
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections;
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public sealed class SQLiteHelper
|
||||
{
|
|
@ -289,31 +289,6 @@ public class Global
|
|||
"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 =
|
||||
[
|
||||
AsIs,
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
global using ServiceLib.Handler;
|
||||
global using ServiceLib.Helper;
|
||||
global using ServiceLib.Manager;
|
||||
global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Services;
|
||||
global using ServiceLib.Services.Statistics;
|
||||
global using ServiceLib.Services.CoreConfig;
|
||||
global using ServiceLib.Models;
|
||||
global using ServiceLib.Resx;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Handler.SysProxy;
|
|
@ -1,15 +1,15 @@
|
|||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public sealed class AppManager
|
||||
public sealed class AppHandler
|
||||
{
|
||||
#region Property
|
||||
|
||||
private static readonly Lazy<AppManager> _instance = new(() => new());
|
||||
private static readonly Lazy<AppHandler> _instance = new(() => new());
|
||||
private Config _config;
|
||||
private int? _statePort;
|
||||
private int? _statePort2;
|
||||
private Job? _processJob;
|
||||
public static AppManager Instance => _instance.Value;
|
||||
public static AppHandler Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
|
||||
public int StatePort
|
||||
|
@ -97,7 +97,7 @@ public sealed class AppManager
|
|||
return localPort + (int)protocol;
|
||||
}
|
||||
|
||||
public void AddProcess(nint processHandle)
|
||||
public void AddProcess(IntPtr processHandle)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
|
@ -1,11 +1,11 @@
|
|||
using static ServiceLib.Models.ClashProxies;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public sealed class ClashApiManager
|
||||
public sealed class ClashApiHandler
|
||||
{
|
||||
private static readonly Lazy<ClashApiManager> instance = new(() => new());
|
||||
public static ClashApiManager Instance => instance.Value;
|
||||
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
|
||||
public static ClashApiHandler Instance => instance.Value;
|
||||
|
||||
private static readonly string _tag = "ClashApiHandler";
|
||||
private Dictionary<string, ProxiesItem>? _proxies;
|
||||
|
@ -65,7 +65,7 @@ public sealed class ClashApiManager
|
|||
return;
|
||||
}
|
||||
var urlBase = $"{GetApiUrl()}/proxies";
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var it in lstProxy)
|
||||
|
@ -182,6 +182,6 @@ public sealed class ClashApiManager
|
|||
|
||||
private string GetApiUrl()
|
||||
{
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}";
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
|
|||
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public static class ConfigHandler
|
||||
public class ConfigHandler
|
||||
{
|
||||
private static readonly string _configRes = Global.ConfigFileName;
|
||||
private static readonly string _tag = "ConfigHandler";
|
||||
|
@ -216,7 +216,7 @@ public static class ConfigHandler
|
|||
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
|
||||
public static async Task<int> AddServer(Config config, ProfileItem profileItem)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
item = profileItem;
|
||||
|
@ -336,7 +336,7 @@ public static class ConfigHandler
|
|||
{
|
||||
foreach (var it in indexes)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
|
@ -418,7 +418,7 @@ public static class ConfigHandler
|
|||
/// <returns>The default profile item or null if none exists</returns>
|
||||
public static async Task<ProfileItem?> GetDefaultServer(Config config)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(config.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(config.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
|
||||
|
@ -449,7 +449,7 @@ public static class ConfigHandler
|
|||
|
||||
for (int i = 0; i < lstProfile.Count; i++)
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||
}
|
||||
|
||||
var sort = 0;
|
||||
|
@ -461,7 +461,7 @@ public static class ConfigHandler
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1;
|
||||
sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -471,7 +471,7 @@ public static class ConfigHandler
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
|
||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -482,7 +482,7 @@ public static class ConfigHandler
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
|
||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ public static class ConfigHandler
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1;
|
||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ public static class ConfigHandler
|
|||
break;
|
||||
}
|
||||
|
||||
ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort);
|
||||
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort);
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
|
@ -559,7 +559,7 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful, -1 if failed</returns>
|
||||
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
item = profileItem;
|
||||
|
@ -601,7 +601,7 @@ public static class ConfigHandler
|
|||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
|
||||
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
|
||||
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
@ -829,13 +829,13 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful, -1 if failed</returns>
|
||||
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(subId, "");
|
||||
var lstModel = await AppHandler.Instance.ProfileItems(subId, "");
|
||||
if (lstModel.Count <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
||||
var lstProfile = (from t in lstModel
|
||||
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
||||
from t22 in t2b.DefaultIfEmpty()
|
||||
|
@ -905,7 +905,7 @@ public static class ConfigHandler
|
|||
|
||||
for (var i = 0; i < lstProfile.Count; i++)
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||
}
|
||||
switch (name)
|
||||
{
|
||||
|
@ -914,7 +914,7 @@ public static class ConfigHandler
|
|||
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
||||
foreach (var item in lstProfile.Where(item => item.Delay <= 0))
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
|
||||
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -924,7 +924,7 @@ public static class ConfigHandler
|
|||
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
||||
foreach (var item in lstProfile.Where(item => item.Speed <= 0))
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
|
||||
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -982,7 +982,7 @@ public static class ConfigHandler
|
|||
/// <returns>Tuple with total count and remaining count after deduplication</returns>
|
||||
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
|
||||
{
|
||||
var lstProfile = await AppManager.Instance.ProfileItems(subId);
|
||||
var lstProfile = await AppHandler.Instance.ProfileItems(subId);
|
||||
if (lstProfile == null)
|
||||
{
|
||||
return new Tuple<int, int>(0, 0);
|
||||
|
@ -1052,15 +1052,15 @@ public static class ConfigHandler
|
|||
if (profileItem.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.IndexId = Utils.GetGuid(false);
|
||||
maxSort = ProfileExManager.Instance.GetMaxSort();
|
||||
maxSort = ProfileExHandler.Instance.GetMaxSort();
|
||||
}
|
||||
if (!toFile && maxSort < 0)
|
||||
{
|
||||
maxSort = ProfileExManager.Instance.GetMaxSort();
|
||||
maxSort = ProfileExHandler.Instance.GetMaxSort();
|
||||
}
|
||||
if (maxSort > 0)
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
||||
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
||||
}
|
||||
|
||||
if (toFile)
|
||||
|
@ -1120,7 +1120,7 @@ public static class ConfigHandler
|
|||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(indexId);
|
||||
if (item == null)
|
||||
{
|
||||
return 0;
|
||||
|
@ -1165,7 +1165,7 @@ public static class ConfigHandler
|
|||
return result;
|
||||
}
|
||||
|
||||
var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
|
||||
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
|
||||
profileItem.IndexId = indexId;
|
||||
if (coreType == ECoreType.Xray)
|
||||
{
|
||||
|
@ -1211,7 +1211,7 @@ public static class ConfigHandler
|
|||
ConfigType = EConfigType.SOCKS,
|
||||
Address = Global.Loopback,
|
||||
Sni = node.Address, //Tun2SocksAddress
|
||||
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||
};
|
||||
}
|
||||
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
|
||||
|
@ -1238,12 +1238,12 @@ public static class ConfigHandler
|
|||
/// <returns>Number of removed servers or -1 if failed</returns>
|
||||
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
|
||||
var lstModel = await AppHandler.Instance.ProfileItems(subid, "");
|
||||
if (lstModel is { Count: <= 0 })
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
||||
var lstProfile = (from t in lstModel
|
||||
join t2 in lstProfileExs on t.IndexId equals t2.IndexId
|
||||
where t2.Delay == -1
|
||||
|
@ -1279,7 +1279,7 @@ public static class ConfigHandler
|
|||
if (isSub && subid.IsNotEmpty())
|
||||
{
|
||||
await RemoveServersViaSubid(config, subid, isSub);
|
||||
subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
|
||||
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? "";
|
||||
}
|
||||
|
||||
var countServers = 0;
|
||||
|
@ -1363,7 +1363,7 @@ public static class ConfigHandler
|
|||
return -1;
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(subid);
|
||||
var subItem = await AppHandler.Instance.GetSubItem(subid);
|
||||
var subRemarks = subItem?.Remarks;
|
||||
var preSocksPort = subItem?.PreSocksPort;
|
||||
|
||||
|
@ -1519,7 +1519,7 @@ public static class ConfigHandler
|
|||
ProfileItem? activeProfile = null;
|
||||
if (isSub && subid.IsNotEmpty())
|
||||
{
|
||||
lstOriSub = await AppManager.Instance.ProfileItems(subid);
|
||||
lstOriSub = await AppHandler.Instance.ProfileItems(subid);
|
||||
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
|
||||
}
|
||||
|
||||
|
@ -1551,7 +1551,7 @@ public static class ConfigHandler
|
|||
//Select active node
|
||||
if (activeProfile != null)
|
||||
{
|
||||
var lstSub = await AppManager.Instance.ProfileItems(subid);
|
||||
var lstSub = await AppHandler.Instance.ProfileItems(subid);
|
||||
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
|
||||
if (existItem != null)
|
||||
{
|
||||
|
@ -1562,13 +1562,13 @@ public static class ConfigHandler
|
|||
//Keep the last traffic statistics
|
||||
if (lstOriSub != null)
|
||||
{
|
||||
var lstSub = await AppManager.Instance.ProfileItems(subid);
|
||||
var lstSub = await AppHandler.Instance.ProfileItems(subid);
|
||||
foreach (var item in lstSub)
|
||||
{
|
||||
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
|
||||
if (existItem != null)
|
||||
{
|
||||
await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
|
||||
await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1608,7 +1608,7 @@ public static class ConfigHandler
|
|||
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
||||
{
|
||||
//TODO Temporary reminder to be removed later
|
||||
NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||
//return -1;
|
||||
}
|
||||
|
||||
|
@ -1626,7 +1626,7 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful, -1 if failed</returns>
|
||||
public static async Task<int> AddSubItem(Config config, SubItem subItem)
|
||||
{
|
||||
var item = await AppManager.Instance.GetSubItem(subItem.Id);
|
||||
var item = await AppHandler.Instance.GetSubItem(subItem.Id);
|
||||
if (item is null)
|
||||
{
|
||||
item = subItem;
|
||||
|
@ -1658,7 +1658,7 @@ public static class ConfigHandler
|
|||
var maxSort = 0;
|
||||
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
|
||||
{
|
||||
var lstSubs = (await AppManager.Instance.SubItems());
|
||||
var lstSubs = (await AppHandler.Instance.SubItems());
|
||||
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
|
||||
}
|
||||
item.Sort = maxSort + 1;
|
||||
|
@ -1712,7 +1712,7 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful</returns>
|
||||
public static async Task<int> DeleteSubItem(Config config, string id)
|
||||
{
|
||||
var item = await AppManager.Instance.GetSubItem(id);
|
||||
var item = await AppHandler.Instance.GetSubItem(id);
|
||||
if (item is null)
|
||||
{
|
||||
return 0;
|
||||
|
@ -1896,7 +1896,7 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful</returns>
|
||||
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
|
||||
{
|
||||
var items = await AppManager.Instance.RoutingItems();
|
||||
var items = await AppHandler.Instance.RoutingItems();
|
||||
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
|
||||
{
|
||||
return -1;
|
||||
|
@ -1976,7 +1976,7 @@ public static class ConfigHandler
|
|||
if (template == null)
|
||||
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
||||
|
||||
var items = await AppManager.Instance.RoutingItems();
|
||||
var items = await AppHandler.Instance.RoutingItems();
|
||||
var maxSort = items.Count;
|
||||
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
|
||||
{
|
||||
|
@ -2023,14 +2023,14 @@ public static class ConfigHandler
|
|||
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
|
||||
{
|
||||
var ver = "V3-";
|
||||
var items = await AppManager.Instance.RoutingItems();
|
||||
var items = await AppHandler.Instance.RoutingItems();
|
||||
|
||||
//TODO Temporary code to be removed later
|
||||
var lockItem = items?.FirstOrDefault(t => t.Locked == true);
|
||||
if (lockItem != null)
|
||||
{
|
||||
await ConfigHandler.RemoveRoutingItem(lockItem);
|
||||
items = await AppManager.Instance.RoutingItems();
|
||||
items = await AppHandler.Instance.RoutingItems();
|
||||
}
|
||||
|
||||
if (!blImportAdvancedRules && items.Count > 0)
|
||||
|
@ -2107,7 +2107,7 @@ public static class ConfigHandler
|
|||
/// <returns>0 if successful</returns>
|
||||
public static async Task<int> InitBuiltinDNS(Config config)
|
||||
{
|
||||
var items = await AppManager.Instance.DNSItems();
|
||||
var items = await AppHandler.Instance.DNSItems();
|
||||
|
||||
// Check existing DNS items and disable those with empty NormalDNS
|
||||
var needsUpdate = false;
|
||||
|
@ -2185,7 +2185,7 @@ public static class ConfigHandler
|
|||
/// <returns>DNS item with configuration from the URL</returns>
|
||||
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
|
||||
{
|
||||
var currentItem = await AppManager.Instance.GetDNSItem(type);
|
||||
var currentItem = await AppHandler.Instance.GetDNSItem(type);
|
||||
|
||||
var downloadHandle = new DownloadService();
|
||||
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
||||
|
@ -2247,7 +2247,7 @@ public static class ConfigHandler
|
|||
|
||||
public static async Task<int> InitBuiltinFullConfigTemplate(Config config)
|
||||
{
|
||||
var items = await AppManager.Instance.FullConfigTemplateItem();
|
||||
var items = await AppHandler.Instance.FullConfigTemplateItem();
|
||||
if (items.Count <= 0)
|
||||
{
|
||||
var item = new FullConfigTemplateItem()
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
using System.Net;
|
||||
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public static class ConnectionHandler
|
||||
public class ConnectionHandler
|
||||
{
|
||||
private static readonly string _tag = "ConnectionHandler";
|
||||
private static readonly Lazy<ConnectionHandler> _instance = new(() => new());
|
||||
public static ConnectionHandler Instance => _instance.Value;
|
||||
|
||||
public static async Task<string> RunAvailabilityCheck()
|
||||
public async Task<string> RunAvailabilityCheck()
|
||||
{
|
||||
var time = await GetRealPingTime();
|
||||
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
||||
var downloadHandle = new DownloadService();
|
||||
var time = await downloadHandle.RunAvailabilityCheck(null);
|
||||
var ip = time > 0 ? await GetIPInfo(downloadHandle) ?? Global.None : Global.None;
|
||||
|
||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||
}
|
||||
|
||||
private static async Task<string?> GetIPInfo()
|
||||
private async Task<string?> GetIPInfo(DownloadService downloadHandle)
|
||||
{
|
||||
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||
var url = AppHandler.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var downloadHandle = new DownloadService();
|
||||
var result = await downloadHandle.TryDownloadString(url, true, "");
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -40,31 +39,4 @@ public static class ConnectionHandler
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ using System.Text;
|
|||
using CliWrap;
|
||||
using CliWrap.Buffered;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class CoreAdminManager
|
||||
public class CoreAdminHandler
|
||||
{
|
||||
private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
|
||||
public static CoreAdminManager Instance => _instance.Value;
|
||||
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new());
|
||||
public static CoreAdminHandler Instance => _instance.Value;
|
||||
private Config _config;
|
||||
private Action<bool, string>? _updateFunc;
|
||||
private int _linuxSudoPid = -1;
|
||||
|
@ -72,7 +72,7 @@ public class CoreAdminManager
|
|||
proc.BeginErrorReadLine();
|
||||
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd);
|
||||
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
|
||||
|
||||
await Task.Delay(100);
|
||||
if (proc is null or { HasExited: true })
|
||||
|
@ -103,7 +103,7 @@ public class CoreAdminManager
|
|||
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
|
||||
var result = await Cli.Wrap(Global.LinuxBash)
|
||||
.WithArguments(arg)
|
||||
.WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
|
||||
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
|
||||
.ExecuteBufferedAsync();
|
||||
|
||||
UpdateFunc(false, result.StandardOutput.ToString());
|
|
@ -3,13 +3,13 @@ namespace ServiceLib.Handler;
|
|||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
public static class CoreConfigHandler
|
||||
public class CoreConfigHandler
|
||||
{
|
||||
private static readonly string _tag = "CoreConfigHandler";
|
||||
|
||||
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
||||
{
|
||||
var config = AppManager.Instance.Config;
|
||||
var config = AppHandler.Instance.Config;
|
||||
var result = new RetResult();
|
||||
|
||||
if (node.ConfigType == EConfigType.Custom)
|
||||
|
@ -21,7 +21,7 @@ public static class CoreConfigHandler
|
|||
_ => await GenerateClientCustomConfig(node, fileName)
|
||||
};
|
||||
}
|
||||
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
||||
}
|
||||
|
@ -112,11 +112,11 @@ public static class CoreConfigHandler
|
|||
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
|
||||
{
|
||||
var result = new RetResult();
|
||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
||||
testItem.Port = port;
|
||||
|
||||
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
/// <summary>
|
||||
/// Core process processing class
|
||||
/// </summary>
|
||||
public class CoreManager
|
||||
public class CoreHandler
|
||||
{
|
||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||
public static CoreManager Instance => _instance.Value;
|
||||
private static readonly Lazy<CoreHandler> _instance = new(() => new());
|
||||
public static CoreHandler Instance => _instance.Value;
|
||||
private Config _config;
|
||||
private Process? _process;
|
||||
private Process? _processPre;
|
||||
|
@ -39,7 +39,7 @@ public class CoreManager
|
|||
|
||||
if (Utils.IsNonWindows())
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo)
|
||||
{
|
||||
if (it.CoreType == ECoreType.v2rayN)
|
||||
|
@ -114,7 +114,7 @@ public class CoreManager
|
|||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
UpdateFunc(false, configPath);
|
||||
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
||||
if (proc is null)
|
||||
{
|
||||
|
@ -126,7 +126,7 @@ public class CoreManager
|
|||
|
||||
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
|
||||
{
|
||||
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
|
||||
var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId);
|
||||
if (node is null)
|
||||
{
|
||||
return -1;
|
||||
|
@ -140,8 +140,8 @@ public class CoreManager
|
|||
return -1;
|
||||
}
|
||||
|
||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
||||
if (proc is null)
|
||||
{
|
||||
|
@ -157,7 +157,7 @@ public class CoreManager
|
|||
{
|
||||
if (_linuxSudo)
|
||||
{
|
||||
await CoreAdminManager.Instance.KillProcessAsLinuxSudo();
|
||||
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo();
|
||||
_linuxSudo = false;
|
||||
}
|
||||
|
||||
|
@ -183,8 +183,8 @@ public class CoreManager
|
|||
|
||||
private async Task CoreStart(ProfileItem node)
|
||||
{
|
||||
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
|
||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
|
||||
|
@ -199,7 +199,7 @@ public class CoreManager
|
|||
{
|
||||
if (_process != null && !_process.HasExited)
|
||||
{
|
||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
||||
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
||||
if (itemSocks != null)
|
||||
{
|
||||
|
@ -208,7 +208,7 @@ public class CoreManager
|
|||
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
||||
if (result.Success)
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||
if (proc is null)
|
||||
{
|
||||
|
@ -231,7 +231,7 @@ public class CoreManager
|
|||
|
||||
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
||||
{
|
||||
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
UpdateFunc(false, msg);
|
||||
|
@ -246,8 +246,8 @@ public class CoreManager
|
|||
&& Utils.IsNonWindows())
|
||||
{
|
||||
_linuxSudo = true;
|
||||
await CoreAdminManager.Instance.Init(_config, _updateFunc);
|
||||
return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
|
||||
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
|
||||
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
|
||||
}
|
||||
|
||||
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
|
||||
|
@ -299,7 +299,7 @@ public class CoreManager
|
|||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
AppManager.Instance.AddProcess(proc.Handle);
|
||||
AppHandler.Instance.AddProcess(proc.Handle);
|
||||
if (proc is null or { HasExited: true })
|
||||
{
|
||||
throw new Exception(ResUI.FailedToRunCore);
|
|
@ -1,12 +1,12 @@
|
|||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public sealed class CoreInfoManager
|
||||
public sealed class CoreInfoHandler
|
||||
{
|
||||
private static readonly Lazy<CoreInfoManager> _instance = new(() => new());
|
||||
private static readonly Lazy<CoreInfoHandler> _instance = new(() => new());
|
||||
private List<CoreInfo>? _coreInfo;
|
||||
public static CoreInfoManager Instance => _instance.Value;
|
||||
public static CoreInfoHandler Instance => _instance.Value;
|
||||
|
||||
public CoreInfoManager()
|
||||
public CoreInfoHandler()
|
||||
{
|
||||
InitCoreInfo();
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class HtmlPageFmt : BaseFmt
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using ReactiveUI;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class NoticeManager
|
||||
public class NoticeHandler
|
||||
{
|
||||
private static readonly Lazy<NoticeManager> _instance = new(() => new());
|
||||
public static NoticeManager Instance => _instance.Value;
|
||||
private static readonly Lazy<NoticeHandler> _instance = new(() => new());
|
||||
public static NoticeHandler Instance => _instance.Value;
|
||||
|
||||
public void Enqueue(string? content)
|
||||
{
|
|
@ -1,12 +1,12 @@
|
|||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class PacManager
|
||||
public class PacHandler
|
||||
{
|
||||
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
|
||||
public static PacManager Instance => _instance.Value;
|
||||
private static readonly Lazy<PacHandler> _instance = new(() => new PacHandler());
|
||||
public static PacHandler Instance => _instance.Value;
|
||||
|
||||
private string _configPath;
|
||||
private int _httpPort;
|
|
@ -2,17 +2,17 @@ using System.Collections.Concurrent;
|
|||
|
||||
//using System.Reactive.Linq;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class ProfileExManager
|
||||
public class ProfileExHandler
|
||||
{
|
||||
private static readonly Lazy<ProfileExManager> _instance = new(() => new());
|
||||
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
|
||||
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
|
||||
private readonly Queue<string> _queIndexIds = new();
|
||||
public static ProfileExManager Instance => _instance.Value;
|
||||
public static ProfileExHandler Instance => _instance.Value;
|
||||
private static readonly string _tag = "ProfileExHandler";
|
||||
|
||||
public ProfileExManager()
|
||||
public ProfileExHandler()
|
||||
{
|
||||
//Init();
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class StatisticsManager
|
||||
public class StatisticsHandler
|
||||
{
|
||||
private static readonly Lazy<StatisticsManager> instance = new(() => new());
|
||||
public static StatisticsManager Instance => instance.Value;
|
||||
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
|
||||
public static StatisticsHandler Instance => instance.Value;
|
||||
|
||||
private Config _config;
|
||||
private ServerStatItem? _serverStatItem;
|
||||
|
@ -91,7 +91,7 @@ public class StatisticsManager
|
|||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
var ticks = DateTime.Now.Date.Ticks;
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
||||
|
||||
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
||||
|
@ -128,7 +128,7 @@ public class StatisticsManager
|
|||
|
||||
private async Task GetServerStatItem(string indexId)
|
||||
{
|
||||
var ticks = DateTime.Now.Date.Ticks;
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
|
||||
{
|
||||
_serverStatItem = null;
|
|
@ -1,11 +1,11 @@
|
|||
namespace ServiceLib.Handler;
|
||||
|
||||
public static class SubscriptionHandler
|
||||
public class SubscriptionHandler
|
||||
{
|
||||
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Action<bool, string> updateFunc)
|
||||
{
|
||||
updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
||||
var subItem = await AppManager.Instance.SubItems();
|
||||
var subItem = await AppHandler.Instance.SubItems();
|
||||
|
||||
if (subItem is not { Count: > 0 })
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingLinux
|
||||
public class ProxySettingLinux
|
||||
{
|
||||
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingOSX
|
||||
public class ProxySettingOSX
|
||||
{
|
||||
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionO
|
|||
|
||||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingWindows
|
||||
public class ProxySettingWindows
|
||||
{
|
||||
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ public static class SysProxyHandler
|
|||
|
||||
try
|
||||
{
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (port <= 0)
|
||||
{
|
||||
|
@ -56,7 +56,7 @@ public static class SysProxyHandler
|
|||
|
||||
if (type != ESysProxyType.Pac && Utils.IsWindows())
|
||||
{
|
||||
PacManager.Instance.Stop();
|
||||
PacHandler.Instance.Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -90,8 +90,8 @@ public static class SysProxyHandler
|
|||
|
||||
private static async Task SetWindowsProxyPac(int port)
|
||||
{
|
||||
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
|
||||
var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||
await PacHandler.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
|
||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class TaskManager
|
||||
public class TaskHandler
|
||||
{
|
||||
private static readonly Lazy<TaskManager> _instance = new(() => new());
|
||||
public static TaskManager Instance => _instance.Value;
|
||||
private static readonly Lazy<TaskHandler> _instance = new(() => new());
|
||||
public static TaskHandler Instance => _instance.Value;
|
||||
|
||||
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ public class TaskManager
|
|||
//Logging.SaveLog("Execute save config");
|
||||
|
||||
await ConfigHandler.SaveConfig(config);
|
||||
await ProfileExManager.Instance.SaveTo();
|
||||
await ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
//Execute once 1 hour
|
||||
|
@ -52,7 +52,7 @@ public class TaskManager
|
|||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
||||
var lstSubs = (await AppManager.Instance.SubItems())?
|
||||
var lstSubs = (await AppHandler.Instance.SubItems())?
|
||||
.Where(t => t.AutoUpdateInterval > 0)
|
||||
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
|
||||
.ToList();
|
||||
|
@ -66,7 +66,7 @@ public class TaskManager
|
|||
|
||||
foreach (var item in lstSubs)
|
||||
{
|
||||
await SubscriptionHandler.UpdateProcess(config, item.Id, true, (success, msg) =>
|
||||
await SubscriptionHandler.UpdateProcess(config, item.Id, true, (bool success, string msg) =>
|
||||
{
|
||||
updateFunc?.Invoke(success, msg);
|
||||
if (success)
|
||||
|
@ -87,7 +87,7 @@ public class TaskManager
|
|||
Logging.SaveLog("Execute update geo files");
|
||||
|
||||
var updateHandle = new UpdateService();
|
||||
await updateHandle.UpdateGeoFileAll(config, (success, msg) =>
|
||||
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
|
||||
{
|
||||
updateFunc?.Invoke(false, msg);
|
||||
});
|
|
@ -1,12 +1,12 @@
|
|||
using System.Net;
|
||||
using WebDav;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public sealed class WebDavManager
|
||||
public sealed class WebDavHandler
|
||||
{
|
||||
private static readonly Lazy<WebDavManager> _instance = new(() => new());
|
||||
public static WebDavManager Instance => _instance.Value;
|
||||
private static readonly Lazy<WebDavHandler> _instance = new(() => new());
|
||||
public static WebDavHandler Instance => _instance.Value;
|
||||
|
||||
private readonly Config? _config;
|
||||
private WebDavClient? _client;
|
||||
|
@ -15,9 +15,9 @@ public sealed class WebDavManager
|
|||
private readonly string _webFileName = "backup.zip";
|
||||
private readonly string _tag = "WebDav--";
|
||||
|
||||
public WebDavManager()
|
||||
public WebDavHandler()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
}
|
||||
|
||||
private async Task<bool> GetClient()
|
962
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
962
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1059,18 +1059,6 @@
|
|||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
|
||||
</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">
|
||||
<value>فعال سازی additional Inbound</value>
|
||||
</data>
|
||||
|
@ -1509,4 +1497,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1059,18 +1059,6 @@
|
|||
<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>
|
||||
</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">
|
||||
<value>További bejövő engedélyezése</value>
|
||||
</data>
|
||||
|
@ -1509,4 +1497,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1059,18 +1059,6 @@
|
|||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>Please make sure the Configuration remarks exist and are unique</value>
|
||||
</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">
|
||||
<value>Enable additional Inbound</value>
|
||||
</data>
|
||||
|
@ -1509,4 +1497,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1059,18 +1059,6 @@
|
|||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>Убедитесь, что примечание существует и является уникальным</value>
|
||||
</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">
|
||||
<value>Включить дополнительный входящий канал</value>
|
||||
</data>
|
||||
|
@ -1509,4 +1497,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1056,18 +1056,6 @@
|
|||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>请确保配置文件别名存在并唯一</value>
|
||||
</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">
|
||||
<value>启用额外监听端口</value>
|
||||
</data>
|
||||
|
@ -1506,4 +1494,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -1056,18 +1056,6 @@
|
|||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>請確保設定檔別名存在並且唯一</value>
|
||||
</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">
|
||||
<value>啟用額外偵聽連接埠</value>
|
||||
</data>
|
||||
|
@ -1506,4 +1494,4 @@
|
|||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -73,12 +73,12 @@ public class CoreConfigClashService
|
|||
}
|
||||
|
||||
//mixed-port
|
||||
fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
//log-level
|
||||
fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel);
|
||||
|
||||
//external-controller
|
||||
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||
fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}";
|
||||
//allow-lan
|
||||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
|
@ -139,7 +139,7 @@ public class CoreConfigClashService
|
|||
return ret;
|
||||
}
|
||||
|
||||
ClashApiManager.Instance.ProfileContent = fileContent;
|
||||
ClashApiHandler.Instance.ProfileContent = fileContent;
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
||||
ret.Success = true;
|
||||
|
|
2278
v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs
Normal file
2278
v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs
Normal file
File diff suppressed because it is too large
Load diff
1952
v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs
Normal file
1952
v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,522 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
|
@ -1,506 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,577 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,365 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
|
@ -1,422 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,695 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
|
@ -19,7 +20,7 @@ public class DownloadService
|
|||
{
|
||||
try
|
||||
{
|
||||
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
|
||||
var progress = new Progress<string>();
|
||||
progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}");
|
||||
|
@ -44,7 +45,7 @@ public class DownloadService
|
|||
{
|
||||
try
|
||||
{
|
||||
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
|
||||
|
||||
var progress = new Progress<double>();
|
||||
|
@ -71,7 +72,7 @@ public class DownloadService
|
|||
|
||||
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
||||
{
|
||||
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
var webRequestHandler = new SocketsHttpHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
|
@ -141,7 +142,7 @@ public class DownloadService
|
|||
{
|
||||
try
|
||||
{
|
||||
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
var webProxy = await GetWebProxy(blProxy);
|
||||
var client = new HttpClient(new SocketsHttpHandler()
|
||||
{
|
||||
|
@ -186,7 +187,7 @@ public class DownloadService
|
|||
{
|
||||
try
|
||||
{
|
||||
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||
|
||||
var webProxy = await GetWebProxy(blProxy);
|
||||
|
||||
|
@ -209,13 +210,70 @@ public class DownloadService
|
|||
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)
|
||||
{
|
||||
if (!blProxy)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
if (await SocketCheck(Global.Loopback, port) == false)
|
||||
{
|
||||
return null;
|
||||
|
|
|
@ -23,7 +23,7 @@ public class SpeedtestService
|
|||
Task.Run(async () =>
|
||||
{
|
||||
await RunAsync(actionType, selecteds);
|
||||
await ProfileExManager.Instance.SaveTo();
|
||||
await ProfileExHandler.Instance.SaveTo();
|
||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||
});
|
||||
}
|
||||
|
@ -98,18 +98,18 @@ public class SpeedtestService
|
|||
case ESpeedActionType.Tcping:
|
||||
case ESpeedActionType.Realping:
|
||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Speedtest:
|
||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
|
||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Mixedtest:
|
||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0);
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ public class SpeedtestService
|
|||
{
|
||||
var responseTime = await GetTcpingTime(it.Address, it.Port);
|
||||
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -191,13 +191,15 @@ public class SpeedtestService
|
|||
var pid = -1;
|
||||
try
|
||||
{
|
||||
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds);
|
||||
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
|
||||
var downloadHandle = new DownloadService();
|
||||
|
||||
List<Task> tasks = new();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
|
@ -211,7 +213,7 @@ public class SpeedtestService
|
|||
}
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await DoRealPing(it);
|
||||
await DoRealPing(downloadHandle, it);
|
||||
}));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
|
@ -253,7 +255,7 @@ public class SpeedtestService
|
|||
var pid = -1;
|
||||
try
|
||||
{
|
||||
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
||||
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
||||
|
@ -261,7 +263,7 @@ public class SpeedtestService
|
|||
else
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
var delay = await DoRealPing(it);
|
||||
var delay = await DoRealPing(downloadHandle, it);
|
||||
if (blSpeedTest)
|
||||
{
|
||||
if (delay > 0)
|
||||
|
@ -292,12 +294,12 @@ public class SpeedtestService
|
|||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async Task<int> DoRealPing(ServerTestItem it)
|
||||
private async Task<int> DoRealPing(DownloadService downloadHandle, ServerTestItem it)
|
||||
{
|
||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
||||
var responseTime = await downloadHandle.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
||||
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
||||
return responseTime;
|
||||
}
|
||||
|
@ -314,7 +316,7 @@ public class SpeedtestService
|
|||
decimal.TryParse(msg, out var dec);
|
||||
if (dec > 0)
|
||||
{
|
||||
ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec);
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, dec);
|
||||
}
|
||||
UpdateFunc(it.IndexId, "", msg);
|
||||
});
|
||||
|
@ -356,8 +358,8 @@ public class SpeedtestService
|
|||
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
||||
{
|
||||
List<List<ServerTestItem>> lstTest = new();
|
||||
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||
var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList();
|
||||
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList();
|
||||
|
||||
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
||||
{
|
||||
|
@ -376,7 +378,7 @@ public class SpeedtestService
|
|||
_updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
||||
if (indexId.IsNotEmpty() && speed.IsNotEmpty())
|
||||
{
|
||||
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
||||
ProfileExHandler.Instance.SetTestMessage(indexId, speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ public class StatisticsSingboxService
|
|||
private bool _exitFlag;
|
||||
private ClientWebSocket? webSocket;
|
||||
private Action<ServerSpeedItem>? _updateFunc;
|
||||
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
|
||||
private string Url => $"ws://{Global.Loopback}:{AppHandler.Instance.StatePort2}/traffic";
|
||||
private static readonly string _tag = "StatisticsSingboxService";
|
||||
|
||||
public StatisticsSingboxService(Config config, Action<ServerSpeedItem> updateFunc)
|
||||
|
|
|
@ -7,7 +7,7 @@ public class StatisticsXrayService
|
|||
private readonly Config _config;
|
||||
private bool _exitFlag;
|
||||
private Action<ServerSpeedItem>? _updateFunc;
|
||||
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
|
||||
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort}/debug/vars";
|
||||
|
||||
public StatisticsXrayService(Config config, Action<ServerSpeedItem> updateFunc)
|
||||
{
|
||||
|
|
|
@ -135,7 +135,7 @@ public class UpdateService
|
|||
|
||||
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
var tagName = string.Empty;
|
||||
if (preRelease)
|
||||
{
|
||||
|
@ -169,7 +169,7 @@ public class UpdateService
|
|||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
string filePath = string.Empty;
|
||||
foreach (var name in coreInfo.CoreExes)
|
||||
{
|
||||
|
@ -221,7 +221,7 @@ public class UpdateService
|
|||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
|
||||
SemanticVersion curVersion;
|
||||
string message;
|
||||
|
@ -379,7 +379,7 @@ public class UpdateService
|
|||
var geoSiteFiles = new List<string>();
|
||||
|
||||
//Collect used files list
|
||||
var routingItems = await AppManager.Instance.RoutingItems();
|
||||
var routingItems = await AppHandler.Instance.RoutingItems();
|
||||
foreach (var routing in routingItems)
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
|
@ -434,7 +434,7 @@ public class UpdateService
|
|||
|
||||
var fileName = $"{type}-{srsName}.srs";
|
||||
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
|
||||
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
|
||||
var url = string.Format(srsUrl, type, $"{type}-{srsName}");
|
||||
|
||||
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
|
||||
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -45,25 +45,25 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
var remarks = SelectedSource.Remarks;
|
||||
if (remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedSource.Address.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
||||
|
||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,12 +74,12 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
return;
|
||||
}
|
||||
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||
item ??= SelectedSource;
|
||||
item.Address = fileName;
|
||||
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||
if (item.IndexId.IsNotEmpty())
|
||||
{
|
||||
SelectedSource = JsonUtils.DeepCopy(item);
|
||||
|
@ -88,7 +88,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FailedImportedCustomServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FailedImportedCustomServer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
var address = SelectedSource.Address;
|
||||
if (address.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FailedReadConfiguration);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FailedReadConfiguration);
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public class AddServerViewModel : MyReactiveObject
|
|||
|
||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -43,32 +43,32 @@ public class AddServerViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource.Remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedSource.Address.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillServerAddress);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddress);
|
||||
return;
|
||||
}
|
||||
var port = SelectedSource.Port.ToString();
|
||||
if (port.IsNullOrEmpty() || !Utils.IsNumeric(port)
|
||||
|| SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectServerPort);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectServerPort);
|
||||
return;
|
||||
}
|
||||
if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
|
||||
{
|
||||
if (SelectedSource.Id.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillPassword);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillPassword);
|
||||
return;
|
||||
}
|
||||
if (SelectedSource.Security.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectEncryption);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ public class AddServerViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource.Id.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillUUID);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillUUID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +84,12 @@ public class AddServerViewModel : MyReactiveObject
|
|||
|
||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
|
||||
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -52,14 +52,14 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
_config.WebDavItem = SelectedSource;
|
||||
_ = await ConfigHandler.SaveConfig(_config);
|
||||
|
||||
var result = await WebDavManager.Instance.CheckConnection();
|
||||
var result = await WebDavHandler.Instance.CheckConnection();
|
||||
if (result)
|
||||
{
|
||||
DisplayOperationMsg(ResUI.OperationSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
var result = await CreateZipFileFromDirectory(fileName);
|
||||
if (result)
|
||||
{
|
||||
var result2 = await WebDavManager.Instance.PutFile(fileName);
|
||||
var result2 = await WebDavHandler.Instance.PutFile(fileName);
|
||||
if (result2)
|
||||
{
|
||||
DisplayOperationMsg(ResUI.OperationSuccess);
|
||||
|
@ -78,21 +78,21 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
}
|
||||
}
|
||||
|
||||
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
||||
}
|
||||
|
||||
private async Task RemoteRestore()
|
||||
{
|
||||
DisplayOperationMsg();
|
||||
var fileName = Utils.GetTempPath(Utils.GetGuid());
|
||||
var result = await WebDavManager.Instance.GetRawFile(fileName);
|
||||
var result = await WebDavHandler.Instance.GetRawFile(fileName);
|
||||
if (result)
|
||||
{
|
||||
await LocalRestore(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
||||
}
|
||||
|
||||
public async Task<bool> LocalBackup(string fileName)
|
||||
|
@ -105,7 +105,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -158,7 +158,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||
|
||||
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
|
|
@ -26,7 +26,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||
|
||||
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||
|
||||
|
@ -58,7 +58,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||
|
||||
private async Task GetClashConnections()
|
||||
{
|
||||
var ret = await ClashApiManager.Instance.GetClashConnectionsAsync();
|
||||
var ret = await ClashApiHandler.Instance.GetClashConnectionsAsync();
|
||||
if (ret == null)
|
||||
{
|
||||
return;
|
||||
|
@ -118,7 +118,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||
{
|
||||
_connectionItems.Clear();
|
||||
}
|
||||
await ClashApiManager.Instance.ClashConnectionClose(id);
|
||||
await ClashApiHandler.Instance.ClashConnectionClose(id);
|
||||
await GetClashConnections();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
|
||||
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -152,13 +152,13 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
{
|
||||
{ "mode", mode.ToString().ToLower() }
|
||||
};
|
||||
await ClashApiManager.Instance.ClashConfigUpdate(headers);
|
||||
await ClashApiHandler.Instance.ClashConfigUpdate(headers);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetClashProxies(bool refreshUI)
|
||||
{
|
||||
var ret = await ClashApiManager.Instance.GetClashProxiesAsync();
|
||||
var ret = await ClashApiHandler.Instance.GetClashProxiesAsync();
|
||||
if (ret?.Item1 == null || ret.Item2 == null)
|
||||
{
|
||||
return;
|
||||
|
@ -182,7 +182,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
var selectedName = SelectedGroup?.Name;
|
||||
_proxyGroups.Clear();
|
||||
|
||||
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
||||
var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups();
|
||||
if (proxyGroups != null && proxyGroups.Count > 0)
|
||||
{
|
||||
foreach (var it in proxyGroups)
|
||||
|
@ -352,11 +352,11 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
var selectedProxy = TryGetProxy(name);
|
||||
if (selectedProxy == null || selectedProxy.type != "Selector")
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
|
||||
await ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode);
|
||||
|
||||
selectedProxy.now = nameNode;
|
||||
var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
||||
|
@ -368,12 +368,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
|
||||
SelectedGroup = group2;
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
|
||||
private async Task ProxiesDelayTest(bool blAll = true)
|
||||
{
|
||||
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
|
||||
ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
|
||||
{
|
||||
if (item == null || result.IsNullOrEmpty())
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
|
||||
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
||||
|
||||
|
@ -60,7 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
|
||||
private async Task Init()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
var item = _config.SimpleDNSItem;
|
||||
UseSystemHosts = item.UseSystemHosts;
|
||||
AddCommonHosts = item.AddCommonHosts;
|
||||
|
@ -76,14 +76,14 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
Hosts = item.Hosts;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray);
|
||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||
DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty;
|
||||
DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty;
|
||||
NormalDNSCompatible = item1?.NormalDNS ?? string.Empty;
|
||||
|
||||
var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
SBCustomDNSEnableCompatible = item2.Enabled;
|
||||
DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty;
|
||||
DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty;
|
||||
|
@ -117,7 +117,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
{
|
||||
if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}'))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(NormalDNS2Compatible);
|
||||
if (obj2 == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -136,12 +136,12 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(TunDNS2Compatible);
|
||||
if (obj2 == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray);
|
||||
item1.Enabled = RayCustomDNSEnableCompatible;
|
||||
item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible;
|
||||
item1.DomainDNSAddress = DomainDNSAddressCompatible;
|
||||
|
@ -149,7 +149,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||
item1.NormalDNS = NormalDNSCompatible;
|
||||
await ConfigHandler.SaveDNSItems(_config, item1);
|
||||
|
||||
var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
item2.Enabled = SBCustomDNSEnableCompatible;
|
||||
item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible;
|
||||
item2.DomainDNSAddress = DomainDNSAddress2Compatible;
|
||||
|
|
|
@ -41,7 +41,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
|||
|
||||
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
|
@ -53,13 +53,13 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
|||
|
||||
private async Task Init()
|
||||
{
|
||||
var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||
EnableFullConfigTemplate4Ray = item?.Enabled ?? false;
|
||||
FullConfigTemplate4Ray = item?.Config ?? string.Empty;
|
||||
AddProxyOnly4Ray = item?.AddProxyOnly ?? false;
|
||||
ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty;
|
||||
|
||||
var item2 = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
var item2 = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
EnableFullConfigTemplate4Singbox = item2?.Enabled ?? false;
|
||||
FullConfigTemplate4Singbox = item2?.Config ?? string.Empty;
|
||||
FullTunConfigTemplate4Singbox = item2?.TunConfig ?? string.Empty;
|
||||
|
@ -75,13 +75,13 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
|||
if (!await SaveSingboxConfigAsync())
|
||||
return;
|
||||
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveXrayConfigAsync()
|
||||
{
|
||||
var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||
item.Enabled = EnableFullConfigTemplate4Ray;
|
||||
item.Config = null;
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
|||
|
||||
private async Task<bool> SaveSingboxConfigAsync()
|
||||
{
|
||||
var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
var item = await AppHandler.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
item.Enabled = EnableFullConfigTemplate4Singbox;
|
||||
item.Config = null;
|
||||
item.TunConfig = null;
|
||||
|
|
|
@ -11,7 +11,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
|||
|
||||
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
||||
|
@ -58,7 +58,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
@ -178,7 +178,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
});
|
||||
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -226,13 +226,13 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
await ProfileExManager.Instance.Init();
|
||||
await CoreManager.Instance.Init(_config, UpdateHandler);
|
||||
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
await ProfileExHandler.Instance.Init();
|
||||
await CoreHandler.Instance.Init(_config, UpdateHandler);
|
||||
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
|
||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||
{
|
||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||
await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler);
|
||||
}
|
||||
|
||||
BlReloadEnabled = true;
|
||||
|
@ -247,16 +247,16 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
private void UpdateHandler(bool notify, string msg)
|
||||
{
|
||||
NoticeManager.Instance.SendMessage(msg);
|
||||
NoticeHandler.Instance.SendMessage(msg);
|
||||
if (notify)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(msg);
|
||||
NoticeHandler.Instance.Enqueue(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTaskHandler(bool success, string msg)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(msg);
|
||||
NoticeHandler.Instance.SendMessageEx(msg);
|
||||
if (success)
|
||||
{
|
||||
var indexIdOld = _config.IndexId;
|
||||
|
@ -303,10 +303,10 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString());
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await ProfileExManager.Instance.SaveTo();
|
||||
await StatisticsManager.Instance.SaveTo();
|
||||
await CoreManager.Instance.CoreStop();
|
||||
StatisticsManager.Instance.Close();
|
||||
await ProfileExHandler.Instance.SaveTo();
|
||||
await StatisticsHandler.Instance.SaveTo();
|
||||
await CoreHandler.Instance.CoreStop();
|
||||
StatisticsHandler.Instance.Close();
|
||||
|
||||
Logging.SaveLog("MyAppExitAsync End");
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
if (!Utils.UpgradeAppExists(out var upgradeFileName))
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
||||
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
||||
Logging.SaveLog("UpgradeApp does not exist");
|
||||
return;
|
||||
}
|
||||
|
@ -404,11 +404,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
RefreshSubscriptions();
|
||||
RefreshServers();
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,7 +420,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
public async Task ScanScreenResult(byte[]? bytes)
|
||||
{
|
||||
var result = QRCodeUtils.ParseBarcode(bytes);
|
||||
var result = QRCodeHelper.ParseBarcode(bytes);
|
||||
await AddScanResultAsync(result);
|
||||
}
|
||||
|
||||
|
@ -437,7 +437,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
return;
|
||||
}
|
||||
|
||||
var result = QRCodeUtils.ParseBarcode(fileName);
|
||||
var result = QRCodeHelper.ParseBarcode(fileName);
|
||||
await AddScanResultAsync(result);
|
||||
}
|
||||
|
||||
|
@ -445,7 +445,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -454,11 +454,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
RefreshSubscriptions();
|
||||
RefreshServers();
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -531,7 +531,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
private async Task ClearServerStatistics()
|
||||
{
|
||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||
await StatisticsHandler.Instance.ClearAllServerStatistics();
|
||||
RefreshServers();
|
||||
}
|
||||
|
||||
|
@ -602,13 +602,13 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
private async Task LoadCore()
|
||||
{
|
||||
var node = await ConfigHandler.GetDefaultServer(_config);
|
||||
await CoreManager.Instance.LoadCore(node);
|
||||
await CoreHandler.Instance.LoadCore(node);
|
||||
}
|
||||
|
||||
public async Task CloseCore()
|
||||
{
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await CoreManager.Instance.CoreStop();
|
||||
await CoreHandler.Instance.CoreStop();
|
||||
}
|
||||
|
||||
private async Task AutoHideStartup()
|
||||
|
|
|
@ -20,7 +20,7 @@ public class MsgViewModel : MyReactiveObject
|
|||
|
||||
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
||||
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
||||
|
|
|
@ -109,7 +109,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
|
||||
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -276,7 +276,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
|
||||
|| localPort <= 0 || localPort >= Global.MaxPort)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
return;
|
||||
}
|
||||
var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics
|
||||
|
@ -369,14 +369,14 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||
{
|
||||
await AutoStartupHandler.UpdateTask(_config);
|
||||
AppManager.Instance.Reset();
|
||||
AppHandler.Instance.Reset();
|
||||
|
||||
NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
|
||||
public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
@ -284,8 +284,8 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
if (result.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(result.Delay);
|
||||
NoticeManager.Instance.Enqueue(result.Delay);
|
||||
NoticeHandler.Instance.SendMessageEx(result.Delay);
|
||||
NoticeHandler.Instance.Enqueue(result.Delay);
|
||||
return;
|
||||
}
|
||||
var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
|
||||
|
@ -403,7 +403,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
|
||||
_subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
foreach (var item in await AppManager.Instance.SubItems())
|
||||
foreach (var item in await AppHandler.Instance.SubItems())
|
||||
{
|
||||
_subItems.Add(item);
|
||||
}
|
||||
|
@ -419,12 +419,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, filter);
|
||||
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, filter);
|
||||
|
||||
await ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||
|
||||
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
||||
lstModel = (from t in lstModel
|
||||
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
||||
from t22 in t2b.DefaultIfEmpty()
|
||||
|
@ -474,7 +474,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
foreach (var profile in orderProfiles)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(profile.IndexId);
|
||||
if (item is not null)
|
||||
{
|
||||
lstSelected.Add(item);
|
||||
|
@ -495,10 +495,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
return;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
eConfigType = item.ConfigType;
|
||||
|
@ -536,7 +536,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId);
|
||||
|
||||
await ConfigHandler.RemoveServers(_config, lstSelected);
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
if (lstSelected.Count == _profileItems.Count)
|
||||
{
|
||||
_profileItems.Clear();
|
||||
|
@ -556,7 +556,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
RefreshServers();
|
||||
Reload();
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
||||
}
|
||||
|
||||
private async Task CopyServer()
|
||||
|
@ -569,7 +569,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
|
||||
{
|
||||
RefreshServers();
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -592,10 +592,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
return;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(indexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -621,10 +621,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
|
||||
public async Task ShareServerAsync()
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
var url = FmtHandler.GetShareUri(item);
|
||||
|
@ -647,7 +647,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
|
||||
if (ret.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
if (ret?.Data?.ToString() == _config.IndexId)
|
||||
|
@ -682,7 +682,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
|
||||
RefreshServers();
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
||||
}
|
||||
|
||||
//move server
|
||||
|
@ -700,7 +700,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
}
|
||||
|
||||
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
|
||||
RefreshServers();
|
||||
SelectedMoveToGroup = null;
|
||||
|
@ -712,7 +712,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -762,10 +762,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
|
||||
private async Task Export2ClientConfigAsync(bool blClipboard)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
if (blClipboard)
|
||||
|
@ -773,12 +773,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
|
||||
if (result.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(result.Msg);
|
||||
NoticeHandler.Instance.Enqueue(result.Msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data);
|
||||
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.SendMessage(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -796,11 +796,11 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
|
||||
if (result.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(result.Msg);
|
||||
NoticeHandler.Instance.Enqueue(result.Msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName));
|
||||
NoticeHandler.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -833,7 +833,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
|
||||
}
|
||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||
NoticeHandler.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -850,7 +850,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
|
||||
item = await AppHandler.Instance.GetSubItem(_config.SubIndexId);
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
|
|
|
@ -28,7 +28,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
|||
|
||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -83,7 +83,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
|||
|
||||
if (!hasRule)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process"));
|
||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process"));
|
||||
return;
|
||||
}
|
||||
//NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
|
|
|
@ -37,7 +37,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
|
||||
public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
|
@ -151,7 +151,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
|
@ -174,7 +174,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
string remarks = SelectedRouting.Remarks;
|
||||
if (remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
return;
|
||||
}
|
||||
var item = SelectedRouting;
|
||||
|
@ -239,12 +239,12 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
|
||||
if (await ConfigHandler.SaveRoutingItem(_config, item) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
if (ret == 0)
|
||||
{
|
||||
RefreshRulesItems();
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
if (ret == 0)
|
||||
{
|
||||
RefreshRulesItems();
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
var url = SelectedRouting.Url;
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.MsgNeedUrl);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.MsgNeedUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||
if (ret == 0)
|
||||
{
|
||||
RefreshRulesItems();
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
|
||||
public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
|
@ -84,7 +84,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
{
|
||||
_routingItems.Clear();
|
||||
|
||||
var routings = await AppManager.Instance.RoutingItems();
|
||||
var routings = await AppHandler.Instance.RoutingItems();
|
||||
foreach (var item in routings)
|
||||
{
|
||||
var it = new RoutingItemModel()
|
||||
|
@ -109,12 +109,12 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
|
||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||
item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
|
@ -146,7 +146,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
{
|
||||
if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
|
@ -155,7 +155,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
}
|
||||
foreach (var it in SelectedSources ?? [SelectedSource])
|
||||
{
|
||||
var item = await AppManager.Instance.GetRoutingItem(it?.Id);
|
||||
var item = await AppHandler.Instance.GetRoutingItem(it?.Id);
|
||||
if (item != null)
|
||||
{
|
||||
await ConfigHandler.RemoveRoutingItem(item);
|
||||
|
@ -168,10 +168,10 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||
|
||||
public async Task RoutingAdvancedSetDefault()
|
||||
{
|
||||
var item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||
var item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||
if (item is null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
|
||||
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
SelectedRouting = new();
|
||||
SelectedServer = new();
|
||||
RunningServerToolTipText = "-";
|
||||
|
@ -228,7 +228,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
private async Task CopyProxyCmdToClipboard()
|
||||
{
|
||||
var cmd = Utils.IsWindows() ? "set" : "export";
|
||||
var address = $"{Global.Loopback}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}";
|
||||
var address = $"{Global.Loopback}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}");
|
||||
|
@ -283,7 +283,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
|
||||
private async Task RefreshServersMenu()
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
|
||||
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, "");
|
||||
|
||||
_servers.Clear();
|
||||
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||
|
@ -334,9 +334,9 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
|
||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
|
||||
|
||||
var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
|
||||
var msg = await Task.Run(ConnectionHandler.Instance.RunAvailabilityCheck);
|
||||
|
||||
NoticeManager.Instance.SendMessageEx(msg);
|
||||
NoticeHandler.Instance.SendMessageEx(msg);
|
||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
}
|
||||
_config.SystemProxyItem.SysProxyType = type;
|
||||
await ChangeSystemProxyAsync(type, true);
|
||||
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
|
||||
NoticeHandler.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
|
||||
|
||||
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
|
@ -381,7 +381,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
_routingItems.Clear();
|
||||
|
||||
BlRouting = true;
|
||||
var routings = await AppManager.Instance.RoutingItems();
|
||||
var routings = await AppHandler.Instance.RoutingItems();
|
||||
foreach (var item in routings)
|
||||
{
|
||||
_routingItems.Add(item);
|
||||
|
@ -404,7 +404,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
return;
|
||||
}
|
||||
|
||||
var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id);
|
||||
var item = await AppHandler.Instance.GetRoutingItem(SelectedRouting?.Id);
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
|
@ -412,7 +412,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
|
||||
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||
NoticeHandler.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
||||
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
||||
}
|
||||
|
@ -471,11 +471,11 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
}
|
||||
else if (Utils.IsLinux())
|
||||
{
|
||||
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
}
|
||||
else if (Utils.IsOSX())
|
||||
{
|
||||
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -487,10 +487,10 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
public async Task InboundDisplayStatus()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
||||
sb.Append($"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||
{
|
||||
sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}");
|
||||
sb.Append($",{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks2)}");
|
||||
}
|
||||
sb.Append(']');
|
||||
InboundDisplay = $"{ResUI.LabLocal}:{sb}";
|
||||
|
@ -498,8 +498,8 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
var lan = _config.Inbound.First().NewPort4LAN
|
||||
? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
|
||||
: $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]";
|
||||
? $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
|
||||
: $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}]";
|
||||
InboundLanDisplay = $"{ResUI.LabLAN}:{lan}";
|
||||
}
|
||||
else
|
||||
|
|
|
@ -13,7 +13,7 @@ public class SubEditViewModel : MyReactiveObject
|
|||
|
||||
public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
|
@ -29,7 +29,7 @@ public class SubEditViewModel : MyReactiveObject
|
|||
var remarks = SelectedSource.Remarks;
|
||||
if (remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -39,25 +39,25 @@ public class SubEditViewModel : MyReactiveObject
|
|||
var uri = Utils.TryUri(url);
|
||||
if (uri == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InvalidUrlTip);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip);
|
||||
return;
|
||||
}
|
||||
//Do not allow http protocol
|
||||
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||
//return;
|
||||
}
|
||||
}
|
||||
|
||||
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||
|
||||
public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
|
@ -61,7 +61,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||
public async Task RefreshSubItems()
|
||||
{
|
||||
_subItems.Clear();
|
||||
_subItems.AddRange(await AppManager.Instance.SubItems());
|
||||
_subItems.AddRange(await AppHandler.Instance.SubItems());
|
||||
}
|
||||
|
||||
public async Task EditSubAsync(bool blNew)
|
||||
|
@ -73,7 +73,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||
}
|
||||
else
|
||||
{
|
||||
item = await AppManager.Instance.GetSubItem(SelectedSource?.Id);
|
||||
item = await AppHandler.Instance.GetSubItem(SelectedSource?.Id);
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
|
@ -98,7 +98,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||
await ConfigHandler.DeleteSubItem(_config, it.Id);
|
||||
}
|
||||
await RefreshSubItems();
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using ServiceLib.Manager;
|
||||
using Splat;
|
||||
using v2rayN.Desktop.Common;
|
||||
using v2rayN.Desktop.Views;
|
||||
|
@ -26,7 +25,7 @@ public partial class App : Application
|
|||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
AppManager.Instance.InitComponents();
|
||||
AppHandler.Instance.InitComponents();
|
||||
|
||||
desktop.Exit += OnExit;
|
||||
desktop.MainWindow = new MainWindow();
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<x:Double x:Key="IconButtonWidth">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="MarginLr4">4,0</Thickness>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
|
||||
namespace v2rayN.Desktop.Base;
|
||||
|
||||
|
@ -21,7 +20,7 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
|||
{
|
||||
try
|
||||
{
|
||||
var sizeItem = ConfigHandler.GetWindowSizeItem(AppManager.Instance.Config, GetType().Name);
|
||||
var sizeItem = ConfigHandler.GetWindowSizeItem(AppHandler.Instance.Config, GetType().Name);
|
||||
if (sizeItem == null)
|
||||
{
|
||||
return;
|
||||
|
@ -46,7 +45,7 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
|||
base.OnClosed(e);
|
||||
try
|
||||
{
|
||||
ConfigHandler.SaveWindowSizeItem(AppManager.Instance.Config, GetType().Name, Width, Height);
|
||||
ConfigHandler.SaveWindowSizeItem(AppHandler.Instance.Config, GetType().Name, Width, Height);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ using Avalonia.ReactiveUI;
|
|||
using Avalonia.Win32.Input;
|
||||
using GlobalHotKeys;
|
||||
|
||||
namespace v2rayN.Desktop.Manager;
|
||||
namespace v2rayN.Desktop.Handler;
|
||||
|
||||
public sealed class HotkeyManager
|
||||
public sealed class HotkeyHandler
|
||||
{
|
||||
private static readonly Lazy<HotkeyManager> _instance = new(() => new());
|
||||
public static HotkeyManager Instance = _instance.Value;
|
||||
private static readonly Lazy<HotkeyHandler> _instance = new(() => new());
|
||||
public static HotkeyHandler Instance = _instance.Value;
|
||||
private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new();
|
||||
private HotKeyManager? _hotKeyManager;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop;
|
||||
|
@ -47,7 +46,7 @@ internal class Program
|
|||
}
|
||||
}
|
||||
|
||||
if (!AppManager.Instance.InitApp())
|
||||
if (!AppHandler.Instance.InitApp())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -63,6 +62,6 @@ internal class Program
|
|||
.WithFontByDefault()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI()
|
||||
.With(new MacOSPlatformOptions { ShowInDock = AppManager.Instance.Config.UiItem.MacOSShowInDock });
|
||||
.With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ using Avalonia.Styling;
|
|||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Semi.Avalonia;
|
||||
using ServiceLib.Manager;
|
||||
|
||||
namespace v2rayN.Desktop.ViewModels;
|
||||
|
||||
|
@ -22,7 +21,7 @@ public class ThemeSettingViewModel : MyReactiveObject
|
|||
|
||||
public ThemeSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
|
||||
BindingUI();
|
||||
RestoreUI();
|
||||
|
@ -75,7 +74,7 @@ public class ThemeSettingViewModel : MyReactiveObject
|
|||
_config.UiItem.CurrentLanguage = CurrentLanguage;
|
||||
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);
|
||||
ConfigHandler.SaveConfig(_config);
|
||||
NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips);
|
||||
NoticeHandler.Instance.Enqueue(ResUI.NeedRebootTips);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -523,7 +523,7 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||
Text="Mtu" />
|
||||
<TextBox
|
||||
x:Name="txtShortId9"
|
||||
Grid.Row="5"
|
||||
|
|
|
@ -2,7 +2,6 @@ using System.Reactive.Disposables;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
@ -51,7 +50,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
|||
|
||||
case EConfigType.Shadowsocks:
|
||||
gridSs.IsVisible = true;
|
||||
cmbSecurity3.ItemsSource = AppManager.Instance.GetShadowsocksSecurities(profileItem);
|
||||
cmbSecurity3.ItemsSource = AppHandler.Instance.GetShadowsocksSecurities(profileItem);
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
|
|
|
@ -2,7 +2,6 @@ using System.Reactive.Disposables;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
@ -15,8 +14,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
Loaded += Window_Loaded;
|
||||
_config = AppHandler.Instance.Config;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
ViewModel = new DNSSettingViewModel(UpdateViewHandler);
|
||||
|
||||
|
@ -101,9 +99,4 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
|||
{
|
||||
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/dns/");
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
btnCancel.Focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
@ -14,8 +13,7 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
Loaded += Window_Loaded;
|
||||
_config = AppHandler.Instance.Config;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
ViewModel = new FullConfigTemplateViewModel(UpdateViewHandler);
|
||||
|
||||
|
@ -50,8 +48,4 @@ 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");
|
||||
}
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
btnCancel.Focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ using Avalonia.Input;
|
|||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Manager;
|
||||
using v2rayN.Desktop.Handler;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
|
@ -21,9 +21,8 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
|
|||
|
||||
btnReset.Click += btnReset_Click;
|
||||
|
||||
HotkeyManager.Instance.IsPause = true;
|
||||
Loaded += Window_Loaded;
|
||||
this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false;
|
||||
HotkeyHandler.Instance.IsPause = true;
|
||||
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
|
@ -135,8 +134,4 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
|
|||
|
||||
return res.ToString();
|
||||
}
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
btnCancel.Focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,10 @@ using Avalonia.Threading;
|
|||
using DialogHostAvalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using Splat;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
using v2rayN.Desktop.Manager;
|
||||
using v2rayN.Desktop.Handler;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
|
@ -29,7 +28,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight };
|
||||
|
||||
this.KeyDown += MainWindow_KeyDown;
|
||||
|
@ -143,7 +142,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
||||
|
||||
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
|
||||
HotkeyManager.Instance.Init(_config, OnHotkeyHandler);
|
||||
HotkeyHandler.Instance.Init(_config, OnHotkeyHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -235,7 +234,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
StorageUI();
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
HotkeyManager.Instance.Dispose();
|
||||
HotkeyHandler.Instance.Dispose();
|
||||
desktop.Shutdown();
|
||||
}
|
||||
break;
|
||||
|
@ -352,7 +351,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
{
|
||||
//ShowHideWindow(false);
|
||||
|
||||
NoticeManager.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)");
|
||||
NoticeHandler.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)");
|
||||
await Task.CompletedTask;
|
||||
//if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
//{
|
||||
|
@ -478,7 +477,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
|
||||
private void AddHelpMenuItem()
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo
|
||||
.Where(t => t.CoreType != ECoreType.v2fly
|
||||
&& t.CoreType != ECoreType.hysteria))
|
||||
|
|
|
@ -736,7 +736,7 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" />
|
||||
Text="Auto Route" />
|
||||
<ToggleSwitch
|
||||
x:Name="togAutoRoute"
|
||||
Grid.Row="2"
|
||||
|
@ -749,7 +749,7 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" />
|
||||
Text="Strict Route" />
|
||||
<ToggleSwitch
|
||||
x:Name="togStrictRoute"
|
||||
Grid.Row="3"
|
||||
|
@ -762,7 +762,7 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsTunStack}" />
|
||||
Text="Stack" />
|
||||
<ComboBox
|
||||
x:Name="cmbStack"
|
||||
Grid.Row="4"
|
||||
|
@ -776,7 +776,7 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||
Text="Mtu" />
|
||||
<ComboBox
|
||||
x:Name="cmbMtu"
|
||||
Grid.Row="5"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
@ -15,9 +13,8 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += Window_Loaded;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
|
||||
ViewModel = new OptionSettingViewModel(UpdateViewHandler);
|
||||
|
||||
|
@ -211,8 +208,4 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
|||
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
|
||||
}
|
||||
}
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
btnCancel.Focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ using Avalonia.Threading;
|
|||
using DialogHostAvalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using ServiceLib.Manager;
|
||||
using Splat;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
|
@ -27,7 +26,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
_config = AppHandler.Instance.Config;
|
||||
_window = window;
|
||||
|
||||
menuSelectAll.Click += menuSelectAll_Click;
|
||||
|
|
|
@ -23,7 +23,7 @@ public partial class QrcodeView : UserControl
|
|||
|
||||
private Bitmap? GetQRCode(string? url)
|
||||
{
|
||||
var bytes = QRCodeUtils.GenQRCode(url);
|
||||
var bytes = QRCodeHelper.GenQRCode(url);
|
||||
return ByteToBitmap(bytes);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += Window_Loaded;
|
||||
this.Closing += RoutingSettingWindow_Closing;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
this.KeyDown += RoutingSettingWindow_KeyDown;
|
||||
|
@ -135,8 +134,4 @@ 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
Loading…
Reference in a new issue