From a0f2f7e29035553562941f4046db88d707daf199 Mon Sep 17 00:00:00 2001 From: Yuqian Yang Date: Fri, 28 Feb 2025 23:13:39 +0800 Subject: chore(store): move everything to store. --- assets/crupest-transparent.png | Bin 12038 -> 0 bytes configs/Microsoft.PowerShell_profile.ps1 | 35 --- configs/bruno/ComfyUI/Get Object Info.bru | 11 - configs/bruno/ComfyUI/Get Prompt History.bru | 15 - configs/bruno/ComfyUI/Post Prompt.bru | 124 -------- configs/bruno/ComfyUI/Upload Image.bru | 18 -- configs/bruno/ComfyUI/View Image.bru | 19 -- configs/bruno/ComfyUI/bruno.json | 9 - .../environments/ChimerAI ComfyUI Server.bru | 3 - configs/crupest-winget.json | 104 ------- configs/magic/extend-script.js | 21 -- configs/magic/extend.yaml | 65 ----- configs/nvim/config-root/.gitignore | 1 - configs/nvim/config-root/.luarc.json | 4 - configs/nvim/config-root/cspell.yaml | 13 - configs/nvim/config-root/init.lua | 63 ---- configs/nvim/config-root/lazy-lock.json | 20 -- .../nvim/config-root/lua/crupest/nvim/keymap.lua | 9 - .../nvim/config-root/lua/crupest/nvim/lsp/c.lua | 25 -- .../nvim/config-root/lua/crupest/nvim/lsp/init.lua | 50 ---- .../nvim/config-root/lua/crupest/nvim/lsp/lua.lua | 29 -- .../config-root/lua/crupest/nvim/plugins/cmp.lua | 31 -- .../lua/crupest/nvim/plugins/gitsigns.lua | 51 ---- .../config-root/lua/crupest/nvim/plugins/init.lua | 12 - .../config-root/lua/crupest/nvim/plugins/lint.lua | 82 ------ .../lua/crupest/nvim/plugins/others.lua | 9 - .../config-root/lua/crupest/nvim/plugins/snip.lua | 75 ----- .../lua/crupest/nvim/plugins/telescope.lua | 11 - .../nvim/config-root/lua/crupest/utils/find.lua | 101 ------- .../nvim/config-root/lua/crupest/utils/nvim.lua | 12 - configs/nvim/config-root/lua/plugins.lua | 30 -- configs/nvim/config-root/nvim-words.txt | 27 -- configs/nvim/copy-nvim-config | 6 - configs/nvim/homebrew-packages | 6 - store/assets/crupest-transparent.png | Bin 0 -> 12038 bytes store/home/bash_profile | 1 + store/home/bashrc | 8 + store/home/bin/neovide-listen | 43 +++ store/home/config/halloy/config.toml | 20 ++ store/home/config/nvim/.gitignore | 1 + store/home/config/nvim/.luarc.json | 4 + store/home/config/nvim/cspell.yaml | 13 + store/home/config/nvim/init.lua | 63 ++++ store/home/config/nvim/lazy-lock.json | 20 ++ store/home/config/nvim/lua/crupest/nvim/keymap.lua | 9 + store/home/config/nvim/lua/crupest/nvim/lsp/c.lua | 25 ++ .../home/config/nvim/lua/crupest/nvim/lsp/init.lua | 50 ++++ .../home/config/nvim/lua/crupest/nvim/lsp/lua.lua | 29 ++ .../config/nvim/lua/crupest/nvim/plugins/cmp.lua | 31 ++ .../nvim/lua/crupest/nvim/plugins/gitsigns.lua | 51 ++++ .../config/nvim/lua/crupest/nvim/plugins/init.lua | 12 + .../config/nvim/lua/crupest/nvim/plugins/lint.lua | 82 ++++++ .../nvim/lua/crupest/nvim/plugins/others.lua | 9 + .../config/nvim/lua/crupest/nvim/plugins/snip.lua | 75 +++++ .../nvim/lua/crupest/nvim/plugins/telescope.lua | 11 + store/home/config/nvim/lua/crupest/utils/find.lua | 101 +++++++ store/home/config/nvim/lua/crupest/utils/nvim.lua | 12 + store/home/config/nvim/lua/plugins.lua | 30 ++ store/home/config/nvim/nvim-words.txt | 27 ++ store/home/gitconfig | 10 + store/misc/bruno/ComfyUI/Get Object Info.bru | 11 + store/misc/bruno/ComfyUI/Get Prompt History.bru | 15 + store/misc/bruno/ComfyUI/Post Prompt.bru | 124 ++++++++ store/misc/bruno/ComfyUI/Upload Image.bru | 18 ++ store/misc/bruno/ComfyUI/View Image.bru | 19 ++ store/misc/bruno/ComfyUI/bruno.json | 9 + .../environments/ChimerAI ComfyUI Server.bru | 3 + store/misc/magic/extend-script.js | 21 ++ store/misc/magic/extend.yaml | 65 +++++ store/win/Microsoft.PowerShell_profile.ps1 | 35 +++ store/win/crupest-winget.json | 104 +++++++ store/win/neovide-listen.ps1 | 38 +++ store/works/Crupest.SecretTool/.gitignore | 7 + .../Crupest.SecretTool/Crupest.SecretTool.sln | 30 ++ .../Crupest.SecretTool/.gitignore | 1 + .../Crupest.SecretTool/Config.cs | 95 ++++++ .../Crupest.SecretTool/Controller.cs | 113 +++++++ .../Crupest.SecretTool/Crupest.SecretTool.csproj | 34 +++ .../Crupest.SecretTool/FileWatcher.cs | 26 ++ .../Crupest.SecretTool/GeoDataManager.cs | 324 +++++++++++++++++++++ .../Crupest.SecretTool/HostMatchConfig.cs | 123 ++++++++ .../Crupest.SecretTool/Program.cs | 113 +++++++ .../PublishProfiles/FolderProfile.pubxml | 13 + .../Crupest.SecretTool/Crupest.SecretTool/Proxy.cs | 76 +++++ .../Crupest.SecretTool/ProxyFile.cs | 31 ++ .../Crupest.SecretTool/Routing.cs | 155 ++++++++++ .../Crupest.SecretTool/SingConfigJsonObjects.cs | 20 ++ .../Crupest.SecretTool/StaticHosts.cs | 40 +++ .../Crupest.SecretTool/SurgeConfigGenerator.cs | 56 ++++ .../Crupest.SecretTool/Template.cs | 231 +++++++++++++++ .../Crupest.SecretTool/ToolConfig.cs | 271 +++++++++++++++++ .../Crupest.SecretTool/V4ConfigJsonObjects.cs | 25 ++ .../Crupest.SecretTool/V5ConfigJsonObjects.cs | 31 ++ .../Crupest.SecretTool/config.json.template | 63 ++++ .../Crupest.SecretTool/config.v5.json.template | 55 ++++ .../Crupest.SecretTool/hosts.txt | 2 + .../Crupest.SecretTool/proxy.txt | 50 ++++ .../Crupest.SecretTool/sing-config.json.template | 45 +++ .../Crupest.SecretTool/sing-inbounds-mobile.json | 11 + .../Crupest.SecretTool/sing-inbounds-pc.json | 14 + store/works/Crupest.SecretTool/build-secret.bash | 41 +++ store/works/Crupest.SecretTool/build-secret.ps1 | 25 ++ .../works/Crupest.SecretTool/tools/cru-proxy-edit | 12 + store/works/Crupest.SecretTool/tools/cru-proxy-log | 13 + .../tools/crupest-secret-tool.service | 8 + .../tools/crupest-secret-tool.xml | 49 ++++ .../tools/life.crupest.secret-tool.plist | 18 ++ store/works/README.md | 9 + tools/Crupest.SecretTool/.gitignore | 7 - tools/Crupest.SecretTool/Crupest.SecretTool.sln | 30 -- .../Crupest.SecretTool/.gitignore | 1 - .../Crupest.SecretTool/Config.cs | 95 ------ .../Crupest.SecretTool/Controller.cs | 113 ------- .../Crupest.SecretTool/Crupest.SecretTool.csproj | 34 --- .../Crupest.SecretTool/FileWatcher.cs | 26 -- .../Crupest.SecretTool/GeoDataManager.cs | 324 --------------------- .../Crupest.SecretTool/HostMatchConfig.cs | 123 -------- .../Crupest.SecretTool/Program.cs | 113 ------- .../PublishProfiles/FolderProfile.pubxml | 13 - .../Crupest.SecretTool/Crupest.SecretTool/Proxy.cs | 76 ----- .../Crupest.SecretTool/ProxyFile.cs | 31 -- .../Crupest.SecretTool/Routing.cs | 155 ---------- .../Crupest.SecretTool/SingConfigJsonObjects.cs | 20 -- .../Crupest.SecretTool/StaticHosts.cs | 40 --- .../Crupest.SecretTool/SurgeConfigGenerator.cs | 56 ---- .../Crupest.SecretTool/Template.cs | 231 --------------- .../Crupest.SecretTool/ToolConfig.cs | 271 ----------------- .../Crupest.SecretTool/V4ConfigJsonObjects.cs | 25 -- .../Crupest.SecretTool/V5ConfigJsonObjects.cs | 31 -- .../Crupest.SecretTool/config.json.template | 63 ---- .../Crupest.SecretTool/config.v5.json.template | 55 ---- .../Crupest.SecretTool/hosts.txt | 2 - .../Crupest.SecretTool/proxy.txt | 50 ---- .../Crupest.SecretTool/sing-config.json.template | 45 --- .../Crupest.SecretTool/sing-inbounds-mobile.json | 11 - .../Crupest.SecretTool/sing-inbounds-pc.json | 14 - tools/Crupest.SecretTool/build-secret.bash | 41 --- tools/Crupest.SecretTool/build-secret.ps1 | 25 -- tools/Crupest.SecretTool/tools/cru-proxy-edit | 12 - tools/Crupest.SecretTool/tools/cru-proxy-log | 13 - .../tools/crupest-secret-tool.service | 8 - .../tools/crupest-secret-tool.xml | 49 ---- .../tools/life.crupest.secret-tool.plist | 18 -- tools/scripts/neovide-listen | 43 --- tools/scripts/neovide-listen.ps1 | 38 --- tools/utility/rename-tree.py | 37 --- works/README.md | 9 - 147 files changed, 3429 insertions(+), 3439 deletions(-) delete mode 100755 assets/crupest-transparent.png delete mode 100644 configs/Microsoft.PowerShell_profile.ps1 delete mode 100644 configs/bruno/ComfyUI/Get Object Info.bru delete mode 100644 configs/bruno/ComfyUI/Get Prompt History.bru delete mode 100644 configs/bruno/ComfyUI/Post Prompt.bru delete mode 100644 configs/bruno/ComfyUI/Upload Image.bru delete mode 100644 configs/bruno/ComfyUI/View Image.bru delete mode 100644 configs/bruno/ComfyUI/bruno.json delete mode 100644 configs/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru delete mode 100644 configs/crupest-winget.json delete mode 100644 configs/magic/extend-script.js delete mode 100644 configs/magic/extend.yaml delete mode 100644 configs/nvim/config-root/.gitignore delete mode 100644 configs/nvim/config-root/.luarc.json delete mode 100644 configs/nvim/config-root/cspell.yaml delete mode 100644 configs/nvim/config-root/init.lua delete mode 100644 configs/nvim/config-root/lazy-lock.json delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/keymap.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/lsp/c.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/lsp/init.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/lsp/lua.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/cmp.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/gitsigns.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/init.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/lint.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/others.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/snip.lua delete mode 100644 configs/nvim/config-root/lua/crupest/nvim/plugins/telescope.lua delete mode 100644 configs/nvim/config-root/lua/crupest/utils/find.lua delete mode 100644 configs/nvim/config-root/lua/crupest/utils/nvim.lua delete mode 100644 configs/nvim/config-root/lua/plugins.lua delete mode 100644 configs/nvim/config-root/nvim-words.txt delete mode 100755 configs/nvim/copy-nvim-config delete mode 100644 configs/nvim/homebrew-packages create mode 100755 store/assets/crupest-transparent.png create mode 100644 store/home/bash_profile create mode 100644 store/home/bashrc create mode 100755 store/home/bin/neovide-listen create mode 100644 store/home/config/halloy/config.toml create mode 100644 store/home/config/nvim/.gitignore create mode 100644 store/home/config/nvim/.luarc.json create mode 100644 store/home/config/nvim/cspell.yaml create mode 100644 store/home/config/nvim/init.lua create mode 100644 store/home/config/nvim/lazy-lock.json create mode 100644 store/home/config/nvim/lua/crupest/nvim/keymap.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/lsp/c.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/lsp/init.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/lsp/lua.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/cmp.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/gitsigns.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/init.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/lint.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/others.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/snip.lua create mode 100644 store/home/config/nvim/lua/crupest/nvim/plugins/telescope.lua create mode 100644 store/home/config/nvim/lua/crupest/utils/find.lua create mode 100644 store/home/config/nvim/lua/crupest/utils/nvim.lua create mode 100644 store/home/config/nvim/lua/plugins.lua create mode 100644 store/home/config/nvim/nvim-words.txt create mode 100644 store/home/gitconfig create mode 100644 store/misc/bruno/ComfyUI/Get Object Info.bru create mode 100644 store/misc/bruno/ComfyUI/Get Prompt History.bru create mode 100644 store/misc/bruno/ComfyUI/Post Prompt.bru create mode 100644 store/misc/bruno/ComfyUI/Upload Image.bru create mode 100644 store/misc/bruno/ComfyUI/View Image.bru create mode 100644 store/misc/bruno/ComfyUI/bruno.json create mode 100644 store/misc/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru create mode 100644 store/misc/magic/extend-script.js create mode 100644 store/misc/magic/extend.yaml create mode 100644 store/win/Microsoft.PowerShell_profile.ps1 create mode 100644 store/win/crupest-winget.json create mode 100644 store/win/neovide-listen.ps1 create mode 100644 store/works/Crupest.SecretTool/.gitignore create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool.sln create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/.gitignore create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Config.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Controller.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Program.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Routing.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/Template.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/config.json.template create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/hosts.txt create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/proxy.txt create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json create mode 100644 store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json create mode 100755 store/works/Crupest.SecretTool/build-secret.bash create mode 100644 store/works/Crupest.SecretTool/build-secret.ps1 create mode 100755 store/works/Crupest.SecretTool/tools/cru-proxy-edit create mode 100755 store/works/Crupest.SecretTool/tools/cru-proxy-log create mode 100644 store/works/Crupest.SecretTool/tools/crupest-secret-tool.service create mode 100644 store/works/Crupest.SecretTool/tools/crupest-secret-tool.xml create mode 100644 store/works/Crupest.SecretTool/tools/life.crupest.secret-tool.plist create mode 100644 store/works/README.md delete mode 100644 tools/Crupest.SecretTool/.gitignore delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool.sln delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json delete mode 100644 tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json delete mode 100755 tools/Crupest.SecretTool/build-secret.bash delete mode 100644 tools/Crupest.SecretTool/build-secret.ps1 delete mode 100755 tools/Crupest.SecretTool/tools/cru-proxy-edit delete mode 100755 tools/Crupest.SecretTool/tools/cru-proxy-log delete mode 100644 tools/Crupest.SecretTool/tools/crupest-secret-tool.service delete mode 100644 tools/Crupest.SecretTool/tools/crupest-secret-tool.xml delete mode 100644 tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist delete mode 100755 tools/scripts/neovide-listen delete mode 100644 tools/scripts/neovide-listen.ps1 delete mode 100755 tools/utility/rename-tree.py delete mode 100644 works/README.md diff --git a/assets/crupest-transparent.png b/assets/crupest-transparent.png deleted file mode 100755 index d890d8d..0000000 Binary files a/assets/crupest-transparent.png and /dev/null differ diff --git a/configs/Microsoft.PowerShell_profile.ps1 b/configs/Microsoft.PowerShell_profile.ps1 deleted file mode 100644 index aeced5f..0000000 --- a/configs/Microsoft.PowerShell_profile.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -function Use-VC { - param( - [Parameter()] - [ValidateSet('x64', 'x86')] - $Arch = 'x64' - ) - - if ($Arch -eq 'x86') { - $p = 'x86'; - } - else { - $p = 'amd64' - } - - cmd /c "`"$(vswhere.exe -format value -property installationPath)\VC\Auxiliary\Build\vcvars64.bat`" $p & set" | - ForEach-Object { - if ($_ -match '=') { - $v = $_ -split '=' - Set-Item -Force -Path "ENV:\$($v[0])" -Value "$($v[1])" - } - } - Pop-Location - Write-Host "Visual Studio Command Prompt variables set." -ForegroundColor Yellow -} - - -function Set-Proxy { - $env:http_proxy = "http://127.0.0.1:2080" - $env:https_proxy = "http://127.0.0.1:2080" -} - -function Reset-Proxy { - Remove-Item env:http_proxy - Remove-Item env:https_proxy -} diff --git a/configs/bruno/ComfyUI/Get Object Info.bru b/configs/bruno/ComfyUI/Get Object Info.bru deleted file mode 100644 index d1a833c..0000000 --- a/configs/bruno/ComfyUI/Get Object Info.bru +++ /dev/null @@ -1,11 +0,0 @@ -meta { - name: Get Object Info - type: http - seq: 4 -} - -get { - url: {{BASE_URL}}/object_info - body: none - auth: none -} diff --git a/configs/bruno/ComfyUI/Get Prompt History.bru b/configs/bruno/ComfyUI/Get Prompt History.bru deleted file mode 100644 index 2e26888..0000000 --- a/configs/bruno/ComfyUI/Get Prompt History.bru +++ /dev/null @@ -1,15 +0,0 @@ -meta { - name: Get Prompt History - type: http - seq: 6 -} - -get { - url: {{BASE_URL}}/history/{{prompt_id}} - body: none - auth: none -} - -vars:pre-request { - prompt_id: 7e345a55-21c4-4bdc-9b34-add561775144 -} diff --git a/configs/bruno/ComfyUI/Post Prompt.bru b/configs/bruno/ComfyUI/Post Prompt.bru deleted file mode 100644 index 09bf89a..0000000 --- a/configs/bruno/ComfyUI/Post Prompt.bru +++ /dev/null @@ -1,124 +0,0 @@ -meta { - name: Post Prompt - type: http - seq: 5 -} - -post { - url: {{BASE_URL}}/prompt - body: json - auth: none -} - -body:json { - { - "client_id": "crupest", - "prompt": { - "3": { - "inputs": { - "seed": 156680208700286, - "steps": 20, - "cfg": 8, - "sampler_name": "euler", - "scheduler": "normal", - "denoise": 1, - "model": [ - "4", - 0 - ], - "positive": [ - "6", - 0 - ], - "negative": [ - "7", - 0 - ], - "latent_image": [ - "5", - 0 - ] - }, - "class_type": "KSampler", - "_meta": { - "title": "KSampler" - } - }, - "4": { - "inputs": { - "ckpt_name": "SUPIR/SUPIR-v0Q.ckpt" - }, - "class_type": "CheckpointLoaderSimple", - "_meta": { - "title": "Load Checkpoint" - } - }, - "5": { - "inputs": { - "width": 512, - "height": 512, - "batch_size": 1 - }, - "class_type": "EmptyLatentImage", - "_meta": { - "title": "Empty Latent Image" - } - }, - "6": { - "inputs": { - "text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,", - "clip": [ - "4", - 1 - ] - }, - "class_type": "CLIPTextEncode", - "_meta": { - "title": "CLIP Text Encode (Prompt)" - } - }, - "7": { - "inputs": { - "text": "text, watermark", - "clip": [ - "4", - 1 - ] - }, - "class_type": "CLIPTextEncode", - "_meta": { - "title": "CLIP Text Encode (Prompt)" - } - }, - "8": { - "inputs": { - "samples": [ - "3", - 0 - ], - "vae": [ - "4", - 2 - ] - }, - "class_type": "VAEDecode", - "_meta": { - "title": "VAE Decode" - } - }, - "9": { - "inputs": { - "filename_prefix": "ComfyUI", - "images": [ - "8", - 0 - ] - }, - "class_type": "SaveImage", - "_meta": { - "title": "Save Image" - } - } - } - } -} diff --git a/configs/bruno/ComfyUI/Upload Image.bru b/configs/bruno/ComfyUI/Upload Image.bru deleted file mode 100644 index 92b4aeb..0000000 --- a/configs/bruno/ComfyUI/Upload Image.bru +++ /dev/null @@ -1,18 +0,0 @@ -meta { - name: Upload Image - type: http - seq: 2 -} - -post { - url: {{BASE_URL}}/upload/image - body: multipartForm - auth: none -} - -body:multipart-form { - overwrite: true - type: input - subfolder: crupest-test - image: @file(/Users/crupest/codes/crupest/assets/crupest-transparent.png) -} diff --git a/configs/bruno/ComfyUI/View Image.bru b/configs/bruno/ComfyUI/View Image.bru deleted file mode 100644 index 395eccd..0000000 --- a/configs/bruno/ComfyUI/View Image.bru +++ /dev/null @@ -1,19 +0,0 @@ -meta { - name: View Image - type: http - seq: 1 -} - -get { - url: {{BASE_URL}}/view?filename=crupest-transparent.png&type=input&subfolder=crupest-test&preview=jpeg;90&channel=rgb - body: none - auth: none -} - -query { - filename: crupest-transparent.png - type: input - subfolder: crupest-test - preview: jpeg;90 - channel: rgb -} diff --git a/configs/bruno/ComfyUI/bruno.json b/configs/bruno/ComfyUI/bruno.json deleted file mode 100644 index ee35540..0000000 --- a/configs/bruno/ComfyUI/bruno.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1", - "name": "ComfyUI", - "type": "collection", - "ignore": [ - "node_modules", - ".git" - ] -} \ No newline at end of file diff --git a/configs/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru b/configs/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru deleted file mode 100644 index 480c8da..0000000 --- a/configs/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru +++ /dev/null @@ -1,3 +0,0 @@ -vars:secret [ - BASE_URL -] diff --git a/configs/crupest-winget.json b/configs/crupest-winget.json deleted file mode 100644 index df2e7d9..0000000 --- a/configs/crupest-winget.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "$schema" : "https://aka.ms/winget-packages.schema.2.0.json", - "CreationDate" : "2024-10-31T18:34:15.174-00:00", - "Sources" : - [ - { - "Packages" : - [ - { - "PackageIdentifier" : "7zip.7zip" - }, - { - "PackageIdentifier" : "Docker.DockerDesktop" - }, - { - "PackageIdentifier" : "Git.Git" - }, - { - "PackageIdentifier" : "Mozilla.Firefox" - }, - { - "PackageIdentifier" : "Mozilla.Thunderbird" - }, - { - "PackageIdentifier" : "VideoLAN.VLC" - }, - { - "PackageIdentifier" : "vim.vim" - }, - { - "PackageIdentifier" : "Neovim.Neovim" - }, - { - "PackageIdentifier" : "OpenJS.NodeJS" - }, - { - "PackageIdentifier" : "voidtools.Everything" - }, - { - "PackageIdentifier" : "Neovide.Neovide" - }, - { - "PackageIdentifier" : "Microsoft.PowerShell" - }, - { - "PackageIdentifier" : "Kitware.CMake" - }, - { - "PackageIdentifier" : "JetBrains.PyCharm.Community" - }, - { - "PackageIdentifier" : "Tencent.QQ.NT" - }, - { - "PackageIdentifier" : "Tencent.WeChat" - }, - { - "PackageIdentifier" : "Python.Launcher" - }, - { - "PackageIdentifier" : "NetEase.CloudMusic" - }, - { - "PackageIdentifier" : "agalwood.Motrix" - }, - { - "PackageIdentifier" : "BurntSushi.ripgrep.MSVC" - }, - { - "PackageIdentifier" : "Microsoft.VisualStudio.Locator" - }, - { - "PackageIdentifier" : "Ninja-build.Ninja" - }, - { - "PackageIdentifier" : "Rufus.Rufus" - }, - { - "PackageIdentifier" : "Rustlang.Rustup" - }, - { - "PackageIdentifier" : "Python.Python.3.13" - }, - { - "PackageIdentifier" : "Microsoft.PowerToys" - }, - { - "PackageIdentifier" : "Microsoft.VisualStudioCode" - }, - { - "PackageIdentifier" : "Microsoft.WinDbg" - } - ], - "SourceDetails" : - { - "Argument" : "https://cdn.winget.microsoft.com/cache", - "Identifier" : "Microsoft.Winget.Source_8wekyb3d8bbwe", - "Name" : "winget", - "Type" : "Microsoft.PreIndexed.Package" - } - } - ], - "WinGetVersion" : "1.9.2411-preview" -} \ No newline at end of file diff --git a/configs/magic/extend-script.js b/configs/magic/extend-script.js deleted file mode 100644 index 519cee9..0000000 --- a/configs/magic/extend-script.js +++ /dev/null @@ -1,21 +0,0 @@ -// Define main function (script entry) - -function main(config, profileName) { - delete config.dns; - delete config.tun; - delete config.hosts; - - delete config["cfw-latency-timeout"] - delete config["cfw-latency-url"] - delete config["cfw-conn-break-strategy"] - - config["proxies"] = [config["crupest-proxy"], ...config["proxies"]] - delete config["crupest-proxy"] - - select_proxy = { name: "node-select", type: "select", proxies: ["auto-select", ...config.proxies.map(p => p.name)] } - auto_select_proxy = config["crupest-auto-select"] - config["proxy-groups"] = [ select_proxy, auto_select_proxy ] - delete config["crupest-auto-select"] - - return config; -} diff --git a/configs/magic/extend.yaml b/configs/magic/extend.yaml deleted file mode 100644 index 3006f08..0000000 --- a/configs/magic/extend.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# Profile Enhancement Merge Template for Clash Verge - -profile: - store-selected: true - -rules: - - "GEOSITE,github,node-select" - - "GEOSITE,google,node-select" - - "GEOSITE,youtube,node-select" - - "GEOSITE,twitter,node-select" - - "GEOSITE,facebook,node-select" - - "GEOSITE,discord,node-select" - - "GEOSITE,reddit,node-select" - - "GEOSITE,twitch,node-select" - - "GEOSITE,quora,node-select" - - "GEOSITE,telegram,node-select" - - "GEOSITE,imgur,node-select" - - "GEOSITE,stackexchange,node-select" - - "GEOSITE,onedrive,node-select" - - - "GEOSITE,duckduckgo,node-select" - - "GEOSITE,wikimedia,node-select" - - "GEOSITE,gitbook,node-select" - - "GEOSITE,gitlab,node-select" - - "GEOSITE,creativecommons,node-select" - - "GEOSITE,archive,node-select" - - "GEOSITE,matrix,node-select" - - "GEOSITE,tor,node-select" - - - "GEOSITE,python,node-select" - - "GEOSITE,ruby,node-select" - - "GEOSITE,rust,node-select" - - "GEOSITE,nodejs,node-select" - - "GEOSITE,npmjs,node-select" - - "GEOSITE,qt,node-select" - - "GEOSITE,docker,node-select" - - "GEOSITE,v2ray,node-select" - - "GEOSITE,homebrew,node-select" - - "GEOSITE,bootstrap,node-select" - - - "GEOSITE,heroku,node-select" - - "GEOSITE,vercel,node-select" - - - "GEOSITE,ieee,node-select" - - "GEOSITE,sci-hub,node-select" - - "GEOSITE,libgen,node-select" - - - "DOMAIN-SUFFIX,gnu.org,node-select" - - "DOMAIN-SUFFIX,nongnu.org,node-select" - - "DOMAIN-SUFFIX,ietf.org,node-select" - - "DOMAIN-SUFFIX,packagist.org,node-select" - - "DOMAIN-SUFFIX,metacubex.one,node-select" - - "MATCH,DIRECT" - -crupest-proxy: - ... - -crupest-auto-select: - name: "auto-select" - type: url-test - interval: 1800 - include-all-proxies: true - url: 'https://www.gstatic.com/generate_204' - filter: "日本|新加坡|香港|台湾|美国" - expected-status: 204 diff --git a/configs/nvim/config-root/.gitignore b/configs/nvim/config-root/.gitignore deleted file mode 100644 index 722d5e7..0000000 --- a/configs/nvim/config-root/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vscode diff --git a/configs/nvim/config-root/.luarc.json b/configs/nvim/config-root/.luarc.json deleted file mode 100644 index f704d01..0000000 --- a/configs/nvim/config-root/.luarc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", - "runtime.version": "LuaJIT" -} \ No newline at end of file diff --git a/configs/nvim/config-root/cspell.yaml b/configs/nvim/config-root/cspell.yaml deleted file mode 100644 index 2a716e2..0000000 --- a/configs/nvim/config-root/cspell.yaml +++ /dev/null @@ -1,13 +0,0 @@ -dictionaryDefinitions: - - name: nvim-words - path: './nvim-words.txt' - addWords: true - -dictionaries: - - nvim-words - -words: - - crupest - -ignorePaths: - - lazy-lock.json diff --git a/configs/nvim/config-root/init.lua b/configs/nvim/config-root/init.lua deleted file mode 100644 index 9de0b2c..0000000 --- a/configs/nvim/config-root/init.lua +++ /dev/null @@ -1,63 +0,0 @@ -if vim.g.neovide then - -- spellchecker: disable-next-line - vim.opt.guifont = "FiraCode Nerd Font"; - vim.g.neovide_window_blurred = true; - vim.g.neovide_transparency = 0.9; - vim.g.neovide_input_ime = false; - vim.g.neovide_cursor_animate_in_insert_mode = false - vim.g.neovide_cursor_vfx_mode = "pixiedust"; - vim.g.neovide_input_macos_option_key_is_meta = 'only_left' -end - -local is_win = vim.fn.has("win32") ~= 0 - --- spellchecker: disable -if is_win then - vim.cmd([[ - let &shell = executable('pwsh') ? 'pwsh' : 'powershell' - let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;' - let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode' - let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode' - set shellquote= shellxquote= - ]]) - vim.opt.completeslash = 'slash' -end --- spellchecker: enable - --- spellchecker: disable -vim.opt.termguicolors = true; -vim.opt.fileformats = "unix,dos"; -vim.opt.softtabstop = 4; -vim.opt.shiftwidth = 4; -vim.opt.expandtab = true; -vim.opt.wrap = false; -vim.opt.number = true; --- spellchecker: enable - -vim.g.load_doxygen_syntax = true; -vim.g.doxygen_javadoc_autobrief = false; - --- Init lazy.nvim -local lazy_path = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not vim.uv.fs_stat(lazy_path) then - vim.fn.system({ - "git", - "clone", - "--filter=blob:none", - "https://github.com/folke/lazy.nvim.git", - "--branch=stable", -- latest stable release - lazy_path, - }) -end -vim.opt.rtp:prepend(lazy_path) - --- Use lazy.nvim -require("lazy").setup("plugins") - -vim.cmd("colorscheme catppuccin-macchiato") - -require("crupest.nvim.lsp").setup() -require("crupest.nvim.plugins").setup() -require("crupest.nvim.keymap").setup() - -vim.cmd("autocmd FileType gitcommit,gitrebase,gitconfig set bufhidden=delete") diff --git a/configs/nvim/config-root/lazy-lock.json b/configs/nvim/config-root/lazy-lock.json deleted file mode 100644 index 3d08239..0000000 --- a/configs/nvim/config-root/lazy-lock.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "LuaSnip": { "branch": "master", "commit": "0f7bbce41ea152a94d12aea286f2ce98e63c0f58" }, - "catppuccin": { "branch": "main", "commit": "faf15ab0201b564b6368ffa47b56feefc92ce3f4" }, - "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, - "cmp-cmdline": { "branch": "main", "commit": "d250c63aa13ead745e3a40f61fdd3470efde3923" }, - "cmp-nvim-lsp": { "branch": "main", "commit": "39e2eda76828d88b773cc27a3f61d2ad782c922d" }, - "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, - "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, - "gitsigns.nvim": { "branch": "main", "commit": "5f808b5e4fef30bd8aca1b803b4e555da07fc412" }, - "lazy.nvim": { "branch": "main", "commit": "56ead98e05bb37a4ec28930a54d836d033cf00f2" }, - "lualine.nvim": { "branch": "master", "commit": "2a5bae925481f999263d6f5ed8361baef8df4f83" }, - "nvim-autopairs": { "branch": "master", "commit": "b464658e9b880f463b9f7e6ccddd93fb0013f559" }, - "nvim-cmp": { "branch": "main", "commit": "ed31156aa2cc14e3bc066c59357cc91536a2bc01" }, - "nvim-lint": { "branch": "master", "commit": "6b46370d02cd001509a765591a3ffc481b538794" }, - "nvim-lspconfig": { "branch": "master", "commit": "4ae9796c4e95ca84ec77946a9f9089b8f1a3eec9" }, - "nvim-tree.lua": { "branch": "master", "commit": "ca7c4c33cac2ad66ec69d45e465379716ef0cc97" }, - "nvim-web-devicons": { "branch": "master", "commit": "edbe0a65cfacbbfff6a4a1e98ddd60c28c560509" }, - "plenary.nvim": { "branch": "master", "commit": "2d9b06177a975543726ce5c73fca176cedbffe9d" }, - "telescope.nvim": { "branch": "master", "commit": "85922dde3767e01d42a08e750a773effbffaea3e" } -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/keymap.lua b/configs/nvim/config-root/lua/crupest/nvim/keymap.lua deleted file mode 100644 index 624c04c..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/keymap.lua +++ /dev/null @@ -1,9 +0,0 @@ -local function setup() - vim.keymap.set("n", "", "bnext") - vim.keymap.set("n", "", "bNext") - vim.keymap.set("n", "", require("crupest.utils.nvim").close_float) -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/lsp/c.lua b/configs/nvim/config-root/lua/crupest/nvim/lsp/c.lua deleted file mode 100644 index bb1f6f7..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/lsp/c.lua +++ /dev/null @@ -1,25 +0,0 @@ -local lspconfig = require("lspconfig") - -local brew_clangd_path = "/usr/local/opt/llvm/bin/clangd" - -local function setup() - local clangd = "clangd" - - if vim.uv.fs_stat(brew_clangd_path) ~= nil then - clangd = brew_clangd_path - end - - -- setup lsp clangd - lspconfig.clangd.setup { - cmd = { clangd }, - on_attach = function(_, bufnr) - vim.keymap.set('n', 's', "ClangdSwitchSourceHeader", { - buffer = bufnr - }) - end - } -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/lsp/init.lua b/configs/nvim/config-root/lua/crupest/nvim/lsp/init.lua deleted file mode 100644 index 0fd29a3..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/lsp/init.lua +++ /dev/null @@ -1,50 +0,0 @@ -local lspconfig = require("lspconfig") -local cmp_nvim_lsp = require("cmp_nvim_lsp") -local cmp_default_caps = cmp_nvim_lsp.default_capabilities() - -local lspconfig_default_caps = lspconfig.util.default_config.capabilities - -lspconfig.util.default_config = vim.tbl_extend( - "force", - lspconfig.util.default_config, - { - capabilities = vim.tbl_extend("force", lspconfig_default_caps, cmp_default_caps), - autostart = false, - }) - -local function setup() - lspconfig.cmake.setup {} - lspconfig.bashls.setup {} - require("crupest.nvim.lsp.c").setup() - require("crupest.nvim.lsp.lua").setup() - - -- Use LspAttach auto command to only map the following keys - -- after the language server attaches to the current buffer - vim.api.nvim_create_autocmd('LspAttach', { - group = vim.api.nvim_create_augroup('UserLspConfig', {}), - callback = function(ev) - -- Buffer local mappings. - -- See `:help vim.lsp.*` for documentation on any of the below functions - local opts = { buffer = ev.buf } - vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts) - vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) - vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts) - vim.keymap.set('n', '', vim.lsp.buf.signature_help, opts) - vim.keymap.set('n', 'wa', vim.lsp.buf.add_workspace_folder, opts) - vim.keymap.set('n', 'wr', vim.lsp.buf.remove_workspace_folder, opts) - vim.keymap.set('n', 'wl', function() - print(vim.inspect(vim.lsp.buf.list_workspace_folders())) - end, opts) - vim.keymap.set('n', 'D', vim.lsp.buf.type_definition, opts) - vim.keymap.set('n', 'rn', vim.lsp.buf.rename, opts) - vim.keymap.set({ 'n', 'v' }, 'ca', vim.lsp.buf.code_action, opts) - vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) - vim.keymap.set('n', 'f', vim.lsp.buf.format, opts) - end, - }) -end - - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/lsp/lua.lua b/configs/nvim/config-root/lua/crupest/nvim/lsp/lua.lua deleted file mode 100644 index 93aa503..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/lsp/lua.lua +++ /dev/null @@ -1,29 +0,0 @@ -local lspconfig = require("lspconfig") - -local function setup() - lspconfig.lua_ls.setup { - settings = { - Lua = { - runtime = { - version = "LuaJIT" - }, - diagnostics = { - globals = { "vim" }, - }, - workspace = { - library = { - [vim.fn.expand "$VIMRUNTIME/lua"] = true, - [vim.fn.expand "$VIMRUNTIME/lua/vim/lsp"] = true, - [vim.fn.stdpath "data" .. "/lazy/lazy.nvim/lua/lazy"] = true, - }, - maxPreload = 100000, - preloadFileSize = 10000, - }, - }, - }, - } -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/cmp.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/cmp.lua deleted file mode 100644 index 9b1d876..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/cmp.lua +++ /dev/null @@ -1,31 +0,0 @@ -local function setup() - local cmp = require("cmp") - local luasnip = require("luasnip") - - cmp.setup { - snippet = { - expand = function(args) - luasnip.lsp_expand(args.body) - end, - }, - window = { - }, - mapping = cmp.mapping.preset.insert({ - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - [''] = cmp.mapping.complete(), - [''] = cmp.mapping.abort(), - [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. - }), - sources = cmp.config.sources({ - { name = 'nvim_lsp' }, - { name = 'luasnip' }, - }, { - { name = 'buffer' }, - }) - } -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/gitsigns.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/gitsigns.lua deleted file mode 100644 index 220c91a..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/gitsigns.lua +++ /dev/null @@ -1,51 +0,0 @@ -local function setup() - local gitsigns = require('gitsigns') - gitsigns.setup { - on_attach = function(bufnr) - local function map(mode, l, r, opts) - opts = opts or {} - opts.buffer = bufnr - vim.keymap.set(mode, l, r, opts) - end - - -- Navigation - map('n', ']c', function() - if vim.wo.diff then - vim.cmd.normal({ ']c', bang = true }) - else - gitsigns.nav_hunk('next') - end - end) - - map('n', '[c', function() - if vim.wo.diff then - vim.cmd.normal({ '[c', bang = true }) - else - gitsigns.nav_hunk('prev') - end - end) - - -- Actions - map('n', 'hs', gitsigns.stage_hunk) - map('n', 'hr', gitsigns.reset_hunk) - map('v', 'hs', function() gitsigns.stage_hunk { vim.fn.line('.'), vim.fn.line('v') } end) - map('v', 'hr', function() gitsigns.reset_hunk { vim.fn.line('.'), vim.fn.line('v') } end) - map('n', 'hS', gitsigns.stage_buffer) - map('n', 'hu', gitsigns.undo_stage_hunk) - map('n', 'hR', gitsigns.reset_buffer) - map('n', 'hp', gitsigns.preview_hunk) - map('n', 'hb', function() gitsigns.blame_line { full = true } end) - map('n', 'tb', gitsigns.toggle_current_line_blame) - map('n', 'hd', gitsigns.diffthis) - map('n', 'hD', function() gitsigns.diffthis('~') end) - map('n', 'td', gitsigns.toggle_deleted) - - -- Text object - map({ 'o', 'x' }, 'ih', ':Gitsigns select_hunk') - end - } -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/init.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/init.lua deleted file mode 100644 index 24e0c2e..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/init.lua +++ /dev/null @@ -1,12 +0,0 @@ -local function setup() - require("crupest.nvim.plugins.lint").setup() - require("crupest.nvim.plugins.snip").setup() - require("crupest.nvim.plugins.cmp").setup() - require("crupest.nvim.plugins.telescope").setup() - require("crupest.nvim.plugins.gitsigns").setup() - require("crupest.nvim.plugins.others").setup() -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/lint.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/lint.lua deleted file mode 100644 index 5e348d6..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/lint.lua +++ /dev/null @@ -1,82 +0,0 @@ -local lint = require("lint") - -local find = require('crupest.utils.find') -local is_win = vim.fn.has("win32") ~= 0 - -local cspell_config_patterns = { - ".cspell.json", - "cspell.json", - ".cSpell.json", - "cSpell.json", - "cspell.config.js", - "cspell.config.cjs", - "cspell.config.json", - "cspell.config.yaml", - "cspell.config.yml", - "cspell.yaml", - "cspell.yml", -} - ---- @type FindExeForBufOpts[] -local my_linters = { - { - name = "cspell", - places = { "node_modules", "global" }, - config_files = cspell_config_patterns, - }, -} - -local function run(opt) - if not opt then - opt = {} - end - - if not opt.buf then - opt.buf = 0 - end - - local linters = {} - - for _, l in ipairs(my_linters) do - local linter = find.find_exe_for_buf(opt.buf, l) - if linter then table.insert(linters, linter) end - end - - - local linter_names = {} - - for _, linter in ipairs(linters) do - table.insert(linter_names, linter.name) - require('lint.linters.' .. linter.name).cmd = linter.exe_path - end - - lint.try_lint(linter_names) -end - -local function setup() - if is_win then - for _, l in ipairs(my_linters) do - local name = l.name - local linter = require('lint.linters.' .. name) - if linter.cmd == 'cmd.exe' then - linter.cmd = linter.args[2] - end - table.remove(linter.args, 1) - table.remove(linter.args, 1) - end - end - - vim.api.nvim_create_autocmd({ "BufWritePost" }, { - callback = function(opt) - run({ - buf = opt.buffer - }) - end, - }) - - vim.keymap.set('n', 'lr', run) -end - -return { - setup = setup, -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/others.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/others.lua deleted file mode 100644 index 2ef0d75..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/others.lua +++ /dev/null @@ -1,9 +0,0 @@ -local function setup() - require('lualine').setup {} - require("nvim-tree").setup {} - require("nvim-autopairs").setup {} -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/snip.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/snip.lua deleted file mode 100644 index 78ed2eb..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/snip.lua +++ /dev/null @@ -1,75 +0,0 @@ ---- spellchecker: disable - -local luasnip = require("luasnip") - -local ls = luasnip --- some shorthands... -local s = ls.snippet -local sn = ls.snippet_node -local t = ls.text_node -local i = ls.insert_node -local f = ls.function_node -local c = ls.choice_node -local d = ls.dynamic_node -local r = ls.restore_node -local l = require("luasnip.extras").lambda -local rep = require("luasnip.extras").rep -local p = require("luasnip.extras").partial -local m = require("luasnip.extras").match -local n = require("luasnip.extras").nonempty -local dl = require("luasnip.extras").dynamic_lambda -local fmt = require("luasnip.extras.fmt").fmt -local fmta = require("luasnip.extras.fmt").fmta -local types = require("luasnip.util.types") -local conds = require("luasnip.extras.conditions") -local conds_expand = require("luasnip.extras.conditions.expand") - -local function copy(args) - return args[1] -end - -local function setup() - vim.keymap.set({ "i", "s" }, "", function() luasnip.jump(1) end, { silent = true }) - vim.keymap.set({ "i", "s" }, "", function() luasnip.jump(-1) end, { silent = true }) - - vim.keymap.set({ "i", "s" }, "", function() - if luasnip.choice_active() then - luasnip.change_choice(1) - end - end, { silent = true }) - - luasnip.add_snippets("cpp", { - s("cs", { - i(1, "classname"), - t("::"), - f(copy, 1), - t("("), - i(0), - t(") { }") - }), - - s("ds", { - i(1, "classname"), - t("::~"), - f(copy, 1), - t("() { }") - }), - - s("csds", { - i(1, "classname"), - t("::"), - f(copy, 1), - t("("), - i(0), - t({ ") { }", "", "" }), - f(copy, 1), - t("::~"), - f(copy, 1), - t("() { }") - }), - }) -end - -return { - setup = setup, -} diff --git a/configs/nvim/config-root/lua/crupest/nvim/plugins/telescope.lua b/configs/nvim/config-root/lua/crupest/nvim/plugins/telescope.lua deleted file mode 100644 index d68b7f2..0000000 --- a/configs/nvim/config-root/lua/crupest/nvim/plugins/telescope.lua +++ /dev/null @@ -1,11 +0,0 @@ -local function setup() - local builtin = require('telescope.builtin') - vim.keymap.set('n', 'ff', builtin.find_files, {}) - vim.keymap.set('n', 'fg', builtin.live_grep, {}) - vim.keymap.set('n', 'fb', builtin.buffers, {}) - vim.keymap.set('n', 'fh', builtin.help_tags, {}) -end - -return { - setup = setup -} diff --git a/configs/nvim/config-root/lua/crupest/utils/find.lua b/configs/nvim/config-root/lua/crupest/utils/find.lua deleted file mode 100644 index dd1f663..0000000 --- a/configs/nvim/config-root/lua/crupest/utils/find.lua +++ /dev/null @@ -1,101 +0,0 @@ -local is_win = vim.fn.has("win32") ~= 0 - -local M = {} - -local windows_exe_ext = { "exe", "bat", "cmd", "ps1" } - ---- Find real path (with ext) for an executable. ---- @param dir string ---- @param name string | string[] ---- @return string | nil -function M.find_exe_file(dir, name) - if type(name) == "string" then - name = { name } - end - for _, n in ipairs(name) do - if vim.uv.fs_stat(vim.fs.joinpath(dir, n)) ~= nil then - return n - end - if is_win then - for _, ext in ipairs(windows_exe_ext) do - if vim.uv.fs_stat(vim.fs.joinpath(dir, n .. "." .. ext)) ~= nil then - return n .. "." .. ext - end - end - end - end - return nil -end - ---- Walk up until found an executable in node_modules. ---- @param path string ---- @param name string ---- @return string | nil exe_path Path to the executable. -function M.find_node_modules_exe(path, name) - local bin_dirs = vim.fs.find("node_modules/.bin", { path = path, upward = true, type = "directory" }) - if #bin_dirs == 0 then return nil end - local exe = M.find_exe_file(bin_dirs[1], name) - return exe and vim.fs.joinpath(bin_dirs[1], exe) -end - ---- Find executable in PATH. ---- @param name string ---- @return string | nil -function M.find_global_exe(name) - local exe = vim.fn.exepath(name) - if exe == "" then return nil end - return exe -end - ---- @alias ExePlace "node_modules" | "global" ---- @param path string ---- @param name string ---- @param places ExePlace[] ---- @return string | nil, ExePlace? -function M.find_exe(path, name, places) - for _, place in ipairs(places) do - if place == "node_modules" then - local r = M.find_node_modules_exe(path, name) - if r then return r, "node_modules" end - end - if place == "global" then - local r = M.find_global_exe(name) - if r then return r, "global" end - end - end - return nil, nil -end - ---- @alias FindExeForBufOpts { name: string, exe: string?, places: ExePlace[], config_files: string[]?, filetypes: string[]? } ---- @alias FindExeForBufResult { name: string, file: string, exe: string, exe_path: string, place: ExePlace, config_file: string?, filetype: string? } ---- @param buf number ---- @param opts FindExeForBufOpts ---- @return FindExeForBufResult | nil -function M.find_exe_for_buf(buf, opts) - local r = {} --- @type FindExeForBufResult - r.name = opts.name - r.file = vim.api.nvim_buf_get_name(buf) - r.exe = opts.exe or opts.name - - if opts.filetypes then - r.filetype = vim.api.nvim_get_option_value("filetype", { scope = "buffer", buf = buf }) - if not vim.tbl_contains(opts.filetypes, r.filetype) then return nil end - end - - if opts.config_files then - local config_file_list = vim.fs.find(opts.config_files, { path = r.file, upward = true }) - if #config_file_list == 0 then return nil end - r.config_file = config_file_list[1] - end - - local exe_path, place = M.find_exe(r.file, r.exe, opts.places) - if exe_path == nil then return nil end - r.exe_path = exe_path - - --- @cast place ExePlace - r.place = place - - return r -end - -return M diff --git a/configs/nvim/config-root/lua/crupest/utils/nvim.lua b/configs/nvim/config-root/lua/crupest/utils/nvim.lua deleted file mode 100644 index 4477ecc..0000000 --- a/configs/nvim/config-root/lua/crupest/utils/nvim.lua +++ /dev/null @@ -1,12 +0,0 @@ -local M = {} - -function M.close_float() - local wins = vim.api.nvim_list_wins() - for _, v in ipairs(wins) do - if vim.api.nvim_win_get_config(v).relative ~= '' then - vim.api.nvim_win_close(v, false) - end - end -end - -return M diff --git a/configs/nvim/config-root/lua/plugins.lua b/configs/nvim/config-root/lua/plugins.lua deleted file mode 100644 index b689559..0000000 --- a/configs/nvim/config-root/lua/plugins.lua +++ /dev/null @@ -1,30 +0,0 @@ --- spellchecker: disable -return { - { "catppuccin/nvim", name = "catppuccin", priority = 1000 }, - "neovim/nvim-lspconfig", - "L3MON4D3/LuaSnip", - "hrsh7th/nvim-cmp", - "hrsh7th/cmp-nvim-lsp", - "hrsh7th/cmp-buffer", - "hrsh7th/cmp-path", - "hrsh7th/cmp-cmdline", - "saadparwaiz1/cmp_luasnip", - { - "nvim-tree/nvim-tree.lua", - lazy = false, - dependencies = { - "nvim-tree/nvim-web-devicons", - }, - }, - { - "nvim-lualine/lualine.nvim", - dependencies = { 'nvim-tree/nvim-web-devicons' } - }, - { - "nvim-telescope/telescope.nvim", - dependencies = { 'nvim-lua/plenary.nvim' } - }, - "windwp/nvim-autopairs", - "mfussenegger/nvim-lint", - "lewis6991/gitsigns.nvim", -} diff --git a/configs/nvim/config-root/nvim-words.txt b/configs/nvim/config-root/nvim-words.txt deleted file mode 100644 index 564904f..0000000 --- a/configs/nvim/config-root/nvim-words.txt +++ /dev/null @@ -1,27 +0,0 @@ -nvim -vimruntime -bnext -bufhidden -exepath -stdpath -augroup -autocmd -autobrief -autopairs -keymap -joinpath -bufnr - -neovide -pixiedust - -lspconfig -clangd -bashls - -catppuccin -macchiato -diffthis -gitsigns -lualine -luasnip diff --git a/configs/nvim/copy-nvim-config b/configs/nvim/copy-nvim-config deleted file mode 100755 index 92863a9..0000000 --- a/configs/nvim/copy-nvim-config +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -MYDIR="$(dirname "$(realpath "$0")")/config-root" - -mkdir -p ${MYDIR} -cp -R ~/.config/nvim/. ${MYDIR} diff --git a/configs/nvim/homebrew-packages b/configs/nvim/homebrew-packages deleted file mode 100644 index 03a95bf..0000000 --- a/configs/nvim/homebrew-packages +++ /dev/null @@ -1,6 +0,0 @@ -llvm -cmake-language-server -bash-language-server -shellcheck -shfmt -lua-language-server diff --git a/store/assets/crupest-transparent.png b/store/assets/crupest-transparent.png new file mode 100755 index 0000000..d890d8d Binary files /dev/null and b/store/assets/crupest-transparent.png differ diff --git a/store/home/bash_profile b/store/home/bash_profile new file mode 100644 index 0000000..b65f405 --- /dev/null +++ b/store/home/bash_profile @@ -0,0 +1 @@ +export PATH="$HOME/.local/bin:$PATH" diff --git a/store/home/bashrc b/store/home/bashrc new file mode 100644 index 0000000..7d31f23 --- /dev/null +++ b/store/home/bashrc @@ -0,0 +1,8 @@ +set-proxy() { + export http_proxy="http://127.0.0.1:7897" + export https_proxy="http://127.0.0.1:7897" +} + +unset-proxy() { + unset http_proxy https_proxy +} diff --git a/store/home/bin/neovide-listen b/store/home/bin/neovide-listen new file mode 100755 index 0000000..2591842 --- /dev/null +++ b/store/home/bin/neovide-listen @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +if [[ -z "$NVIM_SOCKET" ]]; then + NVIM_SOCKET="/tmp/nvimsocket" +fi + +args=() +MY_NEOVIM_PATH="$HOME/codes/neovim/build/bin/nvim" +if [[ -e "$MY_NEOVIM_PATH" ]]; then + echo "Found my neovim at $MY_NEOVIM_PATH" + export VIMRUNTIME="$HOME/codes/neovim/runtime" + args=("${args[@]}" "--neovim-bin" "$MY_NEOVIM_PATH") +fi + +listen_added=0 +for arg in "$@"; do + args=("${args[@]}" "$arg") + if [ "$arg" = '--' ]; then + args=("${args[@]}" "--listen" "$NVIM_SOCKET") + listen_added=1 + fi +done + +if [[ $listen_added = 0 ]]; then + args=("${args[@]}" "--" "--listen" "$NVIM_SOCKET") +fi + +NEOVIDE_BIN=neovide +MY_NEOVIDE_PATH="$HOME/codes/neovide/target/release/neovide" +if [ -e "$MY_NEOVIDE_PATH" ]; then + echo "Found my neovide at $MY_NEOVIDE_PATH" + NEOVIDE_BIN="$MY_NEOVIDE_PATH" +fi + +if which nvr > /dev/null; then + echo "Detected nvr, set git editor env" + export GIT_EDITOR='nvr -cc split --remote-wait' +fi + +args=("$NEOVIDE_BIN" "${args[@]}") +echo "Command is ${args[@]}" +exec "${args[@]}" + diff --git a/store/home/config/halloy/config.toml b/store/home/config/halloy/config.toml new file mode 100644 index 0000000..5d39f52 --- /dev/null +++ b/store/home/config/halloy/config.toml @@ -0,0 +1,20 @@ +tooltips = true + +[notifications] +direct_message = { sound = "peck", show_toast = true } +connected = { sound = "ring", show_toast = true } +disconnected = { sound = "ring", show_toast = true } + +[notifications.highlight] +sound = "dong" +exclude = [ "NickServ" ] + +[servers.liberachat] +nickname = "crupest" +server = "irc.libera.chat" +channels = [ "#hurd" ] +use_tls = true + +[servers.liberachat.sasl.plain] +username = "crupest" +password = ... diff --git a/store/home/config/nvim/.gitignore b/store/home/config/nvim/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/store/home/config/nvim/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/store/home/config/nvim/.luarc.json b/store/home/config/nvim/.luarc.json new file mode 100644 index 0000000..f704d01 --- /dev/null +++ b/store/home/config/nvim/.luarc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime.version": "LuaJIT" +} \ No newline at end of file diff --git a/store/home/config/nvim/cspell.yaml b/store/home/config/nvim/cspell.yaml new file mode 100644 index 0000000..2a716e2 --- /dev/null +++ b/store/home/config/nvim/cspell.yaml @@ -0,0 +1,13 @@ +dictionaryDefinitions: + - name: nvim-words + path: './nvim-words.txt' + addWords: true + +dictionaries: + - nvim-words + +words: + - crupest + +ignorePaths: + - lazy-lock.json diff --git a/store/home/config/nvim/init.lua b/store/home/config/nvim/init.lua new file mode 100644 index 0000000..9de0b2c --- /dev/null +++ b/store/home/config/nvim/init.lua @@ -0,0 +1,63 @@ +if vim.g.neovide then + -- spellchecker: disable-next-line + vim.opt.guifont = "FiraCode Nerd Font"; + vim.g.neovide_window_blurred = true; + vim.g.neovide_transparency = 0.9; + vim.g.neovide_input_ime = false; + vim.g.neovide_cursor_animate_in_insert_mode = false + vim.g.neovide_cursor_vfx_mode = "pixiedust"; + vim.g.neovide_input_macos_option_key_is_meta = 'only_left' +end + +local is_win = vim.fn.has("win32") ~= 0 + +-- spellchecker: disable +if is_win then + vim.cmd([[ + let &shell = executable('pwsh') ? 'pwsh' : 'powershell' + let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;' + let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode' + let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode' + set shellquote= shellxquote= + ]]) + vim.opt.completeslash = 'slash' +end +-- spellchecker: enable + +-- spellchecker: disable +vim.opt.termguicolors = true; +vim.opt.fileformats = "unix,dos"; +vim.opt.softtabstop = 4; +vim.opt.shiftwidth = 4; +vim.opt.expandtab = true; +vim.opt.wrap = false; +vim.opt.number = true; +-- spellchecker: enable + +vim.g.load_doxygen_syntax = true; +vim.g.doxygen_javadoc_autobrief = false; + +-- Init lazy.nvim +local lazy_path = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.uv.fs_stat(lazy_path) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", -- latest stable release + lazy_path, + }) +end +vim.opt.rtp:prepend(lazy_path) + +-- Use lazy.nvim +require("lazy").setup("plugins") + +vim.cmd("colorscheme catppuccin-macchiato") + +require("crupest.nvim.lsp").setup() +require("crupest.nvim.plugins").setup() +require("crupest.nvim.keymap").setup() + +vim.cmd("autocmd FileType gitcommit,gitrebase,gitconfig set bufhidden=delete") diff --git a/store/home/config/nvim/lazy-lock.json b/store/home/config/nvim/lazy-lock.json new file mode 100644 index 0000000..3d08239 --- /dev/null +++ b/store/home/config/nvim/lazy-lock.json @@ -0,0 +1,20 @@ +{ + "LuaSnip": { "branch": "master", "commit": "0f7bbce41ea152a94d12aea286f2ce98e63c0f58" }, + "catppuccin": { "branch": "main", "commit": "faf15ab0201b564b6368ffa47b56feefc92ce3f4" }, + "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, + "cmp-cmdline": { "branch": "main", "commit": "d250c63aa13ead745e3a40f61fdd3470efde3923" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "39e2eda76828d88b773cc27a3f61d2ad782c922d" }, + "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, + "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, + "gitsigns.nvim": { "branch": "main", "commit": "5f808b5e4fef30bd8aca1b803b4e555da07fc412" }, + "lazy.nvim": { "branch": "main", "commit": "56ead98e05bb37a4ec28930a54d836d033cf00f2" }, + "lualine.nvim": { "branch": "master", "commit": "2a5bae925481f999263d6f5ed8361baef8df4f83" }, + "nvim-autopairs": { "branch": "master", "commit": "b464658e9b880f463b9f7e6ccddd93fb0013f559" }, + "nvim-cmp": { "branch": "main", "commit": "ed31156aa2cc14e3bc066c59357cc91536a2bc01" }, + "nvim-lint": { "branch": "master", "commit": "6b46370d02cd001509a765591a3ffc481b538794" }, + "nvim-lspconfig": { "branch": "master", "commit": "4ae9796c4e95ca84ec77946a9f9089b8f1a3eec9" }, + "nvim-tree.lua": { "branch": "master", "commit": "ca7c4c33cac2ad66ec69d45e465379716ef0cc97" }, + "nvim-web-devicons": { "branch": "master", "commit": "edbe0a65cfacbbfff6a4a1e98ddd60c28c560509" }, + "plenary.nvim": { "branch": "master", "commit": "2d9b06177a975543726ce5c73fca176cedbffe9d" }, + "telescope.nvim": { "branch": "master", "commit": "85922dde3767e01d42a08e750a773effbffaea3e" } +} diff --git a/store/home/config/nvim/lua/crupest/nvim/keymap.lua b/store/home/config/nvim/lua/crupest/nvim/keymap.lua new file mode 100644 index 0000000..624c04c --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/keymap.lua @@ -0,0 +1,9 @@ +local function setup() + vim.keymap.set("n", "", "bnext") + vim.keymap.set("n", "", "bNext") + vim.keymap.set("n", "", require("crupest.utils.nvim").close_float) +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/lsp/c.lua b/store/home/config/nvim/lua/crupest/nvim/lsp/c.lua new file mode 100644 index 0000000..bb1f6f7 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/lsp/c.lua @@ -0,0 +1,25 @@ +local lspconfig = require("lspconfig") + +local brew_clangd_path = "/usr/local/opt/llvm/bin/clangd" + +local function setup() + local clangd = "clangd" + + if vim.uv.fs_stat(brew_clangd_path) ~= nil then + clangd = brew_clangd_path + end + + -- setup lsp clangd + lspconfig.clangd.setup { + cmd = { clangd }, + on_attach = function(_, bufnr) + vim.keymap.set('n', 's', "ClangdSwitchSourceHeader", { + buffer = bufnr + }) + end + } +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/lsp/init.lua b/store/home/config/nvim/lua/crupest/nvim/lsp/init.lua new file mode 100644 index 0000000..0fd29a3 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/lsp/init.lua @@ -0,0 +1,50 @@ +local lspconfig = require("lspconfig") +local cmp_nvim_lsp = require("cmp_nvim_lsp") +local cmp_default_caps = cmp_nvim_lsp.default_capabilities() + +local lspconfig_default_caps = lspconfig.util.default_config.capabilities + +lspconfig.util.default_config = vim.tbl_extend( + "force", + lspconfig.util.default_config, + { + capabilities = vim.tbl_extend("force", lspconfig_default_caps, cmp_default_caps), + autostart = false, + }) + +local function setup() + lspconfig.cmake.setup {} + lspconfig.bashls.setup {} + require("crupest.nvim.lsp.c").setup() + require("crupest.nvim.lsp.lua").setup() + + -- Use LspAttach auto command to only map the following keys + -- after the language server attaches to the current buffer + vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('UserLspConfig', {}), + callback = function(ev) + -- Buffer local mappings. + -- See `:help vim.lsp.*` for documentation on any of the below functions + local opts = { buffer = ev.buf } + vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts) + vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) + vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts) + vim.keymap.set('n', '', vim.lsp.buf.signature_help, opts) + vim.keymap.set('n', 'wa', vim.lsp.buf.add_workspace_folder, opts) + vim.keymap.set('n', 'wr', vim.lsp.buf.remove_workspace_folder, opts) + vim.keymap.set('n', 'wl', function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, opts) + vim.keymap.set('n', 'D', vim.lsp.buf.type_definition, opts) + vim.keymap.set('n', 'rn', vim.lsp.buf.rename, opts) + vim.keymap.set({ 'n', 'v' }, 'ca', vim.lsp.buf.code_action, opts) + vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) + vim.keymap.set('n', 'f', vim.lsp.buf.format, opts) + end, + }) +end + + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/lsp/lua.lua b/store/home/config/nvim/lua/crupest/nvim/lsp/lua.lua new file mode 100644 index 0000000..93aa503 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/lsp/lua.lua @@ -0,0 +1,29 @@ +local lspconfig = require("lspconfig") + +local function setup() + lspconfig.lua_ls.setup { + settings = { + Lua = { + runtime = { + version = "LuaJIT" + }, + diagnostics = { + globals = { "vim" }, + }, + workspace = { + library = { + [vim.fn.expand "$VIMRUNTIME/lua"] = true, + [vim.fn.expand "$VIMRUNTIME/lua/vim/lsp"] = true, + [vim.fn.stdpath "data" .. "/lazy/lazy.nvim/lua/lazy"] = true, + }, + maxPreload = 100000, + preloadFileSize = 10000, + }, + }, + }, + } +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/cmp.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/cmp.lua new file mode 100644 index 0000000..9b1d876 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/cmp.lua @@ -0,0 +1,31 @@ +local function setup() + local cmp = require("cmp") + local luasnip = require("luasnip") + + cmp.setup { + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + window = { + }, + mapping = cmp.mapping.preset.insert({ + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping.complete(), + [''] = cmp.mapping.abort(), + [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. + }), + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'luasnip' }, + }, { + { name = 'buffer' }, + }) + } +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/gitsigns.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/gitsigns.lua new file mode 100644 index 0000000..220c91a --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/gitsigns.lua @@ -0,0 +1,51 @@ +local function setup() + local gitsigns = require('gitsigns') + gitsigns.setup { + on_attach = function(bufnr) + local function map(mode, l, r, opts) + opts = opts or {} + opts.buffer = bufnr + vim.keymap.set(mode, l, r, opts) + end + + -- Navigation + map('n', ']c', function() + if vim.wo.diff then + vim.cmd.normal({ ']c', bang = true }) + else + gitsigns.nav_hunk('next') + end + end) + + map('n', '[c', function() + if vim.wo.diff then + vim.cmd.normal({ '[c', bang = true }) + else + gitsigns.nav_hunk('prev') + end + end) + + -- Actions + map('n', 'hs', gitsigns.stage_hunk) + map('n', 'hr', gitsigns.reset_hunk) + map('v', 'hs', function() gitsigns.stage_hunk { vim.fn.line('.'), vim.fn.line('v') } end) + map('v', 'hr', function() gitsigns.reset_hunk { vim.fn.line('.'), vim.fn.line('v') } end) + map('n', 'hS', gitsigns.stage_buffer) + map('n', 'hu', gitsigns.undo_stage_hunk) + map('n', 'hR', gitsigns.reset_buffer) + map('n', 'hp', gitsigns.preview_hunk) + map('n', 'hb', function() gitsigns.blame_line { full = true } end) + map('n', 'tb', gitsigns.toggle_current_line_blame) + map('n', 'hd', gitsigns.diffthis) + map('n', 'hD', function() gitsigns.diffthis('~') end) + map('n', 'td', gitsigns.toggle_deleted) + + -- Text object + map({ 'o', 'x' }, 'ih', ':Gitsigns select_hunk') + end + } +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/init.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/init.lua new file mode 100644 index 0000000..24e0c2e --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/init.lua @@ -0,0 +1,12 @@ +local function setup() + require("crupest.nvim.plugins.lint").setup() + require("crupest.nvim.plugins.snip").setup() + require("crupest.nvim.plugins.cmp").setup() + require("crupest.nvim.plugins.telescope").setup() + require("crupest.nvim.plugins.gitsigns").setup() + require("crupest.nvim.plugins.others").setup() +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/lint.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/lint.lua new file mode 100644 index 0000000..5e348d6 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/lint.lua @@ -0,0 +1,82 @@ +local lint = require("lint") + +local find = require('crupest.utils.find') +local is_win = vim.fn.has("win32") ~= 0 + +local cspell_config_patterns = { + ".cspell.json", + "cspell.json", + ".cSpell.json", + "cSpell.json", + "cspell.config.js", + "cspell.config.cjs", + "cspell.config.json", + "cspell.config.yaml", + "cspell.config.yml", + "cspell.yaml", + "cspell.yml", +} + +--- @type FindExeForBufOpts[] +local my_linters = { + { + name = "cspell", + places = { "node_modules", "global" }, + config_files = cspell_config_patterns, + }, +} + +local function run(opt) + if not opt then + opt = {} + end + + if not opt.buf then + opt.buf = 0 + end + + local linters = {} + + for _, l in ipairs(my_linters) do + local linter = find.find_exe_for_buf(opt.buf, l) + if linter then table.insert(linters, linter) end + end + + + local linter_names = {} + + for _, linter in ipairs(linters) do + table.insert(linter_names, linter.name) + require('lint.linters.' .. linter.name).cmd = linter.exe_path + end + + lint.try_lint(linter_names) +end + +local function setup() + if is_win then + for _, l in ipairs(my_linters) do + local name = l.name + local linter = require('lint.linters.' .. name) + if linter.cmd == 'cmd.exe' then + linter.cmd = linter.args[2] + end + table.remove(linter.args, 1) + table.remove(linter.args, 1) + end + end + + vim.api.nvim_create_autocmd({ "BufWritePost" }, { + callback = function(opt) + run({ + buf = opt.buffer + }) + end, + }) + + vim.keymap.set('n', 'lr', run) +end + +return { + setup = setup, +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/others.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/others.lua new file mode 100644 index 0000000..2ef0d75 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/others.lua @@ -0,0 +1,9 @@ +local function setup() + require('lualine').setup {} + require("nvim-tree").setup {} + require("nvim-autopairs").setup {} +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/snip.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/snip.lua new file mode 100644 index 0000000..78ed2eb --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/snip.lua @@ -0,0 +1,75 @@ +--- spellchecker: disable + +local luasnip = require("luasnip") + +local ls = luasnip +-- some shorthands... +local s = ls.snippet +local sn = ls.snippet_node +local t = ls.text_node +local i = ls.insert_node +local f = ls.function_node +local c = ls.choice_node +local d = ls.dynamic_node +local r = ls.restore_node +local l = require("luasnip.extras").lambda +local rep = require("luasnip.extras").rep +local p = require("luasnip.extras").partial +local m = require("luasnip.extras").match +local n = require("luasnip.extras").nonempty +local dl = require("luasnip.extras").dynamic_lambda +local fmt = require("luasnip.extras.fmt").fmt +local fmta = require("luasnip.extras.fmt").fmta +local types = require("luasnip.util.types") +local conds = require("luasnip.extras.conditions") +local conds_expand = require("luasnip.extras.conditions.expand") + +local function copy(args) + return args[1] +end + +local function setup() + vim.keymap.set({ "i", "s" }, "", function() luasnip.jump(1) end, { silent = true }) + vim.keymap.set({ "i", "s" }, "", function() luasnip.jump(-1) end, { silent = true }) + + vim.keymap.set({ "i", "s" }, "", function() + if luasnip.choice_active() then + luasnip.change_choice(1) + end + end, { silent = true }) + + luasnip.add_snippets("cpp", { + s("cs", { + i(1, "classname"), + t("::"), + f(copy, 1), + t("("), + i(0), + t(") { }") + }), + + s("ds", { + i(1, "classname"), + t("::~"), + f(copy, 1), + t("() { }") + }), + + s("csds", { + i(1, "classname"), + t("::"), + f(copy, 1), + t("("), + i(0), + t({ ") { }", "", "" }), + f(copy, 1), + t("::~"), + f(copy, 1), + t("() { }") + }), + }) +end + +return { + setup = setup, +} diff --git a/store/home/config/nvim/lua/crupest/nvim/plugins/telescope.lua b/store/home/config/nvim/lua/crupest/nvim/plugins/telescope.lua new file mode 100644 index 0000000..d68b7f2 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/nvim/plugins/telescope.lua @@ -0,0 +1,11 @@ +local function setup() + local builtin = require('telescope.builtin') + vim.keymap.set('n', 'ff', builtin.find_files, {}) + vim.keymap.set('n', 'fg', builtin.live_grep, {}) + vim.keymap.set('n', 'fb', builtin.buffers, {}) + vim.keymap.set('n', 'fh', builtin.help_tags, {}) +end + +return { + setup = setup +} diff --git a/store/home/config/nvim/lua/crupest/utils/find.lua b/store/home/config/nvim/lua/crupest/utils/find.lua new file mode 100644 index 0000000..dd1f663 --- /dev/null +++ b/store/home/config/nvim/lua/crupest/utils/find.lua @@ -0,0 +1,101 @@ +local is_win = vim.fn.has("win32") ~= 0 + +local M = {} + +local windows_exe_ext = { "exe", "bat", "cmd", "ps1" } + +--- Find real path (with ext) for an executable. +--- @param dir string +--- @param name string | string[] +--- @return string | nil +function M.find_exe_file(dir, name) + if type(name) == "string" then + name = { name } + end + for _, n in ipairs(name) do + if vim.uv.fs_stat(vim.fs.joinpath(dir, n)) ~= nil then + return n + end + if is_win then + for _, ext in ipairs(windows_exe_ext) do + if vim.uv.fs_stat(vim.fs.joinpath(dir, n .. "." .. ext)) ~= nil then + return n .. "." .. ext + end + end + end + end + return nil +end + +--- Walk up until found an executable in node_modules. +--- @param path string +--- @param name string +--- @return string | nil exe_path Path to the executable. +function M.find_node_modules_exe(path, name) + local bin_dirs = vim.fs.find("node_modules/.bin", { path = path, upward = true, type = "directory" }) + if #bin_dirs == 0 then return nil end + local exe = M.find_exe_file(bin_dirs[1], name) + return exe and vim.fs.joinpath(bin_dirs[1], exe) +end + +--- Find executable in PATH. +--- @param name string +--- @return string | nil +function M.find_global_exe(name) + local exe = vim.fn.exepath(name) + if exe == "" then return nil end + return exe +end + +--- @alias ExePlace "node_modules" | "global" +--- @param path string +--- @param name string +--- @param places ExePlace[] +--- @return string | nil, ExePlace? +function M.find_exe(path, name, places) + for _, place in ipairs(places) do + if place == "node_modules" then + local r = M.find_node_modules_exe(path, name) + if r then return r, "node_modules" end + end + if place == "global" then + local r = M.find_global_exe(name) + if r then return r, "global" end + end + end + return nil, nil +end + +--- @alias FindExeForBufOpts { name: string, exe: string?, places: ExePlace[], config_files: string[]?, filetypes: string[]? } +--- @alias FindExeForBufResult { name: string, file: string, exe: string, exe_path: string, place: ExePlace, config_file: string?, filetype: string? } +--- @param buf number +--- @param opts FindExeForBufOpts +--- @return FindExeForBufResult | nil +function M.find_exe_for_buf(buf, opts) + local r = {} --- @type FindExeForBufResult + r.name = opts.name + r.file = vim.api.nvim_buf_get_name(buf) + r.exe = opts.exe or opts.name + + if opts.filetypes then + r.filetype = vim.api.nvim_get_option_value("filetype", { scope = "buffer", buf = buf }) + if not vim.tbl_contains(opts.filetypes, r.filetype) then return nil end + end + + if opts.config_files then + local config_file_list = vim.fs.find(opts.config_files, { path = r.file, upward = true }) + if #config_file_list == 0 then return nil end + r.config_file = config_file_list[1] + end + + local exe_path, place = M.find_exe(r.file, r.exe, opts.places) + if exe_path == nil then return nil end + r.exe_path = exe_path + + --- @cast place ExePlace + r.place = place + + return r +end + +return M diff --git a/store/home/config/nvim/lua/crupest/utils/nvim.lua b/store/home/config/nvim/lua/crupest/utils/nvim.lua new file mode 100644 index 0000000..4477ecc --- /dev/null +++ b/store/home/config/nvim/lua/crupest/utils/nvim.lua @@ -0,0 +1,12 @@ +local M = {} + +function M.close_float() + local wins = vim.api.nvim_list_wins() + for _, v in ipairs(wins) do + if vim.api.nvim_win_get_config(v).relative ~= '' then + vim.api.nvim_win_close(v, false) + end + end +end + +return M diff --git a/store/home/config/nvim/lua/plugins.lua b/store/home/config/nvim/lua/plugins.lua new file mode 100644 index 0000000..b689559 --- /dev/null +++ b/store/home/config/nvim/lua/plugins.lua @@ -0,0 +1,30 @@ +-- spellchecker: disable +return { + { "catppuccin/nvim", name = "catppuccin", priority = 1000 }, + "neovim/nvim-lspconfig", + "L3MON4D3/LuaSnip", + "hrsh7th/nvim-cmp", + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "hrsh7th/cmp-cmdline", + "saadparwaiz1/cmp_luasnip", + { + "nvim-tree/nvim-tree.lua", + lazy = false, + dependencies = { + "nvim-tree/nvim-web-devicons", + }, + }, + { + "nvim-lualine/lualine.nvim", + dependencies = { 'nvim-tree/nvim-web-devicons' } + }, + { + "nvim-telescope/telescope.nvim", + dependencies = { 'nvim-lua/plenary.nvim' } + }, + "windwp/nvim-autopairs", + "mfussenegger/nvim-lint", + "lewis6991/gitsigns.nvim", +} diff --git a/store/home/config/nvim/nvim-words.txt b/store/home/config/nvim/nvim-words.txt new file mode 100644 index 0000000..564904f --- /dev/null +++ b/store/home/config/nvim/nvim-words.txt @@ -0,0 +1,27 @@ +nvim +vimruntime +bnext +bufhidden +exepath +stdpath +augroup +autocmd +autobrief +autopairs +keymap +joinpath +bufnr + +neovide +pixiedust + +lspconfig +clangd +bashls + +catppuccin +macchiato +diffthis +gitsigns +lualine +luasnip diff --git a/store/home/gitconfig b/store/home/gitconfig new file mode 100644 index 0000000..b343ab3 --- /dev/null +++ b/store/home/gitconfig @@ -0,0 +1,10 @@ +[user] + name = Yuqian Yang + email = crupest@crupest.life +[core] + autocrlf = false + editor = vim +[credential] + helper = /usr/lib/git-core/git-credential-libsecret +[alias] + lg = log --decorate --oneline --graph diff --git a/store/misc/bruno/ComfyUI/Get Object Info.bru b/store/misc/bruno/ComfyUI/Get Object Info.bru new file mode 100644 index 0000000..d1a833c --- /dev/null +++ b/store/misc/bruno/ComfyUI/Get Object Info.bru @@ -0,0 +1,11 @@ +meta { + name: Get Object Info + type: http + seq: 4 +} + +get { + url: {{BASE_URL}}/object_info + body: none + auth: none +} diff --git a/store/misc/bruno/ComfyUI/Get Prompt History.bru b/store/misc/bruno/ComfyUI/Get Prompt History.bru new file mode 100644 index 0000000..2e26888 --- /dev/null +++ b/store/misc/bruno/ComfyUI/Get Prompt History.bru @@ -0,0 +1,15 @@ +meta { + name: Get Prompt History + type: http + seq: 6 +} + +get { + url: {{BASE_URL}}/history/{{prompt_id}} + body: none + auth: none +} + +vars:pre-request { + prompt_id: 7e345a55-21c4-4bdc-9b34-add561775144 +} diff --git a/store/misc/bruno/ComfyUI/Post Prompt.bru b/store/misc/bruno/ComfyUI/Post Prompt.bru new file mode 100644 index 0000000..09bf89a --- /dev/null +++ b/store/misc/bruno/ComfyUI/Post Prompt.bru @@ -0,0 +1,124 @@ +meta { + name: Post Prompt + type: http + seq: 5 +} + +post { + url: {{BASE_URL}}/prompt + body: json + auth: none +} + +body:json { + { + "client_id": "crupest", + "prompt": { + "3": { + "inputs": { + "seed": 156680208700286, + "steps": 20, + "cfg": 8, + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 1, + "model": [ + "4", + 0 + ], + "positive": [ + "6", + 0 + ], + "negative": [ + "7", + 0 + ], + "latent_image": [ + "5", + 0 + ] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "4": { + "inputs": { + "ckpt_name": "SUPIR/SUPIR-v0Q.ckpt" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "5": { + "inputs": { + "width": 512, + "height": 512, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage", + "_meta": { + "title": "Empty Latent Image" + } + }, + "6": { + "inputs": { + "text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,", + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "7": { + "inputs": { + "text": "text, watermark", + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "8": { + "inputs": { + "samples": [ + "3", + 0 + ], + "vae": [ + "4", + 2 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "9": { + "inputs": { + "filename_prefix": "ComfyUI", + "images": [ + "8", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + } + } + } +} diff --git a/store/misc/bruno/ComfyUI/Upload Image.bru b/store/misc/bruno/ComfyUI/Upload Image.bru new file mode 100644 index 0000000..92b4aeb --- /dev/null +++ b/store/misc/bruno/ComfyUI/Upload Image.bru @@ -0,0 +1,18 @@ +meta { + name: Upload Image + type: http + seq: 2 +} + +post { + url: {{BASE_URL}}/upload/image + body: multipartForm + auth: none +} + +body:multipart-form { + overwrite: true + type: input + subfolder: crupest-test + image: @file(/Users/crupest/codes/crupest/assets/crupest-transparent.png) +} diff --git a/store/misc/bruno/ComfyUI/View Image.bru b/store/misc/bruno/ComfyUI/View Image.bru new file mode 100644 index 0000000..395eccd --- /dev/null +++ b/store/misc/bruno/ComfyUI/View Image.bru @@ -0,0 +1,19 @@ +meta { + name: View Image + type: http + seq: 1 +} + +get { + url: {{BASE_URL}}/view?filename=crupest-transparent.png&type=input&subfolder=crupest-test&preview=jpeg;90&channel=rgb + body: none + auth: none +} + +query { + filename: crupest-transparent.png + type: input + subfolder: crupest-test + preview: jpeg;90 + channel: rgb +} diff --git a/store/misc/bruno/ComfyUI/bruno.json b/store/misc/bruno/ComfyUI/bruno.json new file mode 100644 index 0000000..ee35540 --- /dev/null +++ b/store/misc/bruno/ComfyUI/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "ComfyUI", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/store/misc/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru b/store/misc/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru new file mode 100644 index 0000000..480c8da --- /dev/null +++ b/store/misc/bruno/ComfyUI/environments/ChimerAI ComfyUI Server.bru @@ -0,0 +1,3 @@ +vars:secret [ + BASE_URL +] diff --git a/store/misc/magic/extend-script.js b/store/misc/magic/extend-script.js new file mode 100644 index 0000000..519cee9 --- /dev/null +++ b/store/misc/magic/extend-script.js @@ -0,0 +1,21 @@ +// Define main function (script entry) + +function main(config, profileName) { + delete config.dns; + delete config.tun; + delete config.hosts; + + delete config["cfw-latency-timeout"] + delete config["cfw-latency-url"] + delete config["cfw-conn-break-strategy"] + + config["proxies"] = [config["crupest-proxy"], ...config["proxies"]] + delete config["crupest-proxy"] + + select_proxy = { name: "node-select", type: "select", proxies: ["auto-select", ...config.proxies.map(p => p.name)] } + auto_select_proxy = config["crupest-auto-select"] + config["proxy-groups"] = [ select_proxy, auto_select_proxy ] + delete config["crupest-auto-select"] + + return config; +} diff --git a/store/misc/magic/extend.yaml b/store/misc/magic/extend.yaml new file mode 100644 index 0000000..3006f08 --- /dev/null +++ b/store/misc/magic/extend.yaml @@ -0,0 +1,65 @@ +# Profile Enhancement Merge Template for Clash Verge + +profile: + store-selected: true + +rules: + - "GEOSITE,github,node-select" + - "GEOSITE,google,node-select" + - "GEOSITE,youtube,node-select" + - "GEOSITE,twitter,node-select" + - "GEOSITE,facebook,node-select" + - "GEOSITE,discord,node-select" + - "GEOSITE,reddit,node-select" + - "GEOSITE,twitch,node-select" + - "GEOSITE,quora,node-select" + - "GEOSITE,telegram,node-select" + - "GEOSITE,imgur,node-select" + - "GEOSITE,stackexchange,node-select" + - "GEOSITE,onedrive,node-select" + + - "GEOSITE,duckduckgo,node-select" + - "GEOSITE,wikimedia,node-select" + - "GEOSITE,gitbook,node-select" + - "GEOSITE,gitlab,node-select" + - "GEOSITE,creativecommons,node-select" + - "GEOSITE,archive,node-select" + - "GEOSITE,matrix,node-select" + - "GEOSITE,tor,node-select" + + - "GEOSITE,python,node-select" + - "GEOSITE,ruby,node-select" + - "GEOSITE,rust,node-select" + - "GEOSITE,nodejs,node-select" + - "GEOSITE,npmjs,node-select" + - "GEOSITE,qt,node-select" + - "GEOSITE,docker,node-select" + - "GEOSITE,v2ray,node-select" + - "GEOSITE,homebrew,node-select" + - "GEOSITE,bootstrap,node-select" + + - "GEOSITE,heroku,node-select" + - "GEOSITE,vercel,node-select" + + - "GEOSITE,ieee,node-select" + - "GEOSITE,sci-hub,node-select" + - "GEOSITE,libgen,node-select" + + - "DOMAIN-SUFFIX,gnu.org,node-select" + - "DOMAIN-SUFFIX,nongnu.org,node-select" + - "DOMAIN-SUFFIX,ietf.org,node-select" + - "DOMAIN-SUFFIX,packagist.org,node-select" + - "DOMAIN-SUFFIX,metacubex.one,node-select" + - "MATCH,DIRECT" + +crupest-proxy: + ... + +crupest-auto-select: + name: "auto-select" + type: url-test + interval: 1800 + include-all-proxies: true + url: 'https://www.gstatic.com/generate_204' + filter: "日本|新加坡|香港|台湾|美国" + expected-status: 204 diff --git a/store/win/Microsoft.PowerShell_profile.ps1 b/store/win/Microsoft.PowerShell_profile.ps1 new file mode 100644 index 0000000..aeced5f --- /dev/null +++ b/store/win/Microsoft.PowerShell_profile.ps1 @@ -0,0 +1,35 @@ +function Use-VC { + param( + [Parameter()] + [ValidateSet('x64', 'x86')] + $Arch = 'x64' + ) + + if ($Arch -eq 'x86') { + $p = 'x86'; + } + else { + $p = 'amd64' + } + + cmd /c "`"$(vswhere.exe -format value -property installationPath)\VC\Auxiliary\Build\vcvars64.bat`" $p & set" | + ForEach-Object { + if ($_ -match '=') { + $v = $_ -split '=' + Set-Item -Force -Path "ENV:\$($v[0])" -Value "$($v[1])" + } + } + Pop-Location + Write-Host "Visual Studio Command Prompt variables set." -ForegroundColor Yellow +} + + +function Set-Proxy { + $env:http_proxy = "http://127.0.0.1:2080" + $env:https_proxy = "http://127.0.0.1:2080" +} + +function Reset-Proxy { + Remove-Item env:http_proxy + Remove-Item env:https_proxy +} diff --git a/store/win/crupest-winget.json b/store/win/crupest-winget.json new file mode 100644 index 0000000..df2e7d9 --- /dev/null +++ b/store/win/crupest-winget.json @@ -0,0 +1,104 @@ +{ + "$schema" : "https://aka.ms/winget-packages.schema.2.0.json", + "CreationDate" : "2024-10-31T18:34:15.174-00:00", + "Sources" : + [ + { + "Packages" : + [ + { + "PackageIdentifier" : "7zip.7zip" + }, + { + "PackageIdentifier" : "Docker.DockerDesktop" + }, + { + "PackageIdentifier" : "Git.Git" + }, + { + "PackageIdentifier" : "Mozilla.Firefox" + }, + { + "PackageIdentifier" : "Mozilla.Thunderbird" + }, + { + "PackageIdentifier" : "VideoLAN.VLC" + }, + { + "PackageIdentifier" : "vim.vim" + }, + { + "PackageIdentifier" : "Neovim.Neovim" + }, + { + "PackageIdentifier" : "OpenJS.NodeJS" + }, + { + "PackageIdentifier" : "voidtools.Everything" + }, + { + "PackageIdentifier" : "Neovide.Neovide" + }, + { + "PackageIdentifier" : "Microsoft.PowerShell" + }, + { + "PackageIdentifier" : "Kitware.CMake" + }, + { + "PackageIdentifier" : "JetBrains.PyCharm.Community" + }, + { + "PackageIdentifier" : "Tencent.QQ.NT" + }, + { + "PackageIdentifier" : "Tencent.WeChat" + }, + { + "PackageIdentifier" : "Python.Launcher" + }, + { + "PackageIdentifier" : "NetEase.CloudMusic" + }, + { + "PackageIdentifier" : "agalwood.Motrix" + }, + { + "PackageIdentifier" : "BurntSushi.ripgrep.MSVC" + }, + { + "PackageIdentifier" : "Microsoft.VisualStudio.Locator" + }, + { + "PackageIdentifier" : "Ninja-build.Ninja" + }, + { + "PackageIdentifier" : "Rufus.Rufus" + }, + { + "PackageIdentifier" : "Rustlang.Rustup" + }, + { + "PackageIdentifier" : "Python.Python.3.13" + }, + { + "PackageIdentifier" : "Microsoft.PowerToys" + }, + { + "PackageIdentifier" : "Microsoft.VisualStudioCode" + }, + { + "PackageIdentifier" : "Microsoft.WinDbg" + } + ], + "SourceDetails" : + { + "Argument" : "https://cdn.winget.microsoft.com/cache", + "Identifier" : "Microsoft.Winget.Source_8wekyb3d8bbwe", + "Name" : "winget", + "Type" : "Microsoft.PreIndexed.Package" + } + } + ], + "WinGetVersion" : "1.9.2411-preview" +} \ No newline at end of file diff --git a/store/win/neovide-listen.ps1 b/store/win/neovide-listen.ps1 new file mode 100644 index 0000000..e84f3a2 --- /dev/null +++ b/store/win/neovide-listen.ps1 @@ -0,0 +1,38 @@ +$env:NVIM_LISTEN_ADDRESS ??= "\\.\pipe\nvimsocket" + +$neovide_args = @() + +$MY_NEOVIM_PATH="$HOME/codes/neovim/build/bin/nvim.exe" +if (Get-Item $MY_NEOVIM_PATH -ErrorAction Ignore) { + Write-Output "Found my neovim at $MY_NEOVIM_PATH." + $env:VIMRUNTIME="$HOME/codes/neovim/runtime" + $neovide_args += "--neovim-bin", "$MY_NEOVIM_PATH" +} + +$listen_added = $false +foreach ($arg in $args) { + $neovide_args += $arg + if ( $arg -eq '--') { + $neovide_args += "--listen", $env:NVIM_LISTEN_ADDRESS + $listen_added=$true + } +} + +if (-not $listen_added) { + $neovide_args += "--", "--listen", $env:NVIM_LISTEN_ADDRESS +} + +$neovide_bin = "neovide" +$my_neovide_path = "$HOME/codes/neovide/target/release/neovide.exe" +if (Get-Item $my_neovide_path -ErrorAction Ignore) { + Write-Output "Found my neovide at $my_neovide_path." + $neovide_bin = "$my_neovide_path" +} + +if (Get-Command nvr -ErrorAction Ignore) { + Write-Output "Detected nvr, set git editor env." + $env:GIT_EDITOR = "nvr -cc split --remote-wait" +} + +Write-Output "Command is $($neovide_args -join ' ')." +Start-Process $neovide_bin -ArgumentList $neovide_args -Wait diff --git a/store/works/Crupest.SecretTool/.gitignore b/store/works/Crupest.SecretTool/.gitignore new file mode 100644 index 0000000..ac4d8a4 --- /dev/null +++ b/store/works/Crupest.SecretTool/.gitignore @@ -0,0 +1,7 @@ +.vs +bin +obj +*.pubxml.user +*.csproj.user + +publish diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool.sln b/store/works/Crupest.SecretTool/Crupest.SecretTool.sln new file mode 100644 index 0000000..fde4347 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34024.191 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4C2CE80-CDF8-4B08-8912-D1F0F14196AD}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crupest.SecretTool", "Crupest.SecretTool\Crupest.SecretTool.csproj", "{D6335AE4-FD22-49CD-9624-37371F3B4F82}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B1E8FD9C-9157-4F4E-8265-4B37F30EEC5E} + EndGlobalSection +EndGlobal diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/.gitignore b/store/works/Crupest.SecretTool/Crupest.SecretTool/.gitignore new file mode 100644 index 0000000..c936492 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/.gitignore @@ -0,0 +1 @@ +vmess.txt diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Config.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Config.cs new file mode 100644 index 0000000..ff58551 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Config.cs @@ -0,0 +1,95 @@ +namespace Crupest.SecretTool; + +public record ConfigItem(string Value, int LineNumber); + +public class DictionaryConfig(string configString, List? requiredKeys = null) +{ + private static Dictionary Parse(string configString, List? requiredKeys = null) + { + var config = new Dictionary(); + var lines = configString.Split('\n'); + int lineNumber = 1; + + foreach (var line in lines) + { + var l = line; + var beginOfComment = l.IndexOf('#'); + if (beginOfComment >= 0) + { + l = line[..beginOfComment]; + } + l = l.Trim(); + if (!string.IsNullOrEmpty(l)) + { + var equalIndex = l.IndexOf('='); + if (equalIndex == -1) + { + throw new FormatException($"No '=' found in line {lineNumber}."); + } + + config.Add(l[..equalIndex].Trim(), new ConfigItem(l[(equalIndex + 1)..].Trim(), lineNumber)); + } + + lineNumber++; + } + + if (requiredKeys is not null) + { + foreach (var key in requiredKeys) + { + if (!config.ContainsKey(key)) + { + throw new FormatException($"Required key '{key}' not found in config."); + } + } + } + + return config; + } + + public string ConfigString { get; } = configString; + public List? RequiredKeys { get; } = requiredKeys; + public Dictionary Config { get; } = Parse(configString); + public ConfigItem GetItemCaseInsensitive(string key) + { + foreach (var (originalKey, value) in Config) + { + if (string.Equals(originalKey, key, StringComparison.OrdinalIgnoreCase)) + { + return value; + } + } + throw new KeyNotFoundException($"Key '{key}' not found in config case-insensitively."); + } +} + +public class ListConfig(string configString) +{ + private static List Parse(string configString) + { + var config = new List(); + var lines = configString.Split('\n'); + int lineNumber = 1; + + foreach (var line in lines) + { + var l = line; + var beginOfComment = l.IndexOf('#'); + if (beginOfComment >= 0) + { + l = line[..beginOfComment]; + } + l = l.Trim(); + if (!string.IsNullOrEmpty(l)) + { + config.Add(new ConfigItem(l, lineNumber)); + } + lineNumber++; + } + + return config; + } + + public string ConfigString { get; } = configString; + public List Config { get; } = Parse(configString); +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Controller.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Controller.cs new file mode 100644 index 0000000..0803b01 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Controller.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; + +namespace Crupest.SecretTool; + +public class Controller(string executablePath, string configPath, string? assetPath) +{ + public const string ToolAssetEnvironmentVariableName = "v2ray.location.asset"; + + public static string? FindExecutable(string contentDir, out bool isLocal, string? executableName = null) + { + isLocal = false; + executableName ??= "v2ray"; + + if (OperatingSystem.IsWindows()) + { + executableName += ".exe"; + } + + var localToolPath = Path.Combine(contentDir, executableName); + if (File.Exists(localToolPath)) + { + isLocal = true; + return localToolPath; + } + + var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator); + if (paths is not null) + { + foreach (var p in paths) + { + var toolPath = Path.Combine(p, executableName); + if (File.Exists(toolPath)) + { + return toolPath; + } + } + } + + return null; + } + + public string ExecutablePath { get; } = executablePath; + public string ConfigPath { get; } = configPath; + public string? AssetPath { get; } = assetPath; + public Process? CurrentProcess { get; private set; } + + private Process CreateProcess() + { + var process = new Process(); + + var startInfo = new ProcessStartInfo + { + FileName = ExecutablePath, + }; + startInfo.ArgumentList.Add("run"); + startInfo.ArgumentList.Add("-c"); + startInfo.ArgumentList.Add(ConfigPath); + if (AssetPath is not null) + { + startInfo.EnvironmentVariables[ToolAssetEnvironmentVariableName] = AssetPath; + } + + process.StartInfo = startInfo; + process.OutputDataReceived += (_, args) => + { + Console.Out.Write(args.Data); + }; + process.ErrorDataReceived += (_, args) => + { + Console.Error.WriteLine(args.Data); + }; + + return process; + } + + public void Stop() + { + if (CurrentProcess is not null) + { + CurrentProcess.Kill(); + CurrentProcess.Dispose(); + CurrentProcess = null; + Console.WriteLine("V2ray stopped."); + } + } + + public void Start(bool stopOld = false) + { + if (stopOld) Stop(); + + if (CurrentProcess is null) + { + CurrentProcess = CreateProcess(); + CurrentProcess.EnableRaisingEvents = true; + CurrentProcess.Exited += (_, _) => + { + if (CurrentProcess.ExitCode != 0) + { + const string message = "V2ray exited with error."; + Console.Error.WriteLine(message); + throw new Exception(message); + } + }; + CurrentProcess.Start(); + Console.WriteLine("V2ray started."); + } + } + + public void Restart() + { + Start(true); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj b/store/works/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj new file mode 100644 index 0000000..2502e74 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj @@ -0,0 +1,34 @@ + + + + Exe + net8.0 + enable + enable + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs new file mode 100644 index 0000000..26e9231 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs @@ -0,0 +1,26 @@ +namespace Crupest.SecretTool; + +public class FileWatcher(string directory, List fileNames) +{ + public string Directory { get; set; } = directory; + public List FileNames { get; set; } = fileNames; + + public delegate void OnChangedHandler(); + public event OnChangedHandler? OnChanged; + + public void Run() + { + var sourceWatcher = new FileSystemWatcher(Directory); + foreach (var fileName in FileNames) + { + sourceWatcher.Filters.Add(fileName); + } + sourceWatcher.NotifyFilter = NotifyFilters.LastWrite; + + while (true) + { + var result = sourceWatcher.WaitForChanged(WatcherChangeTypes.Changed | WatcherChangeTypes.Created); + OnChanged?.Invoke(); + } + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs new file mode 100644 index 0000000..8f4c171 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs @@ -0,0 +1,324 @@ +using System.IO.Compression; + +namespace Crupest.SecretTool; + +public interface IGeoSiteEntry +{ + bool IsInclude { get; } + string Value { get; } +} + +public record GeoSiteIncludeEntry(string Value, string ContainingSite) : IGeoSiteEntry +{ + public bool IsInclude => true; +} + +public record GeoSiteRuleEntry(HostMatchKind Kind, string Value, List Attributes, string ContainingSite) : IGeoSiteEntry +{ + public bool IsInclude => false; + + public RoutingRuleMatcher GetRoutingRuleMatcher() => new(Kind, Value); +} + +public record GeoSite(string Name, List Entries) +{ + public static GeoSite Parse(string name, string str) + { + List entries = []; + var listConfig = new ListConfig(str); + foreach (var item in listConfig.Config) + { + var (value, line) = item; + + if (value.StartsWith("include:")) + { + var include = value["include:".Length..].Trim(); + if (include.Length == 0 || include.Contains(' ')) + { + throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid include value."); + } + entries.Add(new GeoSiteIncludeEntry(include, name)); + continue; + } + + var segments = value.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + if (segments.Length > 2) + { + throw new FormatException($"Invalid geo site rule '{name}' in line {line}. More than one ':'."); + } + + HostMatchKind kind; + if (segments.Length == 2) + { + kind = segments[0] switch + { + "domain" => kind = HostMatchKind.DomainSuffix, + "full" => kind = HostMatchKind.DomainFull, + "keyword" => kind = HostMatchKind.DomainKeyword, + "regexp" => kind = HostMatchKind.DomainRegex, + _ => throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Unknown matcher.") + }; + } + else + { + kind = HostMatchKind.DomainSuffix; + } + + var domainSegments = segments[^1].Split('@', StringSplitOptions.TrimEntries); + var domain = domainSegments[0]; + if (kind != HostMatchKind.DomainRegex && Uri.CheckHostName(domain) != UriHostNameType.Dns) + { + throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid domain."); + } + + List attributes = []; + foreach (var s in domainSegments[1..]) + { + if (s.Length == 0) + { + throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Empty attribute value."); + } + attributes.Add(s); + } + + entries.Add(new GeoSiteRuleEntry(kind, domain, attributes, name)); + } + return new GeoSite(name, entries); + } +} + +public class GeoSiteData(string directory) +{ + private static List Parse(string directory) + { + var sites = new List(); + foreach (var path in Directory.GetFileSystemEntries(directory)) + { + var content = File.ReadAllText(path); + sites.Add(GeoSite.Parse(Path.GetFileName(path), content)); + } + return sites; + } + + public string DataDirectory { get; } = directory; + + public List Sites { get; } = Parse(directory); + + public GeoSite? GetSite(string name) + { + return Sites.Where(s => s.Name == name).FirstOrDefault(); + } + + public List GetEntriesRecursive(List sites, + List? onlyMatcherKinds = null, List? onlyAttributes = null) + { + List entries = []; + HashSet visited = []; + HashSet? kinds = onlyMatcherKinds?.ToHashSet(); + + void Visit(string site) + { + if (visited.Contains(site)) + { + return; + } + + visited.Add(site); + var siteData = GetSite(site); + if (siteData == null) + { + return; + } + foreach (var entry in siteData.Entries) + { + if (entry is GeoSiteIncludeEntry includeEntry) + { + Visit(includeEntry.Value); + } + else if (entry is GeoSiteRuleEntry geoSiteRuleEntry) + { + if (kinds != null && !kinds.Contains(geoSiteRuleEntry.Kind)) + { + continue; + } + + if (onlyAttributes != null && !geoSiteRuleEntry.Attributes.Intersect(onlyAttributes).Any()) + { + continue; + } + + entries.Add(geoSiteRuleEntry); + } + } + } + + foreach (var s in sites) + { + Visit(s); + } + + return entries; + } +} + +public class GeoDataManager +{ + public const string GeoSiteFileName = "geosite.dat"; + public const string GeoIpFileName = "geoip.dat"; + public const string GeoIpCnFileName = "geoip-only-cn-private.dat"; + + public static class ToolGithub + { + public const string Organization = "v2fly"; + public const string GeoSiteRepository = "domain-list-community"; + public const string GeoIpRepository = "geoip"; + public const string GeoSiteReleaseFilename = "dlc.dat"; + public const string GeoIpReleaseFilename = "geoip.dat"; + public const string GeoIpCnReleaseFilename = "geoip-only-cn-private.dat"; + } + + public static GeoDataManager Instance { get; } = new GeoDataManager(); + + public record GeoDataAsset(string Name, string FileName, string GithubUser, string GithubRepo, string GithubReleaseFileName); + + public GeoDataManager() + { + Assets = + [ + new("geosite", GeoSiteFileName, ToolGithub.Organization, ToolGithub.GeoSiteRepository, ToolGithub.GeoSiteReleaseFilename), + new("geoip", GeoIpFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpReleaseFilename), + new("geoip-cn", GeoIpCnFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpCnReleaseFilename), + ]; + } + + public List Assets { get; set; } + + public GeoSiteData? GeoSiteData { get; set; } + + public GeoSiteData GetOrCreateGeoSiteData(bool clean, bool silent) + { + if (GeoSiteData is not null) { return GeoSiteData; } + GeoSiteData = DownloadAndGenerateGeoSiteData(clean, silent); + return GeoSiteData; + } + + private static string GetReleaseFileUrl(string user, string repo, string fileName) + { + return $"https://github.com/{user}/{repo}/releases/latest/download/{fileName}"; + } + + private static void GithubDownloadRelease(HttpClient httpClient, string user, string repo, string fileName, string outputPath, bool silent) + { + var url = GetReleaseFileUrl(user, repo, fileName); + if (!silent) Console.WriteLine($"Downloading {url} to {outputPath}"); + using var responseStream = httpClient.GetStreamAsync(url).Result; + using var outputFileStream = File.OpenWrite(outputPath); + responseStream.CopyTo(outputFileStream); + } + + public bool HasAllAssets(string directory, out List missing) + { + missing = []; + foreach (var asset in Assets) + { + var assetPath = Path.Combine(directory, asset.FileName); + if (!File.Exists(assetPath)) + { + missing.Add(asset.Name); + } + } + return missing.Count == 0; + } + + public void Download(string outputDir, bool silent) + { + using var httpClient = new HttpClient(); + + foreach (var asset in Assets) + { + if (!silent) + { + Console.WriteLine($"Downloading {asset.Name}..."); + } + GithubDownloadRelease(httpClient, asset.GithubUser, asset.GithubRepo, asset.GithubReleaseFileName, Path.Combine(outputDir, asset.FileName), silent); + if (!silent) + { + Console.WriteLine($"Downloaded {asset.Name}!"); + } + } + + if (!File.Exists(Program.RestartLabelFilePath)) + { + File.Create(Program.RestartLabelFilePath); + } + else + { + File.SetLastWriteTime(Program.RestartLabelFilePath, DateTime.Now); + } + } + + private static string GetGithubRepositoryArchiveUrl(string user, string repo) + { + return $"https://github.com/{user}/{repo}/archive/refs/heads/master.zip"; + } + + private static void GithubDownloadRepository(HttpClient httpClient, string user, string repo, string outputPath, bool silent) + { + var url = GetGithubRepositoryArchiveUrl(user, repo); + if (!silent) { Console.WriteLine($"Begin to download data from {url} to {outputPath}."); } + using var responseStream = httpClient.GetStreamAsync(url).Result; + using var outputFileStream = File.OpenWrite(outputPath); + responseStream.CopyTo(outputFileStream); + if (!silent) { Console.WriteLine("Succeeded to download."); } + } + + private static void Unzip(string zipPath, string outputPath) + { + using var zip = ZipFile.OpenRead(zipPath) ?? throw new Exception($"Failed to open zip file {zipPath}"); + zip.ExtractToDirectory(outputPath); + } + + private static string DownloadAndExtractGeoDataRepository(bool cleanTempDirIfFailed, bool silent, out string tempDirectoryPath) + { + tempDirectoryPath = ""; + const string zipFileName = "v2ray-geosite-master.zip"; + using var httpClient = new HttpClient(); + var tempDirectory = Directory.CreateTempSubdirectory(Program.Name); + tempDirectoryPath = tempDirectory.FullName; + try + { + var archivePath = Path.Combine(tempDirectoryPath, zipFileName); + var extractPath = Path.Combine(tempDirectoryPath, "repo"); + GithubDownloadRepository(httpClient, ToolGithub.Organization, ToolGithub.GeoSiteRepository, archivePath, silent); + if (!silent) { Console.WriteLine($"Extract geo data to {extractPath}."); } + Directory.CreateDirectory(extractPath); + Unzip(archivePath, extractPath); + if (!silent) { Console.WriteLine($"Extraction done."); } + return Path.Join(extractPath, "domain-list-community-master"); + } + catch (Exception) + { + if (cleanTempDirIfFailed) + { + Directory.Delete(tempDirectoryPath, true); + } + throw; + } + } + + private static GeoSiteData DownloadAndGenerateGeoSiteData(bool clean, bool silent) + { + var repoDirectory = DownloadAndExtractGeoDataRepository(clean, silent, out var tempDirectoryPath); + try + { + return new GeoSiteData(Path.Join(repoDirectory, "data")); + } + finally + { + if (clean) + { + Directory.Delete(tempDirectoryPath, true); + } + } + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs new file mode 100644 index 0000000..858333d --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs @@ -0,0 +1,123 @@ +namespace Crupest.SecretTool; + +public enum HostMatchKind +{ + DomainFull, + DomainSuffix, + DomainKeyword, + DomainRegex, + Ip, + GeoSite, + GeoIp, +} + +public static class HostMatchKindExtensions +{ + public static bool IsDomain(this HostMatchKind kind) + { + return kind.IsNonRegexDomain() || kind == HostMatchKind.DomainRegex; + } + + public static bool IsNonRegexDomain(this HostMatchKind kind) + { + return kind is HostMatchKind.DomainFull or HostMatchKind.DomainSuffix or HostMatchKind.DomainKeyword; + } + + + public static List DomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword, HostMatchKind.DomainRegex]; + + public static List NonRegexDomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword]; + + public static List SupportedInSingRouteMatchKinds { get; } = [..DomainMatchKinds, HostMatchKind.Ip]; + + public static bool IsSupportedInSingRoute(this HostMatchKind kind) => SupportedInSingRouteMatchKinds.Contains(kind); +} + +public record HostMatchConfigItem(HostMatchKind Kind, string MatchString, List Values); + +public class HostMatchConfig(string configString, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) +{ + private static List Parse(string configString, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) + { + var items = new ListConfig(configString).Config; + var result = new List(); + + foreach (var item in items) + { + var lineNumber = item.LineNumber; + var line = item.Value; + var hasExplicitMatchKind = false; + var segments = line.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(); + + foreach (var matchKind in Enum.GetValues()) + { + var matchKindName = Enum.GetName(matchKind) ?? throw new Exception("No such match kind."); + if (segments[0] == matchKindName) + { + hasExplicitMatchKind = true; + + if (segments.Count < 2) + { + throw new FormatException($"Explicit match item needs a value in line {lineNumber}."); + } + if (allowedMatchKinds.Contains(matchKind)) + { + if (matchKind.IsNonRegexDomain() && Uri.CheckHostName(matchKindName) != UriHostNameType.Dns) + { + throw new FormatException($"Invalid domain format in line {lineNumber}."); + } + + var components = segments[2..].ToList(); + if (minComponentCount > 0 && components.Count < minComponentCount) + { + throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required."); + } + if (maxComponentCount >= 0 && components.Count > maxComponentCount) + { + throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed."); + } + result.Add(new HostMatchConfigItem(matchKind, segments[1], components)); + } + else + { + throw new FormatException($"Match kind {matchKindName} is not allowed at line {lineNumber}."); + } + } + } + + if (!hasExplicitMatchKind) + { + if (minComponentCount > 0 && segments.Count - 1 < minComponentCount) + { + throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required."); + } + if (maxComponentCount >= 0 && segments.Count - 1 > maxComponentCount) + { + throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed."); + } + result.Add(new HostMatchConfigItem(HostMatchKind.DomainSuffix, segments[0], segments.Count == 1 ? [] : segments[1..])); + } + } + return result; + } + + public string ConfigString { get; } = configString; + public List AllowedMatchKinds { get; } = allowedMatchKinds; + public int MinComponentCount { get; } = minComponentCount; + public int MaxComponentCount { get; } = maxComponentCount; + public List Items { get; } = Parse(configString, allowedMatchKinds, minComponentCount, maxComponentCount); +} + +public class HostMatchConfigFile +{ + public HostMatchConfigFile(string path, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) + { + Path = path; + FileContent = File.ReadAllText(path); + Config = new HostMatchConfig(FileContent, allowedMatchKinds, minComponentCount, maxComponentCount); ; + } + + public string Path { get; } + public string FileContent { get; } + public HostMatchConfig Config { get; } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Program.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Program.cs new file mode 100644 index 0000000..18b1ac0 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Program.cs @@ -0,0 +1,113 @@ +using System.Reflection; + +namespace Crupest.SecretTool; + +public static class Program +{ + public static string Name { get; } = typeof(Program).Namespace ?? throw new Exception("Can't get the name of Crupest.SecretTool."); + + public static string CrupestSecretToolDirectory { get; } = + Environment.GetEnvironmentVariable("CRUPEST_V2RAY_DIR") ?? + Path.GetFullPath(Path.GetDirectoryName( + Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Can't get the path of Crupest.SecretTool.")); + + private const string ConfigOutputFileName = "config.json"; + private const string SurgeRuleSetChinaOutputFileName = "ChinaRuleSet.txt"; + private const string SurgeRuleSetGlobalOutputFileName = "GlobalRuleSet.txt"; + + public const string RestartLabelFileName = "restart.label"; + public static string RestartLabelFilePath { get; } = Path.Combine(CrupestSecretToolDirectory, RestartLabelFileName); + + public static void RunToolAndWatchConfigChange() + { + var executablePath = Controller.FindExecutable(CrupestSecretToolDirectory, out var isLocal) ?? + throw new Exception("Can't find v2ray executable either in Crupest.SecretTool directory or in PATH."); + + string? assetsPath; + if (isLocal) + { + assetsPath = CrupestSecretToolDirectory; + var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing); + if (!assetsComplete) + { + throw new Exception($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is local. So only use assets in Crupest.SecretTool directory."); + } + } + else + { + assetsPath = CrupestSecretToolDirectory; + var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing); + if (!assetsComplete) + { + Console.WriteLine($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is global. So fallback to its own assets."); + assetsPath = null; + } + } + + var controller = new Controller(executablePath, Path.Combine(CrupestSecretToolDirectory, ConfigOutputFileName), assetsPath); + var configFileWatcher = new FileWatcher(CrupestSecretToolDirectory, + [.. ToolConfig.ConfigFileNames, RestartLabelFileName]); + + ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName)); + controller.Start(); + + configFileWatcher.OnChanged += () => + { + ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName)); + controller.Restart(); + }; + + configFileWatcher.Run(); + } + + public static void Main(string[] args) + { + if (args.Length != 0) + { + var verb = args[0].ToLower(); + if (verb == "download-geodata" || verb == "dg") + { + if (args.Length != 1) + { + throw new Exception("Invalid command line arguments. download-geodata requires no arguments."); + } + GeoDataManager.Instance.Download(CrupestSecretToolDirectory, false); + return; + } + else if (verb == "generate-surge-rule-set" || verb == "gsr") + { + if (args.Length != 1) + { + throw new Exception("Invalid command line arguments. download-geodata requires no arguments."); + } + SurgeConfigGenerator.GenerateTo( + CrupestSecretToolDirectory, + Path.Join(CrupestSecretToolDirectory, SurgeRuleSetChinaOutputFileName), + Path.Join(CrupestSecretToolDirectory, SurgeRuleSetGlobalOutputFileName), + true, true + ); + return; + } + else if (verb == "generate-sing-config" || verb == "gs") + { + if (args.Length != 2 || args[1].ToLower() is not ("pc" or "mobile")) + { + throw new Exception("Invalid command line arguments. generate-sing-config requires 1 argument. The argument must be either 'pc' or 'mobile'."); + } + + var config = SingToolConfig.FromDirectory(CrupestSecretToolDirectory, args[1].ToLower() == "mobile", true, true); + Console.Out.WriteLine(config.ToSingConfigString()); + return; + } + else if (verb == "generate" || verb == "g") + { + var config = ToolConfig.FromDirectory(CrupestSecretToolDirectory); + Console.Out.WriteLine(config.ToJsonStringV4()); + return; + } + throw new Exception("Invalid command line arguments."); + } + + RunToolAndWatchConfigChange(); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml b/store/works/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..5fca454 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs new file mode 100644 index 0000000..d2703ba --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs @@ -0,0 +1,76 @@ +namespace Crupest.SecretTool; + +public abstract class Proxy(string tag) : IV4ConfigObject, ISingConfigObject +{ + public string Tag { get; set; } = tag; + + public abstract V4ConfigJsonObjects.Outbound ToJsonObjectV4(); + public abstract SingConfigJsonObjects.OutboundBase ToJsonObjectSing(); + + object IV4ConfigObject.ToJsonObjectV4() + { + return ToJsonObjectV4(); + } + + object ISingConfigObject.ToJsonObjectSing() + { + return ToJsonObjectSing(); + } +} + +public class HttpProxy(string host, int port, string tag) : Proxy(tag) +{ + public string Host { get; set; } = host; + public int Port { get; set; } = port; + + public override SingConfigJsonObjects.OutboundBase ToJsonObjectSing() + { + throw new NotImplementedException("Http proxy is not supported in sing now."); + } + + public override V4ConfigJsonObjects.Outbound ToJsonObjectV4() + { + return new V4ConfigJsonObjects.Outbound(Tag, "http", + new V4ConfigJsonObjects.HttpOutboundSettings([new V4ConfigJsonObjects.HttpOutboundServer(Host, Port, [])]), + null + ); + } +} + + +public class VmessProxy(string host, int port, string userId, string path, string tag) : Proxy(tag) +{ + public string Host { get; set; } = host; + public int Port { get; set; } = port; + public string Path { get; set; } = path; + public string UserId { get; set; } = userId; + + public override SingConfigJsonObjects.OutboundBase ToJsonObjectSing() + { + return new SingConfigJsonObjects.VmessOutbound(Tag, Host, Port, UserId, + Transport: new SingConfigJsonObjects.V2rayWebsocketTransport(Path, new Dictionary { { "Host", Host } }), + Tls: new SingConfigJsonObjects.OutboundTls(true)); + } + + public override V4ConfigJsonObjects.Outbound ToJsonObjectV4() + { + return new V4ConfigJsonObjects.Outbound(Tag, "vmess", + new V4ConfigJsonObjects.VmessOutboundSettings( + [new V4ConfigJsonObjects.VnextServer(Host, Port, [new V4ConfigJsonObjects.VnextServerUser(UserId, 0, "auto", 0)])]), + new V4ConfigJsonObjects.WsStreamSettings("ws", "tls", new V4ConfigJsonObjects.WsSettings(Path, new() { ["Host"] = Host })) + ); + } + + public static VmessProxy CreateFromConfigString(string configString, string tag) + { + var config = new DictionaryConfig(configString, ["host", "port", "userid", "path"]); + var portString = config.GetItemCaseInsensitive("port").Value; + if (!int.TryParse(portString, out var port) || port <= 0) + { + throw new FormatException($"Invalid port number: {portString}: not an integer or is a invalid number."); + } + return new VmessProxy(config.GetItemCaseInsensitive("host").Value, port, + config.GetItemCaseInsensitive("userid").Value, config.GetItemCaseInsensitive("path").Value, tag + ); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs new file mode 100644 index 0000000..81698a3 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs @@ -0,0 +1,31 @@ +namespace Crupest.SecretTool; + +public class ProxyFile : HostMatchConfigFile +{ + public ProxyFile(string path) : base(path, [.. Enum.GetValues()], maxComponentCount: 0) + { + RoutingRuleMatchers = Config.Items.Select(i => new RoutingRuleMatcher(i.Kind, i.MatchString)).ToList(); + } + + public List RoutingRuleMatchers { get; } + + public List GetChinaRulesByGeoSite(GeoSiteData geoSiteData) + { + var geoSites = RoutingRuleMatchers.Where(m => m.MatchKind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList(); + return geoSiteData.GetEntriesRecursive(geoSites, HostMatchKindExtensions.DomainMatchKinds, ["cn"]).Select(e => e.GetRoutingRuleMatcher()).ToList(); + } + + public List GetRulesFlattenGeoSite(GeoSiteData geoSiteData, bool noCn = false) + { + var geoSites = RoutingRuleMatchers.Where(m => m.MatchKind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList(); + var flattenGeoSiteRules = geoSiteData.GetEntriesRecursive(geoSites, HostMatchKindExtensions.DomainMatchKinds) + .Where(e => !noCn || !e.Attributes.Contains("cn")) + .Select(e => e.GetRoutingRuleMatcher()) + .ToList(); + var otherRules = RoutingRuleMatchers.Where(m => m.MatchKind != HostMatchKind.GeoSite).ToList(); + return [ + ..flattenGeoSiteRules, + ..otherRules + ]; + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Routing.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Routing.cs new file mode 100644 index 0000000..fdf1b93 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Routing.cs @@ -0,0 +1,155 @@ +namespace Crupest.SecretTool; + +public record RoutingRuleMatcher(HostMatchKind MatchKind, string MatchString) +{ + public RoutingRule ToRoutingRule(string OutboundTag) => new(MatchKind, MatchString, OutboundTag); +} + +public record RoutingRule(HostMatchKind MatchKind, string MatchString, string OutboundTag) : IV4ConfigObject +{ + public string ToolConfigString => MatchKind switch + { + HostMatchKind.DomainFull => $"full:{MatchString}", + HostMatchKind.DomainSuffix => $"domain:{MatchString}", + HostMatchKind.DomainKeyword => MatchString, + HostMatchKind.DomainRegex => $"regexp:{MatchString}", + HostMatchKind.Ip => MatchString, + HostMatchKind.GeoSite => $"geosite:{MatchString}", + HostMatchKind.GeoIp => $"geoip:{MatchString}", + _ => throw new ArgumentException("Invalid matcher kind.") + }; + + public string ToolConfigStringSing => MatchKind.IsSupportedInSingRoute() ? MatchString : throw new ArgumentException("Unsupported matcher kind for sing."); + + public static Dictionary> GroupByOutboundTag(List rules) + => rules.GroupBy(r => r.OutboundTag).Select(g => (g.Key, g.ToList())).ToDictionary(); + + public static Dictionary> GroupByMatchKind(List rules) + => rules.GroupBy(r => r.MatchKind).Select(g => (g.Key, g.ToList())).ToDictionary(); + + public static List> GroupByOutboundTagAndMatcherKind(List rules) + => GroupByOutboundTag(rules).Values.SelectMany((groupByTag) => GroupByMatchKind(groupByTag).Values).ToList(); + + public static SingConfigJsonObjects.RouteRule ListToJsonObjectSing(List rules) + { + if (rules.Count == 0) + { + throw new ArgumentException("Rule list is empty."); + } + + var outboundTag = rules[0].OutboundTag; + + if (rules.Any(r => !r.MatchKind.IsSupportedInSingRoute())) + { + throw new ArgumentException("Rules must have matcher kinds supported in sing."); + } + + if (rules.Any(r => r.OutboundTag != outboundTag)) + { + throw new ArgumentException("Rules must have the same outbound tag."); + } + + return new SingConfigJsonObjects.RouteRule(Outbound: outboundTag, + Domain: rules.Where(r => r.MatchKind == HostMatchKind.DomainFull).Select(r => r.ToolConfigStringSing).ToList(), + DomainSuffix: rules.Where(r => r.MatchKind == HostMatchKind.DomainSuffix).Select(r => r.ToolConfigStringSing).ToList(), + DomainKeyword: rules.Where(r => r.MatchKind == HostMatchKind.DomainKeyword).Select(r => r.ToolConfigStringSing).ToList(), + DomainRegex: rules.Where(r => r.MatchKind == HostMatchKind.DomainRegex).Select(r => r.ToolConfigStringSing).ToList(), + IpCidr: rules.Where(r => r.MatchKind == HostMatchKind.Ip).Select(r => r.ToolConfigStringSing).ToList() + ); + } + + public static V4ConfigJsonObjects.RoutingRule ListToJsonObject(List rules) + { + if (rules.Count == 0) + { + throw new ArgumentException("Rule list is empty."); + } + + var matchKind = rules[0].MatchKind; + var outboundTag = rules[0].OutboundTag; + + if (rules.Any(r => r.OutboundTag != outboundTag) || rules.Any(r => r.MatchKind != matchKind)) + { + throw new ArgumentException("Rules must have the same matcher kind and outbound tag."); + } + + List toolConfigList = rules.Select(r => r.ToolConfigString).ToList(); + + return new V4ConfigJsonObjects.RoutingRule(OutboundTag: outboundTag, + Ip: (matchKind is HostMatchKind.Ip or HostMatchKind.GeoIp) ? toolConfigList : null, + Domains: (matchKind.IsDomain() || matchKind == HostMatchKind.GeoSite) ? toolConfigList : null + ); + } + + public RoutingRule CloneGeositeWithCnAttribute(string outboundTag) + { + if (MatchKind is not HostMatchKind.GeoSite) + { + throw new ArgumentException("Matcher kind must be GeoSite."); + } + + return new RoutingRule(HostMatchKind.GeoSite, $"{MatchString}@cn", outboundTag); + } + + public RoutingRuleMatcher GetMatcher() => new(MatchKind, MatchString); + + public V4ConfigJsonObjects.RoutingRule ToJsonObjectV4() => ListToJsonObject([this]); + + object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4(); +} + +public record Routing(List Rules) : IV4ConfigObject, ISingConfigObject +{ + public List CreateGeositeCnDirectRules() + { + return Rules.Where(r => r.MatchKind is HostMatchKind.GeoSite) + .Select(r => r.CloneGeositeWithCnAttribute("direct")).ToList(); + } + + public SingConfigJsonObjects.Route ToJsonObjectSing() + { + List ruleJsonObjects = [ new SingConfigJsonObjects.RouteRule(Outbound: "dns-out", Protocol: "dns")]; + ruleJsonObjects.AddRange(RoutingRule.GroupByOutboundTag(Rules).Values.Select(RoutingRule.ListToJsonObjectSing)); + return new SingConfigJsonObjects.Route(ruleJsonObjects); + } + + public V4ConfigJsonObjects.Routing ToJsonObjectV4(string domainStrategy = "IpOnDemand", bool directGeositeCn = true) + { + List ruleJsonObjects = []; + + if (directGeositeCn) + { + ruleJsonObjects.Add(RoutingRule.ListToJsonObject(CreateGeositeCnDirectRules())); + } + + ruleJsonObjects.AddRange(RoutingRule.GroupByOutboundTagAndMatcherKind(Rules).Select(RoutingRule.ListToJsonObject)); + + return new V4ConfigJsonObjects.Routing(ruleJsonObjects, domainStrategy); + } + + object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4(); + + object ISingConfigObject.ToJsonObjectSing() => ToJsonObjectSing(); + + public static Routing FromProxyFile(ProxyFile proxyFile, string outboundTag) + { + return new Routing( + proxyFile.RoutingRuleMatchers.Select(m => m.ToRoutingRule(outboundTag)).ToList()); + } + + public static Routing FromProxyFileForSing(ProxyFile proxyFile, GeoSiteData geoSiteData, string outboundTag, string? directCnOutboundTag = null) + { + List rules = []; + + if (directCnOutboundTag is not null) + { + rules.AddRange(proxyFile.GetChinaRulesByGeoSite(geoSiteData).Select(m => m.ToRoutingRule(directCnOutboundTag)).ToList()); + } + + rules.AddRange(proxyFile.GetRulesFlattenGeoSite(geoSiteData).Where(m => m.MatchKind.IsSupportedInSingRoute()).Select(m => m.ToRoutingRule(outboundTag)).ToList()); + + return new Routing( + rules + ); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs new file mode 100644 index 0000000..56b5563 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs @@ -0,0 +1,20 @@ +namespace Crupest.SecretTool; + +public static class SingConfigJsonObjects +{ + public interface IObject; + + public record OutboundTls(bool Enabled); + public record V2rayTransportBase(string Type); + public record V2rayWebsocketTransport(string Path, Dictionary? Headers = null) : V2rayTransportBase("ws"); + public record OutboundBase(string Tag, string Type) : IObject; + public record VmessOutbound(string Tag, string Server, int ServerPort, string Uuid, string Security = "auto", + V2rayTransportBase? Transport = null, OutboundTls? Tls = null): OutboundBase(Tag, "vmess"); + + public record RouteRule(List? Domain = null, List? DomainSuffix = null, List? DomainKeyword = null, + List? DomainRegex = null, List? IpCidr = null, List? SourceIpCidr = null, string? Protocol = null, + List? Port = null, List? SourcePort = null, List? PortRange = null, List? SourcePortRange = null, + string? Network = null, List? Inbound = null, string? Outbound = null) : IObject; + + public record Route(List Rules) : IObject; +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs new file mode 100644 index 0000000..b112e1c --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs @@ -0,0 +1,40 @@ +namespace Crupest.SecretTool; + +public record StaticHostRule(HostMatchKind MatchKind, string MatchString, List ResolveResult) +{ + public string AddressString() + { + return MatchKind switch + { + HostMatchKind.DomainFull => MatchString, + HostMatchKind.DomainSuffix => $"domain:{MatchString}", + HostMatchKind.DomainKeyword => $"keyword:{MatchString}", + HostMatchKind.DomainRegex => $"regexp:{MatchString}", + _ => throw new ArgumentOutOfRangeException($"Match kind {MatchKind} is not allowed in static host rule."), + }; + } + + public object ResolveResultToJsonObject() + { + return ResolveResult.Count == 1 ? ResolveResult[0] : ResolveResult; + } +} + +public class StaticHosts(List rules) : IV4ConfigObject +{ + public List Rules { get; } = rules; + + public Dictionary ToJsonObjectV4() => + Rules.ToDictionary(rule => rule.AddressString(), rule => rule.ResolveResultToJsonObject()); + + object IV4ConfigObject.ToJsonObjectV4() + { + return ToJsonObjectV4(); + } + + public static StaticHosts CreateFromHostMatchConfigString(string configString) + { + var config = new HostMatchConfig(configString, HostMatchKindExtensions.DomainMatchKinds, minComponentCount: 1); + return new StaticHosts(config.Items.Select(i => new StaticHostRule(i.Kind, i.MatchString, [.. i.Values])).ToList()); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs new file mode 100644 index 0000000..8a57c9f --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs @@ -0,0 +1,56 @@ +namespace Crupest.SecretTool; + +public class SurgeConfigGenerator(ProxyFile proxyFile, GeoSiteData geoData) +{ + public ProxyFile ProxyFile => proxyFile; + public GeoSiteData GeoData => geoData; + + private static string ToSurgeRuleString(HostMatchKind kind, string value) + { + var ruleType = kind switch + { + HostMatchKind.DomainFull => "DOMAIN", + HostMatchKind.DomainSuffix => "DOMAIN-SUFFIX", + HostMatchKind.DomainKeyword => "DOMAIN-KEYWORD", + HostMatchKind.DomainRegex => "URL-REGEX", + _ => throw new Exception("Unacceptable matcher kind for Surge rule.") + }; + + return $"{ruleType},{value}"; + } + + public static string GenerateSurgeRuleSetString(List rules) + { + return string.Join('\n', rules.Select(r => ToSurgeRuleString(r.MatchKind, r.MatchString))); + } + + public string GenerateChinaRuleSet() + { + return GenerateSurgeRuleSetString(proxyFile.GetChinaRulesByGeoSite(GeoData)); + } + + public string GenerateGlobalRuleSet() + { + return GenerateSurgeRuleSetString(proxyFile.GetRulesFlattenGeoSite(geoData, true)); + } + + public static void GenerateTo(ProxyFile proxyFile, GeoSiteData geoSiteData, string cnPath, string globalPath, bool silent) + { + var generator = new SurgeConfigGenerator(proxyFile, geoSiteData); + File.WriteAllText(cnPath, generator.GenerateChinaRuleSet()); + if (!silent) Console.WriteLine($"China rule set written to {cnPath}."); + File.WriteAllText(globalPath, generator.GenerateGlobalRuleSet()); + if (!silent) Console.WriteLine($"Global rule set written to {globalPath}."); + } + + public static void GenerateTo(string directory, string cnPath, string globalPath, bool clean, bool silent) + { + var geoSiteData = GeoDataManager.Instance.GetOrCreateGeoSiteData(clean, silent); + var proxyFile = new ProxyFile(Path.Combine(directory, ToolConfig.ProxyConfigFileName)); + var generator = new SurgeConfigGenerator(proxyFile, geoSiteData); + File.WriteAllText(cnPath, generator.GenerateChinaRuleSet()); + if (!silent) Console.WriteLine($"China rule set written to {cnPath}."); + File.WriteAllText(globalPath, generator.GenerateGlobalRuleSet()); + if (!silent) Console.WriteLine($"Global rule set written to {globalPath}."); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/Template.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/Template.cs new file mode 100644 index 0000000..1fe91b1 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/Template.cs @@ -0,0 +1,231 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Crupest.SecretTool; + +public class Template +{ + private enum ParseState + { + Text, + Dollar, + LeftBracket, + VariableName, + VariableNameFinish, + } + + private interface ITemplateNode + { + string Render(Dictionary values); + } + + private class TextNode(string text) : ITemplateNode + { + + public string Text { get; } = text; + + public string Render(Dictionary values) + { + return Text; + } + } + + private class VariableNode(string variableName) : ITemplateNode + { + public string VariableName { get; } = variableName; + + public string Render(Dictionary values) + { + return values.GetValueOrDefault(VariableName) ?? ""; + } + } + + public Template(string templateString) + { + TemplateString = templateString; + Nodes = Parse(templateString); + VariableNames = Nodes.OfType().Select(node => node.VariableName).ToList(); + } + + private static List Parse(string templateString) + { + int lineNumber = 1; + int columnNumber = 0; + List nodes = []; + ParseState state = ParseState.Text; + StringBuilder stringBuilder = new(); + + string GetPosition() => $"line {lineNumber} column{columnNumber}"; + + [DoesNotReturn] + void ReportInvalidState(string message) + { + throw new Exception($"Invalid state at {GetPosition()}: {message}"); + } + + [DoesNotReturn] + void ReportInvalidCharacter(char c) + { + throw new FormatException($"Unexpected '{c}' at {GetPosition()}."); + } + + void FinishText() + { + if (state != ParseState.Text) + { + ReportInvalidState($"Can't call FinishText here."); + } + + if (stringBuilder.Length > 0) + { + nodes.Add(new TextNode(stringBuilder.ToString())); + stringBuilder.Clear(); + } + } + + foreach (var c in templateString) + { + if (c == '\n') + { + lineNumber++; + columnNumber = 0; + } + + columnNumber++; + + switch (c) + { + case '$': + if (state == ParseState.Text) + { + FinishText(); + state = ParseState.Dollar; + } + else if (state == ParseState.Dollar) + { + if (stringBuilder.Length > 0) + { + throw new Exception($"Invalid state at {GetPosition()}: when we meet the second '$', text builder should be empty."); + } + stringBuilder.Append(c); + state = ParseState.Text; + } + else + { + throw new FormatException($"Unexpected '$' at {GetPosition()}."); + } + break; + case '{': + if (state == ParseState.Text) + { + stringBuilder.Append(c); + } + else if (state == ParseState.Dollar) + { + state = ParseState.LeftBracket; + } + else + { + throw new Exception($"Unexpected '{{' at {GetPosition()}."); + } + break; + case '}': + if (state == ParseState.Text) + { + stringBuilder.Append(c); + state = ParseState.Text; + } + else if (state == ParseState.VariableName || state == ParseState.VariableNameFinish) + { + nodes.Add(new VariableNode(stringBuilder.ToString())); + stringBuilder.Clear(); + state = ParseState.Text; + } + else + { + ReportInvalidCharacter(c); + } + break; + default: + if (state == ParseState.Dollar) + { + ReportInvalidCharacter(c); + } + + if (char.IsWhiteSpace(c)) + { + if (state == ParseState.LeftBracket || state == ParseState.VariableNameFinish) + { + continue; + } + else if (state == ParseState.Text) + { + stringBuilder.Append(c); + } + else if (state == ParseState.VariableName) + { + state = ParseState.VariableNameFinish; + } + else + { + ReportInvalidCharacter(c); + } + } + else + { + if (state == ParseState.Text) + { + stringBuilder.Append(c); + } + else if (state == ParseState.LeftBracket || state == ParseState.VariableName) + { + stringBuilder.Append(c); + state = ParseState.VariableName; + } + else + { + ReportInvalidCharacter(c); + } + } + break; + } + } + + if (state == ParseState.Text) + { + FinishText(); + } + else + { + throw new FormatException("Unexpected end of template string."); + } + + return nodes; + } + + public string TemplateString { get; } + private List Nodes { get; set; } + public List VariableNames { get; } + + public string Generate(Dictionary values, bool allowMissingVariable = false) + { + StringBuilder stringBuilder = new(); + foreach (var node in Nodes) + { + if (node is TextNode textNode) + { + stringBuilder.Append(textNode.Text); + } + else if (node is VariableNode variableNode) + { + var hasValue = values.TryGetValue(variableNode.VariableName, out var value); + if (!hasValue && !allowMissingVariable) + { + throw new Exception($"Variable '{variableNode.VariableName}' is not set."); + } + stringBuilder.Append(hasValue ? value : string.Empty); + } + } + return stringBuilder.ToString(); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs new file mode 100644 index 0000000..809fba1 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs @@ -0,0 +1,271 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Crupest.SecretTool; + +public interface IV4ConfigObject +{ + object ToJsonObjectV4(); +} + +public interface ISingConfigObject +{ + object ToJsonObjectSing(); +} + +public class ToolConfigBase(Template template, List proxies, Routing router) +{ + protected class JsonInterfaceConverter : JsonConverter + { + public override Interface Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write( + Utf8JsonWriter writer, + Interface value, + JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, typeof(object), options); + } + } + + public const string VmessConfigFileName = "vmess.txt"; + public const string ProxyConfigFileName = "proxy.txt"; + + public Template Template { get; set; } = template; + public List Proxies { get; set; } = proxies; + public Routing Routing { get; set; } = router; +} + +public class ToolConfig(Template template, List proxies, Routing router, StaticHosts? hosts) : ToolConfigBase(template, proxies, router) +{ + public const string ConfigTemplateFileName = "config.json.template"; + public const string HostsConfigFileName = "hosts.txt"; + + public static List RequiredConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName]; + public static List ConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName, HostsConfigFileName]; + + private const string ProxyAnchor = "PROXY_ANCHOR"; + private const string RoutingAnchor = "ROUTING_ANCHOR"; + private const string HostsAnchor = "HOSTS_ANCHOR"; + + public const string AddCnAttributeToGeositeEnvironmentVariable = "CRUPEST_V2RAY_GEOSITE_USE_CN"; + + private static bool UseCnGeoSite => Environment.GetEnvironmentVariable(AddCnAttributeToGeositeEnvironmentVariable) switch + { + "0" or "false" or "off" or "disable" => false, + _ => true + }; + + public StaticHosts Hosts { get; set; } = hosts is null ? new StaticHosts([]) : hosts; + + public string ToJsonStringV4(string domainStrategy = "IpOnDemand", bool directGeositeCn = true, bool pretty = true) + { + var jsonOptions = new JsonSerializerOptions(new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }); + // TODO: Make interface converter generic. + jsonOptions.Converters.Add(new JsonInterfaceConverter()); + jsonOptions.Converters.Add(new JsonInterfaceConverter()); + + var templateValues = new Dictionary + { + [ProxyAnchor] = string.Join(',', Proxies.Select(p => JsonSerializer.Serialize(p.ToJsonObjectV4(), jsonOptions))), + [RoutingAnchor] = JsonSerializer.Serialize(Routing.ToJsonObjectV4(domainStrategy, directGeositeCn), jsonOptions), + [HostsAnchor] = JsonSerializer.Serialize(Hosts.ToJsonObjectV4(), jsonOptions), + }; + + var configString = Template.Generate(templateValues); + + if (pretty) + { + var jsonOptionsPretty = new JsonSerializerOptions(jsonOptions) + { + WriteIndented = true, + }; + return JsonSerializer.Serialize(JsonSerializer.Deserialize(configString, jsonOptionsPretty), jsonOptionsPretty); + } + else + { + return configString; + } + } + + public static ToolConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string? hostsPath) + { + foreach (var path in new List([templatePath, vmessPath, proxyPath])) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException($"Required config file not found: {path}."); + } + } + + ProxyFile proxyFile = new(proxyPath); + string templateString, vmessString; + string? hostsString; + + string file = ""; + try + { + file = templatePath; + templateString = File.ReadAllText(templatePath); + file = vmessPath; + vmessString = File.ReadAllText(vmessPath); + hostsString = hostsPath is not null ? File.ReadAllText(hostsPath) : null; + } + catch (Exception e) + { + throw new Exception($"Error reading config file {file}.", e); + } + + try + { + file = templatePath; + var template = new Template(templateString); + file = vmessPath; + var vmess = VmessProxy.CreateFromConfigString(vmessString, "proxy"); + file = proxyPath; + var routing = Routing.FromProxyFile(proxyFile, "proxy"); + file = hostsPath ?? ""; + var hosts = hostsString is not null ? StaticHosts.CreateFromHostMatchConfigString(hostsString) : null; + return new ToolConfig(template, [vmess], routing, hosts); + } + catch (Exception e) + { + throw new Exception($"Error parsing config file {file}.", e); + } + } + + public static ToolConfig FromDirectory(string directory) + { + return FromFiles( + Path.Join(directory, ConfigTemplateFileName), + Path.Join(directory, VmessConfigFileName), + Path.Join(directory, ProxyConfigFileName), + Path.Join(directory, HostsConfigFileName) + ); + } + + public static void FromDirectoryAndWriteToFile(string directory, string outputPath) + { + var config = FromDirectory(directory); + File.WriteAllText(outputPath, config.ToJsonStringV4()); + } +} + +public class SingToolConfig(Template template, List proxies, Routing router, string inboundsString) : ToolConfigBase(template, proxies, router) +{ + + public const string ConfigTemplateFileName = "sing-config.json.template"; + public const string ConfigInboundsPcFileName = "sing-inbounds-pc.json"; + public const string ConfigInboundsMobileFileName = "sing-inbounds-mobile.json"; + + public static List RequiredConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName, ConfigInboundsMobileFileName, ConfigInboundsPcFileName]; + + private const string ProxyAnchor = "PROXY_ANCHOR"; + private const string RouteAnchor = "ROUTE_ANCHOR"; + private const string InboundsAnchor = "INBOUNDS_ANCHOR"; + + public string InboundsString { get; } = inboundsString; + + public string ToSingConfigString(bool pretty = true) + { + var jsonOptions = new JsonSerializerOptions(new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }); + // TODO: Make interface converter generic. + jsonOptions.Converters.Add(new JsonInterfaceConverter()); + jsonOptions.Converters.Add(new JsonInterfaceConverter()); + + var templateValues = new Dictionary + { + [ProxyAnchor] = string.Join(',', Proxies.Select(p => JsonSerializer.Serialize(p.ToJsonObjectSing(), jsonOptions))), + [RouteAnchor] = JsonSerializer.Serialize(Routing.ToJsonObjectSing(), jsonOptions), + [InboundsAnchor] = InboundsString + }; + + var configString = Template.Generate(templateValues); + + if (pretty) + { + var jsonOptionsPretty = new JsonSerializerOptions(jsonOptions) + { + WriteIndented = true, + }; + return JsonSerializer.Serialize(JsonSerializer.Deserialize(configString, jsonOptionsPretty), jsonOptionsPretty); + } + else + { + return configString; + } + } + + public static SingToolConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string inboundsPath, bool clean, bool silent) + { + foreach (var path in new List([templatePath, vmessPath, proxyPath, inboundsPath])) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException($"Required config file not found: {path}."); + } + } + + var geoSiteData = GeoDataManager.Instance.GetOrCreateGeoSiteData(clean, silent); + + ProxyFile proxyFile = new(proxyPath); + string templateString, vmessString, inboundsString; + + string file = ""; + try + { + file = templatePath; + templateString = File.ReadAllText(templatePath); + file = vmessPath; + vmessString = File.ReadAllText(vmessPath); + file = inboundsPath; + inboundsString = File.ReadAllText(inboundsPath); + } + catch (Exception e) + { + throw new Exception($"Error reading config file {file}.", e); + } + + try + { + file = templatePath; + var template = new Template(templateString); + file = vmessPath; + var vmess = VmessProxy.CreateFromConfigString(vmessString, "proxy-out"); + file = proxyPath; + var routing = Routing.FromProxyFileForSing(proxyFile, geoSiteData, "proxy-out", "direct-out"); + return new SingToolConfig(template, [vmess], routing, inboundsString); + } + catch (Exception e) + { + throw new Exception($"Error parsing config file {file}.", e); + } + } + + public static SingToolConfig FromDirectory(string directory, bool isMobile, bool clean, bool silent) + { + return FromFiles( + Path.Join(directory, ConfigTemplateFileName), + Path.Join(directory, VmessConfigFileName), + Path.Join(directory, ProxyConfigFileName), + isMobile ? Path.Join(directory, ConfigInboundsMobileFileName) : Path.Join(directory, ConfigInboundsPcFileName), + clean, silent + ); + } +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs new file mode 100644 index 0000000..3e81dbb --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs @@ -0,0 +1,25 @@ +namespace Crupest.SecretTool; + +public static class V4ConfigJsonObjects +{ + public interface IObject; + public interface IOutboundSettings : IObject; + public interface IOutboundStreamSettings : IObject; + + public record WsSettings(string Path, Dictionary Headers) : IObject; + public record WsStreamSettings(string Network, string Security, WsSettings WsSettings) : IOutboundStreamSettings; + public record VnextServerUser(string Id, int AlterId, string Security, int Level) : IObject; + public record VnextServer(string Address, int Port, List Users) : IObject; + public record VmessOutboundSettings(List Vnext) : IOutboundSettings; + public record HttpOutboundUser(string User, string Pass) : IObject; + public record HttpOutboundServer(string Address, int Port, List Users) : IObject; + public record HttpOutboundSettings(List Servers) : IOutboundSettings; + public record Outbound(string Tag, string Protocol, IOutboundSettings Settings, + IOutboundStreamSettings? StreamSettings) : IObject; + + public record RoutingRule(string DomainMatcher = "mph", string Type = "field", List? Domains = null, List? Ip = null, + string? Port = null, string? SourcePort = null, string? Network = null, List? Source = null, + List? User = null, List? InboundTag = null, List? Protocol = null, string? Attrs = null, + string? OutboundTag = null, string? BalancerTag = null) : IObject; + public record Routing(List Rules, string DomainStrategy = "IpOnDemand", string DomainMatcher = "mph") : IObject; +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs b/store/works/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs new file mode 100644 index 0000000..a50e9be --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs @@ -0,0 +1,31 @@ +namespace Crupest.SecretTool; + +public static class V5ConfigJsonObjects +{ + public record OutboundObject(string Protocol, object Settings, string Tag, object? StreamSettings) + { + public static OutboundObject VmessViaWs(string tag, string address, int port, string uuid, string path) + { + return new OutboundObject("vmess", new VmessSettings(address, port, uuid), tag, StreamSettingsObject.Ws(path)); + } + + public static OutboundObject Http(string tag, string address, int port) + { + return new OutboundObject("http", new HttpSettingsObject(address, port), tag, null); + } + } + + public record WsSettingsObject(string Path, Dictionary Headers); + + public record StreamSettingsObject(string Transport, object TransportSettings, string Security, object SecuritySettings) + { + public static StreamSettingsObject Ws(string path) + { + return new StreamSettingsObject("ws", new WsSettingsObject(path, new()), "tls", new()); + } + } + + public record VmessSettings(string Address, int Port, string Uuid); + + public record HttpSettingsObject(string Address, int Port); +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/config.json.template b/store/works/Crupest.SecretTool/Crupest.SecretTool/config.json.template new file mode 100644 index 0000000..424e996 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/config.json.template @@ -0,0 +1,63 @@ +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "port": 2081, + "listen": "127.0.0.1", + "tag": "socks-inbound", + "protocol": "socks", + "settings": { + "auth": "noauth" + } + }, + { + "port": 2080, + "listen": "127.0.0.1", + "tag": "http-inbound", + "protocol": "http", + "settings": { + "auth": "noauth" + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": {}, + "tag": "direct" + }, + { + "protocol": "blackhole", + "settings": {}, + "tag": "blocked" + }, + ${PROXY_ANCHOR} + ], + "routing": ${ROUTING_ANCHOR}, + "dns": { + "hosts": ${HOSTS_ANCHOR}, + "servers": [ + "https://doh.pub/dns-query", + "1.1.1.1", + "8.8.8.8", + "localhost" + ] + }, + "policy": { + "levels": { + "0": { + "uplinkOnly": 0, + "downlinkOnly": 0 + } + }, + "system": { + "statsInboundUplink": false, + "statsInboundDownlink": false, + "statsOutboundUplink": false, + "statsOutboundDownlink": false + } + }, + "other": {} +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template b/store/works/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template new file mode 100644 index 0000000..01ccf7a --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template @@ -0,0 +1,55 @@ +{ + "log": { + "access": { + "type": "Console", + "level": "Info" + } + }, + "dns": { + "nameServer": [{ + "address": "https://doh.pub/dns-query" + }, { + "address": "1.1.1.1" + }, { + "address": "8.8.8.8" + }, { + "address": "localhost" + }], + "staticHosts": ${HOSTS_ANCHOR} + }, + "inbounds": [{ + { + "protocol": "socks", + "port": 2081, + "listen": "127.0.0.1", + "tag": "socks-inbound", + "settings": { + "auth": "noauth" + } + }, + { + "protocol": "http", + "port": 2080, + "listen": "127.0.0.1", + "tag": "http-inbound", + "settings": { + "auth": "noauth" + } + } + }], + "outbounds": [ + { + "protocol": "freedom", + "settings": {}, + "tag": "direct" + }, + { + "protocol": "blackhole", + "settings": {}, + "tag": "blocked" + }, + ${PROXY_ANCHOR} + ], + "router": ${ROUTER_ANCHOR} +} + diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/hosts.txt b/store/works/Crupest.SecretTool/Crupest.SecretTool/hosts.txt new file mode 100644 index 0000000..88d5015 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/hosts.txt @@ -0,0 +1,2 @@ +cdn.jsdelivr.net cdn.jsdelivr.net.cdn.cloudflare.net + diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/proxy.txt b/store/works/Crupest.SecretTool/Crupest.SecretTool/proxy.txt new file mode 100644 index 0000000..39800f9 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/proxy.txt @@ -0,0 +1,50 @@ +GeoSite microsoft +GeoSite google +GeoSite youtube +GeoSite x +GeoSite facebook +GeoSite discord +GeoSite reddit +GeoSite twitch +GeoSite quora +GeoSite telegram +GeoSite imgur +GeoSite stackexchange +GeoSite medium + +GeoSite duckduckgo +GeoSite wikimedia +GeoSite gitbook +GeoSite github +GeoSite gitlab +GeoSite sourceforge +GeoSite creativecommons +GeoSite archive +GeoSite matrix +GeoSite tor + +GeoSite python +GeoSite ruby +GeoSite rust +GeoSite nodejs +GeoSite npmjs +GeoSite qt +GeoSite docker +GeoSite v2ray +GeoSite homebrew + +GeoSite azure +GeoSite akamai +GeoSite aws +GeoSite jsdelivr +GeoSite fastly +GeoSite heroku +GeoSite bootstrap +GeoSite vercel + +GeoSite ieee +GeoSite sci-hub +GeoSite libgen +GeoSite z-library + +sagernet.org diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template new file mode 100644 index 0000000..d7e55a0 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template @@ -0,0 +1,45 @@ +{ + "log": { + "disabled": false, + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "ali-doh", + "address": "https://dns.alidns.com/dns-query", + "address_resolver": "ali" + }, + { + "tag": "ali", + "address": "223.5.5.5" + }, + { + "tag": "cloudflare", + "address": "1.1.1.1" + }, + { + "tag": "google", + "address": "8.8.8.8" + } + ] + }, + "inbounds": ${INBOUNDS_ANCHOR}, + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + }, + { + "type": "block", + "tag": "block-out" + }, + { + "tag": "dns-out", + "type": "dns" + }, + ${PROXY_ANCHOR} + ], + "route": ${ROUTE_ANCHOR} +} diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json new file mode 100644 index 0000000..5038c40 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json @@ -0,0 +1,11 @@ +[ + { + "tag": "tun-in", + "type": "tun", + "auto_route": true, + "strict_route": true, + "address": [ "172.23.0.1/30", "fdfe:acbd:9876::1/126"], + "sniff": true, + "sniff_override_destination": true + } +] diff --git a/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json new file mode 100644 index 0000000..956d751 --- /dev/null +++ b/store/works/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json @@ -0,0 +1,14 @@ +[ + { + "tag": "http-in", + "type": "http", + "listen": "127.0.0.1", + "listen_port": 3080 + }, + { + "tag": "socks-in", + "type": "socks", + "listen": "127.0.0.1", + "listen_port": 3081 + } +] \ No newline at end of file diff --git a/store/works/Crupest.SecretTool/build-secret.bash b/store/works/Crupest.SecretTool/build-secret.bash new file mode 100755 index 0000000..8878049 --- /dev/null +++ b/store/works/Crupest.SecretTool/build-secret.bash @@ -0,0 +1,41 @@ +#! /usr/bin/env bash + +set -e + +function print_argument_error_message_and_exit() { + argument_error_message="You must specify exactly one argument, the build target (win-x64 | linux-x64 | osx-x64)." + echo "$argument_error_message" + exit 1 +} + + + +if [[ $# != 1 ]]; then + print_argument_error_message_and_exit +fi + +case "$1" in + win-x64 | linux-x64 | osx-x64) + echo "Build target: $1" + ;; + *) + print_argument_error_message_and_exit + ;; +esac + +secret_dir=$(realpath "$(dirname "$0")") + +echo "Secret dir: ${secret_dir}" + +echo "Check dotnet..." +dotnet --version + +echo "Enter \"secret\" dir..." +pushd "$secret_dir" + +echo "Begin to build..." +dotnet publish Crupest.SecretTool -c Release -o "$secret_dir/publish" --sc -r "$1" + +popd + +echo "Finish!" diff --git a/store/works/Crupest.SecretTool/build-secret.ps1 b/store/works/Crupest.SecretTool/build-secret.ps1 new file mode 100644 index 0000000..8aa7987 --- /dev/null +++ b/store/works/Crupest.SecretTool/build-secret.ps1 @@ -0,0 +1,25 @@ +if ($args.Count -ne 1 || $args[0] -notmatch "^win-x64|linux-x64|osx-x64$") +{ + Write-Error "You must specify exactly one argument, the build target (win-x64 | linux-x64 | osx-x64)." + exit 1 +} + +Write-Output "Secret dir: $PSScriptRoot" + +Write-Output "Check dotnet..." +dotnet --version +if ($LASTEXITCODE -ne 0) +{ + Write-Error "dotnet not found." + exit 2 +} + +Write-Output "Enter `"secret`" dir..." +Push-Location $PSScriptRoot + +Write-Output "Begin to build..." +dotnet publish Crupest.SecretTool -c Release -o "$secret_dir/publish" --sc -r $args[0] + +Pop-Location + +Write-Host "Finish!" -ForegroundColor Green diff --git a/store/works/Crupest.SecretTool/tools/cru-proxy-edit b/store/works/Crupest.SecretTool/tools/cru-proxy-edit new file mode 100755 index 0000000..51a33e1 --- /dev/null +++ b/store/works/Crupest.SecretTool/tools/cru-proxy-edit @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +set -e + +p="$HOME/codes/crupest/tools/Crupest.SecretTool/publish/proxy.txt" + +if [[ ! -f "$p" ]]; then + echo "File $p does not exist!" >&2 + exit 1 +fi + +exec vim "$p" diff --git a/store/works/Crupest.SecretTool/tools/cru-proxy-log b/store/works/Crupest.SecretTool/tools/cru-proxy-log new file mode 100755 index 0000000..6ec6ee1 --- /dev/null +++ b/store/works/Crupest.SecretTool/tools/cru-proxy-log @@ -0,0 +1,13 @@ +#! /usr/bin/env bash + +set -e + +if [[ -e /proc ]]; then + # I don't believe your system is Linux but there is no /proc. + exec journalctl --user -u crupest-secret-tool "$@" +elif [[ "$(uname)" == "Darwin" ]]; then + exec less "$HOME/.local/state/Crupest.SecretTool/log" +else + echo "Not supported on systems other than macOS and Linux now." >&2 + exit 1 +fi diff --git a/store/works/Crupest.SecretTool/tools/crupest-secret-tool.service b/store/works/Crupest.SecretTool/tools/crupest-secret-tool.service new file mode 100644 index 0000000..df6d172 --- /dev/null +++ b/store/works/Crupest.SecretTool/tools/crupest-secret-tool.service @@ -0,0 +1,8 @@ +[Unit] +Description=crupest v2ray service + +[Service] +ExecStart=%h/.local/bin/Crupest.SecretTool + +[Install] +WantedBy=default.target diff --git a/store/works/Crupest.SecretTool/tools/crupest-secret-tool.xml b/store/works/Crupest.SecretTool/tools/crupest-secret-tool.xml new file mode 100644 index 0000000..9b85f13 --- /dev/null +++ b/store/works/Crupest.SecretTool/tools/crupest-secret-tool.xml @@ -0,0 +1,49 @@ + + + + + crupest-secret-tool + Crupest Secret Tool + Crupest Secret Tool (powered by WinSW) + + + %BASE%\Crupest.SecretTool.exe + + + + + + %BASE% + + Automatic + \ No newline at end of file diff --git a/store/works/Crupest.SecretTool/tools/life.crupest.secret-tool.plist b/store/works/Crupest.SecretTool/tools/life.crupest.secret-tool.plist new file mode 100644 index 0000000..bdfe490 --- /dev/null +++ b/store/works/Crupest.SecretTool/tools/life.crupest.secret-tool.plist @@ -0,0 +1,18 @@ + + + + + Label + life.crupest.secret-tool + ProgramArguments + + /Users/crupest/.local/bin/Crupest.SecretTool + + KeepAlive + + StandardOutPath + /Users/crupest/.local/state/Crupest.SecretTool/log + StandardErrorPath + /Users/crupest/.local/state/Crupest.SecretTool/error + + diff --git a/store/works/README.md b/store/works/README.md new file mode 100644 index 0000000..b280870 --- /dev/null +++ b/store/works/README.md @@ -0,0 +1,9 @@ +This directory contains some of my works that I have created. I moved them from individual repositories to here for easier management. Some works are stopped for its temporary purpose. Here are some of the descriptions of them. + +- `life`: Something I created in university time. Some for lecture tasks, some are small toys to solve some interesting problems. Maybe more will be added in future. + +- `solutions`: Answers to leetcode and acwing algorithms questions. Stopped now for a well-known reason. Maybe more will be added in future, too. + +- `teapot`: Answer to the pre-interview test for my first job. Dropped now definitely. + +- `ProxyChanger`: Written a long time ago to solve the proxy switching problem in Firefox for a also well-known problem. diff --git a/tools/Crupest.SecretTool/.gitignore b/tools/Crupest.SecretTool/.gitignore deleted file mode 100644 index ac4d8a4..0000000 --- a/tools/Crupest.SecretTool/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.vs -bin -obj -*.pubxml.user -*.csproj.user - -publish diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool.sln b/tools/Crupest.SecretTool/Crupest.SecretTool.sln deleted file mode 100644 index fde4347..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool.sln +++ /dev/null @@ -1,30 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34024.191 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4C2CE80-CDF8-4B08-8912-D1F0F14196AD}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crupest.SecretTool", "Crupest.SecretTool\Crupest.SecretTool.csproj", "{D6335AE4-FD22-49CD-9624-37371F3B4F82}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6335AE4-FD22-49CD-9624-37371F3B4F82}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B1E8FD9C-9157-4F4E-8265-4B37F30EEC5E} - EndGlobalSection -EndGlobal diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore b/tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore deleted file mode 100644 index c936492..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/.gitignore +++ /dev/null @@ -1 +0,0 @@ -vmess.txt diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs deleted file mode 100644 index ff58551..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Config.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace Crupest.SecretTool; - -public record ConfigItem(string Value, int LineNumber); - -public class DictionaryConfig(string configString, List? requiredKeys = null) -{ - private static Dictionary Parse(string configString, List? requiredKeys = null) - { - var config = new Dictionary(); - var lines = configString.Split('\n'); - int lineNumber = 1; - - foreach (var line in lines) - { - var l = line; - var beginOfComment = l.IndexOf('#'); - if (beginOfComment >= 0) - { - l = line[..beginOfComment]; - } - l = l.Trim(); - if (!string.IsNullOrEmpty(l)) - { - var equalIndex = l.IndexOf('='); - if (equalIndex == -1) - { - throw new FormatException($"No '=' found in line {lineNumber}."); - } - - config.Add(l[..equalIndex].Trim(), new ConfigItem(l[(equalIndex + 1)..].Trim(), lineNumber)); - } - - lineNumber++; - } - - if (requiredKeys is not null) - { - foreach (var key in requiredKeys) - { - if (!config.ContainsKey(key)) - { - throw new FormatException($"Required key '{key}' not found in config."); - } - } - } - - return config; - } - - public string ConfigString { get; } = configString; - public List? RequiredKeys { get; } = requiredKeys; - public Dictionary Config { get; } = Parse(configString); - public ConfigItem GetItemCaseInsensitive(string key) - { - foreach (var (originalKey, value) in Config) - { - if (string.Equals(originalKey, key, StringComparison.OrdinalIgnoreCase)) - { - return value; - } - } - throw new KeyNotFoundException($"Key '{key}' not found in config case-insensitively."); - } -} - -public class ListConfig(string configString) -{ - private static List Parse(string configString) - { - var config = new List(); - var lines = configString.Split('\n'); - int lineNumber = 1; - - foreach (var line in lines) - { - var l = line; - var beginOfComment = l.IndexOf('#'); - if (beginOfComment >= 0) - { - l = line[..beginOfComment]; - } - l = l.Trim(); - if (!string.IsNullOrEmpty(l)) - { - config.Add(new ConfigItem(l, lineNumber)); - } - lineNumber++; - } - - return config; - } - - public string ConfigString { get; } = configString; - public List Config { get; } = Parse(configString); -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs deleted file mode 100644 index 0803b01..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Controller.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Diagnostics; - -namespace Crupest.SecretTool; - -public class Controller(string executablePath, string configPath, string? assetPath) -{ - public const string ToolAssetEnvironmentVariableName = "v2ray.location.asset"; - - public static string? FindExecutable(string contentDir, out bool isLocal, string? executableName = null) - { - isLocal = false; - executableName ??= "v2ray"; - - if (OperatingSystem.IsWindows()) - { - executableName += ".exe"; - } - - var localToolPath = Path.Combine(contentDir, executableName); - if (File.Exists(localToolPath)) - { - isLocal = true; - return localToolPath; - } - - var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator); - if (paths is not null) - { - foreach (var p in paths) - { - var toolPath = Path.Combine(p, executableName); - if (File.Exists(toolPath)) - { - return toolPath; - } - } - } - - return null; - } - - public string ExecutablePath { get; } = executablePath; - public string ConfigPath { get; } = configPath; - public string? AssetPath { get; } = assetPath; - public Process? CurrentProcess { get; private set; } - - private Process CreateProcess() - { - var process = new Process(); - - var startInfo = new ProcessStartInfo - { - FileName = ExecutablePath, - }; - startInfo.ArgumentList.Add("run"); - startInfo.ArgumentList.Add("-c"); - startInfo.ArgumentList.Add(ConfigPath); - if (AssetPath is not null) - { - startInfo.EnvironmentVariables[ToolAssetEnvironmentVariableName] = AssetPath; - } - - process.StartInfo = startInfo; - process.OutputDataReceived += (_, args) => - { - Console.Out.Write(args.Data); - }; - process.ErrorDataReceived += (_, args) => - { - Console.Error.WriteLine(args.Data); - }; - - return process; - } - - public void Stop() - { - if (CurrentProcess is not null) - { - CurrentProcess.Kill(); - CurrentProcess.Dispose(); - CurrentProcess = null; - Console.WriteLine("V2ray stopped."); - } - } - - public void Start(bool stopOld = false) - { - if (stopOld) Stop(); - - if (CurrentProcess is null) - { - CurrentProcess = CreateProcess(); - CurrentProcess.EnableRaisingEvents = true; - CurrentProcess.Exited += (_, _) => - { - if (CurrentProcess.ExitCode != 0) - { - const string message = "V2ray exited with error."; - Console.Error.WriteLine(message); - throw new Exception(message); - } - }; - CurrentProcess.Start(); - Console.WriteLine("V2ray started."); - } - } - - public void Restart() - { - Start(true); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj b/tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj deleted file mode 100644 index 2502e74..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Crupest.SecretTool.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs deleted file mode 100644 index 26e9231..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/FileWatcher.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Crupest.SecretTool; - -public class FileWatcher(string directory, List fileNames) -{ - public string Directory { get; set; } = directory; - public List FileNames { get; set; } = fileNames; - - public delegate void OnChangedHandler(); - public event OnChangedHandler? OnChanged; - - public void Run() - { - var sourceWatcher = new FileSystemWatcher(Directory); - foreach (var fileName in FileNames) - { - sourceWatcher.Filters.Add(fileName); - } - sourceWatcher.NotifyFilter = NotifyFilters.LastWrite; - - while (true) - { - var result = sourceWatcher.WaitForChanged(WatcherChangeTypes.Changed | WatcherChangeTypes.Created); - OnChanged?.Invoke(); - } - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs deleted file mode 100644 index 8f4c171..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/GeoDataManager.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System.IO.Compression; - -namespace Crupest.SecretTool; - -public interface IGeoSiteEntry -{ - bool IsInclude { get; } - string Value { get; } -} - -public record GeoSiteIncludeEntry(string Value, string ContainingSite) : IGeoSiteEntry -{ - public bool IsInclude => true; -} - -public record GeoSiteRuleEntry(HostMatchKind Kind, string Value, List Attributes, string ContainingSite) : IGeoSiteEntry -{ - public bool IsInclude => false; - - public RoutingRuleMatcher GetRoutingRuleMatcher() => new(Kind, Value); -} - -public record GeoSite(string Name, List Entries) -{ - public static GeoSite Parse(string name, string str) - { - List entries = []; - var listConfig = new ListConfig(str); - foreach (var item in listConfig.Config) - { - var (value, line) = item; - - if (value.StartsWith("include:")) - { - var include = value["include:".Length..].Trim(); - if (include.Length == 0 || include.Contains(' ')) - { - throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid include value."); - } - entries.Add(new GeoSiteIncludeEntry(include, name)); - continue; - } - - var segments = value.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - if (segments.Length > 2) - { - throw new FormatException($"Invalid geo site rule '{name}' in line {line}. More than one ':'."); - } - - HostMatchKind kind; - if (segments.Length == 2) - { - kind = segments[0] switch - { - "domain" => kind = HostMatchKind.DomainSuffix, - "full" => kind = HostMatchKind.DomainFull, - "keyword" => kind = HostMatchKind.DomainKeyword, - "regexp" => kind = HostMatchKind.DomainRegex, - _ => throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Unknown matcher.") - }; - } - else - { - kind = HostMatchKind.DomainSuffix; - } - - var domainSegments = segments[^1].Split('@', StringSplitOptions.TrimEntries); - var domain = domainSegments[0]; - if (kind != HostMatchKind.DomainRegex && Uri.CheckHostName(domain) != UriHostNameType.Dns) - { - throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Invalid domain."); - } - - List attributes = []; - foreach (var s in domainSegments[1..]) - { - if (s.Length == 0) - { - throw new FormatException($"Invalid geo site rule '{name}' in line {line}. Empty attribute value."); - } - attributes.Add(s); - } - - entries.Add(new GeoSiteRuleEntry(kind, domain, attributes, name)); - } - return new GeoSite(name, entries); - } -} - -public class GeoSiteData(string directory) -{ - private static List Parse(string directory) - { - var sites = new List(); - foreach (var path in Directory.GetFileSystemEntries(directory)) - { - var content = File.ReadAllText(path); - sites.Add(GeoSite.Parse(Path.GetFileName(path), content)); - } - return sites; - } - - public string DataDirectory { get; } = directory; - - public List Sites { get; } = Parse(directory); - - public GeoSite? GetSite(string name) - { - return Sites.Where(s => s.Name == name).FirstOrDefault(); - } - - public List GetEntriesRecursive(List sites, - List? onlyMatcherKinds = null, List? onlyAttributes = null) - { - List entries = []; - HashSet visited = []; - HashSet? kinds = onlyMatcherKinds?.ToHashSet(); - - void Visit(string site) - { - if (visited.Contains(site)) - { - return; - } - - visited.Add(site); - var siteData = GetSite(site); - if (siteData == null) - { - return; - } - foreach (var entry in siteData.Entries) - { - if (entry is GeoSiteIncludeEntry includeEntry) - { - Visit(includeEntry.Value); - } - else if (entry is GeoSiteRuleEntry geoSiteRuleEntry) - { - if (kinds != null && !kinds.Contains(geoSiteRuleEntry.Kind)) - { - continue; - } - - if (onlyAttributes != null && !geoSiteRuleEntry.Attributes.Intersect(onlyAttributes).Any()) - { - continue; - } - - entries.Add(geoSiteRuleEntry); - } - } - } - - foreach (var s in sites) - { - Visit(s); - } - - return entries; - } -} - -public class GeoDataManager -{ - public const string GeoSiteFileName = "geosite.dat"; - public const string GeoIpFileName = "geoip.dat"; - public const string GeoIpCnFileName = "geoip-only-cn-private.dat"; - - public static class ToolGithub - { - public const string Organization = "v2fly"; - public const string GeoSiteRepository = "domain-list-community"; - public const string GeoIpRepository = "geoip"; - public const string GeoSiteReleaseFilename = "dlc.dat"; - public const string GeoIpReleaseFilename = "geoip.dat"; - public const string GeoIpCnReleaseFilename = "geoip-only-cn-private.dat"; - } - - public static GeoDataManager Instance { get; } = new GeoDataManager(); - - public record GeoDataAsset(string Name, string FileName, string GithubUser, string GithubRepo, string GithubReleaseFileName); - - public GeoDataManager() - { - Assets = - [ - new("geosite", GeoSiteFileName, ToolGithub.Organization, ToolGithub.GeoSiteRepository, ToolGithub.GeoSiteReleaseFilename), - new("geoip", GeoIpFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpReleaseFilename), - new("geoip-cn", GeoIpCnFileName, ToolGithub.Organization, ToolGithub.GeoIpRepository, ToolGithub.GeoIpCnReleaseFilename), - ]; - } - - public List Assets { get; set; } - - public GeoSiteData? GeoSiteData { get; set; } - - public GeoSiteData GetOrCreateGeoSiteData(bool clean, bool silent) - { - if (GeoSiteData is not null) { return GeoSiteData; } - GeoSiteData = DownloadAndGenerateGeoSiteData(clean, silent); - return GeoSiteData; - } - - private static string GetReleaseFileUrl(string user, string repo, string fileName) - { - return $"https://github.com/{user}/{repo}/releases/latest/download/{fileName}"; - } - - private static void GithubDownloadRelease(HttpClient httpClient, string user, string repo, string fileName, string outputPath, bool silent) - { - var url = GetReleaseFileUrl(user, repo, fileName); - if (!silent) Console.WriteLine($"Downloading {url} to {outputPath}"); - using var responseStream = httpClient.GetStreamAsync(url).Result; - using var outputFileStream = File.OpenWrite(outputPath); - responseStream.CopyTo(outputFileStream); - } - - public bool HasAllAssets(string directory, out List missing) - { - missing = []; - foreach (var asset in Assets) - { - var assetPath = Path.Combine(directory, asset.FileName); - if (!File.Exists(assetPath)) - { - missing.Add(asset.Name); - } - } - return missing.Count == 0; - } - - public void Download(string outputDir, bool silent) - { - using var httpClient = new HttpClient(); - - foreach (var asset in Assets) - { - if (!silent) - { - Console.WriteLine($"Downloading {asset.Name}..."); - } - GithubDownloadRelease(httpClient, asset.GithubUser, asset.GithubRepo, asset.GithubReleaseFileName, Path.Combine(outputDir, asset.FileName), silent); - if (!silent) - { - Console.WriteLine($"Downloaded {asset.Name}!"); - } - } - - if (!File.Exists(Program.RestartLabelFilePath)) - { - File.Create(Program.RestartLabelFilePath); - } - else - { - File.SetLastWriteTime(Program.RestartLabelFilePath, DateTime.Now); - } - } - - private static string GetGithubRepositoryArchiveUrl(string user, string repo) - { - return $"https://github.com/{user}/{repo}/archive/refs/heads/master.zip"; - } - - private static void GithubDownloadRepository(HttpClient httpClient, string user, string repo, string outputPath, bool silent) - { - var url = GetGithubRepositoryArchiveUrl(user, repo); - if (!silent) { Console.WriteLine($"Begin to download data from {url} to {outputPath}."); } - using var responseStream = httpClient.GetStreamAsync(url).Result; - using var outputFileStream = File.OpenWrite(outputPath); - responseStream.CopyTo(outputFileStream); - if (!silent) { Console.WriteLine("Succeeded to download."); } - } - - private static void Unzip(string zipPath, string outputPath) - { - using var zip = ZipFile.OpenRead(zipPath) ?? throw new Exception($"Failed to open zip file {zipPath}"); - zip.ExtractToDirectory(outputPath); - } - - private static string DownloadAndExtractGeoDataRepository(bool cleanTempDirIfFailed, bool silent, out string tempDirectoryPath) - { - tempDirectoryPath = ""; - const string zipFileName = "v2ray-geosite-master.zip"; - using var httpClient = new HttpClient(); - var tempDirectory = Directory.CreateTempSubdirectory(Program.Name); - tempDirectoryPath = tempDirectory.FullName; - try - { - var archivePath = Path.Combine(tempDirectoryPath, zipFileName); - var extractPath = Path.Combine(tempDirectoryPath, "repo"); - GithubDownloadRepository(httpClient, ToolGithub.Organization, ToolGithub.GeoSiteRepository, archivePath, silent); - if (!silent) { Console.WriteLine($"Extract geo data to {extractPath}."); } - Directory.CreateDirectory(extractPath); - Unzip(archivePath, extractPath); - if (!silent) { Console.WriteLine($"Extraction done."); } - return Path.Join(extractPath, "domain-list-community-master"); - } - catch (Exception) - { - if (cleanTempDirIfFailed) - { - Directory.Delete(tempDirectoryPath, true); - } - throw; - } - } - - private static GeoSiteData DownloadAndGenerateGeoSiteData(bool clean, bool silent) - { - var repoDirectory = DownloadAndExtractGeoDataRepository(clean, silent, out var tempDirectoryPath); - try - { - return new GeoSiteData(Path.Join(repoDirectory, "data")); - } - finally - { - if (clean) - { - Directory.Delete(tempDirectoryPath, true); - } - } - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs deleted file mode 100644 index 858333d..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/HostMatchConfig.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace Crupest.SecretTool; - -public enum HostMatchKind -{ - DomainFull, - DomainSuffix, - DomainKeyword, - DomainRegex, - Ip, - GeoSite, - GeoIp, -} - -public static class HostMatchKindExtensions -{ - public static bool IsDomain(this HostMatchKind kind) - { - return kind.IsNonRegexDomain() || kind == HostMatchKind.DomainRegex; - } - - public static bool IsNonRegexDomain(this HostMatchKind kind) - { - return kind is HostMatchKind.DomainFull or HostMatchKind.DomainSuffix or HostMatchKind.DomainKeyword; - } - - - public static List DomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword, HostMatchKind.DomainRegex]; - - public static List NonRegexDomainMatchKinds { get; } = [HostMatchKind.DomainFull, HostMatchKind.DomainSuffix, HostMatchKind.DomainKeyword]; - - public static List SupportedInSingRouteMatchKinds { get; } = [..DomainMatchKinds, HostMatchKind.Ip]; - - public static bool IsSupportedInSingRoute(this HostMatchKind kind) => SupportedInSingRouteMatchKinds.Contains(kind); -} - -public record HostMatchConfigItem(HostMatchKind Kind, string MatchString, List Values); - -public class HostMatchConfig(string configString, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) -{ - private static List Parse(string configString, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) - { - var items = new ListConfig(configString).Config; - var result = new List(); - - foreach (var item in items) - { - var lineNumber = item.LineNumber; - var line = item.Value; - var hasExplicitMatchKind = false; - var segments = line.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(); - - foreach (var matchKind in Enum.GetValues()) - { - var matchKindName = Enum.GetName(matchKind) ?? throw new Exception("No such match kind."); - if (segments[0] == matchKindName) - { - hasExplicitMatchKind = true; - - if (segments.Count < 2) - { - throw new FormatException($"Explicit match item needs a value in line {lineNumber}."); - } - if (allowedMatchKinds.Contains(matchKind)) - { - if (matchKind.IsNonRegexDomain() && Uri.CheckHostName(matchKindName) != UriHostNameType.Dns) - { - throw new FormatException($"Invalid domain format in line {lineNumber}."); - } - - var components = segments[2..].ToList(); - if (minComponentCount > 0 && components.Count < minComponentCount) - { - throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required."); - } - if (maxComponentCount >= 0 && components.Count > maxComponentCount) - { - throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed."); - } - result.Add(new HostMatchConfigItem(matchKind, segments[1], components)); - } - else - { - throw new FormatException($"Match kind {matchKindName} is not allowed at line {lineNumber}."); - } - } - } - - if (!hasExplicitMatchKind) - { - if (minComponentCount > 0 && segments.Count - 1 < minComponentCount) - { - throw new FormatException($"Too few components in line {lineNumber}, at least {minComponentCount} required."); - } - if (maxComponentCount >= 0 && segments.Count - 1 > maxComponentCount) - { - throw new FormatException($"Too many components in line {lineNumber}, only {maxComponentCount} allowed."); - } - result.Add(new HostMatchConfigItem(HostMatchKind.DomainSuffix, segments[0], segments.Count == 1 ? [] : segments[1..])); - } - } - return result; - } - - public string ConfigString { get; } = configString; - public List AllowedMatchKinds { get; } = allowedMatchKinds; - public int MinComponentCount { get; } = minComponentCount; - public int MaxComponentCount { get; } = maxComponentCount; - public List Items { get; } = Parse(configString, allowedMatchKinds, minComponentCount, maxComponentCount); -} - -public class HostMatchConfigFile -{ - public HostMatchConfigFile(string path, List allowedMatchKinds, int minComponentCount = -1, int maxComponentCount = -1) - { - Path = path; - FileContent = File.ReadAllText(path); - Config = new HostMatchConfig(FileContent, allowedMatchKinds, minComponentCount, maxComponentCount); ; - } - - public string Path { get; } - public string FileContent { get; } - public HostMatchConfig Config { get; } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs deleted file mode 100644 index 18b1ac0..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Program.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Reflection; - -namespace Crupest.SecretTool; - -public static class Program -{ - public static string Name { get; } = typeof(Program).Namespace ?? throw new Exception("Can't get the name of Crupest.SecretTool."); - - public static string CrupestSecretToolDirectory { get; } = - Environment.GetEnvironmentVariable("CRUPEST_V2RAY_DIR") ?? - Path.GetFullPath(Path.GetDirectoryName( - Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Can't get the path of Crupest.SecretTool.")); - - private const string ConfigOutputFileName = "config.json"; - private const string SurgeRuleSetChinaOutputFileName = "ChinaRuleSet.txt"; - private const string SurgeRuleSetGlobalOutputFileName = "GlobalRuleSet.txt"; - - public const string RestartLabelFileName = "restart.label"; - public static string RestartLabelFilePath { get; } = Path.Combine(CrupestSecretToolDirectory, RestartLabelFileName); - - public static void RunToolAndWatchConfigChange() - { - var executablePath = Controller.FindExecutable(CrupestSecretToolDirectory, out var isLocal) ?? - throw new Exception("Can't find v2ray executable either in Crupest.SecretTool directory or in PATH."); - - string? assetsPath; - if (isLocal) - { - assetsPath = CrupestSecretToolDirectory; - var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing); - if (!assetsComplete) - { - throw new Exception($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is local. So only use assets in Crupest.SecretTool directory."); - } - } - else - { - assetsPath = CrupestSecretToolDirectory; - var assetsComplete = GeoDataManager.Instance.HasAllAssets(CrupestSecretToolDirectory, out var missing); - if (!assetsComplete) - { - Console.WriteLine($"Missing assets: {string.Join(", ", missing)} in {CrupestSecretToolDirectory}. This v2ray is global. So fallback to its own assets."); - assetsPath = null; - } - } - - var controller = new Controller(executablePath, Path.Combine(CrupestSecretToolDirectory, ConfigOutputFileName), assetsPath); - var configFileWatcher = new FileWatcher(CrupestSecretToolDirectory, - [.. ToolConfig.ConfigFileNames, RestartLabelFileName]); - - ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName)); - controller.Start(); - - configFileWatcher.OnChanged += () => - { - ToolConfig.FromDirectoryAndWriteToFile(CrupestSecretToolDirectory, Path.Join(CrupestSecretToolDirectory, ConfigOutputFileName)); - controller.Restart(); - }; - - configFileWatcher.Run(); - } - - public static void Main(string[] args) - { - if (args.Length != 0) - { - var verb = args[0].ToLower(); - if (verb == "download-geodata" || verb == "dg") - { - if (args.Length != 1) - { - throw new Exception("Invalid command line arguments. download-geodata requires no arguments."); - } - GeoDataManager.Instance.Download(CrupestSecretToolDirectory, false); - return; - } - else if (verb == "generate-surge-rule-set" || verb == "gsr") - { - if (args.Length != 1) - { - throw new Exception("Invalid command line arguments. download-geodata requires no arguments."); - } - SurgeConfigGenerator.GenerateTo( - CrupestSecretToolDirectory, - Path.Join(CrupestSecretToolDirectory, SurgeRuleSetChinaOutputFileName), - Path.Join(CrupestSecretToolDirectory, SurgeRuleSetGlobalOutputFileName), - true, true - ); - return; - } - else if (verb == "generate-sing-config" || verb == "gs") - { - if (args.Length != 2 || args[1].ToLower() is not ("pc" or "mobile")) - { - throw new Exception("Invalid command line arguments. generate-sing-config requires 1 argument. The argument must be either 'pc' or 'mobile'."); - } - - var config = SingToolConfig.FromDirectory(CrupestSecretToolDirectory, args[1].ToLower() == "mobile", true, true); - Console.Out.WriteLine(config.ToSingConfigString()); - return; - } - else if (verb == "generate" || verb == "g") - { - var config = ToolConfig.FromDirectory(CrupestSecretToolDirectory); - Console.Out.WriteLine(config.ToJsonStringV4()); - return; - } - throw new Exception("Invalid command line arguments."); - } - - RunToolAndWatchConfigChange(); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml b/tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index 5fca454..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Release - Any CPU - bin\Release\net8.0\publish\ - FileSystem - <_TargetId>Folder - - \ No newline at end of file diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs deleted file mode 100644 index d2703ba..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Proxy.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace Crupest.SecretTool; - -public abstract class Proxy(string tag) : IV4ConfigObject, ISingConfigObject -{ - public string Tag { get; set; } = tag; - - public abstract V4ConfigJsonObjects.Outbound ToJsonObjectV4(); - public abstract SingConfigJsonObjects.OutboundBase ToJsonObjectSing(); - - object IV4ConfigObject.ToJsonObjectV4() - { - return ToJsonObjectV4(); - } - - object ISingConfigObject.ToJsonObjectSing() - { - return ToJsonObjectSing(); - } -} - -public class HttpProxy(string host, int port, string tag) : Proxy(tag) -{ - public string Host { get; set; } = host; - public int Port { get; set; } = port; - - public override SingConfigJsonObjects.OutboundBase ToJsonObjectSing() - { - throw new NotImplementedException("Http proxy is not supported in sing now."); - } - - public override V4ConfigJsonObjects.Outbound ToJsonObjectV4() - { - return new V4ConfigJsonObjects.Outbound(Tag, "http", - new V4ConfigJsonObjects.HttpOutboundSettings([new V4ConfigJsonObjects.HttpOutboundServer(Host, Port, [])]), - null - ); - } -} - - -public class VmessProxy(string host, int port, string userId, string path, string tag) : Proxy(tag) -{ - public string Host { get; set; } = host; - public int Port { get; set; } = port; - public string Path { get; set; } = path; - public string UserId { get; set; } = userId; - - public override SingConfigJsonObjects.OutboundBase ToJsonObjectSing() - { - return new SingConfigJsonObjects.VmessOutbound(Tag, Host, Port, UserId, - Transport: new SingConfigJsonObjects.V2rayWebsocketTransport(Path, new Dictionary { { "Host", Host } }), - Tls: new SingConfigJsonObjects.OutboundTls(true)); - } - - public override V4ConfigJsonObjects.Outbound ToJsonObjectV4() - { - return new V4ConfigJsonObjects.Outbound(Tag, "vmess", - new V4ConfigJsonObjects.VmessOutboundSettings( - [new V4ConfigJsonObjects.VnextServer(Host, Port, [new V4ConfigJsonObjects.VnextServerUser(UserId, 0, "auto", 0)])]), - new V4ConfigJsonObjects.WsStreamSettings("ws", "tls", new V4ConfigJsonObjects.WsSettings(Path, new() { ["Host"] = Host })) - ); - } - - public static VmessProxy CreateFromConfigString(string configString, string tag) - { - var config = new DictionaryConfig(configString, ["host", "port", "userid", "path"]); - var portString = config.GetItemCaseInsensitive("port").Value; - if (!int.TryParse(portString, out var port) || port <= 0) - { - throw new FormatException($"Invalid port number: {portString}: not an integer or is a invalid number."); - } - return new VmessProxy(config.GetItemCaseInsensitive("host").Value, port, - config.GetItemCaseInsensitive("userid").Value, config.GetItemCaseInsensitive("path").Value, tag - ); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs deleted file mode 100644 index 81698a3..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/ProxyFile.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Crupest.SecretTool; - -public class ProxyFile : HostMatchConfigFile -{ - public ProxyFile(string path) : base(path, [.. Enum.GetValues()], maxComponentCount: 0) - { - RoutingRuleMatchers = Config.Items.Select(i => new RoutingRuleMatcher(i.Kind, i.MatchString)).ToList(); - } - - public List RoutingRuleMatchers { get; } - - public List GetChinaRulesByGeoSite(GeoSiteData geoSiteData) - { - var geoSites = RoutingRuleMatchers.Where(m => m.MatchKind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList(); - return geoSiteData.GetEntriesRecursive(geoSites, HostMatchKindExtensions.DomainMatchKinds, ["cn"]).Select(e => e.GetRoutingRuleMatcher()).ToList(); - } - - public List GetRulesFlattenGeoSite(GeoSiteData geoSiteData, bool noCn = false) - { - var geoSites = RoutingRuleMatchers.Where(m => m.MatchKind == HostMatchKind.GeoSite).Select(i => i.MatchString).ToList(); - var flattenGeoSiteRules = geoSiteData.GetEntriesRecursive(geoSites, HostMatchKindExtensions.DomainMatchKinds) - .Where(e => !noCn || !e.Attributes.Contains("cn")) - .Select(e => e.GetRoutingRuleMatcher()) - .ToList(); - var otherRules = RoutingRuleMatchers.Where(m => m.MatchKind != HostMatchKind.GeoSite).ToList(); - return [ - ..flattenGeoSiteRules, - ..otherRules - ]; - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs deleted file mode 100644 index fdf1b93..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Routing.cs +++ /dev/null @@ -1,155 +0,0 @@ -namespace Crupest.SecretTool; - -public record RoutingRuleMatcher(HostMatchKind MatchKind, string MatchString) -{ - public RoutingRule ToRoutingRule(string OutboundTag) => new(MatchKind, MatchString, OutboundTag); -} - -public record RoutingRule(HostMatchKind MatchKind, string MatchString, string OutboundTag) : IV4ConfigObject -{ - public string ToolConfigString => MatchKind switch - { - HostMatchKind.DomainFull => $"full:{MatchString}", - HostMatchKind.DomainSuffix => $"domain:{MatchString}", - HostMatchKind.DomainKeyword => MatchString, - HostMatchKind.DomainRegex => $"regexp:{MatchString}", - HostMatchKind.Ip => MatchString, - HostMatchKind.GeoSite => $"geosite:{MatchString}", - HostMatchKind.GeoIp => $"geoip:{MatchString}", - _ => throw new ArgumentException("Invalid matcher kind.") - }; - - public string ToolConfigStringSing => MatchKind.IsSupportedInSingRoute() ? MatchString : throw new ArgumentException("Unsupported matcher kind for sing."); - - public static Dictionary> GroupByOutboundTag(List rules) - => rules.GroupBy(r => r.OutboundTag).Select(g => (g.Key, g.ToList())).ToDictionary(); - - public static Dictionary> GroupByMatchKind(List rules) - => rules.GroupBy(r => r.MatchKind).Select(g => (g.Key, g.ToList())).ToDictionary(); - - public static List> GroupByOutboundTagAndMatcherKind(List rules) - => GroupByOutboundTag(rules).Values.SelectMany((groupByTag) => GroupByMatchKind(groupByTag).Values).ToList(); - - public static SingConfigJsonObjects.RouteRule ListToJsonObjectSing(List rules) - { - if (rules.Count == 0) - { - throw new ArgumentException("Rule list is empty."); - } - - var outboundTag = rules[0].OutboundTag; - - if (rules.Any(r => !r.MatchKind.IsSupportedInSingRoute())) - { - throw new ArgumentException("Rules must have matcher kinds supported in sing."); - } - - if (rules.Any(r => r.OutboundTag != outboundTag)) - { - throw new ArgumentException("Rules must have the same outbound tag."); - } - - return new SingConfigJsonObjects.RouteRule(Outbound: outboundTag, - Domain: rules.Where(r => r.MatchKind == HostMatchKind.DomainFull).Select(r => r.ToolConfigStringSing).ToList(), - DomainSuffix: rules.Where(r => r.MatchKind == HostMatchKind.DomainSuffix).Select(r => r.ToolConfigStringSing).ToList(), - DomainKeyword: rules.Where(r => r.MatchKind == HostMatchKind.DomainKeyword).Select(r => r.ToolConfigStringSing).ToList(), - DomainRegex: rules.Where(r => r.MatchKind == HostMatchKind.DomainRegex).Select(r => r.ToolConfigStringSing).ToList(), - IpCidr: rules.Where(r => r.MatchKind == HostMatchKind.Ip).Select(r => r.ToolConfigStringSing).ToList() - ); - } - - public static V4ConfigJsonObjects.RoutingRule ListToJsonObject(List rules) - { - if (rules.Count == 0) - { - throw new ArgumentException("Rule list is empty."); - } - - var matchKind = rules[0].MatchKind; - var outboundTag = rules[0].OutboundTag; - - if (rules.Any(r => r.OutboundTag != outboundTag) || rules.Any(r => r.MatchKind != matchKind)) - { - throw new ArgumentException("Rules must have the same matcher kind and outbound tag."); - } - - List toolConfigList = rules.Select(r => r.ToolConfigString).ToList(); - - return new V4ConfigJsonObjects.RoutingRule(OutboundTag: outboundTag, - Ip: (matchKind is HostMatchKind.Ip or HostMatchKind.GeoIp) ? toolConfigList : null, - Domains: (matchKind.IsDomain() || matchKind == HostMatchKind.GeoSite) ? toolConfigList : null - ); - } - - public RoutingRule CloneGeositeWithCnAttribute(string outboundTag) - { - if (MatchKind is not HostMatchKind.GeoSite) - { - throw new ArgumentException("Matcher kind must be GeoSite."); - } - - return new RoutingRule(HostMatchKind.GeoSite, $"{MatchString}@cn", outboundTag); - } - - public RoutingRuleMatcher GetMatcher() => new(MatchKind, MatchString); - - public V4ConfigJsonObjects.RoutingRule ToJsonObjectV4() => ListToJsonObject([this]); - - object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4(); -} - -public record Routing(List Rules) : IV4ConfigObject, ISingConfigObject -{ - public List CreateGeositeCnDirectRules() - { - return Rules.Where(r => r.MatchKind is HostMatchKind.GeoSite) - .Select(r => r.CloneGeositeWithCnAttribute("direct")).ToList(); - } - - public SingConfigJsonObjects.Route ToJsonObjectSing() - { - List ruleJsonObjects = [ new SingConfigJsonObjects.RouteRule(Outbound: "dns-out", Protocol: "dns")]; - ruleJsonObjects.AddRange(RoutingRule.GroupByOutboundTag(Rules).Values.Select(RoutingRule.ListToJsonObjectSing)); - return new SingConfigJsonObjects.Route(ruleJsonObjects); - } - - public V4ConfigJsonObjects.Routing ToJsonObjectV4(string domainStrategy = "IpOnDemand", bool directGeositeCn = true) - { - List ruleJsonObjects = []; - - if (directGeositeCn) - { - ruleJsonObjects.Add(RoutingRule.ListToJsonObject(CreateGeositeCnDirectRules())); - } - - ruleJsonObjects.AddRange(RoutingRule.GroupByOutboundTagAndMatcherKind(Rules).Select(RoutingRule.ListToJsonObject)); - - return new V4ConfigJsonObjects.Routing(ruleJsonObjects, domainStrategy); - } - - object IV4ConfigObject.ToJsonObjectV4() => ToJsonObjectV4(); - - object ISingConfigObject.ToJsonObjectSing() => ToJsonObjectSing(); - - public static Routing FromProxyFile(ProxyFile proxyFile, string outboundTag) - { - return new Routing( - proxyFile.RoutingRuleMatchers.Select(m => m.ToRoutingRule(outboundTag)).ToList()); - } - - public static Routing FromProxyFileForSing(ProxyFile proxyFile, GeoSiteData geoSiteData, string outboundTag, string? directCnOutboundTag = null) - { - List rules = []; - - if (directCnOutboundTag is not null) - { - rules.AddRange(proxyFile.GetChinaRulesByGeoSite(geoSiteData).Select(m => m.ToRoutingRule(directCnOutboundTag)).ToList()); - } - - rules.AddRange(proxyFile.GetRulesFlattenGeoSite(geoSiteData).Where(m => m.MatchKind.IsSupportedInSingRoute()).Select(m => m.ToRoutingRule(outboundTag)).ToList()); - - return new Routing( - rules - ); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs deleted file mode 100644 index 56b5563..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/SingConfigJsonObjects.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Crupest.SecretTool; - -public static class SingConfigJsonObjects -{ - public interface IObject; - - public record OutboundTls(bool Enabled); - public record V2rayTransportBase(string Type); - public record V2rayWebsocketTransport(string Path, Dictionary? Headers = null) : V2rayTransportBase("ws"); - public record OutboundBase(string Tag, string Type) : IObject; - public record VmessOutbound(string Tag, string Server, int ServerPort, string Uuid, string Security = "auto", - V2rayTransportBase? Transport = null, OutboundTls? Tls = null): OutboundBase(Tag, "vmess"); - - public record RouteRule(List? Domain = null, List? DomainSuffix = null, List? DomainKeyword = null, - List? DomainRegex = null, List? IpCidr = null, List? SourceIpCidr = null, string? Protocol = null, - List? Port = null, List? SourcePort = null, List? PortRange = null, List? SourcePortRange = null, - string? Network = null, List? Inbound = null, string? Outbound = null) : IObject; - - public record Route(List Rules) : IObject; -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs deleted file mode 100644 index b112e1c..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/StaticHosts.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Crupest.SecretTool; - -public record StaticHostRule(HostMatchKind MatchKind, string MatchString, List ResolveResult) -{ - public string AddressString() - { - return MatchKind switch - { - HostMatchKind.DomainFull => MatchString, - HostMatchKind.DomainSuffix => $"domain:{MatchString}", - HostMatchKind.DomainKeyword => $"keyword:{MatchString}", - HostMatchKind.DomainRegex => $"regexp:{MatchString}", - _ => throw new ArgumentOutOfRangeException($"Match kind {MatchKind} is not allowed in static host rule."), - }; - } - - public object ResolveResultToJsonObject() - { - return ResolveResult.Count == 1 ? ResolveResult[0] : ResolveResult; - } -} - -public class StaticHosts(List rules) : IV4ConfigObject -{ - public List Rules { get; } = rules; - - public Dictionary ToJsonObjectV4() => - Rules.ToDictionary(rule => rule.AddressString(), rule => rule.ResolveResultToJsonObject()); - - object IV4ConfigObject.ToJsonObjectV4() - { - return ToJsonObjectV4(); - } - - public static StaticHosts CreateFromHostMatchConfigString(string configString) - { - var config = new HostMatchConfig(configString, HostMatchKindExtensions.DomainMatchKinds, minComponentCount: 1); - return new StaticHosts(config.Items.Select(i => new StaticHostRule(i.Kind, i.MatchString, [.. i.Values])).ToList()); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs deleted file mode 100644 index 8a57c9f..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/SurgeConfigGenerator.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace Crupest.SecretTool; - -public class SurgeConfigGenerator(ProxyFile proxyFile, GeoSiteData geoData) -{ - public ProxyFile ProxyFile => proxyFile; - public GeoSiteData GeoData => geoData; - - private static string ToSurgeRuleString(HostMatchKind kind, string value) - { - var ruleType = kind switch - { - HostMatchKind.DomainFull => "DOMAIN", - HostMatchKind.DomainSuffix => "DOMAIN-SUFFIX", - HostMatchKind.DomainKeyword => "DOMAIN-KEYWORD", - HostMatchKind.DomainRegex => "URL-REGEX", - _ => throw new Exception("Unacceptable matcher kind for Surge rule.") - }; - - return $"{ruleType},{value}"; - } - - public static string GenerateSurgeRuleSetString(List rules) - { - return string.Join('\n', rules.Select(r => ToSurgeRuleString(r.MatchKind, r.MatchString))); - } - - public string GenerateChinaRuleSet() - { - return GenerateSurgeRuleSetString(proxyFile.GetChinaRulesByGeoSite(GeoData)); - } - - public string GenerateGlobalRuleSet() - { - return GenerateSurgeRuleSetString(proxyFile.GetRulesFlattenGeoSite(geoData, true)); - } - - public static void GenerateTo(ProxyFile proxyFile, GeoSiteData geoSiteData, string cnPath, string globalPath, bool silent) - { - var generator = new SurgeConfigGenerator(proxyFile, geoSiteData); - File.WriteAllText(cnPath, generator.GenerateChinaRuleSet()); - if (!silent) Console.WriteLine($"China rule set written to {cnPath}."); - File.WriteAllText(globalPath, generator.GenerateGlobalRuleSet()); - if (!silent) Console.WriteLine($"Global rule set written to {globalPath}."); - } - - public static void GenerateTo(string directory, string cnPath, string globalPath, bool clean, bool silent) - { - var geoSiteData = GeoDataManager.Instance.GetOrCreateGeoSiteData(clean, silent); - var proxyFile = new ProxyFile(Path.Combine(directory, ToolConfig.ProxyConfigFileName)); - var generator = new SurgeConfigGenerator(proxyFile, geoSiteData); - File.WriteAllText(cnPath, generator.GenerateChinaRuleSet()); - if (!silent) Console.WriteLine($"China rule set written to {cnPath}."); - File.WriteAllText(globalPath, generator.GenerateGlobalRuleSet()); - if (!silent) Console.WriteLine($"Global rule set written to {globalPath}."); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs deleted file mode 100644 index 1fe91b1..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/Template.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text; - -namespace Crupest.SecretTool; - -public class Template -{ - private enum ParseState - { - Text, - Dollar, - LeftBracket, - VariableName, - VariableNameFinish, - } - - private interface ITemplateNode - { - string Render(Dictionary values); - } - - private class TextNode(string text) : ITemplateNode - { - - public string Text { get; } = text; - - public string Render(Dictionary values) - { - return Text; - } - } - - private class VariableNode(string variableName) : ITemplateNode - { - public string VariableName { get; } = variableName; - - public string Render(Dictionary values) - { - return values.GetValueOrDefault(VariableName) ?? ""; - } - } - - public Template(string templateString) - { - TemplateString = templateString; - Nodes = Parse(templateString); - VariableNames = Nodes.OfType().Select(node => node.VariableName).ToList(); - } - - private static List Parse(string templateString) - { - int lineNumber = 1; - int columnNumber = 0; - List nodes = []; - ParseState state = ParseState.Text; - StringBuilder stringBuilder = new(); - - string GetPosition() => $"line {lineNumber} column{columnNumber}"; - - [DoesNotReturn] - void ReportInvalidState(string message) - { - throw new Exception($"Invalid state at {GetPosition()}: {message}"); - } - - [DoesNotReturn] - void ReportInvalidCharacter(char c) - { - throw new FormatException($"Unexpected '{c}' at {GetPosition()}."); - } - - void FinishText() - { - if (state != ParseState.Text) - { - ReportInvalidState($"Can't call FinishText here."); - } - - if (stringBuilder.Length > 0) - { - nodes.Add(new TextNode(stringBuilder.ToString())); - stringBuilder.Clear(); - } - } - - foreach (var c in templateString) - { - if (c == '\n') - { - lineNumber++; - columnNumber = 0; - } - - columnNumber++; - - switch (c) - { - case '$': - if (state == ParseState.Text) - { - FinishText(); - state = ParseState.Dollar; - } - else if (state == ParseState.Dollar) - { - if (stringBuilder.Length > 0) - { - throw new Exception($"Invalid state at {GetPosition()}: when we meet the second '$', text builder should be empty."); - } - stringBuilder.Append(c); - state = ParseState.Text; - } - else - { - throw new FormatException($"Unexpected '$' at {GetPosition()}."); - } - break; - case '{': - if (state == ParseState.Text) - { - stringBuilder.Append(c); - } - else if (state == ParseState.Dollar) - { - state = ParseState.LeftBracket; - } - else - { - throw new Exception($"Unexpected '{{' at {GetPosition()}."); - } - break; - case '}': - if (state == ParseState.Text) - { - stringBuilder.Append(c); - state = ParseState.Text; - } - else if (state == ParseState.VariableName || state == ParseState.VariableNameFinish) - { - nodes.Add(new VariableNode(stringBuilder.ToString())); - stringBuilder.Clear(); - state = ParseState.Text; - } - else - { - ReportInvalidCharacter(c); - } - break; - default: - if (state == ParseState.Dollar) - { - ReportInvalidCharacter(c); - } - - if (char.IsWhiteSpace(c)) - { - if (state == ParseState.LeftBracket || state == ParseState.VariableNameFinish) - { - continue; - } - else if (state == ParseState.Text) - { - stringBuilder.Append(c); - } - else if (state == ParseState.VariableName) - { - state = ParseState.VariableNameFinish; - } - else - { - ReportInvalidCharacter(c); - } - } - else - { - if (state == ParseState.Text) - { - stringBuilder.Append(c); - } - else if (state == ParseState.LeftBracket || state == ParseState.VariableName) - { - stringBuilder.Append(c); - state = ParseState.VariableName; - } - else - { - ReportInvalidCharacter(c); - } - } - break; - } - } - - if (state == ParseState.Text) - { - FinishText(); - } - else - { - throw new FormatException("Unexpected end of template string."); - } - - return nodes; - } - - public string TemplateString { get; } - private List Nodes { get; set; } - public List VariableNames { get; } - - public string Generate(Dictionary values, bool allowMissingVariable = false) - { - StringBuilder stringBuilder = new(); - foreach (var node in Nodes) - { - if (node is TextNode textNode) - { - stringBuilder.Append(textNode.Text); - } - else if (node is VariableNode variableNode) - { - var hasValue = values.TryGetValue(variableNode.VariableName, out var value); - if (!hasValue && !allowMissingVariable) - { - throw new Exception($"Variable '{variableNode.VariableName}' is not set."); - } - stringBuilder.Append(hasValue ? value : string.Empty); - } - } - return stringBuilder.ToString(); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs deleted file mode 100644 index 809fba1..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/ToolConfig.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Crupest.SecretTool; - -public interface IV4ConfigObject -{ - object ToJsonObjectV4(); -} - -public interface ISingConfigObject -{ - object ToJsonObjectSing(); -} - -public class ToolConfigBase(Template template, List proxies, Routing router) -{ - protected class JsonInterfaceConverter : JsonConverter - { - public override Interface Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write( - Utf8JsonWriter writer, - Interface value, - JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, typeof(object), options); - } - } - - public const string VmessConfigFileName = "vmess.txt"; - public const string ProxyConfigFileName = "proxy.txt"; - - public Template Template { get; set; } = template; - public List Proxies { get; set; } = proxies; - public Routing Routing { get; set; } = router; -} - -public class ToolConfig(Template template, List proxies, Routing router, StaticHosts? hosts) : ToolConfigBase(template, proxies, router) -{ - public const string ConfigTemplateFileName = "config.json.template"; - public const string HostsConfigFileName = "hosts.txt"; - - public static List RequiredConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName]; - public static List ConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName, HostsConfigFileName]; - - private const string ProxyAnchor = "PROXY_ANCHOR"; - private const string RoutingAnchor = "ROUTING_ANCHOR"; - private const string HostsAnchor = "HOSTS_ANCHOR"; - - public const string AddCnAttributeToGeositeEnvironmentVariable = "CRUPEST_V2RAY_GEOSITE_USE_CN"; - - private static bool UseCnGeoSite => Environment.GetEnvironmentVariable(AddCnAttributeToGeositeEnvironmentVariable) switch - { - "0" or "false" or "off" or "disable" => false, - _ => true - }; - - public StaticHosts Hosts { get; set; } = hosts is null ? new StaticHosts([]) : hosts; - - public string ToJsonStringV4(string domainStrategy = "IpOnDemand", bool directGeositeCn = true, bool pretty = true) - { - var jsonOptions = new JsonSerializerOptions(new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }); - // TODO: Make interface converter generic. - jsonOptions.Converters.Add(new JsonInterfaceConverter()); - jsonOptions.Converters.Add(new JsonInterfaceConverter()); - - var templateValues = new Dictionary - { - [ProxyAnchor] = string.Join(',', Proxies.Select(p => JsonSerializer.Serialize(p.ToJsonObjectV4(), jsonOptions))), - [RoutingAnchor] = JsonSerializer.Serialize(Routing.ToJsonObjectV4(domainStrategy, directGeositeCn), jsonOptions), - [HostsAnchor] = JsonSerializer.Serialize(Hosts.ToJsonObjectV4(), jsonOptions), - }; - - var configString = Template.Generate(templateValues); - - if (pretty) - { - var jsonOptionsPretty = new JsonSerializerOptions(jsonOptions) - { - WriteIndented = true, - }; - return JsonSerializer.Serialize(JsonSerializer.Deserialize(configString, jsonOptionsPretty), jsonOptionsPretty); - } - else - { - return configString; - } - } - - public static ToolConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string? hostsPath) - { - foreach (var path in new List([templatePath, vmessPath, proxyPath])) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException($"Required config file not found: {path}."); - } - } - - ProxyFile proxyFile = new(proxyPath); - string templateString, vmessString; - string? hostsString; - - string file = ""; - try - { - file = templatePath; - templateString = File.ReadAllText(templatePath); - file = vmessPath; - vmessString = File.ReadAllText(vmessPath); - hostsString = hostsPath is not null ? File.ReadAllText(hostsPath) : null; - } - catch (Exception e) - { - throw new Exception($"Error reading config file {file}.", e); - } - - try - { - file = templatePath; - var template = new Template(templateString); - file = vmessPath; - var vmess = VmessProxy.CreateFromConfigString(vmessString, "proxy"); - file = proxyPath; - var routing = Routing.FromProxyFile(proxyFile, "proxy"); - file = hostsPath ?? ""; - var hosts = hostsString is not null ? StaticHosts.CreateFromHostMatchConfigString(hostsString) : null; - return new ToolConfig(template, [vmess], routing, hosts); - } - catch (Exception e) - { - throw new Exception($"Error parsing config file {file}.", e); - } - } - - public static ToolConfig FromDirectory(string directory) - { - return FromFiles( - Path.Join(directory, ConfigTemplateFileName), - Path.Join(directory, VmessConfigFileName), - Path.Join(directory, ProxyConfigFileName), - Path.Join(directory, HostsConfigFileName) - ); - } - - public static void FromDirectoryAndWriteToFile(string directory, string outputPath) - { - var config = FromDirectory(directory); - File.WriteAllText(outputPath, config.ToJsonStringV4()); - } -} - -public class SingToolConfig(Template template, List proxies, Routing router, string inboundsString) : ToolConfigBase(template, proxies, router) -{ - - public const string ConfigTemplateFileName = "sing-config.json.template"; - public const string ConfigInboundsPcFileName = "sing-inbounds-pc.json"; - public const string ConfigInboundsMobileFileName = "sing-inbounds-mobile.json"; - - public static List RequiredConfigFileNames { get; } = [ConfigTemplateFileName, VmessConfigFileName, ProxyConfigFileName, ConfigInboundsMobileFileName, ConfigInboundsPcFileName]; - - private const string ProxyAnchor = "PROXY_ANCHOR"; - private const string RouteAnchor = "ROUTE_ANCHOR"; - private const string InboundsAnchor = "INBOUNDS_ANCHOR"; - - public string InboundsString { get; } = inboundsString; - - public string ToSingConfigString(bool pretty = true) - { - var jsonOptions = new JsonSerializerOptions(new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }); - // TODO: Make interface converter generic. - jsonOptions.Converters.Add(new JsonInterfaceConverter()); - jsonOptions.Converters.Add(new JsonInterfaceConverter()); - - var templateValues = new Dictionary - { - [ProxyAnchor] = string.Join(',', Proxies.Select(p => JsonSerializer.Serialize(p.ToJsonObjectSing(), jsonOptions))), - [RouteAnchor] = JsonSerializer.Serialize(Routing.ToJsonObjectSing(), jsonOptions), - [InboundsAnchor] = InboundsString - }; - - var configString = Template.Generate(templateValues); - - if (pretty) - { - var jsonOptionsPretty = new JsonSerializerOptions(jsonOptions) - { - WriteIndented = true, - }; - return JsonSerializer.Serialize(JsonSerializer.Deserialize(configString, jsonOptionsPretty), jsonOptionsPretty); - } - else - { - return configString; - } - } - - public static SingToolConfig FromFiles(string templatePath, string vmessPath, string proxyPath, string inboundsPath, bool clean, bool silent) - { - foreach (var path in new List([templatePath, vmessPath, proxyPath, inboundsPath])) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException($"Required config file not found: {path}."); - } - } - - var geoSiteData = GeoDataManager.Instance.GetOrCreateGeoSiteData(clean, silent); - - ProxyFile proxyFile = new(proxyPath); - string templateString, vmessString, inboundsString; - - string file = ""; - try - { - file = templatePath; - templateString = File.ReadAllText(templatePath); - file = vmessPath; - vmessString = File.ReadAllText(vmessPath); - file = inboundsPath; - inboundsString = File.ReadAllText(inboundsPath); - } - catch (Exception e) - { - throw new Exception($"Error reading config file {file}.", e); - } - - try - { - file = templatePath; - var template = new Template(templateString); - file = vmessPath; - var vmess = VmessProxy.CreateFromConfigString(vmessString, "proxy-out"); - file = proxyPath; - var routing = Routing.FromProxyFileForSing(proxyFile, geoSiteData, "proxy-out", "direct-out"); - return new SingToolConfig(template, [vmess], routing, inboundsString); - } - catch (Exception e) - { - throw new Exception($"Error parsing config file {file}.", e); - } - } - - public static SingToolConfig FromDirectory(string directory, bool isMobile, bool clean, bool silent) - { - return FromFiles( - Path.Join(directory, ConfigTemplateFileName), - Path.Join(directory, VmessConfigFileName), - Path.Join(directory, ProxyConfigFileName), - isMobile ? Path.Join(directory, ConfigInboundsMobileFileName) : Path.Join(directory, ConfigInboundsPcFileName), - clean, silent - ); - } -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs deleted file mode 100644 index 3e81dbb..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/V4ConfigJsonObjects.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Crupest.SecretTool; - -public static class V4ConfigJsonObjects -{ - public interface IObject; - public interface IOutboundSettings : IObject; - public interface IOutboundStreamSettings : IObject; - - public record WsSettings(string Path, Dictionary Headers) : IObject; - public record WsStreamSettings(string Network, string Security, WsSettings WsSettings) : IOutboundStreamSettings; - public record VnextServerUser(string Id, int AlterId, string Security, int Level) : IObject; - public record VnextServer(string Address, int Port, List Users) : IObject; - public record VmessOutboundSettings(List Vnext) : IOutboundSettings; - public record HttpOutboundUser(string User, string Pass) : IObject; - public record HttpOutboundServer(string Address, int Port, List Users) : IObject; - public record HttpOutboundSettings(List Servers) : IOutboundSettings; - public record Outbound(string Tag, string Protocol, IOutboundSettings Settings, - IOutboundStreamSettings? StreamSettings) : IObject; - - public record RoutingRule(string DomainMatcher = "mph", string Type = "field", List? Domains = null, List? Ip = null, - string? Port = null, string? SourcePort = null, string? Network = null, List? Source = null, - List? User = null, List? InboundTag = null, List? Protocol = null, string? Attrs = null, - string? OutboundTag = null, string? BalancerTag = null) : IObject; - public record Routing(List Rules, string DomainStrategy = "IpOnDemand", string DomainMatcher = "mph") : IObject; -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs b/tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs deleted file mode 100644 index a50e9be..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/V5ConfigJsonObjects.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Crupest.SecretTool; - -public static class V5ConfigJsonObjects -{ - public record OutboundObject(string Protocol, object Settings, string Tag, object? StreamSettings) - { - public static OutboundObject VmessViaWs(string tag, string address, int port, string uuid, string path) - { - return new OutboundObject("vmess", new VmessSettings(address, port, uuid), tag, StreamSettingsObject.Ws(path)); - } - - public static OutboundObject Http(string tag, string address, int port) - { - return new OutboundObject("http", new HttpSettingsObject(address, port), tag, null); - } - } - - public record WsSettingsObject(string Path, Dictionary Headers); - - public record StreamSettingsObject(string Transport, object TransportSettings, string Security, object SecuritySettings) - { - public static StreamSettingsObject Ws(string path) - { - return new StreamSettingsObject("ws", new WsSettingsObject(path, new()), "tls", new()); - } - } - - public record VmessSettings(string Address, int Port, string Uuid); - - public record HttpSettingsObject(string Address, int Port); -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template b/tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template deleted file mode 100644 index 424e996..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/config.json.template +++ /dev/null @@ -1,63 +0,0 @@ -{ - "log": { - "loglevel": "warning" - }, - "inbounds": [ - { - "port": 2081, - "listen": "127.0.0.1", - "tag": "socks-inbound", - "protocol": "socks", - "settings": { - "auth": "noauth" - } - }, - { - "port": 2080, - "listen": "127.0.0.1", - "tag": "http-inbound", - "protocol": "http", - "settings": { - "auth": "noauth" - } - } - ], - "outbounds": [ - { - "protocol": "freedom", - "settings": {}, - "tag": "direct" - }, - { - "protocol": "blackhole", - "settings": {}, - "tag": "blocked" - }, - ${PROXY_ANCHOR} - ], - "routing": ${ROUTING_ANCHOR}, - "dns": { - "hosts": ${HOSTS_ANCHOR}, - "servers": [ - "https://doh.pub/dns-query", - "1.1.1.1", - "8.8.8.8", - "localhost" - ] - }, - "policy": { - "levels": { - "0": { - "uplinkOnly": 0, - "downlinkOnly": 0 - } - }, - "system": { - "statsInboundUplink": false, - "statsInboundDownlink": false, - "statsOutboundUplink": false, - "statsOutboundDownlink": false - } - }, - "other": {} -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template b/tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template deleted file mode 100644 index 01ccf7a..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/config.v5.json.template +++ /dev/null @@ -1,55 +0,0 @@ -{ - "log": { - "access": { - "type": "Console", - "level": "Info" - } - }, - "dns": { - "nameServer": [{ - "address": "https://doh.pub/dns-query" - }, { - "address": "1.1.1.1" - }, { - "address": "8.8.8.8" - }, { - "address": "localhost" - }], - "staticHosts": ${HOSTS_ANCHOR} - }, - "inbounds": [{ - { - "protocol": "socks", - "port": 2081, - "listen": "127.0.0.1", - "tag": "socks-inbound", - "settings": { - "auth": "noauth" - } - }, - { - "protocol": "http", - "port": 2080, - "listen": "127.0.0.1", - "tag": "http-inbound", - "settings": { - "auth": "noauth" - } - } - }], - "outbounds": [ - { - "protocol": "freedom", - "settings": {}, - "tag": "direct" - }, - { - "protocol": "blackhole", - "settings": {}, - "tag": "blocked" - }, - ${PROXY_ANCHOR} - ], - "router": ${ROUTER_ANCHOR} -} - diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt b/tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt deleted file mode 100644 index 88d5015..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/hosts.txt +++ /dev/null @@ -1,2 +0,0 @@ -cdn.jsdelivr.net cdn.jsdelivr.net.cdn.cloudflare.net - diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt b/tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt deleted file mode 100644 index 39800f9..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/proxy.txt +++ /dev/null @@ -1,50 +0,0 @@ -GeoSite microsoft -GeoSite google -GeoSite youtube -GeoSite x -GeoSite facebook -GeoSite discord -GeoSite reddit -GeoSite twitch -GeoSite quora -GeoSite telegram -GeoSite imgur -GeoSite stackexchange -GeoSite medium - -GeoSite duckduckgo -GeoSite wikimedia -GeoSite gitbook -GeoSite github -GeoSite gitlab -GeoSite sourceforge -GeoSite creativecommons -GeoSite archive -GeoSite matrix -GeoSite tor - -GeoSite python -GeoSite ruby -GeoSite rust -GeoSite nodejs -GeoSite npmjs -GeoSite qt -GeoSite docker -GeoSite v2ray -GeoSite homebrew - -GeoSite azure -GeoSite akamai -GeoSite aws -GeoSite jsdelivr -GeoSite fastly -GeoSite heroku -GeoSite bootstrap -GeoSite vercel - -GeoSite ieee -GeoSite sci-hub -GeoSite libgen -GeoSite z-library - -sagernet.org diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template b/tools/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template deleted file mode 100644 index d7e55a0..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-config.json.template +++ /dev/null @@ -1,45 +0,0 @@ -{ - "log": { - "disabled": false, - "level": "info", - "timestamp": true - }, - "dns": { - "servers": [ - { - "tag": "ali-doh", - "address": "https://dns.alidns.com/dns-query", - "address_resolver": "ali" - }, - { - "tag": "ali", - "address": "223.5.5.5" - }, - { - "tag": "cloudflare", - "address": "1.1.1.1" - }, - { - "tag": "google", - "address": "8.8.8.8" - } - ] - }, - "inbounds": ${INBOUNDS_ANCHOR}, - "outbounds": [ - { - "type": "direct", - "tag": "direct-out" - }, - { - "type": "block", - "tag": "block-out" - }, - { - "tag": "dns-out", - "type": "dns" - }, - ${PROXY_ANCHOR} - ], - "route": ${ROUTE_ANCHOR} -} diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json b/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json deleted file mode 100644 index 5038c40..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-mobile.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "tag": "tun-in", - "type": "tun", - "auto_route": true, - "strict_route": true, - "address": [ "172.23.0.1/30", "fdfe:acbd:9876::1/126"], - "sniff": true, - "sniff_override_destination": true - } -] diff --git a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json b/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json deleted file mode 100644 index 956d751..0000000 --- a/tools/Crupest.SecretTool/Crupest.SecretTool/sing-inbounds-pc.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "tag": "http-in", - "type": "http", - "listen": "127.0.0.1", - "listen_port": 3080 - }, - { - "tag": "socks-in", - "type": "socks", - "listen": "127.0.0.1", - "listen_port": 3081 - } -] \ No newline at end of file diff --git a/tools/Crupest.SecretTool/build-secret.bash b/tools/Crupest.SecretTool/build-secret.bash deleted file mode 100755 index 8878049..0000000 --- a/tools/Crupest.SecretTool/build-secret.bash +++ /dev/null @@ -1,41 +0,0 @@ -#! /usr/bin/env bash - -set -e - -function print_argument_error_message_and_exit() { - argument_error_message="You must specify exactly one argument, the build target (win-x64 | linux-x64 | osx-x64)." - echo "$argument_error_message" - exit 1 -} - - - -if [[ $# != 1 ]]; then - print_argument_error_message_and_exit -fi - -case "$1" in - win-x64 | linux-x64 | osx-x64) - echo "Build target: $1" - ;; - *) - print_argument_error_message_and_exit - ;; -esac - -secret_dir=$(realpath "$(dirname "$0")") - -echo "Secret dir: ${secret_dir}" - -echo "Check dotnet..." -dotnet --version - -echo "Enter \"secret\" dir..." -pushd "$secret_dir" - -echo "Begin to build..." -dotnet publish Crupest.SecretTool -c Release -o "$secret_dir/publish" --sc -r "$1" - -popd - -echo "Finish!" diff --git a/tools/Crupest.SecretTool/build-secret.ps1 b/tools/Crupest.SecretTool/build-secret.ps1 deleted file mode 100644 index 8aa7987..0000000 --- a/tools/Crupest.SecretTool/build-secret.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -if ($args.Count -ne 1 || $args[0] -notmatch "^win-x64|linux-x64|osx-x64$") -{ - Write-Error "You must specify exactly one argument, the build target (win-x64 | linux-x64 | osx-x64)." - exit 1 -} - -Write-Output "Secret dir: $PSScriptRoot" - -Write-Output "Check dotnet..." -dotnet --version -if ($LASTEXITCODE -ne 0) -{ - Write-Error "dotnet not found." - exit 2 -} - -Write-Output "Enter `"secret`" dir..." -Push-Location $PSScriptRoot - -Write-Output "Begin to build..." -dotnet publish Crupest.SecretTool -c Release -o "$secret_dir/publish" --sc -r $args[0] - -Pop-Location - -Write-Host "Finish!" -ForegroundColor Green diff --git a/tools/Crupest.SecretTool/tools/cru-proxy-edit b/tools/Crupest.SecretTool/tools/cru-proxy-edit deleted file mode 100755 index 51a33e1..0000000 --- a/tools/Crupest.SecretTool/tools/cru-proxy-edit +++ /dev/null @@ -1,12 +0,0 @@ -#! /usr/bin/env bash - -set -e - -p="$HOME/codes/crupest/tools/Crupest.SecretTool/publish/proxy.txt" - -if [[ ! -f "$p" ]]; then - echo "File $p does not exist!" >&2 - exit 1 -fi - -exec vim "$p" diff --git a/tools/Crupest.SecretTool/tools/cru-proxy-log b/tools/Crupest.SecretTool/tools/cru-proxy-log deleted file mode 100755 index 6ec6ee1..0000000 --- a/tools/Crupest.SecretTool/tools/cru-proxy-log +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/env bash - -set -e - -if [[ -e /proc ]]; then - # I don't believe your system is Linux but there is no /proc. - exec journalctl --user -u crupest-secret-tool "$@" -elif [[ "$(uname)" == "Darwin" ]]; then - exec less "$HOME/.local/state/Crupest.SecretTool/log" -else - echo "Not supported on systems other than macOS and Linux now." >&2 - exit 1 -fi diff --git a/tools/Crupest.SecretTool/tools/crupest-secret-tool.service b/tools/Crupest.SecretTool/tools/crupest-secret-tool.service deleted file mode 100644 index df6d172..0000000 --- a/tools/Crupest.SecretTool/tools/crupest-secret-tool.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=crupest v2ray service - -[Service] -ExecStart=%h/.local/bin/Crupest.SecretTool - -[Install] -WantedBy=default.target diff --git a/tools/Crupest.SecretTool/tools/crupest-secret-tool.xml b/tools/Crupest.SecretTool/tools/crupest-secret-tool.xml deleted file mode 100644 index 9b85f13..0000000 --- a/tools/Crupest.SecretTool/tools/crupest-secret-tool.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - crupest-secret-tool - Crupest Secret Tool - Crupest Secret Tool (powered by WinSW) - - - %BASE%\Crupest.SecretTool.exe - - - - - - %BASE% - - Automatic - \ No newline at end of file diff --git a/tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist b/tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist deleted file mode 100644 index bdfe490..0000000 --- a/tools/Crupest.SecretTool/tools/life.crupest.secret-tool.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Label - life.crupest.secret-tool - ProgramArguments - - /Users/crupest/.local/bin/Crupest.SecretTool - - KeepAlive - - StandardOutPath - /Users/crupest/.local/state/Crupest.SecretTool/log - StandardErrorPath - /Users/crupest/.local/state/Crupest.SecretTool/error - - diff --git a/tools/scripts/neovide-listen b/tools/scripts/neovide-listen deleted file mode 100755 index 2591842..0000000 --- a/tools/scripts/neovide-listen +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -if [[ -z "$NVIM_SOCKET" ]]; then - NVIM_SOCKET="/tmp/nvimsocket" -fi - -args=() -MY_NEOVIM_PATH="$HOME/codes/neovim/build/bin/nvim" -if [[ -e "$MY_NEOVIM_PATH" ]]; then - echo "Found my neovim at $MY_NEOVIM_PATH" - export VIMRUNTIME="$HOME/codes/neovim/runtime" - args=("${args[@]}" "--neovim-bin" "$MY_NEOVIM_PATH") -fi - -listen_added=0 -for arg in "$@"; do - args=("${args[@]}" "$arg") - if [ "$arg" = '--' ]; then - args=("${args[@]}" "--listen" "$NVIM_SOCKET") - listen_added=1 - fi -done - -if [[ $listen_added = 0 ]]; then - args=("${args[@]}" "--" "--listen" "$NVIM_SOCKET") -fi - -NEOVIDE_BIN=neovide -MY_NEOVIDE_PATH="$HOME/codes/neovide/target/release/neovide" -if [ -e "$MY_NEOVIDE_PATH" ]; then - echo "Found my neovide at $MY_NEOVIDE_PATH" - NEOVIDE_BIN="$MY_NEOVIDE_PATH" -fi - -if which nvr > /dev/null; then - echo "Detected nvr, set git editor env" - export GIT_EDITOR='nvr -cc split --remote-wait' -fi - -args=("$NEOVIDE_BIN" "${args[@]}") -echo "Command is ${args[@]}" -exec "${args[@]}" - diff --git a/tools/scripts/neovide-listen.ps1 b/tools/scripts/neovide-listen.ps1 deleted file mode 100644 index e84f3a2..0000000 --- a/tools/scripts/neovide-listen.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -$env:NVIM_LISTEN_ADDRESS ??= "\\.\pipe\nvimsocket" - -$neovide_args = @() - -$MY_NEOVIM_PATH="$HOME/codes/neovim/build/bin/nvim.exe" -if (Get-Item $MY_NEOVIM_PATH -ErrorAction Ignore) { - Write-Output "Found my neovim at $MY_NEOVIM_PATH." - $env:VIMRUNTIME="$HOME/codes/neovim/runtime" - $neovide_args += "--neovim-bin", "$MY_NEOVIM_PATH" -} - -$listen_added = $false -foreach ($arg in $args) { - $neovide_args += $arg - if ( $arg -eq '--') { - $neovide_args += "--listen", $env:NVIM_LISTEN_ADDRESS - $listen_added=$true - } -} - -if (-not $listen_added) { - $neovide_args += "--", "--listen", $env:NVIM_LISTEN_ADDRESS -} - -$neovide_bin = "neovide" -$my_neovide_path = "$HOME/codes/neovide/target/release/neovide.exe" -if (Get-Item $my_neovide_path -ErrorAction Ignore) { - Write-Output "Found my neovide at $my_neovide_path." - $neovide_bin = "$my_neovide_path" -} - -if (Get-Command nvr -ErrorAction Ignore) { - Write-Output "Detected nvr, set git editor env." - $env:GIT_EDITOR = "nvr -cc split --remote-wait" -} - -Write-Output "Command is $($neovide_args -join ' ')." -Start-Process $neovide_bin -ArgumentList $neovide_args -Wait diff --git a/tools/utility/rename-tree.py b/tools/utility/rename-tree.py deleted file mode 100755 index c177eb6..0000000 --- a/tools/utility/rename-tree.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import os.path -import re - -parser = argparse.ArgumentParser( - prog='rename-tree', - description='Recursively rename directories and files') - -parser.add_argument('old') -parser.add_argument('new') -parser.add_argument('dirs', nargs="+") - -args = parser.parse_args() - -old_regex = re.compile(args.old) -new = args.new - -def rename(path, isdir): - dirname = os.path.dirname(path) - filename = os.path.basename(path) - new_filename = re.sub(old_regex, new, filename) - dir_str = "/" if isdir else "" - if new_filename != filename: - os.rename(path, os.path.join(dirname, new_filename)) - print(f"{path}{dir_str} -> {new_filename}{dir_str}") - -for i, d in enumerate(args.dirs): - print(f"[{i + 1}/{len(args.dirs)}] Run for {d}:") - for dirpath, dirnames, filenames in os.walk(d, topdown=False): - for filename in filenames: - rename(os.path.join(dirpath, filename), False) - rename(dirpath, True) - -print("Done!") diff --git a/works/README.md b/works/README.md deleted file mode 100644 index b280870..0000000 --- a/works/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This directory contains some of my works that I have created. I moved them from individual repositories to here for easier management. Some works are stopped for its temporary purpose. Here are some of the descriptions of them. - -- `life`: Something I created in university time. Some for lecture tasks, some are small toys to solve some interesting problems. Maybe more will be added in future. - -- `solutions`: Answers to leetcode and acwing algorithms questions. Stopped now for a well-known reason. Maybe more will be added in future, too. - -- `teapot`: Answer to the pre-interview test for my first job. Dropped now definitely. - -- `ProxyChanger`: Written a long time ago to solve the proxy switching problem in Firefox for a also well-known problem. -- cgit v1.2.3