Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6391667c15 | ||
|
|
7f26445327 | ||
|
|
291d4bd8e5 | ||
|
|
f2f3a7eb5f | ||
|
|
e7609619d4 | ||
|
|
84bf9ecfaf | ||
|
|
a2917b3ce8 | ||
|
|
d094370209 | ||
|
|
1a6fbf782d | ||
|
|
3f67a23f8b | ||
|
|
b8eb7e7b29 | ||
|
|
1d69916410 | ||
|
|
49fa103077 | ||
|
|
e3a63db966 | ||
|
|
ef4a1903ec | ||
|
|
5a3286dad1 | ||
|
|
058c6e4a85 | ||
|
|
ea1d438e40 | ||
|
|
a108eaf34b | ||
|
|
da28c639b3 | ||
|
|
8ef68127d4 | ||
|
|
f39d966a33 | ||
|
|
f83e83de13 | ||
|
|
abdafc9b3b | ||
|
|
8f93c50151 | ||
|
|
fe7c505cc9 | ||
|
|
0d5afa4ff5 | ||
|
|
2ad716a4ad | ||
|
|
cddf88730f | ||
|
|
3eb49aa24c | ||
|
|
45c987fd86 | ||
|
|
7bec05ec23 | ||
|
|
606b216cd0 | ||
|
|
bb4f33559f | ||
|
|
c7f3e53f28 | ||
|
|
0035e836d7 | ||
|
|
e6da14f4a8 | ||
|
|
f748f1849c | ||
|
|
f8995b78f6 | ||
|
|
a861020828 | ||
|
|
dc94962900 | ||
|
|
4a40b87bba | ||
|
|
4853e2348d | ||
|
|
e104f9f9b2 | ||
|
|
876381a7fb | ||
|
|
4f711b1bd3 | ||
|
|
89893c0945 | ||
|
|
7b7fe0ef46 | ||
|
|
f66226c103 | ||
|
|
d5c50ef27c | ||
|
|
2060ac18fd | ||
|
|
c9c1cd8cbb | ||
|
|
5201dd5ad0 | ||
|
|
4c3c1e0b5f | ||
|
|
c27651b7b7 | ||
|
|
06636d04ac | ||
|
|
6979e21628 | ||
|
|
310d266745 | ||
|
|
120e8d0686 | ||
|
|
186b56aed9 | ||
|
|
c560fe13fe | ||
|
|
95e3ebd815 | ||
|
|
dc2877d817 | ||
|
|
89d6af8fc9 | ||
|
|
dcc9c9fa14 | ||
|
|
a73906505c | ||
|
|
f45290eb3a | ||
|
|
0fb6b2e54b | ||
|
|
9cc99c5c63 | ||
|
|
46801ce339 | ||
|
|
4345c58b45 | ||
|
|
a2028623e7 | ||
|
|
8314ff3271 | ||
|
|
6a9408fe9b | ||
|
|
b5e0a77401 |
40
.github/workflows/build-linux.yml
vendored
40
.github/workflows/build-linux.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v5.0.0
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
@@ -98,4 +98,38 @@ jobs:
|
|||||||
file: ${{ github.workspace }}/v2rayN*.zip
|
file: ${{ github.workspace }}/v2rayN*.zip
|
||||||
tag: ${{ github.event.inputs.release_tag }}
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
file_glob: true
|
file_glob: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
|
# release RHEL package
|
||||||
|
- name: Package RPM (RHEL-family)
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-rhel.sh
|
||||||
|
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
|
||||||
|
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
|
||||||
|
|
||||||
|
- name: Collect RPMs into workspace
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
mkdir -p "${{ github.workspace }}/dist/rpm"
|
||||||
|
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
|
||||||
|
# Rename to requested filenames
|
||||||
|
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
|
||||||
|
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
||||||
|
|
||||||
|
- name: Upload RPM artifacts
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-rpm
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/dist/rpm/**/*.rpm
|
||||||
|
|
||||||
|
- name: Upload RPMs to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|||||||
4
.github/workflows/build-osx.yml
vendored
4
.github/workflows/build-osx.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v5.0.0
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/build-windows-desktop.yml
vendored
4
.github/workflows/build-windows-desktop.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v5.0.0
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/build-windows.yml
vendored
4
.github/workflows/build-windows.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v5.0.0
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v4.3.1
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -397,4 +397,5 @@ FodyWeavers.xsd
|
|||||||
*.msp
|
*.msp
|
||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|||||||
@@ -52,7 +52,17 @@ sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
|
|||||||
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
|
||||||
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
|
||||||
|
|
||||||
# desktop && PATH
|
# Patch
|
||||||
|
# set owner to root:root
|
||||||
|
sudo chown -R root:root "${PackagePath}"
|
||||||
|
# set all directories to 755 (readable & traversable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} +
|
||||||
|
# set all regular files to 644 (readable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} +
|
||||||
|
# ensure main binaries are 755 (executable by all users)
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true
|
||||||
|
|
||||||
|
# build deb package
|
||||||
sudo dpkg-deb -Zxz --build $PackagePath
|
sudo dpkg-deb -Zxz --build $PackagePath
|
||||||
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
|
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
|
||||||
|
|||||||
815
package-rhel.sh
Normal file
815
package-rhel.sh
Normal file
@@ -0,0 +1,815 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ===== Require Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ====
|
||||||
|
if [[ -r /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
case "$ID" in
|
||||||
|
rhel|rocky|almalinux|centos|ubuntu|debian)
|
||||||
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
||||||
|
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Config & Parse arguments =========================================================
|
||||||
|
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
|
||||||
|
WITH_CORE="both" # Default: bundle both xray+sing-box
|
||||||
|
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
|
||||||
|
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
|
||||||
|
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
|
||||||
|
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
|
||||||
|
|
||||||
|
# If the first argument starts with --, do not treat it as a version number
|
||||||
|
if [[ "${VERSION_ARG:-}" == --* ]]; then
|
||||||
|
VERSION_ARG=""
|
||||||
|
fi
|
||||||
|
# Take the first non --* argument as version, discard it
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
|
||||||
|
|
||||||
|
# Parse remaining optional arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--with-core) WITH_CORE="${2:-both}"; shift 2;;
|
||||||
|
--autostart) AUTOSTART=1; shift;;
|
||||||
|
--xray-ver) XRAY_VER="${2:-}"; shift 2;;
|
||||||
|
--singbox-ver) SING_VER="${2:-}"; shift 2;;
|
||||||
|
--netcore) FORCE_NETCORE=1; shift;;
|
||||||
|
--arch) ARCH_OVERRIDE="${2:-}"; shift 2;;
|
||||||
|
--buildfrom) BUILD_FROM="${2:-}"; shift 2;;
|
||||||
|
*)
|
||||||
|
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
|
||||||
|
shift;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Conflict: version number AND --buildfrom cannot be used together
|
||||||
|
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then
|
||||||
|
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
|
||||||
|
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Environment check + Dependencies ========================================
|
||||||
|
host_arch="$(uname -m)"
|
||||||
|
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
|
||||||
|
|
||||||
|
install_ok=0
|
||||||
|
case "$ID" in
|
||||||
|
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
|
||||||
|
rhel|rocky|almalinux|centos)
|
||||||
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
|
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
|
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||||
|
install_ok=1
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
|
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||||
|
install_ok=1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
# ------------------------------ Ubuntu ----------------------------------------------
|
||||||
|
ubuntu)
|
||||||
|
sudo apt-get update
|
||||||
|
# Ensure 'universe' (Ubuntu) to get 'rpm'
|
||||||
|
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
|
||||||
|
sudo apt-get -y install software-properties-common || true
|
||||||
|
sudo add-apt-repository -y universe || true
|
||||||
|
sudo apt-get update
|
||||||
|
fi
|
||||||
|
# Base tools + rpm (provides rpmbuild)
|
||||||
|
sudo apt-get -y install curl unzip tar rsync rpm || true
|
||||||
|
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
|
||||||
|
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
|
||||||
|
# rpmbuild presence check
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||||
|
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# .NET SDK 8 (best effort via apt)
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
sudo apt-get -y install dotnet-sdk-8.0 || true
|
||||||
|
sudo apt-get -y install dotnet-sdk-8 || true
|
||||||
|
sudo apt-get -y install dotnet-sdk || true
|
||||||
|
fi
|
||||||
|
install_ok=1
|
||||||
|
;;
|
||||||
|
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
|
||||||
|
debian)
|
||||||
|
sudo apt-get update
|
||||||
|
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
|
||||||
|
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
|
||||||
|
# rpmbuild presence check
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||||
|
echo " Please ensure 'rpm' is available from Debian repos."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
|
||||||
|
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
|
||||||
|
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
|
||||||
|
export DOTNET_ROOT="$HOME/.dotnet"
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] dotnet installation failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
install_ok=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$install_ok" -ne 1 ]]; then
|
||||||
|
echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:"
|
||||||
|
echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v curl >/dev/null
|
||||||
|
|
||||||
|
# Root directory = the script's location
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Git submodules (best effort)
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Locate project ================================================================
|
||||||
|
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
|
if [[ ! -f "$PROJECT" ]]; then
|
||||||
|
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||||
|
fi
|
||||||
|
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||||
|
|
||||||
|
# ===== Resolve GUI version & auto checkout ============================================
|
||||||
|
VERSION=""
|
||||||
|
|
||||||
|
choose_channel() {
|
||||||
|
# If --buildfrom provided, map it directly and skip interaction.
|
||||||
|
if [[ -n "${BUILD_FROM:-}" ]]; then
|
||||||
|
case "$BUILD_FROM" in
|
||||||
|
1) echo "latest"; return 0;;
|
||||||
|
2) echo "prerelease"; return 0;;
|
||||||
|
3) echo "keep"; return 0;;
|
||||||
|
*) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
|
||||||
|
local ch="latest" sel=""
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
echo "[?] Choose v2rayN release channel:" >&2
|
||||||
|
echo " 1) Latest (stable) [default]" >&2
|
||||||
|
echo " 2) Pre-release (preview)" >&2
|
||||||
|
echo " 3) Keep current (do nothing)" >&2
|
||||||
|
printf "Enter 1, 2 or 3 [default 1]: " >&2
|
||||||
|
if read -r sel </dev/tty; then
|
||||||
|
case "${sel:-}" in
|
||||||
|
2) ch="prerelease" ;;
|
||||||
|
3) ch="keep" ;;
|
||||||
|
*) ch="latest" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
ch="latest"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ch="latest"
|
||||||
|
fi
|
||||||
|
echo "$ch"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_latest() {
|
||||||
|
# Resolve /releases/latest → tag_name
|
||||||
|
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
|
||||||
|
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
|
||||||
|
| head -n1 \
|
||||||
|
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_prerelease() {
|
||||||
|
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
|
||||||
|
local json tag
|
||||||
|
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
|
||||||
|
|
||||||
|
# 1) Use jq if present
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
tag="$(printf '%s' "$json" \
|
||||||
|
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
|
||||||
|
| sed 's/^v//')" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Fallback to sed/grep only
|
||||||
|
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
|
||||||
|
tag="$(printf '%s' "$json" \
|
||||||
|
| tr '\n' ' ' \
|
||||||
|
| sed 's/},[[:space:]]*{/\n/g' \
|
||||||
|
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
|
||||||
|
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
|
||||||
|
| head -n1 \
|
||||||
|
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
|
||||||
|
printf '%s\n' "$tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
git_try_checkout() {
|
||||||
|
# Try a series of refs and checkout when found.
|
||||||
|
local want="$1" ref=""
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
git fetch --tags --force --prune --depth=1 || true
|
||||||
|
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
|
||||||
|
ref="v${want}"
|
||||||
|
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$ref" ]]; then
|
||||||
|
echo "[OK] Found ref '${ref}', checking out..."
|
||||||
|
git checkout -f "${ref}"
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then
|
||||||
|
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
|
||||||
|
if git_try_checkout "${VERSION_ARG#v}"; then
|
||||||
|
VERSION="${VERSION_ARG#v}"
|
||||||
|
else
|
||||||
|
echo "[WARN] Tag '${VERSION_ARG}' not found."
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
if [[ "$ch" == "keep" ]]; then
|
||||||
|
echo "[*] Keep current repository state (no checkout)."
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
else
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
if [[ -z "$tag" ]]; then
|
||||||
|
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
if [[ "$ch" == "keep" ]]; then
|
||||||
|
echo "[*] Keep current repository state (no checkout)."
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
else
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
if [[ -z "$tag" ]]; then
|
||||||
|
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
|
||||||
|
VERSION="${VERSION_ARG:-}"
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
fi
|
||||||
|
echo "[*] GUI version resolved as: ${VERSION}"
|
||||||
|
|
||||||
|
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
|
||||||
|
download_xray() {
|
||||||
|
# Download Xray core and install to outdir/xray
|
||||||
|
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
|
||||||
|
else
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Download xray: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$zipname"
|
||||||
|
unzip -q "$tmp/$zipname" -d "$tmp"
|
||||||
|
install -Dm755 "$tmp/xray" "$outdir/xray"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_singbox() {
|
||||||
|
# Download sing-box core and install to outdir/sing-box
|
||||||
|
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
|
||||||
|
else
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
|
||||||
|
fi
|
||||||
|
echo "[+] Download sing-box: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$tarname"
|
||||||
|
tar -C "$tmp" -xzf "$tmp/$tarname"
|
||||||
|
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
|
||||||
|
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
|
||||||
|
install -Dm755 "$bin" "$outdir/sing-box"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
|
||||||
|
download_mihomo() {
|
||||||
|
# Download mihomo into outroot/bin/mihomo/mihomo
|
||||||
|
local outroot="$1"
|
||||||
|
local url=""
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
|
||||||
|
else
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
|
||||||
|
fi
|
||||||
|
echo "[+] Download mihomo: $url"
|
||||||
|
mkdir -p "$outroot/bin/mihomo"
|
||||||
|
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
|
||||||
|
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Move geo files to a unified path: outroot/bin/xray/
|
||||||
|
unify_geo_layout() {
|
||||||
|
local outroot="$1"
|
||||||
|
mkdir -p "$outroot/bin/xray"
|
||||||
|
local srcs=( \
|
||||||
|
"$outroot/bin/geosite.dat" \
|
||||||
|
"$outroot/bin/geoip.dat" \
|
||||||
|
"$outroot/bin/geoip-only-cn-private.dat" \
|
||||||
|
"$outroot/bin/Country.mmdb" \
|
||||||
|
"$outroot/bin/geoip.metadb" \
|
||||||
|
)
|
||||||
|
for s in "${srcs[@]}"; do
|
||||||
|
if [[ -f "$s" ]]; then
|
||||||
|
mv -f "$s" "$outroot/bin/xray/$(basename "$s")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download geo/rule assets; then unify to bin/xray/
|
||||||
|
download_geo_assets() {
|
||||||
|
local outroot="$1"
|
||||||
|
local bin_dir="$outroot/bin"
|
||||||
|
local srss_dir="$bin_dir/srss"
|
||||||
|
mkdir -p "$bin_dir" "$srss_dir"
|
||||||
|
|
||||||
|
echo "[+] Download Xray Geo to ${bin_dir}"
|
||||||
|
curl -fsSL -o "$bin_dir/geosite.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/Country.mmdb" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
|
||||||
|
|
||||||
|
echo "[+] Download sing-box rule DB & rule-sets"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.metadb" \
|
||||||
|
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
|
||||||
|
|
||||||
|
for f in \
|
||||||
|
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
|
||||||
|
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
|
||||||
|
done
|
||||||
|
for f in \
|
||||||
|
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
|
||||||
|
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Unify to bin/xray/
|
||||||
|
unify_geo_layout "$outroot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prefer the prebuilt v2rayN core bundle; then unify geo layout
|
||||||
|
download_v2rayn_bundle() {
|
||||||
|
local outroot="$1"
|
||||||
|
local url=""
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip"
|
||||||
|
else
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Try v2rayN bundle archive: $url"
|
||||||
|
local tmp zipname
|
||||||
|
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
|
||||||
|
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
|
||||||
|
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
|
||||||
|
|
||||||
|
if [[ -d "$tmp/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$tmp/bin/" "$outroot/bin/"
|
||||||
|
else
|
||||||
|
rsync -a "$tmp/" "$outroot/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||||
|
# keep mihomo
|
||||||
|
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
local nested_dir
|
||||||
|
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||||
|
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$nested_dir/bin/" "$outroot/bin/"
|
||||||
|
rm -rf "$nested_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unify to bin/xray/
|
||||||
|
unify_geo_layout "$outroot"
|
||||||
|
|
||||||
|
echo "[+] Bundle extracted to $outroot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Build results collection for --arch all ========================================
|
||||||
|
BUILT_RPMS=() # Will collect absolute paths of built RPMs
|
||||||
|
BUILT_ALL=0 # Flag to know if we should print the final summary
|
||||||
|
|
||||||
|
# ===== Build (single-arch) function ====================================================
|
||||||
|
build_for_arch() {
|
||||||
|
# $1: target short arch: x64 | arm64
|
||||||
|
local short="$1"
|
||||||
|
local rid rpm_target archdir
|
||||||
|
case "$short" in
|
||||||
|
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;;
|
||||||
|
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;;
|
||||||
|
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
|
||||||
|
|
||||||
|
# .NET publish (self-contained) for this RID
|
||||||
|
dotnet clean "$PROJECT" -c Release
|
||||||
|
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
|
||||||
|
|
||||||
|
dotnet restore "$PROJECT"
|
||||||
|
dotnet publish "$PROJECT" \
|
||||||
|
-c Release -r "$rid" \
|
||||||
|
-p:PublishSingleFile=false \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true
|
||||||
|
|
||||||
|
# Per-arch variables (scoped)
|
||||||
|
local RID_DIR="$rid"
|
||||||
|
local PUBDIR
|
||||||
|
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
|
||||||
|
[[ -d "$PUBDIR" ]]
|
||||||
|
|
||||||
|
# Make RID_DIR visible to download helpers (they read this var)
|
||||||
|
export RID_DIR
|
||||||
|
|
||||||
|
# Per-arch working area
|
||||||
|
local PKGROOT="v2rayN-publish"
|
||||||
|
local WORKDIR
|
||||||
|
WORKDIR="$(mktemp -d)"
|
||||||
|
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
|
||||||
|
|
||||||
|
# rpmbuild topdir selection
|
||||||
|
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
|
||||||
|
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
|
||||||
|
rpmdev-setuptree
|
||||||
|
TOPDIR="${HOME}/rpmbuild"
|
||||||
|
SPECDIR="${TOPDIR}/SPECS"
|
||||||
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
|
USE_TOPDIR_DEFINE=0
|
||||||
|
else
|
||||||
|
TOPDIR="${WORKDIR}/rpmbuild"
|
||||||
|
SPECDIR="${TOPDIR}/SPECS}"
|
||||||
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
|
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
|
||||||
|
USE_TOPDIR_DEFINE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage publish content
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT"
|
||||||
|
cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/"
|
||||||
|
|
||||||
|
# Optional icon
|
||||||
|
local ICON_CANDIDATE
|
||||||
|
ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png"
|
||||||
|
[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true
|
||||||
|
|
||||||
|
# Prepare bin structure
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
|
||||||
|
|
||||||
|
# Bundle / cores per-arch
|
||||||
|
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
|
||||||
|
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
|
||||||
|
echo "[*] Using v2rayN bundle archive."
|
||||||
|
else
|
||||||
|
echo "[*] Bundle failed, fallback to separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[*] --netcore specified: use separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
||||||
|
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tarball
|
||||||
|
mkdir -p "$SOURCEDIR"
|
||||||
|
tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT"
|
||||||
|
|
||||||
|
# SPEC
|
||||||
|
local SPECFILE="$SPECDIR/v2rayN.spec"
|
||||||
|
mkdir -p "$SPECDIR"
|
||||||
|
cat > "$SPECFILE" <<'SPEC'
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%undefine _debuginfo_subpackages
|
||||||
|
%undefine _debugsource_packages
|
||||||
|
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
|
||||||
|
%global __requires_exclude ^liblttng-ust\.so\..*$
|
||||||
|
|
||||||
|
Name: v2rayN
|
||||||
|
Version: __VERSION__
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
|
||||||
|
License: GPL-3.0-only
|
||||||
|
URL: https://github.com/2dust/v2rayN
|
||||||
|
BugURL: https://github.com/2dust/v2rayN/issues
|
||||||
|
ExclusiveArch: aarch64 x86_64
|
||||||
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
|
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
||||||
|
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
|
||||||
|
|
||||||
|
%description
|
||||||
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
|
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
|
||||||
|
Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS
|
||||||
|
For more information, Please visit our website
|
||||||
|
https://github.com/2dust/v2rayN
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -n __PKGROOT__
|
||||||
|
|
||||||
|
%build
|
||||||
|
# no build
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
|
# Launcher (prefer native ELF first, then DLL fallback; also create Geo symlinks for the user)
|
||||||
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
|
#!/usr/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
DIR="/opt/v2rayN"
|
||||||
|
|
||||||
|
# --- Symlink GEO files into user's XDG dir (first-run convenience) ---
|
||||||
|
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||||
|
USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin"
|
||||||
|
SYS_XRAY_DIR="$DIR/bin/xray"
|
||||||
|
mkdir -p "$USR_GEO_DIR"
|
||||||
|
for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do
|
||||||
|
if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then
|
||||||
|
ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# --- end GEO ---
|
||||||
|
|
||||||
|
# Prefer native apphost
|
||||||
|
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
||||||
|
|
||||||
|
# DLL fallback
|
||||||
|
for dll in v2rayN.Desktop.dll v2rayN.dll; do
|
||||||
|
if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "v2rayN launcher: no executable found in $DIR" >&2
|
||||||
|
ls -l "$DIR" >&2 || true
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
chmod 0755 %{buildroot}%{_bindir}/v2rayn
|
||||||
|
|
||||||
|
# Desktop file
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/applications
|
||||||
|
cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=v2rayN for Red Hat Enterprise Linux
|
||||||
|
Exec=v2rayn
|
||||||
|
Icon=v2rayn
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Icon
|
||||||
|
if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
|
||||||
|
install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
fi
|
||||||
|
|
||||||
|
%post
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%postun
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%files
|
||||||
|
%{_bindir}/v2rayn
|
||||||
|
/opt/v2rayN
|
||||||
|
%{_datadir}/applications/v2rayn.desktop
|
||||||
|
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
SPEC
|
||||||
|
|
||||||
|
# Autostart injection (inside %install) and %files entry
|
||||||
|
if [[ "$AUTOSTART" -eq 1 ]]; then
|
||||||
|
awk '
|
||||||
|
BEGIN{ins=0}
|
||||||
|
/^%post$/ && !ins {
|
||||||
|
print "# --- Autostart (.desktop) ---"
|
||||||
|
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
|
||||||
|
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
|
||||||
|
print "[Desktop Entry]"
|
||||||
|
print "Type=Application"
|
||||||
|
print "Name=v2rayN (Autostart)"
|
||||||
|
print "Exec=v2rayn"
|
||||||
|
print "X-GNOME-Autostart-enabled=true"
|
||||||
|
print "NoDisplay=false"
|
||||||
|
print "EOF"
|
||||||
|
ins=1
|
||||||
|
}
|
||||||
|
{print}
|
||||||
|
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||||
|
|
||||||
|
awk '
|
||||||
|
BEGIN{infiles=0; done=0}
|
||||||
|
/^%files$/ {infiles=1}
|
||||||
|
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
|
||||||
|
print
|
||||||
|
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
|
||||||
|
done=1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{print}
|
||||||
|
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace placeholders
|
||||||
|
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
||||||
|
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
||||||
|
|
||||||
|
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
|
||||||
|
# NOTE: We define only __strip to point to the target-arch strip.
|
||||||
|
# DO NOT override __brp_strip (it must stay the brp script path).
|
||||||
|
local STRIP_ARGS=()
|
||||||
|
if [[ "$ID" == "ubuntu" ]]; then
|
||||||
|
local STRIP_BIN=""
|
||||||
|
if [[ "$short" == "x64" ]]; then
|
||||||
|
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
|
||||||
|
else
|
||||||
|
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
|
||||||
|
fi
|
||||||
|
if [[ -x "$STRIP_BIN" ]]; then
|
||||||
|
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build RPM for this arch (force rpm --target to match compile arch)
|
||||||
|
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||||
|
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||||
|
else
|
||||||
|
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
|
||||||
|
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||||
|
mkdir -p "$HOME/rpmbuild"
|
||||||
|
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
|
||||||
|
TOPDIR="$HOME/rpmbuild"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build done for $short. RPM at:"
|
||||||
|
local f
|
||||||
|
for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
echo " $f"
|
||||||
|
BUILT_RPMS+=("$f")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Arch selection and build orchestration =========================================
|
||||||
|
case "${ARCH_OVERRIDE:-}" in
|
||||||
|
"")
|
||||||
|
# No --arch: use host architecture
|
||||||
|
if [[ "$host_arch" == "aarch64" ]]; then
|
||||||
|
build_for_arch arm64
|
||||||
|
else
|
||||||
|
build_for_arch x64
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
x64|amd64)
|
||||||
|
build_for_arch x64
|
||||||
|
;;
|
||||||
|
arm64|aarch64)
|
||||||
|
build_for_arch arm64
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
BUILT_ALL=1
|
||||||
|
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
|
||||||
|
build_for_arch x64
|
||||||
|
build_for_arch arm64
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ===== Final summary if building both arches ==========================================
|
||||||
|
if [[ "$BUILT_ALL" -eq 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "================ Build Summary (both architectures) ================"
|
||||||
|
if [[ "${#BUILT_RPMS[@]}" -gt 0 ]]; then
|
||||||
|
for rp in "${BUILT_RPMS[@]}"; do
|
||||||
|
echo "$rp"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "[WARN] No RPMs detected in summary (check build logs above)."
|
||||||
|
fi
|
||||||
|
echo "==================================================================="
|
||||||
|
fi
|
||||||
@@ -79,15 +79,7 @@ internal class UpgradeApp
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
TryExtractToFile(entry, entryOutputPath);
|
||||||
{
|
|
||||||
entry.ExtractToFile(entryOutputPath, true);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Thread.Sleep(1000);
|
|
||||||
entry.ExtractToFile(entryOutputPath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine(entryOutputPath);
|
Console.WriteLine(entryOutputPath);
|
||||||
}
|
}
|
||||||
@@ -113,4 +105,24 @@ internal class UpgradeApp
|
|||||||
|
|
||||||
Utils.StartV2RayN();
|
Utils.StartV2RayN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryExtractToFile(ZipArchiveEntry entry, string outputPath)
|
||||||
|
{
|
||||||
|
var retryCount = 5;
|
||||||
|
var delayMs = 1000;
|
||||||
|
|
||||||
|
for (var i = 1; i <= retryCount; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
entry.ExtractToFile(outputPath, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Thread.Sleep(delayMs * i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.13.7</Version>
|
<Version>7.14.7</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.3" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.3" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.3" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.5" />
|
||||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.3" />
|
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.5" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||||
<PackageVersion Include="Downloader" Version="4.0.2" />
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||||
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
|
||||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
|
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
||||||
<PackageVersion Include="Splat.NLog" Version="15.4.1" />
|
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
|
||||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
|||||||
Submodule v2rayN/GlobalHotKeys updated: ef73fa22c4...270f023fcc
@@ -4,7 +4,7 @@ using ZXing.SkiaSharp;
|
|||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
public class QRCodeHelper
|
public class QRCodeUtils
|
||||||
{
|
{
|
||||||
public static byte[]? GenQRCode(string? url)
|
public static byte[]? GenQRCode(string? url)
|
||||||
{
|
{
|
||||||
@@ -15,5 +15,6 @@ public enum ECoreType
|
|||||||
brook = 27,
|
brook = 27,
|
||||||
overtls = 28,
|
overtls = 28,
|
||||||
shadowquic = 29,
|
shadowquic = 29,
|
||||||
|
mieru = 30,
|
||||||
v2rayN = 99
|
v2rayN = 99
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace ServiceLib.Enums;
|
|
||||||
|
|
||||||
public enum EMsgCommand
|
|
||||||
{
|
|
||||||
ClearMsg,
|
|
||||||
SendMsgView,
|
|
||||||
SendSnackMsg,
|
|
||||||
RefreshProfiles,
|
|
||||||
AppExit
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ public enum EViewAction
|
|||||||
ShowYesNo,
|
ShowYesNo,
|
||||||
SaveFileDialog,
|
SaveFileDialog,
|
||||||
AddBatchRoutingRulesYesNo,
|
AddBatchRoutingRulesYesNo,
|
||||||
AdjustMainLvColWidth,
|
|
||||||
SetClipboardData,
|
SetClipboardData,
|
||||||
AddServerViaClipboard,
|
AddServerViaClipboard,
|
||||||
ImportRulesFromClipboard,
|
ImportRulesFromClipboard,
|
||||||
@@ -16,7 +15,6 @@ public enum EViewAction
|
|||||||
ShowHideWindow,
|
ShowHideWindow,
|
||||||
ScanScreenTask,
|
ScanScreenTask,
|
||||||
ScanImageTask,
|
ScanImageTask,
|
||||||
Shutdown,
|
|
||||||
BrowseServer,
|
BrowseServer,
|
||||||
ImportRulesFromFile,
|
ImportRulesFromFile,
|
||||||
InitSettingFont,
|
InitSettingFont,
|
||||||
@@ -29,18 +27,10 @@ public enum EViewAction
|
|||||||
DNSSettingWindow,
|
DNSSettingWindow,
|
||||||
RoutingSettingWindow,
|
RoutingSettingWindow,
|
||||||
OptionSettingWindow,
|
OptionSettingWindow,
|
||||||
|
FullConfigTemplateWindow,
|
||||||
GlobalHotkeySettingWindow,
|
GlobalHotkeySettingWindow,
|
||||||
SubSettingWindow,
|
SubSettingWindow,
|
||||||
DispatcherSpeedTest,
|
|
||||||
DispatcherRefreshConnections,
|
|
||||||
DispatcherRefreshProxyGroups,
|
|
||||||
DispatcherProxiesDelayTest,
|
|
||||||
DispatcherStatistics,
|
|
||||||
DispatcherServerAvailability,
|
|
||||||
DispatcherReload,
|
|
||||||
DispatcherRefreshServersBiz,
|
DispatcherRefreshServersBiz,
|
||||||
DispatcherRefreshIcon,
|
DispatcherRefreshIcon,
|
||||||
DispatcherCheckUpdate,
|
|
||||||
DispatcherCheckUpdateFinished,
|
|
||||||
DispatcherShowMsg,
|
DispatcherShowMsg,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class Global
|
|||||||
public const string ProxyTag = "proxy";
|
public const string ProxyTag = "proxy";
|
||||||
public const string DirectTag = "direct";
|
public const string DirectTag = "direct";
|
||||||
public const string BlockTag = "block";
|
public const string BlockTag = "block";
|
||||||
|
public const string DnsTag = "dns-module";
|
||||||
public const string StreamSecurity = "tls";
|
public const string StreamSecurity = "tls";
|
||||||
public const string StreamSecurityReality = "reality";
|
public const string StreamSecurityReality = "reality";
|
||||||
public const string Loopback = "127.0.0.1";
|
public const string Loopback = "127.0.0.1";
|
||||||
@@ -56,6 +57,9 @@ public class Global
|
|||||||
public const string HttpsProtocol = "https://";
|
public const string HttpsProtocol = "https://";
|
||||||
public const string SocksProtocol = "socks://";
|
public const string SocksProtocol = "socks://";
|
||||||
public const string Socks5Protocol = "socks5://";
|
public const string Socks5Protocol = "socks5://";
|
||||||
|
public const string AsIs = "AsIs";
|
||||||
|
public const string IPIfNonMatch = "IPIfNonMatch";
|
||||||
|
public const string IPOnDemand = "IPOnDemand";
|
||||||
|
|
||||||
public const string UserEMail = "t@t.tt";
|
public const string UserEMail = "t@t.tt";
|
||||||
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||||
@@ -285,11 +289,36 @@ public class Global
|
|||||||
"sing_box"
|
"sing_box"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> XraySupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.Hysteria2,
|
||||||
|
EConfigType.TUIC,
|
||||||
|
EConfigType.Anytls,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainStrategies =
|
public static readonly List<string> DomainStrategies =
|
||||||
[
|
[
|
||||||
"AsIs",
|
AsIs,
|
||||||
"IPIfNonMatch",
|
IPIfNonMatch,
|
||||||
"IPOnDemand"
|
IPOnDemand
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainStrategies4Singbox =
|
public static readonly List<string> DomainStrategies4Singbox =
|
||||||
@@ -301,13 +330,6 @@ public class Global
|
|||||||
""
|
""
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainMatchers =
|
|
||||||
[
|
|
||||||
"linear",
|
|
||||||
"mph",
|
|
||||||
""
|
|
||||||
];
|
|
||||||
|
|
||||||
public static readonly List<string> Fingerprints =
|
public static readonly List<string> Fingerprints =
|
||||||
[
|
[
|
||||||
"chrome",
|
"chrome",
|
||||||
@@ -460,9 +482,11 @@ public class Global
|
|||||||
public static readonly List<int> TunMtus =
|
public static readonly List<int> TunMtus =
|
||||||
[
|
[
|
||||||
1280,
|
1280,
|
||||||
1408,
|
1408,
|
||||||
1500,
|
1500,
|
||||||
9000
|
4064,
|
||||||
|
9000,
|
||||||
|
65535
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> TunStacks =
|
public static readonly List<string> TunStacks =
|
||||||
@@ -536,6 +560,7 @@ public class Global
|
|||||||
{ ECoreType.brook, "txthinking/brook" },
|
{ ECoreType.brook, "txthinking/brook" },
|
||||||
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
||||||
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
||||||
|
{ ECoreType.mieru, "enfein/mieru" },
|
||||||
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
global using ServiceLib.Base;
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
global using ServiceLib.Enums;
|
||||||
global using ServiceLib.Handler;
|
global using ServiceLib.Handler;
|
||||||
|
global using ServiceLib.Helper;
|
||||||
|
global using ServiceLib.Manager;
|
||||||
global using ServiceLib.Handler.Fmt;
|
global using ServiceLib.Handler.Fmt;
|
||||||
global using ServiceLib.Services;
|
global using ServiceLib.Services;
|
||||||
global using ServiceLib.Services.Statistics;
|
global using ServiceLib.Services.Statistics;
|
||||||
global using ServiceLib.Services.CoreConfig;
|
global using ServiceLib.Services.CoreConfig;
|
||||||
global using ServiceLib.Models;
|
global using ServiceLib.Models;
|
||||||
global using ServiceLib.Resx;
|
global using ServiceLib.Resx;
|
||||||
global using ServiceLib.Handler.SysProxy;
|
global using ServiceLib.Handler.SysProxy;
|
||||||
|
|||||||
21
v2rayN/ServiceLib/Handler/AppEvents.cs
Normal file
21
v2rayN/ServiceLib/Handler/AppEvents.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
|
||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
public static class AppEvents
|
||||||
|
{
|
||||||
|
public static readonly Subject<Unit> ProfilesRefreshRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<string> SendSnackMsgRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<string> SendMsgViewRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<Unit> AppExitRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<bool> ShutdownRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new();
|
||||||
|
|
||||||
|
public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
public class ConfigHandler
|
public static class ConfigHandler
|
||||||
{
|
{
|
||||||
private static readonly string _configRes = Global.ConfigFileName;
|
private static readonly string _configRes = Global.ConfigFileName;
|
||||||
private static readonly string _tag = "ConfigHandler";
|
private static readonly string _tag = "ConfigHandler";
|
||||||
@@ -216,7 +216,7 @@ public class ConfigHandler
|
|||||||
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
|
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
|
||||||
public static async Task<int> AddServer(Config config, ProfileItem profileItem)
|
public static async Task<int> AddServer(Config config, ProfileItem profileItem)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
item = profileItem;
|
item = profileItem;
|
||||||
@@ -336,7 +336,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
foreach (var it in indexes)
|
foreach (var it in indexes)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(it.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -418,7 +418,7 @@ public class ConfigHandler
|
|||||||
/// <returns>The default profile item or null if none exists</returns>
|
/// <returns>The default profile item or null if none exists</returns>
|
||||||
public static async Task<ProfileItem?> GetDefaultServer(Config config)
|
public static async Task<ProfileItem?> GetDefaultServer(Config config)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(config.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(config.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
|
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
|
||||||
@@ -449,7 +449,7 @@ public class ConfigHandler
|
|||||||
|
|
||||||
for (int i = 0; i < lstProfile.Count; i++)
|
for (int i = 0; i < lstProfile.Count; i++)
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sort = 0;
|
var sort = 0;
|
||||||
@@ -461,7 +461,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1;
|
sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -471,7 +471,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
|
sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -482,7 +482,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
|
sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -492,7 +492,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1;
|
sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -501,7 +501,7 @@ public class ConfigHandler
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort);
|
ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort);
|
||||||
return await Task.FromResult(0);
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
|
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
item = profileItem;
|
item = profileItem;
|
||||||
@@ -601,7 +601,7 @@ public class ConfigHandler
|
|||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Id = profileItem.Id.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
profileItem.Security = profileItem.Security.TrimEx();
|
||||||
|
|
||||||
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
|
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -829,13 +829,13 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
|
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
|
||||||
{
|
{
|
||||||
var lstModel = await AppHandler.Instance.ProfileItems(subId, "");
|
var lstModel = await AppManager.Instance.ProfileItems(subId, "");
|
||||||
if (lstModel.Count <= 0)
|
if (lstModel.Count <= 0)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
|
||||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||||
var lstProfile = (from t in lstModel
|
var lstProfile = (from t in lstModel
|
||||||
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
||||||
from t22 in t2b.DefaultIfEmpty()
|
from t22 in t2b.DefaultIfEmpty()
|
||||||
@@ -905,7 +905,7 @@ public class ConfigHandler
|
|||||||
|
|
||||||
for (var i = 0; i < lstProfile.Count; i++)
|
for (var i = 0; i < lstProfile.Count; i++)
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||||
}
|
}
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
@@ -914,7 +914,7 @@ public class ConfigHandler
|
|||||||
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
||||||
foreach (var item in lstProfile.Where(item => item.Delay <= 0))
|
foreach (var item in lstProfile.Where(item => item.Delay <= 0))
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
|
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -924,7 +924,7 @@ public class ConfigHandler
|
|||||||
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
var maxSort = lstProfile.Max(t => t.Sort) + 10;
|
||||||
foreach (var item in lstProfile.Where(item => item.Speed <= 0))
|
foreach (var item in lstProfile.Where(item => item.Speed <= 0))
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
|
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -963,7 +963,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (profileItem.Security.IsNotEmpty() && profileItem.Security != Global.None)
|
if (profileItem.Security.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profileItem.Security = Global.None;
|
profileItem.Security = Global.None;
|
||||||
}
|
}
|
||||||
@@ -982,7 +982,7 @@ public class ConfigHandler
|
|||||||
/// <returns>Tuple with total count and remaining count after deduplication</returns>
|
/// <returns>Tuple with total count and remaining count after deduplication</returns>
|
||||||
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
|
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
|
||||||
{
|
{
|
||||||
var lstProfile = await AppHandler.Instance.ProfileItems(subId);
|
var lstProfile = await AppManager.Instance.ProfileItems(subId);
|
||||||
if (lstProfile == null)
|
if (lstProfile == null)
|
||||||
{
|
{
|
||||||
return new Tuple<int, int>(0, 0);
|
return new Tuple<int, int>(0, 0);
|
||||||
@@ -1052,15 +1052,15 @@ public class ConfigHandler
|
|||||||
if (profileItem.IndexId.IsNullOrEmpty())
|
if (profileItem.IndexId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profileItem.IndexId = Utils.GetGuid(false);
|
profileItem.IndexId = Utils.GetGuid(false);
|
||||||
maxSort = ProfileExHandler.Instance.GetMaxSort();
|
maxSort = ProfileExManager.Instance.GetMaxSort();
|
||||||
}
|
}
|
||||||
if (!toFile && maxSort < 0)
|
if (!toFile && maxSort < 0)
|
||||||
{
|
{
|
||||||
maxSort = ProfileExHandler.Instance.GetMaxSort();
|
maxSort = ProfileExManager.Instance.GetMaxSort();
|
||||||
}
|
}
|
||||||
if (maxSort > 0)
|
if (maxSort > 0)
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toFile)
|
if (toFile)
|
||||||
@@ -1120,7 +1120,7 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(indexId);
|
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1165,7 +1165,7 @@ public class ConfigHandler
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
|
var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
|
||||||
profileItem.IndexId = indexId;
|
profileItem.IndexId = indexId;
|
||||||
if (coreType == ECoreType.Xray)
|
if (coreType == ECoreType.Xray)
|
||||||
{
|
{
|
||||||
@@ -1211,7 +1211,7 @@ public class ConfigHandler
|
|||||||
ConfigType = EConfigType.SOCKS,
|
ConfigType = EConfigType.SOCKS,
|
||||||
Address = Global.Loopback,
|
Address = Global.Loopback,
|
||||||
Sni = node.Address, //Tun2SocksAddress
|
Sni = node.Address, //Tun2SocksAddress
|
||||||
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)
|
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
|
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
|
||||||
@@ -1238,12 +1238,12 @@ public class ConfigHandler
|
|||||||
/// <returns>Number of removed servers or -1 if failed</returns>
|
/// <returns>Number of removed servers or -1 if failed</returns>
|
||||||
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
||||||
{
|
{
|
||||||
var lstModel = await AppHandler.Instance.ProfileItems(subid, "");
|
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
|
||||||
if (lstModel is { Count: <= 0 })
|
if (lstModel is { Count: <= 0 })
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||||
var lstProfile = (from t in lstModel
|
var lstProfile = (from t in lstModel
|
||||||
join t2 in lstProfileExs on t.IndexId equals t2.IndexId
|
join t2 in lstProfileExs on t.IndexId equals t2.IndexId
|
||||||
where t2.Delay == -1
|
where t2.Delay == -1
|
||||||
@@ -1279,7 +1279,7 @@ public class ConfigHandler
|
|||||||
if (isSub && subid.IsNotEmpty())
|
if (isSub && subid.IsNotEmpty())
|
||||||
{
|
{
|
||||||
await RemoveServersViaSubid(config, subid, isSub);
|
await RemoveServersViaSubid(config, subid, isSub);
|
||||||
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? "";
|
subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var countServers = 0;
|
var countServers = 0;
|
||||||
@@ -1363,7 +1363,7 @@ public class ConfigHandler
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subItem = await AppHandler.Instance.GetSubItem(subid);
|
var subItem = await AppManager.Instance.GetSubItem(subid);
|
||||||
var subRemarks = subItem?.Remarks;
|
var subRemarks = subItem?.Remarks;
|
||||||
var preSocksPort = subItem?.PreSocksPort;
|
var preSocksPort = subItem?.PreSocksPort;
|
||||||
|
|
||||||
@@ -1412,6 +1412,11 @@ public class ConfigHandler
|
|||||||
{
|
{
|
||||||
profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
|
profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
|
||||||
}
|
}
|
||||||
|
//Is Html Page
|
||||||
|
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
//Is Clash configuration
|
//Is Clash configuration
|
||||||
if (profileItem is null)
|
if (profileItem is null)
|
||||||
{
|
{
|
||||||
@@ -1514,7 +1519,7 @@ public class ConfigHandler
|
|||||||
ProfileItem? activeProfile = null;
|
ProfileItem? activeProfile = null;
|
||||||
if (isSub && subid.IsNotEmpty())
|
if (isSub && subid.IsNotEmpty())
|
||||||
{
|
{
|
||||||
lstOriSub = await AppHandler.Instance.ProfileItems(subid);
|
lstOriSub = await AppManager.Instance.ProfileItems(subid);
|
||||||
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
|
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1546,7 +1551,7 @@ public class ConfigHandler
|
|||||||
//Select active node
|
//Select active node
|
||||||
if (activeProfile != null)
|
if (activeProfile != null)
|
||||||
{
|
{
|
||||||
var lstSub = await AppHandler.Instance.ProfileItems(subid);
|
var lstSub = await AppManager.Instance.ProfileItems(subid);
|
||||||
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
|
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
|
||||||
if (existItem != null)
|
if (existItem != null)
|
||||||
{
|
{
|
||||||
@@ -1557,13 +1562,13 @@ public class ConfigHandler
|
|||||||
//Keep the last traffic statistics
|
//Keep the last traffic statistics
|
||||||
if (lstOriSub != null)
|
if (lstOriSub != null)
|
||||||
{
|
{
|
||||||
var lstSub = await AppHandler.Instance.ProfileItems(subid);
|
var lstSub = await AppManager.Instance.ProfileItems(subid);
|
||||||
foreach (var item in lstSub)
|
foreach (var item in lstSub)
|
||||||
{
|
{
|
||||||
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
|
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
|
||||||
if (existItem != null)
|
if (existItem != null)
|
||||||
{
|
{
|
||||||
await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
|
await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1603,7 +1608,7 @@ public class ConfigHandler
|
|||||||
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
||||||
{
|
{
|
||||||
//TODO Temporary reminder to be removed later
|
//TODO Temporary reminder to be removed later
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||||
//return -1;
|
//return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1621,7 +1626,7 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> AddSubItem(Config config, SubItem subItem)
|
public static async Task<int> AddSubItem(Config config, SubItem subItem)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetSubItem(subItem.Id);
|
var item = await AppManager.Instance.GetSubItem(subItem.Id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
item = subItem;
|
item = subItem;
|
||||||
@@ -1653,7 +1658,7 @@ public class ConfigHandler
|
|||||||
var maxSort = 0;
|
var maxSort = 0;
|
||||||
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
|
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
|
||||||
{
|
{
|
||||||
var lstSubs = (await AppHandler.Instance.SubItems());
|
var lstSubs = (await AppManager.Instance.SubItems());
|
||||||
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
|
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
|
||||||
}
|
}
|
||||||
item.Sort = maxSort + 1;
|
item.Sort = maxSort + 1;
|
||||||
@@ -1707,7 +1712,7 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful</returns>
|
/// <returns>0 if successful</returns>
|
||||||
public static async Task<int> DeleteSubItem(Config config, string id)
|
public static async Task<int> DeleteSubItem(Config config, string id)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetSubItem(id);
|
var item = await AppManager.Instance.GetSubItem(id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1891,7 +1896,7 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful</returns>
|
/// <returns>0 if successful</returns>
|
||||||
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
|
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
|
||||||
{
|
{
|
||||||
var items = await AppHandler.Instance.RoutingItems();
|
var items = await AppManager.Instance.RoutingItems();
|
||||||
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
|
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1971,7 +1976,7 @@ public class ConfigHandler
|
|||||||
if (template == null)
|
if (template == null)
|
||||||
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
||||||
|
|
||||||
var items = await AppHandler.Instance.RoutingItems();
|
var items = await AppManager.Instance.RoutingItems();
|
||||||
var maxSort = items.Count;
|
var maxSort = items.Count;
|
||||||
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
|
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
|
||||||
{
|
{
|
||||||
@@ -2018,14 +2023,14 @@ public class ConfigHandler
|
|||||||
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
|
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
|
||||||
{
|
{
|
||||||
var ver = "V3-";
|
var ver = "V3-";
|
||||||
var items = await AppHandler.Instance.RoutingItems();
|
var items = await AppManager.Instance.RoutingItems();
|
||||||
|
|
||||||
//TODO Temporary code to be removed later
|
//TODO Temporary code to be removed later
|
||||||
var lockItem = items?.FirstOrDefault(t => t.Locked == true);
|
var lockItem = items?.FirstOrDefault(t => t.Locked == true);
|
||||||
if (lockItem != null)
|
if (lockItem != null)
|
||||||
{
|
{
|
||||||
await ConfigHandler.RemoveRoutingItem(lockItem);
|
await ConfigHandler.RemoveRoutingItem(lockItem);
|
||||||
items = await AppHandler.Instance.RoutingItems();
|
items = await AppManager.Instance.RoutingItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!blImportAdvancedRules && items.Count > 0)
|
if (!blImportAdvancedRules && items.Count > 0)
|
||||||
@@ -2102,7 +2107,7 @@ public class ConfigHandler
|
|||||||
/// <returns>0 if successful</returns>
|
/// <returns>0 if successful</returns>
|
||||||
public static async Task<int> InitBuiltinDNS(Config config)
|
public static async Task<int> InitBuiltinDNS(Config config)
|
||||||
{
|
{
|
||||||
var items = await AppHandler.Instance.DNSItems();
|
var items = await AppManager.Instance.DNSItems();
|
||||||
|
|
||||||
// Check existing DNS items and disable those with empty NormalDNS
|
// Check existing DNS items and disable those with empty NormalDNS
|
||||||
var needsUpdate = false;
|
var needsUpdate = false;
|
||||||
@@ -2180,7 +2185,7 @@ public class ConfigHandler
|
|||||||
/// <returns>DNS item with configuration from the URL</returns>
|
/// <returns>DNS item with configuration from the URL</returns>
|
||||||
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
|
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
|
||||||
{
|
{
|
||||||
var currentItem = await AppHandler.Instance.GetDNSItem(type);
|
var currentItem = await AppManager.Instance.GetDNSItem(type);
|
||||||
|
|
||||||
var downloadHandle = new DownloadService();
|
var downloadHandle = new DownloadService();
|
||||||
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
||||||
@@ -2238,6 +2243,55 @@ public class ConfigHandler
|
|||||||
|
|
||||||
#endregion Simple DNS
|
#endregion Simple DNS
|
||||||
|
|
||||||
|
#region Custom Config
|
||||||
|
|
||||||
|
public static async Task<int> InitBuiltinFullConfigTemplate(Config config)
|
||||||
|
{
|
||||||
|
var items = await AppManager.Instance.FullConfigTemplateItem();
|
||||||
|
if (items.Count <= 0)
|
||||||
|
{
|
||||||
|
var item = new FullConfigTemplateItem()
|
||||||
|
{
|
||||||
|
Remarks = "V2ray",
|
||||||
|
CoreType = ECoreType.Xray,
|
||||||
|
};
|
||||||
|
await SaveFullConfigTemplate(config, item);
|
||||||
|
|
||||||
|
var item2 = new FullConfigTemplateItem()
|
||||||
|
{
|
||||||
|
Remarks = "sing-box",
|
||||||
|
CoreType = ECoreType.sing_box,
|
||||||
|
};
|
||||||
|
await SaveFullConfigTemplate(config, item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> SaveFullConfigTemplate(Config config, FullConfigTemplateItem item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Id.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
item.Id = Utils.GetGuid(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Custom Config
|
||||||
|
|
||||||
#region Regional Presets
|
#region Regional Presets
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2267,10 +2321,22 @@ public class ConfigHandler
|
|||||||
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
|
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
|
||||||
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
|
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
|
||||||
|
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json"));
|
var xrayDnsRussia = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json");
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json"));
|
var singboxDnsRussia = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json");
|
||||||
|
var simpleDnsRussia = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json");
|
||||||
|
|
||||||
config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json") ?? InitBuiltinSimpleDNS();
|
if (simpleDnsRussia == null)
|
||||||
|
{
|
||||||
|
xrayDnsRussia.Enabled = true;
|
||||||
|
singboxDnsRussia.Enabled = true;
|
||||||
|
config.SimpleDNSItem = InitBuiltinSimpleDNS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.SimpleDNSItem = simpleDnsRussia;
|
||||||
|
}
|
||||||
|
await SaveDNSItems(config, xrayDnsRussia);
|
||||||
|
await SaveDNSItems(config, singboxDnsRussia);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EPresetType.Iran:
|
case EPresetType.Iran:
|
||||||
@@ -2278,10 +2344,22 @@ public class ConfigHandler
|
|||||||
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
|
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
|
||||||
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
|
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
|
||||||
|
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json"));
|
var xrayDnsIran = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json");
|
||||||
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json"));
|
var singboxDnsIran = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json");
|
||||||
|
var simpleDnsIran = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json");
|
||||||
|
|
||||||
config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json") ?? InitBuiltinSimpleDNS();
|
if (simpleDnsIran == null)
|
||||||
|
{
|
||||||
|
xrayDnsIran.Enabled = true;
|
||||||
|
singboxDnsIran.Enabled = true;
|
||||||
|
config.SimpleDNSItem = InitBuiltinSimpleDNS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.SimpleDNSItem = simpleDnsIran;
|
||||||
|
}
|
||||||
|
await SaveDNSItems(config, xrayDnsIran);
|
||||||
|
await SaveDNSItems(config, singboxDnsIran);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
public class ConnectionHandler
|
public static class ConnectionHandler
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ConnectionHandler> _instance = new(() => new());
|
private static readonly string _tag = "ConnectionHandler";
|
||||||
public static ConnectionHandler Instance => _instance.Value;
|
|
||||||
|
|
||||||
public async Task<string> RunAvailabilityCheck()
|
public static async Task<string> RunAvailabilityCheck()
|
||||||
{
|
{
|
||||||
var downloadHandle = new DownloadService();
|
var time = await GetRealPingTime();
|
||||||
var time = await downloadHandle.RunAvailabilityCheck(null);
|
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
||||||
var ip = time > 0 ? await GetIPInfo(downloadHandle) ?? Global.None : Global.None;
|
|
||||||
|
|
||||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GetIPInfo(DownloadService downloadHandle)
|
private static async Task<string?> GetIPInfo()
|
||||||
{
|
{
|
||||||
var url = AppHandler.Instance.Config.SpeedTestItem.IPAPIUrl;
|
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||||
if (url.IsNullOrEmpty())
|
if (url.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var downloadHandle = new DownloadService();
|
||||||
var result = await downloadHandle.TryDownloadString(url, true, "");
|
var result = await downloadHandle.TryDownloadString(url, true, "");
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
@@ -39,4 +40,31 @@ public class ConnectionHandler
|
|||||||
|
|
||||||
return $"({country ?? "unknown"}) {ip}";
|
return $"({country ?? "unknown"}) {ip}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<int> GetRealPingTime()
|
||||||
|
{
|
||||||
|
var responseTime = -1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
|
||||||
|
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
|
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
|
||||||
|
if (responseTime > 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ namespace ServiceLib.Handler;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Core configuration file processing class
|
/// Core configuration file processing class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CoreConfigHandler
|
public static class CoreConfigHandler
|
||||||
{
|
{
|
||||||
private static readonly string _tag = "CoreConfigHandler";
|
private static readonly string _tag = "CoreConfigHandler";
|
||||||
|
|
||||||
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
||||||
{
|
{
|
||||||
var config = AppHandler.Instance.Config;
|
var config = AppManager.Instance.Config;
|
||||||
var result = new RetResult();
|
var result = new RetResult();
|
||||||
|
|
||||||
if (node.ConfigType == EConfigType.Custom)
|
if (node.ConfigType == EConfigType.Custom)
|
||||||
@@ -21,7 +21,7 @@ public class CoreConfigHandler
|
|||||||
_ => await GenerateClientCustomConfig(node, fileName)
|
_ => await GenerateClientCustomConfig(node, fileName)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
||||||
}
|
}
|
||||||
@@ -112,11 +112,11 @@ public class CoreConfigHandler
|
|||||||
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
|
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
|
||||||
{
|
{
|
||||||
var result = new RetResult();
|
var result = new RetResult();
|
||||||
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
||||||
testItem.Port = port;
|
testItem.Port = port;
|
||||||
|
|
||||||
if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,14 +220,7 @@ public class BaseFmt
|
|||||||
|
|
||||||
protected static bool Contains(string str, params string[] s)
|
protected static bool Contains(string str, params string[] s)
|
||||||
{
|
{
|
||||||
foreach (var item in s)
|
return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase));
|
||||||
{
|
|
||||||
if (str.Contains(item, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static string WriteAllText(string strData, string ext = "json")
|
protected static string WriteAllText(string strData, string ext = "json")
|
||||||
|
|||||||
9
v2rayN/ServiceLib/Handler/Fmt/HtmlPageFmt.cs
Normal file
9
v2rayN/ServiceLib/Handler/Fmt/HtmlPageFmt.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class HtmlPageFmt : BaseFmt
|
||||||
|
{
|
||||||
|
public static bool IsHtmlPage(string strData)
|
||||||
|
{
|
||||||
|
return Contains(strData, "<html", "<!doctype html", "<head");
|
||||||
|
}
|
||||||
|
}
|
||||||
221
v2rayN/ServiceLib/Handler/SubscriptionHandler.cs
Normal file
221
v2rayN/ServiceLib/Handler/SubscriptionHandler.cs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
public static class SubscriptionHandler
|
||||||
|
{
|
||||||
|
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> updateFunc)
|
||||||
|
{
|
||||||
|
await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
||||||
|
var subItem = await AppManager.Instance.SubItems();
|
||||||
|
|
||||||
|
if (subItem is not { Count: > 0 })
|
||||||
|
{
|
||||||
|
await updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var successCount = 0;
|
||||||
|
foreach (var item in subItem)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsValidSubscription(item, subId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashCode = $"{item.Remarks}->";
|
||||||
|
if (item.Enabled == false)
|
||||||
|
{
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create download handler
|
||||||
|
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
||||||
|
|
||||||
|
// Get all subscription content (main subscription + additional subscriptions)
|
||||||
|
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
|
||||||
|
|
||||||
|
// Process download result
|
||||||
|
if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc))
|
||||||
|
{
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var hashCode = $"{item.Remarks}->";
|
||||||
|
Logging.SaveLog("UpdateSubscription", ex);
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
|
||||||
|
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidSubscription(SubItem item, string subId)
|
||||||
|
{
|
||||||
|
var id = item.Id.TrimEx();
|
||||||
|
var url = item.Url.TrimEx();
|
||||||
|
|
||||||
|
if (id.IsNullOrEmpty() || url.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subId.IsNotEmpty() && item.Id != subId)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DownloadService CreateDownloadHandler(string hashCode, Func<bool, string, Task> updateFunc)
|
||||||
|
{
|
||||||
|
var downloadHandle = new DownloadService();
|
||||||
|
downloadHandle.Error += (sender2, args) =>
|
||||||
|
{
|
||||||
|
updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
|
||||||
|
};
|
||||||
|
return downloadHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent)
|
||||||
|
{
|
||||||
|
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
|
||||||
|
|
||||||
|
// If download with proxy fails, try direct connection
|
||||||
|
if (blProxy && result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
result = await downloadHandle.TryDownloadString(url, false, userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
|
||||||
|
{
|
||||||
|
// Download main subscription content
|
||||||
|
var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle);
|
||||||
|
|
||||||
|
// Process additional subscription links (if any)
|
||||||
|
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
|
||||||
|
{
|
||||||
|
result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
|
||||||
|
{
|
||||||
|
// Prepare subscription URL and download directly
|
||||||
|
var url = Utils.GetPunycode(item.Url.TrimEx());
|
||||||
|
|
||||||
|
// If conversion is needed
|
||||||
|
if (item.ConvertTarget.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty()
|
||||||
|
? Global.SubConvertUrls.FirstOrDefault()
|
||||||
|
: config.ConstItem.SubConvertUrl;
|
||||||
|
|
||||||
|
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
|
||||||
|
|
||||||
|
if (!url.Contains("target="))
|
||||||
|
{
|
||||||
|
url += string.Format("&target={0}", item.ConvertTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.Contains("config="))
|
||||||
|
{
|
||||||
|
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and return result directly
|
||||||
|
return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle)
|
||||||
|
{
|
||||||
|
var result = mainResult;
|
||||||
|
|
||||||
|
// If main subscription result is Base64 encoded, decode it first
|
||||||
|
if (result.IsNotEmpty() && Utils.IsBase64String(result))
|
||||||
|
{
|
||||||
|
result = Utils.Base64Decode(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process additional URL list
|
||||||
|
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
|
||||||
|
foreach (var it in lstUrl)
|
||||||
|
{
|
||||||
|
var url2 = Utils.GetPunycode(it);
|
||||||
|
if (url2.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent);
|
||||||
|
|
||||||
|
if (additionalResult.IsNotEmpty())
|
||||||
|
{
|
||||||
|
// Process additional subscription results, add to main result
|
||||||
|
if (Utils.IsBase64String(additionalResult))
|
||||||
|
{
|
||||||
|
result += Environment.NewLine + Utils.Base64Decode(additionalResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result += Environment.NewLine + additionalResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> updateFunc)
|
||||||
|
{
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
||||||
|
|
||||||
|
// If result is too short, display content directly
|
||||||
|
if (result.Length < 99)
|
||||||
|
{
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{result}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
|
||||||
|
|
||||||
|
// Add servers to configuration
|
||||||
|
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
|
||||||
|
if (ret <= 0)
|
||||||
|
{
|
||||||
|
Logging.SaveLog("FailedImportSubscription");
|
||||||
|
Logging.SaveLog(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update completion message
|
||||||
|
await updateFunc?.Invoke(false, ret > 0
|
||||||
|
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
||||||
|
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
||||||
|
|
||||||
|
return ret > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ServiceLib.Handler.SysProxy;
|
namespace ServiceLib.Handler.SysProxy;
|
||||||
|
|
||||||
public class ProxySettingLinux
|
public static class ProxySettingLinux
|
||||||
{
|
{
|
||||||
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ServiceLib.Handler.SysProxy;
|
namespace ServiceLib.Handler.SysProxy;
|
||||||
|
|
||||||
public class ProxySettingOSX
|
public static class ProxySettingOSX
|
||||||
{
|
{
|
||||||
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionO
|
|||||||
|
|
||||||
namespace ServiceLib.Handler.SysProxy;
|
namespace ServiceLib.Handler.SysProxy;
|
||||||
|
|
||||||
public class ProxySettingWindows
|
public static class ProxySettingWindows
|
||||||
{
|
{
|
||||||
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
|
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public static class SysProxyHandler
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||||
if (port <= 0)
|
if (port <= 0)
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ public static class SysProxyHandler
|
|||||||
|
|
||||||
if (type != ESysProxyType.Pac && Utils.IsWindows())
|
if (type != ESysProxyType.Pac && Utils.IsWindows())
|
||||||
{
|
{
|
||||||
PacHandler.Stop();
|
PacManager.Instance.Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -90,8 +90,8 @@ public static class SysProxyHandler
|
|||||||
|
|
||||||
private static async Task SetWindowsProxyPac(int port)
|
private static async Task SetWindowsProxyPac(int port)
|
||||||
{
|
{
|
||||||
var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac);
|
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||||
await PacHandler.Start(Utils.GetConfigPath(), port, portPac);
|
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
|
||||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Downloader;
|
using Downloader;
|
||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Helper;
|
||||||
|
|
||||||
public class DownloaderHelper
|
public class DownloaderHelper
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Helper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -202,4 +204,35 @@ public class HttpClientHelper
|
|||||||
}
|
}
|
||||||
} while (isMoreToRead);
|
} while (isMoreToRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
||||||
|
{
|
||||||
|
var responseTime = -1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
|
||||||
|
using var client = new HttpClient(new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
Proxy = webProxy,
|
||||||
|
UseProxy = webProxy != null
|
||||||
|
});
|
||||||
|
|
||||||
|
List<int> oneTime = new();
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
|
||||||
|
timer.Stop();
|
||||||
|
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch //(Exception ex)
|
||||||
|
{
|
||||||
|
//Utile.SaveLog(ex.Message, ex);
|
||||||
|
}
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Helper;
|
||||||
|
|
||||||
public sealed class SQLiteHelper
|
public sealed class SQLiteHelper
|
||||||
{
|
{
|
||||||
@@ -26,7 +26,7 @@ public sealed class SQLiteHelper
|
|||||||
|
|
||||||
public async Task<int> InsertAllAsync(IEnumerable models)
|
public async Task<int> InsertAllAsync(IEnumerable models)
|
||||||
{
|
{
|
||||||
return await _dbAsync.InsertAllAsync(models);
|
return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> InsertAsync(object model)
|
public async Task<int> InsertAsync(object model)
|
||||||
@@ -46,7 +46,7 @@ public sealed class SQLiteHelper
|
|||||||
|
|
||||||
public async Task<int> UpdateAllAsync(IEnumerable models)
|
public async Task<int> UpdateAllAsync(IEnumerable models)
|
||||||
{
|
{
|
||||||
return await _dbAsync.UpdateAllAsync(models);
|
return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> DeleteAsync(object model)
|
public async Task<int> DeleteAsync(object model)
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
namespace ServiceLib.Handler;
|
using System.Reactive;
|
||||||
|
|
||||||
public sealed class AppHandler
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
|
public sealed class AppManager
|
||||||
{
|
{
|
||||||
#region Property
|
#region Property
|
||||||
|
|
||||||
private static readonly Lazy<AppHandler> _instance = new(() => new());
|
private static readonly Lazy<AppManager> _instance = new(() => new());
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private int? _statePort;
|
private int? _statePort;
|
||||||
private int? _statePort2;
|
private int? _statePort2;
|
||||||
private Job? _processJob;
|
private Job? _processJob;
|
||||||
public static AppHandler Instance => _instance.Value;
|
public static AppManager Instance => _instance.Value;
|
||||||
public Config Config => _config;
|
public Config Config => _config;
|
||||||
|
|
||||||
public int StatePort
|
public int StatePort
|
||||||
@@ -34,7 +36,7 @@ public sealed class AppHandler
|
|||||||
|
|
||||||
#endregion Property
|
#endregion Property
|
||||||
|
|
||||||
#region Init
|
#region App
|
||||||
|
|
||||||
public bool InitApp()
|
public bool InitApp()
|
||||||
{
|
{
|
||||||
@@ -64,6 +66,7 @@ public sealed class AppHandler
|
|||||||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||||
|
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,40 @@ public sealed class AppHandler
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
public async Task AppExitAsync(bool needShutdown)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.SaveLog("AppExitAsync Begin");
|
||||||
|
|
||||||
|
await SysProxyHandler.UpdateSysProxy(_config, true);
|
||||||
|
AppEvents.AppExitRequested.OnNext(Unit.Default);
|
||||||
|
await Task.Delay(50); //Wait for AppExitRequested to be processed
|
||||||
|
|
||||||
|
await ConfigHandler.SaveConfig(_config);
|
||||||
|
await ProfileExManager.Instance.SaveTo();
|
||||||
|
await StatisticsManager.Instance.SaveTo();
|
||||||
|
await CoreManager.Instance.CoreStop();
|
||||||
|
StatisticsManager.Instance.Close();
|
||||||
|
|
||||||
|
Logging.SaveLog("AppExitAsync End");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (needShutdown)
|
||||||
|
{
|
||||||
|
Shutdown(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown(bool byUser)
|
||||||
|
{
|
||||||
|
AppEvents.ShutdownRequested.OnNext(byUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion App
|
||||||
|
|
||||||
#region Config
|
#region Config
|
||||||
|
|
||||||
@@ -96,7 +132,7 @@ public sealed class AppHandler
|
|||||||
return localPort + (int)protocol;
|
return localPort + (int)protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddProcess(IntPtr processHandle)
|
public void AddProcess(nint processHandle)
|
||||||
{
|
{
|
||||||
if (Utils.IsWindows())
|
if (Utils.IsWindows())
|
||||||
{
|
{
|
||||||
@@ -203,6 +239,16 @@ public sealed class AppHandler
|
|||||||
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<FullConfigTemplateItem>?> FullConfigTemplateItem()
|
||||||
|
{
|
||||||
|
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FullConfigTemplateItem?> GetFullConfigTemplateItem(ECoreType eCoreType)
|
||||||
|
{
|
||||||
|
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion SqliteHelper
|
#endregion SqliteHelper
|
||||||
|
|
||||||
#region Core Type
|
#region Core Type
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using static ServiceLib.Models.ClashProxies;
|
using static ServiceLib.Models.ClashProxies;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class ClashApiHandler
|
public sealed class ClashApiManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
|
private static readonly Lazy<ClashApiManager> instance = new(() => new());
|
||||||
public static ClashApiHandler Instance => instance.Value;
|
public static ClashApiManager Instance => instance.Value;
|
||||||
|
|
||||||
private static readonly string _tag = "ClashApiHandler";
|
private static readonly string _tag = "ClashApiHandler";
|
||||||
private Dictionary<string, ProxiesItem>? _proxies;
|
private Dictionary<string, ProxiesItem>? _proxies;
|
||||||
@@ -35,7 +35,7 @@ public sealed class ClashApiHandler
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> updateFunc)
|
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Func<ClashProxyModel?, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -65,7 +65,7 @@ public sealed class ClashApiHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var urlBase = $"{GetApiUrl()}/proxies";
|
var urlBase = $"{GetApiUrl()}/proxies";
|
||||||
urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach (var it in lstProxy)
|
foreach (var it in lstProxy)
|
||||||
@@ -79,12 +79,12 @@ public sealed class ClashApiHandler
|
|||||||
tasks.Add(Task.Run(async () =>
|
tasks.Add(Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
updateFunc?.Invoke(it, result);
|
await updateFunc?.Invoke(it, result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
updateFunc?.Invoke(null, "");
|
await updateFunc?.Invoke(null, "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +182,6 @@ public sealed class ClashApiHandler
|
|||||||
|
|
||||||
private string GetApiUrl()
|
private string GetApiUrl()
|
||||||
{
|
{
|
||||||
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}";
|
return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,18 +3,18 @@ using System.Text;
|
|||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class CoreAdminHandler
|
public class CoreAdminManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new());
|
private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
|
||||||
public static CoreAdminHandler Instance => _instance.Value;
|
public static CoreAdminManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private int _linuxSudoPid = -1;
|
private int _linuxSudoPid = -1;
|
||||||
private const string _tag = "CoreAdminHandler";
|
private const string _tag = "CoreAdminHandler";
|
||||||
|
|
||||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
if (_config != null)
|
if (_config != null)
|
||||||
{
|
{
|
||||||
@@ -26,9 +26,9 @@ public class CoreAdminHandler
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(bool notify, string msg)
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
||||||
@@ -60,7 +60,7 @@ public class CoreAdminHandler
|
|||||||
{
|
{
|
||||||
if (e.Data.IsNotEmpty())
|
if (e.Data.IsNotEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ public class CoreAdminHandler
|
|||||||
proc.BeginErrorReadLine();
|
proc.BeginErrorReadLine();
|
||||||
|
|
||||||
await Task.Delay(10);
|
await Task.Delay(10);
|
||||||
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
|
await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd);
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
if (proc is null or { HasExited: true })
|
if (proc is null or { HasExited: true })
|
||||||
@@ -103,10 +103,10 @@ public class CoreAdminHandler
|
|||||||
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
|
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
|
||||||
var result = await Cli.Wrap(Global.LinuxBash)
|
var result = await Cli.Wrap(Global.LinuxBash)
|
||||||
.WithArguments(arg)
|
.WithArguments(arg)
|
||||||
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
|
.WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
|
||||||
.ExecuteBufferedAsync();
|
.ExecuteBufferedAsync();
|
||||||
|
|
||||||
UpdateFunc(false, result.StandardOutput.ToString());
|
await UpdateFunc(false, result.StandardOutput.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class CoreInfoHandler
|
public sealed class CoreInfoManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<CoreInfoHandler> _instance = new(() => new());
|
private static readonly Lazy<CoreInfoManager> _instance = new(() => new());
|
||||||
private List<CoreInfo>? _coreInfo;
|
private List<CoreInfo>? _coreInfo;
|
||||||
public static CoreInfoHandler Instance => _instance.Value;
|
public static CoreInfoManager Instance => _instance.Value;
|
||||||
|
|
||||||
public CoreInfoHandler()
|
public CoreInfoManager()
|
||||||
{
|
{
|
||||||
InitCoreInfo();
|
InitCoreInfo();
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,10 @@ public sealed class CoreInfoHandler
|
|||||||
Url = GetCoreUrl(ECoreType.v2fly),
|
Url = GetCoreUrl(ECoreType.v2fly),
|
||||||
Match = "V2Ray",
|
Match = "V2Ray",
|
||||||
VersionArg = "-version",
|
VersionArg = "-version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
@@ -90,6 +94,10 @@ public sealed class CoreInfoHandler
|
|||||||
Url = GetCoreUrl(ECoreType.v2fly_v5),
|
Url = GetCoreUrl(ECoreType.v2fly_v5),
|
||||||
Match = "V2Ray",
|
Match = "V2Ray",
|
||||||
VersionArg = "version",
|
VersionArg = "version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
@@ -107,20 +115,25 @@ public sealed class CoreInfoHandler
|
|||||||
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
|
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
|
||||||
Match = "Xray",
|
Match = "Xray",
|
||||||
VersionArg = "-version",
|
VersionArg = "-version",
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ Global.XrayLocalAsset, Utils.GetBinPath("") },
|
||||||
|
{ Global.XrayLocalCert, Utils.GetBinPath("") },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.mihomo,
|
CoreType = ECoreType.mihomo,
|
||||||
CoreExes = ["mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
||||||
Arguments = "-f {0}" + PortableMode(),
|
Arguments = "-f {0}" + PortableMode(),
|
||||||
Url = GetCoreUrl(ECoreType.mihomo),
|
Url = GetCoreUrl(ECoreType.mihomo),
|
||||||
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||||
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
|
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-v1-{0}.zip",
|
||||||
DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
||||||
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
|
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{0}.gz",
|
||||||
DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
||||||
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-compatible-{0}.gz",
|
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{0}.gz",
|
||||||
DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
|
DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
|
||||||
Match = "Mihomo",
|
Match = "Mihomo",
|
||||||
VersionArg = "-v",
|
VersionArg = "-v",
|
||||||
@@ -205,12 +218,24 @@ public sealed class CoreInfoHandler
|
|||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.shadowquic,
|
CoreType = ECoreType.shadowquic,
|
||||||
CoreExes = [ "shadowquic", "shadowquic"],
|
CoreExes = [ "shadowquic" ],
|
||||||
Arguments = "-c {0}",
|
Arguments = "-c {0}",
|
||||||
Url = GetCoreUrl(ECoreType.shadowquic),
|
Url = GetCoreUrl(ECoreType.shadowquic),
|
||||||
AbsolutePath = false,
|
AbsolutePath = false,
|
||||||
}
|
},
|
||||||
|
|
||||||
|
new CoreInfo
|
||||||
|
{
|
||||||
|
CoreType = ECoreType.mieru,
|
||||||
|
CoreExes = [ "mieru" ],
|
||||||
|
Arguments = "run",
|
||||||
|
Url = GetCoreUrl(ECoreType.mieru),
|
||||||
|
AbsolutePath = false,
|
||||||
|
Environment = new Dictionary<string, string?>()
|
||||||
|
{
|
||||||
|
{ "MIERU_CONFIG_JSON_FILE", "{0}" },
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Core process processing class
|
/// Core process processing class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CoreHandler
|
public class CoreManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<CoreHandler> _instance = new(() => new());
|
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||||
public static CoreHandler Instance => _instance.Value;
|
public static CoreManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private Process? _process;
|
private Process? _process;
|
||||||
private Process? _processPre;
|
private Process? _processPre;
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private const string _tag = "CoreHandler";
|
private const string _tag = "CoreHandler";
|
||||||
|
|
||||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
|
||||||
|
|
||||||
//Copy the bin folder to the storage location (for init)
|
//Copy the bin folder to the storage location (for init)
|
||||||
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
||||||
{
|
{
|
||||||
@@ -39,7 +35,7 @@ public class CoreHandler
|
|||||||
|
|
||||||
if (Utils.IsNonWindows())
|
if (Utils.IsNonWindows())
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||||
foreach (var it in coreInfo)
|
foreach (var it in coreInfo)
|
||||||
{
|
{
|
||||||
if (it.CoreType == ECoreType.v2rayN)
|
if (it.CoreType == ECoreType.v2rayN)
|
||||||
@@ -67,7 +63,7 @@ public class CoreHandler
|
|||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
UpdateFunc(false, ResUI.CheckServerSettings);
|
await UpdateFunc(false, ResUI.CheckServerSettings);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +71,13 @@ public class CoreHandler
|
|||||||
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
UpdateFunc(true, result.Msg);
|
await UpdateFunc(true, result.Msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc(false, $"{node.GetSummary()}");
|
await UpdateFunc(false, $"{node.GetSummary()}");
|
||||||
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
|
await UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
|
||||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||||
await CoreStop();
|
await CoreStop();
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
@@ -95,7 +91,7 @@ public class CoreHandler
|
|||||||
await CoreStartPreService(node);
|
await CoreStartPreService(node);
|
||||||
if (_process != null)
|
if (_process != null)
|
||||||
{
|
{
|
||||||
UpdateFunc(true, $"{node.GetSummary()}");
|
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,16 +101,16 @@ public class CoreHandler
|
|||||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||||
var configPath = Utils.GetBinConfigPath(fileName);
|
var configPath = Utils.GetBinConfigPath(fileName);
|
||||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
||||||
UpdateFunc(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||||
UpdateFunc(false, configPath);
|
await UpdateFunc(false, configPath);
|
||||||
|
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
var proc = await RunProcess(coreInfo, fileName, true, false);
|
||||||
if (proc is null)
|
if (proc is null)
|
||||||
{
|
{
|
||||||
@@ -126,7 +122,7 @@ public class CoreHandler
|
|||||||
|
|
||||||
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
|
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
|
||||||
{
|
{
|
||||||
var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId);
|
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
|
||||||
if (node is null)
|
if (node is null)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -140,8 +136,8 @@ public class CoreHandler
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
var proc = await RunProcess(coreInfo, fileName, true, false);
|
||||||
if (proc is null)
|
if (proc is null)
|
||||||
{
|
{
|
||||||
@@ -157,7 +153,7 @@ public class CoreHandler
|
|||||||
{
|
{
|
||||||
if (_linuxSudo)
|
if (_linuxSudo)
|
||||||
{
|
{
|
||||||
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo();
|
await CoreAdminManager.Instance.KillProcessAsLinuxSudo();
|
||||||
_linuxSudo = false;
|
_linuxSudo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +179,8 @@ public class CoreHandler
|
|||||||
|
|
||||||
private async Task CoreStart(ProfileItem node)
|
private async Task CoreStart(ProfileItem node)
|
||||||
{
|
{
|
||||||
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
|
|
||||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||||
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
|
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
|
||||||
@@ -199,7 +195,7 @@ public class CoreHandler
|
|||||||
{
|
{
|
||||||
if (_process != null && !_process.HasExited)
|
if (_process != null && !_process.HasExited)
|
||||||
{
|
{
|
||||||
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
||||||
if (itemSocks != null)
|
if (itemSocks != null)
|
||||||
{
|
{
|
||||||
@@ -208,7 +204,7 @@ public class CoreHandler
|
|||||||
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||||
if (proc is null)
|
if (proc is null)
|
||||||
{
|
{
|
||||||
@@ -220,9 +216,9 @@ public class CoreHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(bool notify, string msg)
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Private
|
#endregion Private
|
||||||
@@ -231,10 +227,10 @@ public class CoreHandler
|
|||||||
|
|
||||||
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
||||||
{
|
{
|
||||||
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg);
|
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, msg);
|
await UpdateFunc(false, msg);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,8 +242,8 @@ public class CoreHandler
|
|||||||
&& Utils.IsNonWindows())
|
&& Utils.IsNonWindows())
|
||||||
{
|
{
|
||||||
_linuxSudo = true;
|
_linuxSudo = true;
|
||||||
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
|
await CoreAdminManager.Instance.Init(_config, _updateFunc);
|
||||||
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
|
return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
|
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
|
||||||
@@ -255,7 +251,7 @@ public class CoreHandler
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
UpdateFunc(mayNeedSudo, ex.Message);
|
await UpdateFunc(mayNeedSudo, ex.Message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +273,10 @@ public class CoreHandler
|
|||||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
foreach (var kv in coreInfo.Environment)
|
||||||
|
{
|
||||||
|
proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (displayLog)
|
if (displayLog)
|
||||||
{
|
{
|
||||||
@@ -284,7 +284,7 @@ public class CoreHandler
|
|||||||
{
|
{
|
||||||
if (e.Data.IsNotEmpty())
|
if (e.Data.IsNotEmpty())
|
||||||
{
|
{
|
||||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proc.OutputDataReceived += dataHandler;
|
proc.OutputDataReceived += dataHandler;
|
||||||
@@ -299,7 +299,7 @@ public class CoreHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
AppHandler.Instance.AddProcess(proc.Handle);
|
AppManager.Instance.AddProcess(proc.Handle);
|
||||||
if (proc is null or { HasExited: true })
|
if (proc is null or { HasExited: true })
|
||||||
{
|
{
|
||||||
throw new Exception(ResUI.FailedToRunCore);
|
throw new Exception(ResUI.FailedToRunCore);
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
using ReactiveUI;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
public class NoticeManager
|
||||||
|
|
||||||
public class NoticeHandler
|
|
||||||
{
|
{
|
||||||
private static readonly Lazy<NoticeHandler> _instance = new(() => new());
|
private static readonly Lazy<NoticeManager> _instance = new(() => new());
|
||||||
public static NoticeHandler Instance => _instance.Value;
|
public static NoticeManager Instance => _instance.Value;
|
||||||
|
|
||||||
public void Enqueue(string? content)
|
public void Enqueue(string? content)
|
||||||
{
|
{
|
||||||
@@ -13,7 +11,7 @@ public class NoticeHandler
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString());
|
AppEvents.SendSnackMsgRequested.OnNext(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessage(string? content)
|
public void SendMessage(string? content)
|
||||||
@@ -22,7 +20,7 @@ public class NoticeHandler
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString());
|
AppEvents.SendMsgViewRequested.OnNext(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessageEx(string? content)
|
public void SendMessageEx(string? content)
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class PacHandler
|
public class PacManager
|
||||||
{
|
{
|
||||||
private static string _configPath;
|
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
|
||||||
private static int _httpPort;
|
public static PacManager Instance => _instance.Value;
|
||||||
private static int _pacPort;
|
|
||||||
private static TcpListener? _tcpListener;
|
|
||||||
private static byte[] _writeContent;
|
|
||||||
private static bool _isRunning;
|
|
||||||
private static bool _needRestart = true;
|
|
||||||
|
|
||||||
public static async Task Start(string configPath, int httpPort, int pacPort)
|
private string _configPath;
|
||||||
|
private int _httpPort;
|
||||||
|
private int _pacPort;
|
||||||
|
private TcpListener? _tcpListener;
|
||||||
|
private byte[] _writeContent;
|
||||||
|
private bool _isRunning;
|
||||||
|
private bool _needRestart = true;
|
||||||
|
|
||||||
|
public async Task StartAsync(string configPath, int httpPort, int pacPort)
|
||||||
{
|
{
|
||||||
_needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
|
_needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ public class PacHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task InitText()
|
private async Task InitText()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_configPath, "pac.txt");
|
var path = Path.Combine(_configPath, "pac.txt");
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ public class PacHandler
|
|||||||
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
|
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunListener()
|
private void RunListener()
|
||||||
{
|
{
|
||||||
_tcpListener = TcpListener.Create(_pacPort);
|
_tcpListener = TcpListener.Create(_pacPort);
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
@@ -87,14 +90,14 @@ public class PacHandler
|
|||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteContent(TcpClient client)
|
private void WriteContent(TcpClient client)
|
||||||
{
|
{
|
||||||
var stream = client.GetStream();
|
var stream = client.GetStream();
|
||||||
stream.Write(_writeContent, 0, _writeContent.Length);
|
stream.Write(_writeContent, 0, _writeContent.Length);
|
||||||
stream.Flush();
|
stream.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
if (_tcpListener == null)
|
if (_tcpListener == null)
|
||||||
{
|
{
|
||||||
@@ -2,17 +2,17 @@ using System.Collections.Concurrent;
|
|||||||
|
|
||||||
//using System.Reactive.Linq;
|
//using System.Reactive.Linq;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class ProfileExHandler
|
public class ProfileExManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
|
private static readonly Lazy<ProfileExManager> _instance = new(() => new());
|
||||||
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
|
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
|
||||||
private readonly Queue<string> _queIndexIds = new();
|
private readonly Queue<string> _queIndexIds = new();
|
||||||
public static ProfileExHandler Instance => _instance.Value;
|
public static ProfileExManager Instance => _instance.Value;
|
||||||
private static readonly string _tag = "ProfileExHandler";
|
private static readonly string _tag = "ProfileExHandler";
|
||||||
|
|
||||||
public ProfileExHandler()
|
public ProfileExManager()
|
||||||
{
|
{
|
||||||
//Init();
|
//Init();
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class StatisticsHandler
|
public class StatisticsManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
|
private static readonly Lazy<StatisticsManager> instance = new(() => new());
|
||||||
public static StatisticsHandler Instance => instance.Value;
|
public static StatisticsManager Instance => instance.Value;
|
||||||
|
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private ServerStatItem? _serverStatItem;
|
private ServerStatItem? _serverStatItem;
|
||||||
private List<ServerStatItem> _lstServerStat;
|
private List<ServerStatItem> _lstServerStat;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
|
|
||||||
private StatisticsXrayService? _statisticsXray;
|
private StatisticsXrayService? _statisticsXray;
|
||||||
private StatisticsSingboxService? _statisticsSingbox;
|
private StatisticsSingboxService? _statisticsSingbox;
|
||||||
private static readonly string _tag = "StatisticsHandler";
|
private static readonly string _tag = "StatisticsHandler";
|
||||||
public List<ServerStatItem> ServerStat => _lstServerStat;
|
public List<ServerStatItem> ServerStat => _lstServerStat;
|
||||||
|
|
||||||
public async Task Init(Config config, Action<ServerSpeedItem> updateFunc)
|
public async Task Init(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -91,15 +91,15 @@ public class StatisticsHandler
|
|||||||
{
|
{
|
||||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
||||||
|
|
||||||
long ticks = DateTime.Now.Date.Ticks;
|
var ticks = DateTime.Now.Date.Ticks;
|
||||||
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
||||||
|
|
||||||
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateServerStatHandler(ServerSpeedItem server)
|
private async Task UpdateServerStatHandler(ServerSpeedItem server)
|
||||||
{
|
{
|
||||||
_ = UpdateServerStat(server);
|
await UpdateServerStat(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateServerStat(ServerSpeedItem server)
|
private async Task UpdateServerStat(ServerSpeedItem server)
|
||||||
@@ -123,12 +123,12 @@ public class StatisticsHandler
|
|||||||
server.TodayDown = _serverStatItem.TodayDown;
|
server.TodayDown = _serverStatItem.TodayDown;
|
||||||
server.TotalUp = _serverStatItem.TotalUp;
|
server.TotalUp = _serverStatItem.TotalUp;
|
||||||
server.TotalDown = _serverStatItem.TotalDown;
|
server.TotalDown = _serverStatItem.TotalDown;
|
||||||
_updateFunc?.Invoke(server);
|
await _updateFunc?.Invoke(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetServerStatItem(string indexId)
|
private async Task GetServerStatItem(string indexId)
|
||||||
{
|
{
|
||||||
long ticks = DateTime.Now.Date.Ticks;
|
var ticks = DateTime.Now.Date.Ticks;
|
||||||
if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
|
if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
|
||||||
{
|
{
|
||||||
_serverStatItem = null;
|
_serverStatItem = null;
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class TaskHandler
|
public class TaskManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<TaskHandler> _instance = new(() => new());
|
private static readonly Lazy<TaskManager> _instance = new(() => new());
|
||||||
public static TaskHandler Instance => _instance.Value;
|
public static TaskManager Instance => _instance.Value;
|
||||||
|
private Config _config;
|
||||||
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
|
|
||||||
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
|
public void RegUpdateTask(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
Task.Run(() => ScheduledTasks(config, updateFunc));
|
_config = config;
|
||||||
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
|
Task.Run(ScheduledTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ScheduledTasks(Config config, Action<bool, string> updateFunc)
|
private async Task ScheduledTasks()
|
||||||
{
|
{
|
||||||
Logging.SaveLog("Setup Scheduled Tasks");
|
Logging.SaveLog("Setup Scheduled Tasks");
|
||||||
|
|
||||||
@@ -21,15 +26,15 @@ public class TaskHandler
|
|||||||
await Task.Delay(1000 * 60);
|
await Task.Delay(1000 * 60);
|
||||||
|
|
||||||
//Execute once 1 minute
|
//Execute once 1 minute
|
||||||
await UpdateTaskRunSubscription(config, updateFunc);
|
await UpdateTaskRunSubscription();
|
||||||
|
|
||||||
//Execute once 20 minute
|
//Execute once 20 minute
|
||||||
if (numOfExecuted % 20 == 0)
|
if (numOfExecuted % 20 == 0)
|
||||||
{
|
{
|
||||||
//Logging.SaveLog("Execute save config");
|
//Logging.SaveLog("Execute save config");
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await ProfileExHandler.Instance.SaveTo();
|
await ProfileExManager.Instance.SaveTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Execute once 1 hour
|
//Execute once 1 hour
|
||||||
@@ -42,17 +47,17 @@ public class TaskHandler
|
|||||||
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
||||||
|
|
||||||
//Check once 1 hour
|
//Check once 1 hour
|
||||||
await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc);
|
await UpdateTaskRunGeo(numOfExecuted / 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
numOfExecuted++;
|
numOfExecuted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateTaskRunSubscription()
|
||||||
{
|
{
|
||||||
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
||||||
var lstSubs = (await AppHandler.Instance.SubItems())?
|
var lstSubs = (await AppManager.Instance.SubItems())?
|
||||||
.Where(t => t.AutoUpdateInterval > 0)
|
.Where(t => t.AutoUpdateInterval > 0)
|
||||||
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
|
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -63,34 +68,33 @@ public class TaskHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logging.SaveLog("Execute update subscription");
|
Logging.SaveLog("Execute update subscription");
|
||||||
var updateHandle = new UpdateService();
|
|
||||||
|
|
||||||
foreach (var item in lstSubs)
|
foreach (var item in lstSubs)
|
||||||
{
|
{
|
||||||
await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) =>
|
await SubscriptionHandler.UpdateProcess(_config, item.Id, true, async (success, msg) =>
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(success, msg);
|
await _updateFunc?.Invoke(success, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
Logging.SaveLog($"Update subscription end. {msg}");
|
Logging.SaveLog($"Update subscription end. {msg}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
item.UpdateTime = updateTime;
|
item.UpdateTime = updateTime;
|
||||||
await ConfigHandler.AddSubItem(config, item);
|
await ConfigHandler.AddSubItem(_config, item);
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTaskRunGeo(Config config, int hours, Action<bool, string> updateFunc)
|
private async Task UpdateTaskRunGeo(int hours)
|
||||||
{
|
{
|
||||||
if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0)
|
if (_config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % _config.GuiItem.AutoUpdateInterval == 0)
|
||||||
{
|
{
|
||||||
Logging.SaveLog("Execute update geo files");
|
Logging.SaveLog("Execute update geo files");
|
||||||
|
|
||||||
var updateHandle = new UpdateService();
|
var updateHandle = new UpdateService();
|
||||||
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
|
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, msg);
|
await _updateFunc?.Invoke(false, msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using WebDav;
|
using WebDav;
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class WebDavHandler
|
public sealed class WebDavManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<WebDavHandler> _instance = new(() => new());
|
private static readonly Lazy<WebDavManager> _instance = new(() => new());
|
||||||
public static WebDavHandler Instance => _instance.Value;
|
public static WebDavManager Instance => _instance.Value;
|
||||||
|
|
||||||
private readonly Config? _config;
|
private readonly Config? _config;
|
||||||
private WebDavClient? _client;
|
private WebDavClient? _client;
|
||||||
@@ -15,9 +15,9 @@ public sealed class WebDavHandler
|
|||||||
private readonly string _webFileName = "backup.zip";
|
private readonly string _webFileName = "backup.zip";
|
||||||
private readonly string _tag = "WebDav--";
|
private readonly string _tag = "WebDav--";
|
||||||
|
|
||||||
public WebDavHandler()
|
public WebDavManager()
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> GetClient()
|
private async Task<bool> GetClient()
|
||||||
@@ -142,6 +142,7 @@ public class CoreTypeItem
|
|||||||
public class TunModeItem
|
public class TunModeItem
|
||||||
{
|
{
|
||||||
public bool EnableTun { get; set; }
|
public bool EnableTun { get; set; }
|
||||||
|
public bool AutoRoute { get; set; } = true;
|
||||||
public bool StrictRoute { get; set; } = true;
|
public bool StrictRoute { get; set; } = true;
|
||||||
public string Stack { get; set; }
|
public string Stack { get; set; }
|
||||||
public int Mtu { get; set; }
|
public int Mtu { get; set; }
|
||||||
@@ -164,7 +165,6 @@ public class RoutingBasicItem
|
|||||||
{
|
{
|
||||||
public string DomainStrategy { get; set; }
|
public string DomainStrategy { get; set; }
|
||||||
public string DomainStrategy4Singbox { get; set; }
|
public string DomainStrategy4Singbox { get; set; }
|
||||||
public string DomainMatcher { get; set; }
|
|
||||||
public string RoutingIndexId { get; set; }
|
public string RoutingIndexId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ public class CoreInfo
|
|||||||
public string? Match { get; set; }
|
public string? Match { get; set; }
|
||||||
public string? VersionArg { get; set; }
|
public string? VersionArg { get; set; }
|
||||||
public bool AbsolutePath { get; set; }
|
public bool AbsolutePath { get; set; }
|
||||||
|
public IDictionary<string, string?> Environment { get; set; } = new Dictionary<string, string?>();
|
||||||
}
|
}
|
||||||
|
|||||||
18
v2rayN/ServiceLib/Models/FullConfigTemplateItem.cs
Normal file
18
v2rayN/ServiceLib/Models/FullConfigTemplateItem.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class FullConfigTemplateItem
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string Remarks { get; set; }
|
||||||
|
public bool Enabled { get; set; } = false;
|
||||||
|
public ECoreType CoreType { get; set; }
|
||||||
|
public string? Config { get; set; }
|
||||||
|
public string? TunConfig { get; set; }
|
||||||
|
public bool? AddProxyOnly { get; set; } = false;
|
||||||
|
public string? ProxyDetour { get; set; }
|
||||||
|
}
|
||||||
@@ -203,8 +203,15 @@ public class Response4Ray
|
|||||||
|
|
||||||
public class Dns4Ray
|
public class Dns4Ray
|
||||||
{
|
{
|
||||||
public Dictionary<string, List<string>>? hosts { get; set; }
|
public Dictionary<string, object>? hosts { get; set; }
|
||||||
public List<object> servers { get; set; }
|
public List<object> servers { get; set; }
|
||||||
|
public string? clientIp { get; set; }
|
||||||
|
public string? queryStrategy { get; set; }
|
||||||
|
public bool? disableCache { get; set; }
|
||||||
|
public bool? disableFallback { get; set; }
|
||||||
|
public bool? disableFallbackIfMatch { get; set; }
|
||||||
|
public bool? useSystemHosts { get; set; }
|
||||||
|
public string? tag { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DnsServer4Ray
|
public class DnsServer4Ray
|
||||||
@@ -214,14 +221,18 @@ public class DnsServer4Ray
|
|||||||
public bool? skipFallback { get; set; }
|
public bool? skipFallback { get; set; }
|
||||||
public List<string>? expectedIPs { get; set; }
|
public List<string>? expectedIPs { get; set; }
|
||||||
public List<string>? unexpectedIPs { get; set; }
|
public List<string>? unexpectedIPs { get; set; }
|
||||||
|
public string? clientIp { get; set; }
|
||||||
|
public string? queryStrategy { get; set; }
|
||||||
|
public int? timeoutMs { get; set; }
|
||||||
|
public bool? disableCache { get; set; }
|
||||||
|
public bool? finalQuery { get; set; }
|
||||||
|
public string? tag { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Routing4Ray
|
public class Routing4Ray
|
||||||
{
|
{
|
||||||
public string domainStrategy { get; set; }
|
public string domainStrategy { get; set; }
|
||||||
|
|
||||||
public string? domainMatcher { get; set; }
|
|
||||||
|
|
||||||
public List<RulesItem4Ray> rules { get; set; }
|
public List<RulesItem4Ray> rules { get; set; }
|
||||||
|
|
||||||
public List<BalancersItem4Ray>? balancers { get; set; }
|
public List<BalancersItem4Ray>? balancers { get; set; }
|
||||||
|
|||||||
162
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
162
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -186,6 +186,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Please fill in the correct config template 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string FillCorrectConfigTemplateText {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FillCorrectConfigTemplateText", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
|
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -933,6 +942,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Full Config Template Setting 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuFullConfigTemplate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuFullConfigTemplate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Global Hotkey Setting 的本地化字符串。
|
/// 查找类似 Global Hotkey Setting 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1842,6 +1860,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Start parsing and processing subscription content 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string MsgStartParsingSubscription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MsgStartParsingSubscription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Started updating {0}... 的本地化字符串。
|
/// 查找类似 Started updating {0}... 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2229,6 +2256,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbAddProxyProtocolOutboundOnly {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Address 的本地化字符串。
|
/// 查找类似 Address 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2310,6 +2346,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Prevent domain-based routing rules from failing 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbBlockSVCBHTTPSQueriesTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbBlockSVCBHTTPSQueriesTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Browse 的本地化字符串。
|
/// 查找类似 Browse 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2427,15 +2472,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Domain Matcher 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbdomainMatcher {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbdomainMatcher", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Domain strategy 的本地化字符串。
|
/// 查找类似 Domain strategy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2508,6 +2544,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 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. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFullConfigTemplateDesc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFullConfigTemplateDesc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Enable Full Config Template 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFullConfigTemplateEnable {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFullConfigTemplateEnable", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Global Hotkey Settings 的本地化字符串。
|
/// 查找类似 Global Hotkey Settings 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2688,15 +2742,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Prevent DNS Leaks 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbPreventDNSLeaks {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbPreventDNSLeaks", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Private Key 的本地化字符串。
|
/// 查找类似 Private Key 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2724,6 +2769,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 v2ray Full Config Template 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRayFullConfigTemplate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRayFullConfigTemplate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRayFullConfigTemplateDesc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRayFullConfigTemplateDesc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Alias (remarks) 的本地化字符串。
|
/// 查找类似 Alias (remarks) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2886,6 +2949,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSBFullConfigTemplate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSBFullConfigTemplate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Add Outbound and Endpoint Config Only, Click to view the document 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSBFullConfigTemplateDesc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSBFullConfigTemplateDesc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Resolve Outbound Domains 的本地化字符串。
|
/// 查找类似 Resolve Outbound Domains 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3660,6 +3741,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Auto Route 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunAutoRoute {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Tun Mode settings 的本地化字符串。
|
/// 查找类似 Tun Mode settings 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3669,6 +3759,33 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 MTU 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunMtu {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Stack 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunStack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Strict Route 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStrictRoute {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enable UDP 的本地化字符串。
|
/// 查找类似 Enable UDP 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3696,6 +3813,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Set Upstream Proxy Tag 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSetUpstreamProxyDetour {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Short Id 的本地化字符串。
|
/// 查找类似 Short Id 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>تنظیم کردن به عنوان قانون فعال</value>
|
<value>تنظیم کردن به عنوان قانون فعال</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>تطبیق دامنه</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>استراتژی دامنه</value>
|
<value>استراتژی دامنه</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1062,6 +1059,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
|
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>مسیریابی خودکار</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>مسیریابی سختگیرانه</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>پشته شبکه</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>فعال سازی additional Inbound</value>
|
<value>فعال سازی additional Inbound</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1443,9 +1452,6 @@
|
|||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>Block SVCB and HTTPS Queries</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>Prevent DNS Leaks</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1470,4 +1476,40 @@
|
|||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>Prevent domain-based routing rules from failing</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>Please fill in the correct config template</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Full Config Template Setting</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>Enable Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>v2ray Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>Do Not Add Non-Proxy Protocol Outbound</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>Set Upstream Proxy Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>sing-box Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>Beállítás aktív szabályként (Enter)</value>
|
<value>Beállítás aktív szabályként (Enter)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>Tartomány illesztő</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>Tartomány stratégia</value>
|
<value>Tartomány stratégia</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1062,6 +1059,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value>
|
<value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Automatikus útválasztás</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Szigorú útválasztás</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Hálózati verem</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>További bejövő engedélyezése</value>
|
<value>További bejövő engedélyezése</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1443,9 +1452,6 @@
|
|||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>Block SVCB and HTTPS Queries</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>Prevent DNS Leaks</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1470,4 +1476,40 @@
|
|||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>Prevent domain-based routing rules from failing</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>Please fill in the correct config template</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Full Config Template Setting</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>Enable Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>v2ray Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>Do Not Add Non-Proxy Protocol Outbound</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>Set Upstream Proxy Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>sing-box Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>Set as active rule (Enter)</value>
|
<value>Set as active rule (Enter)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>Domain Matcher</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>Domain strategy</value>
|
<value>Domain strategy</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1062,6 +1059,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Please make sure the Configuration remarks exist and are unique</value>
|
<value>Please make sure the Configuration remarks exist and are unique</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Auto Route</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Strict Route</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Stack</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>Enable additional Inbound</value>
|
<value>Enable additional Inbound</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1443,9 +1452,6 @@
|
|||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>Block SVCB and HTTPS Queries</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>Prevent DNS Leaks</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1470,4 +1476,40 @@
|
|||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>Prevent domain-based routing rules from failing</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>Please fill in the correct config template</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Full Config Template Setting</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>Enable Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>v2ray Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>Do Not Add Non-Proxy Protocol Outbound</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>Set Upstream Proxy Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>sing-box Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>Установить как активное правило</value>
|
<value>Установить как активное правило</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>Сопоставитель доменов</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>Доменная стратегия</value>
|
<value>Доменная стратегия</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1062,6 +1059,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Убедитесь, что примечание существует и является уникальным</value>
|
<value>Убедитесь, что примечание существует и является уникальным</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Автоматическая маршрутизация</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Строгая маршрутизация</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Сетевой стек</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>Включить дополнительный входящий канал</value>
|
<value>Включить дополнительный входящий канал</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1099,13 +1108,13 @@
|
|||||||
<value>Отмена тестирования...</value>
|
<value>Отмена тестирования...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TransportRequestHostTip5" xml:space="preserve">
|
<data name="TransportRequestHostTip5" xml:space="preserve">
|
||||||
<value>*gRPC Authority</value>
|
<value>* gRPC Authority (HTTP/2 псевдозаголовок :authority)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>Добавить сервер [HTTP]</value>
|
<value>Добавить сервер [HTTP]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||||
<value>which conflicts with the group previous proxy</value>
|
<value>что конфликтует с предыдущим прокси группы</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>Включить фрагментацию (Fragment)</value>
|
<value>Включить фрагментацию (Fragment)</value>
|
||||||
@@ -1318,13 +1327,13 @@
|
|||||||
<value>Пароль sudo системы</value>
|
<value>Пароль sudo системы</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||||
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
|
<value>Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
||||||
<value>*XHTTP-режим</value>
|
<value>*XHTTP-режим</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TransportExtraTip" xml:space="preserve">
|
<data name="TransportExtraTip" xml:space="preserve">
|
||||||
<value>Дополнительный XHTTP сырой JSON, формат: { XHTTPObject }</value>
|
<value>Дополнительный „сырой“ JSON для XHTTP, формат: { XHTTP Object }</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
|
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
|
||||||
<value>Скрыть в трее при закрытии окна</value>
|
<value>Скрыть в трее при закрытии окна</value>
|
||||||
@@ -1393,10 +1402,10 @@
|
|||||||
<value>URL для тестирования текущего соединения</value>
|
<value>URL для тестирования текущего соединения</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRuleOutboundTagTip" xml:space="preserve">
|
<data name="TbRuleOutboundTagTip" xml:space="preserve">
|
||||||
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
|
<value>Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
||||||
<value>Incorrect password, please try again.</value>
|
<value>Неверный пароль, попробуйте ещё раз.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbMldsa65Verify" xml:space="preserve">
|
<data name="TbMldsa65Verify" xml:space="preserve">
|
||||||
<value>Mldsa65Verify</value>
|
<value>Mldsa65Verify</value>
|
||||||
@@ -1405,69 +1414,102 @@
|
|||||||
<value>Добавить сервер [Anytls]</value>
|
<value>Добавить сервер [Anytls]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNS" xml:space="preserve">
|
<data name="TbRemoteDNS" xml:space="preserve">
|
||||||
<value>Remote DNS</value>
|
<value>Удалённый DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Domestic DNS</value>
|
<value>Внутренний DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
|
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
|
||||||
<value>Outbound DNS Resolution (sing-box)</value>
|
<value>Резолвер DNS для исходящих (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Resolve Outbound Domains</value>
|
<value>Разрешать домены для исходящих соединений</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
||||||
<value>sing-box DoH Resolver Server</value>
|
<value>Сервер DoH-резолвера (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Fallback DNS Resolution, Suggest IP</value>
|
<value>Резервное DNS-разрешение (рекомендуется указывать IP)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>Стратегия резолвинга Freedom (Xray)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>sing-box Direct Resolution Strategy</value>
|
<value>Стратегия прямого резолвинга (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>sing-box Remote Resolution Strategy</value>
|
<value>Стратегия удалённого резолвинга (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Добавить стандартные записи hosts (DNS)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
<data name="TbSBDoHOverride" xml:space="preserve">
|
||||||
<value>The sing-box DoH resolution server can be overwritten</value>
|
<value>Сервер DoH-резолвера sing-box можно переопределить</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>Блокировать DNS-запросы SVCB и HTTPS</value>
|
||||||
</data>
|
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>Prevent DNS Leaks</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||||
<value>Apply to Proxy Domains Only</value>
|
<value>Применять только к доменам через прокси</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>Базовые настройки DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThAdvancedDNSSettings" xml:space="preserve">
|
<data name="ThAdvancedDNSSettings" xml:space="preserve">
|
||||||
<value>Advanced DNS Settings</value>
|
<value>Расширенные настройки DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
||||||
<value>Validate Regional Domain IPs</value>
|
<value>Проверять IP-адреса региональных доменов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
|
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Enable Custom DNS</value>
|
<value>Включить пользовательский DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>Предотвращает сбои доменных правил маршрутизации</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Настройка полного шаблона конфигурации</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>Включить полный шаблон конфигурации</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Полный шаблон конфигурации v2ray</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Добавляет только конфигурацию исходящих (outbound), а также routing.balancers и routing.rules.outboundTag. Нажмите, чтобы открыть документ</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>Не добавлять исходящие для непрокси-протоколов</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>Задать тег верхнего прокси (upstream)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Полный шаблон конфигурации sing-box</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>设为活动规则 (Enter)</value>
|
<value>设为活动规则 (Enter)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>域名匹配算法</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>域名解析策略</value>
|
<value>域名解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1059,6 +1056,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>请确保配置文件别名存在并唯一</value>
|
<value>请确保配置文件别名存在并唯一</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>自动路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>严格路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>协议栈</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>启用额外监听端口</value>
|
<value>启用额外监听端口</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1440,9 +1449,6 @@
|
|||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>阻止 SVCB 和 HTTPS 查询</value>
|
<value>阻止 SVCB 和 HTTPS 查询</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>避免 DNS 泄漏</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts:(“域名1 ip1 ip2” 一行一个)</value>
|
<value>DNS Hosts:(“域名1 ip1 ip2” 一行一个)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1467,4 +1473,40 @@
|
|||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>避免域名分流规则失效</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>请填写正确的配置模板</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>完整配置模板设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>启用完整配置模板</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>v2ray 完整配置模板</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>仅添加出站配置,routing.balancers 和 routing.rules.outboundTag,点击查看文档</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>不添加非代理协议出站</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>设置上游代理 tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>sing-box 完整配置模板</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>仅添加出站和端点配置,点击查看文档</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>开始解析和处理订阅内容</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -825,9 +825,6 @@
|
|||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>設為活動規則 (Enter)</value>
|
<value>設為活動規則 (Enter)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainMatcher" xml:space="preserve">
|
|
||||||
<value>域名匹配演算法</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>域名解析策略</value>
|
<value>域名解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1059,6 +1056,18 @@
|
|||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>請確保設定檔別名存在並且唯一</value>
|
<value>請確保設定檔別名存在並且唯一</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>自動路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>嚴格路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>協定堆疊</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>啟用額外偵聽連接埠</value>
|
<value>啟用額外偵聽連接埠</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1440,9 +1449,6 @@
|
|||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>Block SVCB and HTTPS Queries</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreventDNSLeaks" xml:space="preserve">
|
|
||||||
<value>Prevent DNS Leaks</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1467,4 +1473,40 @@
|
|||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
|
<value>Prevent domain-based routing rules from failing</value>
|
||||||
|
</data>
|
||||||
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
|
<value>Please fill in the correct config template</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>Full Config Template Setting</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
|
<value>Enable Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>v2ray Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
|
<value>Do Not Add Non-Proxy Protocol Outbound</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
|
<value>Set Upstream Proxy Tag</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
|
<value>sing-box Full Config Template</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
|
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
|
<value>開始解析和處理訂閱內容</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -73,12 +73,12 @@ public class CoreConfigClashService
|
|||||||
}
|
}
|
||||||
|
|
||||||
//mixed-port
|
//mixed-port
|
||||||
fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
//log-level
|
//log-level
|
||||||
fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel);
|
fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel);
|
||||||
|
|
||||||
//external-controller
|
//external-controller
|
||||||
fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}";
|
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||||
//allow-lan
|
//allow-lan
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
{
|
{
|
||||||
@@ -139,7 +139,7 @@ public class CoreConfigClashService
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClashApiHandler.Instance.ProfileContent = fileContent;
|
ClashApiManager.Instance.ProfileContent = fileContent;
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,522 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService(Config config)
|
||||||
|
{
|
||||||
|
private readonly Config _config = config;
|
||||||
|
private static readonly string _tag = "CoreConfigSingboxService";
|
||||||
|
|
||||||
|
#region public gen function
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null
|
||||||
|
|| node.Port <= 0)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
|
if (singboxConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(singboxConfig);
|
||||||
|
|
||||||
|
await GenInbounds(singboxConfig);
|
||||||
|
|
||||||
|
if (node.ConfigType == EConfigType.WireGuard)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.RemoveAt(0);
|
||||||
|
var endpoints = new Endpoints4Sbox();
|
||||||
|
await GenEndpoint(node, endpoints);
|
||||||
|
endpoints.tag = Global.ProxyTag;
|
||||||
|
singboxConfig.endpoints = new() { endpoints };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenOutbound(node, singboxConfig.outbounds.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenMoreOutbounds(node, singboxConfig);
|
||||||
|
|
||||||
|
await GenRouting(singboxConfig);
|
||||||
|
|
||||||
|
await GenDns(node, singboxConfig);
|
||||||
|
|
||||||
|
await GenExperimental(singboxConfig);
|
||||||
|
|
||||||
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
|
||||||
|
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
|
if (singboxConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
List<IPEndPoint> lstIpEndPoints = new();
|
||||||
|
List<TcpConnectionInformation> lstTcpConns = new();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||||
|
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
||||||
|
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(singboxConfig);
|
||||||
|
//GenDns(new(), singboxConfig);
|
||||||
|
singboxConfig.inbounds.Clear();
|
||||||
|
singboxConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
|
|
||||||
|
foreach (var it in selecteds)
|
||||||
|
{
|
||||||
|
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.Port <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||||
|
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||||
|
{
|
||||||
|
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//find unused port
|
||||||
|
var port = initPort;
|
||||||
|
for (var k = initPort; k < Global.MaxPort; k++)
|
||||||
|
{
|
||||||
|
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//found
|
||||||
|
port = k;
|
||||||
|
initPort = port + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Port In Used
|
||||||
|
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it.Port = port;
|
||||||
|
it.AllowTest = true;
|
||||||
|
|
||||||
|
//inbound
|
||||||
|
Inbound4Sbox inbound = new()
|
||||||
|
{
|
||||||
|
listen = Global.Loopback,
|
||||||
|
listen_port = port,
|
||||||
|
type = EInboundProtocol.mixed.ToString(),
|
||||||
|
};
|
||||||
|
inbound.tag = inbound.type + inbound.listen_port.ToString();
|
||||||
|
singboxConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
//outbound
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.Shadowsocks
|
||||||
|
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.VLESS
|
||||||
|
&& !Global.Flows.Contains(item.Flow))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||||
|
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||||
|
&& item.PublicKey.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var server = await GenServer(item);
|
||||||
|
if (server is null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
var tag = Global.ProxyTag + inbound.listen_port.ToString();
|
||||||
|
server.tag = tag;
|
||||||
|
if (server is Endpoints4Sbox endpoint)
|
||||||
|
{
|
||||||
|
singboxConfig.endpoints ??= new();
|
||||||
|
singboxConfig.endpoints.Add(endpoint);
|
||||||
|
}
|
||||||
|
else if (server is Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.Add(outbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
//rule
|
||||||
|
Rule4Sbox rule = new()
|
||||||
|
{
|
||||||
|
inbound = new List<string> { inbound.tag },
|
||||||
|
outbound = tag
|
||||||
|
};
|
||||||
|
singboxConfig.route.rules.Add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
|
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
||||||
|
{
|
||||||
|
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
||||||
|
}
|
||||||
|
singboxConfig.route.default_domain_resolver = new()
|
||||||
|
{
|
||||||
|
server = Global.SingboxFinalResolverTag
|
||||||
|
};
|
||||||
|
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node is not { Port: > 0 })
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
|
if (singboxConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(singboxConfig);
|
||||||
|
if (node.ConfigType == EConfigType.WireGuard)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.RemoveAt(0);
|
||||||
|
var endpoints = new Endpoints4Sbox();
|
||||||
|
await GenEndpoint(node, endpoints);
|
||||||
|
endpoints.tag = Global.ProxyTag;
|
||||||
|
singboxConfig.endpoints = new() { endpoints };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenOutbound(node, singboxConfig.outbounds.First());
|
||||||
|
}
|
||||||
|
await GenMoreOutbounds(node, singboxConfig);
|
||||||
|
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
|
if (item != null && item.Enabled == true)
|
||||||
|
{
|
||||||
|
await GenDnsDomainsCompatible(singboxConfig, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
||||||
|
}
|
||||||
|
singboxConfig.route.default_domain_resolver = new()
|
||||||
|
{
|
||||||
|
server = Global.SingboxFinalResolverTag
|
||||||
|
};
|
||||||
|
|
||||||
|
singboxConfig.route.rules.Clear();
|
||||||
|
singboxConfig.inbounds.Clear();
|
||||||
|
singboxConfig.inbounds.Add(new()
|
||||||
|
{
|
||||||
|
tag = $"{EInboundProtocol.mixed}{port}",
|
||||||
|
listen = Global.Loopback,
|
||||||
|
listen_port = port,
|
||||||
|
type = EInboundProtocol.mixed.ToString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
|
if (singboxConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(singboxConfig);
|
||||||
|
await GenInbounds(singboxConfig);
|
||||||
|
await GenRouting(singboxConfig);
|
||||||
|
await GenExperimental(singboxConfig);
|
||||||
|
singboxConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
|
foreach (var it in selecteds)
|
||||||
|
{
|
||||||
|
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.Port <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||||
|
{
|
||||||
|
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.Shadowsocks
|
||||||
|
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//outbound
|
||||||
|
proxyProfiles.Add(item);
|
||||||
|
}
|
||||||
|
if (proxyProfiles.Count <= 0)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
await GenOutboundsList(proxyProfiles, singboxConfig);
|
||||||
|
|
||||||
|
await GenDns(null, singboxConfig);
|
||||||
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
|
ret.Success = true;
|
||||||
|
|
||||||
|
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
if (node == null || fileName is null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(fileName))
|
||||||
|
{
|
||||||
|
File.Delete(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressFileName = node.Address;
|
||||||
|
if (addressFileName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (!File.Exists(addressFileName))
|
||||||
|
{
|
||||||
|
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
|
||||||
|
}
|
||||||
|
if (!File.Exists(addressFileName))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedReadConfiguration + "1";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Address == Global.CoreMultipleLoadConfigFileName)
|
||||||
|
{
|
||||||
|
var txtFile = File.ReadAllText(addressFileName);
|
||||||
|
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
|
||||||
|
if (singboxConfig == null)
|
||||||
|
{
|
||||||
|
File.Copy(addressFileName, fileName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenInbounds(singboxConfig);
|
||||||
|
await GenExperimental(singboxConfig);
|
||||||
|
|
||||||
|
var content = JsonUtils.Serialize(singboxConfig, true);
|
||||||
|
await File.WriteAllTextAsync(fileName, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.Copy(addressFileName, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//check again
|
||||||
|
if (!File.Exists(fileName))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedReadConfiguration + "2";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion public gen function
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||||
|
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
|
||||||
|
{
|
||||||
|
return JsonUtils.Serialize(singboxConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||||
|
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return JsonUtils.Serialize(singboxConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||||
|
if (fullConfigTemplateNode == null)
|
||||||
|
{
|
||||||
|
return JsonUtils.Serialize(singboxConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process outbounds
|
||||||
|
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||||
|
foreach (var outbound in singboxConfig.outbounds)
|
||||||
|
{
|
||||||
|
if (outbound.type.ToLower() is "direct" or "block")
|
||||||
|
{
|
||||||
|
if (fullConfigTemplate.AddProxyOnly == true)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
|
||||||
|
{
|
||||||
|
outbound.detour = fullConfigTemplate.ProxyDetour;
|
||||||
|
}
|
||||||
|
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||||
|
}
|
||||||
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
|
// Process endpoints
|
||||||
|
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
|
||||||
|
{
|
||||||
|
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
|
||||||
|
foreach (var endpoint in singboxConfig.endpoints)
|
||||||
|
{
|
||||||
|
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
endpoint.detour = fullConfigTemplate.ProxyDetour;
|
||||||
|
}
|
||||||
|
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
|
||||||
|
}
|
||||||
|
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
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.UseSystemHosts == true)
|
||||||
|
{
|
||||||
|
var systemHosts = Utils.GetSystemHosts();
|
||||||
|
if (systemHosts != null && systemHosts.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var host in systemHosts)
|
||||||
|
{
|
||||||
|
hostsDns.predefined.TryAdd(host.Key, new List<string> { host.Value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var userHostsMap = simpleDNSItem.Hosts
|
||||||
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(parts => parts.Length >= 2)
|
||||||
|
.GroupBy(parts => parts[0])
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var kvp in userHostsMap)
|
||||||
|
{
|
||||||
|
hostsDns.predefined[kvp.Key] = kvp.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var listen = "0.0.0.0";
|
||||||
|
singboxConfig.inbounds = [];
|
||||||
|
|
||||||
|
if (!_config.TunModeItem.EnableTun
|
||||||
|
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|
||||||
|
{
|
||||||
|
var inbound = new Inbound4Sbox()
|
||||||
|
{
|
||||||
|
type = EInboundProtocol.mixed.ToString(),
|
||||||
|
tag = EInboundProtocol.socks.ToString(),
|
||||||
|
listen = Global.Loopback,
|
||||||
|
};
|
||||||
|
singboxConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
|
|
||||||
|
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||||
|
{
|
||||||
|
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
|
||||||
|
singboxConfig.inbounds.Add(inbound2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
|
{
|
||||||
|
if (_config.Inbound.First().NewPort4LAN)
|
||||||
|
{
|
||||||
|
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
|
||||||
|
inbound3.listen = listen;
|
||||||
|
singboxConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
|
//auth
|
||||||
|
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||||
|
{
|
||||||
|
inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inbound.listen = listen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.TunModeItem.EnableTun)
|
||||||
|
{
|
||||||
|
if (_config.TunModeItem.Mtu <= 0)
|
||||||
|
{
|
||||||
|
_config.TunModeItem.Mtu = Global.TunMtus.First();
|
||||||
|
}
|
||||||
|
if (_config.TunModeItem.Stack.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
_config.TunModeItem.Stack = Global.TunStacks.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
|
||||||
|
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
|
||||||
|
tunInbound.mtu = _config.TunModeItem.Mtu;
|
||||||
|
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||||
|
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||||
|
tunInbound.stack = _config.TunModeItem.Stack;
|
||||||
|
if (_config.TunModeItem.EnableIPv6Address == false)
|
||||||
|
{
|
||||||
|
tunInbound.address = ["172.18.0.1/30"];
|
||||||
|
}
|
||||||
|
|
||||||
|
singboxConfig.inbounds.Add(tunInbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> GenLog(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (_config.CoreBasicItem.Loglevel)
|
||||||
|
{
|
||||||
|
case "debug":
|
||||||
|
case "info":
|
||||||
|
case "error":
|
||||||
|
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "warning":
|
||||||
|
singboxConfig.log.level = "warn";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (_config.CoreBasicItem.Loglevel == Global.None)
|
||||||
|
{
|
||||||
|
singboxConfig.log.disabled = true;
|
||||||
|
}
|
||||||
|
if (_config.CoreBasicItem.LogEnabled)
|
||||||
|
{
|
||||||
|
var dtNow = DateTime.Now;
|
||||||
|
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,577 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> GenOutbound(ProfileItem node, Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
outbound.server = node.Address;
|
||||||
|
outbound.server_port = node.Port;
|
||||||
|
outbound.type = Global.ProtocolTypes[node.ConfigType];
|
||||||
|
|
||||||
|
switch (node.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.VMess:
|
||||||
|
{
|
||||||
|
outbound.uuid = node.Id;
|
||||||
|
outbound.alter_id = node.AlterId;
|
||||||
|
if (Global.VmessSecurities.Contains(node.Security))
|
||||||
|
{
|
||||||
|
outbound.security = node.Security;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outbound.security = Global.DefaultSecurity;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Shadowsocks:
|
||||||
|
{
|
||||||
|
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
|
||||||
|
outbound.password = node.Id;
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.SOCKS:
|
||||||
|
{
|
||||||
|
outbound.version = "5";
|
||||||
|
if (node.Security.IsNotEmpty()
|
||||||
|
&& node.Id.IsNotEmpty())
|
||||||
|
{
|
||||||
|
outbound.username = node.Security;
|
||||||
|
outbound.password = node.Id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.HTTP:
|
||||||
|
{
|
||||||
|
if (node.Security.IsNotEmpty()
|
||||||
|
&& node.Id.IsNotEmpty())
|
||||||
|
{
|
||||||
|
outbound.username = node.Security;
|
||||||
|
outbound.password = node.Id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.VLESS:
|
||||||
|
{
|
||||||
|
outbound.uuid = node.Id;
|
||||||
|
|
||||||
|
outbound.packet_encoding = "xudp";
|
||||||
|
|
||||||
|
if (node.Flow.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outbound.flow = node.Flow;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Trojan:
|
||||||
|
{
|
||||||
|
outbound.password = node.Id;
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Hysteria2:
|
||||||
|
{
|
||||||
|
outbound.password = node.Id;
|
||||||
|
|
||||||
|
if (node.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
outbound.obfs = new()
|
||||||
|
{
|
||||||
|
type = "salamander",
|
||||||
|
password = node.Path.TrimEx(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null;
|
||||||
|
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null;
|
||||||
|
if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
|
||||||
|
{
|
||||||
|
outbound.server_port = null;
|
||||||
|
outbound.server_ports = node.Ports.Split(',')
|
||||||
|
.Select(p => p.Trim())
|
||||||
|
.Where(p => p.IsNotEmpty())
|
||||||
|
.Select(p =>
|
||||||
|
{
|
||||||
|
var port = p.Replace('-', ':');
|
||||||
|
return port.Contains(':') ? port : $"{port}:{port}";
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.TUIC:
|
||||||
|
{
|
||||||
|
outbound.uuid = node.Id;
|
||||||
|
outbound.password = node.Security;
|
||||||
|
outbound.congestion_control = node.HeaderType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Anytls:
|
||||||
|
{
|
||||||
|
outbound.password = node.Id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenOutboundTls(node, outbound);
|
||||||
|
|
||||||
|
await GenOutboundTransport(node, outbound);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
endpoint.address = Utils.String2List(node.RequestHost);
|
||||||
|
endpoint.type = Global.ProtocolTypes[node.ConfigType];
|
||||||
|
|
||||||
|
switch (node.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.WireGuard:
|
||||||
|
{
|
||||||
|
var peer = new Peer4Sbox
|
||||||
|
{
|
||||||
|
public_key = node.PublicKey,
|
||||||
|
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
|
||||||
|
address = node.Address,
|
||||||
|
port = node.Port,
|
||||||
|
// TODO default ["0.0.0.0/0", "::/0"]
|
||||||
|
allowed_ips = new() { "0.0.0.0/0", "::/0" },
|
||||||
|
};
|
||||||
|
endpoint.private_key = node.Id;
|
||||||
|
endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
|
||||||
|
endpoint.peers = new() { peer };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BaseServer4Sbox?> GenServer(ProfileItem node)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
if (node.ConfigType == EConfigType.WireGuard)
|
||||||
|
{
|
||||||
|
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||||
|
await GenEndpoint(node, endpoint);
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
await GenOutbound(node, outbound);
|
||||||
|
return outbound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult<BaseServer4Sbox?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||||
|
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var mux = new Multiplex4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
protocol = _config.Mux4SboxItem.Protocol,
|
||||||
|
max_connections = _config.Mux4SboxItem.MaxConnections,
|
||||||
|
padding = _config.Mux4SboxItem.Padding,
|
||||||
|
};
|
||||||
|
outbound.multiplex = mux;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
var server_name = string.Empty;
|
||||||
|
if (node.Sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
server_name = node.Sni;
|
||||||
|
}
|
||||||
|
else if (node.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
server_name = Utils.String2List(node.RequestHost)?.First();
|
||||||
|
}
|
||||||
|
var tls = new Tls4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
record_fragment = _config.CoreBasicItem.EnableFragment,
|
||||||
|
server_name = server_name,
|
||||||
|
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||||
|
alpn = node.GetAlpn(),
|
||||||
|
};
|
||||||
|
if (node.Fingerprint.IsNotEmpty())
|
||||||
|
{
|
||||||
|
tls.utls = new Utls4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurityReality)
|
||||||
|
{
|
||||||
|
tls.reality = new Reality4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
public_key = node.PublicKey,
|
||||||
|
short_id = node.ShortId
|
||||||
|
};
|
||||||
|
tls.insecure = false;
|
||||||
|
}
|
||||||
|
outbound.tls = tls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transport = new Transport4Sbox();
|
||||||
|
|
||||||
|
switch (node.GetNetwork())
|
||||||
|
{
|
||||||
|
case nameof(ETransport.h2):
|
||||||
|
transport.type = nameof(ETransport.http);
|
||||||
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||||
|
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.tcp): //http
|
||||||
|
if (node.HeaderType == Global.TcpHeaderHttp)
|
||||||
|
{
|
||||||
|
if (node.ConfigType == EConfigType.Shadowsocks)
|
||||||
|
{
|
||||||
|
outbound.plugin = "obfs-local";
|
||||||
|
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transport.type = nameof(ETransport.http);
|
||||||
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||||
|
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.ws):
|
||||||
|
transport.type = nameof(ETransport.ws);
|
||||||
|
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||||
|
if (node.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
transport.headers = new()
|
||||||
|
{
|
||||||
|
Host = node.RequestHost
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.httpupgrade):
|
||||||
|
transport.type = nameof(ETransport.httpupgrade);
|
||||||
|
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||||
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.quic):
|
||||||
|
transport.type = nameof(ETransport.quic);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.grpc):
|
||||||
|
transport.type = nameof(ETransport.grpc);
|
||||||
|
transport.service_name = node.Path;
|
||||||
|
transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s");
|
||||||
|
transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s");
|
||||||
|
transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (transport.type != null)
|
||||||
|
{
|
||||||
|
outbound.transport = transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
if (node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||||
|
if (subItem is null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//current proxy
|
||||||
|
BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag, null);
|
||||||
|
outbound ??= singboxConfig.outbounds.First();
|
||||||
|
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
|
||||||
|
//Previous proxy
|
||||||
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
string? prevOutboundTag = null;
|
||||||
|
if (prevNode is not null
|
||||||
|
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
|
||||||
|
{
|
||||||
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||||
|
var prevServer = await GenServer(prevNode);
|
||||||
|
prevServer.tag = prevOutboundTag;
|
||||||
|
if (prevServer is Endpoints4Sbox endpoint)
|
||||||
|
{
|
||||||
|
singboxConfig.endpoints ??= new();
|
||||||
|
singboxConfig.endpoints.Add(endpoint);
|
||||||
|
}
|
||||||
|
else if (prevServer is Outbound4Sbox outboundPrev)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.Add(outboundPrev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||||
|
|
||||||
|
if (nextServer is not null)
|
||||||
|
{
|
||||||
|
if (nextServer is Endpoints4Sbox endpoint)
|
||||||
|
{
|
||||||
|
singboxConfig.endpoints ??= new();
|
||||||
|
singboxConfig.endpoints.Insert(0, endpoint);
|
||||||
|
}
|
||||||
|
else if (nextServer is Outbound4Sbox outboundNext)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.Insert(0, outboundNext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get outbound template and initialize lists
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
if (txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultOutbounds = new List<Outbound4Sbox>();
|
||||||
|
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
|
||||||
|
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
|
||||||
|
var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints
|
||||||
|
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||||
|
|
||||||
|
// Cache for chain proxies to avoid duplicate generation
|
||||||
|
var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>();
|
||||||
|
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||||
|
var prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
|
// Process each node
|
||||||
|
var index = 0;
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// Handle proxy chain
|
||||||
|
string? prevTag = null;
|
||||||
|
var currentServer = await GenServer(node);
|
||||||
|
var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||||
|
if (nextServer != null)
|
||||||
|
{
|
||||||
|
nextServer = JsonUtils.DeepCopy(nextServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||||
|
|
||||||
|
// current proxy
|
||||||
|
currentServer.tag = $"{Global.ProxyTag}-{index}";
|
||||||
|
proxyTags.Add(currentServer.tag);
|
||||||
|
|
||||||
|
if (!node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||||
|
{
|
||||||
|
prevTag = value; // maybe null
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
if (prevNode is not null
|
||||||
|
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
|
||||||
|
{
|
||||||
|
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||||
|
prevOutbound.tag = prevTag;
|
||||||
|
prevOutbounds.Add(prevOutbound);
|
||||||
|
}
|
||||||
|
prevProxyTags[node.Subid] = prevTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
|
||||||
|
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||||
|
{
|
||||||
|
nextProxyCache[node.Subid] = nextServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextServer is not null)
|
||||||
|
{
|
||||||
|
if (nextServer is Endpoints4Sbox nextEndpoint)
|
||||||
|
{
|
||||||
|
resultEndpoints.Add(nextEndpoint);
|
||||||
|
}
|
||||||
|
else if (nextServer is Outbound4Sbox nextOutbound)
|
||||||
|
{
|
||||||
|
resultOutbounds.Add(nextOutbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentServer is Endpoints4Sbox currentEndpoint)
|
||||||
|
{
|
||||||
|
resultEndpoints.Add(currentEndpoint);
|
||||||
|
}
|
||||||
|
else if (currentServer is Outbound4Sbox currentOutbound)
|
||||||
|
{
|
||||||
|
resultOutbounds.Add(currentOutbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add urltest outbound (auto selection based on latency)
|
||||||
|
if (proxyTags.Count > 0)
|
||||||
|
{
|
||||||
|
var outUrltest = new Outbound4Sbox
|
||||||
|
{
|
||||||
|
type = "urltest",
|
||||||
|
tag = $"{Global.ProxyTag}-auto",
|
||||||
|
outbounds = proxyTags,
|
||||||
|
interrupt_exist_connections = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add selector outbound (manual selection)
|
||||||
|
var outSelector = new Outbound4Sbox
|
||||||
|
{
|
||||||
|
type = "selector",
|
||||||
|
tag = Global.ProxyTag,
|
||||||
|
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||||
|
interrupt_exist_connections = false,
|
||||||
|
};
|
||||||
|
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||||
|
|
||||||
|
// Insert these at the beginning
|
||||||
|
resultOutbounds.Insert(0, outUrltest);
|
||||||
|
resultOutbounds.Insert(0, outSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds
|
||||||
|
resultOutbounds.AddRange(prevOutbounds);
|
||||||
|
resultOutbounds.AddRange(singboxConfig.outbounds);
|
||||||
|
singboxConfig.outbounds = resultOutbounds;
|
||||||
|
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
|
||||||
|
resultEndpoints.AddRange(singboxConfig.endpoints);
|
||||||
|
singboxConfig.endpoints = resultEndpoints;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
|
||||||
|
if (!prevOutboundTag.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
outbound.detour = prevOutboundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next proxy
|
||||||
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode is not null
|
||||||
|
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType))
|
||||||
|
{
|
||||||
|
nextOutbound ??= await GenServer(nextNode);
|
||||||
|
nextOutbound.tag = outbound.tag;
|
||||||
|
|
||||||
|
outbound.tag = $"mid-{outbound.tag}";
|
||||||
|
nextOutbound.detour = outbound.tag;
|
||||||
|
}
|
||||||
|
return nextOutbound;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
singboxConfig.route.final = Global.ProxyTag;
|
||||||
|
var item = _config.SimpleDNSItem;
|
||||||
|
|
||||||
|
var defaultDomainResolverTag = Global.SingboxOutboundResolverTag;
|
||||||
|
var directDNSStrategy = item.SingboxStrategy4Direct.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : item.SingboxStrategy4Direct;
|
||||||
|
|
||||||
|
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
|
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
||||||
|
{
|
||||||
|
defaultDomainResolverTag = Global.SingboxFinalResolverTag;
|
||||||
|
directDNSStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : rawDNSItem.DomainStrategy4Freedom;
|
||||||
|
}
|
||||||
|
singboxConfig.route.default_domain_resolver = new()
|
||||||
|
{
|
||||||
|
server = defaultDomainResolverTag,
|
||||||
|
strategy = directDNSStrategy
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_config.TunModeItem.EnableTun)
|
||||||
|
{
|
||||||
|
singboxConfig.route.auto_detect_interface = true;
|
||||||
|
|
||||||
|
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
|
||||||
|
if (tunRules != null)
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.AddRange(tunRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
port = new() { 53 },
|
||||||
|
action = "hijack-dns",
|
||||||
|
process_name = lstDnsExe
|
||||||
|
});
|
||||||
|
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
outbound = Global.DirectTag,
|
||||||
|
process_name = lstDirectExe
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.Inbound.First().SniffingEnabled)
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
action = "sniff"
|
||||||
|
});
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
protocol = new() { "dns" },
|
||||||
|
action = "hijack-dns"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
port = new() { 53 },
|
||||||
|
network = new() { "udp" },
|
||||||
|
action = "hijack-dns"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
outbound = Global.DirectTag,
|
||||||
|
clash_mode = ERuleMode.Direct.ToString()
|
||||||
|
});
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
outbound = Global.ProxyTag,
|
||||||
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
|
||||||
|
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
|
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
||||||
|
{
|
||||||
|
domainStrategy = defaultRouting.DomainStrategy4Singbox;
|
||||||
|
}
|
||||||
|
var resolveRule = new Rule4Sbox
|
||||||
|
{
|
||||||
|
action = "resolve",
|
||||||
|
strategy = domainStrategy
|
||||||
|
};
|
||||||
|
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.Add(resolveRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
|
var ipRules = new List<RulesItem>();
|
||||||
|
if (routing != null)
|
||||||
|
{
|
||||||
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||||
|
foreach (var item1 in rules ?? [])
|
||||||
|
{
|
||||||
|
if (item1.Enabled)
|
||||||
|
{
|
||||||
|
await GenRoutingUserRule(item1, singboxConfig);
|
||||||
|
if (item1.Ip != null && item1.Ip.Count > 0)
|
||||||
|
{
|
||||||
|
ipRules.Add(item1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.Add(resolveRule);
|
||||||
|
foreach (var item2 in ipRules)
|
||||||
|
{
|
||||||
|
await GenRoutingUserRule(item2, singboxConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
|
||||||
|
{
|
||||||
|
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||||
|
|
||||||
|
foreach (var coreConfig in coreInfoResult)
|
||||||
|
{
|
||||||
|
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var baseExeName in coreConfig.CoreExes)
|
||||||
|
{
|
||||||
|
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||||
|
}
|
||||||
|
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lstDnsExe = new List<string>(dnsExeSet);
|
||||||
|
lstDirectExe = new List<string>(directExeSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
|
||||||
|
var rules = singboxConfig.route.rules;
|
||||||
|
|
||||||
|
var rule = new Rule4Sbox();
|
||||||
|
if (item.OutboundTag == "block")
|
||||||
|
{
|
||||||
|
rule.action = "reject";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule.outbound = item.OutboundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Port.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var portRanges = item.Port.Split(',').Where(it => it.Contains('-')).Select(it => it.Replace("-", ":")).ToList();
|
||||||
|
var ports = item.Port.Split(',').Where(it => !it.Contains('-')).Select(it => it.ToInt()).ToList();
|
||||||
|
|
||||||
|
rule.port_range = portRanges.Count > 0 ? portRanges : null;
|
||||||
|
rule.port = ports.Count > 0 ? ports : null;
|
||||||
|
}
|
||||||
|
if (item.Network.IsNotEmpty())
|
||||||
|
{
|
||||||
|
rule.network = Utils.String2List(item.Network);
|
||||||
|
}
|
||||||
|
if (item.Protocol?.Count > 0)
|
||||||
|
{
|
||||||
|
rule.protocol = item.Protocol;
|
||||||
|
}
|
||||||
|
if (item.InboundTag?.Count >= 0)
|
||||||
|
{
|
||||||
|
rule.inbound = item.InboundTag;
|
||||||
|
}
|
||||||
|
var rule1 = JsonUtils.DeepCopy(rule);
|
||||||
|
var rule2 = JsonUtils.DeepCopy(rule);
|
||||||
|
var rule3 = JsonUtils.DeepCopy(rule);
|
||||||
|
|
||||||
|
var hasDomainIp = false;
|
||||||
|
if (item.Domain?.Count > 0)
|
||||||
|
{
|
||||||
|
var countDomain = 0;
|
||||||
|
foreach (var it in item.Domain)
|
||||||
|
{
|
||||||
|
if (ParseV2Domain(it, rule1))
|
||||||
|
countDomain++;
|
||||||
|
}
|
||||||
|
if (countDomain > 0)
|
||||||
|
{
|
||||||
|
rules.Add(rule1);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Ip?.Count > 0)
|
||||||
|
{
|
||||||
|
var countIp = 0;
|
||||||
|
foreach (var it in item.Ip)
|
||||||
|
{
|
||||||
|
if (ParseV2Address(it, rule2))
|
||||||
|
countIp++;
|
||||||
|
}
|
||||||
|
if (countIp > 0)
|
||||||
|
{
|
||||||
|
rules.Add(rule2);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
|
||||||
|
{
|
||||||
|
rule3.process_name = item.Process;
|
||||||
|
rules.Add(rule3);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDomainIp
|
||||||
|
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
|
||||||
|
{
|
||||||
|
rules.Add(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ParseV2Domain(string domain, Rule4Sbox rule)
|
||||||
|
{
|
||||||
|
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (domain.StartsWith("geosite:"))
|
||||||
|
{
|
||||||
|
rule.geosite ??= [];
|
||||||
|
rule.geosite?.Add(domain.Substring(8));
|
||||||
|
}
|
||||||
|
else if (domain.StartsWith("regexp:"))
|
||||||
|
{
|
||||||
|
rule.domain_regex ??= [];
|
||||||
|
rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7));
|
||||||
|
}
|
||||||
|
else if (domain.StartsWith("domain:"))
|
||||||
|
{
|
||||||
|
rule.domain ??= [];
|
||||||
|
rule.domain_suffix ??= [];
|
||||||
|
rule.domain?.Add(domain.Substring(7));
|
||||||
|
rule.domain_suffix?.Add("." + domain.Substring(7));
|
||||||
|
}
|
||||||
|
else if (domain.StartsWith("full:"))
|
||||||
|
{
|
||||||
|
rule.domain ??= [];
|
||||||
|
rule.domain?.Add(domain.Substring(5));
|
||||||
|
}
|
||||||
|
else if (domain.StartsWith("keyword:"))
|
||||||
|
{
|
||||||
|
rule.domain_keyword ??= [];
|
||||||
|
rule.domain_keyword?.Add(domain.Substring(8));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule.domain_keyword ??= [];
|
||||||
|
rule.domain_keyword?.Add(domain);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ParseV2Address(string address, Rule4Sbox rule)
|
||||||
|
{
|
||||||
|
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (address.Equals("geoip:private"))
|
||||||
|
{
|
||||||
|
rule.ip_is_private = true;
|
||||||
|
}
|
||||||
|
else if (address.StartsWith("geoip:"))
|
||||||
|
{
|
||||||
|
rule.geoip ??= new();
|
||||||
|
rule.geoip?.Add(address.Substring(6));
|
||||||
|
}
|
||||||
|
else if (address.Equals("geoip:!private"))
|
||||||
|
{
|
||||||
|
rule.ip_is_private = false;
|
||||||
|
}
|
||||||
|
else if (address.StartsWith("geoip:!"))
|
||||||
|
{
|
||||||
|
rule.geoip ??= new();
|
||||||
|
rule.geoip?.Add(address.Substring(6));
|
||||||
|
rule.invert = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule.ip_cidr ??= new();
|
||||||
|
rule.ip_cidr?.Add(address);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
if (Global.OutboundTags.Contains(outboundTag))
|
||||||
|
{
|
||||||
|
return outboundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||||
|
if (node == null
|
||||||
|
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
|
||||||
|
{
|
||||||
|
return Global.ProxyTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
var server = await GenServer(node);
|
||||||
|
if (server is null)
|
||||||
|
{
|
||||||
|
return Global.ProxyTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||||
|
if (server is Endpoints4Sbox endpoint)
|
||||||
|
{
|
||||||
|
singboxConfig.endpoints ??= new();
|
||||||
|
singboxConfig.endpoints.Add(endpoint);
|
||||||
|
}
|
||||||
|
else if (server is Outbound4Sbox outbound)
|
||||||
|
{
|
||||||
|
singboxConfig.outbounds.Add(outbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
|
||||||
|
{
|
||||||
|
if (rule_set != null)
|
||||||
|
ruleSets.AddRange(rule_set);
|
||||||
|
}
|
||||||
|
var geosite = "geosite";
|
||||||
|
var geoip = "geoip";
|
||||||
|
var ruleSets = new List<string>();
|
||||||
|
|
||||||
|
//convert route geosite & geoip to ruleset
|
||||||
|
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||||
|
{
|
||||||
|
rule.rule_set ??= new List<string>();
|
||||||
|
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||||
|
rule.geosite = null;
|
||||||
|
AddRuleSets(ruleSets, rule.rule_set);
|
||||||
|
}
|
||||||
|
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||||
|
{
|
||||||
|
rule.rule_set ??= new List<string>();
|
||||||
|
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||||
|
rule.geoip = null;
|
||||||
|
AddRuleSets(ruleSets, rule.rule_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert dns geosite & geoip to ruleset
|
||||||
|
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||||
|
{
|
||||||
|
rule.rule_set ??= new List<string>();
|
||||||
|
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||||
|
rule.geosite = null;
|
||||||
|
}
|
||||||
|
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||||
|
{
|
||||||
|
rule.rule_set ??= new List<string>();
|
||||||
|
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||||
|
rule.geoip = null;
|
||||||
|
}
|
||||||
|
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
|
||||||
|
{
|
||||||
|
AddRuleSets(ruleSets, dnsRule.rule_set);
|
||||||
|
}
|
||||||
|
//rules in rules
|
||||||
|
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
|
||||||
|
{
|
||||||
|
foreach (var item2 in item ?? [])
|
||||||
|
{
|
||||||
|
AddRuleSets(ruleSets, item2.rule_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//load custom ruleset file
|
||||||
|
List<Ruleset4Sbox> customRulesets = [];
|
||||||
|
|
||||||
|
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
|
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
|
||||||
|
if (result.IsNotEmpty())
|
||||||
|
{
|
||||||
|
customRulesets = (JsonUtils.Deserialize<List<Ruleset4Sbox>>(result) ?? [])
|
||||||
|
.Where(t => t.tag != null)
|
||||||
|
.Where(t => t.type != null)
|
||||||
|
.Where(t => t.format != null)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Local srs files address
|
||||||
|
var localSrss = Utils.GetBinPath("srss");
|
||||||
|
|
||||||
|
//Add ruleset srs
|
||||||
|
singboxConfig.route.rule_set = [];
|
||||||
|
foreach (var item in new HashSet<string>(ruleSets))
|
||||||
|
{
|
||||||
|
if (item.IsNullOrEmpty())
|
||||||
|
{ continue; }
|
||||||
|
var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item));
|
||||||
|
if (customRuleset is null)
|
||||||
|
{
|
||||||
|
var pathSrs = Path.Combine(localSrss, $"{item}.srs");
|
||||||
|
if (File.Exists(pathSrs))
|
||||||
|
{
|
||||||
|
customRuleset = new()
|
||||||
|
{
|
||||||
|
type = "local",
|
||||||
|
format = "binary",
|
||||||
|
tag = item,
|
||||||
|
path = pathSrs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
|
||||||
|
? Global.SingboxRulesetUrl
|
||||||
|
: _config.ConstItem.SrsSourceUrl;
|
||||||
|
|
||||||
|
customRuleset = new()
|
||||||
|
{
|
||||||
|
type = "remote",
|
||||||
|
format = "binary",
|
||||||
|
tag = item,
|
||||||
|
url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item),
|
||||||
|
download_detour = Global.ProxyTag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
singboxConfig.route.rule_set.Add(customRuleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigSingboxService
|
||||||
|
{
|
||||||
|
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
//if (_config.guiItem.enableStatistics)
|
||||||
|
{
|
||||||
|
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||||
|
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
|
||||||
|
{
|
||||||
|
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
|
||||||
|
{
|
||||||
|
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||||
|
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
path = Utils.GetBinPath("cache.db"),
|
||||||
|
store_fakeip = _config.SimpleDNSItem.FakeIP == true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService(Config config)
|
||||||
|
{
|
||||||
|
private readonly Config _config = config;
|
||||||
|
private static readonly string _tag = "CoreConfigV2rayService";
|
||||||
|
|
||||||
|
#region public gen function
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null
|
||||||
|
|| node.Port <= 0)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.GetNetwork() is nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
|
if (v2rayConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(v2rayConfig);
|
||||||
|
|
||||||
|
await GenInbounds(v2rayConfig);
|
||||||
|
|
||||||
|
await GenOutbound(node, v2rayConfig.outbounds.First());
|
||||||
|
|
||||||
|
await GenMoreOutbounds(node, v2rayConfig);
|
||||||
|
|
||||||
|
await GenRouting(v2rayConfig);
|
||||||
|
|
||||||
|
await GenDns(node, v2rayConfig);
|
||||||
|
|
||||||
|
await GenStatistic(v2rayConfig);
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
|
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
|
if (v2rayConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(v2rayConfig);
|
||||||
|
await GenInbounds(v2rayConfig);
|
||||||
|
await GenRouting(v2rayConfig);
|
||||||
|
await GenDns(null, v2rayConfig);
|
||||||
|
await GenStatistic(v2rayConfig);
|
||||||
|
v2rayConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
|
foreach (var it in selecteds)
|
||||||
|
{
|
||||||
|
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.Port <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||||
|
{
|
||||||
|
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.Shadowsocks
|
||||||
|
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//outbound
|
||||||
|
proxyProfiles.Add(item);
|
||||||
|
}
|
||||||
|
if (proxyProfiles.Count <= 0)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
await GenOutboundsList(proxyProfiles, v2rayConfig);
|
||||||
|
|
||||||
|
//add balancers
|
||||||
|
await GenBalancer(v2rayConfig, multipleLoad);
|
||||||
|
|
||||||
|
var balancer = v2rayConfig.routing.balancers.First();
|
||||||
|
|
||||||
|
//add rule
|
||||||
|
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList();
|
||||||
|
if (rules?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
rule.outboundTag = null;
|
||||||
|
rule.balancerTag = balancer.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||||
|
{
|
||||||
|
v2rayConfig.routing.rules.Add(new()
|
||||||
|
{
|
||||||
|
ip = ["0.0.0.0/0", "::/0"],
|
||||||
|
balancerTag = balancer.tag,
|
||||||
|
type = "field"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
v2rayConfig.routing.rules.Add(new()
|
||||||
|
{
|
||||||
|
network = "tcp,udp",
|
||||||
|
balancerTag = balancer.tag,
|
||||||
|
type = "field"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Success = true;
|
||||||
|
|
||||||
|
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
|
if (v2rayConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
List<IPEndPoint> lstIpEndPoints = new();
|
||||||
|
List<TcpConnectionInformation> lstTcpConns = new();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||||
|
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
||||||
|
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(v2rayConfig);
|
||||||
|
v2rayConfig.inbounds.Clear();
|
||||||
|
v2rayConfig.outbounds.Clear();
|
||||||
|
v2rayConfig.routing.rules.Clear();
|
||||||
|
|
||||||
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
|
|
||||||
|
foreach (var it in selecteds)
|
||||||
|
{
|
||||||
|
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.Port <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||||
|
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||||
|
{
|
||||||
|
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//find unused port
|
||||||
|
var port = initPort;
|
||||||
|
for (var k = initPort; k < Global.MaxPort; k++)
|
||||||
|
{
|
||||||
|
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//found
|
||||||
|
port = k;
|
||||||
|
initPort = port + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Port In Used
|
||||||
|
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it.Port = port;
|
||||||
|
it.AllowTest = true;
|
||||||
|
|
||||||
|
//outbound
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.Shadowsocks
|
||||||
|
&& !Global.SsSecuritiesInXray.Contains(item.Security))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.ConfigType == EConfigType.VLESS
|
||||||
|
&& !Global.Flows.Contains(item.Flow))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||||
|
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||||
|
&& item.PublicKey.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//inbound
|
||||||
|
Inbounds4Ray inbound = new()
|
||||||
|
{
|
||||||
|
listen = Global.Loopback,
|
||||||
|
port = port,
|
||||||
|
protocol = EInboundProtocol.mixed.ToString(),
|
||||||
|
};
|
||||||
|
inbound.tag = inbound.protocol + inbound.port.ToString();
|
||||||
|
v2rayConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(item, outbound);
|
||||||
|
outbound.tag = Global.ProxyTag + inbound.port.ToString();
|
||||||
|
v2rayConfig.outbounds.Add(outbound);
|
||||||
|
|
||||||
|
//rule
|
||||||
|
RulesItem4Ray rule = new()
|
||||||
|
{
|
||||||
|
inboundTag = new List<string> { inbound.tag },
|
||||||
|
outboundTag = outbound.tag,
|
||||||
|
type = "field"
|
||||||
|
};
|
||||||
|
v2rayConfig.routing.rules.Add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node is not { Port: > 0 })
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.GetNetwork() is nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
|
if (v2rayConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenLog(v2rayConfig);
|
||||||
|
await GenOutbound(node, v2rayConfig.outbounds.First());
|
||||||
|
await GenMoreOutbounds(node, v2rayConfig);
|
||||||
|
|
||||||
|
v2rayConfig.routing.rules.Clear();
|
||||||
|
v2rayConfig.inbounds.Clear();
|
||||||
|
v2rayConfig.inbounds.Add(new()
|
||||||
|
{
|
||||||
|
tag = $"{EInboundProtocol.socks}{port}",
|
||||||
|
listen = Global.Loopback,
|
||||||
|
port = port,
|
||||||
|
protocol = EInboundProtocol.mixed.ToString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion public gen function
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||||
|
{
|
||||||
|
if (multipleLoad == EMultipleLoad.LeastPing)
|
||||||
|
{
|
||||||
|
var observatory = new Observatory4Ray
|
||||||
|
{
|
||||||
|
subjectSelector = [Global.ProxyTag],
|
||||||
|
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||||
|
probeInterval = "3m",
|
||||||
|
enableConcurrency = true,
|
||||||
|
};
|
||||||
|
v2rayConfig.observatory = observatory;
|
||||||
|
}
|
||||||
|
else if (multipleLoad == EMultipleLoad.LeastLoad)
|
||||||
|
{
|
||||||
|
var burstObservatory = new BurstObservatory4Ray
|
||||||
|
{
|
||||||
|
subjectSelector = [Global.ProxyTag],
|
||||||
|
pingConfig = new()
|
||||||
|
{
|
||||||
|
destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||||
|
interval = "5m",
|
||||||
|
timeout = "30s",
|
||||||
|
sampling = 2,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
v2rayConfig.burstObservatory = burstObservatory;
|
||||||
|
}
|
||||||
|
var strategyType = multipleLoad switch
|
||||||
|
{
|
||||||
|
EMultipleLoad.Random => "random",
|
||||||
|
EMultipleLoad.RoundRobin => "roundRobin",
|
||||||
|
EMultipleLoad.LeastPing => "leastPing",
|
||||||
|
EMultipleLoad.LeastLoad => "leastLoad",
|
||||||
|
_ => "roundRobin",
|
||||||
|
};
|
||||||
|
var balancer = new BalancersItem4Ray
|
||||||
|
{
|
||||||
|
selector = [Global.ProxyTag],
|
||||||
|
strategy = new() { type = strategyType },
|
||||||
|
tag = $"{Global.ProxyTag}-round",
|
||||||
|
};
|
||||||
|
v2rayConfig.routing.balancers = [balancer];
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
|
||||||
|
{
|
||||||
|
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||||
|
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return JsonUtils.Serialize(v2rayConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
|
||||||
|
if (fullConfigTemplateNode == null)
|
||||||
|
{
|
||||||
|
return JsonUtils.Serialize(v2rayConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||||
|
if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0)
|
||||||
|
{
|
||||||
|
var balancer = v2rayConfig.routing.balancers.First();
|
||||||
|
|
||||||
|
// Modify existing rules in custom config
|
||||||
|
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
||||||
|
if (rulesNode != null)
|
||||||
|
{
|
||||||
|
foreach (var rule in rulesNode.AsArray())
|
||||||
|
{
|
||||||
|
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
|
||||||
|
{
|
||||||
|
rule.AsObject().Remove("outboundTag");
|
||||||
|
rule["balancerTag"] = balancer.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure routing node exists
|
||||||
|
if (fullConfigTemplateNode["routing"] == null)
|
||||||
|
{
|
||||||
|
fullConfigTemplateNode["routing"] = new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle balancers - append instead of override
|
||||||
|
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
|
||||||
|
{
|
||||||
|
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
|
||||||
|
{
|
||||||
|
foreach (var balancerNode in newBalancers)
|
||||||
|
{
|
||||||
|
customBalancersNode.Add(balancerNode?.DeepClone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle outbounds - append instead of override
|
||||||
|
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||||
|
foreach (var outbound in v2rayConfig.outbounds)
|
||||||
|
{
|
||||||
|
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||||
|
{
|
||||||
|
if (fullConfigTemplate.AddProxyOnly == true)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty)))
|
||||||
|
{
|
||||||
|
outbound.streamSettings ??= new StreamSettings4Ray();
|
||||||
|
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
|
||||||
|
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
|
||||||
|
}
|
||||||
|
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||||
|
}
|
||||||
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
|
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
410
v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs
Normal file
410
v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
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();
|
||||||
|
var normalHost = v2rayConfig?.dns?.hosts;
|
||||||
|
|
||||||
|
if (normalHost != null && systemHosts?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var host in systemHosts)
|
||||||
|
{
|
||||||
|
normalHost.TryAdd(host.Key, new List<string> { host.Value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var userHostsMap = simpleDNSItem.Hosts
|
||||||
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(parts => parts.Length >= 2)
|
||||||
|
.GroupBy(parts => parts[0])
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var listen = "0.0.0.0";
|
||||||
|
v2rayConfig.inbounds = [];
|
||||||
|
|
||||||
|
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
||||||
|
v2rayConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||||
|
{
|
||||||
|
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
||||||
|
v2rayConfig.inbounds.Add(inbound2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
|
{
|
||||||
|
if (_config.Inbound.First().NewPort4LAN)
|
||||||
|
{
|
||||||
|
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
|
||||||
|
inbound3.listen = listen;
|
||||||
|
v2rayConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
|
//auth
|
||||||
|
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||||
|
{
|
||||||
|
inbound3.settings.auth = "password";
|
||||||
|
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inbound.listen = listen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
|
||||||
|
{
|
||||||
|
string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound = JsonUtils.Deserialize<Inbounds4Ray>(result);
|
||||||
|
if (inbound == null)
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
inbound.tag = protocol.ToString();
|
||||||
|
inbound.port = inItem.LocalPort + (int)protocol;
|
||||||
|
inbound.protocol = EInboundProtocol.mixed.ToString();
|
||||||
|
inbound.settings.udp = inItem.UdpEnabled;
|
||||||
|
inbound.sniffing.enabled = inItem.SniffingEnabled;
|
||||||
|
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||||
|
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
||||||
|
|
||||||
|
return inbound;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenLog(V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config.CoreBasicItem.LogEnabled)
|
||||||
|
{
|
||||||
|
var dtNow = DateTime.Now;
|
||||||
|
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||||
|
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
||||||
|
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||||
|
v2rayConfig.log.access = null;
|
||||||
|
v2rayConfig.log.error = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,695 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||||
|
switch (node.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.VMess:
|
||||||
|
{
|
||||||
|
VnextItem4Ray vnextItem;
|
||||||
|
if (outbound.settings.vnext.Count <= 0)
|
||||||
|
{
|
||||||
|
vnextItem = new VnextItem4Ray();
|
||||||
|
outbound.settings.vnext.Add(vnextItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vnextItem = outbound.settings.vnext.First();
|
||||||
|
}
|
||||||
|
vnextItem.address = node.Address;
|
||||||
|
vnextItem.port = node.Port;
|
||||||
|
|
||||||
|
UsersItem4Ray usersItem;
|
||||||
|
if (vnextItem.users.Count <= 0)
|
||||||
|
{
|
||||||
|
usersItem = new UsersItem4Ray();
|
||||||
|
vnextItem.users.Add(usersItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usersItem = vnextItem.users.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
usersItem.id = node.Id;
|
||||||
|
usersItem.alterId = node.AlterId;
|
||||||
|
usersItem.email = Global.UserEMail;
|
||||||
|
if (Global.VmessSecurities.Contains(node.Security))
|
||||||
|
{
|
||||||
|
usersItem.security = node.Security;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usersItem.security = Global.DefaultSecurity;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
||||||
|
|
||||||
|
outbound.settings.servers = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Shadowsocks:
|
||||||
|
{
|
||||||
|
ServersItem4Ray serversItem;
|
||||||
|
if (outbound.settings.servers.Count <= 0)
|
||||||
|
{
|
||||||
|
serversItem = new ServersItem4Ray();
|
||||||
|
outbound.settings.servers.Add(serversItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serversItem = outbound.settings.servers.First();
|
||||||
|
}
|
||||||
|
serversItem.address = node.Address;
|
||||||
|
serversItem.port = node.Port;
|
||||||
|
serversItem.password = node.Id;
|
||||||
|
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none";
|
||||||
|
|
||||||
|
serversItem.ota = false;
|
||||||
|
serversItem.level = 1;
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
|
||||||
|
outbound.settings.vnext = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.SOCKS:
|
||||||
|
case EConfigType.HTTP:
|
||||||
|
{
|
||||||
|
ServersItem4Ray serversItem;
|
||||||
|
if (outbound.settings.servers.Count <= 0)
|
||||||
|
{
|
||||||
|
serversItem = new ServersItem4Ray();
|
||||||
|
outbound.settings.servers.Add(serversItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serversItem = outbound.settings.servers.First();
|
||||||
|
}
|
||||||
|
serversItem.address = node.Address;
|
||||||
|
serversItem.port = node.Port;
|
||||||
|
serversItem.method = null;
|
||||||
|
serversItem.password = null;
|
||||||
|
|
||||||
|
if (node.Security.IsNotEmpty()
|
||||||
|
&& node.Id.IsNotEmpty())
|
||||||
|
{
|
||||||
|
SocksUsersItem4Ray socksUsersItem = new()
|
||||||
|
{
|
||||||
|
user = node.Security,
|
||||||
|
pass = node.Id,
|
||||||
|
level = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
|
||||||
|
}
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
|
||||||
|
outbound.settings.vnext = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.VLESS:
|
||||||
|
{
|
||||||
|
VnextItem4Ray vnextItem;
|
||||||
|
if (outbound.settings.vnext?.Count <= 0)
|
||||||
|
{
|
||||||
|
vnextItem = new VnextItem4Ray();
|
||||||
|
outbound.settings.vnext.Add(vnextItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vnextItem = outbound.settings.vnext.First();
|
||||||
|
}
|
||||||
|
vnextItem.address = node.Address;
|
||||||
|
vnextItem.port = node.Port;
|
||||||
|
|
||||||
|
UsersItem4Ray usersItem;
|
||||||
|
if (vnextItem.users.Count <= 0)
|
||||||
|
{
|
||||||
|
usersItem = new UsersItem4Ray();
|
||||||
|
vnextItem.users.Add(usersItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usersItem = vnextItem.users.First();
|
||||||
|
}
|
||||||
|
usersItem.id = node.Id;
|
||||||
|
usersItem.email = Global.UserEMail;
|
||||||
|
usersItem.encryption = node.Security;
|
||||||
|
|
||||||
|
if (node.Flow.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usersItem.flow = node.Flow;
|
||||||
|
await GenOutboundMux(node, outbound, false, muxEnabled);
|
||||||
|
}
|
||||||
|
outbound.settings.servers = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.Trojan:
|
||||||
|
{
|
||||||
|
ServersItem4Ray serversItem;
|
||||||
|
if (outbound.settings.servers.Count <= 0)
|
||||||
|
{
|
||||||
|
serversItem = new ServersItem4Ray();
|
||||||
|
outbound.settings.servers.Add(serversItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serversItem = outbound.settings.servers.First();
|
||||||
|
}
|
||||||
|
serversItem.address = node.Address;
|
||||||
|
serversItem.port = node.Port;
|
||||||
|
serversItem.password = node.Id;
|
||||||
|
|
||||||
|
serversItem.ota = false;
|
||||||
|
serversItem.level = 1;
|
||||||
|
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
|
||||||
|
outbound.settings.vnext = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EConfigType.WireGuard:
|
||||||
|
{
|
||||||
|
var peer = new WireguardPeer4Ray
|
||||||
|
{
|
||||||
|
publicKey = node.PublicKey,
|
||||||
|
endpoint = node.Address + ":" + node.Port.ToString()
|
||||||
|
};
|
||||||
|
var setting = new Outboundsettings4Ray
|
||||||
|
{
|
||||||
|
address = Utils.String2List(node.RequestHost),
|
||||||
|
secretKey = node.Id,
|
||||||
|
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
|
||||||
|
mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(),
|
||||||
|
peers = new List<WireguardPeer4Ray> { peer }
|
||||||
|
};
|
||||||
|
outbound.settings = setting;
|
||||||
|
outbound.settings.vnext = null;
|
||||||
|
outbound.settings.servers = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
|
||||||
|
await GenBoundStreamSettings(node, outbound);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
outbound.mux.enabled = false;
|
||||||
|
outbound.mux.concurrency = -1;
|
||||||
|
|
||||||
|
if (enabledTCP)
|
||||||
|
{
|
||||||
|
outbound.mux.enabled = true;
|
||||||
|
outbound.mux.concurrency = _config.Mux4RayItem.Concurrency;
|
||||||
|
}
|
||||||
|
else if (enabledUDP)
|
||||||
|
{
|
||||||
|
outbound.mux.enabled = true;
|
||||||
|
outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency;
|
||||||
|
outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var streamSettings = outbound.streamSettings;
|
||||||
|
streamSettings.network = node.GetNetwork();
|
||||||
|
var host = node.RequestHost.TrimEx();
|
||||||
|
var path = node.Path.TrimEx();
|
||||||
|
var sni = node.Sni.TrimEx();
|
||||||
|
var useragent = "";
|
||||||
|
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent];
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
useragent = _config.CoreBasicItem.DefUserAgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if tls
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
streamSettings.security = node.StreamSecurity;
|
||||||
|
|
||||||
|
TlsSettings4Ray tlsSettings = new()
|
||||||
|
{
|
||||||
|
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||||
|
alpn = node.GetAlpn(),
|
||||||
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||||
|
};
|
||||||
|
if (sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
tlsSettings.serverName = sni;
|
||||||
|
}
|
||||||
|
else if (host.IsNotEmpty())
|
||||||
|
{
|
||||||
|
tlsSettings.serverName = Utils.String2List(host)?.First();
|
||||||
|
}
|
||||||
|
streamSettings.tlsSettings = tlsSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if Reality
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurityReality)
|
||||||
|
{
|
||||||
|
streamSettings.security = node.StreamSecurity;
|
||||||
|
|
||||||
|
TlsSettings4Ray realitySettings = new()
|
||||||
|
{
|
||||||
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
|
||||||
|
serverName = sni,
|
||||||
|
publicKey = node.PublicKey,
|
||||||
|
shortId = node.ShortId,
|
||||||
|
spiderX = node.SpiderX,
|
||||||
|
mldsa65Verify = node.Mldsa65Verify,
|
||||||
|
show = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
streamSettings.realitySettings = realitySettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
//streamSettings
|
||||||
|
switch (node.GetNetwork())
|
||||||
|
{
|
||||||
|
case nameof(ETransport.kcp):
|
||||||
|
KcpSettings4Ray kcpSettings = new()
|
||||||
|
{
|
||||||
|
mtu = _config.KcpItem.Mtu,
|
||||||
|
tti = _config.KcpItem.Tti
|
||||||
|
};
|
||||||
|
|
||||||
|
kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity;
|
||||||
|
kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity;
|
||||||
|
|
||||||
|
kcpSettings.congestion = _config.KcpItem.Congestion;
|
||||||
|
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
|
||||||
|
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
|
||||||
|
kcpSettings.header = new Header4Ray
|
||||||
|
{
|
||||||
|
type = node.HeaderType,
|
||||||
|
domain = host.IsNullOrEmpty() ? null : host
|
||||||
|
};
|
||||||
|
if (path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
kcpSettings.seed = path;
|
||||||
|
}
|
||||||
|
streamSettings.kcpSettings = kcpSettings;
|
||||||
|
break;
|
||||||
|
//ws
|
||||||
|
case nameof(ETransport.ws):
|
||||||
|
WsSettings4Ray wsSettings = new();
|
||||||
|
wsSettings.headers = new Headers4Ray();
|
||||||
|
|
||||||
|
if (host.IsNotEmpty())
|
||||||
|
{
|
||||||
|
wsSettings.host = host;
|
||||||
|
wsSettings.headers.Host = host;
|
||||||
|
}
|
||||||
|
if (path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
wsSettings.path = path;
|
||||||
|
}
|
||||||
|
if (useragent.IsNotEmpty())
|
||||||
|
{
|
||||||
|
wsSettings.headers.UserAgent = useragent;
|
||||||
|
}
|
||||||
|
streamSettings.wsSettings = wsSettings;
|
||||||
|
|
||||||
|
break;
|
||||||
|
//httpupgrade
|
||||||
|
case nameof(ETransport.httpupgrade):
|
||||||
|
HttpupgradeSettings4Ray httpupgradeSettings = new();
|
||||||
|
|
||||||
|
if (path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
httpupgradeSettings.path = path;
|
||||||
|
}
|
||||||
|
if (host.IsNotEmpty())
|
||||||
|
{
|
||||||
|
httpupgradeSettings.host = host;
|
||||||
|
}
|
||||||
|
streamSettings.httpupgradeSettings = httpupgradeSettings;
|
||||||
|
|
||||||
|
break;
|
||||||
|
//xhttp
|
||||||
|
case nameof(ETransport.xhttp):
|
||||||
|
streamSettings.network = ETransport.xhttp.ToString();
|
||||||
|
XhttpSettings4Ray xhttpSettings = new();
|
||||||
|
|
||||||
|
if (path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
xhttpSettings.path = path;
|
||||||
|
}
|
||||||
|
if (host.IsNotEmpty())
|
||||||
|
{
|
||||||
|
xhttpSettings.host = host;
|
||||||
|
}
|
||||||
|
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType))
|
||||||
|
{
|
||||||
|
xhttpSettings.mode = node.HeaderType;
|
||||||
|
}
|
||||||
|
if (node.Extra.IsNotEmpty())
|
||||||
|
{
|
||||||
|
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamSettings.xhttpSettings = xhttpSettings;
|
||||||
|
await GenOutboundMux(node, outbound);
|
||||||
|
|
||||||
|
break;
|
||||||
|
//h2
|
||||||
|
case nameof(ETransport.h2):
|
||||||
|
HttpSettings4Ray httpSettings = new();
|
||||||
|
|
||||||
|
if (host.IsNotEmpty())
|
||||||
|
{
|
||||||
|
httpSettings.host = Utils.String2List(host);
|
||||||
|
}
|
||||||
|
httpSettings.path = path;
|
||||||
|
|
||||||
|
streamSettings.httpSettings = httpSettings;
|
||||||
|
|
||||||
|
break;
|
||||||
|
//quic
|
||||||
|
case nameof(ETransport.quic):
|
||||||
|
QuicSettings4Ray quicsettings = new()
|
||||||
|
{
|
||||||
|
security = host,
|
||||||
|
key = path,
|
||||||
|
header = new Header4Ray
|
||||||
|
{
|
||||||
|
type = node.HeaderType
|
||||||
|
}
|
||||||
|
};
|
||||||
|
streamSettings.quicSettings = quicsettings;
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
if (sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
streamSettings.tlsSettings.serverName = sni;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
streamSettings.tlsSettings.serverName = node.Address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.grpc):
|
||||||
|
GrpcSettings4Ray grpcSettings = new()
|
||||||
|
{
|
||||||
|
authority = host.IsNullOrEmpty() ? null : host,
|
||||||
|
serviceName = path,
|
||||||
|
multiMode = node.HeaderType == Global.GrpcMultiMode,
|
||||||
|
idle_timeout = _config.GrpcItem.IdleTimeout,
|
||||||
|
health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
|
||||||
|
permit_without_stream = _config.GrpcItem.PermitWithoutStream,
|
||||||
|
initial_windows_size = _config.GrpcItem.InitialWindowsSize,
|
||||||
|
};
|
||||||
|
streamSettings.grpcSettings = grpcSettings;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
//tcp
|
||||||
|
if (node.HeaderType == Global.TcpHeaderHttp)
|
||||||
|
{
|
||||||
|
TcpSettings4Ray tcpSettings = new()
|
||||||
|
{
|
||||||
|
header = new Header4Ray
|
||||||
|
{
|
||||||
|
type = node.HeaderType
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//request Host
|
||||||
|
string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName);
|
||||||
|
string[] arrHost = host.Split(',');
|
||||||
|
string host2 = string.Join(",".AppendQuotes(), arrHost);
|
||||||
|
request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}");
|
||||||
|
request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}");
|
||||||
|
//Path
|
||||||
|
string pathHttp = @"/";
|
||||||
|
if (path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
string[] arrPath = path.Split(',');
|
||||||
|
pathHttp = string.Join(",".AppendQuotes(), arrPath);
|
||||||
|
}
|
||||||
|
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
|
||||||
|
tcpSettings.header.request = JsonUtils.Deserialize<object>(request);
|
||||||
|
|
||||||
|
streamSettings.tcpSettings = tcpSettings;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
//fragment proxy
|
||||||
|
if (_config.CoreBasicItem.EnableFragment
|
||||||
|
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
|
||||||
|
{
|
||||||
|
var fragmentOutbound = new Outbounds4Ray
|
||||||
|
{
|
||||||
|
protocol = "freedom",
|
||||||
|
tag = $"{Global.ProxyTag}3",
|
||||||
|
settings = new()
|
||||||
|
{
|
||||||
|
fragment = new()
|
||||||
|
{
|
||||||
|
packets = _config.Fragment4RayItem?.Packets,
|
||||||
|
length = _config.Fragment4RayItem?.Length,
|
||||||
|
interval = _config.Fragment4RayItem?.Interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
v2rayConfig.outbounds.Add(fragmentOutbound);
|
||||||
|
v2rayConfig.outbounds.First().streamSettings.sockopt = new()
|
||||||
|
{
|
||||||
|
dialerProxy = fragmentOutbound.tag
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||||
|
if (subItem is null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//current proxy
|
||||||
|
var outbound = v2rayConfig.outbounds.First();
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
|
||||||
|
//Previous proxy
|
||||||
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
string? prevOutboundTag = null;
|
||||||
|
if (prevNode is not null
|
||||||
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
||||||
|
{
|
||||||
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||||
|
prevOutbound.tag = prevOutboundTag;
|
||||||
|
v2rayConfig.outbounds.Add(prevOutbound);
|
||||||
|
}
|
||||||
|
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||||
|
|
||||||
|
if (nextOutbound is not null)
|
||||||
|
{
|
||||||
|
v2rayConfig.outbounds.Insert(0, nextOutbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get template and initialize list
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
if (txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultOutbounds = new List<Outbounds4Ray>();
|
||||||
|
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
||||||
|
|
||||||
|
// Cache for chain proxies to avoid duplicate generation
|
||||||
|
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
||||||
|
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||||
|
int prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
|
// Process nodes
|
||||||
|
int index = 0;
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// Handle proxy chain
|
||||||
|
string? prevTag = null;
|
||||||
|
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||||
|
if (nextOutbound != null)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||||
|
|
||||||
|
// current proxy
|
||||||
|
await GenOutbound(node, currentOutbound);
|
||||||
|
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||||
|
|
||||||
|
if (!node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||||
|
{
|
||||||
|
prevTag = value; // maybe null
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
if (prevNode is not null
|
||||||
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
||||||
|
{
|
||||||
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||||
|
prevOutbound.tag = prevTag;
|
||||||
|
prevOutbounds.Add(prevOutbound);
|
||||||
|
}
|
||||||
|
prevProxyTags[node.Subid] = prevTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
||||||
|
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||||
|
{
|
||||||
|
nextProxyCache[node.Subid] = nextOutbound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextOutbound is not null)
|
||||||
|
{
|
||||||
|
resultOutbounds.Add(nextOutbound);
|
||||||
|
}
|
||||||
|
resultOutbounds.Add(currentOutbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
||||||
|
resultOutbounds.AddRange(prevOutbounds);
|
||||||
|
resultOutbounds.AddRange(v2rayConfig.outbounds);
|
||||||
|
v2rayConfig.outbounds = resultOutbounds;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a chained outbound configuration for the given subItem and outbound.
|
||||||
|
/// The outbound's tag must be set before calling this method.
|
||||||
|
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||||
|
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||||
|
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||||
|
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||||
|
/// </returns>
|
||||||
|
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
|
||||||
|
if (!prevOutboundTag.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
outbound.streamSettings.sockopt = new()
|
||||||
|
{
|
||||||
|
dialerProxy = prevOutboundTag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next proxy
|
||||||
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode is not null
|
||||||
|
&& Global.XraySupportConfigType.Contains(nextNode.ConfigType))
|
||||||
|
{
|
||||||
|
if (nextOutbound == null)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(nextNode, nextOutbound);
|
||||||
|
}
|
||||||
|
nextOutbound.tag = outbound.tag;
|
||||||
|
|
||||||
|
outbound.tag = $"mid-{outbound.tag}";
|
||||||
|
nextOutbound.streamSettings.sockopt = new()
|
||||||
|
{
|
||||||
|
dialerProxy = outbound.tag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return nextOutbound;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (v2rayConfig.routing?.rules != null)
|
||||||
|
{
|
||||||
|
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
||||||
|
|
||||||
|
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
|
if (routing != null)
|
||||||
|
{
|
||||||
|
if (routing.DomainStrategy.IsNotEmpty())
|
||||||
|
{
|
||||||
|
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
|
||||||
|
}
|
||||||
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||||
|
foreach (var item in rules)
|
||||||
|
{
|
||||||
|
if (item.Enabled)
|
||||||
|
{
|
||||||
|
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
|
||||||
|
await GenRoutingUserRule(item2, v2rayConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (rule == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
|
||||||
|
|
||||||
|
if (rule.port.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
rule.port = null;
|
||||||
|
}
|
||||||
|
if (rule.network.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
rule.network = null;
|
||||||
|
}
|
||||||
|
if (rule.domain?.Count == 0)
|
||||||
|
{
|
||||||
|
rule.domain = null;
|
||||||
|
}
|
||||||
|
if (rule.ip?.Count == 0)
|
||||||
|
{
|
||||||
|
rule.ip = null;
|
||||||
|
}
|
||||||
|
if (rule.protocol?.Count == 0)
|
||||||
|
{
|
||||||
|
rule.protocol = null;
|
||||||
|
}
|
||||||
|
if (rule.inboundTag?.Count == 0)
|
||||||
|
{
|
||||||
|
rule.inboundTag = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasDomainIp = false;
|
||||||
|
if (rule.domain?.Count > 0)
|
||||||
|
{
|
||||||
|
var it = JsonUtils.DeepCopy(rule);
|
||||||
|
it.ip = null;
|
||||||
|
it.type = "field";
|
||||||
|
for (var k = it.domain.Count - 1; k >= 0; k--)
|
||||||
|
{
|
||||||
|
if (it.domain[k].StartsWith("#"))
|
||||||
|
{
|
||||||
|
it.domain.RemoveAt(k);
|
||||||
|
}
|
||||||
|
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
|
||||||
|
}
|
||||||
|
v2rayConfig.routing.rules.Add(it);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
if (rule.ip?.Count > 0)
|
||||||
|
{
|
||||||
|
var it = JsonUtils.DeepCopy(rule);
|
||||||
|
it.domain = null;
|
||||||
|
it.type = "field";
|
||||||
|
v2rayConfig.routing.rules.Add(it);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
if (!hasDomainIp)
|
||||||
|
{
|
||||||
|
if (rule.port.IsNotEmpty()
|
||||||
|
|| rule.protocol?.Count > 0
|
||||||
|
|| rule.inboundTag?.Count > 0
|
||||||
|
|| rule.network != null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var it = JsonUtils.DeepCopy(rule);
|
||||||
|
it.type = "field";
|
||||||
|
v2rayConfig.routing.rules.Add(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
if (Global.OutboundTags.Contains(outboundTag))
|
||||||
|
{
|
||||||
|
return outboundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||||
|
if (node == null
|
||||||
|
|| !Global.XraySupportConfigType.Contains(node.ConfigType))
|
||||||
|
{
|
||||||
|
return Global.ProxyTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(node, outbound);
|
||||||
|
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||||
|
v2rayConfig.outbounds.Add(outbound);
|
||||||
|
|
||||||
|
return outbound.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
public partial class CoreConfigV2rayService
|
||||||
|
{
|
||||||
|
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||||
|
{
|
||||||
|
string tag = EInboundProtocol.api.ToString();
|
||||||
|
Metrics4Ray apiObj = new();
|
||||||
|
Policy4Ray policyObj = new();
|
||||||
|
SystemPolicy4Ray policySystemSetting = new();
|
||||||
|
|
||||||
|
v2rayConfig.stats = new Stats4Ray();
|
||||||
|
|
||||||
|
apiObj.tag = tag;
|
||||||
|
v2rayConfig.metrics = apiObj;
|
||||||
|
|
||||||
|
policySystemSetting.statsOutboundDownlink = true;
|
||||||
|
policySystemSetting.statsOutboundUplink = true;
|
||||||
|
policyObj.system = policySystemSetting;
|
||||||
|
v2rayConfig.policy = policyObj;
|
||||||
|
|
||||||
|
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
|
||||||
|
{
|
||||||
|
Inbounds4Ray apiInbound = new();
|
||||||
|
Inboundsettings4Ray apiInboundSettings = new();
|
||||||
|
apiInbound.tag = tag;
|
||||||
|
apiInbound.listen = Global.Loopback;
|
||||||
|
apiInbound.port = AppManager.Instance.StatePort;
|
||||||
|
apiInbound.protocol = Global.InboundAPIProtocol;
|
||||||
|
apiInboundSettings.address = Global.Loopback;
|
||||||
|
apiInbound.settings = apiInboundSettings;
|
||||||
|
v2rayConfig.inbounds.Add(apiInbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
|
||||||
|
{
|
||||||
|
RulesItem4Ray apiRoutingRule = new()
|
||||||
|
{
|
||||||
|
inboundTag = new List<string> { tag },
|
||||||
|
outboundTag = tag,
|
||||||
|
type = "field"
|
||||||
|
};
|
||||||
|
|
||||||
|
v2rayConfig.routing.rules.Add(apiRoutingRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -16,11 +15,11 @@ public class DownloadService
|
|||||||
|
|
||||||
private static readonly string _tag = "DownloadService";
|
private static readonly string _tag = "DownloadService";
|
||||||
|
|
||||||
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action<bool, string> updateFunc)
|
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||||
|
|
||||||
var progress = new Progress<string>();
|
var progress = new Progress<string>();
|
||||||
progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}");
|
progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}");
|
||||||
@@ -32,10 +31,10 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ex.Message);
|
await updateFunc?.Invoke(false, ex.Message);
|
||||||
if (ex.InnerException != null)
|
if (ex.InnerException != null)
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke(false, ex.InnerException.Message);
|
await updateFunc?.Invoke(false, ex.InnerException.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -45,7 +44,7 @@ public class DownloadService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||||
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
|
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
|
||||||
|
|
||||||
var progress = new Progress<double>();
|
var progress = new Progress<double>();
|
||||||
@@ -72,7 +71,7 @@ public class DownloadService
|
|||||||
|
|
||||||
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
||||||
{
|
{
|
||||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||||
var webRequestHandler = new SocketsHttpHandler
|
var webRequestHandler = new SocketsHttpHandler
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false,
|
AllowAutoRedirect = false,
|
||||||
@@ -142,7 +141,7 @@ public class DownloadService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
var client = new HttpClient(new SocketsHttpHandler()
|
var client = new HttpClient(new SocketsHttpHandler()
|
||||||
{
|
{
|
||||||
@@ -187,7 +186,7 @@ public class DownloadService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
SetSecurityProtocol(AppManager.Instance.Config.GuiItem.EnableSecurityProtocolTls13);
|
||||||
|
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
|
|
||||||
@@ -210,70 +209,13 @@ public class DownloadService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> RunAvailabilityCheck(IWebProxy? webProxy)
|
|
||||||
{
|
|
||||||
var responseTime = -1;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
webProxy ??= await GetWebProxy(true);
|
|
||||||
var config = AppHandler.Instance.Config;
|
|
||||||
|
|
||||||
for (var i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
|
||||||
if (responseTime > 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return responseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
|
||||||
{
|
|
||||||
var responseTime = -1;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var cts = new CancellationTokenSource();
|
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
|
|
||||||
using var client = new HttpClient(new SocketsHttpHandler()
|
|
||||||
{
|
|
||||||
Proxy = webProxy,
|
|
||||||
UseProxy = webProxy != null
|
|
||||||
});
|
|
||||||
|
|
||||||
List<int> oneTime = new();
|
|
||||||
for (var i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
var timer = Stopwatch.StartNew();
|
|
||||||
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
|
|
||||||
timer.Stop();
|
|
||||||
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
|
|
||||||
}
|
|
||||||
catch //(Exception ex)
|
|
||||||
{
|
|
||||||
//Utile.SaveLog(ex.Message, ex);
|
|
||||||
}
|
|
||||||
return responseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<WebProxy?> GetWebProxy(bool blProxy)
|
private async Task<WebProxy?> GetWebProxy(bool blProxy)
|
||||||
{
|
{
|
||||||
if (!blProxy)
|
if (!blProxy)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
if (await SocketCheck(Global.Loopback, port) == false)
|
if (await SocketCheck(Global.Loopback, port) == false)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,26 +5,20 @@ using System.Net.Sockets;
|
|||||||
|
|
||||||
namespace ServiceLib.Services;
|
namespace ServiceLib.Services;
|
||||||
|
|
||||||
public class SpeedtestService
|
public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateFunc)
|
||||||
{
|
{
|
||||||
private static readonly string _tag = "SpeedtestService";
|
private static readonly string _tag = "SpeedtestService";
|
||||||
private Config? _config;
|
private readonly Config? _config = config;
|
||||||
private Action<SpeedTestResult>? _updateFunc;
|
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
|
||||||
private static readonly ConcurrentBag<string> _lstExitLoop = new();
|
private static readonly ConcurrentBag<string> _lstExitLoop = new();
|
||||||
|
|
||||||
public SpeedtestService(Config config, Action<SpeedTestResult> updateFunc)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_updateFunc = updateFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await RunAsync(actionType, selecteds);
|
await RunAsync(actionType, selecteds);
|
||||||
await ProfileExHandler.Instance.SaveTo();
|
await ProfileExManager.Instance.SaveTo();
|
||||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
await UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +37,7 @@ public class SpeedtestService
|
|||||||
var exitLoopKey = Utils.GetGuid(false);
|
var exitLoopKey = Utils.GetGuid(false);
|
||||||
_lstExitLoop.Add(exitLoopKey);
|
_lstExitLoop.Add(exitLoopKey);
|
||||||
|
|
||||||
var lstSelected = GetClearItem(actionType, selecteds);
|
var lstSelected = await GetClearItem(actionType, selecteds);
|
||||||
|
|
||||||
switch (actionType)
|
switch (actionType)
|
||||||
{
|
{
|
||||||
@@ -65,7 +59,7 @@ public class SpeedtestService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ServerTestItem> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||||
{
|
{
|
||||||
var lstSelected = new List<ServerTestItem>();
|
var lstSelected = new List<ServerTestItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
@@ -97,19 +91,19 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
case ESpeedActionType.Tcping:
|
case ESpeedActionType.Tcping:
|
||||||
case ESpeedActionType.Realping:
|
case ESpeedActionType.Realping:
|
||||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
|
await UpdateFunc(it.IndexId, ResUI.Speedtesting, "");
|
||||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESpeedActionType.Speedtest:
|
case ESpeedActionType.Speedtest:
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait);
|
||||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESpeedActionType.Mixedtest:
|
case ESpeedActionType.Mixedtest:
|
||||||
UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
await UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
||||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, 0);
|
||||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,8 +126,8 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
var responseTime = await GetTcpingTime(it.Address, it.Port);
|
var responseTime = await GetTcpingTime(it.Address, it.Port);
|
||||||
|
|
||||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -169,11 +163,11 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
||||||
{
|
{
|
||||||
UpdateFunc("", ResUI.SpeedtestingSkip);
|
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count));
|
await UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count));
|
||||||
|
|
||||||
if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount)
|
if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount)
|
||||||
{
|
{
|
||||||
@@ -191,15 +185,13 @@ public class SpeedtestService
|
|||||||
var pid = -1;
|
var pid = -1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(selecteds);
|
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds);
|
||||||
if (pid < 0)
|
if (pid < 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
var downloadHandle = new DownloadService();
|
|
||||||
|
|
||||||
List<Task> tasks = new();
|
List<Task> tasks = new();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
@@ -213,7 +205,7 @@ public class SpeedtestService
|
|||||||
}
|
}
|
||||||
tasks.Add(Task.Run(async () =>
|
tasks.Add(Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await DoRealPing(downloadHandle, it);
|
await DoRealPing(it);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
@@ -241,7 +233,7 @@ public class SpeedtestService
|
|||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (it.ConfigType == EConfigType.Custom)
|
||||||
@@ -255,15 +247,15 @@ public class SpeedtestService
|
|||||||
var pid = -1;
|
var pid = -1;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it);
|
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
||||||
if (pid < 0)
|
if (pid < 0)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
var delay = await DoRealPing(downloadHandle, it);
|
var delay = await DoRealPing(it);
|
||||||
if (blSpeedTest)
|
if (blSpeedTest)
|
||||||
{
|
{
|
||||||
if (delay > 0)
|
if (delay > 0)
|
||||||
@@ -272,7 +264,7 @@ public class SpeedtestService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,31 +286,31 @@ public class SpeedtestService
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> DoRealPing(DownloadService downloadHandle, ServerTestItem it)
|
private async Task<int> DoRealPing(ServerTestItem it)
|
||||||
{
|
{
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||||
var responseTime = await downloadHandle.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
||||||
|
|
||||||
ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it)
|
private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it)
|
||||||
{
|
{
|
||||||
UpdateFunc(it.IndexId, "", ResUI.Speedtesting);
|
await UpdateFunc(it.IndexId, "", ResUI.Speedtesting);
|
||||||
|
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||||
var url = _config.SpeedTestItem.SpeedTestUrl;
|
var url = _config.SpeedTestItem.SpeedTestUrl;
|
||||||
var timeout = _config.SpeedTestItem.SpeedTestTimeout;
|
var timeout = _config.SpeedTestItem.SpeedTestTimeout;
|
||||||
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (success, msg) =>
|
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, async (success, msg) =>
|
||||||
{
|
{
|
||||||
decimal.TryParse(msg, out var dec);
|
decimal.TryParse(msg, out var dec);
|
||||||
if (dec > 0)
|
if (dec > 0)
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetTestSpeed(it.IndexId, dec);
|
ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec);
|
||||||
}
|
}
|
||||||
UpdateFunc(it.IndexId, "", msg);
|
await UpdateFunc(it.IndexId, "", msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,8 +350,8 @@ public class SpeedtestService
|
|||||||
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
||||||
{
|
{
|
||||||
List<List<ServerTestItem>> lstTest = new();
|
List<List<ServerTestItem>> lstTest = new();
|
||||||
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList();
|
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||||
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList();
|
var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||||
|
|
||||||
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
||||||
{
|
{
|
||||||
@@ -373,12 +365,12 @@ public class SpeedtestService
|
|||||||
return lstTest;
|
return lstTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFunc(string indexId, string delay, string speed = "")
|
private async Task UpdateFunc(string indexId, string delay, string speed = "")
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
await _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
||||||
if (indexId.IsNotEmpty() && speed.IsNotEmpty())
|
if (indexId.IsNotEmpty() && speed.IsNotEmpty())
|
||||||
{
|
{
|
||||||
ProfileExHandler.Instance.SetTestMessage(indexId, speed);
|
ProfileExManager.Instance.SetTestMessage(indexId, speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ public class StatisticsSingboxService
|
|||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
private bool _exitFlag;
|
private bool _exitFlag;
|
||||||
private ClientWebSocket? webSocket;
|
private ClientWebSocket? webSocket;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
private string Url => $"ws://{Global.Loopback}:{AppHandler.Instance.StatePort2}/traffic";
|
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
|
||||||
private static readonly string _tag = "StatisticsSingboxService";
|
private static readonly string _tag = "StatisticsSingboxService";
|
||||||
|
|
||||||
public StatisticsSingboxService(Config config, Action<ServerSpeedItem> updateFunc)
|
public StatisticsSingboxService(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -90,7 +90,7 @@ public class StatisticsSingboxService
|
|||||||
{
|
{
|
||||||
ParseOutput(result, out var up, out var down);
|
ParseOutput(result, out var up, out var down);
|
||||||
|
|
||||||
_updateFunc?.Invoke(new ServerSpeedItem()
|
await _updateFunc?.Invoke(new ServerSpeedItem()
|
||||||
{
|
{
|
||||||
ProxyUp = (long)(up / 1000),
|
ProxyUp = (long)(up / 1000),
|
||||||
ProxyDown = (long)(down / 1000)
|
ProxyDown = (long)(down / 1000)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ public class StatisticsXrayService
|
|||||||
private ServerSpeedItem _serverSpeedItem = new();
|
private ServerSpeedItem _serverSpeedItem = new();
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
private bool _exitFlag;
|
private bool _exitFlag;
|
||||||
private Action<ServerSpeedItem>? _updateFunc;
|
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||||
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort}/debug/vars";
|
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
|
||||||
|
|
||||||
public StatisticsXrayService(Config config, Action<ServerSpeedItem> updateFunc)
|
public StatisticsXrayService(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
@@ -39,7 +39,7 @@ public class StatisticsXrayService
|
|||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
var server = ParseOutput(result) ?? new ServerSpeedItem();
|
var server = ParseOutput(result) ?? new ServerSpeedItem();
|
||||||
_updateFunc?.Invoke(server);
|
await _updateFunc?.Invoke(server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace ServiceLib.Services;
|
|||||||
|
|
||||||
public class UpdateService
|
public class UpdateService
|
||||||
{
|
{
|
||||||
private Action<bool, string>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private int _timeout = 30;
|
private readonly int _timeout = 30;
|
||||||
private static readonly string _tag = "UpdateService";
|
private static readonly string _tag = "UpdateService";
|
||||||
|
|
||||||
public async Task CheckUpdateGuiN(Config config, Action<bool, string> updateFunc, bool preRelease)
|
public async Task CheckUpdateGuiN(Config config, Func<bool, string, Task> updateFunc, bool preRelease)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
var url = string.Empty;
|
var url = string.Empty;
|
||||||
@@ -20,25 +20,25 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||||
_updateFunc?.Invoke(true, Utils.UrlEncode(fileName));
|
UpdateFunc(true, Utils.UrlEncode(fileName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
||||||
var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Data?.ToString();
|
url = result.Data?.ToString();
|
||||||
fileName = Utils.GetTempPath(Utils.GetGuid());
|
fileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
@@ -46,11 +46,11 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckUpdateCore(ECoreType type, Config config, Action<bool, string> updateFunc, bool preRelease)
|
public async Task CheckUpdateCore(ECoreType type, Config config, Func<bool, string, Task> updateFunc, bool preRelease)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
var url = string.Empty;
|
var url = string.Empty;
|
||||||
@@ -61,34 +61,34 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgUnpacking);
|
UpdateFunc(false, ResUI.MsgUnpacking);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(true, fileName);
|
UpdateFunc(true, fileName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
UpdateFunc(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, type));
|
await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
|
||||||
var result = await CheckUpdateAsync(downloadHandle, type, preRelease);
|
var result = await CheckUpdateAsync(downloadHandle, type, preRelease);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
|
|
||||||
url = result.Data?.ToString();
|
url = result.Data?.ToString();
|
||||||
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
|
||||||
@@ -99,148 +99,17 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (!result.Msg.IsNullOrEmpty())
|
if (!result.Msg.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action<bool, string> updateFunc)
|
public async Task UpdateGeoFileAll(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
|
||||||
_updateFunc = updateFunc;
|
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
|
||||||
var subItem = await AppHandler.Instance.SubItems();
|
|
||||||
|
|
||||||
if (subItem is not { Count: > 0 })
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in subItem)
|
|
||||||
{
|
|
||||||
var id = item.Id.TrimEx();
|
|
||||||
var url = item.Url.TrimEx();
|
|
||||||
var userAgent = item.UserAgent.TrimEx();
|
|
||||||
var hashCode = $"{item.Remarks}->";
|
|
||||||
if (id.IsNullOrEmpty() || url.IsNullOrEmpty() || (subId.IsNotEmpty() && item.Id != subId))
|
|
||||||
{
|
|
||||||
//_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (item.Enabled == false)
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadHandle = new DownloadService();
|
|
||||||
downloadHandle.Error += (sender2, args) =>
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
|
|
||||||
};
|
|
||||||
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
|
||||||
|
|
||||||
//one url
|
|
||||||
url = Utils.GetPunycode(url);
|
|
||||||
//convert
|
|
||||||
if (item.ConvertTarget.IsNotEmpty())
|
|
||||||
{
|
|
||||||
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl;
|
|
||||||
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
|
|
||||||
if (!url.Contains("target="))
|
|
||||||
{
|
|
||||||
url += string.Format("&target={0}", item.ConvertTarget);
|
|
||||||
}
|
|
||||||
if (!url.Contains("config="))
|
|
||||||
{
|
|
||||||
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
|
|
||||||
if (blProxy && result.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
result = await downloadHandle.TryDownloadString(url, false, userAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
//more url
|
|
||||||
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
|
|
||||||
{
|
|
||||||
if (result.IsNotEmpty() && Utils.IsBase64String(result))
|
|
||||||
{
|
|
||||||
result = Utils.Base64Decode(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
|
|
||||||
foreach (var it in lstUrl)
|
|
||||||
{
|
|
||||||
var url2 = Utils.GetPunycode(it);
|
|
||||||
if (url2.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent);
|
|
||||||
if (blProxy && result2.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent);
|
|
||||||
}
|
|
||||||
if (result2.IsNotEmpty())
|
|
||||||
{
|
|
||||||
if (Utils.IsBase64String(result2))
|
|
||||||
{
|
|
||||||
result += Environment.NewLine + Utils.Base64Decode(result2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result += Environment.NewLine + result2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
|
||||||
if (result?.Length < 99)
|
|
||||||
{
|
|
||||||
_updateFunc?.Invoke(false, $"{hashCode}{result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
|
|
||||||
if (ret <= 0)
|
|
||||||
{
|
|
||||||
Logging.SaveLog("FailedImportSubscription");
|
|
||||||
Logging.SaveLog(result);
|
|
||||||
}
|
|
||||||
_updateFunc?.Invoke(false,
|
|
||||||
ret > 0
|
|
||||||
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
|
||||||
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
|
||||||
}
|
|
||||||
_updateFunc?.Invoke(false, "-------------------------------------------------------");
|
|
||||||
|
|
||||||
//await ConfigHandler.DedupServerList(config, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateGeoFileAll(Config config, Action<bool, string> updateFunc)
|
|
||||||
{
|
{
|
||||||
await UpdateGeoFiles(config, updateFunc);
|
await UpdateGeoFiles(config, updateFunc);
|
||||||
await UpdateOtherFiles(config, updateFunc);
|
await UpdateOtherFiles(config, updateFunc);
|
||||||
await UpdateSrsFileAll(config, updateFunc);
|
await UpdateSrsFileAll(config, updateFunc);
|
||||||
_updateFunc?.Invoke(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region CheckUpdate private
|
#region CheckUpdate private
|
||||||
@@ -259,14 +128,14 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new RetResult(false, ex.Message);
|
return new RetResult(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
|
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||||
var tagName = string.Empty;
|
var tagName = string.Empty;
|
||||||
if (preRelease)
|
if (preRelease)
|
||||||
{
|
{
|
||||||
@@ -300,7 +169,7 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||||
string filePath = string.Empty;
|
string filePath = string.Empty;
|
||||||
foreach (var name in coreInfo.CoreExes)
|
foreach (var name in coreInfo.CoreExes)
|
||||||
{
|
{
|
||||||
@@ -343,7 +212,7 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new SemanticVersion("");
|
return new SemanticVersion("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +221,7 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
|
||||||
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
|
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
|
||||||
SemanticVersion curVersion;
|
SemanticVersion curVersion;
|
||||||
string message;
|
string message;
|
||||||
@@ -403,7 +272,7 @@ public class UpdateService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
await UpdateFunc(false, ex.Message);
|
||||||
return new RetResult(false, ex.Message);
|
return new RetResult(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,7 +333,7 @@ public class UpdateService
|
|||||||
|
|
||||||
#region Geo private
|
#region Geo private
|
||||||
|
|
||||||
private async Task UpdateGeoFiles(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateGeoFiles(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
@@ -483,7 +352,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateOtherFiles(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateOtherFiles(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
//If it is not in China area, no update is required
|
//If it is not in China area, no update is required
|
||||||
if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
|
||||||
@@ -502,7 +371,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFileAll(Config config, Action<bool, string> updateFunc)
|
private async Task UpdateSrsFileAll(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
@@ -510,7 +379,7 @@ public class UpdateService
|
|||||||
var geoSiteFiles = new List<string>();
|
var geoSiteFiles = new List<string>();
|
||||||
|
|
||||||
//Collect used files list
|
//Collect used files list
|
||||||
var routingItems = await AppHandler.Instance.RoutingItems();
|
var routingItems = await AppManager.Instance.RoutingItems();
|
||||||
foreach (var routing in routingItems)
|
foreach (var routing in routingItems)
|
||||||
{
|
{
|
||||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||||
@@ -557,7 +426,7 @@ public class UpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSrsFile(string type, string srsName, Config config, Action<bool, string> updateFunc)
|
private async Task UpdateSrsFile(string type, string srsName, Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
|
var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
|
||||||
? Global.SingboxRulesetUrl
|
? Global.SingboxRulesetUrl
|
||||||
@@ -565,12 +434,12 @@ public class UpdateService
|
|||||||
|
|
||||||
var fileName = $"{type}-{srsName}.srs";
|
var fileName = $"{type}-{srsName}.srs";
|
||||||
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
|
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
|
||||||
var url = string.Format(srsUrl, type, $"{type}-{srsName}");
|
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
|
||||||
|
|
||||||
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
|
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadGeoFile(string url, string fileName, string targetPath, Action<bool, string> updateFunc)
|
private async Task DownloadGeoFile(string url, string fileName, string targetPath, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());
|
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
|
|
||||||
@@ -579,7 +448,7 @@ public class UpdateService
|
|||||||
{
|
{
|
||||||
if (args.Success)
|
if (args.Success)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
UpdateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -588,26 +457,31 @@ public class UpdateService
|
|||||||
File.Copy(tmpFileName, targetPath, true);
|
File.Copy(tmpFileName, targetPath, true);
|
||||||
|
|
||||||
File.Delete(tmpFileName);
|
File.Delete(tmpFileName);
|
||||||
//_updateFunc?.Invoke(true, "");
|
//await UpdateFunc(true, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, ex.Message);
|
UpdateFunc(false, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.Msg);
|
UpdateFunc(false, args.Msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
downloadHandle.Error += (sender2, args) =>
|
downloadHandle.Error += (sender2, args) =>
|
||||||
{
|
{
|
||||||
_updateFunc?.Invoke(false, args.GetException().Message);
|
UpdateFunc(false, args.GetException().Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout);
|
await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Geo private
|
#endregion Geo private
|
||||||
|
|
||||||
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
|
{
|
||||||
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -45,25 +45,25 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
var remarks = SelectedSource.Remarks;
|
var remarks = SelectedSource.Remarks;
|
||||||
if (remarks.IsNullOrEmpty())
|
if (remarks.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectedSource.Address.IsNullOrEmpty())
|
if (SelectedSource.Address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
||||||
|
|
||||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +74,12 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = await AppHandler.Instance.GetProfileItem(SelectedSource.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||||
item ??= SelectedSource;
|
item ??= SelectedSource;
|
||||||
item.Address = fileName;
|
item.Address = fileName;
|
||||||
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
|
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||||
if (item.IndexId.IsNotEmpty())
|
if (item.IndexId.IsNotEmpty())
|
||||||
{
|
{
|
||||||
SelectedSource = JsonUtils.DeepCopy(item);
|
SelectedSource = JsonUtils.DeepCopy(item);
|
||||||
@@ -88,7 +88,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FailedImportedCustomServer);
|
NoticeManager.Instance.Enqueue(ResUI.FailedImportedCustomServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
var address = SelectedSource.Address;
|
var address = SelectedSource.Address;
|
||||||
if (address.IsNullOrEmpty())
|
if (address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FailedReadConfiguration);
|
NoticeManager.Instance.Enqueue(ResUI.FailedReadConfiguration);
|
||||||
}
|
}
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class AddServerViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -43,32 +43,32 @@ public class AddServerViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource.Remarks.IsNullOrEmpty())
|
if (SelectedSource.Remarks.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectedSource.Address.IsNullOrEmpty())
|
if (SelectedSource.Address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillServerAddress);
|
NoticeManager.Instance.Enqueue(ResUI.FillServerAddress);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var port = SelectedSource.Port.ToString();
|
var port = SelectedSource.Port.ToString();
|
||||||
if (port.IsNullOrEmpty() || !Utils.IsNumeric(port)
|
if (port.IsNullOrEmpty() || !Utils.IsNumeric(port)
|
||||||
|| SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort)
|
|| SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectServerPort);
|
NoticeManager.Instance.Enqueue(ResUI.FillCorrectServerPort);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
|
if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
|
||||||
{
|
{
|
||||||
if (SelectedSource.Id.IsNullOrEmpty())
|
if (SelectedSource.Id.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillPassword);
|
NoticeManager.Instance.Enqueue(ResUI.FillPassword);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SelectedSource.Security.IsNullOrEmpty())
|
if (SelectedSource.Security.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectEncryption);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ public class AddServerViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource.Id.IsNullOrEmpty())
|
if (SelectedSource.Id.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillUUID);
|
NoticeManager.Instance.Enqueue(ResUI.FillUUID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,12 +84,12 @@ public class AddServerViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -52,14 +52,14 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
_config.WebDavItem = SelectedSource;
|
_config.WebDavItem = SelectedSource;
|
||||||
_ = await ConfigHandler.SaveConfig(_config);
|
_ = await ConfigHandler.SaveConfig(_config);
|
||||||
|
|
||||||
var result = await WebDavHandler.Instance.CheckConnection();
|
var result = await WebDavManager.Instance.CheckConnection();
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
DisplayOperationMsg(ResUI.OperationSuccess);
|
DisplayOperationMsg(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
var result = await CreateZipFileFromDirectory(fileName);
|
var result = await CreateZipFileFromDirectory(fileName);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
var result2 = await WebDavHandler.Instance.PutFile(fileName);
|
var result2 = await WebDavManager.Instance.PutFile(fileName);
|
||||||
if (result2)
|
if (result2)
|
||||||
{
|
{
|
||||||
DisplayOperationMsg(ResUI.OperationSuccess);
|
DisplayOperationMsg(ResUI.OperationSuccess);
|
||||||
@@ -78,21 +78,21 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemoteRestore()
|
private async Task RemoteRestore()
|
||||||
{
|
{
|
||||||
DisplayOperationMsg();
|
DisplayOperationMsg();
|
||||||
var fileName = Utils.GetTempPath(Utils.GetGuid());
|
var fileName = Utils.GetTempPath(Utils.GetGuid());
|
||||||
var result = await WebDavHandler.Instance.GetRawFile(fileName);
|
var result = await WebDavManager.Instance.GetRawFile(fileName);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
await LocalRestore(fileName);
|
await LocalRestore(fileName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LocalBackup(string fileName)
|
public async Task<bool> LocalBackup(string fileName)
|
||||||
@@ -105,7 +105,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -136,8 +136,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
var result = await CreateZipFileFromDirectory(fileBackup);
|
var result = await CreateZipFileFromDirectory(fileBackup);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
await service?.MyAppExitAsync(true);
|
|
||||||
await SQLiteHelper.Instance.DisposeDbConnectionAsync();
|
await SQLiteHelper.Instance.DisposeDbConnectionAsync();
|
||||||
|
|
||||||
var toPath = Utils.GetConfigPath();
|
var toPath = Utils.GetConfigPath();
|
||||||
@@ -154,11 +153,11 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
|||||||
_ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath());
|
_ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
service?.Shutdown(true);
|
AppManager.Instance.Shutdown(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
|
DisplayOperationMsg(WebDavManager.Instance.GetLastError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -11,22 +13,24 @@ namespace ServiceLib.ViewModels;
|
|||||||
public class CheckUpdateViewModel : MyReactiveObject
|
public class CheckUpdateViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private const string _geo = "GeoFiles";
|
private const string _geo = "GeoFiles";
|
||||||
private string _v2rayN = ECoreType.v2rayN.ToString();
|
private readonly string _v2rayN = ECoreType.v2rayN.ToString();
|
||||||
private List<CheckUpdateModel> _lstUpdated = [];
|
private List<CheckUpdateModel> _lstUpdated = [];
|
||||||
|
private static readonly string _tag = "CheckUpdateViewModel";
|
||||||
|
|
||||||
private IObservableCollection<CheckUpdateModel> _checkUpdateModel = new ObservableCollectionExtended<CheckUpdateModel>();
|
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels => _checkUpdateModel;
|
|
||||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||||
|
|
||||||
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
|
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
|
||||||
|
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
|
||||||
{
|
{
|
||||||
await CheckUpdate();
|
Logging.SaveLog(_tag, ex);
|
||||||
|
_ = UpdateView(_v2rayN, ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
|
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
|
||||||
@@ -41,20 +45,20 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void RefreshCheckUpdateItems()
|
private void RefreshCheckUpdateItems()
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Clear();
|
CheckUpdateModels.Clear();
|
||||||
|
|
||||||
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
|
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN));
|
CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN));
|
||||||
//Not Windows and under Win10
|
//Not Windows and under Win10
|
||||||
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
|
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
|
||||||
{
|
{
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
|
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_checkUpdateModel.Add(GetCheckUpdateModel(_geo));
|
CheckUpdateModels.Add(GetCheckUpdateModel(_geo));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
||||||
@@ -69,32 +73,31 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task SaveSelectedCoreTypes()
|
private async Task SaveSelectedCoreTypes()
|
||||||
{
|
{
|
||||||
_config.CheckUpdateItem.SelectedCoreTypes = _checkUpdateModel.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
|
_config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckUpdate()
|
private async Task CheckUpdate()
|
||||||
{
|
{
|
||||||
await Task.Run(async () =>
|
await Task.Run(CheckUpdateTask);
|
||||||
{
|
|
||||||
await CheckUpdateTask();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckUpdateTask()
|
private async Task CheckUpdateTask()
|
||||||
{
|
{
|
||||||
_lstUpdated.Clear();
|
_lstUpdated.Clear();
|
||||||
_lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true)
|
_lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true)
|
||||||
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
|
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
|
||||||
await SaveSelectedCoreTypes();
|
await SaveSelectedCoreTypes();
|
||||||
|
|
||||||
for (var k = _checkUpdateModel.Count - 1; k >= 0; k--)
|
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
|
||||||
{
|
{
|
||||||
var item = _checkUpdateModel[k];
|
var item = CheckUpdateModels[k];
|
||||||
if (item.IsSelected != true)
|
if (item.IsSelected != true)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
UpdateView(item.CoreType, "...");
|
await UpdateView(item.CoreType, "...");
|
||||||
if (item.CoreType == _geo)
|
if (item.CoreType == _geo)
|
||||||
{
|
{
|
||||||
await CheckUpdateGeo();
|
await CheckUpdateGeo();
|
||||||
@@ -132,9 +135,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateGeo()
|
private async Task CheckUpdateGeo()
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(_geo, msg);
|
await UpdateView(_geo, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdatedPlusPlus(_geo, "");
|
UpdatedPlusPlus(_geo, "");
|
||||||
@@ -149,12 +152,12 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateN(bool preRelease)
|
private async Task CheckUpdateN(bool preRelease)
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, msg);
|
await UpdateView(_v2rayN, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ResUI.OperationSuccess);
|
await UpdateView(_v2rayN, ResUI.OperationSuccess);
|
||||||
UpdatedPlusPlus(_v2rayN, msg);
|
UpdatedPlusPlus(_v2rayN, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,12 +170,12 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
||||||
{
|
{
|
||||||
void _updateUI(bool success, string msg)
|
async Task _updateUI(bool success, string msg)
|
||||||
{
|
{
|
||||||
UpdateView(model.CoreType, msg);
|
await UpdateView(model.CoreType, msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
|
await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
|
||||||
|
|
||||||
UpdatedPlusPlus(model.CoreType, msg);
|
UpdatedPlusPlus(model.CoreType, msg);
|
||||||
}
|
}
|
||||||
@@ -189,21 +192,30 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count)
|
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, false);
|
await UpdateFinishedSub(false);
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
await UpgradeCore();
|
await UpgradeCore();
|
||||||
|
|
||||||
if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true))
|
if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true))
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
UpgradeN();
|
await UpgradeN();
|
||||||
}
|
}
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, true);
|
await UpdateFinishedSub(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFinishedResult(bool blReload)
|
private async Task UpdateFinishedSub(bool blReload)
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) =>
|
||||||
|
{
|
||||||
|
_ = UpdateFinishedResult(blReload);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateFinishedResult(bool blReload)
|
||||||
{
|
{
|
||||||
if (blReload)
|
if (blReload)
|
||||||
{
|
{
|
||||||
@@ -215,7 +227,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpgradeN()
|
private async Task UpgradeN()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -224,16 +236,23 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Utils.UpgradeAppExists(out _))
|
if (!Utils.UpgradeAppExists(out var upgradeFileName))
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip);
|
await UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip);
|
||||||
|
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
||||||
|
Logging.SaveLog("UpgradeApp does not exist");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.UpgradeApp(fileName);
|
|
||||||
|
var id = ProcUtils.ProcessStart(upgradeFileName, fileName, Utils.StartupPath());
|
||||||
|
if (id > 0)
|
||||||
|
{
|
||||||
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
UpdateView(_v2rayN, ex.Message);
|
await UpdateView(_v2rayN, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +303,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully);
|
await UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully);
|
||||||
|
|
||||||
if (File.Exists(fileName))
|
if (File.Exists(fileName))
|
||||||
{
|
{
|
||||||
@@ -293,23 +312,31 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateView(string coreType, string msg)
|
private async Task UpdateView(string coreType, string msg)
|
||||||
{
|
{
|
||||||
var item = new CheckUpdateModel()
|
var item = new CheckUpdateModel()
|
||||||
{
|
{
|
||||||
CoreType = coreType,
|
CoreType = coreType,
|
||||||
Remarks = msg,
|
Remarks = msg,
|
||||||
};
|
};
|
||||||
_updateView?.Invoke(EViewAction.DispatcherCheckUpdate, item);
|
|
||||||
|
RxApp.MainThreadScheduler.Schedule(item, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = UpdateViewResult(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateViewResult(CheckUpdateModel model)
|
public async Task UpdateViewResult(CheckUpdateModel model)
|
||||||
{
|
{
|
||||||
var found = _checkUpdateModel.FirstOrDefault(t => t.CoreType == model.CoreType);
|
var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType);
|
||||||
if (found == null)
|
if (found == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var itemCopy = JsonUtils.DeepCopy(found);
|
var itemCopy = JsonUtils.DeepCopy(found);
|
||||||
itemCopy.Remarks = model.Remarks;
|
itemCopy.Remarks = model.Remarks;
|
||||||
_checkUpdateModel.Replace(found, itemCopy);
|
CheckUpdateModels.Replace(found, itemCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -9,8 +10,7 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class ClashConnectionsViewModel : MyReactiveObject
|
public class ClashConnectionsViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private IObservableCollection<ClashConnectionModel> _connectionItems = new ObservableCollectionExtended<ClashConnectionModel>();
|
public IObservableCollection<ClashConnectionModel> ConnectionItems { get; } = new ObservableCollectionExtended<ClashConnectionModel>();
|
||||||
public IObservableCollection<ClashConnectionModel> ConnectionItems => _connectionItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashConnectionModel SelectedSource { get; set; }
|
public ClashConnectionModel SelectedSource { get; set; }
|
||||||
@@ -26,7 +26,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||||
|
|
||||||
@@ -58,18 +58,22 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task GetClashConnections()
|
private async Task GetClashConnections()
|
||||||
{
|
{
|
||||||
var ret = await ClashApiHandler.Instance.GetClashConnectionsAsync();
|
var ret = await ClashApiManager.Instance.GetClashConnectionsAsync();
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = _updateView?.Invoke(EViewAction.DispatcherRefreshConnections, ret?.connections);
|
RxApp.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = RefreshConnections(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshConnections(List<ConnectionItem>? connections)
|
public async Task RefreshConnections(List<ConnectionItem>? connections)
|
||||||
{
|
{
|
||||||
_connectionItems.Clear();
|
ConnectionItems.Clear();
|
||||||
|
|
||||||
var dtNow = DateTime.Now;
|
var dtNow = DateTime.Now;
|
||||||
var lstModel = new List<ClashConnectionModel>();
|
var lstModel = new List<ClashConnectionModel>();
|
||||||
@@ -99,7 +103,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectionItems.AddRange(lstModel);
|
ConnectionItems.AddRange(lstModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClashConnectionClose(bool all)
|
public async Task ClashConnectionClose(bool all)
|
||||||
@@ -116,9 +120,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_connectionItems.Clear();
|
ConnectionItems.Clear();
|
||||||
}
|
}
|
||||||
await ClashApiHandler.Instance.ClashConnectionClose(id);
|
await ClashApiManager.Instance.ClashConnectionClose(id);
|
||||||
await GetClashConnections();
|
await GetClashConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Concurrency;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
@@ -15,11 +17,8 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
private Dictionary<string, ProvidersItem>? _providers;
|
private Dictionary<string, ProvidersItem>? _providers;
|
||||||
private readonly int _delayTimeout = 99999999;
|
private readonly int _delayTimeout = 99999999;
|
||||||
|
|
||||||
private IObservableCollection<ClashProxyModel> _proxyGroups = new ObservableCollectionExtended<ClashProxyModel>();
|
public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||||
private IObservableCollection<ClashProxyModel> _proxyDetails = new ObservableCollectionExtended<ClashProxyModel>();
|
public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||||
|
|
||||||
public IObservableCollection<ClashProxyModel> ProxyGroups => _proxyGroups;
|
|
||||||
public IObservableCollection<ClashProxyModel> ProxyDetails => _proxyDetails;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashProxyModel SelectedGroup { get; set; }
|
public ClashProxyModel SelectedGroup { get; set; }
|
||||||
@@ -43,7 +42,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -152,13 +151,13 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
{ "mode", mode.ToString().ToLower() }
|
{ "mode", mode.ToString().ToLower() }
|
||||||
};
|
};
|
||||||
await ClashApiHandler.Instance.ClashConfigUpdate(headers);
|
await ClashApiManager.Instance.ClashConfigUpdate(headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetClashProxies(bool refreshUI)
|
private async Task GetClashProxies(bool refreshUI)
|
||||||
{
|
{
|
||||||
var ret = await ClashApiHandler.Instance.GetClashProxiesAsync();
|
var ret = await ClashApiManager.Instance.GetClashProxiesAsync();
|
||||||
if (ret?.Item1 == null || ret.Item2 == null)
|
if (ret?.Item1 == null || ret.Item2 == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -168,11 +167,11 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (refreshUI)
|
if (refreshUI)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.DispatcherRefreshProxyGroups, null);
|
RxApp.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshProxyGroups()
|
public async Task RefreshProxyGroups()
|
||||||
{
|
{
|
||||||
if (_proxies == null)
|
if (_proxies == null)
|
||||||
{
|
{
|
||||||
@@ -180,9 +179,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
var selectedName = SelectedGroup?.Name;
|
var selectedName = SelectedGroup?.Name;
|
||||||
_proxyGroups.Clear();
|
ProxyGroups.Clear();
|
||||||
|
|
||||||
var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups();
|
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
||||||
if (proxyGroups != null && proxyGroups.Count > 0)
|
if (proxyGroups != null && proxyGroups.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var it in proxyGroups)
|
foreach (var it in proxyGroups)
|
||||||
@@ -196,7 +195,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_proxyGroups.Add(new ClashProxyModel()
|
ProxyGroups.Add(new ClashProxyModel()
|
||||||
{
|
{
|
||||||
Now = item.now,
|
Now = item.now,
|
||||||
Name = item.name,
|
Name = item.name,
|
||||||
@@ -212,12 +211,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var item = _proxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||||
if (item != null && item.Name.IsNotEmpty())
|
if (item != null && item.Name.IsNotEmpty())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_proxyGroups.Add(new ClashProxyModel()
|
ProxyGroups.Add(new ClashProxyModel()
|
||||||
{
|
{
|
||||||
Now = kv.Value.now,
|
Now = kv.Value.now,
|
||||||
Name = kv.Key,
|
Name = kv.Key,
|
||||||
@@ -225,15 +224,15 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_proxyGroups != null && _proxyGroups.Count > 0)
|
if (ProxyGroups != null && ProxyGroups.Count > 0)
|
||||||
{
|
{
|
||||||
if (selectedName != null && _proxyGroups.Any(t => t.Name == selectedName))
|
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
|
||||||
{
|
{
|
||||||
SelectedGroup = _proxyGroups.FirstOrDefault(t => t.Name == selectedName);
|
SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedGroup = _proxyGroups.First();
|
SelectedGroup = ProxyGroups.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -244,7 +243,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void RefreshProxyDetails(bool c)
|
private void RefreshProxyDetails(bool c)
|
||||||
{
|
{
|
||||||
_proxyDetails.Clear();
|
ProxyDetails.Clear();
|
||||||
if (!c)
|
if (!c)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -297,7 +296,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_proxyDetails.AddRange(lstDetails);
|
ProxyDetails.AddRange(lstDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProxiesItem? TryGetProxy(string name)
|
private ProxiesItem? TryGetProxy(string name)
|
||||||
@@ -352,43 +351,48 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
var selectedProxy = TryGetProxy(name);
|
var selectedProxy = TryGetProxy(name);
|
||||||
if (selectedProxy == null || selectedProxy.type != "Selector")
|
if (selectedProxy == null || selectedProxy.type != "Selector")
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode);
|
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
|
||||||
|
|
||||||
selectedProxy.now = nameNode;
|
selectedProxy.now = nameNode;
|
||||||
var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
var group = ProxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
||||||
if (group != null)
|
if (group != null)
|
||||||
{
|
{
|
||||||
group.Now = nameNode;
|
group.Now = nameNode;
|
||||||
var group2 = JsonUtils.DeepCopy(group);
|
var group2 = JsonUtils.DeepCopy(group);
|
||||||
_proxyGroups.Replace(group, group2);
|
ProxyGroups.Replace(group, group2);
|
||||||
|
|
||||||
SelectedGroup = group2;
|
SelectedGroup = group2;
|
||||||
}
|
}
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProxiesDelayTest(bool blAll = true)
|
private async Task ProxiesDelayTest(bool blAll = true)
|
||||||
{
|
{
|
||||||
ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
|
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, ProxyDetails.ToList(), async (item, result) =>
|
||||||
{
|
{
|
||||||
if (item == null || result.IsNullOrEmpty())
|
if (item == null || result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherProxiesDelayTest, new SpeedTestResult() { IndexId = item.Name, Delay = result });
|
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
||||||
|
RxApp.MainThreadScheduler.Schedule(model, (scheduler, model) =>
|
||||||
|
{
|
||||||
|
_ = ProxiesDelayTestResult(model);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProxiesDelayTestResult(SpeedTestResult result)
|
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
||||||
{
|
{
|
||||||
//UpdateHandler(false, $"{item.name}={result}");
|
//UpdateHandler(false, $"{item.name}={result}");
|
||||||
var detail = _proxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
||||||
if (detail == null)
|
if (detail == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -410,7 +414,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
detail.Delay = _delayTimeout;
|
detail.Delay = _delayTimeout;
|
||||||
detail.DelayName = string.Empty;
|
detail.DelayName = string.Empty;
|
||||||
}
|
}
|
||||||
_proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion proxy function
|
#endregion proxy function
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task Init()
|
private async Task Init()
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
var item = _config.SimpleDNSItem;
|
var item = _config.SimpleDNSItem;
|
||||||
UseSystemHosts = item.UseSystemHosts;
|
UseSystemHosts = item.UseSystemHosts;
|
||||||
AddCommonHosts = item.AddCommonHosts;
|
AddCommonHosts = item.AddCommonHosts;
|
||||||
@@ -76,14 +76,14 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
Hosts = item.Hosts;
|
Hosts = item.Hosts;
|
||||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||||
|
|
||||||
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray);
|
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||||
DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty;
|
DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty;
|
||||||
DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty;
|
DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty;
|
||||||
NormalDNSCompatible = item1?.NormalDNS ?? string.Empty;
|
NormalDNSCompatible = item1?.NormalDNS ?? string.Empty;
|
||||||
|
|
||||||
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
|
var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
SBCustomDNSEnableCompatible = item2.Enabled;
|
SBCustomDNSEnableCompatible = item2.Enabled;
|
||||||
DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty;
|
DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty;
|
||||||
DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty;
|
DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty;
|
||||||
@@ -117,7 +117,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}'))
|
if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}'))
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(NormalDNS2Compatible);
|
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(NormalDNS2Compatible);
|
||||||
if (obj2 == null)
|
if (obj2 == null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,12 +136,12 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(TunDNS2Compatible);
|
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(TunDNS2Compatible);
|
||||||
if (obj2 == null)
|
if (obj2 == null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray);
|
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||||
item1.Enabled = RayCustomDNSEnableCompatible;
|
item1.Enabled = RayCustomDNSEnableCompatible;
|
||||||
item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible;
|
item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible;
|
||||||
item1.DomainDNSAddress = DomainDNSAddressCompatible;
|
item1.DomainDNSAddress = DomainDNSAddressCompatible;
|
||||||
@@ -149,8 +149,8 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
item1.NormalDNS = NormalDNSCompatible;
|
item1.NormalDNS = NormalDNSCompatible;
|
||||||
await ConfigHandler.SaveDNSItems(_config, item1);
|
await ConfigHandler.SaveDNSItems(_config, item1);
|
||||||
|
|
||||||
var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
|
var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
item2.Enabled = RayCustomDNSEnableCompatible;
|
item2.Enabled = SBCustomDNSEnableCompatible;
|
||||||
item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible;
|
item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible;
|
||||||
item2.DomainDNSAddress = DomainDNSAddress2Compatible;
|
item2.DomainDNSAddress = DomainDNSAddress2Compatible;
|
||||||
item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2Compatible));
|
item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2Compatible));
|
||||||
|
|||||||
113
v2rayN/ServiceLib/ViewModels/FullConfigTemplateViewModel.cs
Normal file
113
v2rayN/ServiceLib/ViewModels/FullConfigTemplateViewModel.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
|
public class FullConfigTemplateViewModel : MyReactiveObject
|
||||||
|
{
|
||||||
|
#region Reactive
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool EnableFullConfigTemplate4Ray { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string FullConfigTemplate4Ray { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string FullConfigTemplate4Singbox { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string FullTunConfigTemplate4Singbox { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool AddProxyOnly4Ray { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool AddProxyOnly4Singbox { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string ProxyDetour4Ray { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string ProxyDetour4Singbox { get; set; }
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||||
|
|
||||||
|
#endregion Reactive
|
||||||
|
|
||||||
|
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
|
{
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
_updateView = updateView;
|
||||||
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await SaveSettingAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
var item = await AppManager.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);
|
||||||
|
EnableFullConfigTemplate4Singbox = item2?.Enabled ?? false;
|
||||||
|
FullConfigTemplate4Singbox = item2?.Config ?? string.Empty;
|
||||||
|
FullTunConfigTemplate4Singbox = item2?.TunConfig ?? string.Empty;
|
||||||
|
AddProxyOnly4Singbox = item2?.AddProxyOnly ?? false;
|
||||||
|
ProxyDetour4Singbox = item2?.ProxyDetour ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSettingAsync()
|
||||||
|
{
|
||||||
|
if (!await SaveXrayConfigAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!await SaveSingboxConfigAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
|
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> SaveXrayConfigAsync()
|
||||||
|
{
|
||||||
|
var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||||
|
item.Enabled = EnableFullConfigTemplate4Ray;
|
||||||
|
item.Config = null;
|
||||||
|
|
||||||
|
item.Config = FullConfigTemplate4Ray;
|
||||||
|
|
||||||
|
item.AddProxyOnly = AddProxyOnly4Ray;
|
||||||
|
item.ProxyDetour = ProxyDetour4Ray;
|
||||||
|
|
||||||
|
await ConfigHandler.SaveFullConfigTemplate(_config, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> SaveSingboxConfigAsync()
|
||||||
|
{
|
||||||
|
var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||||
|
item.Enabled = EnableFullConfigTemplate4Singbox;
|
||||||
|
item.Config = null;
|
||||||
|
item.TunConfig = null;
|
||||||
|
|
||||||
|
item.Config = FullConfigTemplate4Singbox;
|
||||||
|
item.TunConfig = FullTunConfigTemplate4Singbox;
|
||||||
|
|
||||||
|
item.AddProxyOnly = AddProxyOnly4Singbox;
|
||||||
|
item.ProxyDetour = ProxyDetour4Singbox;
|
||||||
|
|
||||||
|
await ConfigHandler.SaveFullConfigTemplate(_config, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
||||||
@@ -58,7 +58,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Concurrency;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
using Splat;
|
||||||
@@ -39,6 +40,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> FullConfigTemplateCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
||||||
@@ -70,7 +72,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
#region WhenAnyValue && ReactiveCommand
|
#region WhenAnyValue && ReactiveCommand
|
||||||
@@ -169,11 +171,15 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await DNSSettingAsync();
|
await DNSSettingAsync();
|
||||||
});
|
});
|
||||||
|
FullConfigTemplateCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await FullConfigTemplateAsync();
|
||||||
|
});
|
||||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
|
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -220,13 +226,14 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
await ConfigHandler.InitBuiltinRouting(_config);
|
await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
await ConfigHandler.InitBuiltinDNS(_config);
|
await ConfigHandler.InitBuiltinDNS(_config);
|
||||||
await ProfileExHandler.Instance.Init();
|
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||||
await CoreHandler.Instance.Init(_config, UpdateHandler);
|
await ProfileExManager.Instance.Init();
|
||||||
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
await CoreManager.Instance.Init(_config, UpdateHandler);
|
||||||
|
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||||
|
|
||||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||||
{
|
{
|
||||||
await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler);
|
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
@@ -239,95 +246,40 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region Actions
|
#region Actions
|
||||||
|
|
||||||
private void UpdateHandler(bool notify, string msg)
|
private async Task UpdateHandler(bool notify, string msg)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.SendMessage(msg);
|
NoticeManager.Instance.SendMessage(msg);
|
||||||
if (notify)
|
if (notify)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(msg);
|
NoticeManager.Instance.Enqueue(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTaskHandler(bool success, string msg)
|
private async Task UpdateTaskHandler(bool success, string msg)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.SendMessageEx(msg);
|
NoticeManager.Instance.SendMessageEx(msg);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
var indexIdOld = _config.IndexId;
|
var indexIdOld = _config.IndexId;
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (indexIdOld != _config.IndexId)
|
if (indexIdOld != _config.IndexId)
|
||||||
{
|
{
|
||||||
_ = Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null);
|
AppEvents.AdjustMainLvColWidthRequested.OnNext(Unit.Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStatisticsHandler(ServerSpeedItem update)
|
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
if (!_config.UiItem.ShowInTaskbar)
|
if (!_config.UiItem.ShowInTaskbar)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_updateView?.Invoke(EViewAction.DispatcherStatistics, update);
|
AppEvents.DispatcherStatisticsRequested.OnNext(update);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStatisticsResult(ServerSpeedItem update)
|
|
||||||
{
|
|
||||||
if (_config.GuiItem.DisplayRealTimeSpeed)
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.UpdateStatistics(update);
|
|
||||||
}
|
|
||||||
if (_config.GuiItem.EnableStatistics && (update.ProxyUp + update.ProxyDown) > 0 && DateTime.Now.Second % 9 == 0)
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.UpdateStatistics(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task MyAppExitAsync(bool blWindowsShutDown)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logging.SaveLog("MyAppExitAsync Begin");
|
|
||||||
|
|
||||||
await SysProxyHandler.UpdateSysProxy(_config, true);
|
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString());
|
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
|
||||||
await ProfileExHandler.Instance.SaveTo();
|
|
||||||
await StatisticsHandler.Instance.SaveTo();
|
|
||||||
await CoreHandler.Instance.CoreStop();
|
|
||||||
StatisticsHandler.Instance.Close();
|
|
||||||
|
|
||||||
Logging.SaveLog("MyAppExitAsync End");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!blWindowsShutDown)
|
|
||||||
{
|
|
||||||
_updateView?.Invoke(EViewAction.Shutdown, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpgradeApp(string arg)
|
|
||||||
{
|
|
||||||
if (!Utils.UpgradeAppExists(out var upgradeFileName))
|
|
||||||
{
|
|
||||||
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip);
|
|
||||||
Logging.SaveLog("UpgradeApp does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = ProcUtils.ProcessStart(upgradeFileName, arg, Utils.StartupPath());
|
|
||||||
if (id > 0)
|
|
||||||
{
|
|
||||||
await MyAppExitAsync(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowHideWindow(bool? blShow)
|
public void ShowHideWindow(bool? blShow)
|
||||||
@@ -335,18 +287,15 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
_updateView?.Invoke(EViewAction.ShowHideWindow, blShow);
|
_updateView?.Invoke(EViewAction.ShowHideWindow, blShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Shutdown(bool byUser)
|
|
||||||
{
|
|
||||||
_updateView?.Invoke(EViewAction.Shutdown, byUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Actions
|
#endregion Actions
|
||||||
|
|
||||||
#region Servers && Groups
|
#region Servers && Groups
|
||||||
|
|
||||||
private void RefreshServers()
|
private async Task RefreshServers()
|
||||||
{
|
{
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString());
|
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSubscriptions()
|
private void RefreshSubscriptions()
|
||||||
@@ -378,7 +327,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (item.IndexId == _config.IndexId)
|
if (item.IndexId == _config.IndexId)
|
||||||
{
|
{
|
||||||
await Reload();
|
await Reload();
|
||||||
@@ -393,16 +342,16 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
RefreshSubscriptions();
|
RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +363,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task ScanScreenResult(byte[]? bytes)
|
public async Task ScanScreenResult(byte[]? bytes)
|
||||||
{
|
{
|
||||||
var result = QRCodeHelper.ParseBarcode(bytes);
|
var result = QRCodeUtils.ParseBarcode(bytes);
|
||||||
await AddScanResultAsync(result);
|
await AddScanResultAsync(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +380,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = QRCodeHelper.ParseBarcode(fileName);
|
var result = QRCodeUtils.ParseBarcode(fileName);
|
||||||
await AddScanResultAsync(result);
|
await AddScanResultAsync(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,20 +388,20 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound);
|
NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
RefreshSubscriptions();
|
RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,7 +420,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
|
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
|
||||||
{
|
{
|
||||||
await (new UpdateService()).UpdateSubscriptionProcess(_config, subId, blProxy, UpdateTaskHandler);
|
await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Subscription
|
#endregion Subscription
|
||||||
@@ -508,16 +457,25 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FullConfigTemplateAsync()
|
||||||
|
{
|
||||||
|
var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null);
|
||||||
|
if (ret == true)
|
||||||
|
{
|
||||||
|
await Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RebootAsAdmin()
|
public async Task RebootAsAdmin()
|
||||||
{
|
{
|
||||||
ProcUtils.RebootAsAdmin();
|
ProcUtils.RebootAsAdmin();
|
||||||
await MyAppExitAsync(false);
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ClearServerStatistics()
|
private async Task ClearServerStatistics()
|
||||||
{
|
{
|
||||||
await StatisticsHandler.Instance.ClearAllServerStatistics();
|
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OpenTheFileLocation()
|
private async Task OpenTheFileLocation()
|
||||||
@@ -561,7 +519,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
});
|
});
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherReload, null);
|
RxApp.MainThreadScheduler.Schedule(() => _ = ReloadResult());
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
if (_hasNextReloadJob)
|
if (_hasNextReloadJob)
|
||||||
@@ -571,7 +529,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadResult()
|
public async Task ReloadResult()
|
||||||
{
|
{
|
||||||
// BlReloadEnabled = true;
|
// BlReloadEnabled = true;
|
||||||
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
|
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
|
||||||
@@ -581,19 +539,21 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
|
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ TabMainSelectedIndex = 0; }
|
{
|
||||||
|
TabMainSelectedIndex = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadCore()
|
private async Task LoadCore()
|
||||||
{
|
{
|
||||||
var node = await ConfigHandler.GetDefaultServer(_config);
|
var node = await ConfigHandler.GetDefaultServer(_config);
|
||||||
await CoreHandler.Instance.LoadCore(node);
|
await CoreManager.Instance.LoadCore(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseCore()
|
public async Task CloseCore()
|
||||||
{
|
{
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await CoreHandler.Instance.CoreStop();
|
await CoreManager.Instance.CoreStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AutoHideStartup()
|
private async Task AutoHideStartup()
|
||||||
@@ -616,7 +576,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler);
|
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
|
||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
@@ -7,8 +8,8 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class MsgViewModel : MyReactiveObject
|
public class MsgViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private ConcurrentQueue<string> _queueMsg = new();
|
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||||
private int _numMaxMsg = 500;
|
private readonly int _numMaxMsg = 500;
|
||||||
private bool _lastMsgFilterNotAvailable;
|
private bool _lastMsgFilterNotAvailable;
|
||||||
private bool _blLockShow = false;
|
private bool _blLockShow = false;
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
||||||
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
||||||
@@ -34,12 +35,10 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
y => y == true)
|
y => y == true)
|
||||||
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
|
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
|
||||||
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.SendMsgView.ToString()).Subscribe(OnNext);
|
AppEvents.SendMsgViewRequested
|
||||||
}
|
.AsObservable()
|
||||||
|
//.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
private async void OnNext(string x)
|
.Subscribe(async content => await AppendQueueMsg(content));
|
||||||
{
|
|
||||||
await AppendQueueMsg(x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AppendQueueMsg(string msg)
|
private async Task AppendQueueMsg(string msg)
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region Tun mode
|
#region Tun mode
|
||||||
|
|
||||||
|
[Reactive] public bool TunAutoRoute { get; set; }
|
||||||
[Reactive] public bool TunStrictRoute { get; set; }
|
[Reactive] public bool TunStrictRoute { get; set; }
|
||||||
[Reactive] public string TunStack { get; set; }
|
[Reactive] public string TunStack { get; set; }
|
||||||
[Reactive] public int TunMtu { get; set; }
|
[Reactive] public int TunMtu { get; set; }
|
||||||
@@ -108,7 +109,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -201,6 +202,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region Tun mode
|
#region Tun mode
|
||||||
|
|
||||||
|
TunAutoRoute = _config.TunModeItem.AutoRoute;
|
||||||
TunStrictRoute = _config.TunModeItem.StrictRoute;
|
TunStrictRoute = _config.TunModeItem.StrictRoute;
|
||||||
TunStack = _config.TunModeItem.Stack;
|
TunStack = _config.TunModeItem.Stack;
|
||||||
TunMtu = _config.TunModeItem.Mtu;
|
TunMtu = _config.TunModeItem.Mtu;
|
||||||
@@ -274,7 +276,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
|
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
|
||||||
|| localPort <= 0 || localPort >= Global.MaxPort)
|
|| localPort <= 0 || localPort >= Global.MaxPort)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics
|
var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics
|
||||||
@@ -354,6 +356,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol;
|
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol;
|
||||||
|
|
||||||
//tun mode
|
//tun mode
|
||||||
|
_config.TunModeItem.AutoRoute = TunAutoRoute;
|
||||||
_config.TunModeItem.StrictRoute = TunStrictRoute;
|
_config.TunModeItem.StrictRoute = TunStrictRoute;
|
||||||
_config.TunModeItem.Stack = TunStack;
|
_config.TunModeItem.Stack = TunStack;
|
||||||
_config.TunModeItem.Mtu = TunMtu;
|
_config.TunModeItem.Mtu = TunMtu;
|
||||||
@@ -366,14 +369,14 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||||
{
|
{
|
||||||
await AutoStartupHandler.UpdateTask(_config);
|
await AutoStartupHandler.UpdateTask(_config);
|
||||||
AppHandler.Instance.Reset();
|
AppManager.Instance.Reset();
|
||||||
|
|
||||||
NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
@@ -22,13 +23,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
private IObservableCollection<ProfileItemModel> _profileItems = new ObservableCollectionExtended<ProfileItemModel>();
|
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||||
public IObservableCollection<ProfileItemModel> ProfileItems => _profileItems;
|
|
||||||
|
|
||||||
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>();
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
public IObservableCollection<SubItem> SubItems => _subItems;
|
|
||||||
|
|
||||||
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>();
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItemModel SelectedProfile { get; set; }
|
public ProfileItemModel SelectedProfile { get; set; }
|
||||||
@@ -100,7 +97,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
#region WhenAnyValue && ReactiveCommand
|
#region WhenAnyValue && ReactiveCommand
|
||||||
@@ -126,7 +123,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.ServerFilter,
|
x => x.ServerFilter,
|
||||||
y => y != null && _serverFilter != y)
|
y => y != null && _serverFilter != y)
|
||||||
.Subscribe(c => ServerFilterChanged(c));
|
.Subscribe(async c => await ServerFilterChanged(c));
|
||||||
|
|
||||||
//servers delete
|
//servers delete
|
||||||
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -247,10 +244,19 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
if (_updateView != null)
|
#region AppEvents
|
||||||
{
|
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext);
|
AppEvents.ProfilesRefreshRequested
|
||||||
}
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
AppEvents.DispatcherStatisticsRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
@@ -263,32 +269,27 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedServer = new();
|
SelectedServer = new();
|
||||||
|
|
||||||
await RefreshSubscriptions();
|
await RefreshSubscriptions();
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
#endregion Init
|
||||||
|
|
||||||
#region Actions
|
#region Actions
|
||||||
|
|
||||||
private async void OnNext(string x)
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reload()
|
private void Reload()
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSpeedTestResult(SpeedTestResult result)
|
public async Task SetSpeedTestResult(SpeedTestResult result)
|
||||||
{
|
{
|
||||||
if (result.IndexId.IsNullOrEmpty())
|
if (result.IndexId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.SendMessageEx(result.Delay);
|
NoticeManager.Instance.SendMessageEx(result.Delay);
|
||||||
NoticeHandler.Instance.Enqueue(result.Delay);
|
NoticeManager.Instance.Enqueue(result.Delay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
|
var item = ProfileItems.FirstOrDefault(it => it.IndexId == result.IndexId);
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -307,11 +308,18 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(ServerSpeedItem update)
|
public async Task UpdateStatistics(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
|
if (!_config.GuiItem.EnableStatistics
|
||||||
|
|| (update.ProxyUp + update.ProxyDown) <= 0
|
||||||
|
|| DateTime.Now.Second % 3 != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
|
var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
item.TodayDown = Utils.HumanFy(update.TodayDown);
|
item.TodayDown = Utils.HumanFy(update.TodayDown);
|
||||||
@@ -336,11 +344,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AutofitColumnWidthAsync()
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Actions
|
#endregion Actions
|
||||||
|
|
||||||
#region Servers && Groups
|
#region Servers && Groups
|
||||||
@@ -353,12 +356,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
_config.SubIndexId = SelectedSub?.Id;
|
_config.SubIndexId = SelectedSub?.Id;
|
||||||
|
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
{
|
{
|
||||||
if (!c)
|
if (!c)
|
||||||
{
|
{
|
||||||
@@ -367,22 +370,24 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
_serverFilter = ServerFilter;
|
_serverFilter = ServerFilter;
|
||||||
if (_serverFilter.IsNullOrEmpty())
|
if (_serverFilter.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshServers()
|
public async Task RefreshServers()
|
||||||
{
|
{
|
||||||
MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString());
|
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
private async Task RefreshServersBiz()
|
||||||
{
|
{
|
||||||
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
||||||
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||||
|
|
||||||
_profileItems.Clear();
|
ProfileItems.Clear();
|
||||||
_profileItems.AddRange(lstModel);
|
ProfileItems.AddRange(lstModel);
|
||||||
if (lstModel.Count > 0)
|
if (lstModel.Count > 0)
|
||||||
{
|
{
|
||||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||||
@@ -395,36 +400,38 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = lstModel.First();
|
SelectedProfile = lstModel.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
public async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
_subItems.Clear();
|
SubItems.Clear();
|
||||||
|
|
||||||
_subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||||
|
|
||||||
foreach (var item in await AppHandler.Instance.SubItems())
|
foreach (var item in await AppManager.Instance.SubItems())
|
||||||
{
|
{
|
||||||
_subItems.Add(item);
|
SubItems.Add(item);
|
||||||
}
|
}
|
||||||
if (_config.SubIndexId != null && _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null)
|
if (_config.SubIndexId != null && SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null)
|
||||||
{
|
{
|
||||||
SelectedSub = _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId);
|
SelectedSub = SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedSub = _subItems.First();
|
SelectedSub = SubItems.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
{
|
{
|
||||||
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, filter);
|
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, filter);
|
||||||
|
|
||||||
await ConfigHandler.SetDefaultServer(_config, lstModel);
|
await ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||||
|
|
||||||
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
|
||||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
|
||||||
lstModel = (from t in lstModel
|
lstModel = (from t in lstModel
|
||||||
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
||||||
from t22 in t2b.DefaultIfEmpty()
|
from t22 in t2b.DefaultIfEmpty()
|
||||||
@@ -474,7 +481,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
foreach (var profile in orderProfiles)
|
foreach (var profile in orderProfiles)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(profile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
|
||||||
if (item is not null)
|
if (item is not null)
|
||||||
{
|
{
|
||||||
lstSelected.Add(item);
|
lstSelected.Add(item);
|
||||||
@@ -495,10 +502,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eConfigType = item.ConfigType;
|
eConfigType = item.ConfigType;
|
||||||
@@ -514,7 +521,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (item.IndexId == _config.IndexId)
|
if (item.IndexId == _config.IndexId)
|
||||||
{
|
{
|
||||||
Reload();
|
Reload();
|
||||||
@@ -536,12 +543,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId);
|
var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId);
|
||||||
|
|
||||||
await ConfigHandler.RemoveServers(_config, lstSelected);
|
await ConfigHandler.RemoveServers(_config, lstSelected);
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
if (lstSelected.Count == _profileItems.Count)
|
if (lstSelected.Count == ProfileItems.Count)
|
||||||
{
|
{
|
||||||
_profileItems.Clear();
|
ProfileItems.Clear();
|
||||||
}
|
}
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
if (exists)
|
if (exists)
|
||||||
{
|
{
|
||||||
Reload();
|
Reload();
|
||||||
@@ -553,10 +560,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
|
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
|
||||||
if (tuple.Item1 > 0 || tuple.Item2 > 0)
|
if (tuple.Item1 > 0 || tuple.Item2 > 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CopyServer()
|
private async Task CopyServer()
|
||||||
@@ -568,8 +575,8 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
|
if (await ConfigHandler.CopyServer(_config, lstSelected) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,16 +599,16 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = await AppHandler.Instance.GetProfileItem(indexId);
|
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
|
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,10 +628,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task ShareServerAsync()
|
public async Task ShareServerAsync()
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var url = FmtHandler.GetShareUri(item);
|
var url = FmtHandler.GetShareUri(item);
|
||||||
@@ -647,12 +654,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
|
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
|
||||||
if (ret.Success != true)
|
if (ret.Success != true)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ret?.Data?.ToString() == _config.IndexId)
|
if (ret?.Data?.ToString() == _config.IndexId)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -675,14 +682,14 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_dicHeaderSort[colName] = !asc;
|
_dicHeaderSort[colName] = !asc;
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveInvalidServerResult()
|
public async Task RemoveInvalidServerResult()
|
||||||
{
|
{
|
||||||
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
|
var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId);
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
//move server
|
//move server
|
||||||
@@ -700,9 +707,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
|
await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id);
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
|
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
SelectedMoveToGroup = null;
|
SelectedMoveToGroup = null;
|
||||||
SelectedMoveToGroup = new();
|
SelectedMoveToGroup = new();
|
||||||
}
|
}
|
||||||
@@ -712,7 +719,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId);
|
var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,18 +730,18 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0)
|
if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem)
|
public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem)
|
||||||
{
|
{
|
||||||
var targetIndex = _profileItems.IndexOf(targetItem);
|
var targetIndex = ProfileItems.IndexOf(targetItem);
|
||||||
if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex)
|
if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex)
|
||||||
{
|
{
|
||||||
if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0)
|
if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0)
|
||||||
{
|
{
|
||||||
RefreshServers();
|
await RefreshServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,7 +750,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (actionType == ESpeedActionType.Mixedtest)
|
if (actionType == ESpeedActionType.Mixedtest)
|
||||||
{
|
{
|
||||||
SelectedProfiles = _profileItems;
|
SelectedProfiles = ProfileItems;
|
||||||
}
|
}
|
||||||
var lstSelected = await GetProfileItems(false);
|
var lstSelected = await GetProfileItems(false);
|
||||||
if (lstSelected == null)
|
if (lstSelected == null)
|
||||||
@@ -751,7 +758,14 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_speedtestService ??= new SpeedtestService(_config, (SpeedTestResult result) => _updateView?.Invoke(EViewAction.DispatcherSpeedTest, result));
|
_speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) =>
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(result, (scheduler, result) =>
|
||||||
|
{
|
||||||
|
_ = SetSpeedTestResult(result);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
_speedtestService?.RunLoop(actionType, lstSelected);
|
_speedtestService?.RunLoop(actionType, lstSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,10 +776,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task Export2ClientConfigAsync(bool blClipboard)
|
private async Task Export2ClientConfigAsync(bool blClipboard)
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (blClipboard)
|
if (blClipboard)
|
||||||
@@ -773,12 +787,12 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
|
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(result.Msg);
|
NoticeManager.Instance.Enqueue(result.Msg);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data);
|
await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data);
|
||||||
NoticeHandler.Instance.SendMessage(ResUI.OperationSuccess);
|
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -796,11 +810,11 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
|
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(result.Msg);
|
NoticeManager.Instance.Enqueue(result.Msg);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName));
|
NoticeManager.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,7 +847,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
|
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
|
||||||
}
|
}
|
||||||
NoticeHandler.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,7 +864,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item = await AppHandler.Instance.GetSubItem(_config.SubIndexId);
|
item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -83,7 +83,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (!hasRule)
|
if (!hasRule)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process"));
|
NoticeManager.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
//NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItem SelectedRouting { get; set; }
|
public RoutingItem SelectedRouting { get; set; }
|
||||||
|
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
|
||||||
private IObservableCollection<RulesItemModel> _rulesItems = new ObservableCollectionExtended<RulesItemModel>();
|
|
||||||
public IObservableCollection<RulesItemModel> RulesItems => _rulesItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RulesItemModel SelectedSource { get; set; }
|
public RulesItemModel SelectedSource { get; set; }
|
||||||
@@ -37,7 +35,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
var canEditRemove = this.WhenAnyValue(
|
var canEditRemove = this.WhenAnyValue(
|
||||||
@@ -101,7 +99,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public void RefreshRulesItems()
|
public void RefreshRulesItems()
|
||||||
{
|
{
|
||||||
_rulesItems.Clear();
|
RulesItems.Clear();
|
||||||
|
|
||||||
foreach (var item in _rules)
|
foreach (var item in _rules)
|
||||||
{
|
{
|
||||||
@@ -118,7 +116,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
Enabled = item.Enabled,
|
Enabled = item.Enabled,
|
||||||
Remarks = item.Remarks,
|
Remarks = item.Remarks,
|
||||||
};
|
};
|
||||||
_rulesItems.Add(it);
|
RulesItems.Add(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +149,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||||
@@ -174,7 +172,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +203,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +224,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
string remarks = SelectedRouting.Remarks;
|
string remarks = SelectedRouting.Remarks;
|
||||||
if (remarks.IsNullOrEmpty())
|
if (remarks.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = SelectedRouting;
|
var item = SelectedRouting;
|
||||||
@@ -239,12 +237,12 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (await ConfigHandler.SaveRoutingItem(_config, item) == 0)
|
if (await ConfigHandler.SaveRoutingItem(_config, item) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +264,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
RefreshRulesItems();
|
RefreshRulesItems();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +279,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
RefreshRulesItems();
|
RefreshRulesItems();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +288,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
var url = SelectedRouting.Url;
|
var url = SelectedRouting.Url;
|
||||||
if (url.IsNullOrEmpty())
|
if (url.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.MsgNeedUrl);
|
NoticeManager.Instance.Enqueue(ResUI.MsgNeedUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +298,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
RefreshRulesItems();
|
RefreshRulesItems();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
#region Reactive
|
#region Reactive
|
||||||
|
|
||||||
private IObservableCollection<RoutingItemModel> _routingItems = new ObservableCollectionExtended<RoutingItemModel>();
|
public IObservableCollection<RoutingItemModel> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItemModel>();
|
||||||
public IObservableCollection<RoutingItemModel> RoutingItems => _routingItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItemModel SelectedSource { get; set; }
|
public RoutingItemModel SelectedSource { get; set; }
|
||||||
@@ -20,9 +19,6 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public string DomainStrategy { get; set; }
|
public string DomainStrategy { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public string DomainMatcher { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string DomainStrategy4Singbox { get; set; }
|
public string DomainStrategy4Singbox { get; set; }
|
||||||
|
|
||||||
@@ -38,7 +34,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
var canEditRemove = this.WhenAnyValue(
|
var canEditRemove = this.WhenAnyValue(
|
||||||
@@ -75,7 +71,6 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
SelectedSource = new();
|
SelectedSource = new();
|
||||||
|
|
||||||
DomainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
DomainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
||||||
DomainMatcher = _config.RoutingBasicItem.DomainMatcher;
|
|
||||||
DomainStrategy4Singbox = _config.RoutingBasicItem.DomainStrategy4Singbox;
|
DomainStrategy4Singbox = _config.RoutingBasicItem.DomainStrategy4Singbox;
|
||||||
|
|
||||||
await ConfigHandler.InitBuiltinRouting(_config);
|
await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
@@ -86,9 +81,9 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshRoutingItems()
|
public async Task RefreshRoutingItems()
|
||||||
{
|
{
|
||||||
_routingItems.Clear();
|
RoutingItems.Clear();
|
||||||
|
|
||||||
var routings = await AppHandler.Instance.RoutingItems();
|
var routings = await AppManager.Instance.RoutingItems();
|
||||||
foreach (var item in routings)
|
foreach (var item in routings)
|
||||||
{
|
{
|
||||||
var it = new RoutingItemModel()
|
var it = new RoutingItemModel()
|
||||||
@@ -102,24 +97,23 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
|
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
|
||||||
Sort = item.Sort,
|
Sort = item.Sort,
|
||||||
};
|
};
|
||||||
_routingItems.Add(it);
|
RoutingItems.Add(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveRoutingAsync()
|
private async Task SaveRoutingAsync()
|
||||||
{
|
{
|
||||||
_config.RoutingBasicItem.DomainStrategy = DomainStrategy;
|
_config.RoutingBasicItem.DomainStrategy = DomainStrategy;
|
||||||
_config.RoutingBasicItem.DomainMatcher = DomainMatcher;
|
|
||||||
_config.RoutingBasicItem.DomainStrategy4Singbox = DomainStrategy4Singbox;
|
_config.RoutingBasicItem.DomainStrategy4Singbox = DomainStrategy4Singbox;
|
||||||
|
|
||||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +128,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id);
|
item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -151,7 +145,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty())
|
if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||||
@@ -160,7 +154,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
foreach (var it in SelectedSources ?? [SelectedSource])
|
foreach (var it in SelectedSources ?? [SelectedSource])
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetRoutingItem(it?.Id);
|
var item = await AppManager.Instance.GetRoutingItem(it?.Id);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
await ConfigHandler.RemoveRoutingItem(item);
|
await ConfigHandler.RemoveRoutingItem(item);
|
||||||
@@ -173,10 +167,10 @@ public class RoutingSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RoutingAdvancedSetDefault()
|
public async Task RoutingAdvancedSetDefault()
|
||||||
{
|
{
|
||||||
var item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id);
|
var item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@@ -11,11 +13,9 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
private IObservableCollection<RoutingItem> _routingItems = new ObservableCollectionExtended<RoutingItem>();
|
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
||||||
public IObservableCollection<RoutingItem> RoutingItems => _routingItems;
|
|
||||||
|
|
||||||
private IObservableCollection<ComboItem> _servers = new ObservableCollectionExtended<ComboItem>();
|
public IObservableCollection<ComboItem> Servers { get; } = new ObservableCollectionExtended<ComboItem>();
|
||||||
public IObservableCollection<ComboItem> Servers => _servers;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItem SelectedRouting { get; set; }
|
public RoutingItem SelectedRouting { get; set; }
|
||||||
@@ -100,7 +100,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
SelectedRouting = new();
|
SelectedRouting = new();
|
||||||
SelectedServer = new();
|
SelectedServer = new();
|
||||||
RunningServerToolTipText = "-";
|
RunningServerToolTipText = "-";
|
||||||
@@ -197,10 +197,20 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
#region AppEvents
|
||||||
|
|
||||||
if (updateView != null)
|
if (updateView != null)
|
||||||
{
|
{
|
||||||
InitUpdateView(updateView);
|
InitUpdateView(updateView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppEvents.DispatcherStatisticsRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,19 +226,17 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
if (_updateView != null)
|
if (_updateView != null)
|
||||||
{
|
{
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext);
|
AppEvents.ProfilesRefreshRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnNext(string x)
|
|
||||||
{
|
|
||||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CopyProxyCmdToClipboard()
|
private async Task CopyProxyCmdToClipboard()
|
||||||
{
|
{
|
||||||
var cmd = Utils.IsWindows() ? "set" : "export";
|
var cmd = Utils.IsWindows() ? "set" : "export";
|
||||||
var address = $"{Global.Loopback}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}";
|
var address = $"{Global.Loopback}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}";
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}");
|
sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}");
|
||||||
@@ -263,7 +271,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
await service.UpdateSubscriptionProcess("", blProxy);
|
await service.UpdateSubscriptionProcess("", blProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshServersBiz()
|
private async Task RefreshServersBiz()
|
||||||
{
|
{
|
||||||
await RefreshServersMenu();
|
await RefreshServersMenu();
|
||||||
|
|
||||||
@@ -283,9 +291,9 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RefreshServersMenu()
|
private async Task RefreshServersMenu()
|
||||||
{
|
{
|
||||||
var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, "");
|
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
|
||||||
|
|
||||||
_servers.Clear();
|
Servers.Clear();
|
||||||
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||||
{
|
{
|
||||||
BlServers = false;
|
BlServers = false;
|
||||||
@@ -299,7 +307,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
string name = it.GetSummary();
|
string name = it.GetSummary();
|
||||||
|
|
||||||
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
||||||
_servers.Add(item);
|
Servers.Add(item);
|
||||||
if (_config.IndexId == it.IndexId)
|
if (_config.IndexId == it.IndexId)
|
||||||
{
|
{
|
||||||
SelectedServer = item;
|
SelectedServer = item;
|
||||||
@@ -332,18 +340,24 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
|
await TestServerAvailabilitySub(ResUI.Speedtesting);
|
||||||
|
|
||||||
var msg = await Task.Run(async () =>
|
var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
|
||||||
{
|
|
||||||
return await ConnectionHandler.Instance.RunAvailabilityCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
NoticeHandler.Instance.SendMessageEx(msg);
|
NoticeManager.Instance.SendMessageEx(msg);
|
||||||
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
|
await TestServerAvailabilitySub(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TestServerAvailabilityResult(string msg)
|
private async Task TestServerAvailabilitySub(string msg)
|
||||||
|
{
|
||||||
|
RxApp.MainThreadScheduler.Schedule(msg, (scheduler, msg) =>
|
||||||
|
{
|
||||||
|
_ = TestServerAvailabilityResult(msg);
|
||||||
|
return Disposable.Empty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TestServerAvailabilityResult(string msg)
|
||||||
{
|
{
|
||||||
RunningInfoDisplay = msg;
|
RunningInfoDisplay = msg;
|
||||||
}
|
}
|
||||||
@@ -358,7 +372,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
_config.SystemProxyItem.SysProxyType = type;
|
_config.SystemProxyItem.SysProxyType = type;
|
||||||
await ChangeSystemProxyAsync(type, true);
|
await ChangeSystemProxyAsync(type, true);
|
||||||
NoticeHandler.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
|
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
|
||||||
|
|
||||||
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
|
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
@@ -381,13 +395,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshRoutingsMenu()
|
public async Task RefreshRoutingsMenu()
|
||||||
{
|
{
|
||||||
_routingItems.Clear();
|
RoutingItems.Clear();
|
||||||
|
|
||||||
BlRouting = true;
|
BlRouting = true;
|
||||||
var routings = await AppHandler.Instance.RoutingItems();
|
var routings = await AppManager.Instance.RoutingItems();
|
||||||
foreach (var item in routings)
|
foreach (var item in routings)
|
||||||
{
|
{
|
||||||
_routingItems.Add(item);
|
RoutingItems.Add(item);
|
||||||
if (item.IsActive)
|
if (item.IsActive)
|
||||||
{
|
{
|
||||||
SelectedRouting = item;
|
SelectedRouting = item;
|
||||||
@@ -407,7 +421,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = await AppHandler.Instance.GetRoutingItem(SelectedRouting?.Id);
|
var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -415,7 +429,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
||||||
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
||||||
}
|
}
|
||||||
@@ -474,11 +488,11 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else if (Utils.IsLinux())
|
else if (Utils.IsLinux())
|
||||||
{
|
{
|
||||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||||
}
|
}
|
||||||
else if (Utils.IsOSX())
|
else if (Utils.IsOSX())
|
||||||
{
|
{
|
||||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -490,10 +504,10 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
public async Task InboundDisplayStatus()
|
public async Task InboundDisplayStatus()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.Append($"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
||||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||||
{
|
{
|
||||||
sb.Append($",{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks2)}");
|
sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}");
|
||||||
}
|
}
|
||||||
sb.Append(']');
|
sb.Append(']');
|
||||||
InboundDisplay = $"{ResUI.LabLocal}:{sb}";
|
InboundDisplay = $"{ResUI.LabLocal}:{sb}";
|
||||||
@@ -501,8 +515,8 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
if (_config.Inbound.First().AllowLANConn)
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
{
|
{
|
||||||
var lan = _config.Inbound.First().NewPort4LAN
|
var lan = _config.Inbound.First().NewPort4LAN
|
||||||
? $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
|
? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
|
||||||
: $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}]";
|
: $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]";
|
||||||
InboundLanDisplay = $"{ResUI.LabLAN}:{lan}";
|
InboundLanDisplay = $"{ResUI.LabLAN}:{lan}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -512,8 +526,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatistics(ServerSpeedItem update)
|
public async Task UpdateStatistics(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
|
if (!_config.GuiItem.DisplayRealTimeSpeed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.IsRunningCore(ECoreType.sing_box))
|
if (_config.IsRunningCore(ECoreType.sing_box))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class SubEditViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -29,7 +29,7 @@ public class SubEditViewModel : MyReactiveObject
|
|||||||
var remarks = SelectedSource.Remarks;
|
var remarks = SelectedSource.Remarks;
|
||||||
if (remarks.IsNullOrEmpty())
|
if (remarks.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,25 +39,25 @@ public class SubEditViewModel : MyReactiveObject
|
|||||||
var uri = Utils.TryUri(url);
|
var uri = Utils.TryUri(url);
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip);
|
NoticeManager.Instance.Enqueue(ResUI.InvalidUrlTip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Do not allow http protocol
|
//Do not allow http protocol
|
||||||
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
|
||||||
//return;
|
//return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public class SubSettingViewModel : MyReactiveObject
|
public class SubSettingViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private IObservableCollection<SubItem> _subItems = new ObservableCollectionExtended<SubItem>();
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
public IObservableCollection<SubItem> SubItems => _subItems;
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedSource { get; set; }
|
public SubItem SelectedSource { get; set; }
|
||||||
@@ -24,7 +23,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
{
|
{
|
||||||
_config = AppHandler.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
var canEditRemove = this.WhenAnyValue(
|
var canEditRemove = this.WhenAnyValue(
|
||||||
@@ -60,8 +59,8 @@ public class SubSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshSubItems()
|
public async Task RefreshSubItems()
|
||||||
{
|
{
|
||||||
_subItems.Clear();
|
SubItems.Clear();
|
||||||
_subItems.AddRange(await AppHandler.Instance.SubItems());
|
SubItems.AddRange(await AppManager.Instance.SubItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditSubAsync(bool blNew)
|
public async Task EditSubAsync(bool blNew)
|
||||||
@@ -73,7 +72,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item = await AppHandler.Instance.GetSubItem(SelectedSource?.Id);
|
item = await AppManager.Instance.GetSubItem(SelectedSource?.Id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -98,7 +97,7 @@ public class SubSettingViewModel : MyReactiveObject
|
|||||||
await ConfigHandler.DeleteSubItem(_config, it.Id);
|
await ConfigHandler.DeleteSubItem(_config, it.Id);
|
||||||
}
|
}
|
||||||
await RefreshSubItems();
|
await RefreshSubItems();
|
||||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using ServiceLib.Manager;
|
||||||
using Splat;
|
using Splat;
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
using v2rayN.Desktop.Views;
|
using v2rayN.Desktop.Views;
|
||||||
@@ -25,7 +26,7 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
AppHandler.Instance.InitComponents();
|
AppManager.Instance.InitComponents();
|
||||||
|
|
||||||
desktop.Exit += OnExit;
|
desktop.Exit += OnExit;
|
||||||
desktop.MainWindow = new MainWindow();
|
desktop.MainWindow = new MainWindow();
|
||||||
@@ -73,11 +74,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
private async void MenuExit_Click(object? sender, EventArgs e)
|
private async void MenuExit_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
await AppManager.Instance.AppExitAsync(false);
|
||||||
if (service != null)
|
AppManager.Instance.Shutdown(true);
|
||||||
{
|
|
||||||
await service.MyAppExitAsync(true);
|
|
||||||
}
|
|
||||||
service?.Shutdown(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<x:Double x:Key="IconButtonWidth">32</x:Double>
|
<x:Double x:Key="IconButtonWidth">32</x:Double>
|
||||||
<x:Double x:Key="IconButtonHeight">32</x:Double>
|
<x:Double x:Key="IconButtonHeight">32</x:Double>
|
||||||
|
<x:Double x:Key="MenuFlyoutMaxHeight">1000</x:Double>
|
||||||
|
|
||||||
<Thickness x:Key="Margin2">2</Thickness>
|
<Thickness x:Key="Margin2">2</Thickness>
|
||||||
<Thickness x:Key="MarginLr4">4,0</Thickness>
|
<Thickness x:Key="MarginLr4">4,0</Thickness>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user