mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-26 20:21:35 +08:00
Update On Fri Sep 20 20:34:48 CEST 2024
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -769,3 +769,4 @@ Update On Mon Sep 16 20:36:05 CEST 2024
|
||||
Update On Tue Sep 17 20:34:06 CEST 2024
|
||||
Update On Wed Sep 18 20:34:25 CEST 2024
|
||||
Update On Thu Sep 19 20:35:43 CEST 2024
|
||||
Update On Fri Sep 20 20:34:37 CEST 2024
|
||||
|
@@ -192,3 +192,23 @@ jobs:
|
||||
gh release upload $TAG_NAME "$sha_file" --clobber
|
||||
echo "Uploaded $sha_file to release $TAG_NAME"
|
||||
done
|
||||
- name: Upload AppImage to Github Artifact
|
||||
if: ${{ inputs.arch == 'x86_64' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-appimage
|
||||
path: ./backend/target/**/*.AppImage
|
||||
- name: Upload deb to Github Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-deb
|
||||
path: |
|
||||
./backend/target/**/*.deb
|
||||
./backend/target/**/*.deb.sha256
|
||||
- name: Upload rpm to Github Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-rpm
|
||||
path: |
|
||||
./backend/target/**/*.rpm
|
||||
./backend/target/**/*.rpm.sha256
|
||||
|
@@ -132,3 +132,10 @@ jobs:
|
||||
run: |
|
||||
${{ inputs.nightly == true && 'pnpm build:nightly --target aarch64-apple-darwin' || 'pnpm build --target aarch64-apple-darwin' }}
|
||||
pnpm upload:osx-aarch64
|
||||
|
||||
- name: Upload to Github Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-macOS-${{ inputs.aarch64 == true && 'aarch64' || 'amd64' }}
|
||||
path: |
|
||||
./backend/target/**/*.dmg
|
||||
|
@@ -9,6 +9,12 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
fixed-webview:
|
||||
description: "Fixed WebView"
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
nightly:
|
||||
description: "Nightly prepare"
|
||||
required: true
|
||||
@@ -28,7 +34,7 @@ on:
|
||||
options:
|
||||
- x86_64
|
||||
- i686
|
||||
# - aarch64
|
||||
- aarch64
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -38,6 +44,12 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
fixed-webview:
|
||||
description: "Fixed WebView"
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
nightly:
|
||||
description: "Nightly prepare"
|
||||
required: true
|
||||
@@ -103,53 +115,115 @@ jobs:
|
||||
'i686' {
|
||||
pnpm check --arch ia32 --sidecar-host i686-pc-windows-msvc
|
||||
}
|
||||
'aarch64' {
|
||||
pnpm check --arch arm64 --sidecar-host aarch64-pc-windows-msvc
|
||||
}
|
||||
}
|
||||
|
||||
- name: Download fixed WebView
|
||||
if: ${{ inputs.fixed-webview == true }}
|
||||
run: |
|
||||
$condition = '${{ inputs.arch }}'
|
||||
switch ($condition) {
|
||||
'x86_64' {
|
||||
$arch= 'x64'
|
||||
}
|
||||
'i686' {
|
||||
$arch = 'x86'
|
||||
}
|
||||
'aarch64' {
|
||||
$arch = 'arm64'
|
||||
}
|
||||
}
|
||||
|
||||
$version = '127.0.2651.105'
|
||||
$uri = "https://github.com/westinyang/WebView2RuntimeArchive/releases/download/$version/Microsoft.WebView2.FixedVersionRuntime.$version.$arch.cab"
|
||||
$outfile = "Microsoft.WebView2.FixedVersionRuntime.$version.$arch.cab"
|
||||
echo "Downloading $uri to $outfile"
|
||||
invoke-webrequest -uri $uri -outfile $outfile
|
||||
echo "Download finished, attempting to extract"
|
||||
expand.exe $outfile -F:* ./backend/tauri
|
||||
echo "Extraction finished"
|
||||
|
||||
- name: Prepare (Windows NSIS and Portable)
|
||||
if: ${{ inputs.fixed-webview == false }}
|
||||
run: ${{ inputs.nightly == true && 'pnpm prepare:nightly --nsis' || 'pnpm prepare:release --nsis' }}
|
||||
|
||||
- name: Prepare (Windows NSIS and Portable) with fixed WebView
|
||||
if: ${{ inputs.fixed-webview == true }}
|
||||
run: ${{ inputs.nightly == true && 'pnpm prepare:nightly --nsis --fixed-webview' || 'pnpm prepare:release --nsis --fixed-webview' }}
|
||||
|
||||
- name: Build UI
|
||||
run: |
|
||||
pnpm -F ui build
|
||||
# TODO: optimize strategy
|
||||
- name: Tauri build x86_64
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
if: ${{ inputs.arch == 'x86_64' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
|
||||
with:
|
||||
tagName: ${{ inputs.tag }}
|
||||
releaseName: "Clash Nyanpasu Dev"
|
||||
releaseBody: "More new features are now supported."
|
||||
releaseDraft: false
|
||||
prerelease: true
|
||||
tauriScript: pnpm tauri
|
||||
args: ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json' || '-f default-meta' }}
|
||||
run: |
|
||||
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json' || '-f default-meta' }}
|
||||
|
||||
- name: Tauri build i686
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
if: ${{ inputs.arch != 'x86_64' }}
|
||||
if: ${{ inputs.arch == 'i686' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
|
||||
with:
|
||||
tagName: ${{ inputs.tag }}
|
||||
releaseName: "Clash Nyanpasu Dev"
|
||||
releaseBody: "More new features are now supported."
|
||||
releaseDraft: false
|
||||
prerelease: true
|
||||
tauriScript: pnpm tauri
|
||||
args: ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target i686-pc-windows-msvc' || '-f default-meta --target i686-pc-windows-msvc' }}
|
||||
run: |
|
||||
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target i686-pc-windows-msvc' || '-f default-meta --target i686-pc-windows-msvc' }}
|
||||
- name: Tauri build arm64
|
||||
if: ${{ inputs.arch == 'aarch64' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
|
||||
run: |
|
||||
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target aarch64-pc-windows-msvc' || '-f default-meta --target aarch64-pc-windows-msvc' }}
|
||||
|
||||
- name: Rename fixed webview bundle name
|
||||
if: ${{ inputs.fixed-webview == true }}
|
||||
run: |
|
||||
$files = Get-ChildItem -Path "./backend/target" -Recurse -Include "*.exe", "*.zip", "*.zip.sig" | Where-Object { $_.FullName -like "*\bundle\*" }
|
||||
$condition = '${{ inputs.arch }}'
|
||||
switch ($condition) {
|
||||
'x86_64' {
|
||||
$arch= 'x64'
|
||||
}
|
||||
'i686' {
|
||||
$arch = 'x86'
|
||||
}
|
||||
'aarch64' {
|
||||
$arch = 'arm64'
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($file in $files) {
|
||||
echo "Renaming $file"
|
||||
$newname = $file.FullName -replace $arch, "fixed-webview-$arch"
|
||||
Rename-Item -Path $file -NewName $newname
|
||||
}
|
||||
|
||||
- name: Upload to release
|
||||
run: |
|
||||
$files = Get-ChildItem -Path "./backend/target" -Recurse -Include "*.exe", "*.zip", "*.zip.sig" | Where-Object { $_.FullName -like "*\bundle\*" }
|
||||
foreach ($file in $files) {
|
||||
echo "Uploading $file"
|
||||
gh release upload ${{ inputs.tag }} "$file" --clobber
|
||||
}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Portable Bundle
|
||||
if: ${{ inputs.portable == true }}
|
||||
run: |
|
||||
pnpm portable
|
||||
pnpm portable ${{ inputs.fixed-webview == true && '--fixed-webview' || '' }}
|
||||
env:
|
||||
RUST_ARCH: ${{ inputs.arch }}
|
||||
TAG_NAME: ${{ inputs.tag }}
|
||||
@@ -158,3 +232,18 @@ jobs:
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
|
||||
VITE_WIN_PORTABLE: 1
|
||||
|
||||
- name: Upload NSIS Installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-windows-${{ inputs.arch }}${{ inputs.fixed-webview == true && '-fixed-webview' || '' }}-nsis-installer
|
||||
path: |
|
||||
./backend/target/**/bundle/*.exe
|
||||
|
||||
- name: Upload portable
|
||||
if: ${{ inputs.portable == true }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Clash.Nyanpasu-windows-${{ inputs.arch }}${{ inputs.fixed-webview == true && '-fixed-webview' || '' }}-portable
|
||||
path: |
|
||||
./*_portable.zip
|
||||
|
@@ -54,6 +54,11 @@ jobs:
|
||||
run: pnpm updater:nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update Nightly Fixed Webview Updater
|
||||
if: ${{ inputs.nightly == true }}
|
||||
run: pnpm updater:nightly --fixed-webview
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update Stable Updater
|
||||
if: ${{ inputs.nightly == false }}
|
||||
@@ -61,3 +66,10 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_BODY: ${{ inputs.release_body || github.event.release.body }}
|
||||
|
||||
- name: Update Stable Fixed Webview Updater
|
||||
if: ${{ inputs.nightly == false }}
|
||||
run: pnpm updater --fixed-webview
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_BODY: ${{ inputs.release_body || github.event.release.body }}
|
||||
|
@@ -51,3 +51,4 @@ jobs:
|
||||
TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
||||
TELEGRAM_TO: "@keikolog"
|
||||
TELEGRAM_TO_NIGHTLY: "@ClashNyanpasu"
|
||||
WORKFLOW_RUN_ID: ${{ github.run_id }}
|
||||
|
@@ -24,10 +24,23 @@ jobs:
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
fixed-webview: false
|
||||
arch: "x86_64"
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
windows_aarch64_build:
|
||||
name: Windows aarch64 Build
|
||||
uses: ./.github/workflows/deps-build-windows-nsis.yaml
|
||||
needs: [delete_current_releases]
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
fixed-webview: false
|
||||
arch: "aarch64"
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
windows_i686_build:
|
||||
name: Windows i686 Build
|
||||
uses: ./.github/workflows/deps-build-windows-nsis.yaml
|
||||
@@ -35,10 +48,47 @@ jobs:
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
fixed-webview: false
|
||||
arch: "i686"
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
windows_amd64_build_fixed_webview:
|
||||
name: Windows x86_64 Build with Fixed WebView
|
||||
uses: ./.github/workflows/deps-build-windows-nsis.yaml
|
||||
needs: [delete_current_releases]
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
arch: "x86_64"
|
||||
fixed-webview: true
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
windows_aarch64_build_fixed_webview:
|
||||
name: Windows aarch64 Build with Fixed WebView
|
||||
uses: ./.github/workflows/deps-build-windows-nsis.yaml
|
||||
needs: [delete_current_releases]
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
arch: "aarch64"
|
||||
fixed-webview: true
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
windows_i686_build_fixed_webview:
|
||||
name: Windows i686 Build with Fixed WebView
|
||||
uses: ./.github/workflows/deps-build-windows-nsis.yaml
|
||||
needs: [delete_current_releases]
|
||||
with:
|
||||
portable: true
|
||||
nightly: true
|
||||
arch: "i686"
|
||||
fixed-webview: true
|
||||
tag: "pre-release"
|
||||
secrets: inherit
|
||||
|
||||
linux_amd64_build:
|
||||
name: Linux amd64 Build
|
||||
uses: ./.github/workflows/deps-build-linux.yaml
|
||||
@@ -79,15 +129,15 @@ jobs:
|
||||
arch: "armhf"
|
||||
secrets: inherit
|
||||
|
||||
linux_armel_build:
|
||||
name: Linux armel Build
|
||||
uses: ./.github/workflows/deps-build-linux.yaml
|
||||
needs: [delete_current_releases]
|
||||
with:
|
||||
nightly: true
|
||||
tag: "pre-release"
|
||||
arch: "armel"
|
||||
secrets: inherit
|
||||
# linux_armel_build:
|
||||
# name: Linux armel Build
|
||||
# uses: ./.github/workflows/deps-build-linux.yaml
|
||||
# needs: [delete_current_releases]
|
||||
# with:
|
||||
# nightly: true
|
||||
# tag: "pre-release"
|
||||
# arch: "armel"
|
||||
# secrets: inherit
|
||||
|
||||
macos_amd64_build:
|
||||
name: macOS amd64 Build
|
||||
@@ -111,15 +161,18 @@ jobs:
|
||||
|
||||
update_tag:
|
||||
name: Update tag
|
||||
needs:
|
||||
[
|
||||
needs: [
|
||||
windows_amd64_build,
|
||||
windows_i686_build,
|
||||
windows_aarch64_build,
|
||||
windows_amd64_build_fixed_webview,
|
||||
windows_i686_build_fixed_webview,
|
||||
windows_aarch64_build_fixed_webview,
|
||||
linux_amd64_build,
|
||||
linux_i686_build,
|
||||
linux_aarch64_build,
|
||||
linux_armhf_build,
|
||||
linux_armel_build,
|
||||
# linux_armel_build,
|
||||
macos_amd64_build,
|
||||
macos_aarch64_build,
|
||||
]
|
||||
|
84
clash-nyanpasu/backend/Cargo.lock
generated
84
clash-nyanpasu/backend/Cargo.lock
generated
@@ -228,23 +228,6 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.9.1"
|
||||
@@ -1303,7 +1286,6 @@ dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cocoa",
|
||||
"colored",
|
||||
"convert_case 0.6.0",
|
||||
"ctrlc",
|
||||
@@ -1333,7 +1315,9 @@ dependencies = [
|
||||
"num_cpus",
|
||||
"nyanpasu-ipc",
|
||||
"nyanpasu-utils",
|
||||
"objc",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"open",
|
||||
"openssl",
|
||||
@@ -1352,7 +1336,7 @@ dependencies = [
|
||||
"regex",
|
||||
"relative-path",
|
||||
"reqwest",
|
||||
"rfd 0.14.1",
|
||||
"rfd",
|
||||
"rs-snowflake",
|
||||
"runas",
|
||||
"rust-i18n",
|
||||
@@ -2072,7 +2056,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dirs-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"thiserror",
|
||||
@@ -4725,7 +4709,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nyanpasu-ipc"
|
||||
version = "1.0.6"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#06b52b490e1ef9fce7f798d45dff4c01faa54d5b"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#a9d91584893489ffb96741bfee3f5b20a4f2eba7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4750,7 +4734,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nyanpasu-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
|
||||
dependencies = [
|
||||
"constcat",
|
||||
"derive_builder",
|
||||
@@ -5023,7 +5007,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "os-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
|
||||
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
|
||||
dependencies = [
|
||||
"nix 0.29.0",
|
||||
"shared_child",
|
||||
@@ -5282,9 +5266,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.12"
|
||||
version = "2.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea"
|
||||
checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
@@ -5293,9 +5277,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.12"
|
||||
version = "2.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d"
|
||||
checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -5303,9 +5287,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.12"
|
||||
version = "2.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe"
|
||||
checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@@ -5316,9 +5300,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.12"
|
||||
version = "2.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174"
|
||||
checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
@@ -6151,37 +6135,13 @@ dependencies = [
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251"
|
||||
dependencies = [
|
||||
"ashpd 0.8.1",
|
||||
"block",
|
||||
"dispatch",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e"
|
||||
dependencies = [
|
||||
"ashpd 0.9.1",
|
||||
"ashpd",
|
||||
"block2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
@@ -7273,9 +7233,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e"
|
||||
checksum = "82e7ede56f9ef03a0bb384c7b2bed4f3985ee7f3f79ec887c50d8466eec21096"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cocoa",
|
||||
@@ -7513,7 +7473,7 @@ source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#6bf1bd8d
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd 0.15.0",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -8458,9 +8418,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
# dockerfile = "./manifest/docker/ubuntu-22.04-aarch64/Dockerfile"
|
||||
image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-aarch64:latest"
|
||||
image = "ghcr.io/libnyanpasu/builder-debian-trixie-aarch64:latest"
|
||||
# pre-build = [
|
||||
# "dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
# """echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe multiverse" | tee /etc/apt/sources.list && \
|
||||
@@ -25,7 +25,7 @@ image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-aarch64:latest"
|
||||
# ]
|
||||
|
||||
[target.armv7-unknown-linux-gnueabihf]
|
||||
image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-armhf:latest"
|
||||
image = "ghcr.io/libnyanpasu/builder-debian-trixie-armhf:latest"
|
||||
|
||||
[target.armv7-unknown-linux-gnueabi]
|
||||
image = "ghcr.io/libnyanpasu/builder-debian-trixie-armel:latest"
|
||||
|
@@ -69,7 +69,7 @@ runas = { git = "https://github.com/libnyanpasu/rust-runas.git" }
|
||||
backon = { version = "1.0.1", features = ["tokio-sleep"] }
|
||||
rust-i18n = "3"
|
||||
adler = "1.0.2"
|
||||
rfd = { version = "0.14", default-features = false, features = [
|
||||
rfd = { version = "0.15", default-features = false, features = [
|
||||
"tokio",
|
||||
"gtk3",
|
||||
"common-controls-v6",
|
||||
@@ -155,8 +155,15 @@ tauri-plugin-global-shortcut = { git = "https://github.com/tauri-apps/plugins-wo
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.26.0"
|
||||
objc = "0.2.7"
|
||||
objc2 = "0.5.2"
|
||||
objc2-app-kit = { version = "0.2.2", features = [
|
||||
"NSApplication",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
"NSWindow",
|
||||
"NSView",
|
||||
] }
|
||||
objc2-foundation = { version = "0.2.2", features = ["NSGeometry"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
deelevate = "0.2.0"
|
||||
|
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"windows": {
|
||||
"webviewInstallMode": {
|
||||
"type": "fixedRuntime",
|
||||
"path": "SHOULD_BE_REPLACED_WITH_THE_PATH_TO_THE_FIXED_WEBVIEW"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"endpoints": [
|
||||
"https://mirror.ghproxy.com/https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview-proxy.json",
|
||||
"https://gh-proxy.com/https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview-proxy.json",
|
||||
"https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,6 +22,8 @@ pub enum ClashCore {
|
||||
Mihomo,
|
||||
#[serde(rename = "mihomo-alpha")]
|
||||
MihomoAlpha,
|
||||
#[serde(rename = "clash-rs-alpha")]
|
||||
ClashRsAlpha,
|
||||
}
|
||||
|
||||
impl Default for ClashCore {
|
||||
@@ -40,6 +42,7 @@ impl From<ClashCore> for String {
|
||||
ClashCore::ClashRs => "clash-rs".into(),
|
||||
ClashCore::Mihomo => "mihomo".into(),
|
||||
ClashCore::MihomoAlpha => "mihomo-alpha".into(),
|
||||
ClashCore::ClashRsAlpha => "clash-rs-alpha".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +54,7 @@ impl std::fmt::Display for ClashCore {
|
||||
ClashCore::ClashRs => write!(f, "clash-rs"),
|
||||
ClashCore::Mihomo => write!(f, "mihomo"),
|
||||
ClashCore::MihomoAlpha => write!(f, "mihomo-alpha"),
|
||||
ClashCore::ClashRsAlpha => write!(f, "clash-rs-alpha"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +74,9 @@ impl From<&ClashCore> for nyanpasu_utils::core::CoreType {
|
||||
ClashCore::MihomoAlpha => nyanpasu_utils::core::CoreType::Clash(
|
||||
nyanpasu_utils::core::ClashCoreType::MihomoAlpha,
|
||||
),
|
||||
ClashCore::ClashRsAlpha => nyanpasu_utils::core::CoreType::Clash(
|
||||
nyanpasu_utils::core::ClashCoreType::ClashRustAlpha,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +89,7 @@ impl TryFrom<&nyanpasu_utils::core::CoreType> for ClashCore {
|
||||
nyanpasu_utils::core::CoreType::Clash(clash) => match clash {
|
||||
nyanpasu_utils::core::ClashCoreType::ClashPremium => Ok(ClashCore::ClashPremium),
|
||||
nyanpasu_utils::core::ClashCoreType::ClashRust => Ok(ClashCore::ClashRs),
|
||||
nyanpasu_utils::core::ClashCoreType::ClashRustAlpha => Ok(ClashCore::ClashRsAlpha),
|
||||
nyanpasu_utils::core::ClashCoreType::Mihomo => Ok(ClashCore::Mihomo),
|
||||
nyanpasu_utils::core::ClashCoreType::MihomoAlpha => Ok(ClashCore::MihomoAlpha),
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::utils::dirs::{app_config_dir, app_data_dir, app_install_dir};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::{borrow::Cow, ffi::OsString};
|
||||
use std::ffi::OsString;
|
||||
|
||||
use super::SERVICE_PATH;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ use crate::core::{
|
||||
tasks::task::Task,
|
||||
};
|
||||
use log::debug;
|
||||
use redb::{ReadableTable, TableDefinition};
|
||||
use redb::ReadableTable;
|
||||
use std::{
|
||||
str,
|
||||
sync::{Arc, OnceLock},
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, sync::atomic::AtomicU16};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
config::{nyanpasu::ClashCore, Config},
|
||||
@@ -21,6 +21,9 @@ pub mod proxies;
|
||||
pub use self::icon::on_scale_factor_changed;
|
||||
use self::proxies::SystemTrayMenuProxiesExt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::sync::atomic::AtomicU16;
|
||||
|
||||
struct TrayState<R: Runtime> {
|
||||
menu: Mutex<Menu<R>>,
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ pub struct ManifestVersionLatest {
|
||||
mihomo: String,
|
||||
mihomo_alpha: String,
|
||||
clash_rs: String,
|
||||
clash_rs_alpha: String,
|
||||
clash_premium: String,
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@ pub struct ArchTemplate {
|
||||
mihomo: HashMap<String, String>,
|
||||
mihomo_alpha: HashMap<String, String>,
|
||||
clash_rs: HashMap<String, String>,
|
||||
clash_rs_alpha: HashMap<String, String>,
|
||||
clash_premium: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -77,6 +79,7 @@ impl Default for ManifestVersionLatest {
|
||||
mihomo: "".to_string(),
|
||||
mihomo_alpha: "".to_string(),
|
||||
clash_rs: "".to_string(),
|
||||
clash_rs_alpha: "".to_string(),
|
||||
clash_premium: "".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -118,6 +121,14 @@ impl ManifestVersion {
|
||||
.replace("{}", &self.latest.clash_rs),
|
||||
CoreTypeMeta::ClashRs(self.latest.clash_rs.clone()),
|
||||
)),
|
||||
ClashCore::ClashRsAlpha => Some((
|
||||
self.arch_template
|
||||
.clash_rs_alpha
|
||||
.get(arch)?
|
||||
.clone()
|
||||
.replace("{}", &self.latest.clash_rs_alpha),
|
||||
CoreTypeMeta::ClashRs(self.latest.clash_rs_alpha.clone()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,11 +173,15 @@ impl UpdaterManager {
|
||||
let mirror = self.get_mirror().unwrap();
|
||||
let latest = self.get_latest_version_manifest(&mirror);
|
||||
let mihomo_alpha_version = self.get_mihomo_alpha_version();
|
||||
let (latest, mihomo_alpha_version) = join!(latest, mihomo_alpha_version);
|
||||
let clash_rs_alpha_version = self.get_clash_rs_alpha_version();
|
||||
let (latest, mihomo_alpha_version, clash_rs_alpha_version) =
|
||||
join!(latest, mihomo_alpha_version, clash_rs_alpha_version);
|
||||
log::debug!("latest version: {:?}", latest);
|
||||
self.manifest_version = latest?;
|
||||
log::debug!("mihomo alpha version: {:?}", mihomo_alpha_version);
|
||||
self.manifest_version.latest.mihomo_alpha = mihomo_alpha_version?;
|
||||
log::debug!("clash rs alpha version: {:?}", clash_rs_alpha_version);
|
||||
self.manifest_version.latest.clash_rs_alpha = clash_rs_alpha_version?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -217,6 +232,30 @@ impl UpdaterManager {
|
||||
Ok(res.text().await?.trim().to_string())
|
||||
}
|
||||
|
||||
async fn get_clash_rs_alpha_version(&self) -> Result<String> {
|
||||
self.mirror_speed_test().await?;
|
||||
let mirror = self.get_mirror().unwrap();
|
||||
let url = crate::utils::candy::parse_gh_url(
|
||||
&mirror,
|
||||
"/Watfaq/clash-rs/releases/download/latest/version.txt",
|
||||
)?;
|
||||
let res = self.client.get(url).send().await?;
|
||||
let status_code = res.status();
|
||||
if !status_code.is_success() {
|
||||
anyhow::bail!(
|
||||
"failed to get clash rs alpha version: response status is {}, expected 200",
|
||||
status_code
|
||||
);
|
||||
}
|
||||
let res = res.text().await?;
|
||||
let version = res
|
||||
.trim()
|
||||
.split(' ')
|
||||
.last()
|
||||
.ok_or(anyhow!("no version found"))?;
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
pub async fn update_core(&mut self, core_type: &ClashCore) -> Result<usize> {
|
||||
self.mirror_speed_test().await?;
|
||||
let (artifact, tag) = self
|
||||
|
@@ -19,7 +19,7 @@ pub(super) fn get_arch() -> anyhow::Result<&'static str> {
|
||||
("armel", "linux") => Ok("linux-armv7"),
|
||||
("aarch64", "macos") => Ok("darwin-arm64"),
|
||||
("aarch64", "linux") => Ok("linux-aarch64"),
|
||||
// ("aarch64", "windows") => Ok("windows-arm64"),
|
||||
("aarch64", "windows") => Ok("windows-arm64"),
|
||||
_ => anyhow::bail!("unsupported platform"),
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ pub(super) enum CoreTypeMeta {
|
||||
Mihomo(String),
|
||||
MihomoAlpha,
|
||||
ClashRs(String),
|
||||
ClashRsAlpha,
|
||||
}
|
||||
|
||||
pub(super) fn get_download_path(core_type: CoreTypeMeta, artifact: &str) -> String {
|
||||
@@ -43,6 +44,9 @@ pub(super) fn get_download_path(core_type: CoreTypeMeta, artifact: &str) -> Stri
|
||||
CoreTypeMeta::ClashRs(tag) => {
|
||||
format!("Watfaq/clash-rs/releases/download/{}/{}", tag, artifact)
|
||||
}
|
||||
CoreTypeMeta::ClashRsAlpha => {
|
||||
format!("Watfaq/clash-rs/releases/download/latest/{}", artifact)
|
||||
}
|
||||
CoreTypeMeta::ClashPremium(tag) => format!(
|
||||
"zhongfly/Clash-premium-backup/releases/download/{}/{}",
|
||||
tag, artifact
|
||||
|
@@ -4,14 +4,6 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate cocoa;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
mod cmds;
|
||||
mod config;
|
||||
mod consts;
|
||||
@@ -28,7 +20,6 @@ use crate::{
|
||||
utils::{init, resolve},
|
||||
};
|
||||
use tauri::Emitter;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use utils::resolve::{is_window_opened, reset_window_open_counter};
|
||||
|
||||
rust_i18n::i18n!("../../locales");
|
||||
@@ -338,9 +329,9 @@ pub fn run() -> std::io::Result<()> {
|
||||
reset_window_open_counter();
|
||||
let _ = resolve::save_window_state(app_handle, true);
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
log_err!(app_handle.run_on_main_thread(|| {
|
||||
crate::utils::dock::macos::hide_dock_icon();
|
||||
}
|
||||
}));
|
||||
}
|
||||
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
|
||||
log::debug!(target: "app", "window moved or resized");
|
||||
|
@@ -1,23 +1,22 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos {
|
||||
extern crate cocoa;
|
||||
extern crate objc;
|
||||
|
||||
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy};
|
||||
use objc::runtime::YES;
|
||||
|
||||
pub unsafe fn show_dock_icon() {
|
||||
let app = NSApp();
|
||||
app.setActivationPolicy_(
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||
);
|
||||
app.activateIgnoringOtherApps_(YES);
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use std::cell::Cell;
|
||||
thread_local! {
|
||||
static MARK: Cell<MainThreadMarker> = Cell::new(MainThreadMarker::new().unwrap());
|
||||
}
|
||||
|
||||
pub unsafe fn hide_dock_icon() {
|
||||
let app = NSApp();
|
||||
app.setActivationPolicy_(
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
|
||||
);
|
||||
pub fn show_dock_icon() {
|
||||
let app = NSApplication::sharedApplication(MARK.get());
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
|
||||
unsafe {
|
||||
app.activate();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_dock_icon() {
|
||||
let app = NSApplication::sharedApplication(MARK.get());
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy::Accessory);
|
||||
}
|
||||
}
|
||||
|
@@ -99,14 +99,32 @@ pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
||||
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let code = "Visual Studio Code";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(windows)]
|
||||
let code = "code.cmd";
|
||||
#[cfg(all(not(windows), not(target_os = "macos")))]
|
||||
let code = "code";
|
||||
|
||||
let shell = app.shell();
|
||||
|
||||
trace_err!(
|
||||
match which::which(code) {
|
||||
Ok(_) => crate::utils::open::with(path, code),
|
||||
Ok(code_path) => {
|
||||
log::debug!(target: "app", "find VScode `{}`", code_path.display());
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
crate::utils::open::with(path, code)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::ffi::OsString;
|
||||
let mut buf = OsString::with_capacity(path.as_os_str().len() + 2);
|
||||
buf.push("\"");
|
||||
buf.push(path.as_os_str());
|
||||
buf.push("\"");
|
||||
|
||||
open::with_detached(buf, code)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "Can't find VScode `{err}`");
|
||||
// default open
|
||||
|
@@ -32,34 +32,39 @@ pub fn reset_window_open_counter() {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_window_controls_pos(window: cocoa::base::id, x: f64, y: f64) {
|
||||
use cocoa::{
|
||||
appkit::{NSView, NSWindow, NSWindowButton},
|
||||
foundation::NSRect,
|
||||
};
|
||||
fn set_window_controls_pos(window: objc2::rc::Retained<objc2_app_kit::NSWindow>, x: f64, y: f64) {
|
||||
use objc2_app_kit::NSWindowButton;
|
||||
use objc2_foundation::NSRect;
|
||||
let close = window
|
||||
.standardWindowButton(NSWindowButton::NSWindowCloseButton)
|
||||
.unwrap();
|
||||
let miniaturize = window
|
||||
.standardWindowButton(NSWindowButton::NSWindowMiniaturizeButton)
|
||||
.unwrap();
|
||||
let zoom = window
|
||||
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
||||
.unwrap();
|
||||
|
||||
let title_bar_container_view = unsafe { close.superview().unwrap().superview().unwrap() };
|
||||
|
||||
let close_rect = close.frame();
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = title_bar_container_view.frame();
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height;
|
||||
unsafe {
|
||||
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
title_bar_container_view.setFrame(title_bar_rect);
|
||||
}
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
let space_between = miniaturize.frame().origin.x - close.frame().origin.x;
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
||||
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = button.frame();
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
unsafe {
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
@@ -86,13 +91,15 @@ pub fn find_unused_port() -> Result<u16> {
|
||||
pub fn resolve_setup(app: &mut App) {
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||
#[cfg(target_os = "macos")]
|
||||
let app_handle = app.app_handle().clone();
|
||||
app.listen("react_app_mounted", move |_| {
|
||||
tracing::debug!("Frontend React App is mounted, reset open window counter");
|
||||
reset_window_open_counter();
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
log_err!(app_handle.run_on_main_thread(move || {
|
||||
crate::utils::dock::macos::show_dock_icon();
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
handle::Handle::global().init(app.app_handle().clone());
|
||||
@@ -250,14 +257,24 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_controls_and_log_error(app_handle: &tauri::AppHandle, window_name: &str) {
|
||||
use objc2::{rc::Retained, runtime::NSObject};
|
||||
use objc2_app_kit::NSWindow;
|
||||
match app_handle
|
||||
.get_webview_window(window_name)
|
||||
.unwrap()
|
||||
.ns_window()
|
||||
{
|
||||
Ok(raw_window) => {
|
||||
let window_id: cocoa::base::id = raw_window as _;
|
||||
set_window_controls_pos(window_id, 26.0, 26.0);
|
||||
let obj: Option<Retained<NSWindow>> =
|
||||
unsafe { Retained::from_raw(raw_window as *mut NSWindow) };
|
||||
match obj {
|
||||
Some(window) => {
|
||||
set_window_controls_pos(window, 26.0, 26.0);
|
||||
}
|
||||
None => {
|
||||
log::error!(target: "app", "failed to get ns_window");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "failed to get ns_window, {err}");
|
||||
@@ -447,7 +464,7 @@ pub async fn resolve_core_version(app_handle: &AppHandle, core_type: &ClashCore)
|
||||
ClashCore::ClashPremium | ClashCore::Mihomo | ClashCore::MihomoAlpha => {
|
||||
shell.sidecar(core)?.args(["-v"])
|
||||
}
|
||||
ClashCore::ClashRs => shell.sidecar(core)?.args(["-V"]),
|
||||
ClashCore::ClashRs | ClashCore::ClashRsAlpha => shell.sidecar(core)?.args(["-V"]),
|
||||
};
|
||||
let out = cmd.output().await?;
|
||||
if !out.status.success() {
|
||||
|
@@ -9,7 +9,6 @@
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"webviewInstallMode": {
|
||||
"silent": true,
|
||||
"type": "embedBootstrapper"
|
||||
},
|
||||
"wix": {
|
||||
|
@@ -18,6 +18,6 @@
|
||||
"swr": "2.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.7"
|
||||
"@types/react": "18.3.8"
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ export const VALID_CORE: Core[] = [
|
||||
{ name: "Mihomo", core: "mihomo" },
|
||||
{ name: "Mihomo Alpha", core: "mihomo-alpha" },
|
||||
{ name: "Clash Rust", core: "clash-rs" },
|
||||
{ name: "Clash Rust Alpha", core: "clash-rs-alpha" },
|
||||
];
|
||||
|
||||
export const fetchCoreVersion = async () => {
|
||||
|
@@ -3,7 +3,12 @@ import { Clash } from "./clash";
|
||||
export interface VergeConfig {
|
||||
app_log_level?: "trace" | "debug" | "info" | "warn" | "error" | string;
|
||||
language?: string;
|
||||
clash_core?: "mihomo" | "mihomo-alpha" | "clash-rs" | "clash";
|
||||
clash_core?:
|
||||
| "mihomo"
|
||||
| "mihomo-alpha"
|
||||
| "clash-rs"
|
||||
| "clash-rs-alpha"
|
||||
| "clash";
|
||||
theme_mode?: "light" | "dark" | "system";
|
||||
theme_blur?: boolean;
|
||||
traffic_graph?: boolean;
|
||||
|
@@ -30,10 +30,10 @@
|
||||
"dayjs": "1.11.13",
|
||||
"framer-motion": "12.0.0-alpha.1",
|
||||
"i18next": "23.15.1",
|
||||
"jotai": "2.9.3",
|
||||
"jotai": "2.10.0",
|
||||
"json-schema": "0.4.0",
|
||||
"material-react-table": "3.0.1",
|
||||
"monaco-editor": "0.51.0",
|
||||
"monaco-editor": "0.52.0",
|
||||
"mui-color-input": "4.0.0",
|
||||
"react": "rc",
|
||||
"react-dom": "rc",
|
||||
@@ -66,7 +66,7 @@
|
||||
"@tauri-apps/plugin-process": "2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-shell": "2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-updater": "2.0.0-rc.2",
|
||||
"@types/react": "18.3.7",
|
||||
"@types/react": "18.3.8",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/validator": "13.12.2",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
@@ -75,8 +75,8 @@
|
||||
"meta-json-schema": "1.18.8",
|
||||
"monaco-yaml": "5.2.2",
|
||||
"nanoid": "5.0.7",
|
||||
"sass": "1.79.1",
|
||||
"shiki": "1.17.7",
|
||||
"sass": "1.79.2",
|
||||
"shiki": "1.18.0",
|
||||
"tailwindcss-textshadow": "2.1.3",
|
||||
"unplugin-auto-import": "0.18.3",
|
||||
"unplugin-icons": "0.19.3",
|
||||
|
@@ -31,7 +31,8 @@ export const getImage = (core: ClashCore) => {
|
||||
return ClashMeta;
|
||||
}
|
||||
|
||||
case "clash-rs": {
|
||||
case "clash-rs":
|
||||
case "clash-rs-alpha": {
|
||||
return ClashRs;
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,7 @@
|
||||
"@radix-ui/react-scroll-area": "1.1.0",
|
||||
"@tauri-apps/api": "2.0.0-rc.5",
|
||||
"@types/d3": "7.4.3",
|
||||
"@types/react": "18.3.7",
|
||||
"@types/react": "18.3.8",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
"ahooks": "3.8.1",
|
||||
"d3": "7.9.0",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@types/d3-interpolate-path": "2.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"d3-interpolate-path": "2.3.0",
|
||||
"sass": "1.79.1",
|
||||
"sass": "1.79.2",
|
||||
"tailwind-merge": "2.5.2",
|
||||
"typescript-plugin-css-modules": "5.1.0",
|
||||
"vite-plugin-dts": "4.2.1"
|
||||
|
@@ -2,14 +2,16 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.18.8",
|
||||
"mihomo_alpha": "alpha-3676d1b",
|
||||
"clash_rs": "v0.3.2",
|
||||
"clash_premium": "2023-09-05-gdcc8d87"
|
||||
"mihomo_alpha": "alpha-a08aa10",
|
||||
"clash_rs": "v0.4.0",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.4.0"
|
||||
},
|
||||
"arch_template": {
|
||||
"mihomo": {
|
||||
"windows-i386": "mihomo-windows-386-{}.zip",
|
||||
"windows-x86_64": "mihomo-windows-amd64-compatible-{}.zip",
|
||||
"windows-arm64": "mihomo-windows-arm64-{}.zip",
|
||||
"linux-aarch64": "mihomo-linux-arm64-{}.gz",
|
||||
"linux-amd64": "mihomo-linux-amd64-compatible-{}.gz",
|
||||
"linux-i386": "mihomo-linux-386-{}.gz",
|
||||
@@ -21,6 +23,7 @@
|
||||
"mihomo_alpha": {
|
||||
"windows-i386": "mihomo-windows-386-{}.zip",
|
||||
"windows-x86_64": "mihomo-windows-amd64-compatible-{}.zip",
|
||||
"windows-arm64": "mihomo-windows-arm64-{}.zip",
|
||||
"linux-aarch64": "mihomo-linux-arm64-{}.gz",
|
||||
"linux-amd64": "mihomo-linux-amd64-compatible-{}.gz",
|
||||
"linux-i386": "mihomo-linux-386-{}.gz",
|
||||
@@ -32,6 +35,7 @@
|
||||
"clash_rs": {
|
||||
"windows-i386": "clash-i686-pc-windows-msvc-static-crt.exe",
|
||||
"windows-x86_64": "clash-x86_64-pc-windows-msvc.exe",
|
||||
"windows-arm64": "clash-aarch64-pc-windows-msvc.exe",
|
||||
"linux-aarch64": "clash-aarch64-unknown-linux-gnu-static-crt",
|
||||
"linux-amd64": "clash-x86_64-unknown-linux-gnu-static-crt",
|
||||
"linux-i386": "clash-i686-unknown-linux-gnu-static-crt",
|
||||
@@ -43,6 +47,7 @@
|
||||
"clash_premium": {
|
||||
"windows-i386": "clash-windows-386-n{}.zip",
|
||||
"windows-x86_64": "clash-windows-amd64-n{}.zip",
|
||||
"windows-arm64": "clash-windows-arm64-n{}.zip",
|
||||
"linux-aarch64": "clash-linux-arm64-n{}.gz",
|
||||
"linux-amd64": "clash-linux-amd64-n{}.gz",
|
||||
"linux-i386": "clash-linux-386-n{}.gz",
|
||||
@@ -50,7 +55,19 @@
|
||||
"darwin-x64": "clash-darwin-amd64-n{}.gz",
|
||||
"linux-armv7": "clash-linux-armv5-n{}.gz",
|
||||
"linux-armv7hf": "clash-linux-armv7-n{}.gz"
|
||||
},
|
||||
"clash_rs_alpha": {
|
||||
"windows-i386": "clash-i686-pc-windows-msvc-static-crt.exe",
|
||||
"windows-x86_64": "clash-x86_64-pc-windows-msvc.exe",
|
||||
"windows-arm64": "clash-aarch64-pc-windows-msvc.exe",
|
||||
"linux-aarch64": "clash-aarch64-unknown-linux-gnu-static-crt",
|
||||
"linux-amd64": "clash-x86_64-unknown-linux-gnu-static-crt",
|
||||
"linux-i386": "clash-i686-unknown-linux-gnu-static-crt",
|
||||
"darwin-arm64": "clash-aarch64-apple-darwin",
|
||||
"darwin-x64": "clash-x86_64-apple-darwin",
|
||||
"linux-armv7": "clash-armv7-unknown-linux-gnueabi-static-crt",
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2024-09-18T22:20:35.900Z"
|
||||
"updated_at": "2024-09-20T02:46:22.198Z"
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@
|
||||
"eslint-plugin-prettier": "5.2.1",
|
||||
"eslint-plugin-promise": "7.1.0",
|
||||
"eslint-plugin-react": "7.36.1",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-92aaa43-20240919",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"knip": "5.30.2",
|
||||
"lint-staged": "15.2.10",
|
||||
@@ -104,7 +104,7 @@
|
||||
"tsx": "4.19.1",
|
||||
"typescript": "5.6.2"
|
||||
},
|
||||
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c",
|
||||
"packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b",
|
||||
"engines": {
|
||||
"node": "22.9.0"
|
||||
},
|
||||
|
754
clash-nyanpasu/pnpm-lock.yaml
generated
754
clash-nyanpasu/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ const tasks: {
|
||||
{ name: "mihomo", func: () => resolve.clashMeta(), retry: 5 },
|
||||
{ name: "mihomo-alpha", func: () => resolve.clashMetaAlpha(), retry: 5 },
|
||||
{ name: "clash-rs", func: () => resolve.clashRust(), retry: 5 },
|
||||
{ name: "clash-rs-alpha", func: () => resolve.clashRustAlpha(), retry: 5 },
|
||||
{ name: "wintun", func: () => resolve.wintun(), retry: 5, winOnly: true },
|
||||
{
|
||||
name: "nyanpasu-service",
|
||||
|
@@ -5,6 +5,7 @@ import { consola } from "./utils/logger";
|
||||
import {
|
||||
resolveClashPremium,
|
||||
resolveClashRs,
|
||||
resolveClashRsAlpha,
|
||||
resolveMihomo,
|
||||
resolveMihomoAlpha,
|
||||
} from "./utils/manifest";
|
||||
@@ -17,6 +18,7 @@ export async function generateLatestVersion() {
|
||||
resolveMihomoAlpha,
|
||||
resolveClashRs,
|
||||
resolveClashPremium,
|
||||
resolveClashRsAlpha,
|
||||
];
|
||||
|
||||
consola.start("Resolving latest versions");
|
||||
|
@@ -6,3 +6,11 @@ export const CLASH_RS_MANIFEST: ClashManifest = {
|
||||
VERSION: versionManifest.latest.clash_rs,
|
||||
ARCH_MAPPING: versionManifest.arch_template.clash_rs,
|
||||
};
|
||||
|
||||
export const CLASH_RS_ALPHA_MANIFEST: ClashManifest = {
|
||||
VERSION_URL:
|
||||
"https://github.com/Watfaq/clash-rs/releases/download/latest/version.txt",
|
||||
URL_PREFIX: "https://github.com/Watfaq/clash-rs/releases/download/latest",
|
||||
VERSION: versionManifest.latest.clash_rs_alpha,
|
||||
ARCH_MAPPING: versionManifest.arch_template.clash_rs_alpha,
|
||||
};
|
||||
|
@@ -3,9 +3,11 @@ import AdmZip from "adm-zip";
|
||||
import fs from "fs-extra";
|
||||
import { context, getOctokit } from "@actions/github";
|
||||
import packageJson from "../package.json";
|
||||
import { TAURI_APP_DIR } from "./utils/env";
|
||||
import { colorize, consola } from "./utils/logger";
|
||||
|
||||
const RUST_ARCH = process.env.RUST_ARCH || "x86_64";
|
||||
const fixedWebview = process.argv.includes("--fixed-webview");
|
||||
|
||||
/// Script for ci
|
||||
/// 打包绿色版/便携版 (only Windows)
|
||||
@@ -38,12 +40,27 @@ async function resolvePortable() {
|
||||
zip.addLocalFile(path.join(buildDir, "mihomo-alpha.exe"));
|
||||
zip.addLocalFile(path.join(buildDir, "nyanpasu-service.exe"));
|
||||
zip.addLocalFile(path.join(buildDir, "clash-rs.exe"));
|
||||
zip.addLocalFile(path.join(buildDir, "clash-rs-alpha.exe"));
|
||||
zip.addLocalFolder(path.join(buildDir, "resources"), "resources");
|
||||
|
||||
if (fixedWebview) {
|
||||
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
|
||||
file.includes("WebView2"),
|
||||
);
|
||||
if (!webviewPath) {
|
||||
throw new Error("WebView2 runtime not found");
|
||||
}
|
||||
zip.addLocalFolder(
|
||||
path.join(TAURI_APP_DIR, webviewPath),
|
||||
path.basename(webviewPath),
|
||||
);
|
||||
}
|
||||
|
||||
zip.addLocalFolder(configDir, ".config");
|
||||
|
||||
const { version } = packageJson;
|
||||
|
||||
const zipFile = `Clash.Nyanpasu_${version}_${RUST_ARCH}_portable.zip`;
|
||||
const zipFile = `Clash.Nyanpasu_${version}_${RUST_ARCH}${fixedWebview ? "_fixed-webview" : ""}_portable.zip`;
|
||||
zip.writeZip(zipFile);
|
||||
|
||||
consola.success("create portable zip successfully");
|
||||
|
@@ -2,7 +2,11 @@ import { execSync } from "child_process";
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { merge } from "lodash-es";
|
||||
import { cwd, TAURI_APP_DIR } from "./utils/env";
|
||||
import {
|
||||
cwd,
|
||||
TAURI_APP_DIR,
|
||||
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
|
||||
} from "./utils/env";
|
||||
import { consola } from "./utils/logger";
|
||||
|
||||
const TAURI_DEV_APP_CONF_PATH = path.join(
|
||||
@@ -24,15 +28,34 @@ const NYANPASU_PACKAGE_JSON_PATH = path.join(
|
||||
|
||||
const isNSIS = process.argv.includes("--nsis"); // only build nsis
|
||||
const isMSI = process.argv.includes("--msi"); // only build msi
|
||||
const fixedWebview = process.argv.includes("--fixed-webview");
|
||||
|
||||
async function main() {
|
||||
consola.debug("Read config...");
|
||||
const tauriAppConf = await fs.readJSON(TAURI_APP_CONF);
|
||||
const tauriAppOverrides = await fs.readJSON(TAURI_DEV_APP_OVERRIDES_PATH);
|
||||
const tauriConf = merge(tauriAppConf, tauriAppOverrides);
|
||||
let tauriConf = merge(tauriAppConf, tauriAppOverrides);
|
||||
const packageJson = await fs.readJSON(NYANPASU_PACKAGE_JSON_PATH);
|
||||
const rootPackageJson = await fs.readJSON(ROOT_PACKAGE_JSON_PATH);
|
||||
// const wxsFile = await fs.readFile(WXS_PATH, "utf-8");
|
||||
if (fixedWebview) {
|
||||
const fixedWebview2Config = await fs.readJSON(
|
||||
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
|
||||
);
|
||||
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
|
||||
file.includes("WebView2"),
|
||||
);
|
||||
if (!webviewPath) {
|
||||
throw new Error("WebView2 runtime not found");
|
||||
}
|
||||
tauriConf = merge(tauriConf, fixedWebview2Config);
|
||||
delete tauriConf.bundle.windows.webviewInstallMode.silent;
|
||||
tauriConf.bundle.windows.webviewInstallMode.path = `./${path.basename(webviewPath)}`;
|
||||
tauriConf.plugins.updater.endpoints =
|
||||
tauriConf.plugins.updater.endpoints.map((o: string) =>
|
||||
o.replace("update-", "update-nightly-"),
|
||||
);
|
||||
}
|
||||
|
||||
if (isNSIS) {
|
||||
tauriConf.bundle.targets = ["nsis"];
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { cwd, TAURI_APP_DIR } from "./utils/env";
|
||||
import { merge } from "lodash-es";
|
||||
import {
|
||||
cwd,
|
||||
TAURI_APP_DIR,
|
||||
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
|
||||
} from "./utils/env";
|
||||
import { consola } from "./utils/logger";
|
||||
|
||||
const TAURI_APP_CONF = path.join(TAURI_APP_DIR, "tauri.conf.json");
|
||||
@@ -14,19 +19,35 @@ const PACKAGE_JSON_PATH = path.join(cwd, "package.json");
|
||||
// const WXS_PATH = path.join(TAURI_APP_DIR, "templates", "nightly.wxs");
|
||||
|
||||
const isNSIS = process.argv.includes("--nsis"); // only build nsis
|
||||
const fixedWebview = process.argv.includes("--fixed-webview");
|
||||
|
||||
async function main() {
|
||||
consola.debug("Read config...");
|
||||
const tauriAppConf = await fs.readJSON(TAURI_APP_CONF);
|
||||
// const tauriAppOverrides = await fs.readJSON(TAURI_DEV_APP_OVERRIDES_PATH);
|
||||
// const tauriConf = merge(tauriAppConf, tauriAppOverrides);
|
||||
const tauriConf = tauriAppConf;
|
||||
let tauriConf = tauriAppConf;
|
||||
// const wxsFile = await fs.readFile(WXS_PATH, "utf-8");
|
||||
|
||||
// if (isNSIS) {
|
||||
// tauriConf.tauri.bundle.targets = ["nsis", "updater"];
|
||||
// }
|
||||
|
||||
if (fixedWebview) {
|
||||
const fixedWebview2Config = await fs.readJSON(
|
||||
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
|
||||
);
|
||||
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
|
||||
file.includes("WebView2"),
|
||||
);
|
||||
if (!webviewPath) {
|
||||
throw new Error("WebView2 runtime not found");
|
||||
}
|
||||
tauriConf = merge(tauriConf, fixedWebview2Config);
|
||||
delete tauriConf.bundle.windows.webviewInstallMode.silent;
|
||||
tauriConf.bundle.windows.webviewInstallMode.path = `./${path.basename(webviewPath)}`;
|
||||
}
|
||||
|
||||
consola.debug("Write tauri version to tauri.conf.json");
|
||||
await fs.writeJSON(TAURI_APP_CONF, tauriConf, { spaces: 2 });
|
||||
consola.debug("tauri.conf.json updated");
|
||||
|
@@ -38,6 +38,8 @@ if (!process.env.GITHUB_TOKEN) {
|
||||
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
|
||||
const WORKFLOW_RUN_ID = process.env.WORKFLOW_RUN_ID;
|
||||
|
||||
const resourceFormats = [
|
||||
".exe",
|
||||
"portable.zip",
|
||||
@@ -101,6 +103,29 @@ const repoInfo = {
|
||||
consola.log(`existed ${item}:`, existsSync(item));
|
||||
});
|
||||
|
||||
if (!nightlyBuild) {
|
||||
await client.sendMessage(TELEGRAM_TO, {
|
||||
message: array2text([
|
||||
`Clash Nyanpasu ${version} Released!`,
|
||||
"",
|
||||
"Check out on GitHub:",
|
||||
` - https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${version}`,
|
||||
]),
|
||||
});
|
||||
consola.success("Send release message to telegram successfully");
|
||||
} else {
|
||||
await client.sendMessage(TELEGRAM_TO_NIGHTLY, {
|
||||
message: array2text([
|
||||
`Clash Nyanpasu Nightly Build ${GIT_SHORT_HASH} released!`,
|
||||
"",
|
||||
"Could be unstable, use at your own risk. Download at:",
|
||||
`- https://github.com/libnyanpasu/clash-nyanpasu/actions/runs/${WORKFLOW_RUN_ID}`,
|
||||
"",
|
||||
"You could also waiting for the telegram bot to upload the binaries, although it may take a while or even fail.",
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
consola.start("Staring upload tasks (nightly)");
|
||||
|
||||
// upload windows binary
|
||||
@@ -151,21 +176,6 @@ const repoInfo = {
|
||||
|
||||
consola.success("Upload finished (nightly)");
|
||||
|
||||
if (!nightlyBuild) {
|
||||
consola.start("Staring upload tasks (release)");
|
||||
|
||||
await client.sendMessage(TELEGRAM_TO, {
|
||||
message: array2text([
|
||||
`Clash Nyanpasu ${version} Released!`,
|
||||
"",
|
||||
"Check out on GitHub:",
|
||||
` - https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${version}`,
|
||||
]),
|
||||
});
|
||||
|
||||
consola.success("Upload finished (release)");
|
||||
}
|
||||
|
||||
await client.disconnect();
|
||||
|
||||
process.exit();
|
||||
|
@@ -20,11 +20,9 @@ export interface BinInfo {
|
||||
}
|
||||
|
||||
export enum SupportedArch {
|
||||
// blocked by clash-rs
|
||||
WindowsX86_32 = "windows-i386",
|
||||
WindowsX86_64 = "windows-x86_64",
|
||||
// blocked by clash-rs#212
|
||||
// WindowsArm64 = "windows-arm64",
|
||||
WindowsArm64 = "windows-arm64",
|
||||
LinuxAarch64 = "linux-aarch64",
|
||||
LinuxAmd64 = "linux-amd64",
|
||||
LinuxI386 = "linux-i386",
|
||||
@@ -38,6 +36,7 @@ export enum SupportedCore {
|
||||
Mihomo = "mihomo",
|
||||
MihomoAlpha = "mihomo_alpha",
|
||||
ClashRs = "clash_rs",
|
||||
ClashRsAlpha = "clash_rs_alpha",
|
||||
ClashPremium = "clash_premium",
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,10 @@ import { consola } from "./utils/logger";
|
||||
const UPDATE_TAG_NAME = "updater";
|
||||
const UPDATE_JSON_FILE = "update-nightly.json";
|
||||
const UPDATE_JSON_PROXY = "update-nightly-proxy.json";
|
||||
const UPDATE_FIXED_WEBVIEW_FILE = "update-nightly-fixed-webview.json";
|
||||
const UPDATE_FIXED_WEBVIEW_PROXY = "update-nightly-fixed-webview-proxy.json";
|
||||
|
||||
const isFixedWebview = process.argv.includes("--fixed-webview");
|
||||
|
||||
/// generate update.json
|
||||
/// upload to update tag's release asset
|
||||
@@ -57,34 +61,44 @@ async function resolveUpdater() {
|
||||
const promises = latestPreRelease.assets.map(async (asset) => {
|
||||
const { name, browser_download_url } = asset;
|
||||
|
||||
function isMatch(name: string, extension: string, arch: string) {
|
||||
return (
|
||||
name.endsWith(extension) &&
|
||||
name.includes(arch) &&
|
||||
(isFixedWebview
|
||||
? name.includes("fixed-webview")
|
||||
: !name.includes("fixed-webview"))
|
||||
);
|
||||
}
|
||||
|
||||
// win64 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("x64")) {
|
||||
if (isMatch(name, ".nsis.zip", "x64")) {
|
||||
updateData.platforms.win64.url = browser_download_url;
|
||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
||||
}
|
||||
// win64 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("x64")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "x64")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms.win64.signature = sig;
|
||||
updateData.platforms["windows-x86_64"].signature = sig;
|
||||
}
|
||||
|
||||
// win32 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("x86")) {
|
||||
if (isMatch(name, ".nsis.zip", "x86")) {
|
||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
||||
}
|
||||
// win32 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("x86")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "x86")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-i686"].signature = sig;
|
||||
}
|
||||
|
||||
// win arm64 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("arm64")) {
|
||||
if (isMatch(name, ".nsis.zip", "arm64")) {
|
||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||
}
|
||||
// win arm64 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("arm64")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "arm64")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-aarch64"].signature = sig;
|
||||
}
|
||||
@@ -178,14 +192,22 @@ async function resolveUpdater() {
|
||||
|
||||
// delete the old assets
|
||||
for (const asset of updateRelease.assets) {
|
||||
if (asset.name === UPDATE_JSON_FILE) {
|
||||
if (
|
||||
isFixedWebview
|
||||
? asset.name === UPDATE_FIXED_WEBVIEW_FILE
|
||||
: asset.name === UPDATE_JSON_FILE
|
||||
) {
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
asset_id: asset.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (asset.name === UPDATE_JSON_PROXY) {
|
||||
if (
|
||||
isFixedWebview
|
||||
? asset.name === UPDATE_FIXED_WEBVIEW_PROXY
|
||||
: asset.name === UPDATE_JSON_PROXY
|
||||
) {
|
||||
await github.rest.repos
|
||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
||||
.catch((err) => {
|
||||
@@ -198,14 +220,14 @@ async function resolveUpdater() {
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_FILE,
|
||||
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_FILE : UPDATE_JSON_FILE,
|
||||
data: JSON.stringify(updateData, null, 2),
|
||||
});
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_PROXY,
|
||||
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_PROXY : UPDATE_JSON_PROXY,
|
||||
data: JSON.stringify(updateDataNew, null, 2),
|
||||
});
|
||||
consola.success("updater files updated");
|
||||
|
@@ -7,8 +7,12 @@ import { colorize, consola } from "./utils/logger";
|
||||
const UPDATE_TAG_NAME = "updater";
|
||||
const UPDATE_JSON_FILE = "update.json";
|
||||
const UPDATE_JSON_PROXY = "update-proxy.json";
|
||||
const UPDATE_FIXED_WEBVIEW_FILE = "update-fixed-webview.json";
|
||||
const UPDATE_FIXED_WEBVIEW_PROXY = "update-fixed-webview-proxy.json";
|
||||
const UPDATE_RELEASE_BODY = process.env.RELEASE_BODY || "";
|
||||
|
||||
const isFixedWebview = process.argv.includes("--fixed-webview");
|
||||
|
||||
/// generate update.json
|
||||
/// upload to update tag's release asset
|
||||
async function resolveUpdater() {
|
||||
@@ -65,34 +69,44 @@ async function resolveUpdater() {
|
||||
const promises = latestRelease.assets.map(async (asset) => {
|
||||
const { name, browser_download_url } = asset;
|
||||
|
||||
function isMatch(name: string, extension: string, arch: string) {
|
||||
return (
|
||||
name.endsWith(extension) &&
|
||||
name.includes(arch) &&
|
||||
(isFixedWebview
|
||||
? name.includes("fixed-webview")
|
||||
: !name.includes("fixed-webview"))
|
||||
);
|
||||
}
|
||||
|
||||
// win64 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("x64")) {
|
||||
if (isMatch(name, ".nsis.zip", "x64")) {
|
||||
updateData.platforms.win64.url = browser_download_url;
|
||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
||||
}
|
||||
// win64 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("x64")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "x64")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms.win64.signature = sig;
|
||||
updateData.platforms["windows-x86_64"].signature = sig;
|
||||
}
|
||||
|
||||
// win32 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("x86")) {
|
||||
if (isMatch(name, ".nsis.zip", "x86")) {
|
||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
||||
}
|
||||
// win32 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("x86")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "x86")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-i686"].signature = sig;
|
||||
}
|
||||
|
||||
// win arm64 url
|
||||
if (name.endsWith(".nsis.zip") && name.includes("arm64")) {
|
||||
if (isMatch(name, ".nsis.zip", "arm64")) {
|
||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||
}
|
||||
// win arm64 signature
|
||||
if (name.endsWith(".nsis.zip.sig") && name.includes("arm64")) {
|
||||
if (isMatch(name, ".nsis.zip.sig", "arm64")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-aarch64"].signature = sig;
|
||||
}
|
||||
@@ -169,14 +183,22 @@ async function resolveUpdater() {
|
||||
|
||||
// delete the old assets
|
||||
for (const asset of updateRelease.assets) {
|
||||
if (asset.name === UPDATE_JSON_FILE) {
|
||||
if (
|
||||
isFixedWebview
|
||||
? asset.name === UPDATE_FIXED_WEBVIEW_FILE
|
||||
: asset.name === UPDATE_JSON_FILE
|
||||
) {
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
asset_id: asset.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (asset.name === UPDATE_JSON_PROXY) {
|
||||
if (
|
||||
isFixedWebview
|
||||
? asset.name === UPDATE_FIXED_WEBVIEW_PROXY
|
||||
: asset.name === UPDATE_JSON_PROXY
|
||||
) {
|
||||
await github.rest.repos
|
||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
||||
.catch((err) => {
|
||||
@@ -189,14 +211,14 @@ async function resolveUpdater() {
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_FILE,
|
||||
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_FILE : UPDATE_JSON_FILE,
|
||||
data: JSON.stringify(updateData, null, 2),
|
||||
});
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_PROXY,
|
||||
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_PROXY : UPDATE_JSON_PROXY,
|
||||
data: JSON.stringify(updateDataNew, null, 2),
|
||||
});
|
||||
}
|
||||
|
@@ -2,6 +2,10 @@ import path from "path";
|
||||
|
||||
export const cwd = process.cwd();
|
||||
export const TAURI_APP_DIR = path.join(cwd, "backend/tauri");
|
||||
export const TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH = path.join(
|
||||
TAURI_APP_DIR,
|
||||
"overrides/fixed-webview2.conf.json",
|
||||
);
|
||||
export const MANIFEST_DIR = path.join(cwd, "manifest");
|
||||
export const GITHUB_PROXY = "https://mirror.ghproxy.com/";
|
||||
export const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
|
@@ -26,7 +26,7 @@ export const resolveMihomo = async (): LatestVersionResolver => {
|
||||
const archMapping: ArchMapping = {
|
||||
[SupportedArch.WindowsX86_32]: "mihomo-windows-386-{}.zip",
|
||||
[SupportedArch.WindowsX86_64]: "mihomo-windows-amd64-compatible-{}.zip",
|
||||
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-{}.zip",
|
||||
[SupportedArch.WindowsArm64]: "mihomo-windows-arm64-{}.zip",
|
||||
[SupportedArch.LinuxAarch64]: "mihomo-linux-arm64-{}.gz",
|
||||
[SupportedArch.LinuxAmd64]: "mihomo-linux-amd64-compatible-{}.gz",
|
||||
[SupportedArch.LinuxI386]: "mihomo-linux-386-{}.gz",
|
||||
@@ -55,7 +55,7 @@ export const resolveMihomoAlpha = async (): LatestVersionResolver => {
|
||||
const archMapping: ArchMapping = {
|
||||
[SupportedArch.WindowsX86_32]: "mihomo-windows-386-{}.zip",
|
||||
[SupportedArch.WindowsX86_64]: "mihomo-windows-amd64-compatible-{}.zip",
|
||||
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-{}.zip",
|
||||
[SupportedArch.WindowsArm64]: "mihomo-windows-arm64-{}.zip",
|
||||
[SupportedArch.LinuxAarch64]: "mihomo-linux-arm64-{}.gz",
|
||||
[SupportedArch.LinuxAmd64]: "mihomo-linux-amd64-compatible-{}.gz",
|
||||
[SupportedArch.LinuxI386]: "mihomo-linux-386-{}.gz",
|
||||
@@ -85,7 +85,7 @@ export const resolveClashRs = async (): LatestVersionResolver => {
|
||||
const archMapping: ArchMapping = {
|
||||
[SupportedArch.WindowsX86_32]: "clash-i686-pc-windows-msvc-static-crt.exe",
|
||||
[SupportedArch.WindowsX86_64]: "clash-x86_64-pc-windows-msvc.exe",
|
||||
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-alpha-{}.zip",
|
||||
[SupportedArch.WindowsArm64]: "clash-aarch64-pc-windows-msvc.exe",
|
||||
[SupportedArch.LinuxAarch64]: "clash-aarch64-unknown-linux-gnu-static-crt",
|
||||
[SupportedArch.LinuxAmd64]: "clash-x86_64-unknown-linux-gnu-static-crt",
|
||||
[SupportedArch.LinuxI386]: "clash-i686-unknown-linux-gnu-static-crt",
|
||||
@@ -102,6 +102,37 @@ export const resolveClashRs = async (): LatestVersionResolver => {
|
||||
};
|
||||
};
|
||||
|
||||
export const resolveClashRsAlpha = async (): LatestVersionResolver => {
|
||||
const resp = await fetch(
|
||||
"https://github.com/Watfaq/clash-rs/releases/download/latest/version.txt",
|
||||
);
|
||||
|
||||
const alphaVersion = resp.ok
|
||||
? (await resp.text()).trim().split(" ").pop()!
|
||||
: "latest";
|
||||
|
||||
consola.debug(`clash-rs alpha latest release: ${alphaVersion}`);
|
||||
|
||||
const archMapping: ArchMapping = {
|
||||
[SupportedArch.WindowsX86_32]: "clash-i686-pc-windows-msvc-static-crt.exe",
|
||||
[SupportedArch.WindowsX86_64]: "clash-x86_64-pc-windows-msvc.exe",
|
||||
[SupportedArch.WindowsArm64]: "clash-aarch64-pc-windows-msvc.exe",
|
||||
[SupportedArch.LinuxAarch64]: "clash-aarch64-unknown-linux-gnu-static-crt",
|
||||
[SupportedArch.LinuxAmd64]: "clash-x86_64-unknown-linux-gnu-static-crt",
|
||||
[SupportedArch.LinuxI386]: "clash-i686-unknown-linux-gnu-static-crt",
|
||||
[SupportedArch.DarwinArm64]: "clash-aarch64-apple-darwin",
|
||||
[SupportedArch.DarwinX64]: "clash-x86_64-apple-darwin",
|
||||
[SupportedArch.LinuxArmv7]: "clash-armv7-unknown-linux-gnueabi-static-crt",
|
||||
[SupportedArch.LinuxArmv7hf]: "clash-armv7-unknown-linux-gnueabihf",
|
||||
} satisfies ArchMapping;
|
||||
|
||||
return {
|
||||
name: "clash_rs_alpha",
|
||||
version: alphaVersion,
|
||||
archMapping,
|
||||
};
|
||||
};
|
||||
|
||||
export const resolveClashPremium = async (): LatestVersionResolver => {
|
||||
const latestRelease = await octokit.rest.repos.getLatestRelease(
|
||||
applyProxy({
|
||||
@@ -115,7 +146,7 @@ export const resolveClashPremium = async (): LatestVersionResolver => {
|
||||
const archMapping: ArchMapping = {
|
||||
[SupportedArch.WindowsX86_32]: "clash-windows-386-n{}.zip",
|
||||
[SupportedArch.WindowsX86_64]: "clash-windows-amd64-n{}.zip",
|
||||
// [SupportedArch.WindowsAarch64]: "clash-windows-arm64-n{}.zip",
|
||||
[SupportedArch.WindowsArm64]: "clash-windows-arm64-n{}.zip",
|
||||
[SupportedArch.LinuxAarch64]: "clash-linux-arm64-n{}.gz",
|
||||
[SupportedArch.LinuxAmd64]: "clash-linux-amd64-n{}.gz",
|
||||
[SupportedArch.LinuxI386]: "clash-linux-386-n{}.gz",
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
getClashBackupInfo,
|
||||
getClashMetaAlphaInfo,
|
||||
getClashMetaInfo,
|
||||
getClashRustAlphaInfo,
|
||||
getClashRustInfo,
|
||||
getNyanpasuServiceInfo,
|
||||
} from "./resource";
|
||||
@@ -197,4 +198,8 @@ export class Resolve {
|
||||
public async clashRust() {
|
||||
return await this.sidecar(getClashRustInfo(this.infoOption));
|
||||
}
|
||||
|
||||
public async clashRustAlpha() {
|
||||
return await this.sidecar(getClashRustAlphaInfo(this.infoOption));
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,10 @@ import {
|
||||
CLASH_META_MANIFEST,
|
||||
} from "../manifest/clash-meta";
|
||||
import { CLASH_MANIFEST } from "../manifest/clash-premium";
|
||||
import { CLASH_RS_MANIFEST } from "../manifest/clash-rs";
|
||||
import {
|
||||
CLASH_RS_ALPHA_MANIFEST,
|
||||
CLASH_RS_MANIFEST,
|
||||
} from "../manifest/clash-rs";
|
||||
import { BinInfo, SupportedArch } from "../types";
|
||||
import { getProxyAgent } from "./";
|
||||
import { SIDECAR_HOST } from "./consts";
|
||||
@@ -26,6 +29,8 @@ function mappingArch(platform: NodeJS.Platform, arch: NodeArch): SupportedArch {
|
||||
return SupportedArch.WindowsX86_64;
|
||||
case "win32-ia32":
|
||||
return SupportedArch.WindowsX86_32;
|
||||
case "win32-arm64":
|
||||
return SupportedArch.WindowsArm64;
|
||||
case "linux-x64":
|
||||
return SupportedArch.LinuxAmd64;
|
||||
case "linux-ia32":
|
||||
@@ -206,6 +211,68 @@ export const getClashRustInfo = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const getClashRsAlphaLatestVersion = async () => {
|
||||
const { VERSION_URL } = CLASH_RS_ALPHA_MANIFEST;
|
||||
|
||||
try {
|
||||
const opts = {} as Partial<RequestInit>;
|
||||
|
||||
const httpProxy = getProxyAgent();
|
||||
|
||||
if (httpProxy) {
|
||||
opts.agent = httpProxy;
|
||||
}
|
||||
|
||||
const response = await fetch(VERSION_URL!, {
|
||||
method: "GET",
|
||||
...opts,
|
||||
});
|
||||
|
||||
const v = (await response.text()).trim().split(" ").pop()!;
|
||||
|
||||
consola.info(`Clash Rs Alpha latest release version: ${v}`);
|
||||
|
||||
return v.trim();
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest release version:", error);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const getClashRustAlphaInfo = async ({
|
||||
platform,
|
||||
arch,
|
||||
sidecarHost,
|
||||
}: {
|
||||
platform: string;
|
||||
arch: string;
|
||||
sidecarHost?: string;
|
||||
}): Promise<BinInfo> => {
|
||||
const { ARCH_MAPPING, URL_PREFIX } = CLASH_RS_ALPHA_MANIFEST;
|
||||
const version = await getClashRsAlphaLatestVersion();
|
||||
const archLabel = mappingArch(platform as NodeJS.Platform, arch as NodeArch);
|
||||
const name = ARCH_MAPPING[archLabel].replace("{}", version as string);
|
||||
|
||||
const isWin = platform === "win32";
|
||||
|
||||
const exeFile = `${name}`;
|
||||
|
||||
const downloadURL = `${URL_PREFIX}/${name}`;
|
||||
|
||||
const tmpFile = `${name}`;
|
||||
|
||||
const targetFile = `clash-rs-alpha-${sidecarHost}${isWin ? ".exe" : ""}`;
|
||||
|
||||
return {
|
||||
name: "clash-rs-alpha",
|
||||
targetFile,
|
||||
exeFile,
|
||||
tmpFile,
|
||||
downloadURL,
|
||||
};
|
||||
};
|
||||
|
||||
export const getMetaAlphaLatestVersion = async () => {
|
||||
const { VERSION_URL } = CLASH_META_ALPHA_MANIFEST;
|
||||
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 7320;
|
||||
private const uint CodeGenVersion = 7331;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -138,6 +138,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
// Ensure that conditions met for that branch are also met for the current one.
|
||||
// Prefer the latest sources for the phi node.
|
||||
|
||||
int undefCount = 0;
|
||||
|
||||
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
||||
{
|
||||
BasicBlock phiBlock = phiNode.GetBlock(i);
|
||||
@@ -159,6 +161,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
return match;
|
||||
}
|
||||
}
|
||||
else if (phiSource.Type == OperandType.Undefined)
|
||||
{
|
||||
undefCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// If all sources but one are undefined, we can assume that the one
|
||||
// that is not undefined is the right one.
|
||||
|
||||
if (undefCount == phiNode.SourcesCount - 1)
|
||||
{
|
||||
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
||||
{
|
||||
Operand phiSource = phiNode.GetSource(i);
|
||||
|
||||
if (phiSource.Type != OperandType.Undefined)
|
||||
{
|
||||
return phiSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -61,7 +61,7 @@ pub enum DnsClient {
|
||||
stream: ProxyClientStream<MonProxyStream<ShadowTcpStream>>,
|
||||
},
|
||||
UdpRemote {
|
||||
socket: MonProxySocket,
|
||||
socket: MonProxySocket<ShadowUdpSocket>,
|
||||
ns: Address,
|
||||
control: UdpSocketControlData,
|
||||
server_windows: LruCache<u64, PacketWindowFilter>,
|
||||
|
@@ -217,7 +217,7 @@ where
|
||||
peer_addr: SocketAddr,
|
||||
bypassed_ipv4_socket: Option<ShadowUdpSocket>,
|
||||
bypassed_ipv6_socket: Option<ShadowUdpSocket>,
|
||||
proxied_socket: Option<MonProxySocket>,
|
||||
proxied_socket: Option<MonProxySocket<ShadowUdpSocket>>,
|
||||
keepalive_tx: mpsc::Sender<SocketAddr>,
|
||||
keepalive_flag: bool,
|
||||
balancer: PingBalancer,
|
||||
@@ -409,7 +409,7 @@ where
|
||||
|
||||
#[inline]
|
||||
async fn receive_from_proxied_opt(
|
||||
socket: &Option<MonProxySocket>,
|
||||
socket: &Option<MonProxySocket<ShadowUdpSocket>>,
|
||||
buf: &mut Vec<u8>,
|
||||
) -> io::Result<(usize, Address, Option<UdpSocketControlData>)> {
|
||||
match *socket {
|
||||
|
@@ -3,25 +3,44 @@
|
||||
use std::{io, net::SocketAddr, sync::Arc};
|
||||
|
||||
use shadowsocks::{
|
||||
relay::{socks5::Address, udprelay::options::UdpSocketControlData},
|
||||
relay::{
|
||||
socks5::Address,
|
||||
udprelay::{options::UdpSocketControlData, DatagramReceive, DatagramSend},
|
||||
},
|
||||
ProxySocket,
|
||||
};
|
||||
use tokio::net::ToSocketAddrs;
|
||||
|
||||
use super::flow::FlowStat;
|
||||
|
||||
/// Monitored `ProxySocket`
|
||||
pub struct MonProxySocket {
|
||||
socket: ProxySocket,
|
||||
pub struct MonProxySocket<S> {
|
||||
socket: ProxySocket<S>,
|
||||
flow_stat: Arc<FlowStat>,
|
||||
}
|
||||
|
||||
impl MonProxySocket {
|
||||
impl<S> MonProxySocket<S> {
|
||||
/// Create a new socket with flow monitor
|
||||
pub fn from_socket(socket: ProxySocket, flow_stat: Arc<FlowStat>) -> MonProxySocket {
|
||||
pub fn from_socket(socket: ProxySocket<S>, flow_stat: Arc<FlowStat>) -> MonProxySocket<S> {
|
||||
MonProxySocket { socket, flow_stat }
|
||||
}
|
||||
|
||||
/// Get the underlying `ProxySocket<S>` immutable reference
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &ProxySocket<S> {
|
||||
&self.socket
|
||||
}
|
||||
|
||||
/// Get the flow statistic data
|
||||
#[inline]
|
||||
pub fn flow_stat(&self) -> &FlowStat {
|
||||
&self.flow_stat
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MonProxySocket<S>
|
||||
where
|
||||
S: DatagramSend,
|
||||
{
|
||||
/// Send a UDP packet to addr through proxy
|
||||
#[inline]
|
||||
pub async fn send(&self, addr: &Address, payload: &[u8]) -> io::Result<()> {
|
||||
@@ -47,7 +66,7 @@ impl MonProxySocket {
|
||||
|
||||
/// Send a UDP packet to target from proxy
|
||||
#[inline]
|
||||
pub async fn send_to<A: ToSocketAddrs>(&self, target: A, addr: &Address, payload: &[u8]) -> io::Result<()> {
|
||||
pub async fn send_to(&self, target: SocketAddr, addr: &Address, payload: &[u8]) -> io::Result<()> {
|
||||
let n = self.socket.send_to(target, addr, payload).await?;
|
||||
self.flow_stat.incr_tx(n as u64);
|
||||
|
||||
@@ -56,9 +75,9 @@ impl MonProxySocket {
|
||||
|
||||
/// Send a UDP packet to target from proxy
|
||||
#[inline]
|
||||
pub async fn send_to_with_ctrl<A: ToSocketAddrs>(
|
||||
pub async fn send_to_with_ctrl(
|
||||
&self,
|
||||
target: A,
|
||||
target: SocketAddr,
|
||||
addr: &Address,
|
||||
control: &UdpSocketControlData,
|
||||
payload: &[u8],
|
||||
@@ -68,7 +87,12 @@ impl MonProxySocket {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MonProxySocket<S>
|
||||
where
|
||||
S: DatagramReceive,
|
||||
{
|
||||
/// Receive packet from Shadowsocks' UDP server
|
||||
///
|
||||
/// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet
|
||||
@@ -126,14 +150,4 @@ impl MonProxySocket {
|
||||
|
||||
Ok((n, peer_addr, addr, control))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &ProxySocket {
|
||||
&self.socket
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn flow_stat(&self) -> &FlowStat {
|
||||
&self.flow_stat
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,10 @@ use shadowsocks::{
|
||||
config::ServerUser,
|
||||
crypto::CipherCategory,
|
||||
lookup_then,
|
||||
net::{get_ip_stack_capabilities, AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket},
|
||||
net::{
|
||||
get_ip_stack_capabilities, AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket,
|
||||
UdpSocket as InboundUdpSocket,
|
||||
},
|
||||
relay::{
|
||||
socks5::Address,
|
||||
udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE},
|
||||
@@ -87,7 +90,7 @@ pub struct UdpServer {
|
||||
keepalive_tx: mpsc::Sender<NatKey>,
|
||||
keepalive_rx: mpsc::Receiver<NatKey>,
|
||||
time_to_live: Duration,
|
||||
listener: Arc<MonProxySocket>,
|
||||
listener: Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
svr_cfg: ServerConfig,
|
||||
}
|
||||
|
||||
@@ -276,7 +279,7 @@ impl UdpServer {
|
||||
|
||||
async fn recv_one_packet(
|
||||
context: &ServiceContext,
|
||||
l: &MonProxySocket,
|
||||
l: &MonProxySocket<InboundUdpSocket>,
|
||||
buffer: &mut [u8],
|
||||
) -> Option<(usize, SocketAddr, Address, Option<UdpSocketControlData>)> {
|
||||
let (n, peer_addr, target_addr, control) = match l.recv_from_with_ctrl(buffer).await {
|
||||
@@ -316,7 +319,7 @@ impl UdpServer {
|
||||
|
||||
async fn send_packet(
|
||||
&mut self,
|
||||
listener: &Arc<MonProxySocket>,
|
||||
listener: &Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
peer_addr: SocketAddr,
|
||||
target_addr: Address,
|
||||
control: Option<UdpSocketControlData>,
|
||||
@@ -394,7 +397,7 @@ impl Drop for UdpAssociation {
|
||||
impl UdpAssociation {
|
||||
fn new_association(
|
||||
context: Arc<ServiceContext>,
|
||||
inbound: Arc<MonProxySocket>,
|
||||
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
peer_addr: SocketAddr,
|
||||
keepalive_tx: mpsc::Sender<NatKey>,
|
||||
) -> UdpAssociation {
|
||||
@@ -405,7 +408,7 @@ impl UdpAssociation {
|
||||
#[cfg(feature = "aead-cipher-2022")]
|
||||
fn new_session(
|
||||
context: Arc<ServiceContext>,
|
||||
inbound: Arc<MonProxySocket>,
|
||||
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
peer_addr: SocketAddr,
|
||||
keepalive_tx: mpsc::Sender<NatKey>,
|
||||
client_session_id: u64,
|
||||
@@ -447,7 +450,7 @@ struct UdpAssociationContext {
|
||||
outbound_ipv6_socket: Option<OutboundUdpSocket>,
|
||||
keepalive_tx: mpsc::Sender<NatKey>,
|
||||
keepalive_flag: bool,
|
||||
inbound: Arc<MonProxySocket>,
|
||||
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
// AEAD 2022
|
||||
client_session: Option<ClientSessionContext>,
|
||||
server_session_id: u64,
|
||||
@@ -472,7 +475,7 @@ fn generate_server_session_id() -> u64 {
|
||||
impl UdpAssociationContext {
|
||||
fn create(
|
||||
context: Arc<ServiceContext>,
|
||||
inbound: Arc<MonProxySocket>,
|
||||
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
|
||||
peer_addr: SocketAddr,
|
||||
keepalive_tx: mpsc::Sender<NatKey>,
|
||||
client_session_id: Option<u64>,
|
||||
|
@@ -24,7 +24,7 @@ use std::{
|
||||
))]
|
||||
use futures::future;
|
||||
use futures::ready;
|
||||
use pin_project::pin_project;
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
@@ -86,9 +86,7 @@ fn make_mtu_error(packet_size: usize, mtu: usize) -> io::Error {
|
||||
|
||||
/// Wrappers for outbound `UdpSocket`
|
||||
#[derive(Debug)]
|
||||
#[pin_project]
|
||||
pub struct UdpSocket {
|
||||
#[pin]
|
||||
socket: tokio::net::UdpSocket,
|
||||
mtu: Option<usize>,
|
||||
}
|
||||
|
209
shadowsocks-rust/crates/shadowsocks/src/relay/udprelay/compat.rs
Normal file
209
shadowsocks-rust/crates/shadowsocks/src/relay/udprelay/compat.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::ready;
|
||||
use pin_project::pin_project;
|
||||
use tokio::io::ReadBuf;
|
||||
|
||||
use crate::net::UdpSocket;
|
||||
|
||||
/// A socket I/O object that can transport datagram
|
||||
pub trait DatagramSocket {
|
||||
/// Local binded address
|
||||
fn local_addr(&self) -> io::Result<SocketAddr>;
|
||||
}
|
||||
|
||||
/// A socket I/O object that can receive datagram
|
||||
pub trait DatagramReceive {
|
||||
/// `recv` data into `buf`
|
||||
fn poll_recv(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>>;
|
||||
/// `recv` data into `buf` with source address
|
||||
fn poll_recv_from(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<SocketAddr>>;
|
||||
/// Check if the underlying I/O object is ready for `recv`
|
||||
fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
|
||||
}
|
||||
|
||||
/// A socket I/O object that can send datagram
|
||||
pub trait DatagramSend {
|
||||
/// `send` data with `buf`, returning the sent bytes
|
||||
fn poll_send(&self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>;
|
||||
/// `send` data with `buf` to `target`, returning the sent bytes
|
||||
fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>>;
|
||||
/// Check if the underlying I/O object is ready for `send`
|
||||
fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
|
||||
}
|
||||
|
||||
impl DatagramSocket for UdpSocket {
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.deref().local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl DatagramReceive for UdpSocket {
|
||||
fn poll_recv(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
UdpSocket::poll_recv(self, cx, buf)
|
||||
}
|
||||
|
||||
fn poll_recv_from(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<SocketAddr>> {
|
||||
UdpSocket::poll_recv_from(self, cx, buf)
|
||||
}
|
||||
|
||||
fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.deref().poll_recv_ready(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl DatagramSend for UdpSocket {
|
||||
fn poll_send(&self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
UdpSocket::poll_send(self, cx, buf)
|
||||
}
|
||||
|
||||
fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
|
||||
UdpSocket::poll_send_to(self, cx, buf, target)
|
||||
}
|
||||
|
||||
fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.deref().poll_send_ready(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `recv`
|
||||
#[pin_project]
|
||||
pub struct RecvFut<'a, S: DatagramReceive + ?Sized> {
|
||||
#[pin]
|
||||
io: &'a S,
|
||||
buf: &'a mut [u8],
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramReceive + ?Sized> Future for RecvFut<'a, S> {
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let mut read_buf = ReadBuf::new(this.buf);
|
||||
ready!(this.io.poll_recv(cx, &mut read_buf))?;
|
||||
Ok(read_buf.filled().len()).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `recv_from`
|
||||
#[pin_project]
|
||||
pub struct RecvFromFut<'a, S: DatagramReceive + ?Sized> {
|
||||
#[pin]
|
||||
io: &'a S,
|
||||
buf: &'a mut [u8],
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramReceive + ?Sized> Future for RecvFromFut<'a, S> {
|
||||
type Output = io::Result<(usize, SocketAddr)>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let mut read_buf = ReadBuf::new(this.buf);
|
||||
let src_addr = ready!(this.io.poll_recv_from(cx, &mut read_buf))?;
|
||||
Ok((read_buf.filled().len(), src_addr)).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `recv_ready`
|
||||
pub struct RecvReadyFut<'a, S: DatagramReceive + ?Sized> {
|
||||
io: &'a S,
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramReceive + ?Sized> Future for RecvReadyFut<'a, S> {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.io.poll_recv_ready(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `send`
|
||||
pub struct SendFut<'a, S: DatagramSend + ?Sized> {
|
||||
io: &'a S,
|
||||
buf: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramSend + ?Sized> Future for SendFut<'a, S> {
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.io.poll_send(cx, self.buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `send_to`
|
||||
pub struct SendToFut<'a, S: DatagramSend + ?Sized> {
|
||||
io: &'a S,
|
||||
target: SocketAddr,
|
||||
buf: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramSend + ?Sized> Future for SendToFut<'a, S> {
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.io.poll_send_to(cx, self.buf, self.target)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for `recv_ready`
|
||||
pub struct SendReadyFut<'a, S: DatagramSend + ?Sized> {
|
||||
io: &'a S,
|
||||
}
|
||||
|
||||
impl<'a, S: DatagramSend + ?Sized> Future for SendReadyFut<'a, S> {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.io.poll_send_ready(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for `DatagramReceive`
|
||||
pub trait DatagramReceiveExt: DatagramReceive {
|
||||
/// Async method for `poll_recv`
|
||||
fn recv<'a, 'b>(&'a self, buf: &'a mut [u8]) -> RecvFut<'a, Self> {
|
||||
RecvFut { io: self, buf }
|
||||
}
|
||||
|
||||
/// Async method for `poll_recv_from`
|
||||
fn recv_from<'a, 'b>(&'a self, buf: &'a mut [u8]) -> RecvFromFut<'a, Self> {
|
||||
RecvFromFut { io: self, buf }
|
||||
}
|
||||
|
||||
/// Async method for `poll_recv_ready`
|
||||
fn recv_ready<'a>(&'a self) -> RecvReadyFut<'a, Self> {
|
||||
RecvReadyFut { io: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: DatagramReceive> DatagramReceiveExt for S {}
|
||||
|
||||
/// Extension methods for `DatagramSend`
|
||||
pub trait DatagramSendExt: DatagramSend {
|
||||
/// Async method for `poll_send`
|
||||
fn send<'a>(&'a self, buf: &'a [u8]) -> SendFut<'a, Self> {
|
||||
SendFut { io: self, buf }
|
||||
}
|
||||
|
||||
/// Async method for `poll_send_to`
|
||||
fn send_to<'a>(&'a self, buf: &'a [u8], target: SocketAddr) -> SendToFut<'a, Self> {
|
||||
SendToFut { io: self, target, buf }
|
||||
}
|
||||
|
||||
/// Async method for `poll_send_ready`
|
||||
fn send_ready<'a>(&'a self) -> SendReadyFut<'a, Self> {
|
||||
SendReadyFut { io: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: DatagramSend> DatagramSendExt for S {}
|
@@ -50,10 +50,12 @@
|
||||
use std::time::Duration;
|
||||
|
||||
pub use self::proxy_socket::ProxySocket;
|
||||
pub use compat::{DatagramReceive, DatagramReceiveExt, DatagramSend, DatagramSendExt, DatagramSocket};
|
||||
|
||||
mod aead;
|
||||
#[cfg(feature = "aead-cipher-2022")]
|
||||
mod aead_2022;
|
||||
mod compat;
|
||||
pub mod crypto_io;
|
||||
pub mod options;
|
||||
pub mod proxy_socket;
|
||||
|
@@ -12,7 +12,7 @@ use byte_string::ByteStr;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use log::{info, trace, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::{io::ReadBuf, net::ToSocketAddrs, time};
|
||||
use tokio::{io::ReadBuf, time};
|
||||
|
||||
use crate::{
|
||||
config::{ServerAddr, ServerConfig, ServerUserManager},
|
||||
@@ -22,9 +22,12 @@ use crate::{
|
||||
relay::{socks5::Address, udprelay::options::UdpSocketControlData},
|
||||
};
|
||||
|
||||
use super::crypto_io::{
|
||||
decrypt_client_payload, decrypt_server_payload, encrypt_client_payload, encrypt_server_payload, ProtocolError,
|
||||
ProtocolResult,
|
||||
use super::{
|
||||
compat::{DatagramReceive, DatagramReceiveExt, DatagramSend, DatagramSendExt, DatagramSocket},
|
||||
crypto_io::{
|
||||
decrypt_client_payload, decrypt_server_payload, encrypt_client_payload, encrypt_server_payload, ProtocolError,
|
||||
ProtocolResult,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -70,9 +73,9 @@ pub type ProxySocketResult<T> = Result<T, ProxySocketError>;
|
||||
|
||||
/// UDP client for communicating with ShadowSocks' server
|
||||
#[derive(Debug)]
|
||||
pub struct ProxySocket {
|
||||
pub struct ProxySocket<S> {
|
||||
socket_type: UdpSocketType,
|
||||
socket: ShadowUdpSocket,
|
||||
io: S,
|
||||
method: CipherKind,
|
||||
key: Box<[u8]>,
|
||||
send_timeout: Option<Duration>,
|
||||
@@ -82,9 +85,12 @@ pub struct ProxySocket {
|
||||
user_manager: Option<Arc<ServerUserManager>>,
|
||||
}
|
||||
|
||||
impl ProxySocket {
|
||||
impl ProxySocket<ShadowUdpSocket> {
|
||||
/// Create a client to communicate with Shadowsocks' UDP server (outbound)
|
||||
pub async fn connect(context: SharedContext, svr_cfg: &ServerConfig) -> ProxySocketResult<ProxySocket> {
|
||||
pub async fn connect(
|
||||
context: SharedContext,
|
||||
svr_cfg: &ServerConfig,
|
||||
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
|
||||
ProxySocket::connect_with_opts(context, svr_cfg, &DEFAULT_CONNECT_OPTS)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
@@ -95,7 +101,7 @@ impl ProxySocket {
|
||||
context: SharedContext,
|
||||
svr_cfg: &ServerConfig,
|
||||
opts: &ConnectOpts,
|
||||
) -> ProxySocketResult<ProxySocket> {
|
||||
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
|
||||
// Note: Plugins doesn't support UDP relay
|
||||
|
||||
let socket = ShadowUdpSocket::connect_server_with_opts(&context, svr_cfg.udp_external_addr(), opts).await?;
|
||||
@@ -115,42 +121,11 @@ impl ProxySocket {
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a `ProxySocket` from a `UdpSocket`
|
||||
pub fn from_socket<S>(
|
||||
socket_type: UdpSocketType,
|
||||
/// Create a `ProxySocket` binding to a specific address (inbound)
|
||||
pub async fn bind(
|
||||
context: SharedContext,
|
||||
svr_cfg: &ServerConfig,
|
||||
socket: S,
|
||||
) -> ProxySocket
|
||||
where
|
||||
S: Into<ShadowUdpSocket>,
|
||||
{
|
||||
let key = svr_cfg.key().to_vec().into_boxed_slice();
|
||||
let method = svr_cfg.method();
|
||||
|
||||
// NOTE: svr_cfg.timeout() is not for this socket, but for associations.
|
||||
|
||||
ProxySocket {
|
||||
socket_type,
|
||||
socket: socket.into(),
|
||||
method,
|
||||
key,
|
||||
send_timeout: None,
|
||||
recv_timeout: None,
|
||||
context,
|
||||
identity_keys: match socket_type {
|
||||
UdpSocketType::Client => svr_cfg.clone_identity_keys(),
|
||||
UdpSocketType::Server => Arc::new(Vec::new()),
|
||||
},
|
||||
user_manager: match socket_type {
|
||||
UdpSocketType::Client => None,
|
||||
UdpSocketType::Server => svr_cfg.clone_user_manager(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `ProxySocket` binding to a specific address (inbound)
|
||||
pub async fn bind(context: SharedContext, svr_cfg: &ServerConfig) -> ProxySocketResult<ProxySocket> {
|
||||
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
|
||||
ProxySocket::bind_with_opts(context, svr_cfg, AcceptOpts::default())
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
@@ -161,7 +136,7 @@ impl ProxySocket {
|
||||
context: SharedContext,
|
||||
svr_cfg: &ServerConfig,
|
||||
opts: AcceptOpts,
|
||||
) -> ProxySocketResult<ProxySocket> {
|
||||
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
|
||||
// Plugins doesn't support UDP
|
||||
let socket = match svr_cfg.udp_external_addr() {
|
||||
ServerAddr::SocketAddr(sa) => ShadowUdpSocket::listen_with_opts(sa, opts).await?,
|
||||
@@ -179,7 +154,54 @@ impl ProxySocket {
|
||||
socket,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ProxySocket<S> {
|
||||
/// Create a `ProxySocket` from a I/O object that impls `DatagramTransport`
|
||||
pub fn from_socket(
|
||||
socket_type: UdpSocketType,
|
||||
context: SharedContext,
|
||||
svr_cfg: &ServerConfig,
|
||||
socket: S,
|
||||
) -> ProxySocket<S> {
|
||||
let key = svr_cfg.key().to_vec().into_boxed_slice();
|
||||
let method = svr_cfg.method();
|
||||
|
||||
// NOTE: svr_cfg.timeout() is not for this socket, but for associations.
|
||||
ProxySocket {
|
||||
socket_type,
|
||||
io: socket,
|
||||
method,
|
||||
key,
|
||||
send_timeout: None,
|
||||
recv_timeout: None,
|
||||
context,
|
||||
identity_keys: match socket_type {
|
||||
UdpSocketType::Client => svr_cfg.clone_identity_keys(),
|
||||
UdpSocketType::Server => Arc::new(Vec::new()),
|
||||
},
|
||||
user_manager: match socket_type {
|
||||
UdpSocketType::Client => None,
|
||||
UdpSocketType::Server => svr_cfg.clone_user_manager(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `send` timeout, `None` will clear timeout
|
||||
pub fn set_send_timeout(&mut self, t: Option<Duration>) {
|
||||
self.send_timeout = t;
|
||||
}
|
||||
|
||||
/// Set `recv` timeout, `None` will clear timeout
|
||||
pub fn set_recv_timeout(&mut self, t: Option<Duration>) {
|
||||
self.recv_timeout = t;
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ProxySocket<S>
|
||||
where
|
||||
S: DatagramSend,
|
||||
{
|
||||
fn encrypt_send_buffer(
|
||||
&self,
|
||||
addr: &Address,
|
||||
@@ -241,8 +263,8 @@ impl ProxySocket {
|
||||
);
|
||||
|
||||
let send_len = match self.send_timeout {
|
||||
None => self.socket.send(&send_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.socket.send(&send_buf)).await {
|
||||
None => self.io.send(&send_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.io.send(&send_buf)).await {
|
||||
Ok(Ok(l)) => l,
|
||||
Ok(Err(err)) => return Err(err.into()),
|
||||
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
|
||||
@@ -295,7 +317,7 @@ impl ProxySocket {
|
||||
|
||||
let n_send_buf = send_buf.len();
|
||||
|
||||
match self.socket.poll_send(cx, &send_buf).map_err(|x| x.into()) {
|
||||
match self.io.poll_send(cx, &send_buf).map_err(|x| x.into()) {
|
||||
Poll::Ready(Ok(l)) => {
|
||||
if l == n_send_buf {
|
||||
Poll::Ready(Ok(payload.len()))
|
||||
@@ -340,14 +362,14 @@ impl ProxySocket {
|
||||
self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?;
|
||||
|
||||
info!(
|
||||
"UDP server client send to {}, payload length {} bytes, packet length {} bytes",
|
||||
"UDP server client poll_send_to to {}, payload length {} bytes, packet length {} bytes",
|
||||
target,
|
||||
payload.len(),
|
||||
send_buf.len()
|
||||
);
|
||||
|
||||
let n_send_buf = send_buf.len();
|
||||
match self.socket.poll_send_to(cx, &send_buf, target).map_err(|x| x.into()) {
|
||||
match self.io.poll_send_to(cx, &send_buf, target).map_err(|x| x.into()) {
|
||||
Poll::Ready(Ok(l)) => {
|
||||
if l == n_send_buf {
|
||||
Poll::Ready(Ok(payload.len()))
|
||||
@@ -363,25 +385,20 @@ impl ProxySocket {
|
||||
///
|
||||
/// Check if socket is ready to `send`, or writable.
|
||||
pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<ProxySocketResult<()>> {
|
||||
self.socket.poll_send_ready(cx).map_err(|x| x.into())
|
||||
self.io.poll_send_ready(cx).map_err(|x| x.into())
|
||||
}
|
||||
|
||||
/// Send a UDP packet to target through proxy `target`
|
||||
pub async fn send_to<A: ToSocketAddrs>(
|
||||
&self,
|
||||
target: A,
|
||||
addr: &Address,
|
||||
payload: &[u8],
|
||||
) -> ProxySocketResult<usize> {
|
||||
pub async fn send_to(&self, target: SocketAddr, addr: &Address, payload: &[u8]) -> ProxySocketResult<usize> {
|
||||
self.send_to_with_ctrl(target, addr, &DEFAULT_SOCKET_CONTROL, payload)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Send a UDP packet to target through proxy `target`
|
||||
pub async fn send_to_with_ctrl<A: ToSocketAddrs>(
|
||||
pub async fn send_to_with_ctrl(
|
||||
&self,
|
||||
target: A,
|
||||
target: SocketAddr,
|
||||
addr: &Address,
|
||||
control: &UdpSocketControlData,
|
||||
payload: &[u8],
|
||||
@@ -390,7 +407,7 @@ impl ProxySocket {
|
||||
self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?;
|
||||
|
||||
trace!(
|
||||
"UDP server client send to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes",
|
||||
"UDP server client send_to to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes",
|
||||
addr,
|
||||
control,
|
||||
payload.len(),
|
||||
@@ -398,8 +415,8 @@ impl ProxySocket {
|
||||
);
|
||||
|
||||
let send_len = match self.send_timeout {
|
||||
None => self.socket.send_to(&send_buf, target).await?,
|
||||
Some(d) => match time::timeout(d, self.socket.send_to(&send_buf, target)).await {
|
||||
None => self.io.send_to(&send_buf, target).await?,
|
||||
Some(d) => match time::timeout(d, self.io.send_to(&send_buf, target)).await {
|
||||
Ok(Ok(l)) => l,
|
||||
Ok(Err(err)) => return Err(err.into()),
|
||||
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
|
||||
@@ -408,7 +425,7 @@ impl ProxySocket {
|
||||
|
||||
if send_buf.len() != send_len {
|
||||
warn!(
|
||||
"UDP server client send {} bytes, but actually sent {} bytes",
|
||||
"UDP server client send_to {} bytes, but actually sent {} bytes",
|
||||
send_buf.len(),
|
||||
send_len
|
||||
);
|
||||
@@ -416,7 +433,12 @@ impl ProxySocket {
|
||||
|
||||
Ok(send_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ProxySocket<S>
|
||||
where
|
||||
S: DatagramReceive,
|
||||
{
|
||||
fn decrypt_recv_buffer(
|
||||
&self,
|
||||
recv_buf: &mut [u8],
|
||||
@@ -448,10 +470,9 @@ impl ProxySocket {
|
||||
&self,
|
||||
recv_buf: &mut [u8],
|
||||
) -> ProxySocketResult<(usize, Address, usize, Option<UdpSocketControlData>)> {
|
||||
// Waiting for response from server SERVER -> CLIENT
|
||||
let recv_n = match self.recv_timeout {
|
||||
None => self.socket.recv(recv_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.socket.recv(recv_buf)).await {
|
||||
None => self.io.recv(recv_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.io.recv(recv_buf)).await {
|
||||
Ok(Ok(l)) => l,
|
||||
Ok(Err(err)) => return Err(err.into()),
|
||||
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
|
||||
@@ -498,8 +519,8 @@ impl ProxySocket {
|
||||
) -> ProxySocketResult<(usize, SocketAddr, Address, usize, Option<UdpSocketControlData>)> {
|
||||
// Waiting for response from server SERVER -> CLIENT
|
||||
let (recv_n, target_addr) = match self.recv_timeout {
|
||||
None => self.socket.recv_from(recv_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.socket.recv_from(recv_buf)).await {
|
||||
None => self.io.recv_from(recv_buf).await?,
|
||||
Some(d) => match time::timeout(d, self.io.recv_from(recv_buf)).await {
|
||||
Ok(Ok(l)) => l,
|
||||
Ok(Err(err)) => return Err(err.into()),
|
||||
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
|
||||
@@ -542,7 +563,7 @@ impl ProxySocket {
|
||||
cx: &mut Context<'_>,
|
||||
recv_buf: &mut ReadBuf,
|
||||
) -> Poll<ProxySocketResult<(usize, Address, usize, Option<UdpSocketControlData>)>> {
|
||||
ready!(self.socket.poll_recv(cx, recv_buf))?;
|
||||
ready!(self.io.poll_recv(cx, recv_buf))?;
|
||||
|
||||
let n_recv = recv_buf.filled().len();
|
||||
|
||||
@@ -570,7 +591,7 @@ impl ProxySocket {
|
||||
cx: &mut Context<'_>,
|
||||
recv_buf: &mut ReadBuf,
|
||||
) -> Poll<ProxySocketResult<(usize, SocketAddr, Address, usize, Option<UdpSocketControlData>)>> {
|
||||
let src = ready!(self.socket.poll_recv_from(cx, recv_buf))?;
|
||||
let src = ready!(self.io.poll_recv_from(cx, recv_buf))?;
|
||||
|
||||
let n_recv = recv_buf.filled().len();
|
||||
match self.decrypt_recv_buffer(recv_buf.filled_mut(), self.user_manager.as_deref()) {
|
||||
@@ -581,29 +602,27 @@ impl ProxySocket {
|
||||
|
||||
/// poll family functions
|
||||
pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<ProxySocketResult<()>> {
|
||||
self.socket.poll_recv_ready(cx).map_err(|x| x.into())
|
||||
self.io.poll_recv_ready(cx).map_err(|x| x.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ProxySocket<S>
|
||||
where
|
||||
S: DatagramSocket,
|
||||
{
|
||||
/// Get local addr of socket
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.socket.local_addr()
|
||||
}
|
||||
|
||||
/// Set `send` timeout, `None` will clear timeout
|
||||
pub fn set_send_timeout(&mut self, t: Option<Duration>) {
|
||||
self.send_timeout = t;
|
||||
}
|
||||
|
||||
/// Set `recv` timeout, `None` will clear timeout
|
||||
pub fn set_recv_timeout(&mut self, t: Option<Duration>) {
|
||||
self.recv_timeout = t;
|
||||
self.io.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl AsRawFd for ProxySocket {
|
||||
impl<S> AsRawFd for ProxySocket<S>
|
||||
where
|
||||
S: AsRawFd,
|
||||
{
|
||||
/// Retrieve raw fd of the outbound socket
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.socket.as_raw_fd()
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ use shadowsocks::{
|
||||
config::{ServerConfig, ServerType},
|
||||
context::{Context, SharedContext},
|
||||
crypto::CipherKind,
|
||||
net::UdpSocket as ShadowUdpSocket,
|
||||
relay::{socks5::Address, udprelay::ProxySocket},
|
||||
};
|
||||
|
||||
@@ -15,7 +16,7 @@ async fn handle_udp_server_client(
|
||||
peer_addr: SocketAddr,
|
||||
remote_addr: Address,
|
||||
payload: &[u8],
|
||||
socket: &ProxySocket,
|
||||
socket: &ProxySocket<ShadowUdpSocket>,
|
||||
) -> io::Result<()> {
|
||||
let remote_socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||
|
||||
|
@@ -145,7 +145,6 @@ build_macos_dmg:
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
--codesign "B2324162A090F01F96111CF802A83F0F36674F80" \
|
||||
--notarize "notarytool-password" \
|
||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||
|
||||
|
@@ -114,7 +114,7 @@ dependencies {
|
||||
implementation "com.blacksquircle.ui:editorkit:2.2.0"
|
||||
implementation "com.blacksquircle.ui:language-json:2.2.0"
|
||||
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.8") {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
implementation "com.google.guava:guava:33.0.0-android"
|
||||
|
@@ -5,8 +5,8 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '8.6.0' apply false
|
||||
id 'com.android.library' version '8.6.0' apply false
|
||||
id 'com.android.application' version '8.6.1' apply false
|
||||
id 'com.android.library' version '8.6.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
|
||||
id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false
|
||||
id 'com.github.triplet.play' version '3.8.4' apply false
|
||||
|
@@ -1,3 +1,3 @@
|
||||
VERSION_CODE=392
|
||||
VERSION_NAME=1.9.5
|
||||
VERSION_CODE=394
|
||||
VERSION_NAME=1.9.6
|
||||
GO_VERSION=go1.23.1
|
||||
|
@@ -1,40 +1,39 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
actor Database {
|
||||
private static var writer: (any DatabaseWriter)?
|
||||
enum Database {
|
||||
static let sharedWriter = makeShared()
|
||||
|
||||
static func sharedWriter() throws -> any DatabaseWriter {
|
||||
if let writer {
|
||||
return writer
|
||||
}
|
||||
try FileManager.default.createDirectory(at: FilePath.sharedDirectory, withIntermediateDirectories: true)
|
||||
let database = try DatabasePool(path: FilePath.sharedDirectory.appendingPathComponent("settings.db").relativePath)
|
||||
var migrator = DatabaseMigrator().disablingDeferredForeignKeyChecks()
|
||||
migrator.registerMigration("initialize") { db in
|
||||
try db.create(table: "profiles") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("name", .text).notNull()
|
||||
t.column("order", .integer).notNull()
|
||||
t.column("type", .integer).notNull().defaults(to: ProfileType.local.rawValue)
|
||||
t.column("path", .text).notNull()
|
||||
t.column("remoteURL", .text)
|
||||
t.column("autoUpdate", .boolean).notNull().defaults(to: false)
|
||||
t.column("lastUpdated", .datetime)
|
||||
private static func makeShared() -> any DatabaseWriter {
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: FilePath.sharedDirectory, withIntermediateDirectories: true)
|
||||
let database = try DatabasePool(path: FilePath.sharedDirectory.appendingPathComponent("settings.db").relativePath)
|
||||
var migrator = DatabaseMigrator().disablingDeferredForeignKeyChecks()
|
||||
migrator.registerMigration("initialize") { db in
|
||||
try db.create(table: "profiles") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("name", .text).notNull()
|
||||
t.column("order", .integer).notNull()
|
||||
t.column("type", .integer).notNull().defaults(to: ProfileType.local.rawValue)
|
||||
t.column("path", .text).notNull()
|
||||
t.column("remoteURL", .text)
|
||||
t.column("autoUpdate", .boolean).notNull().defaults(to: false)
|
||||
t.column("lastUpdated", .datetime)
|
||||
}
|
||||
try db.create(table: "preferences") { t in
|
||||
t.primaryKey("name", .text, onConflict: .replace).notNull()
|
||||
t.column("data", .blob)
|
||||
}
|
||||
}
|
||||
try db.create(table: "preferences") { t in
|
||||
t.primaryKey("name", .text, onConflict: .replace).notNull()
|
||||
t.column("data", .blob)
|
||||
migrator.registerMigration("add_auto_update_interval") { db in
|
||||
try db.alter(table: "profiles") { t in
|
||||
t.add(column: "autoUpdateInterval", .integer).notNull().defaults(to: 0)
|
||||
}
|
||||
}
|
||||
try migrator.migrate(database)
|
||||
return database
|
||||
} catch {
|
||||
fatalError(error.localizedDescription)
|
||||
}
|
||||
migrator.registerMigration("add_auto_update_interval") { db in
|
||||
try db.alter(table: "profiles") { t in
|
||||
t.add(column: "autoUpdateInterval", .integer).notNull().defaults(to: 0)
|
||||
}
|
||||
}
|
||||
|
||||
try migrator.migrate(database)
|
||||
writer = database
|
||||
return database
|
||||
}
|
||||
}
|
||||
|
@@ -102,5 +102,5 @@ public struct TypedProfile: Transferable, Codable {
|
||||
}
|
||||
|
||||
public extension UTType {
|
||||
static var profile: UTType { .init(exportedAs: "io.nekohasekai.sfa.profile") }
|
||||
static var profile: UTType { .init(exportedAs: "io.nekohasekai.sfavt.profile") }
|
||||
}
|
||||
|
@@ -4,37 +4,37 @@ import GRDB
|
||||
public enum ProfileManager {
|
||||
public nonisolated static func create(_ profile: Profile) async throws {
|
||||
profile.order = try await nextOrder()
|
||||
try await Database.sharedWriter().write { db in
|
||||
try await Database.sharedWriter.write { db in
|
||||
try profile.insert(db, onConflict: .fail)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func get(_ profileID: Int64) async throws -> Profile? {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try Profile.fetchOne(db, id: profileID)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func get(by profileName: String) async throws -> Profile? {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try Profile.filter(Column("name") == profileName).fetchOne(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func delete(_ profile: Profile) async throws {
|
||||
_ = try await Database.sharedWriter().write { db in
|
||||
_ = try await Database.sharedWriter.write { db in
|
||||
try profile.delete(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func delete(by id: Int64) async throws {
|
||||
_ = try await Database.sharedWriter().write { db in
|
||||
_ = try await Database.sharedWriter.write { db in
|
||||
try Profile.deleteOne(db, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func delete(_ profileList: [Profile]) async throws -> Int {
|
||||
try await Database.sharedWriter().write { db in
|
||||
try await Database.sharedWriter.write { db in
|
||||
try Profile.deleteAll(db, keys: profileList.map {
|
||||
["id": $0.id!]
|
||||
})
|
||||
@@ -42,20 +42,20 @@ public enum ProfileManager {
|
||||
}
|
||||
|
||||
public nonisolated static func delete(by id: [Int64]) async throws -> Int {
|
||||
try await Database.sharedWriter().write { db in
|
||||
try await Database.sharedWriter.write { db in
|
||||
try Profile.deleteAll(db, ids: id)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func update(_ profile: Profile) async throws {
|
||||
_ = try await Database.sharedWriter().write { db in
|
||||
_ = try await Database.sharedWriter.write { db in
|
||||
try profile.updateChanges(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func update(_ profileList: [Profile]) async throws {
|
||||
// TODO: batch update
|
||||
try await Database.sharedWriter().write { db in
|
||||
try await Database.sharedWriter.write { db in
|
||||
for profile in profileList {
|
||||
try profile.updateChanges(db)
|
||||
}
|
||||
@@ -63,25 +63,25 @@ public enum ProfileManager {
|
||||
}
|
||||
|
||||
public nonisolated static func list() async throws -> [Profile] {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try Profile.all().order(Column("order").asc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func listRemote() async throws -> [Profile] {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try Profile.filter(Column("type") == ProfileType.remote.rawValue).order(Column("order").asc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func listAutoUpdateEnabled() async throws -> [Profile] {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try Profile.filter(Column("autoUpdate") == true).order(Column("order").asc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public nonisolated static func nextID() async throws -> Int64 {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
if let lastProfile = try Profile.select(Column("id")).order(Column("id").desc).fetchOne(db) {
|
||||
return lastProfile.id! + 1
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ public enum ProfileManager {
|
||||
}
|
||||
|
||||
private nonisolated static func nextOrder() async throws -> UInt32 {
|
||||
try await Database.sharedWriter().read { db in
|
||||
try await Database.sharedWriter.read { db in
|
||||
try UInt32(Profile.fetchCount(db))
|
||||
}
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ extension SharedPreferences {
|
||||
}
|
||||
|
||||
private nonisolated static func read<T: Codable>(_ name: String) async throws -> T? {
|
||||
guard let item = try await (Database.sharedWriter().read { db in
|
||||
guard let item = try await (Database.sharedWriter.read { db in
|
||||
try Item.fetchOne(db, id: name)
|
||||
})
|
||||
else {
|
||||
@@ -48,12 +48,12 @@ extension SharedPreferences {
|
||||
|
||||
private nonisolated static func write(_ name: String, _ value: (some Codable)?) async throws {
|
||||
if value == nil {
|
||||
_ = try await Database.sharedWriter().write { db in
|
||||
_ = try await Database.sharedWriter.write { db in
|
||||
try Item.deleteOne(db, id: name)
|
||||
}
|
||||
} else {
|
||||
let data = try BinaryEncoder().encode(value)
|
||||
try await Database.sharedWriter().write { db in
|
||||
try await Database.sharedWriter.write { db in
|
||||
try Item(name: name, data: data).insert(db)
|
||||
}
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
@@ -106,7 +106,7 @@
|
||||
<string>AppIcon.icns</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -62,7 +62,7 @@
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
@@ -88,7 +88,7 @@
|
||||
<string>AppIcon.icns</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -62,7 +62,7 @@
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
@@ -88,7 +88,7 @@
|
||||
<string>AppIcon.icns</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.nekohasekai.sfa.profile</string>
|
||||
<string>io.nekohasekai.sfavt.profile</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
|
@@ -2011,7 +2011,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
SDKROOT = appletvos;
|
||||
@@ -2046,7 +2046,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
SDKROOT = appletvos;
|
||||
@@ -2288,7 +2288,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
@@ -2330,7 +2330,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
@@ -2353,7 +2353,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 272;
|
||||
CURRENT_PROJECT_VERSION = 276;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 287TTNZF8L;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -2371,7 +2371,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
@@ -2393,7 +2393,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 272;
|
||||
CURRENT_PROJECT_VERSION = 276;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 287TTNZF8L;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -2411,7 +2411,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.5;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
@@ -2541,7 +2541,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.4;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
|
||||
PRODUCT_NAME = "$(inherited)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -2577,7 +2577,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.4;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
|
||||
PRODUCT_NAME = "$(inherited)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -2619,7 +2619,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.4;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
|
||||
PRODUCT_NAME = SFM;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -2660,7 +2660,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.9.4;
|
||||
MARKETING_VERSION = 1.9.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
|
||||
PRODUCT_NAME = SFM;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@@ -27,7 +27,7 @@ func main() {
|
||||
objectsMap := project["objects"].(map[string]any)
|
||||
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
|
||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.standalone", "io.nekohasekai.sfa.system"}, newVersion.String())
|
||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
|
||||
if updated0 || updated1 {
|
||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||
}
|
||||
|
@@ -40,21 +40,27 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
deadline := time.Now().Add(timeout)
|
||||
var errors []error
|
||||
err := conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set read deadline")
|
||||
}
|
||||
defer conn.SetReadDeadline(time.Time{})
|
||||
for _, sniffer := range sniffers {
|
||||
if buffer.IsEmpty() {
|
||||
err = sniffer(ctx, metadata, io.TeeReader(conn, buffer))
|
||||
} else {
|
||||
err = sniffer(ctx, metadata, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer)))
|
||||
for i := 0; ; i++ {
|
||||
err := conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set read deadline")
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
_, err = buffer.ReadOnceFrom(conn)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
if i > 0 {
|
||||
break
|
||||
}
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
errors = nil
|
||||
for _, sniffer := range sniffers {
|
||||
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return E.Errors(errors...)
|
||||
}
|
||||
|
@@ -2,7 +2,12 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.10.0-beta.9
|
||||
#### 1.10.0-beta.10
|
||||
|
||||
* Add `process_path_regex` rule item
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
|
@@ -7,12 +7,6 @@ icon: material/apple
|
||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||
|
||||
!!! failure "Unavailable"
|
||||
|
||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
||||
|
||||
We are working on getting sing-box apps back on the App Store, which should be completed within a week (SFI on the App Store and others on TestFlight are already available).
|
||||
|
||||
## :material-graph: Requirements
|
||||
|
||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||
|
@@ -4,7 +4,7 @@ icon: material/horse
|
||||
|
||||
# Trojan
|
||||
|
||||
Trojan is the most commonly used TLS proxy made in China. It can be used in various combinations,
|
||||
Torjan is the most commonly used TLS proxy made in China. It can be used in various combinations,
|
||||
but only the combination of uTLS and multiplexing is recommended.
|
||||
|
||||
| Protocol and implementation combination | Specification | Resists passive detection | Resists active probes |
|
||||
|
@@ -57,7 +57,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
|
||||
inet4Addresses: inet4Addresses,
|
||||
inet6Addresses: inet6Addresses,
|
||||
gso: gso,
|
||||
events: make(chan wgTun.Event),
|
||||
events: make(chan wgTun.Event, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -5,9 +5,9 @@ PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
|
||||
PKG_SOURCE_DATE:=2024-09-18
|
||||
PKG_SOURCE_VERSION:=3676d1b79fe8567c9375d3e812f84eb683c8f77d
|
||||
PKG_MIRROR_HASH:=1f9f436a001213ae8f68f962e34366b901bba5a8926a8a89aaaee7ba2e85e44b
|
||||
PKG_SOURCE_DATE:=2024-09-19
|
||||
PKG_SOURCE_VERSION:=a08aa10630813485d7899e65ff59c3fae56f8364
|
||||
PKG_MIRROR_HASH:=415cafd17a7c3362da952b9c5ecaa8f38d01e79b9609fdbc2384be225ae97451
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
||||
@@ -16,7 +16,7 @@ PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
PKG_BUILD_VERSION:=alpha-3676d1b
|
||||
PKG_BUILD_VERSION:=alpha-a08aa10
|
||||
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
||||
|
||||
GO_PKG:=github.com/metacubex/mihomo
|
||||
|
@@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=b9d2ae86f70922be6426d734464e395ae00f537af40eb69d74ee41d65b29fdf0
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20240914091803
|
||||
GEOSITE_VER:=20240920063125
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=c171f61d3ba8e0dcf31a9548e9fd928a9416e064ad9417664eadda8d25eb6ad9
|
||||
HASH:=aeefcd8b3e5b27c22e2e7dfb6ff5e8d0741fd540d96ab355fd00a0472f5884a7
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202409160034
|
||||
|
@@ -33,11 +33,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
val subItem = mActivity.subscriptions[position].second
|
||||
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
||||
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
||||
if (subItem.enabled) {
|
||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorAccent)
|
||||
} else {
|
||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(0)
|
||||
}
|
||||
holder.itemSubSettingBinding.chkEnable.isChecked = subItem.enabled
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
||||
@@ -46,10 +42,10 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
.putExtra("subId", subId)
|
||||
)
|
||||
}
|
||||
holder.itemSubSettingBinding.infoContainer.setOnClickListener {
|
||||
subItem.enabled = !subItem.enabled
|
||||
|
||||
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked ->
|
||||
subItem.enabled = isChecked
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(subItem.url)) {
|
||||
|
@@ -15,6 +15,7 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.*
|
||||
import com.v2ray.ang.util.MmkvManager.serverRawStorage
|
||||
import com.v2ray.ang.util.MmkvManager.settingsStorage
|
||||
import com.v2ray.ang.util.MmkvManager.subStorage
|
||||
import com.v2ray.ang.util.fmt.ShadowsocksFmt
|
||||
import com.v2ray.ang.util.fmt.SocksFmt
|
||||
import com.v2ray.ang.util.fmt.TrojanFmt
|
||||
@@ -22,6 +23,7 @@ import com.v2ray.ang.util.fmt.VlessFmt
|
||||
import com.v2ray.ang.util.fmt.VmessFmt
|
||||
import com.v2ray.ang.util.fmt.WireguardFmt
|
||||
import java.lang.reflect.Type
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
|
||||
object AngConfigManager {
|
||||
@@ -397,7 +399,7 @@ object AngConfigManager {
|
||||
servers.lines()
|
||||
.forEach { str ->
|
||||
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
|
||||
count += MmkvManager.importUrlAsSubscription(str)
|
||||
count += importUrlAsSubscription(str)
|
||||
}
|
||||
}
|
||||
return count
|
||||
@@ -577,4 +579,19 @@ object AngConfigManager {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun importUrlAsSubscription(url: String): Int {
|
||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||
subscriptions.forEach {
|
||||
if (it.second.url == url) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
val uri = URI(Utils.fixIllegalUrl(url))
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = uri.fragment ?: "import sub"
|
||||
subItem.url = url
|
||||
subStorage.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import java.net.URI
|
||||
|
||||
object MmkvManager {
|
||||
private const val ID_MAIN = "MAIN"
|
||||
@@ -152,21 +151,6 @@ object MmkvManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun importUrlAsSubscription(url: String): Int {
|
||||
val subscriptions = decodeSubscriptions()
|
||||
subscriptions.forEach {
|
||||
if (it.second.url == url) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
val uri = URI(Utils.fixIllegalUrl(url))
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = uri.fragment ?: "import sub"
|
||||
subItem.url = url
|
||||
subStorage.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||
return 1
|
||||
}
|
||||
|
||||
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
|
||||
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
|
||||
subStorage.allKeys()?.forEach { key ->
|
||||
@@ -228,25 +212,6 @@ object MmkvManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||
|
||||
val serverDelays = mutableListOf<ServerDelay>()
|
||||
val serverList = decodeServerList()
|
||||
serverList.forEach { key ->
|
||||
val delay = decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
|
||||
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
|
||||
}
|
||||
serverDelays.sortBy { it.testDelayMillis }
|
||||
|
||||
serverDelays.forEach {
|
||||
serverList.remove(it.guid)
|
||||
serverList.add(it.guid)
|
||||
}
|
||||
|
||||
encodeServerList(serverList)
|
||||
}
|
||||
|
||||
fun getServerViaRemarks(remarks: String?): ServerConfig? {
|
||||
if (remarks == null) {
|
||||
return null
|
||||
|
@@ -310,7 +310,22 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
MmkvManager.sortByTestResults()
|
||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||
|
||||
val serverDelays = mutableListOf<ServerDelay>()
|
||||
val serverList = MmkvManager.decodeServerList()
|
||||
serverList.forEach { key ->
|
||||
val delay = MmkvManager.decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
|
||||
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
|
||||
}
|
||||
serverDelays.sortBy { it.testDelayMillis }
|
||||
|
||||
serverDelays.forEach {
|
||||
serverList.remove(it.guid)
|
||||
serverList.add(it.guid)
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerList(serverList)
|
||||
}
|
||||
|
||||
|
||||
|
@@ -34,7 +34,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@@ -73,7 +73,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_configuration_share"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -97,7 +97,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_configuration_restore"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -129,7 +129,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_source_code"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -153,7 +153,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_pref_feedback"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -178,7 +178,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_tg_channel"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
@@ -202,7 +202,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/title_privacy_policy"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
@@ -11,8 +11,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp">
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -43,7 +43,7 @@
|
||||
android:id="@+id/switch_per_app_proxy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp" />
|
||||
android:paddingStart="@dimen/padding_start" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
android:id="@+id/switch_bypass_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp" />
|
||||
android:paddingStart="@dimen/padding_start" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
@@ -3,8 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
tools:context=".ui.LogcatActivity">
|
||||
|
||||
<ProgressBar
|
||||
|
@@ -77,7 +77,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:minLines="1"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:text="@string/connection_test_pending"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
|
@@ -86,12 +86,12 @@
|
||||
android:text="@string/sub_setting_enable" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="6dp" />
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -108,12 +108,12 @@
|
||||
android:text="@string/sub_auto_update" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/auto_update_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="6dp" />
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@@ -11,9 +11,9 @@
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
android:paddingRight="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
@@ -22,8 +22,8 @@
|
||||
android:layout_weight="1.0"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/layout_margin_right_height"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/name"
|
||||
@@ -44,9 +44,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingStart="@dimen/padding_start"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingEnd="@dimen/padding_end"
|
||||
android:paddingRight="6dp" />
|
||||
|
||||
</LinearLayout>
|
@@ -11,7 +11,7 @@
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
app:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -80,7 +80,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="5dp">
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_subscription"
|
||||
@@ -172,7 +172,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="5dp">
|
||||
android:paddingEnd="@dimen/padding_end">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_type"
|
||||
|
@@ -6,18 +6,19 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_cardview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
@@ -26,19 +27,12 @@
|
||||
android:nextFocusRight="@+id/layout_edit"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chk_enable"
|
||||
android:layout_width="6dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
@@ -54,12 +48,25 @@
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/chk_enable"
|
||||
android:text="@string/sub_setting_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="@dimen/sub_height"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_margin="@dimen/cardview_margin"
|
||||
app:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -24,7 +24,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
android:paddingStart="@dimen/padding_start">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@@ -7,7 +7,11 @@
|
||||
<dimen name="edit_height">50dp</dimen>
|
||||
<dimen name="png_height">24dp</dimen>
|
||||
<dimen name="server_height">72dp</dimen>
|
||||
<dimen name="sub_height">108dp</dimen>
|
||||
<dimen name="connection_test_height">60dp</dimen>
|
||||
<dimen name="padding_start">16dp</dimen>
|
||||
<dimen name="padding_end">16dp</dimen>
|
||||
<dimen name="cardview_margin">2dp</dimen>
|
||||
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
@@ -55,6 +55,9 @@ func (c *Config) GetRequestHeader() http.Header {
|
||||
}
|
||||
|
||||
func (c *Config) WriteResponseHeader(writer http.ResponseWriter) {
|
||||
// CORS headers for the browser dialer
|
||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
writer.Header().Set("Access-Control-Allow-Methods", "GET, POST")
|
||||
paddingLen := c.GetNormalizedXPaddingBytes().roll()
|
||||
if paddingLen > 0 {
|
||||
writer.Header().Set("X-Padding", strings.Repeat("0", int(paddingLen)))
|
||||
|
Reference in New Issue
Block a user