From 4d3db5606581aa6051c52b233159d3fae905f95e Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 2 Apr 2025 11:44:23 +0800 Subject: [PATCH] csharp_style_namespace_declarations = file_scoped --- v2rayN/AmazTool/Program.cs | 39 +- v2rayN/AmazTool/UpgradeApp.cs | 183 +- v2rayN/AmazTool/Utils.cs | 75 +- v2rayN/Directory.Build.props | 2 +- v2rayN/ServiceLib/Base/MyReactiveObject.cs | 15 +- v2rayN/ServiceLib/Common/AesUtils.cs | 181 +- v2rayN/ServiceLib/Common/DesUtils.cs | 133 +- v2rayN/ServiceLib/Common/DownloaderHelper.cs | 313 +- v2rayN/ServiceLib/Common/FileManager.cs | 395 +- v2rayN/ServiceLib/Common/HttpClientHelper.cs | 333 +- v2rayN/ServiceLib/Common/Job.cs | 5 +- v2rayN/ServiceLib/Common/JsonUtils.cs | 223 +- v2rayN/ServiceLib/Common/Logging.cs | 77 +- v2rayN/ServiceLib/Common/QRCodeHelper.cs | 167 +- v2rayN/ServiceLib/Common/SemanticVersion.cs | 321 +- v2rayN/ServiceLib/Common/SqliteHelper.cs | 161 +- v2rayN/ServiceLib/Common/StringEx.cs | 139 +- v2rayN/ServiceLib/Common/Utils.cs | 1635 ++++---- v2rayN/ServiceLib/Common/WindowsUtils.cs | 109 +- v2rayN/ServiceLib/Common/YamlUtils.cs | 125 +- v2rayN/ServiceLib/Enums/EConfigType.cs | 29 +- v2rayN/ServiceLib/Enums/ECoreType.cs | 33 +- v2rayN/ServiceLib/Enums/EGirdOrientation.cs | 15 +- v2rayN/ServiceLib/Enums/EGlobalHotkey.cs | 19 +- v2rayN/ServiceLib/Enums/EInboundProtocol.cs | 25 +- v2rayN/ServiceLib/Enums/EMove.cs | 19 +- v2rayN/ServiceLib/Enums/EMsgCommand.cs | 17 +- v2rayN/ServiceLib/Enums/EMultipleLoad.cs | 15 +- v2rayN/ServiceLib/Enums/EPresetType.cs | 15 +- v2rayN/ServiceLib/Enums/ERuleMode.cs | 17 +- v2rayN/ServiceLib/Enums/EServerColName.cs | 39 +- v2rayN/ServiceLib/Enums/ESpeedActionType.cs | 17 +- v2rayN/ServiceLib/Enums/ESysProxyType.cs | 17 +- v2rayN/ServiceLib/Enums/ETheme.cs | 23 +- v2rayN/ServiceLib/Enums/ETransport.cs | 27 +- v2rayN/ServiceLib/Enums/EViewAction.cs | 89 +- v2rayN/ServiceLib/Global.cs | 509 ++- v2rayN/ServiceLib/Handler/AppHandler.cs | 441 +- .../ServiceLib/Handler/AutoStartupHandler.cs | 367 +- v2rayN/ServiceLib/Handler/ClashApiHandler.cs | 295 +- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 3615 ++++++++--------- .../ServiceLib/Handler/CoreConfigHandler.cs | 275 +- v2rayN/ServiceLib/Handler/CoreHandler.cs | 679 ++-- v2rayN/ServiceLib/Handler/CoreInfoHandler.cs | 111 +- v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs | 425 +- v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs | 33 +- v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs | 153 +- v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs | 175 +- .../ServiceLib/Handler/Fmt/NaiveproxyFmt.cs | 33 +- .../ServiceLib/Handler/Fmt/ShadowsocksFmt.cs | 295 +- v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs | 73 +- v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs | 189 +- v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs | 75 +- v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs | 107 +- v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs | 75 +- v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs | 99 +- v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs | 219 +- v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs | 115 +- v2rayN/ServiceLib/Handler/NoticeHandler.cs | 75 +- v2rayN/ServiceLib/Handler/PacHandler.cs | 177 +- v2rayN/ServiceLib/Handler/ProfileExHandler.cs | 287 +- .../ServiceLib/Handler/StatisticsHandler.cs | 315 +- .../Handler/SysProxy/ProxySettingLinux.cs | 49 +- .../Handler/SysProxy/ProxySettingOSX.cs | 55 +- .../Handler/SysProxy/ProxySettingWindows.cs | 679 ++-- .../Handler/SysProxy/SysProxyHandler.cs | 165 +- v2rayN/ServiceLib/Handler/TaskHandler.cs | 165 +- v2rayN/ServiceLib/Handler/WebDavHandler.cs | 315 +- v2rayN/ServiceLib/Models/CheckUpdateModel.cs | 19 +- .../ServiceLib/Models/ClashConnectionModel.cs | 31 +- v2rayN/ServiceLib/Models/ClashConnections.cs | 67 +- v2rayN/ServiceLib/Models/ClashProviders.cs | 25 +- v2rayN/ServiceLib/Models/ClashProxies.cs | 41 +- v2rayN/ServiceLib/Models/ClashProxyModel.cs | 23 +- v2rayN/ServiceLib/Models/CmdItem.cs | 13 +- v2rayN/ServiceLib/Models/ComboItem.cs | 25 +- v2rayN/ServiceLib/Models/Config.cs | 101 +- v2rayN/ServiceLib/Models/ConfigItems.cs | 477 ++- v2rayN/ServiceLib/Models/CoreInfo.cs | 37 +- v2rayN/ServiceLib/Models/DNSItem.cs | 33 +- v2rayN/ServiceLib/Models/GitHubRelease.cs | 75 +- v2rayN/ServiceLib/Models/IPAPIInfo.cs | 23 +- v2rayN/ServiceLib/Models/ProfileExItem.cs | 23 +- v2rayN/ServiceLib/Models/ProfileItem.cs | 180 +- v2rayN/ServiceLib/Models/ProfileItemModel.cs | 33 +- v2rayN/ServiceLib/Models/RetResult.cs | 47 +- v2rayN/ServiceLib/Models/RoutingItem.cs | 39 +- v2rayN/ServiceLib/Models/RoutingItemModel.cs | 13 +- v2rayN/ServiceLib/Models/RoutingTemplate.cs | 15 +- v2rayN/ServiceLib/Models/RulesItem.cs | 35 +- v2rayN/ServiceLib/Models/RulesItemModel.cs | 19 +- v2rayN/ServiceLib/Models/ServerSpeedItem.cs | 31 +- v2rayN/ServiceLib/Models/ServerStatItem.cs | 27 +- v2rayN/ServiceLib/Models/ServerTestItem.cs | 21 +- v2rayN/ServiceLib/Models/SingboxConfig.cs | 509 ++- v2rayN/ServiceLib/Models/SpeedTestResult.cs | 17 +- v2rayN/ServiceLib/Models/SsSIP008.cs | 33 +- v2rayN/ServiceLib/Models/SubItem.cs | 45 +- v2rayN/ServiceLib/Models/V2rayConfig.cs | 865 ++-- v2rayN/ServiceLib/Models/V2rayMetricsVars.cs | 13 +- v2rayN/ServiceLib/Models/V2rayTcpRequest.cs | 33 +- v2rayN/ServiceLib/Models/VmessQRCode.cs | 55 +- .../CoreConfig/CoreConfigClashService.cs | 459 ++- .../CoreConfig/CoreConfigSingboxService.cs | 2677 ++++++------ .../CoreConfig/CoreConfigV2rayService.cs | 2455 ++++++----- v2rayN/ServiceLib/Services/DownloadService.cs | 575 ++- .../ServiceLib/Services/SpeedtestService.cs | 663 ++- .../Statistics/StatisticsSingboxService.cs | 185 +- .../Statistics/StatisticsXrayService.cs | 177 +- v2rayN/ServiceLib/Services/UpdateService.cs | 1043 +++-- .../ViewModels/AddServer2ViewModel.cs | 207 +- .../ViewModels/AddServerViewModel.cs | 151 +- .../ViewModels/BackupAndRestoreViewModel.cs | 329 +- .../ViewModels/CheckUpdateViewModel.cs | 551 ++- .../ViewModels/ClashConnectionsViewModel.cs | 225 +- .../ViewModels/ClashProxiesViewModel.cs | 745 ++-- .../ViewModels/DNSSettingViewModel.cs | 189 +- .../GlobalHotkeySettingViewModel.cs | 91 +- .../ViewModels/MainWindowViewModel.cs | 1129 +++-- v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 185 +- .../ViewModels/OptionSettingViewModel.cs | 743 ++-- .../ViewModels/ProfilesViewModel.cs | 1461 ++++--- .../ViewModels/RoutingRuleDetailsViewModel.cs | 155 +- .../ViewModels/RoutingRuleSettingViewModel.cs | 617 ++- .../ViewModels/RoutingSettingViewModel.cs | 321 +- .../ViewModels/StatusBarViewModel.cs | 969 +++-- .../ServiceLib/ViewModels/SubEditViewModel.cs | 87 +- .../ViewModels/SubSettingViewModel.cs | 161 +- .../Common/AppBuilderExtension.cs | 19 +- v2rayN/v2rayN.Desktop/Common/AvaUtils.cs | 87 +- v2rayN/v2rayN.Desktop/Common/UI.cs | 73 +- .../Converters/DelayColorConverter.cs | 31 +- .../v2rayN.Desktop/Handler/HotkeyHandler.cs | 137 +- .../ViewModels/ThemeSettingViewModel.cs | 257 +- .../Views/AddServer2Window.axaml.cs | 115 +- .../Views/AddServerWindow.axaml.cs | 609 ++- .../Views/BackupAndRestoreView.axaml.cs | 105 +- .../Views/CheckUpdateView.axaml.cs | 67 +- .../Views/ClashConnectionsView.axaml.cs | 89 +- .../Views/ClashProxiesView.axaml.cs | 117 +- .../Views/DNSSettingWindow.axaml.cs | 121 +- .../Views/GlobalHotkeySettingWindow.axaml.cs | 201 +- .../v2rayN.Desktop/Views/MainWindow.axaml.cs | 855 ++-- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 121 +- .../Views/OptionSettingWindow.axaml.cs | 439 +- .../Views/ProfilesView.axaml.cs | 913 +++-- .../v2rayN.Desktop/Views/QrcodeView.axaml.cs | 59 +- .../Views/RoutingRuleDetailsWindow.axaml.cs | 157 +- .../Views/RoutingRuleSettingWindow.axaml.cs | 383 +- .../Views/RoutingSettingWindow.axaml.cs | 241 +- .../Views/StatusBarView.axaml.cs | 165 +- .../Views/SubEditWindow.axaml.cs | 95 +- .../Views/SubSettingWindow.axaml.cs | 171 +- .../Views/ThemeSettingView.axaml.cs | 59 +- v2rayN/v2rayN/App.xaml.cs | 111 +- v2rayN/v2rayN/Base/MyDGTextColumn.cs | 13 +- v2rayN/v2rayN/Common/QRCodeHelper.cs | 113 +- v2rayN/v2rayN/Common/UI.cs | 93 +- v2rayN/v2rayN/Common/WindowsUtils.cs | 173 +- .../v2rayN/Converters/DelayColorConverter.cs | 37 +- .../Converters/InverseBooleanConverter.cs | 31 +- .../v2rayN/Converters/MaterialDesignFonts.cs | 35 +- v2rayN/v2rayN/Handler/HotkeyHandler.cs | 337 +- v2rayN/v2rayN/Handler/WindowsHandler.cs | 183 +- .../ViewModels/ThemeSettingViewModel.cs | 331 +- v2rayN/v2rayN/Views/AddServer2Window.xaml.cs | 97 +- v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 597 ++- .../v2rayN/Views/BackupAndRestoreView.xaml.cs | 73 +- v2rayN/v2rayN/Views/CheckUpdateView.xaml.cs | 71 +- .../v2rayN/Views/ClashConnectionsView.xaml.cs | 97 +- v2rayN/v2rayN/Views/ClashProxiesView.xaml.cs | 127 +- v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs | 125 +- .../Views/GlobalHotkeySettingWindow.xaml.cs | 205 +- v2rayN/v2rayN/Views/MainWindow.xaml.cs | 759 ++-- v2rayN/v2rayN/Views/MsgView.xaml.cs | 135 +- .../v2rayN/Views/OptionSettingWindow.xaml.cs | 465 ++- v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 861 ++-- v2rayN/v2rayN/Views/QrcodeView.xaml.cs | 15 +- .../Views/RoutingRuleDetailsWindow.xaml.cs | 151 +- .../Views/RoutingRuleSettingWindow.xaml.cs | 353 +- .../v2rayN/Views/RoutingSettingWindow.xaml.cs | 235 +- v2rayN/v2rayN/Views/StatusBarView.xaml.cs | 191 +- v2rayN/v2rayN/Views/SubEditWindow.xaml.cs | 91 +- v2rayN/v2rayN/Views/SubSettingWindow.xaml | 10 +- v2rayN/v2rayN/Views/SubSettingWindow.xaml.cs | 187 +- v2rayN/v2rayN/Views/ThemeSettingView.xaml.cs | 65 +- 186 files changed, 23574 insertions(+), 23759 deletions(-) diff --git a/v2rayN/AmazTool/Program.cs b/v2rayN/AmazTool/Program.cs index 07f151cf..d71c25c2 100644 --- a/v2rayN/AmazTool/Program.cs +++ b/v2rayN/AmazTool/Program.cs @@ -1,26 +1,25 @@ -namespace AmazTool +namespace AmazTool; + +internal static class Program { - internal static class Program + [STAThread] + private static void Main(string[] args) { - [STAThread] - private static void Main(string[] args) + if (args.Length == 0) { - if (args.Length == 0) - { - Console.WriteLine(Resx.Resource.Guidelines); - Thread.Sleep(5000); - return; - } - - var argData = Uri.UnescapeDataString(string.Join(" ", args)); - if (argData.Equals("rebootas")) - { - Thread.Sleep(1000); - Utils.StartV2RayN(); - return; - } - - UpgradeApp.Upgrade(argData); + Console.WriteLine(Resx.Resource.Guidelines); + Thread.Sleep(5000); + return; } + + var argData = Uri.UnescapeDataString(string.Join(" ", args)); + if (argData.Equals("rebootas")) + { + Thread.Sleep(1000); + Utils.StartV2RayN(); + return; + } + + UpgradeApp.Upgrade(argData); } } diff --git a/v2rayN/AmazTool/UpgradeApp.cs b/v2rayN/AmazTool/UpgradeApp.cs index e1a76c3a..a4eb288c 100644 --- a/v2rayN/AmazTool/UpgradeApp.cs +++ b/v2rayN/AmazTool/UpgradeApp.cs @@ -2,116 +2,115 @@ using System.Diagnostics; using System.IO.Compression; using System.Text; -namespace AmazTool +namespace AmazTool; + +internal class UpgradeApp { - internal class UpgradeApp + public static void Upgrade(string fileName) { - public static void Upgrade(string fileName) + Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}"); + + Utils.Waiting(5); + + if (!File.Exists(fileName)) { - Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}"); + Console.WriteLine(Resx.Resource.UpgradeFileNotFound); + return; + } - Utils.Waiting(5); - - if (!File.Exists(fileName)) + Console.WriteLine(Resx.Resource.TryTerminateProcess); + try + { + var existing = Process.GetProcessesByName(Utils.V2rayN); + foreach (var pp in existing) { - Console.WriteLine(Resx.Resource.UpgradeFileNotFound); - return; - } - - Console.WriteLine(Resx.Resource.TryTerminateProcess); - try - { - var existing = Process.GetProcessesByName(Utils.V2rayN); - foreach (var pp in existing) + var path = pp.MainModule?.FileName ?? ""; + if (path.StartsWith(Utils.GetPath(Utils.V2rayN))) { - var path = pp.MainModule?.FileName ?? ""; - if (path.StartsWith(Utils.GetPath(Utils.V2rayN))) - { - pp?.Kill(); - pp?.WaitForExit(1000); - } + pp?.Kill(); + pp?.WaitForExit(1000); } } - catch (Exception ex) - { - // Access may be denied without admin right. The user may not be an administrator. - Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace); - } + } + catch (Exception ex) + { + // Access may be denied without admin right. The user may not be an administrator. + Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace); + } - Console.WriteLine(Resx.Resource.StartUnzipping); - StringBuilder sb = new(); - try - { - var thisAppOldFile = $"{Utils.GetExePath()}.tmp"; - File.Delete(thisAppOldFile); - var splitKey = "/"; + Console.WriteLine(Resx.Resource.StartUnzipping); + StringBuilder sb = new(); + try + { + var thisAppOldFile = $"{Utils.GetExePath()}.tmp"; + File.Delete(thisAppOldFile); + var splitKey = "/"; - using var archive = ZipFile.OpenRead(fileName); - foreach (var entry in archive.Entries) + using var archive = ZipFile.OpenRead(fileName); + foreach (var entry in archive.Entries) + { + try { + if (entry.Length == 0) + { + continue; + } + + Console.WriteLine(entry.FullName); + + var lst = entry.FullName.Split(splitKey); + if (lst.Length == 1) + { + continue; + } + + var fullName = string.Join(splitKey, lst[1..lst.Length]); + + if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase)) + { + File.Move(Utils.GetExePath(), thisAppOldFile); + } + + var entryOutputPath = Utils.GetPath(fullName); + Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); + //In the bin folder, if the file already exists, it will be skipped + if (fullName.StartsWith("bin") && File.Exists(entryOutputPath)) + { + continue; + } + try { - if (entry.Length == 0) - { - continue; - } - - Console.WriteLine(entry.FullName); - - var lst = entry.FullName.Split(splitKey); - if (lst.Length == 1) - { - continue; - } - - var fullName = string.Join(splitKey, lst[1..lst.Length]); - - if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase)) - { - File.Move(Utils.GetExePath(), thisAppOldFile); - } - - var entryOutputPath = Utils.GetPath(fullName); - Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); - //In the bin folder, if the file already exists, it will be skipped - if (fullName.StartsWith("bin") && File.Exists(entryOutputPath)) - { - continue; - } - - try - { - entry.ExtractToFile(entryOutputPath, true); - } - catch - { - Thread.Sleep(1000); - entry.ExtractToFile(entryOutputPath, true); - } - - Console.WriteLine(entryOutputPath); + entry.ExtractToFile(entryOutputPath, true); } - catch (Exception ex) + catch { - sb.Append(ex.StackTrace); + Thread.Sleep(1000); + entry.ExtractToFile(entryOutputPath, true); } + + Console.WriteLine(entryOutputPath); + } + catch (Exception ex) + { + sb.Append(ex.StackTrace); } } - catch (Exception ex) - { - Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace); - //return; - } - if (sb.Length > 0) - { - Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString()); - //return; - } - - Console.WriteLine(Resx.Resource.Restartv2rayN); - Utils.Waiting(2); - - Utils.StartV2RayN(); } + catch (Exception ex) + { + Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace); + //return; + } + if (sb.Length > 0) + { + Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString()); + //return; + } + + Console.WriteLine(Resx.Resource.Restartv2rayN); + Utils.Waiting(2); + + Utils.StartV2RayN(); } } diff --git a/v2rayN/AmazTool/Utils.cs b/v2rayN/AmazTool/Utils.cs index d206f898..df13ecf6 100644 --- a/v2rayN/AmazTool/Utils.cs +++ b/v2rayN/AmazTool/Utils.cs @@ -1,52 +1,51 @@ using System.Diagnostics; -namespace AmazTool +namespace AmazTool; + +internal class Utils { - internal class Utils + public static string GetExePath() { - public static string GetExePath() - { - return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; - } + return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; + } - public static string StartupPath() - { - return AppDomain.CurrentDomain.BaseDirectory; - } + public static string StartupPath() + { + return AppDomain.CurrentDomain.BaseDirectory; + } - public static string GetPath(string fileName) + public static string GetPath(string fileName) + { + var startupPath = StartupPath(); + if (string.IsNullOrEmpty(fileName)) { - var startupPath = StartupPath(); - if (string.IsNullOrEmpty(fileName)) + return startupPath; + } + return Path.Combine(startupPath, fileName); + } + + public static string V2rayN => "v2rayN"; + + public static void StartV2RayN() + { + Process process = new() + { + StartInfo = new() { - return startupPath; + UseShellExecute = true, + FileName = V2rayN, + WorkingDirectory = StartupPath() } - return Path.Combine(startupPath, fileName); - } + }; + process.Start(); + } - public static string V2rayN => "v2rayN"; - - public static void StartV2RayN() + public static void Waiting(int second) + { + for (var i = second; i > 0; i--) { - Process process = new() - { - StartInfo = new() - { - UseShellExecute = true, - FileName = V2rayN, - WorkingDirectory = StartupPath() - } - }; - process.Start(); - } - - public static void Waiting(int second) - { - for (var i = second; i > 0; i--) - { - Console.WriteLine(i); - Thread.Sleep(1000); - } + Console.WriteLine(i); + Thread.Sleep(1000); } } } diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 4a689310..3ad760a3 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -8,7 +8,7 @@ net8.0 true true - CA1031;CS1591;NU1507;CA1416 + CA1031;CS1591;NU1507;CA1416;IDE0058 annotations enable 2dust diff --git a/v2rayN/ServiceLib/Base/MyReactiveObject.cs b/v2rayN/ServiceLib/Base/MyReactiveObject.cs index 0049429d..0172faad 100644 --- a/v2rayN/ServiceLib/Base/MyReactiveObject.cs +++ b/v2rayN/ServiceLib/Base/MyReactiveObject.cs @@ -1,10 +1,9 @@ -using ReactiveUI; +using ReactiveUI; -namespace ServiceLib.Base +namespace ServiceLib.Base; + +public class MyReactiveObject : ReactiveObject { - public class MyReactiveObject : ReactiveObject - { - protected static Config? _config; - protected Func>? _updateView; - } -} \ No newline at end of file + protected static Config? _config; + protected Func>? _updateView; +} diff --git a/v2rayN/ServiceLib/Common/AesUtils.cs b/v2rayN/ServiceLib/Common/AesUtils.cs index dcd4ac2a..c32c3bff 100644 --- a/v2rayN/ServiceLib/Common/AesUtils.cs +++ b/v2rayN/ServiceLib/Common/AesUtils.cs @@ -1,101 +1,100 @@ using System.Security.Cryptography; using System.Text; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class AesUtils { - public class AesUtils + private const int KeySize = 256; // AES-256 + private const int IvSize = 16; // AES block size + private const int Iterations = 10000; + private static readonly byte[] Salt = Encoding.ASCII.GetBytes("saltysalt".PadRight(16, ' ')); // google浏览器默认盐值 + private static readonly string DefaultPassword = Utils.GetMd5(Utils.GetHomePath() + "AesUtils"); + + /// + /// Encrypt + /// + /// Plain text + /// Password for key derivation or direct key in ASCII bytes + /// Base64 encoded cipher text with IV + public static string Encrypt(string text, string? password = null) { - private const int KeySize = 256; // AES-256 - private const int IvSize = 16; // AES block size - private const int Iterations = 10000; - private static readonly byte[] Salt = Encoding.ASCII.GetBytes("saltysalt".PadRight(16, ' ')); // google浏览器默认盐值 - private static readonly string DefaultPassword = Utils.GetMd5(Utils.GetHomePath() + "AesUtils"); + if (string.IsNullOrEmpty(text)) + return string.Empty; - /// - /// Encrypt - /// - /// Plain text - /// Password for key derivation or direct key in ASCII bytes - /// Base64 encoded cipher text with IV - public static string Encrypt(string text, string? password = null) + var plaintext = Encoding.UTF8.GetBytes(text); + var key = GetKey(password); + var iv = GenerateIv(); + + using var aes = Aes.Create(); + aes.Key = key; + aes.IV = iv; + + using var ms = new MemoryStream(); + ms.Write(iv, 0, iv.Length); + + using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)) { - if (string.IsNullOrEmpty(text)) - return string.Empty; - - var plaintext = Encoding.UTF8.GetBytes(text); - var key = GetKey(password); - var iv = GenerateIv(); - - using var aes = Aes.Create(); - aes.Key = key; - aes.IV = iv; - - using var ms = new MemoryStream(); - ms.Write(iv, 0, iv.Length); - - using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)) - { - cs.Write(plaintext, 0, plaintext.Length); - cs.FlushFinalBlock(); - } - - var cipherTextWithIv = ms.ToArray(); - return Convert.ToBase64String(cipherTextWithIv); + cs.Write(plaintext, 0, plaintext.Length); + cs.FlushFinalBlock(); } - /// - /// Decrypt - /// - /// Base64 encoded cipher text with IV - /// Password for key derivation or direct key in ASCII bytes - /// Plain text - public static string Decrypt(string cipherTextWithIv, string? password = null) - { - if (string.IsNullOrEmpty(cipherTextWithIv)) - return string.Empty; - - var cipherTextWithIvBytes = Convert.FromBase64String(cipherTextWithIv); - var key = GetKey(password); - - var iv = new byte[IvSize]; - Buffer.BlockCopy(cipherTextWithIvBytes, 0, iv, 0, IvSize); - - var cipherText = new byte[cipherTextWithIvBytes.Length - IvSize]; - Buffer.BlockCopy(cipherTextWithIvBytes, IvSize, cipherText, 0, cipherText.Length); - - using var aes = Aes.Create(); - aes.Key = key; - aes.IV = iv; - - using var ms = new MemoryStream(); - using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write)) - { - cs.Write(cipherText, 0, cipherText.Length); - cs.FlushFinalBlock(); - } - - var plainText = ms.ToArray(); - return Encoding.UTF8.GetString(plainText); - } - - private static byte[] GetKey(string? password) - { - if (password.IsNullOrEmpty()) - { - password = DefaultPassword; - } - - using var pbkdf2 = new Rfc2898DeriveBytes(password, Salt, Iterations, HashAlgorithmName.SHA256); - return pbkdf2.GetBytes(KeySize / 8); - } - - private static byte[] GenerateIv() - { - var randomNumber = new byte[IvSize]; - - using var rng = RandomNumberGenerator.Create(); - rng.GetBytes(randomNumber); - return randomNumber; - } + var cipherTextWithIv = ms.ToArray(); + return Convert.ToBase64String(cipherTextWithIv); } -} \ No newline at end of file + + /// + /// Decrypt + /// + /// Base64 encoded cipher text with IV + /// Password for key derivation or direct key in ASCII bytes + /// Plain text + public static string Decrypt(string cipherTextWithIv, string? password = null) + { + if (string.IsNullOrEmpty(cipherTextWithIv)) + return string.Empty; + + var cipherTextWithIvBytes = Convert.FromBase64String(cipherTextWithIv); + var key = GetKey(password); + + var iv = new byte[IvSize]; + Buffer.BlockCopy(cipherTextWithIvBytes, 0, iv, 0, IvSize); + + var cipherText = new byte[cipherTextWithIvBytes.Length - IvSize]; + Buffer.BlockCopy(cipherTextWithIvBytes, IvSize, cipherText, 0, cipherText.Length); + + using var aes = Aes.Create(); + aes.Key = key; + aes.IV = iv; + + using var ms = new MemoryStream(); + using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write)) + { + cs.Write(cipherText, 0, cipherText.Length); + cs.FlushFinalBlock(); + } + + var plainText = ms.ToArray(); + return Encoding.UTF8.GetString(plainText); + } + + private static byte[] GetKey(string? password) + { + if (password.IsNullOrEmpty()) + { + password = DefaultPassword; + } + + using var pbkdf2 = new Rfc2898DeriveBytes(password, Salt, Iterations, HashAlgorithmName.SHA256); + return pbkdf2.GetBytes(KeySize / 8); + } + + private static byte[] GenerateIv() + { + var randomNumber = new byte[IvSize]; + + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(randomNumber); + return randomNumber; + } +} diff --git a/v2rayN/ServiceLib/Common/DesUtils.cs b/v2rayN/ServiceLib/Common/DesUtils.cs index 132fff65..aae02206 100644 --- a/v2rayN/ServiceLib/Common/DesUtils.cs +++ b/v2rayN/ServiceLib/Common/DesUtils.cs @@ -1,75 +1,74 @@ using System.Security.Cryptography; using System.Text; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class DesUtils { - public class DesUtils + /// + /// Encrypt + /// + /// + /// /// + /// + public static string Encrypt(string? text, string? key = null) { - /// - /// Encrypt - /// - /// - /// /// - /// - public static string Encrypt(string? text, string? key = null) + if (text.IsNullOrEmpty()) { - if (text.IsNullOrEmpty()) - { - return string.Empty; - } - GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); - var dsp = DES.Create(); - using var memStream = new MemoryStream(); - using var cryStream = new CryptoStream(memStream, dsp.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write); - using var sWriter = new StreamWriter(cryStream); - sWriter.Write(text); - sWriter.Flush(); - cryStream.FlushFinalBlock(); - memStream.Flush(); - return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length); - } - - /// - /// Decrypt - /// - /// - /// - /// - public static string Decrypt(string? encryptText, string? key = null) - { - if (encryptText.IsNullOrEmpty()) - { - return string.Empty; - } - GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); - var dsp = DES.Create(); - var buffer = Convert.FromBase64String(encryptText); - - using var memStream = new MemoryStream(); - using var cryStream = new CryptoStream(memStream, dsp.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Write); - cryStream.Write(buffer, 0, buffer.Length); - cryStream.FlushFinalBlock(); - return Encoding.UTF8.GetString(memStream.ToArray()); - } - - private static void GetKeyIv(string key, out byte[] rgbKey, out byte[] rgbIv) - { - if (key.IsNullOrEmpty()) - { - throw new ArgumentNullException("The key cannot be null"); - } - if (key.Length <= 8) - { - throw new ArgumentNullException("The key length cannot be less than 8 characters."); - } - - rgbKey = Encoding.ASCII.GetBytes(key.Substring(0, 8)); - rgbIv = Encoding.ASCII.GetBytes(key.Insert(0, "w").Substring(0, 8)); - } - - private static string GetDefaultKey() - { - return Utils.GetMd5(Utils.GetHomePath() + "DesUtils"); + return string.Empty; } + GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); + var dsp = DES.Create(); + using var memStream = new MemoryStream(); + using var cryStream = new CryptoStream(memStream, dsp.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write); + using var sWriter = new StreamWriter(cryStream); + sWriter.Write(text); + sWriter.Flush(); + cryStream.FlushFinalBlock(); + memStream.Flush(); + return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length); } -} \ No newline at end of file + + /// + /// Decrypt + /// + /// + /// + /// + public static string Decrypt(string? encryptText, string? key = null) + { + if (encryptText.IsNullOrEmpty()) + { + return string.Empty; + } + GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); + var dsp = DES.Create(); + var buffer = Convert.FromBase64String(encryptText); + + using var memStream = new MemoryStream(); + using var cryStream = new CryptoStream(memStream, dsp.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Write); + cryStream.Write(buffer, 0, buffer.Length); + cryStream.FlushFinalBlock(); + return Encoding.UTF8.GetString(memStream.ToArray()); + } + + private static void GetKeyIv(string key, out byte[] rgbKey, out byte[] rgbIv) + { + if (key.IsNullOrEmpty()) + { + throw new ArgumentNullException("The key cannot be null"); + } + if (key.Length <= 8) + { + throw new ArgumentNullException("The key length cannot be less than 8 characters."); + } + + rgbKey = Encoding.ASCII.GetBytes(key.Substring(0, 8)); + rgbIv = Encoding.ASCII.GetBytes(key.Insert(0, "w").Substring(0, 8)); + } + + private static string GetDefaultKey() + { + return Utils.GetMd5(Utils.GetHomePath() + "DesUtils"); + } +} diff --git a/v2rayN/ServiceLib/Common/DownloaderHelper.cs b/v2rayN/ServiceLib/Common/DownloaderHelper.cs index 03dcf8e3..0c4cde0c 100644 --- a/v2rayN/ServiceLib/Common/DownloaderHelper.cs +++ b/v2rayN/ServiceLib/Common/DownloaderHelper.cs @@ -1,181 +1,180 @@ using System.Net; using Downloader; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class DownloaderHelper { - public class DownloaderHelper + private static readonly Lazy _instance = new(() => new()); + public static DownloaderHelper Instance => _instance.Value; + + public async Task DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent, int timeout) { - private static readonly Lazy _instance = new(() => new()); - public static DownloaderHelper Instance => _instance.Value; - - public async Task DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent, int timeout) + if (url.IsNullOrEmpty()) { - if (url.IsNullOrEmpty()) - { - return null; - } + return null; + } - Uri uri = new(url); - //Authorization Header - var headers = new WebHeaderCollection(); - if (uri.UserInfo.IsNotEmpty()) - { - headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo)); - } + Uri uri = new(url); + //Authorization Header + var headers = new WebHeaderCollection(); + if (uri.UserInfo.IsNotEmpty()) + { + headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo)); + } - var downloadOpt = new DownloadConfiguration() - { - Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, - RequestConfiguration = + var downloadOpt = new DownloadConfiguration() + { + Timeout = timeout * 1000, + MaxTryAgainOnFailover = 2, + RequestConfiguration = { Headers = headers, UserAgent = userAgent, Timeout = timeout * 1000, Proxy = webProxy } - }; + }; - await using var downloader = new Downloader.DownloadService(downloadOpt); - downloader.DownloadFileCompleted += (sender, value) => + await using var downloader = new Downloader.DownloadService(downloadOpt); + downloader.DownloadFileCompleted += (sender, value) => + { + if (value.Error != null) { - if (value.Error != null) + throw value.Error; + } + }; + + using var cts = new CancellationTokenSource(); + await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); + using StreamReader reader = new(stream); + + downloadOpt = null; + + return await reader.ReadToEndAsync(cts.Token); + } + + public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress progress, int timeout) + { + if (url.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(url)); + } + + var downloadOpt = new DownloadConfiguration() + { + Timeout = timeout * 1000, + MaxTryAgainOnFailover = 2, + RequestConfiguration = + { + Timeout= timeout * 1000, + Proxy = webProxy + } + }; + + var totalDatetime = DateTime.Now; + var totalSecond = 0; + var hasValue = false; + double maxSpeed = 0; + await using var downloader = new Downloader.DownloadService(downloadOpt); + //downloader.DownloadStarted += (sender, value) => + //{ + // if (progress != null) + // { + // progress.Report("Start download data..."); + // } + //}; + downloader.DownloadProgressChanged += (sender, value) => + { + var ts = DateTime.Now - totalDatetime; + if (progress != null && ts.Seconds > totalSecond) + { + hasValue = true; + totalSecond = ts.Seconds; + if (value.BytesPerSecondSpeed > maxSpeed) + { + maxSpeed = value.BytesPerSecondSpeed; + var speed = (maxSpeed / 1000 / 1000).ToString("#0.0"); + progress.Report(speed); + } + } + }; + downloader.DownloadFileCompleted += (sender, value) => + { + if (progress != null) + { + if (!hasValue && value.Error != null) + { + progress.Report(value.Error?.Message); + } + } + }; + //progress.Report("......"); + using var cts = new CancellationTokenSource(); + cts.CancelAfter(timeout * 1000); + await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token); + + downloadOpt = null; + } + + public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress progress, int timeout) + { + if (url.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(url)); + } + if (fileName.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(fileName)); + } + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + var downloadOpt = new DownloadConfiguration() + { + Timeout = timeout * 1000, + MaxTryAgainOnFailover = 2, + RequestConfiguration = + { + Timeout= timeout * 1000, + Proxy = webProxy + } + }; + + var progressPercentage = 0; + var hasValue = false; + await using var downloader = new Downloader.DownloadService(downloadOpt); + downloader.DownloadStarted += (sender, value) => progress?.Report(0); + downloader.DownloadProgressChanged += (sender, value) => + { + hasValue = true; + var percent = (int)value.ProgressPercentage;// Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100); + if (progressPercentage != percent && percent % 10 == 0) + { + progressPercentage = percent; + progress.Report(percent); + } + }; + downloader.DownloadFileCompleted += (sender, value) => + { + if (progress != null) + { + if (hasValue && value.Error == null) + { + progress.Report(101); + } + else if (value.Error != null) { throw value.Error; } - }; - - using var cts = new CancellationTokenSource(); - await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); - using StreamReader reader = new(stream); - - downloadOpt = null; - - return await reader.ReadToEndAsync(cts.Token); - } - - public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress progress, int timeout) - { - if (url.IsNullOrEmpty()) - { - throw new ArgumentNullException(nameof(url)); } + }; - var downloadOpt = new DownloadConfiguration() - { - Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, - RequestConfiguration = - { - Timeout= timeout * 1000, - Proxy = webProxy - } - }; + using var cts = new CancellationTokenSource(); + await downloader.DownloadFileTaskAsync(url, fileName, cts.Token); - var totalDatetime = DateTime.Now; - var totalSecond = 0; - var hasValue = false; - double maxSpeed = 0; - await using var downloader = new Downloader.DownloadService(downloadOpt); - //downloader.DownloadStarted += (sender, value) => - //{ - // if (progress != null) - // { - // progress.Report("Start download data..."); - // } - //}; - downloader.DownloadProgressChanged += (sender, value) => - { - var ts = DateTime.Now - totalDatetime; - if (progress != null && ts.Seconds > totalSecond) - { - hasValue = true; - totalSecond = ts.Seconds; - if (value.BytesPerSecondSpeed > maxSpeed) - { - maxSpeed = value.BytesPerSecondSpeed; - var speed = (maxSpeed / 1000 / 1000).ToString("#0.0"); - progress.Report(speed); - } - } - }; - downloader.DownloadFileCompleted += (sender, value) => - { - if (progress != null) - { - if (!hasValue && value.Error != null) - { - progress.Report(value.Error?.Message); - } - } - }; - //progress.Report("......"); - using var cts = new CancellationTokenSource(); - cts.CancelAfter(timeout * 1000); - await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token); - - downloadOpt = null; - } - - public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress progress, int timeout) - { - if (url.IsNullOrEmpty()) - { - throw new ArgumentNullException(nameof(url)); - } - if (fileName.IsNullOrEmpty()) - { - throw new ArgumentNullException(nameof(fileName)); - } - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - var downloadOpt = new DownloadConfiguration() - { - Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, - RequestConfiguration = - { - Timeout= timeout * 1000, - Proxy = webProxy - } - }; - - var progressPercentage = 0; - var hasValue = false; - await using var downloader = new Downloader.DownloadService(downloadOpt); - downloader.DownloadStarted += (sender, value) => progress?.Report(0); - downloader.DownloadProgressChanged += (sender, value) => - { - hasValue = true; - var percent = (int)value.ProgressPercentage;// Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100); - if (progressPercentage != percent && percent % 10 == 0) - { - progressPercentage = percent; - progress.Report(percent); - } - }; - downloader.DownloadFileCompleted += (sender, value) => - { - if (progress != null) - { - if (hasValue && value.Error == null) - { - progress.Report(101); - } - else if (value.Error != null) - { - throw value.Error; - } - } - }; - - using var cts = new CancellationTokenSource(); - await downloader.DownloadFileTaskAsync(url, fileName, cts.Token); - - downloadOpt = null; - } + downloadOpt = null; } } diff --git a/v2rayN/ServiceLib/Common/FileManager.cs b/v2rayN/ServiceLib/Common/FileManager.cs index 2f452899..d988c702 100644 --- a/v2rayN/ServiceLib/Common/FileManager.cs +++ b/v2rayN/ServiceLib/Common/FileManager.cs @@ -2,226 +2,225 @@ using System.Formats.Tar; using System.IO.Compression; using System.Text; -namespace ServiceLib.Common -{ - public static class FileManager - { - private static readonly string _tag = "FileManager"; +namespace ServiceLib.Common; - public static bool ByteArrayToFile(string fileName, byte[] content) +public static class FileManager +{ + private static readonly string _tag = "FileManager"; + + public static bool ByteArrayToFile(string fileName, byte[] content) + { + try { - try + File.WriteAllBytes(fileName, content); + return true; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return false; + } + + public static void DecompressFile(string fileName, byte[] content) + { + try + { + using var fs = File.Create(fileName); + using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false); + input.CopyTo(fs); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public static void DecompressFile(string fileName, string toPath, string? toName) + { + try + { + FileInfo fileInfo = new(fileName); + using var originalFileStream = fileInfo.OpenRead(); + using var decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath); + using GZipStream decompressionStream = new(originalFileStream, CompressionMode.Decompress); + decompressionStream.CopyTo(decompressedFileStream); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public static void DecompressTarFile(string fileName, string toPath) + { + try + { + using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); + using var gz = new GZipStream(fs, CompressionMode.Decompress, leaveOpen: true); + TarFile.ExtractToDirectory(gz, toPath, overwriteFiles: true); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public static string NonExclusiveReadAllText(string path) + { + return NonExclusiveReadAllText(path, Encoding.Default); + } + + private static string NonExclusiveReadAllText(string path, Encoding encoding) + { + try + { + using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader sr = new(fs, encoding); + return sr.ReadToEnd(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + throw; + } + } + + public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName) + { + try + { + using var archive = ZipFile.OpenRead(fileName); + foreach (var entry in archive.Entries) { - File.WriteAllBytes(fileName, content); - return true; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + if (entry.Length == 0) + { + continue; + } + try + { + if (ignoredName.IsNotEmpty() && entry.Name.Contains(ignoredName)) + { + continue; + } + entry.ExtractToFile(Path.Combine(toPath, entry.Name), true); + } + catch (IOException ex) + { + Logging.SaveLog(_tag, ex); + } } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); return false; } + return true; + } - public static void DecompressFile(string fileName, byte[] content) + public static List? GetFilesFromZip(string fileName) + { + if (!File.Exists(fileName)) { - try + return null; + } + try + { + using var archive = ZipFile.OpenRead(fileName); + return archive.Entries.Select(entry => entry.FullName).ToList(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return null; + } + } + + public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) + { + try + { + if (File.Exists(destinationArchiveFileName)) { - using var fs = File.Create(fileName); - using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false); - input.CopyTo(fs); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + File.Delete(destinationArchiveFileName); } + + ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return false; + } + return true; + } + + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite, string? ignoredName = null) + { + // Get information about the source directory + var dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); } - public static void DecompressFile(string fileName, string toPath, string? toName) + // Cache directories before we start copying + var dirs = dir.GetDirectories(); + + // Create the destination directory + _ = Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (var file in dir.GetFiles()) { - try + if (ignoredName.IsNotEmpty() && file.Name.Contains(ignoredName)) { - FileInfo fileInfo = new(fileName); - using var originalFileStream = fileInfo.OpenRead(); - using var decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath); - using GZipStream decompressionStream = new(originalFileStream, CompressionMode.Decompress); - decompressionStream.CopyTo(decompressedFileStream); + continue; } - catch (Exception ex) + if (file.Extension == file.Name) { - Logging.SaveLog(_tag, ex); + continue; } + var targetFilePath = Path.Combine(destinationDir, file.Name); + if (!overwrite && File.Exists(targetFilePath)) + { + continue; + } + _ = file.CopyTo(targetFilePath, overwrite); } - public static void DecompressTarFile(string fileName, string toPath) + // If recursive and copying subdirectories, recursively call this method + if (recursive) { - try + foreach (var subDir in dirs) { - using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); - using var gz = new GZipStream(fs, CompressionMode.Decompress, leaveOpen: true); - TarFile.ExtractToDirectory(gz, toPath, overwriteFiles: true); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public static string NonExclusiveReadAllText(string path) - { - return NonExclusiveReadAllText(path, Encoding.Default); - } - - private static string NonExclusiveReadAllText(string path, Encoding encoding) - { - try - { - using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader sr = new(fs, encoding); - return sr.ReadToEnd(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - throw; - } - } - - public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName) - { - try - { - using var archive = ZipFile.OpenRead(fileName); - foreach (var entry in archive.Entries) - { - if (entry.Length == 0) - { - continue; - } - try - { - if (ignoredName.IsNotEmpty() && entry.Name.Contains(ignoredName)) - { - continue; - } - entry.ExtractToFile(Path.Combine(toPath, entry.Name), true); - } - catch (IOException ex) - { - Logging.SaveLog(_tag, ex); - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return false; - } - return true; - } - - public static List? GetFilesFromZip(string fileName) - { - if (!File.Exists(fileName)) - { - return null; - } - try - { - using var archive = ZipFile.OpenRead(fileName); - return archive.Entries.Select(entry => entry.FullName).ToList(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return null; - } - } - - public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) - { - try - { - if (File.Exists(destinationArchiveFileName)) - { - File.Delete(destinationArchiveFileName); - } - - ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return false; - } - return true; - } - - public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite, string? ignoredName = null) - { - // Get information about the source directory - var dir = new DirectoryInfo(sourceDir); - - // Check if the source directory exists - if (!dir.Exists) - { - throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); - } - - // Cache directories before we start copying - var dirs = dir.GetDirectories(); - - // Create the destination directory - _ = Directory.CreateDirectory(destinationDir); - - // Get the files in the source directory and copy to the destination directory - foreach (var file in dir.GetFiles()) - { - if (ignoredName.IsNotEmpty() && file.Name.Contains(ignoredName)) - { - continue; - } - if (file.Extension == file.Name) - { - continue; - } - var targetFilePath = Path.Combine(destinationDir, file.Name); - if (!overwrite && File.Exists(targetFilePath)) - { - continue; - } - _ = file.CopyTo(targetFilePath, overwrite); - } - - // If recursive and copying subdirectories, recursively call this method - if (recursive) - { - foreach (var subDir in dirs) - { - var newDestinationDir = Path.Combine(destinationDir, subDir.Name); - CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite, ignoredName); - } - } - } - - public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine) - { - try - { - var files = Directory.GetFiles(sourceDir, "*.*"); - foreach (var filePath in files) - { - var file = new FileInfo(filePath); - if (file.CreationTime >= dtLine) - { - continue; - } - file.Delete(); - } - } - catch - { - // ignored + var newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite, ignoredName); } } } + + public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine) + { + try + { + var files = Directory.GetFiles(sourceDir, "*.*"); + foreach (var filePath in files) + { + var file = new FileInfo(filePath); + if (file.CreationTime >= dtLine) + { + continue; + } + file.Delete(); + } + } + catch + { + // ignored + } + } } diff --git a/v2rayN/ServiceLib/Common/HttpClientHelper.cs b/v2rayN/ServiceLib/Common/HttpClientHelper.cs index f532062e..8bc1384a 100644 --- a/v2rayN/ServiceLib/Common/HttpClientHelper.cs +++ b/v2rayN/ServiceLib/Common/HttpClientHelper.cs @@ -2,205 +2,204 @@ using System.Net.Http.Headers; using System.Net.Mime; using System.Text; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +/// +/// +public class HttpClientHelper { - /// - /// - public class HttpClientHelper + private static readonly Lazy _instance = new(() => { - private static readonly Lazy _instance = new(() => - { - SocketsHttpHandler handler = new() { UseCookies = false }; - HttpClientHelper helper = new(new HttpClient(handler)); - return helper; - }); + SocketsHttpHandler handler = new() { UseCookies = false }; + HttpClientHelper helper = new(new HttpClient(handler)); + return helper; + }); - public static HttpClientHelper Instance => _instance.Value; - private readonly HttpClient httpClient; + public static HttpClientHelper Instance => _instance.Value; + private readonly HttpClient httpClient; - private HttpClientHelper(HttpClient httpClient) + private HttpClientHelper(HttpClient httpClient) + { + this.httpClient = httpClient; + } + + public async Task TryGetAsync(string url) + { + if (url.IsNullOrEmpty()) { - this.httpClient = httpClient; + return null; } - public async Task TryGetAsync(string url) + try { - if (url.IsNullOrEmpty()) - { - return null; - } + var response = await httpClient.GetAsync(url); + return await response.Content.ReadAsStringAsync(); + } + catch + { + return null; + } + } - try - { - var response = await httpClient.GetAsync(url); - return await response.Content.ReadAsStringAsync(); - } - catch - { - return null; - } + public async Task GetAsync(string url) + { + if (url.IsNullOrEmpty()) + { + return null; + } + return await httpClient.GetStringAsync(url); + } + + public async Task GetAsync(HttpClient client, string url, CancellationToken token = default) + { + if (url.IsNullOrEmpty()) + { + return null; + } + return await client.GetStringAsync(url, token); + } + + public async Task PutAsync(string url, Dictionary headers) + { + var jsonContent = JsonUtils.Serialize(headers); + var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json); + + await httpClient.PutAsync(url, content); + } + + public async Task PatchAsync(string url, Dictionary headers) + { + var myContent = JsonUtils.Serialize(headers); + var buffer = Encoding.UTF8.GetBytes(myContent); + var byteContent = new ByteArrayContent(buffer); + byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + await httpClient.PatchAsync(url, byteContent); + } + + public async Task DeleteAsync(string url) + { + await httpClient.DeleteAsync(url); + } + + public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress? progress, CancellationToken token = default) + { + ArgumentNullException.ThrowIfNull(url); + ArgumentNullException.ThrowIfNull(fileName); + if (File.Exists(fileName)) + { + File.Delete(fileName); } - public async Task GetAsync(string url) + using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); + + if (!response.IsSuccessStatusCode) { - if (url.IsNullOrEmpty()) - { - return null; - } - return await httpClient.GetStringAsync(url); + throw new Exception(response.StatusCode.ToString()); } - public async Task GetAsync(HttpClient client, string url, CancellationToken token = default) + var total = response.Content.Headers.ContentLength ?? -1L; + var canReportProgress = total != -1 && progress != null; + + await using var stream = await response.Content.ReadAsStreamAsync(token); + await using var file = File.Create(fileName); + var totalRead = 0L; + var buffer = new byte[1024 * 1024]; + var progressPercentage = 0; + + while (true) { - if (url.IsNullOrEmpty()) + token.ThrowIfCancellationRequested(); + + var read = await stream.ReadAsync(buffer, token); + totalRead += read; + + if (read == 0) { - return null; + break; } - return await client.GetStringAsync(url, token); - } + await file.WriteAsync(buffer.AsMemory(0, read), token); - public async Task PutAsync(string url, Dictionary headers) - { - var jsonContent = JsonUtils.Serialize(headers); - var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json); - - await httpClient.PutAsync(url, content); - } - - public async Task PatchAsync(string url, Dictionary headers) - { - var myContent = JsonUtils.Serialize(headers); - var buffer = Encoding.UTF8.GetBytes(myContent); - var byteContent = new ByteArrayContent(buffer); - byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - await httpClient.PatchAsync(url, byteContent); - } - - public async Task DeleteAsync(string url) - { - await httpClient.DeleteAsync(url); - } - - public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress? progress, CancellationToken token = default) - { - ArgumentNullException.ThrowIfNull(url); - ArgumentNullException.ThrowIfNull(fileName); - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); - - if (!response.IsSuccessStatusCode) - { - throw new Exception(response.StatusCode.ToString()); - } - - var total = response.Content.Headers.ContentLength ?? -1L; - var canReportProgress = total != -1 && progress != null; - - await using var stream = await response.Content.ReadAsStreamAsync(token); - await using var file = File.Create(fileName); - var totalRead = 0L; - var buffer = new byte[1024 * 1024]; - var progressPercentage = 0; - - while (true) - { - token.ThrowIfCancellationRequested(); - - var read = await stream.ReadAsync(buffer, token); - totalRead += read; - - if (read == 0) - { - break; - } - await file.WriteAsync(buffer.AsMemory(0, read), token); - - if (canReportProgress) - { - var percent = (int)(100.0 * totalRead / total); - //if (progressPercentage != percent && percent % 10 == 0) - { - progressPercentage = percent; - progress?.Report(percent); - } - } - } if (canReportProgress) { - progress?.Report(101); + var percent = (int)(100.0 * totalRead / total); + //if (progressPercentage != percent && percent % 10 == 0) + { + progressPercentage = percent; + progress?.Report(percent); + } } } - - public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress progress, CancellationToken token = default) + if (canReportProgress) { - if (url.IsNullOrEmpty()) + progress?.Report(101); + } + } + + public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress progress, CancellationToken token = default) + { + if (url.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(url)); + } + + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); + + if (!response.IsSuccessStatusCode) + { + throw new Exception(response.StatusCode.ToString()); + } + + //var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L; + //var canReportProgress = total != -1 && progress != null; + + await using var stream = await response.Content.ReadAsStreamAsync(token); + var totalRead = 0L; + var buffer = new byte[1024 * 64]; + var isMoreToRead = true; + var progressSpeed = string.Empty; + var totalDatetime = DateTime.Now; + var totalSecond = 0; + + do + { + if (token.IsCancellationRequested) { - throw new ArgumentNullException(nameof(url)); - } - - var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); - - if (!response.IsSuccessStatusCode) - { - throw new Exception(response.StatusCode.ToString()); - } - - //var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L; - //var canReportProgress = total != -1 && progress != null; - - await using var stream = await response.Content.ReadAsStreamAsync(token); - var totalRead = 0L; - var buffer = new byte[1024 * 64]; - var isMoreToRead = true; - var progressSpeed = string.Empty; - var totalDatetime = DateTime.Now; - var totalSecond = 0; - - do - { - if (token.IsCancellationRequested) + if (totalRead > 0) { - if (totalRead > 0) - { - return; - } - else - { - token.ThrowIfCancellationRequested(); - } - } - - var read = await stream.ReadAsync(buffer, token); - - if (read == 0) - { - isMoreToRead = false; + return; } else { - var data = new byte[read]; - buffer.ToList().CopyTo(0, data, 0, read); + token.ThrowIfCancellationRequested(); + } + } - totalRead += read; + var read = await stream.ReadAsync(buffer, token); - var ts = DateTime.Now - totalDatetime; - if (progress != null && ts.Seconds > totalSecond) + if (read == 0) + { + isMoreToRead = false; + } + else + { + var data = new byte[read]; + buffer.ToList().CopyTo(0, data, 0, read); + + totalRead += read; + + var ts = DateTime.Now - totalDatetime; + if (progress != null && ts.Seconds > totalSecond) + { + totalSecond = ts.Seconds; + var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0"); + if (progressSpeed != speed) { - totalSecond = ts.Seconds; - var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0"); - if (progressSpeed != speed) - { - progressSpeed = speed; - progress.Report(speed); - } + progressSpeed = speed; + progress.Report(speed); } } - } while (isMoreToRead); - } + } + } while (isMoreToRead); } } diff --git a/v2rayN/ServiceLib/Common/Job.cs b/v2rayN/ServiceLib/Common/Job.cs index fdbc8dbf..fe968d2d 100644 --- a/v2rayN/ServiceLib/Common/Job.cs +++ b/v2rayN/ServiceLib/Common/Job.cs @@ -1,8 +1,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace ServiceLib.Common -{ +namespace ServiceLib.Common; /* * See: * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net @@ -178,4 +177,4 @@ namespace ServiceLib.Common } #endregion Helper classes -} + diff --git a/v2rayN/ServiceLib/Common/JsonUtils.cs b/v2rayN/ServiceLib/Common/JsonUtils.cs index 7d427f74..9ffa00ca 100644 --- a/v2rayN/ServiceLib/Common/JsonUtils.cs +++ b/v2rayN/ServiceLib/Common/JsonUtils.cs @@ -1,131 +1,130 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class JsonUtils { - public class JsonUtils + private static readonly string _tag = "JsonUtils"; + + /// + /// DeepCopy + /// + /// + /// + /// + public static T DeepCopy(T obj) { - private static readonly string _tag = "JsonUtils"; + return Deserialize(Serialize(obj, false))!; + } - /// - /// DeepCopy - /// - /// - /// - /// - public static T DeepCopy(T obj) + /// + /// Deserialize to object + /// + /// + /// + /// + public static T? Deserialize(string? strJson) + { + try { - return Deserialize(Serialize(obj, false))!; - } - - /// - /// Deserialize to object - /// - /// - /// - /// - public static T? Deserialize(string? strJson) - { - try - { - if (string.IsNullOrWhiteSpace(strJson)) - { - return default; - } - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - return JsonSerializer.Deserialize(strJson, options); - } - catch + if (string.IsNullOrWhiteSpace(strJson)) { return default; } + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + return JsonSerializer.Deserialize(strJson, options); } - - /// - /// parse - /// - /// - /// - public static JsonNode? ParseJson(string strJson) + catch { - try + return default; + } + } + + /// + /// parse + /// + /// + /// + public static JsonNode? ParseJson(string strJson) + { + try + { + if (string.IsNullOrWhiteSpace(strJson)) { - if (string.IsNullOrWhiteSpace(strJson)) - { - return null; - } - return JsonNode.Parse(strJson); - } - catch - { - //SaveLog(ex.Message, ex); return null; } + return JsonNode.Parse(strJson); } - - /// - /// Serialize Object to Json string - /// - /// - /// - /// - /// - public static string Serialize(object? obj, bool indented = true, bool nullValue = false) + catch { - var result = string.Empty; - try - { - if (obj == null) - { - return result; - } - var options = new JsonSerializerOptions - { - WriteIndented = indented, - DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull - }; - result = JsonSerializer.Serialize(obj, options); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return result; + //SaveLog(ex.Message, ex); + return null; } - - /// - /// Serialize Object to Json string - /// - /// - /// - /// - public static string Serialize(object? obj, JsonSerializerOptions options) - { - var result = string.Empty; - try - { - if (obj == null) - { - return result; - } - result = JsonSerializer.Serialize(obj, options); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return result; - } - - /// - /// SerializeToNode - /// - /// - /// - public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj); } -} \ No newline at end of file + + /// + /// Serialize Object to Json string + /// + /// + /// + /// + /// + public static string Serialize(object? obj, bool indented = true, bool nullValue = false) + { + var result = string.Empty; + try + { + if (obj == null) + { + return result; + } + var options = new JsonSerializerOptions + { + WriteIndented = indented, + DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull + }; + result = JsonSerializer.Serialize(obj, options); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return result; + } + + /// + /// Serialize Object to Json string + /// + /// + /// + /// + public static string Serialize(object? obj, JsonSerializerOptions options) + { + var result = string.Empty; + try + { + if (obj == null) + { + return result; + } + result = JsonSerializer.Serialize(obj, options); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return result; + } + + /// + /// SerializeToNode + /// + /// + /// + public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj); +} diff --git a/v2rayN/ServiceLib/Common/Logging.cs b/v2rayN/ServiceLib/Common/Logging.cs index 7f7089b7..fdd49f5c 100644 --- a/v2rayN/ServiceLib/Common/Logging.cs +++ b/v2rayN/ServiceLib/Common/Logging.cs @@ -2,53 +2,54 @@ using NLog; using NLog.Config; using NLog.Targets; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class Logging { - public class Logging + private static readonly Logger _logger1 = LogManager.GetLogger("Log1"); + private static readonly Logger _logger2 = LogManager.GetLogger("Log2"); + + public static void Setup() { - public static void Setup() + LoggingConfiguration config = new(); + FileTarget fileTarget = new(); + config.AddTarget("file", fileTarget); + fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}"; + fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt"); + config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); + LogManager.Configuration = config; + } + + public static void LoggingEnabled(bool enable) + { + if (!enable) { - LoggingConfiguration config = new(); - FileTarget fileTarget = new(); - config.AddTarget("file", fileTarget); - fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}"; - fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt"); - config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); - LogManager.Configuration = config; + LogManager.SuspendLogging(); + } + } + + public static void SaveLog(string strContent) + { + if (!LogManager.IsLoggingEnabled()) + { + return; } - public static void LoggingEnabled(bool enable) + _logger1.Info(strContent); + } + + public static void SaveLog(string strTitle, Exception ex) + { + if (!LogManager.IsLoggingEnabled()) { - if (!enable) - { - LogManager.SuspendLogging(); - } + return; } - public static void SaveLog(string strContent) + _logger2.Debug($"{strTitle},{ex.Message}"); + _logger2.Debug(ex.StackTrace); + if (ex?.InnerException != null) { - if (!LogManager.IsLoggingEnabled()) - { - return; - } - - LogManager.GetLogger("Log1").Info(strContent); - } - - public static void SaveLog(string strTitle, Exception ex) - { - if (!LogManager.IsLoggingEnabled()) - { - return; - } - - var logger = LogManager.GetLogger("Log2"); - logger.Debug($"{strTitle},{ex.Message}"); - logger.Debug(ex.StackTrace); - if (ex?.InnerException != null) - { - logger.Error(ex.InnerException); - } + _logger2.Error(ex.InnerException); } } } diff --git a/v2rayN/ServiceLib/Common/QRCodeHelper.cs b/v2rayN/ServiceLib/Common/QRCodeHelper.cs index 5cd5ac25..9938eadd 100644 --- a/v2rayN/ServiceLib/Common/QRCodeHelper.cs +++ b/v2rayN/ServiceLib/Common/QRCodeHelper.cs @@ -1,90 +1,89 @@ -using QRCoder; +using QRCoder; using SkiaSharp; using ZXing.SkiaSharp; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class QRCodeHelper { - public class QRCodeHelper + public static byte[]? GenQRCode(string? url) { - public static byte[]? GenQRCode(string? url) - { - using QRCodeGenerator qrGenerator = new(); - using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q); - using PngByteQRCode qrCode = new(qrCodeData); - return qrCode.GetGraphic(20); - } - - public static string? ParseBarcode(string? fileName) - { - if (fileName == null || !File.Exists(fileName)) - { - return null; - } - - try - { - var image = SKImage.FromEncodedData(fileName); - var bitmap = SKBitmap.FromImage(image); - - return ReaderBarcode(bitmap); - } - catch - { - // ignored - } - - return null; - } - - public static string? ParseBarcode(byte[]? bytes) - { - try - { - var bitmap = SKBitmap.Decode(bytes); - //using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write); - //using var image = SKImage.FromBitmap(bitmap); - //using var encodedImage = image.Encode(); - //encodedImage.SaveTo(stream); - return ReaderBarcode(bitmap); - } - catch - { - // ignored - } - - return null; - } - - private static string? ReaderBarcode(SKBitmap? bitmap) - { - var reader = new BarcodeReader(); - var result = reader.Decode(bitmap); - - if (result != null && result.Text.IsNotEmpty()) - { - return result.Text; - } - - //FlipBitmap - var result2 = reader.Decode(FlipBitmap(bitmap)); - return result2?.Text; - } - - private static SKBitmap FlipBitmap(SKBitmap bmp) - { - // Create a bitmap (to return) - var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType); - - // Create a canvas to draw into the bitmap - using var canvas = new SKCanvas(flipped); - - // Set a transform matrix which moves the bitmap to the right, - // and then "scales" it by -1, which just flips the pixels - // horizontally - canvas.Translate(bmp.Width, 0); - canvas.Scale(-1, 1); - canvas.DrawBitmap(bmp, 0, 0); - return flipped; - } + using QRCodeGenerator qrGenerator = new(); + using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q); + using PngByteQRCode qrCode = new(qrCodeData); + return qrCode.GetGraphic(20); } -} \ No newline at end of file + + public static string? ParseBarcode(string? fileName) + { + if (fileName == null || !File.Exists(fileName)) + { + return null; + } + + try + { + var image = SKImage.FromEncodedData(fileName); + var bitmap = SKBitmap.FromImage(image); + + return ReaderBarcode(bitmap); + } + catch + { + // ignored + } + + return null; + } + + public static string? ParseBarcode(byte[]? bytes) + { + try + { + var bitmap = SKBitmap.Decode(bytes); + //using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write); + //using var image = SKImage.FromBitmap(bitmap); + //using var encodedImage = image.Encode(); + //encodedImage.SaveTo(stream); + return ReaderBarcode(bitmap); + } + catch + { + // ignored + } + + return null; + } + + private static string? ReaderBarcode(SKBitmap? bitmap) + { + var reader = new BarcodeReader(); + var result = reader.Decode(bitmap); + + if (result != null && result.Text.IsNotEmpty()) + { + return result.Text; + } + + //FlipBitmap + var result2 = reader.Decode(FlipBitmap(bitmap)); + return result2?.Text; + } + + private static SKBitmap FlipBitmap(SKBitmap bmp) + { + // Create a bitmap (to return) + var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType); + + // Create a canvas to draw into the bitmap + using var canvas = new SKCanvas(flipped); + + // Set a transform matrix which moves the bitmap to the right, + // and then "scales" it by -1, which just flips the pixels + // horizontally + canvas.Translate(bmp.Width, 0); + canvas.Scale(-1, 1); + canvas.DrawBitmap(bmp, 0, 0); + return flipped; + } +} diff --git a/v2rayN/ServiceLib/Common/SemanticVersion.cs b/v2rayN/ServiceLib/Common/SemanticVersion.cs index 5bfc9692..64167084 100644 --- a/v2rayN/ServiceLib/Common/SemanticVersion.cs +++ b/v2rayN/ServiceLib/Common/SemanticVersion.cs @@ -1,187 +1,186 @@ -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class SemanticVersion { - public class SemanticVersion + private readonly int major; + private readonly int minor; + private readonly int patch; + private readonly string version; + + public SemanticVersion(int major, int minor, int patch) { - private readonly int major; - private readonly int minor; - private readonly int patch; - private readonly string version; + this.major = major; + this.minor = minor; + this.patch = patch; + version = $"{major}.{minor}.{patch}"; + } - public SemanticVersion(int major, int minor, int patch) + public SemanticVersion(string? version) + { + try { - this.major = major; - this.minor = minor; - this.patch = patch; - version = $"{major}.{minor}.{patch}"; - } - - public SemanticVersion(string? version) - { - try - { - if (string.IsNullOrEmpty(version)) - { - major = 0; - minor = 0; - patch = 0; - return; - } - this.version = version.RemovePrefix('v'); - - var parts = this.version.Split('.'); - if (parts.Length == 2) - { - major = int.Parse(parts.First()); - minor = int.Parse(parts.Last()); - patch = 0; - } - else if (parts.Length is 3 or 4) - { - major = int.Parse(parts[0]); - minor = int.Parse(parts[1]); - patch = int.Parse(parts[2]); - } - else - { - throw new ArgumentException("Invalid version string"); - } - } - catch + if (string.IsNullOrEmpty(version)) { major = 0; minor = 0; patch = 0; + return; } - } + this.version = version.RemovePrefix('v'); - public override bool Equals(object? obj) - { - if (obj is SemanticVersion other) + var parts = this.version.Split('.'); + if (parts.Length == 2) { - return major == other.major && minor == other.minor && patch == other.patch; + major = int.Parse(parts.First()); + minor = int.Parse(parts.Last()); + patch = 0; + } + else if (parts.Length is 3 or 4) + { + major = int.Parse(parts[0]); + minor = int.Parse(parts[1]); + patch = int.Parse(parts[2]); } else { - return false; + throw new ArgumentException("Invalid version string"); } } - - public override int GetHashCode() + catch { - return major.GetHashCode() ^ minor.GetHashCode() ^ patch.GetHashCode(); + major = 0; + minor = 0; + patch = 0; } + } - /// - /// Use ToVersionString(string? prefix) instead if possible. - /// - /// major.minor.patch - public override string ToString() + public override bool Equals(object? obj) + { + if (obj is SemanticVersion other) + { + return major == other.major && minor == other.minor && patch == other.patch; + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return major.GetHashCode() ^ minor.GetHashCode() ^ patch.GetHashCode(); + } + + /// + /// Use ToVersionString(string? prefix) instead if possible. + /// + /// major.minor.patch + public override string ToString() + { + return version; + } + + public string ToVersionString(string? prefix = null) + { + if (prefix == null) { return version; } - - public string ToVersionString(string? prefix = null) + else { - if (prefix == null) - { - return version; - } - else - { - return $"{prefix}{version}"; - } + return $"{prefix}{version}"; } - - public static bool operator ==(SemanticVersion v1, SemanticVersion v2) - { return v1.Equals(v2); } - - public static bool operator !=(SemanticVersion v1, SemanticVersion v2) - { return !v1.Equals(v2); } - - public static bool operator >=(SemanticVersion v1, SemanticVersion v2) - { return v1.GreaterEquals(v2); } - - public static bool operator <=(SemanticVersion v1, SemanticVersion v2) - { return v1.LessEquals(v2); } - - #region Private - - private bool GreaterEquals(SemanticVersion other) - { - if (major < other.major) - { - return false; - } - else if (major > other.major) - { - return true; - } - else - { - if (minor < other.minor) - { - return false; - } - else if (minor > other.minor) - { - return true; - } - else - { - if (patch < other.patch) - { - return false; - } - else if (patch > other.patch) - { - return true; - } - else - { - return true; - } - } - } - } - - private bool LessEquals(SemanticVersion other) - { - if (major < other.major) - { - return true; - } - else if (major > other.major) - { - return false; - } - else - { - if (minor < other.minor) - { - return true; - } - else if (minor > other.minor) - { - return false; - } - else - { - if (patch < other.patch) - { - return true; - } - else if (patch > other.patch) - { - return false; - } - else - { - return true; - } - } - } - } - - #endregion Private } + + public static bool operator ==(SemanticVersion v1, SemanticVersion v2) + { return v1.Equals(v2); } + + public static bool operator !=(SemanticVersion v1, SemanticVersion v2) + { return !v1.Equals(v2); } + + public static bool operator >=(SemanticVersion v1, SemanticVersion v2) + { return v1.GreaterEquals(v2); } + + public static bool operator <=(SemanticVersion v1, SemanticVersion v2) + { return v1.LessEquals(v2); } + + #region Private + + private bool GreaterEquals(SemanticVersion other) + { + if (major < other.major) + { + return false; + } + else if (major > other.major) + { + return true; + } + else + { + if (minor < other.minor) + { + return false; + } + else if (minor > other.minor) + { + return true; + } + else + { + if (patch < other.patch) + { + return false; + } + else if (patch > other.patch) + { + return true; + } + else + { + return true; + } + } + } + } + + private bool LessEquals(SemanticVersion other) + { + if (major < other.major) + { + return true; + } + else if (major > other.major) + { + return false; + } + else + { + if (minor < other.minor) + { + return true; + } + else if (minor > other.minor) + { + return false; + } + else + { + if (patch < other.patch) + { + return true; + } + else if (patch > other.patch) + { + return false; + } + else + { + return true; + } + } + } + } + + #endregion Private } diff --git a/v2rayN/ServiceLib/Common/SqliteHelper.cs b/v2rayN/ServiceLib/Common/SqliteHelper.cs index dca10c57..62b0a830 100644 --- a/v2rayN/ServiceLib/Common/SqliteHelper.cs +++ b/v2rayN/ServiceLib/Common/SqliteHelper.cs @@ -1,91 +1,90 @@ using System.Collections; using SQLite; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public sealed class SQLiteHelper { - public sealed class SQLiteHelper + private static readonly Lazy _instance = new(() => new()); + public static SQLiteHelper Instance => _instance.Value; + private readonly string _connstr; + private SQLiteConnection _db; + private SQLiteAsyncConnection _dbAsync; + private readonly string _configDB = "guiNDB.db"; + + public SQLiteHelper() { - private static readonly Lazy _instance = new(() => new()); - public static SQLiteHelper Instance => _instance.Value; - private readonly string _connstr; - private SQLiteConnection _db; - private SQLiteAsyncConnection _dbAsync; - private readonly string _configDB = "guiNDB.db"; + _connstr = Utils.GetConfigPath(_configDB); + _db = new SQLiteConnection(_connstr, false); + _dbAsync = new SQLiteAsyncConnection(_connstr, false); + } - public SQLiteHelper() + public CreateTableResult CreateTable() + { + return _db.CreateTable(); + } + + public async Task InsertAllAsync(IEnumerable models) + { + return await _dbAsync.InsertAllAsync(models); + } + + public async Task InsertAsync(object model) + { + return await _dbAsync.InsertAsync(model); + } + + public async Task ReplaceAsync(object model) + { + return await _dbAsync.InsertOrReplaceAsync(model); + } + + public async Task UpdateAsync(object model) + { + return await _dbAsync.UpdateAsync(model); + } + + public async Task UpdateAllAsync(IEnumerable models) + { + return await _dbAsync.UpdateAllAsync(models); + } + + public async Task DeleteAsync(object model) + { + return await _dbAsync.DeleteAsync(model); + } + + public async Task DeleteAllAsync() + { + return await _dbAsync.DeleteAllAsync(); + } + + public async Task ExecuteAsync(string sql) + { + return await _dbAsync.ExecuteAsync(sql); + } + + public async Task> QueryAsync(string sql) where T : new() + { + return await _dbAsync.QueryAsync(sql); + } + + public AsyncTableQuery TableAsync() where T : new() + { + return _dbAsync.Table(); + } + + public async Task DisposeDbConnectionAsync() + { + await Task.Factory.StartNew(() => { - _connstr = Utils.GetConfigPath(_configDB); - _db = new SQLiteConnection(_connstr, false); - _dbAsync = new SQLiteAsyncConnection(_connstr, false); - } + _db?.Close(); + _db?.Dispose(); + _db = null; - public CreateTableResult CreateTable() - { - return _db.CreateTable(); - } - - public async Task InsertAllAsync(IEnumerable models) - { - return await _dbAsync.InsertAllAsync(models); - } - - public async Task InsertAsync(object model) - { - return await _dbAsync.InsertAsync(model); - } - - public async Task ReplaceAsync(object model) - { - return await _dbAsync.InsertOrReplaceAsync(model); - } - - public async Task UpdateAsync(object model) - { - return await _dbAsync.UpdateAsync(model); - } - - public async Task UpdateAllAsync(IEnumerable models) - { - return await _dbAsync.UpdateAllAsync(models); - } - - public async Task DeleteAsync(object model) - { - return await _dbAsync.DeleteAsync(model); - } - - public async Task DeleteAllAsync() - { - return await _dbAsync.DeleteAllAsync(); - } - - public async Task ExecuteAsync(string sql) - { - return await _dbAsync.ExecuteAsync(sql); - } - - public async Task> QueryAsync(string sql) where T : new() - { - return await _dbAsync.QueryAsync(sql); - } - - public AsyncTableQuery TableAsync() where T : new() - { - return _dbAsync.Table(); - } - - public async Task DisposeDbConnectionAsync() - { - await Task.Factory.StartNew(() => - { - _db?.Close(); - _db?.Dispose(); - _db = null; - - _dbAsync?.GetConnection()?.Close(); - _dbAsync?.GetConnection()?.Dispose(); - _dbAsync = null; - }); - } + _dbAsync?.GetConnection()?.Close(); + _dbAsync?.GetConnection()?.Dispose(); + _dbAsync = null; + }); } } diff --git a/v2rayN/ServiceLib/Common/StringEx.cs b/v2rayN/ServiceLib/Common/StringEx.cs index ab5b808a..1b880b74 100644 --- a/v2rayN/ServiceLib/Common/StringEx.cs +++ b/v2rayN/ServiceLib/Common/StringEx.cs @@ -1,83 +1,82 @@ using System.Diagnostics.CodeAnalysis; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public static class StringEx { - public static class StringEx + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) { - public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) - { - return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value); - } + return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value); + } - public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value) - { - return string.IsNullOrWhiteSpace(value); - } + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value) + { + return string.IsNullOrWhiteSpace(value); + } - public static bool IsNotEmpty([NotNullWhen(false)] this string? value) - { - return !string.IsNullOrEmpty(value); - } + public static bool IsNotEmpty([NotNullWhen(false)] this string? value) + { + return !string.IsNullOrEmpty(value); + } - public static bool BeginWithAny(this string s, IEnumerable chars) + public static bool BeginWithAny(this string s, IEnumerable chars) + { + if (s.IsNullOrEmpty()) { - if (s.IsNullOrEmpty()) + return false; + } + return chars.Contains(s.First()); + } + + private static bool IsWhiteSpace(this string value) + { + return value.All(char.IsWhiteSpace); + } + + public static IEnumerable NonWhiteSpaceLines(this TextReader reader) + { + while (reader.ReadLine() is { } line) + { + if (line.IsWhiteSpace()) { - return false; + continue; } - return chars.Contains(s.First()); - } - - private static bool IsWhiteSpace(this string value) - { - return value.All(char.IsWhiteSpace); - } - - public static IEnumerable NonWhiteSpaceLines(this TextReader reader) - { - while (reader.ReadLine() is { } line) - { - if (line.IsWhiteSpace()) - { - continue; - } - yield return line; - } - } - - public static string TrimEx(this string? value) - { - return value == null ? string.Empty : value.Trim(); - } - - public static string RemovePrefix(this string value, char prefix) - { - return value.StartsWith(prefix) ? value[1..] : value; - } - - public static string RemovePrefix(this string value, string prefix) - { - return value.StartsWith(prefix) ? value[prefix.Length..] : value; - } - - public static string UpperFirstChar(this string value) - { - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - return char.ToUpper(value.First()) + value[1..]; - } - - public static string AppendQuotes(this string value) - { - return string.IsNullOrEmpty(value) ? string.Empty : $"\"{value}\""; - } - - public static int ToInt(this string? value, int defaultValue = 0) - { - return int.TryParse(value, out var result) ? result : defaultValue; + yield return line; } } + + public static string TrimEx(this string? value) + { + return value == null ? string.Empty : value.Trim(); + } + + public static string RemovePrefix(this string value, char prefix) + { + return value.StartsWith(prefix) ? value[1..] : value; + } + + public static string RemovePrefix(this string value, string prefix) + { + return value.StartsWith(prefix) ? value[prefix.Length..] : value; + } + + public static string UpperFirstChar(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + + return char.ToUpper(value.First()) + value[1..]; + } + + public static string AppendQuotes(this string value) + { + return string.IsNullOrEmpty(value) ? string.Empty : $"\"{value}\""; + } + + public static int ToInt(this string? value, int defaultValue = 0) + { + return int.TryParse(value, out var result) ? result : defaultValue; + } } diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 8b75e2cd..03a1a860 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -11,856 +11,855 @@ using System.Text; using CliWrap; using CliWrap.Buffered; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class Utils { - public class Utils + private static readonly string _tag = "Utils"; + + #region 转换函数 + + /// + /// Convert to comma-separated string + /// + /// + /// + /// + public static string List2String(List? lst, bool wrap = false) { - private static readonly string _tag = "Utils"; - - #region 转换函数 - - /// - /// Convert to comma-separated string - /// - /// - /// - /// - public static string List2String(List? lst, bool wrap = false) + if (lst == null || lst.Count == 0) { - if (lst == null || lst.Count == 0) - { - return string.Empty; - } - - var separator = wrap ? "," + Environment.NewLine : ","; - - try - { - return string.Join(separator, lst); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return string.Empty; - } - } - - /// - /// Comma-separated string - /// - /// - /// - public static List? String2List(string? str) - { - if (string.IsNullOrWhiteSpace(str)) - { - return null; - } - - try - { - str = str.Replace(Environment.NewLine, string.Empty); - return new List(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return null; - } - } - - /// - /// Comma-separated string, sorted and then converted to List - /// - /// - /// - public static List? String2ListSorted(string str) - { - var lst = String2List(str); - lst?.Sort(); - return lst; - } - - /// - /// Base64 Encode - /// - /// - /// - public static string Base64Encode(string plainText) - { - try - { - var plainTextBytes = Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return string.Empty; } - /// - /// Base64 Decode - /// - /// - /// - public static string Base64Decode(string? plainText) + var separator = wrap ? "," + Environment.NewLine : ","; + + try { - try - { - if (plainText.IsNullOrEmpty()) - { - return ""; - } - - plainText = plainText.Trim() - .Replace(Environment.NewLine, "") - .Replace("\n", "") - .Replace("\r", "") - .Replace('_', '/') - .Replace('-', '+') - .Replace(" ", ""); - - if (plainText.Length % 4 > 0) - { - plainText = plainText.PadRight(plainText.Length + 4 - (plainText.Length % 4), '='); - } - - var data = Convert.FromBase64String(plainText); - return Encoding.UTF8.GetString(data); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - + return string.Join(separator, lst); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); return string.Empty; } + } - public static bool ToBool(object obj) + /// + /// Comma-separated string + /// + /// + /// + public static List? String2List(string? str) + { + if (string.IsNullOrWhiteSpace(str)) { - try - { - return Convert.ToBoolean(obj); - } - catch - { - return false; - } - } - - public static string ToString(object? obj) - { - try - { - return obj?.ToString() ?? string.Empty; - } - catch - { - return string.Empty; - } - } - - public static string HumanFy(long amount) - { - if (amount <= 0) - { - return $"{amount:f1} B"; - } - - string[] units = ["KB", "MB", "GB", "TB", "PB"]; - var unitIndex = 0; - double size = amount; - - // Loop and divide by 1024 until a suitable unit is found - while (size >= 1024 && unitIndex < units.Length - 1) - { - size /= 1024; - unitIndex++; - } - - return $"{size:f1} {units[unitIndex]}"; - } - - public static string UrlEncode(string url) - { - return Uri.EscapeDataString(url); - } - - public static string UrlDecode(string url) - { - return Uri.UnescapeDataString(url); - } - - public static NameValueCollection ParseQueryString(string query) - { - var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); - if (query.IsNullOrEmpty()) - { - return result; - } - - var parts = query[1..].Split('&', StringSplitOptions.RemoveEmptyEntries); - foreach (var part in parts) - { - var keyValue = part.Split('='); - if (keyValue.Length != 2) - { - continue; - } - - var key = Uri.UnescapeDataString(keyValue.First()); - var val = Uri.UnescapeDataString(keyValue.Last()); - - if (result[key] is null) - { - result.Add(key, val); - } - } - - return result; - } - - public static string GetMd5(string str) - { - if (string.IsNullOrEmpty(str)) - { - return string.Empty; - } - - try - { - var byteOld = Encoding.UTF8.GetBytes(str); - var byteNew = MD5.HashData(byteOld); - StringBuilder sb = new(32); - foreach (var b in byteNew) - { - sb.Append(b.ToString("x2")); - } - - return sb.ToString(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return string.Empty; - } - } - - public static string GetFileHash(string filePath) - { - if (string.IsNullOrEmpty(filePath)) - { - return string.Empty; - } - - if (!File.Exists(filePath)) - { - return string.Empty; - } - - try - { - using var md5 = MD5.Create(); - using var stream = File.OpenRead(filePath); - var hash = md5.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return string.Empty; - } - } - - /// - /// idn to idc - /// - /// - /// - public static string GetPunycode(string url) - { - if (url.IsNullOrEmpty()) - { - return url; - } - - try - { - Uri uri = new(url); - if (uri.Host == uri.IdnHost || uri.Host == $"[{uri.IdnHost}]") - { - return url; - } - else - { - return url.Replace(uri.Host, uri.IdnHost); - } - } - catch - { - return url; - } - } - - public static bool IsBase64String(string? plainText) - { - if (plainText.IsNullOrEmpty()) - return false; - var buffer = new Span(new byte[plainText.Length]); - return Convert.TryFromBase64String(plainText, buffer, out var _); - } - - public static string Convert2Comma(string text) - { - if (text.IsNullOrEmpty()) - { - return text; - } - - return text.Replace(",", ",").Replace(Environment.NewLine, ","); - } - - #endregion 转换函数 - - #region 数据检查 - - /// - /// Determine if the input is a number - /// - /// - /// - public static bool IsNumeric(string oText) - { - return oText.All(char.IsNumber); - } - - /// - /// Validate if the domain address is valid - /// - /// - public static bool IsDomain(string? domain) - { - if (domain.IsNullOrEmpty()) - { - return false; - } - - return Uri.CheckHostName(domain) == UriHostNameType.Dns; - } - - public static bool IsIpv6(string ip) - { - if (IPAddress.TryParse(ip, out var address)) - { - return address.AddressFamily switch - { - AddressFamily.InterNetwork => false, - AddressFamily.InterNetworkV6 => true, - _ => false, - }; - } - - return false; - } - - public static Uri? TryUri(string url) - { - try - { - return new Uri(url); - } - catch (UriFormatException) - { - return null; - } - } - - public static bool IsPrivateNetwork(string ip) - { - if (IPAddress.TryParse(ip, out var address)) - { - var ipBytes = address.GetAddressBytes(); - if (ipBytes[0] == 10) - return true; - if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) - return true; - if (ipBytes[0] == 192 && ipBytes[1] == 168) - return true; - } - - return false; - } - - #endregion 数据检查 - - #region 测速 - - private static bool PortInUse(int port) - { - try - { - List lstIpEndPoints = new(); - List lstTcpConns = new(); - - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); - - if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0) - { - return true; - } - - if (lstTcpConns?.FindIndex(it => it.LocalEndPoint.Port == port) >= 0) - { - return true; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return false; - } - - public static int GetFreePort(int defaultPort = 9090) - { - try - { - if (!Utils.PortInUse(defaultPort)) - { - return defaultPort; - } - - TcpListener l = new(IPAddress.Loopback, 0); - l.Start(); - var port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; - } - catch - { - } - - return 59090; - } - - #endregion 测速 - - #region 杂项 - - public static bool UpgradeAppExists(out string upgradeFileName) - { - upgradeFileName = Path.Combine(GetBaseDirectory(), GetExeName("AmazTool")); - return File.Exists(upgradeFileName); - } - - /// - /// Get version - /// - /// - public static string GetVersion(bool blFull = true) - { - try - { - return blFull - ? $"{Global.AppName} - V{GetVersionInfo()} - {RuntimeInformation.ProcessArchitecture}" - : $"{Global.AppName}/{GetVersionInfo()}"; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return Global.AppName; - } - - public static string GetVersionInfo() - { - try - { - return Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(3) ?? "0.0"; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return "0.0"; - } - } - - public static string GetRuntimeInfo() - { - return $"{Utils.GetVersion()} | {Utils.StartupPath()} | {Utils.GetExePath()} | {Environment.OSVersion}"; - } - - /// - /// GUID - /// - /// - public static string GetGuid(bool full = true) - { - try - { - if (full) - { - return Guid.NewGuid().ToString("D"); - } - else - { - return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0).ToString(); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return string.Empty; - } - - public static bool IsGuidByParse(string strSrc) - { - return Guid.TryParse(strSrc, out _); - } - - public static Dictionary GetSystemHosts() - { - var systemHosts = new Dictionary(); - var hostFile = @"C:\Windows\System32\drivers\etc\hosts"; - try - { - if (File.Exists(hostFile)) - { - var hosts = File.ReadAllText(hostFile).Replace("\r", ""); - var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var host in hostsList) - { - if (host.StartsWith("#")) - continue; - var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - if (hostItem.Length != 2) - continue; - systemHosts.Add(hostItem.Last(), hostItem.First()); - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return systemHosts; - } - - public static async Task GetCliWrapOutput(string filePath, string? arg) - { - return await GetCliWrapOutput(filePath, arg != null ? new List() { arg } : null); - } - - public static async Task GetCliWrapOutput(string filePath, IEnumerable? args) - { - try - { - var cmd = Cli.Wrap(filePath); - if (args != null) - { - if (args.Count() == 1) - { - cmd = cmd.WithArguments(args.First()); - } - else - { - cmd = cmd.WithArguments(args); - } - } - - var result = await cmd.ExecuteBufferedAsync(); - if (result.IsSuccess) - { - return result.StandardOutput.ToString(); - } - - Logging.SaveLog(result.ToString() ?? ""); - } - catch (Exception ex) - { - Logging.SaveLog("GetCliWrapOutput", ex); - } - return null; } - #endregion 杂项 - - #region TempPath - - public static bool HasWritePermission() + try { - try + str = str.Replace(Environment.NewLine, string.Empty); + return new List(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return null; + } + } + + /// + /// Comma-separated string, sorted and then converted to List + /// + /// + /// + public static List? String2ListSorted(string str) + { + var lst = String2List(str); + lst?.Sort(); + return lst; + } + + /// + /// Base64 Encode + /// + /// + /// + public static string Base64Encode(string plainText) + { + try + { + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return string.Empty; + } + + /// + /// Base64 Decode + /// + /// + /// + public static string Base64Decode(string? plainText) + { + try + { + if (plainText.IsNullOrEmpty()) { - var basePath = GetBaseDirectory(); - //When this file exists, it is equivalent to having no permission to read and write - if (File.Exists(Path.Combine(basePath, "NotStoreConfigHere.txt"))) - { - return false; - } - - //Check if it is installed by Windows WinGet - if (IsWindows() && basePath.Contains("Users") && basePath.Contains("WinGet")) - { - return false; - } - - var tempPath = Path.Combine(basePath, "guiTemps"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - var fileName = Path.Combine(tempPath, GetGuid()); - File.Create(fileName).Close(); - File.Delete(fileName); + return ""; } - catch (Exception) + + plainText = plainText.Trim() + .Replace(Environment.NewLine, "") + .Replace("\n", "") + .Replace("\r", "") + .Replace('_', '/') + .Replace('-', '+') + .Replace(" ", ""); + + if (plainText.Length % 4 > 0) + { + plainText = plainText.PadRight(plainText.Length + 4 - (plainText.Length % 4), '='); + } + + var data = Convert.FromBase64String(plainText); + return Encoding.UTF8.GetString(data); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return string.Empty; + } + + public static bool ToBool(object obj) + { + try + { + return Convert.ToBoolean(obj); + } + catch + { + return false; + } + } + + public static string ToString(object? obj) + { + try + { + return obj?.ToString() ?? string.Empty; + } + catch + { + return string.Empty; + } + } + + public static string HumanFy(long amount) + { + if (amount <= 0) + { + return $"{amount:f1} B"; + } + + string[] units = ["KB", "MB", "GB", "TB", "PB"]; + var unitIndex = 0; + double size = amount; + + // Loop and divide by 1024 until a suitable unit is found + while (size >= 1024 && unitIndex < units.Length - 1) + { + size /= 1024; + unitIndex++; + } + + return $"{size:f1} {units[unitIndex]}"; + } + + public static string UrlEncode(string url) + { + return Uri.EscapeDataString(url); + } + + public static string UrlDecode(string url) + { + return Uri.UnescapeDataString(url); + } + + public static NameValueCollection ParseQueryString(string query) + { + var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); + if (query.IsNullOrEmpty()) + { + return result; + } + + var parts = query[1..].Split('&', StringSplitOptions.RemoveEmptyEntries); + foreach (var part in parts) + { + var keyValue = part.Split('='); + if (keyValue.Length != 2) + { + continue; + } + + var key = Uri.UnescapeDataString(keyValue.First()); + var val = Uri.UnescapeDataString(keyValue.Last()); + + if (result[key] is null) + { + result.Add(key, val); + } + } + + return result; + } + + public static string GetMd5(string str) + { + if (string.IsNullOrEmpty(str)) + { + return string.Empty; + } + + try + { + var byteOld = Encoding.UTF8.GetBytes(str); + var byteNew = MD5.HashData(byteOld); + StringBuilder sb = new(32); + foreach (var b in byteNew) + { + sb.Append(b.ToString("x2")); + } + + return sb.ToString(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return string.Empty; + } + } + + public static string GetFileHash(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return string.Empty; + } + + if (!File.Exists(filePath)) + { + return string.Empty; + } + + try + { + using var md5 = MD5.Create(); + using var stream = File.OpenRead(filePath); + var hash = md5.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return string.Empty; + } + } + + /// + /// idn to idc + /// + /// + /// + public static string GetPunycode(string url) + { + if (url.IsNullOrEmpty()) + { + return url; + } + + try + { + Uri uri = new(url); + if (uri.Host == uri.IdnHost || uri.Host == $"[{uri.IdnHost}]") + { + return url; + } + else + { + return url.Replace(uri.Host, uri.IdnHost); + } + } + catch + { + return url; + } + } + + public static bool IsBase64String(string? plainText) + { + if (plainText.IsNullOrEmpty()) + return false; + var buffer = new Span(new byte[plainText.Length]); + return Convert.TryFromBase64String(plainText, buffer, out var _); + } + + public static string Convert2Comma(string text) + { + if (text.IsNullOrEmpty()) + { + return text; + } + + return text.Replace(",", ",").Replace(Environment.NewLine, ","); + } + + #endregion 转换函数 + + #region 数据检查 + + /// + /// Determine if the input is a number + /// + /// + /// + public static bool IsNumeric(string oText) + { + return oText.All(char.IsNumber); + } + + /// + /// Validate if the domain address is valid + /// + /// + public static bool IsDomain(string? domain) + { + if (domain.IsNullOrEmpty()) + { + return false; + } + + return Uri.CheckHostName(domain) == UriHostNameType.Dns; + } + + public static bool IsIpv6(string ip) + { + if (IPAddress.TryParse(ip, out var address)) + { + return address.AddressFamily switch + { + AddressFamily.InterNetwork => false, + AddressFamily.InterNetworkV6 => true, + _ => false, + }; + } + + return false; + } + + public static Uri? TryUri(string url) + { + try + { + return new Uri(url); + } + catch (UriFormatException) + { + return null; + } + } + + public static bool IsPrivateNetwork(string ip) + { + if (IPAddress.TryParse(ip, out var address)) + { + var ipBytes = address.GetAddressBytes(); + if (ipBytes[0] == 10) + return true; + if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) + return true; + if (ipBytes[0] == 192 && ipBytes[1] == 168) + return true; + } + + return false; + } + + #endregion 数据检查 + + #region 测速 + + private static bool PortInUse(int port) + { + try + { + List lstIpEndPoints = new(); + List lstTcpConns = new(); + + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); + + if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0) + { + return true; + } + + if (lstTcpConns?.FindIndex(it => it.LocalEndPoint.Port == port) >= 0) + { + return true; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return false; + } + + public static int GetFreePort(int defaultPort = 9090) + { + try + { + if (!Utils.PortInUse(defaultPort)) + { + return defaultPort; + } + + TcpListener l = new(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + catch + { + } + + return 59090; + } + + #endregion 测速 + + #region 杂项 + + public static bool UpgradeAppExists(out string upgradeFileName) + { + upgradeFileName = Path.Combine(GetBaseDirectory(), GetExeName("AmazTool")); + return File.Exists(upgradeFileName); + } + + /// + /// Get version + /// + /// + public static string GetVersion(bool blFull = true) + { + try + { + return blFull + ? $"{Global.AppName} - V{GetVersionInfo()} - {RuntimeInformation.ProcessArchitecture}" + : $"{Global.AppName}/{GetVersionInfo()}"; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return Global.AppName; + } + + public static string GetVersionInfo() + { + try + { + return Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(3) ?? "0.0"; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return "0.0"; + } + } + + public static string GetRuntimeInfo() + { + return $"{Utils.GetVersion()} | {Utils.StartupPath()} | {Utils.GetExePath()} | {Environment.OSVersion}"; + } + + /// + /// GUID + /// + /// + public static string GetGuid(bool full = true) + { + try + { + if (full) + { + return Guid.NewGuid().ToString("D"); + } + else + { + return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0).ToString(); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return string.Empty; + } + + public static bool IsGuidByParse(string strSrc) + { + return Guid.TryParse(strSrc, out _); + } + + public static Dictionary GetSystemHosts() + { + var systemHosts = new Dictionary(); + var hostFile = @"C:\Windows\System32\drivers\etc\hosts"; + try + { + if (File.Exists(hostFile)) + { + var hosts = File.ReadAllText(hostFile).Replace("\r", ""); + var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var host in hostsList) + { + if (host.StartsWith("#")) + continue; + var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (hostItem.Length != 2) + continue; + systemHosts.Add(hostItem.Last(), hostItem.First()); + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return systemHosts; + } + + public static async Task GetCliWrapOutput(string filePath, string? arg) + { + return await GetCliWrapOutput(filePath, arg != null ? new List() { arg } : null); + } + + public static async Task GetCliWrapOutput(string filePath, IEnumerable? args) + { + try + { + var cmd = Cli.Wrap(filePath); + if (args != null) + { + if (args.Count() == 1) + { + cmd = cmd.WithArguments(args.First()); + } + else + { + cmd = cmd.WithArguments(args); + } + } + + var result = await cmd.ExecuteBufferedAsync(); + if (result.IsSuccess) + { + return result.StandardOutput.ToString(); + } + + Logging.SaveLog(result.ToString() ?? ""); + } + catch (Exception ex) + { + Logging.SaveLog("GetCliWrapOutput", ex); + } + + return null; + } + + #endregion 杂项 + + #region TempPath + + public static bool HasWritePermission() + { + try + { + var basePath = GetBaseDirectory(); + //When this file exists, it is equivalent to having no permission to read and write + if (File.Exists(Path.Combine(basePath, "NotStoreConfigHere.txt"))) { return false; } - return true; - } - - public static string GetPath(string fileName) - { - var startupPath = StartupPath(); - if (fileName.IsNullOrEmpty()) + //Check if it is installed by Windows WinGet + if (IsWindows() && basePath.Contains("Users") && basePath.Contains("WinGet")) { - return startupPath; + return false; } - return Path.Combine(startupPath, fileName); - } - - public static string GetBaseDirectory(string fileName = "") - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - public static string GetExePath() - { - return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; - } - - public static string StartupPath() - { - if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") - { - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "v2rayN"); - } - - return GetBaseDirectory(); - } - - public static string GetTempPath(string filename = "") - { - var tempPath = Path.Combine(StartupPath(), "guiTemps"); + var tempPath = Path.Combine(basePath, "guiTemps"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } + var fileName = Path.Combine(tempPath, GetGuid()); + File.Create(fileName).Close(); + File.Delete(fileName); + } + catch (Exception) + { + return false; } - public static string GetBackupPath(string filename) - { - var tempPath = Path.Combine(StartupPath(), "guiBackups"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } + return true; + } + public static string GetPath(string fileName) + { + var startupPath = StartupPath(); + if (fileName.IsNullOrEmpty()) + { + return startupPath; + } + + return Path.Combine(startupPath, fileName); + } + + public static string GetBaseDirectory(string fileName = "") + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + public static string GetExePath() + { + return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; + } + + public static string StartupPath() + { + if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "v2rayN"); + } + + return GetBaseDirectory(); + } + + public static string GetTempPath(string filename = "") + { + var tempPath = Path.Combine(StartupPath(), "guiTemps"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { return Path.Combine(tempPath, filename); } - - public static string GetConfigPath(string filename = "") - { - var tempPath = Path.Combine(StartupPath(), "guiConfigs"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } - } - - public static string GetBinPath(string filename, string? coreType = null) - { - var tempPath = Path.Combine(StartupPath(), "bin"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - - if (coreType != null) - { - tempPath = Path.Combine(tempPath, coreType.ToLower().ToString()); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } - } - - public static string GetLogPath(string filename = "") - { - var tempPath = Path.Combine(StartupPath(), "guiLogs"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } - } - - public static string GetFontsPath(string filename = "") - { - var tempPath = Path.Combine(StartupPath(), "guiFonts"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } - } - - public static string GetBinConfigPath(string filename = "") - { - var tempPath = Path.Combine(StartupPath(), "binConfigs"); - if (!Directory.Exists(tempPath)) - { - Directory.CreateDirectory(tempPath); - } - - if (filename.IsNullOrEmpty()) - { - return tempPath; - } - else - { - return Path.Combine(tempPath, filename); - } - } - - #endregion TempPath - - #region Platform - - public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - - public static bool IsOSX() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - - public static bool IsNonWindows() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - public static string GetExeName(string name) - { - return IsWindows() ? $"{name}.exe" : name; - } - - public static bool IsAdministrator() - { - if (IsWindows()) - { - return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); - } - return false; - //else - //{ - // var id = GetLinuxUserId().Result ?? "1000"; - // if (int.TryParse(id, out var userId)) - // { - // return userId == 0; - // } - // else - // { - // return false; - // } - //} - } - - private static async Task GetLinuxUserId() - { - var arg = new List() { "-c", "id -u" }; - return await GetCliWrapOutput(Global.LinuxBash, arg); - } - - public static async Task SetLinuxChmod(string? fileName) - { - if (fileName.IsNullOrEmpty()) - return null; - if (fileName.Contains(' ')) - fileName = fileName.AppendQuotes(); - //File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); - var arg = new List() { "-c", $"chmod +x {fileName}" }; - return await GetCliWrapOutput(Global.LinuxBash, arg); - } - - public static async Task GetLinuxFontFamily(string lang) - { - // var arg = new List() { "-c", $"fc-list :lang={lang} family" }; - var arg = new List() { "-c", $"fc-list : family" }; - return await GetCliWrapOutput(Global.LinuxBash, arg); - } - - public static string? GetHomePath() - { - return IsWindows() - ? Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%") - : Environment.GetEnvironmentVariable("HOME"); - } - - #endregion Platform } + + public static string GetBackupPath(string filename) + { + var tempPath = Path.Combine(StartupPath(), "guiBackups"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + return Path.Combine(tempPath, filename); + } + + public static string GetConfigPath(string filename = "") + { + var tempPath = Path.Combine(StartupPath(), "guiConfigs"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { + return Path.Combine(tempPath, filename); + } + } + + public static string GetBinPath(string filename, string? coreType = null) + { + var tempPath = Path.Combine(StartupPath(), "bin"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (coreType != null) + { + tempPath = Path.Combine(tempPath, coreType.ToLower().ToString()); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { + return Path.Combine(tempPath, filename); + } + } + + public static string GetLogPath(string filename = "") + { + var tempPath = Path.Combine(StartupPath(), "guiLogs"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { + return Path.Combine(tempPath, filename); + } + } + + public static string GetFontsPath(string filename = "") + { + var tempPath = Path.Combine(StartupPath(), "guiFonts"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { + return Path.Combine(tempPath, filename); + } + } + + public static string GetBinConfigPath(string filename = "") + { + var tempPath = Path.Combine(StartupPath(), "binConfigs"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + + if (filename.IsNullOrEmpty()) + { + return tempPath; + } + else + { + return Path.Combine(tempPath, filename); + } + } + + #endregion TempPath + + #region Platform + + public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + public static bool IsOSX() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + + public static bool IsNonWindows() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + public static string GetExeName(string name) + { + return IsWindows() ? $"{name}.exe" : name; + } + + public static bool IsAdministrator() + { + if (IsWindows()) + { + return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + } + return false; + //else + //{ + // var id = GetLinuxUserId().Result ?? "1000"; + // if (int.TryParse(id, out var userId)) + // { + // return userId == 0; + // } + // else + // { + // return false; + // } + //} + } + + private static async Task GetLinuxUserId() + { + var arg = new List() { "-c", "id -u" }; + return await GetCliWrapOutput(Global.LinuxBash, arg); + } + + public static async Task SetLinuxChmod(string? fileName) + { + if (fileName.IsNullOrEmpty()) + return null; + if (fileName.Contains(' ')) + fileName = fileName.AppendQuotes(); + //File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + var arg = new List() { "-c", $"chmod +x {fileName}" }; + return await GetCliWrapOutput(Global.LinuxBash, arg); + } + + public static async Task GetLinuxFontFamily(string lang) + { + // var arg = new List() { "-c", $"fc-list :lang={lang} family" }; + var arg = new List() { "-c", $"fc-list : family" }; + return await GetCliWrapOutput(Global.LinuxBash, arg); + } + + public static string? GetHomePath() + { + return IsWindows() + ? Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%") + : Environment.GetEnvironmentVariable("HOME"); + } + + #endregion Platform } diff --git a/v2rayN/ServiceLib/Common/WindowsUtils.cs b/v2rayN/ServiceLib/Common/WindowsUtils.cs index 1d4740c3..6c6f7be1 100644 --- a/v2rayN/ServiceLib/Common/WindowsUtils.cs +++ b/v2rayN/ServiceLib/Common/WindowsUtils.cs @@ -2,73 +2,72 @@ using System.Security.Cryptography; using System.Text; using Microsoft.Win32; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +internal static class WindowsUtils { - internal static class WindowsUtils + private static readonly string _tag = "WindowsUtils"; + + public static string? RegReadValue(string path, string name, string def) { - private static readonly string _tag = "WindowsUtils"; - - public static string? RegReadValue(string path, string name, string def) + RegistryKey? regKey = null; + try { - RegistryKey? regKey = null; - try - { - regKey = Registry.CurrentUser.OpenSubKey(path, false); - var value = regKey?.GetValue(name) as string; - return value.IsNullOrEmpty() ? def : value; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - finally - { - regKey?.Close(); - } - return def; + regKey = Registry.CurrentUser.OpenSubKey(path, false); + var value = regKey?.GetValue(name) as string; + return value.IsNullOrEmpty() ? def : value; } - - public static void RegWriteValue(string path, string name, object value) + catch (Exception ex) { - RegistryKey? regKey = null; - try + Logging.SaveLog(_tag, ex); + } + finally + { + regKey?.Close(); + } + return def; + } + + public static void RegWriteValue(string path, string name, object value) + { + RegistryKey? regKey = null; + try + { + regKey = Registry.CurrentUser.CreateSubKey(path); + if (value.ToString().IsNullOrEmpty()) { - regKey = Registry.CurrentUser.CreateSubKey(path); - if (value.ToString().IsNullOrEmpty()) - { - regKey?.DeleteValue(name, false); - } - else - { - regKey?.SetValue(name, value); - } + regKey?.DeleteValue(name, false); } - catch (Exception ex) + else { - Logging.SaveLog(_tag, ex); - } - finally - { - regKey?.Close(); + regKey?.SetValue(name, value); } } - - public static async Task RemoveTunDevice() + catch (Exception ex) { - try - { - var sum = MD5.HashData(Encoding.UTF8.GetBytes("wintunsingbox_tun")); - var guid = new Guid(sum); - var pnpUtilPath = @"C:\Windows\System32\pnputil.exe"; - var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """; + Logging.SaveLog(_tag, ex); + } + finally + { + regKey?.Close(); + } + } - // Try to remove the device - _ = await Utils.GetCliWrapOutput(pnpUtilPath, arg); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } + public static async Task RemoveTunDevice() + { + try + { + var sum = MD5.HashData(Encoding.UTF8.GetBytes("wintunsingbox_tun")); + var guid = new Guid(sum); + var pnpUtilPath = @"C:\Windows\System32\pnputil.exe"; + var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """; + + // Try to remove the device + _ = await Utils.GetCliWrapOutput(pnpUtilPath, arg); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); } } } diff --git a/v2rayN/ServiceLib/Common/YamlUtils.cs b/v2rayN/ServiceLib/Common/YamlUtils.cs index 511e41e5..e9be015f 100644 --- a/v2rayN/ServiceLib/Common/YamlUtils.cs +++ b/v2rayN/ServiceLib/Common/YamlUtils.cs @@ -2,79 +2,78 @@ using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -namespace ServiceLib.Common +namespace ServiceLib.Common; + +public class YamlUtils { - public class YamlUtils + private static readonly string _tag = "YamlUtils"; + + #region YAML + + /// + /// 反序列化成对象 + /// + /// + /// + /// + public static T FromYaml(string str) { - private static readonly string _tag = "YamlUtils"; - - #region YAML - - /// - /// 反序列化成对象 - /// - /// - /// - /// - public static T FromYaml(string str) + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + try { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .Build(); - try - { - var obj = deserializer.Deserialize(str); - return obj; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return deserializer.Deserialize(""); - } + var obj = deserializer.Deserialize(str); + return obj; } - - /// - /// 序列化 - /// - /// - /// - public static string ToYaml(object? obj) + catch (Exception ex) { - var result = string.Empty; - if (obj == null) - { - return result; - } - var serializer = new SerializerBuilder() - .WithNamingConvention(HyphenatedNamingConvention.Instance) - .Build(); + Logging.SaveLog(_tag, ex); + return deserializer.Deserialize(""); + } + } - try - { - result = serializer.Serialize(obj); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } + /// + /// 序列化 + /// + /// + /// + public static string ToYaml(object? obj) + { + var result = string.Empty; + if (obj == null) + { return result; } + var serializer = new SerializerBuilder() + .WithNamingConvention(HyphenatedNamingConvention.Instance) + .Build(); - public static string? PreprocessYaml(string str) + try { - try - { - var mergingParser = new MergingParser(new Parser(new StringReader(str))); - var obj = new DeserializerBuilder().Build().Deserialize(mergingParser); - return ToYaml(obj); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return null; - } + result = serializer.Serialize(obj); } - - #endregion YAML + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return result; } + + public static string? PreprocessYaml(string str) + { + try + { + var mergingParser = new MergingParser(new Parser(new StringReader(str))); + var obj = new DeserializerBuilder().Build().Deserialize(mergingParser); + return ToYaml(obj); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return null; + } + } + + #endregion YAML } diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index 5c73e728..f56d0e0f 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -1,16 +1,15 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EConfigType { - public enum EConfigType - { - VMess = 1, - Custom = 2, - Shadowsocks = 3, - SOCKS = 4, - VLESS = 5, - Trojan = 6, - Hysteria2 = 7, - TUIC = 8, - WireGuard = 9, - HTTP = 10 - } -} \ No newline at end of file + VMess = 1, + Custom = 2, + Shadowsocks = 3, + SOCKS = 4, + VLESS = 5, + Trojan = 6, + Hysteria2 = 7, + TUIC = 8, + WireGuard = 9, + HTTP = 10 +} diff --git a/v2rayN/ServiceLib/Enums/ECoreType.cs b/v2rayN/ServiceLib/Enums/ECoreType.cs index d2252078..6700fe44 100644 --- a/v2rayN/ServiceLib/Enums/ECoreType.cs +++ b/v2rayN/ServiceLib/Enums/ECoreType.cs @@ -1,19 +1,18 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ECoreType { - public enum ECoreType - { - v2fly = 1, - Xray = 2, - v2fly_v5 = 4, - mihomo = 13, - hysteria = 21, - naiveproxy = 22, - tuic = 23, - sing_box = 24, - juicity = 25, - hysteria2 = 26, - brook = 27, - overtls = 28, - v2rayN = 99 - } + v2fly = 1, + Xray = 2, + v2fly_v5 = 4, + mihomo = 13, + hysteria = 21, + naiveproxy = 22, + tuic = 23, + sing_box = 24, + juicity = 25, + hysteria2 = 26, + brook = 27, + overtls = 28, + v2rayN = 99 } diff --git a/v2rayN/ServiceLib/Enums/EGirdOrientation.cs b/v2rayN/ServiceLib/Enums/EGirdOrientation.cs index 1a62c7bb..e45d3b71 100644 --- a/v2rayN/ServiceLib/Enums/EGirdOrientation.cs +++ b/v2rayN/ServiceLib/Enums/EGirdOrientation.cs @@ -1,9 +1,8 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EGirdOrientation { - public enum EGirdOrientation - { - Horizontal, - Vertical, - Tab, - } -} \ No newline at end of file + Horizontal, + Vertical, + Tab, +} diff --git a/v2rayN/ServiceLib/Enums/EGlobalHotkey.cs b/v2rayN/ServiceLib/Enums/EGlobalHotkey.cs index 328bf36e..3ec982aa 100644 --- a/v2rayN/ServiceLib/Enums/EGlobalHotkey.cs +++ b/v2rayN/ServiceLib/Enums/EGlobalHotkey.cs @@ -1,11 +1,10 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EGlobalHotkey { - public enum EGlobalHotkey - { - ShowForm = 0, - SystemProxyClear = 1, - SystemProxySet = 2, - SystemProxyUnchanged = 3, - SystemProxyPac = 4, - } -} \ No newline at end of file + ShowForm = 0, + SystemProxyClear = 1, + SystemProxySet = 2, + SystemProxyUnchanged = 3, + SystemProxyPac = 4, +} diff --git a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs index 6da9d1da..768a428b 100644 --- a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs +++ b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs @@ -1,14 +1,13 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EInboundProtocol { - public enum EInboundProtocol - { - socks = 0, - socks2, - socks3, - pac, - api, - api2, - mixed, - speedtest = 21 - } -} \ No newline at end of file + socks = 0, + socks2, + socks3, + pac, + api, + api2, + mixed, + speedtest = 21 +} diff --git a/v2rayN/ServiceLib/Enums/EMove.cs b/v2rayN/ServiceLib/Enums/EMove.cs index ced5a4e6..86b14c75 100644 --- a/v2rayN/ServiceLib/Enums/EMove.cs +++ b/v2rayN/ServiceLib/Enums/EMove.cs @@ -1,11 +1,10 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EMove { - public enum EMove - { - Top = 1, - Up = 2, - Down = 3, - Bottom = 4, - Position = 5 - } -} \ No newline at end of file + Top = 1, + Up = 2, + Down = 3, + Bottom = 4, + Position = 5 +} diff --git a/v2rayN/ServiceLib/Enums/EMsgCommand.cs b/v2rayN/ServiceLib/Enums/EMsgCommand.cs index 9bf844ff..23be4ac1 100644 --- a/v2rayN/ServiceLib/Enums/EMsgCommand.cs +++ b/v2rayN/ServiceLib/Enums/EMsgCommand.cs @@ -1,11 +1,10 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EMsgCommand { - public enum EMsgCommand - { - ClearMsg, - SendMsgView, - SendSnackMsg, - RefreshProfiles, - AppExit - } + ClearMsg, + SendMsgView, + SendSnackMsg, + RefreshProfiles, + AppExit } diff --git a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs index a5541f91..42be7a5b 100644 --- a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs +++ b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs @@ -1,10 +1,9 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EMultipleLoad { - public enum EMultipleLoad - { - Random, - RoundRobin, - LeastPing, - LeastLoad - } + Random, + RoundRobin, + LeastPing, + LeastLoad } diff --git a/v2rayN/ServiceLib/Enums/EPresetType.cs b/v2rayN/ServiceLib/Enums/EPresetType.cs index 855b10a1..a1763b33 100644 --- a/v2rayN/ServiceLib/Enums/EPresetType.cs +++ b/v2rayN/ServiceLib/Enums/EPresetType.cs @@ -1,9 +1,8 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EPresetType { - public enum EPresetType - { - Default = 0, - Russia = 1, - Iran = 2, - } -} \ No newline at end of file + Default = 0, + Russia = 1, + Iran = 2, +} diff --git a/v2rayN/ServiceLib/Enums/ERuleMode.cs b/v2rayN/ServiceLib/Enums/ERuleMode.cs index 179384d5..fd89449d 100644 --- a/v2rayN/ServiceLib/Enums/ERuleMode.cs +++ b/v2rayN/ServiceLib/Enums/ERuleMode.cs @@ -1,10 +1,9 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ERuleMode { - public enum ERuleMode - { - Rule = 0, - Global = 1, - Direct = 2, - Unchanged = 3 - } -} \ No newline at end of file + Rule = 0, + Global = 1, + Direct = 2, + Unchanged = 3 +} diff --git a/v2rayN/ServiceLib/Enums/EServerColName.cs b/v2rayN/ServiceLib/Enums/EServerColName.cs index ef981b74..9f50f4df 100644 --- a/v2rayN/ServiceLib/Enums/EServerColName.cs +++ b/v2rayN/ServiceLib/Enums/EServerColName.cs @@ -1,21 +1,20 @@ -namespace ServiceLib.Enums -{ - public enum EServerColName - { - Def = 0, - ConfigType, - Remarks, - Address, - Port, - Network, - StreamSecurity, - SubRemarks, - DelayVal, - SpeedVal, +namespace ServiceLib.Enums; - TodayDown, - TodayUp, - TotalDown, - TotalUp - } -} \ No newline at end of file +public enum EServerColName +{ + Def = 0, + ConfigType, + Remarks, + Address, + Port, + Network, + StreamSecurity, + SubRemarks, + DelayVal, + SpeedVal, + + TodayDown, + TodayUp, + TotalDown, + TotalUp +} diff --git a/v2rayN/ServiceLib/Enums/ESpeedActionType.cs b/v2rayN/ServiceLib/Enums/ESpeedActionType.cs index 63f13919..0478bcc0 100644 --- a/v2rayN/ServiceLib/Enums/ESpeedActionType.cs +++ b/v2rayN/ServiceLib/Enums/ESpeedActionType.cs @@ -1,10 +1,9 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ESpeedActionType { - public enum ESpeedActionType - { - Tcping, - Realping, - Speedtest, - Mixedtest - } -} \ No newline at end of file + Tcping, + Realping, + Speedtest, + Mixedtest +} diff --git a/v2rayN/ServiceLib/Enums/ESysProxyType.cs b/v2rayN/ServiceLib/Enums/ESysProxyType.cs index 84beaffe..39816d42 100644 --- a/v2rayN/ServiceLib/Enums/ESysProxyType.cs +++ b/v2rayN/ServiceLib/Enums/ESysProxyType.cs @@ -1,10 +1,9 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ESysProxyType { - public enum ESysProxyType - { - ForcedClear = 0, - ForcedChange = 1, - Unchanged = 2, - Pac = 3 - } -} \ No newline at end of file + ForcedClear = 0, + ForcedChange = 1, + Unchanged = 2, + Pac = 3 +} diff --git a/v2rayN/ServiceLib/Enums/ETheme.cs b/v2rayN/ServiceLib/Enums/ETheme.cs index 7701ae8f..6ea1e4f1 100644 --- a/v2rayN/ServiceLib/Enums/ETheme.cs +++ b/v2rayN/ServiceLib/Enums/ETheme.cs @@ -1,13 +1,12 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ETheme { - public enum ETheme - { - FollowSystem, - Dark, - Light, - Aquatic, - Desert, - Dusk, - NightSky - } -} \ No newline at end of file + FollowSystem, + Dark, + Light, + Aquatic, + Desert, + Dusk, + NightSky +} diff --git a/v2rayN/ServiceLib/Enums/ETransport.cs b/v2rayN/ServiceLib/Enums/ETransport.cs index 2cd43821..b1166608 100644 --- a/v2rayN/ServiceLib/Enums/ETransport.cs +++ b/v2rayN/ServiceLib/Enums/ETransport.cs @@ -1,15 +1,14 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum ETransport { - public enum ETransport - { - tcp, - kcp, - ws, - httpupgrade, - xhttp, - h2, - http, - quic, - grpc - } -} \ No newline at end of file + tcp, + kcp, + ws, + httpupgrade, + xhttp, + h2, + http, + quic, + grpc +} diff --git a/v2rayN/ServiceLib/Enums/EViewAction.cs b/v2rayN/ServiceLib/Enums/EViewAction.cs index c37e1212..75142925 100644 --- a/v2rayN/ServiceLib/Enums/EViewAction.cs +++ b/v2rayN/ServiceLib/Enums/EViewAction.cs @@ -1,46 +1,45 @@ -namespace ServiceLib.Enums +namespace ServiceLib.Enums; + +public enum EViewAction { - public enum EViewAction - { - CloseWindow, - ShowYesNo, - SaveFileDialog, - AddBatchRoutingRulesYesNo, - AdjustMainLvColWidth, - SetClipboardData, - AddServerViaClipboard, - ImportRulesFromClipboard, - ProfilesFocus, - ShareSub, - ShareServer, - ShowHideWindow, - ScanScreenTask, - ScanImageTask, - Shutdown, - BrowseServer, - ImportRulesFromFile, - InitSettingFont, - SubEditWindow, - RoutingRuleSettingWindow, - RoutingRuleDetailsWindow, - AddServerWindow, - AddServer2Window, - DNSSettingWindow, - RoutingSettingWindow, - OptionSettingWindow, - GlobalHotkeySettingWindow, - SubSettingWindow, - DispatcherSpeedTest, - DispatcherRefreshConnections, - DispatcherRefreshProxyGroups, - DispatcherProxiesDelayTest, - DispatcherStatistics, - DispatcherServerAvailability, - DispatcherReload, - DispatcherRefreshServersBiz, - DispatcherRefreshIcon, - DispatcherCheckUpdate, - DispatcherCheckUpdateFinished, - DispatcherShowMsg, - } -} \ No newline at end of file + CloseWindow, + ShowYesNo, + SaveFileDialog, + AddBatchRoutingRulesYesNo, + AdjustMainLvColWidth, + SetClipboardData, + AddServerViaClipboard, + ImportRulesFromClipboard, + ProfilesFocus, + ShareSub, + ShareServer, + ShowHideWindow, + ScanScreenTask, + ScanImageTask, + Shutdown, + BrowseServer, + ImportRulesFromFile, + InitSettingFont, + SubEditWindow, + RoutingRuleSettingWindow, + RoutingRuleDetailsWindow, + AddServerWindow, + AddServer2Window, + DNSSettingWindow, + RoutingSettingWindow, + OptionSettingWindow, + GlobalHotkeySettingWindow, + SubSettingWindow, + DispatcherSpeedTest, + DispatcherRefreshConnections, + DispatcherRefreshProxyGroups, + DispatcherProxiesDelayTest, + DispatcherStatistics, + DispatcherServerAvailability, + DispatcherReload, + DispatcherRefreshServersBiz, + DispatcherRefreshIcon, + DispatcherCheckUpdate, + DispatcherCheckUpdateFinished, + DispatcherShowMsg, +} diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index da70326b..50b2cd2e 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -1,155 +1,155 @@ -namespace ServiceLib +namespace ServiceLib; + +public class Global { - public class Global - { - #region const + #region const - public const string AppName = "v2rayN"; - public const string GithubUrl = "https://github.com"; - public const string GithubApiUrl = "https://api.github.com/repos"; - public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; - public const string SpeedPingTestUrl = @"https://www.google.com/generate_204"; - public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs"; - public const string IPAPIUrl = "https://api.ip.sb/geoip"; + public const string AppName = "v2rayN"; + public const string GithubUrl = "https://github.com"; + public const string GithubApiUrl = "https://api.github.com/repos"; + public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; + public const string SpeedPingTestUrl = @"https://www.google.com/generate_204"; + public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs"; + public const string IPAPIUrl = "https://api.ip.sb/geoip"; - public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; - public const string ConfigFileName = "guiNConfig.json"; - public const string CoreConfigFileName = "config.json"; - public const string CorePreConfigFileName = "configPre.json"; - public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; - public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; - public const string ClashMixinConfigFileName = "Mixin.yaml"; + public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; + public const string ConfigFileName = "guiNConfig.json"; + public const string CoreConfigFileName = "config.json"; + public const string CorePreConfigFileName = "configPre.json"; + public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; + public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; + public const string ClashMixinConfigFileName = "Mixin.yaml"; - public const string NamespaceSample = "ServiceLib.Sample."; - public const string V2raySampleClient = NamespaceSample + "SampleClientConfig"; - public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig"; - public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest"; - public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse"; - public const string V2raySampleInbound = NamespaceSample + "SampleInbound"; - public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound"; - public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound"; - public const string CustomRoutingFileName = NamespaceSample + "custom_routing_"; - public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns"; - public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound"; - public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules"; - public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal"; - public const string DNSSingboxNormalFileName = NamespaceSample + "dns_singbox_normal"; - public const string ClashMixinYaml = NamespaceSample + "clash_mixin_yaml"; - public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml"; - public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config"; - public const string PacFileName = NamespaceSample + "pac"; - public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh"; - public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh"; + public const string NamespaceSample = "ServiceLib.Sample."; + public const string V2raySampleClient = NamespaceSample + "SampleClientConfig"; + public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig"; + public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest"; + public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse"; + public const string V2raySampleInbound = NamespaceSample + "SampleInbound"; + public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound"; + public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound"; + public const string CustomRoutingFileName = NamespaceSample + "custom_routing_"; + public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns"; + public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound"; + public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules"; + public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal"; + public const string DNSSingboxNormalFileName = NamespaceSample + "dns_singbox_normal"; + public const string ClashMixinYaml = NamespaceSample + "clash_mixin_yaml"; + public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml"; + public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config"; + public const string PacFileName = NamespaceSample + "pac"; + public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh"; + public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh"; - public const string DefaultSecurity = "auto"; - public const string DefaultNetwork = "tcp"; - public const string TcpHeaderHttp = "http"; - public const string None = "none"; - public const string ProxyTag = "proxy"; - public const string DirectTag = "direct"; - public const string BlockTag = "block"; - public const string StreamSecurity = "tls"; - public const string StreamSecurityReality = "reality"; - public const string Loopback = "127.0.0.1"; - public const string InboundAPIProtocol = "dokodemo-door"; - public const string HttpProtocol = "http://"; - public const string HttpsProtocol = "https://"; - public const string SocksProtocol = "socks://"; - public const string Socks5Protocol = "socks5://"; + public const string DefaultSecurity = "auto"; + public const string DefaultNetwork = "tcp"; + public const string TcpHeaderHttp = "http"; + public const string None = "none"; + public const string ProxyTag = "proxy"; + public const string DirectTag = "direct"; + public const string BlockTag = "block"; + public const string StreamSecurity = "tls"; + public const string StreamSecurityReality = "reality"; + public const string Loopback = "127.0.0.1"; + public const string InboundAPIProtocol = "dokodemo-door"; + public const string HttpProtocol = "http://"; + public const string HttpsProtocol = "https://"; + public const string SocksProtocol = "socks://"; + public const string Socks5Protocol = "socks5://"; - public const string UserEMail = "t@t.tt"; - public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; - public const string AutoRunName = "v2rayNAutoRun"; - public const string SystemProxyExceptionsWindows = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"; - public const string SystemProxyExceptionsLinux = "localhost,127.0.0.0/8,::1"; - public const string RoutingRuleComma = ""; - public const string GrpcGunMode = "gun"; - public const string GrpcMultiMode = "multi"; - public const int MaxPort = 65536; - public const int MinFontSize = 8; - public const string RebootAs = "rebootas"; - public const string AvaAssets = "avares://v2rayN/Assets/"; - public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2"; - public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET"; - public const string XrayLocalAsset = "XRAY_LOCATION_ASSET"; - public const string XrayLocalCert = "XRAY_LOCATION_CERT"; - public const int SpeedTestPageSize = 1000; - public const string LinuxBash = "/bin/bash"; + public const string UserEMail = "t@t.tt"; + public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; + public const string AutoRunName = "v2rayNAutoRun"; + public const string SystemProxyExceptionsWindows = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"; + public const string SystemProxyExceptionsLinux = "localhost,127.0.0.0/8,::1"; + public const string RoutingRuleComma = ""; + public const string GrpcGunMode = "gun"; + public const string GrpcMultiMode = "multi"; + public const int MaxPort = 65536; + public const int MinFontSize = 8; + public const string RebootAs = "rebootas"; + public const string AvaAssets = "avares://v2rayN/Assets/"; + public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2"; + public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET"; + public const string XrayLocalAsset = "XRAY_LOCATION_ASSET"; + public const string XrayLocalCert = "XRAY_LOCATION_CERT"; + public const int SpeedTestPageSize = 1000; + public const string LinuxBash = "/bin/bash"; - public static readonly List IEProxyProtocols = - [ - "{ip}:{http_port}", + public static readonly List IEProxyProtocols = + [ + "{ip}:{http_port}", "socks={ip}:{socks_port}", "http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}", "http=http://{ip}:{http_port};https=http://{ip}:{http_port}", "" - ]; + ]; - public static readonly List SubConvertUrls = - [ - @"https://sub.xeton.dev/sub?url={0}", + public static readonly List SubConvertUrls = + [ + @"https://sub.xeton.dev/sub?url={0}", @"https://api.dler.io/sub?url={0}", @"http://127.0.0.1:25500/sub?url={0}", "" - ]; + ]; - public static readonly List SubConvertConfig = - [@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"]; + public static readonly List SubConvertConfig = + [@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"]; - public static readonly List SubConvertTargets = - [ - "", + public static readonly List SubConvertTargets = + [ + "", "mixed", "v2ray", "clash", "ss" - ]; + ]; - public static readonly List SpeedTestUrls = - [ - @"https://cachefly.cachefly.net/50mb.test", + public static readonly List SpeedTestUrls = + [ + @"https://cachefly.cachefly.net/50mb.test", @"https://speed.cloudflare.com/__down?bytes=10000000", @"https://speed.cloudflare.com/__down?bytes=50000000", @"https://speed.cloudflare.com/__down?bytes=100000000", ]; - public static readonly List SpeedPingTestUrls = - [ - @"https://www.google.com/generate_204", + public static readonly List SpeedPingTestUrls = + [ + @"https://www.google.com/generate_204", @"https://www.gstatic.com/generate_204", @"https://www.apple.com/library/test/success.html", @"http://www.msftconnecttest.com/connecttest.txt" - ]; + ]; - public static readonly List GeoFilesSources = - [ - "", + public static readonly List GeoFilesSources = + [ + "", @"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat", @"https://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/{0}.dat" - ]; + ]; - public static readonly List SingboxRulesetSources = - [ - "", + public static readonly List SingboxRulesetSources = + [ + "", @"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-rules-dat@release/sing-box/rule-set-{0}/{1}.srs", @"https://cdn.jsdelivr.net/gh/chocolate4u/Iran-sing-box-rules@rule-set/{1}.srs" - ]; + ]; - public static readonly List RoutingRulesSources = - [ - "", + public static readonly List RoutingRulesSources = + [ + "", @"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/template.json", @"https://cdn.jsdelivr.net/gh/Chocolate4U/Iran-v2ray-rules@main/v2rayN/template.json" - ]; + ]; - public static readonly List DNSTemplateSources = - [ - "", + public static readonly List DNSTemplateSources = + [ + "", @"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/", @"https://cdn.jsdelivr.net/gh/Chocolate4U/Iran-v2ray-rules@main/v2rayN/" - ]; + ]; - public static readonly Dictionary UserAgentTexts = new() + public static readonly Dictionary UserAgentTexts = new() { {"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" }, {"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" }, @@ -158,9 +158,9 @@ namespace ServiceLib {"none",""} }; - public const string Hysteria2ProtocolShare = "hy2://"; + public const string Hysteria2ProtocolShare = "hy2://"; - public static readonly Dictionary ProtocolShares = new() + public static readonly Dictionary ProtocolShares = new() { { EConfigType.VMess, "vmess://" }, { EConfigType.Shadowsocks, "ss://" }, @@ -172,7 +172,7 @@ namespace ServiceLib { EConfigType.WireGuard, "wireguard://" } }; - public static readonly Dictionary ProtocolTypes = new() + public static readonly Dictionary ProtocolTypes = new() { { EConfigType.VMess, "vmess" }, { EConfigType.Shadowsocks, "shadowsocks" }, @@ -185,28 +185,28 @@ namespace ServiceLib { EConfigType.WireGuard, "wireguard" } }; - public static readonly List VmessSecurities = - [ - "aes-128-gcm", + public static readonly List VmessSecurities = + [ + "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" - ]; + ]; - public static readonly List SsSecurities = - [ - "aes-256-gcm", + public static readonly List SsSecurities = + [ + "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" - ]; + ]; - public static readonly List SsSecuritiesInXray = - [ - "aes-256-gcm", + public static readonly List SsSecuritiesInXray = + [ + "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", @@ -217,11 +217,11 @@ namespace ServiceLib "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" - ]; + ]; - public static readonly List SsSecuritiesInSingbox = - [ - "aes-256-gcm", + public static readonly List SsSecuritiesInSingbox = + [ + "aes-256-gcm", "aes-192-gcm", "aes-128-gcm", "chacha20-ietf-poly1305", @@ -239,18 +239,18 @@ namespace ServiceLib "rc4-md5", "chacha20-ietf", "xchacha20" - ]; + ]; - public static readonly List Flows = - [ - "", + public static readonly List Flows = + [ + "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" - ]; + ]; - public static readonly List Networks = - [ - "tcp", + public static readonly List Networks = + [ + "tcp", "kcp", "ws", "httpupgrade", @@ -258,50 +258,50 @@ namespace ServiceLib "h2", "quic", "grpc" - ]; + ]; - public static readonly List KcpHeaderTypes = - [ - "srtp", + public static readonly List KcpHeaderTypes = + [ + "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" - ]; + ]; - public static readonly List CoreTypes = - [ - "Xray", + public static readonly List CoreTypes = + [ + "Xray", "sing_box" - ]; + ]; - public static readonly List DomainStrategies = - [ - "AsIs", + public static readonly List DomainStrategies = + [ + "AsIs", "IPIfNonMatch", "IPOnDemand" - ]; + ]; - public static readonly List DomainStrategies4Singbox = - [ - "ipv4_only", + public static readonly List DomainStrategies4Singbox = + [ + "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6", "" - ]; + ]; - public static readonly List DomainMatchers = - [ - "linear", + public static readonly List DomainMatchers = + [ + "linear", "mph", "" - ]; + ]; - public static readonly List Fingerprints = - [ - "chrome", + public static readonly List Fingerprints = + [ + "chrome", "firefox", "safari", "ios", @@ -312,174 +312,174 @@ namespace ServiceLib "random", "randomized", "" - ]; + ]; - public static readonly List UserAgent = - [ - "chrome", + public static readonly List UserAgent = + [ + "chrome", "firefox", "safari", "edge", "none" - ]; + ]; - public static readonly List XhttpMode = - [ - "auto", + public static readonly List XhttpMode = + [ + "auto", "packet-up", "stream-up", "stream-one" - ]; + ]; - public static readonly List AllowInsecure = - [ - "true", + public static readonly List AllowInsecure = + [ + "true", "false", "" - ]; + ]; - public static readonly List DomainStrategy4Freedoms = - [ - "AsIs", + public static readonly List DomainStrategy4Freedoms = + [ + "AsIs", "UseIP", "UseIPv4", "UseIPv6", "" - ]; + ]; - public static readonly List SingboxDomainStrategy4Out = - [ - "ipv4_only", + public static readonly List SingboxDomainStrategy4Out = + [ + "ipv4_only", "prefer_ipv4", "prefer_ipv6", "ipv6_only", "" - ]; + ]; - public static readonly List DomainDNSAddress = - [ - "223.5.5.5", + public static readonly List DomainDNSAddress = + [ + "223.5.5.5", "223.6.6.6", "localhost" - ]; + ]; - public static readonly List SingboxDomainDNSAddress = - [ - "223.5.5.5", + public static readonly List SingboxDomainDNSAddress = + [ + "223.5.5.5", "223.6.6.6", "dhcp://auto" - ]; + ]; - public static readonly List Languages = - [ - "zh-Hans", + public static readonly List Languages = + [ + "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru", "hu" - ]; + ]; - public static readonly List Alpns = - [ - "h3", + public static readonly List Alpns = + [ + "h3", "h2", "http/1.1", "h3,h2", "h2,http/1.1", "h3,h2,http/1.1", "" - ]; + ]; - public static readonly List LogLevels = - [ - "debug", + public static readonly List LogLevels = + [ + "debug", "info", "warning", "error", "none" - ]; + ]; - public static readonly List InboundTags = - [ - "socks", + public static readonly List InboundTags = + [ + "socks", "socks2", "socks3" - ]; + ]; - public static readonly List RuleProtocols = - [ - "http", + public static readonly List RuleProtocols = + [ + "http", "tls", "bittorrent" - ]; + ]; - public static readonly List RuleNetworks = - [ - "", + public static readonly List RuleNetworks = + [ + "", "tcp", "udp", "tcp,udp" - ]; + ]; - public static readonly List destOverrideProtocols = - [ - "http", + public static readonly List destOverrideProtocols = + [ + "http", "tls", "quic", "fakedns", "fakedns+others" - ]; + ]; - public static readonly List TunMtus = - [ - 1280, + public static readonly List TunMtus = + [ + 1280, 1408, 1500, 9000 - ]; + ]; - public static readonly List TunStacks = - [ - "gvisor", + public static readonly List TunStacks = + [ + "gvisor", "system", "mixed" - ]; + ]; - public static readonly List PresetMsgFilters = - [ - "proxy", + public static readonly List PresetMsgFilters = + [ + "proxy", "direct", "block", "" - ]; + ]; - public static readonly List SingboxMuxs = - [ - "h2mux", + public static readonly List SingboxMuxs = + [ + "h2mux", "smux", "yamux", "" - ]; + ]; - public static readonly List TuicCongestionControls = - [ - "cubic", + public static readonly List TuicCongestionControls = + [ + "cubic", "new_reno", "bbr" - ]; + ]; - public static readonly List allowSelectType = - [ - "selector", + public static readonly List allowSelectType = + [ + "selector", "urltest", "loadbalance", "fallback" - ]; + ]; - public static readonly List notAllowTestType = - [ - "selector", + public static readonly List notAllowTestType = + [ + "selector", "urltest", "direct", "reject", @@ -487,15 +487,15 @@ namespace ServiceLib "pass", "loadbalance", "fallback" - ]; + ]; - public static readonly List proxyVehicleType = - [ - "file", + public static readonly List proxyVehicleType = + [ + "file", "http" - ]; + ]; - public static readonly Dictionary CoreUrls = new() + public static readonly Dictionary CoreUrls = new() { { ECoreType.v2fly, "v2fly/v2ray-core" }, { ECoreType.v2fly_v5, "v2fly/v2ray-core" }, @@ -512,13 +512,12 @@ namespace ServiceLib { ECoreType.v2rayN, "2dust/v2rayN" }, }; - public static readonly List OtherGeoUrls = - [ - @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat", + public static readonly List OtherGeoUrls = + [ + @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat", @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb", @"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" - ]; + ]; - #endregion const - } + #endregion const } diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index 0ac794ac..ae46a47d 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -1,244 +1,243 @@ -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public sealed class AppHandler { - public sealed class AppHandler + #region Property + + private static readonly Lazy _instance = new(() => new()); + private Config _config; + private int? _statePort; + private int? _statePort2; + private Job? _processJob; + private bool? _isAdministrator; + public static AppHandler Instance => _instance.Value; + public Config Config => _config; + + public int StatePort { - #region Property - - private static readonly Lazy _instance = new(() => new()); - private Config _config; - private int? _statePort; - private int? _statePort2; - private Job? _processJob; - private bool? _isAdministrator; - public static AppHandler Instance => _instance.Value; - public Config Config => _config; - - public int StatePort + get { - get + _statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api)); + return _statePort.Value; + } + } + + public int StatePort2 + { + get + { + _statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2)); + return _statePort2.Value + (_config.TunModeItem.EnableTun ? 1 : 0); + } + } + + public bool IsAdministrator + { + get + { + _isAdministrator ??= Utils.IsAdministrator(); + return _isAdministrator.Value; + } + } + + #endregion Property + + #region Init + + public bool InitApp() + { + if (Utils.HasWritePermission() == false) + { + Environment.SetEnvironmentVariable(Global.LocalAppData, "1", EnvironmentVariableTarget.Process); + } + + Logging.Setup(); + var config = ConfigHandler.LoadConfig(); + if (config == null) + { + return false; + } + _config = config; + Thread.CurrentThread.CurrentUICulture = new(_config.UiItem.CurrentLanguage); + + //Under Win10 + if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10) + { + Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User); + } + + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + return true; + } + + public bool InitComponents() + { + Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}"); + Logging.LoggingEnabled(_config.GuiItem.EnableLog); + + return true; + } + + public bool Reset() + { + _statePort = null; + _statePort2 = null; + return true; + } + + #endregion Init + + #region Config + + public int GetLocalPort(EInboundProtocol protocol) + { + var localPort = _config.Inbound.FirstOrDefault(t => t.Protocol == nameof(EInboundProtocol.socks))?.LocalPort ?? 10808; + return localPort + (int)protocol; + } + + public void AddProcess(IntPtr processHandle) + { + if (Utils.IsWindows()) + { + _processJob ??= new(); + try + { + _processJob?.AddProcess(processHandle); + } + catch { - _statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api)); - return _statePort.Value; } } + } - public int StatePort2 + #endregion Config + + #region SqliteHelper + + public async Task?> SubItems() + { + return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); + } + + public async Task GetSubItem(string? subid) + { + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Id == subid); + } + + public async Task?> ProfileItems(string subid) + { + if (subid.IsNullOrEmpty()) { - get - { - _statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2)); - return _statePort2.Value + (_config.TunModeItem.EnableTun ? 1 : 0); - } + return await SQLiteHelper.Instance.TableAsync().ToListAsync(); } - - public bool IsAdministrator + else { - get - { - _isAdministrator ??= Utils.IsAdministrator(); - return _isAdministrator.Value; - } + return await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid).ToListAsync(); } + } - #endregion Property + public async Task?> ProfileItemIndexes(string subid) + { + return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList(); + } - #region Init - - public bool InitApp() - { - if (Utils.HasWritePermission() == false) - { - Environment.SetEnvironmentVariable(Global.LocalAppData, "1", EnvironmentVariableTarget.Process); - } - - Logging.Setup(); - var config = ConfigHandler.LoadConfig(); - if (config == null) - { - return false; - } - _config = config; - Thread.CurrentThread.CurrentUICulture = new(_config.UiItem.CurrentLanguage); - - //Under Win10 - if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10) - { - Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User); - } - - SQLiteHelper.Instance.CreateTable(); - SQLiteHelper.Instance.CreateTable(); - SQLiteHelper.Instance.CreateTable(); - SQLiteHelper.Instance.CreateTable(); - SQLiteHelper.Instance.CreateTable(); - SQLiteHelper.Instance.CreateTable(); - return true; - } - - public bool InitComponents() - { - Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}"); - Logging.LoggingEnabled(_config.GuiItem.EnableLog); - - return true; - } - - public bool Reset() - { - _statePort = null; - _statePort2 = null; - return true; - } - - #endregion Init - - #region Config - - public int GetLocalPort(EInboundProtocol protocol) - { - var localPort = _config.Inbound.FirstOrDefault(t => t.Protocol == nameof(EInboundProtocol.socks))?.LocalPort ?? 10808; - return localPort + (int)protocol; - } - - public void AddProcess(IntPtr processHandle) - { - if (Utils.IsWindows()) - { - _processJob ??= new(); - try - { - _processJob?.AddProcess(processHandle); - } - catch - { - } - } - } - - #endregion Config - - #region SqliteHelper - - public async Task?> SubItems() - { - return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); - } - - public async Task GetSubItem(string? subid) - { - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Id == subid); - } - - public async Task?> ProfileItems(string subid) - { - if (subid.IsNullOrEmpty()) - { - return await SQLiteHelper.Instance.TableAsync().ToListAsync(); - } - else - { - return await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid).ToListAsync(); - } - } - - public async Task?> ProfileItemIndexes(string subid) - { - return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList(); - } - - public async Task?> ProfileItems(string subid, string filter) - { - var sql = @$"select a.* + public async Task?> ProfileItems(string subid, string filter) + { + var sql = @$"select a.* ,b.remarks subRemarks from ProfileItem a left join SubItem b on a.subid = b.id where 1=1 "; - if (subid.IsNotEmpty()) + if (subid.IsNotEmpty()) + { + sql += $" and a.subid = '{subid}'"; + } + if (filter.IsNotEmpty()) + { + if (filter.Contains('\'')) { - sql += $" and a.subid = '{subid}'"; + filter = filter.Replace("'", ""); } - if (filter.IsNotEmpty()) - { - if (filter.Contains('\'')) - { - filter = filter.Replace("'", ""); - } - sql += string.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter); - } - - return await SQLiteHelper.Instance.QueryAsync(sql); + sql += string.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter); } - public async Task GetProfileItem(string indexId) - { - if (indexId.IsNullOrEmpty()) - { - return null; - } - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); - } - - public async Task GetProfileItemViaRemarks(string? remarks) - { - if (remarks.IsNullOrEmpty()) - { - return null; - } - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Remarks == remarks); - } - - public async Task?> RoutingItems() - { - return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); - } - - public async Task GetRoutingItem(string id) - { - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Id == id); - } - - public async Task?> DNSItems() - { - return await SQLiteHelper.Instance.TableAsync().ToListAsync(); - } - - public async Task GetDNSItem(ECoreType eCoreType) - { - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); - } - - #endregion SqliteHelper - - #region Core Type - - public List GetShadowsocksSecurities(ProfileItem profileItem) - { - var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks); - switch (coreType) - { - case ECoreType.v2fly: - return Global.SsSecurities; - - case ECoreType.Xray: - return Global.SsSecuritiesInXray; - - case ECoreType.sing_box: - return Global.SsSecuritiesInSingbox; - } - return Global.SsSecuritiesInSingbox; - } - - public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType) - { - if (profileItem?.CoreType != null) - { - return (ECoreType)profileItem.CoreType; - } - - var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType); - return item?.CoreType ?? ECoreType.Xray; - } - - #endregion Core Type + return await SQLiteHelper.Instance.QueryAsync(sql); } + + public async Task GetProfileItem(string indexId) + { + if (indexId.IsNullOrEmpty()) + { + return null; + } + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); + } + + public async Task GetProfileItemViaRemarks(string? remarks) + { + if (remarks.IsNullOrEmpty()) + { + return null; + } + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Remarks == remarks); + } + + public async Task?> RoutingItems() + { + return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); + } + + public async Task GetRoutingItem(string id) + { + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Id == id); + } + + public async Task?> DNSItems() + { + return await SQLiteHelper.Instance.TableAsync().ToListAsync(); + } + + public async Task GetDNSItem(ECoreType eCoreType) + { + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); + } + + #endregion SqliteHelper + + #region Core Type + + public List GetShadowsocksSecurities(ProfileItem profileItem) + { + var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks); + switch (coreType) + { + case ECoreType.v2fly: + return Global.SsSecurities; + + case ECoreType.Xray: + return Global.SsSecuritiesInXray; + + case ECoreType.sing_box: + return Global.SsSecuritiesInSingbox; + } + return Global.SsSecuritiesInSingbox; + } + + public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType) + { + if (profileItem?.CoreType != null) + { + return (ECoreType)profileItem.CoreType; + } + + var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType); + return item?.CoreType ?? ECoreType.Xray; + } + + #endregion Core Type } diff --git a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs index 4ba63ba8..015271d9 100644 --- a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs +++ b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs @@ -1,223 +1,223 @@ using System.Security.Principal; using System.Text.RegularExpressions; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public static class AutoStartupHandler { - public static class AutoStartupHandler + private static readonly string _tag = "AutoStartupHandler"; + + public static async Task UpdateTask(Config config) { - private static readonly string _tag = "AutoStartupHandler"; - - public static async Task UpdateTask(Config config) + if (Utils.IsWindows()) { - if (Utils.IsWindows()) + await ClearTaskWindows(); + + if (config.GuiItem.AutoRun) { - await ClearTaskWindows(); - - if (config.GuiItem.AutoRun) - { - await SetTaskWindows(); - } + await SetTaskWindows(); } - else if (Utils.IsLinux()) + } + else if (Utils.IsLinux()) + { + await ClearTaskLinux(); + + if (config.GuiItem.AutoRun) { - await ClearTaskLinux(); - - if (config.GuiItem.AutoRun) - { - await SetTaskLinux(); - } + await SetTaskLinux(); } - else if (Utils.IsOSX()) + } + else if (Utils.IsOSX()) + { + await ClearTaskOSX(); + + if (config.GuiItem.AutoRun) { - await ClearTaskOSX(); - - if (config.GuiItem.AutoRun) - { - await SetTaskOSX(); - } + await SetTaskOSX(); } - - return true; } - #region Windows + return true; + } - private static async Task ClearTaskWindows() + #region Windows + + private static async Task ClearTaskWindows() + { + var autoRunName = GetAutoRunNameWindows(); + WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, ""); + if (Utils.IsAdministrator()) + { + AutoStartTaskService(autoRunName, "", ""); + } + + await Task.CompletedTask; + } + + private static async Task SetTaskWindows() + { + try { var autoRunName = GetAutoRunNameWindows(); - WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, ""); + var exePath = Utils.GetExePath(); if (Utils.IsAdministrator()) { - AutoStartTaskService(autoRunName, "", ""); + AutoStartTaskService(autoRunName, exePath, ""); } - - await Task.CompletedTask; - } - - private static async Task SetTaskWindows() - { - try + else { - var autoRunName = GetAutoRunNameWindows(); - var exePath = Utils.GetExePath(); - if (Utils.IsAdministrator()) - { - AutoStartTaskService(autoRunName, exePath, ""); - } - else - { - WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, exePath.AppendQuotes()); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - await Task.CompletedTask; - } - - /// - /// Auto Start via TaskService - /// - /// - /// - /// - /// - public static void AutoStartTaskService(string taskName, string fileName, string description) - { - if (taskName.IsNullOrEmpty()) - { - return; - } - - var logonUser = WindowsIdentity.GetCurrent().Name; - using var taskService = new Microsoft.Win32.TaskScheduler.TaskService(); - var tasks = taskService.RootFolder.GetTasks(new Regex(taskName)); - if (fileName.IsNullOrEmpty()) - { - foreach (var t in tasks) - { - taskService.RootFolder.DeleteTask(t.Name); - } - return; - } - - var task = taskService.NewTask(); - task.RegistrationInfo.Description = description; - task.Settings.DisallowStartIfOnBatteries = false; - task.Settings.StopIfGoingOnBatteries = false; - task.Settings.RunOnlyIfIdle = false; - task.Settings.IdleSettings.StopOnIdleEnd = false; - task.Settings.ExecutionTimeLimit = TimeSpan.Zero; - task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) }); - task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest; - task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); - - taskService.RootFolder.RegisterTaskDefinition(taskName, task); - } - - private static string GetAutoRunNameWindows() - { - return $"{Global.AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}"; - } - - #endregion Windows - - #region Linux - - private static async Task ClearTaskLinux() - { - try - { - File.Delete(GetHomePathLinux()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - await Task.CompletedTask; - } - - private static async Task SetTaskLinux() - { - try - { - var linuxConfig = EmbedUtils.GetEmbedText(Global.LinuxAutostartConfig); - if (linuxConfig.IsNotEmpty()) - { - linuxConfig = linuxConfig.Replace("$ExecPath$", Utils.GetExePath()); - Logging.SaveLog(linuxConfig); - - var homePath = GetHomePathLinux(); - await File.WriteAllTextAsync(homePath, linuxConfig); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, exePath.AppendQuotes()); } } - - private static string GetHomePathLinux() + catch (Exception ex) { - var homePath = Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop"); - Directory.CreateDirectory(Path.GetDirectoryName(homePath)); - return homePath; + Logging.SaveLog(_tag, ex); + } + await Task.CompletedTask; + } + + /// + /// Auto Start via TaskService + /// + /// + /// + /// + /// + public static void AutoStartTaskService(string taskName, string fileName, string description) + { + if (taskName.IsNullOrEmpty()) + { + return; } - #endregion Linux - - #region macOS - - private static async Task ClearTaskOSX() + var logonUser = WindowsIdentity.GetCurrent().Name; + using var taskService = new Microsoft.Win32.TaskScheduler.TaskService(); + var tasks = taskService.RootFolder.GetTasks(new Regex(taskName)); + if (fileName.IsNullOrEmpty()) { - try + foreach (var t in tasks) { - var launchAgentPath = GetLaunchAgentPathMacOS(); - if (File.Exists(launchAgentPath)) - { - var args = new[] { "-c", $"launchctl unload -w \"{launchAgentPath}\"" }; - await Utils.GetCliWrapOutput(Global.LinuxBash, args); - - File.Delete(launchAgentPath); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + taskService.RootFolder.DeleteTask(t.Name); } + return; } - private static async Task SetTaskOSX() - { - try - { - var plistContent = GenerateLaunchAgentPlist(); - var launchAgentPath = GetLaunchAgentPathMacOS(); - await File.WriteAllTextAsync(launchAgentPath, plistContent); + var task = taskService.NewTask(); + task.RegistrationInfo.Description = description; + task.Settings.DisallowStartIfOnBatteries = false; + task.Settings.StopIfGoingOnBatteries = false; + task.Settings.RunOnlyIfIdle = false; + task.Settings.IdleSettings.StopOnIdleEnd = false; + task.Settings.ExecutionTimeLimit = TimeSpan.Zero; + task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) }); + task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest; + task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); - var args = new[] { "-c", $"launchctl load -w \"{launchAgentPath}\"" }; + taskService.RootFolder.RegisterTaskDefinition(taskName, task); + } + + private static string GetAutoRunNameWindows() + { + return $"{Global.AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}"; + } + + #endregion Windows + + #region Linux + + private static async Task ClearTaskLinux() + { + try + { + File.Delete(GetHomePathLinux()); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + await Task.CompletedTask; + } + + private static async Task SetTaskLinux() + { + try + { + var linuxConfig = EmbedUtils.GetEmbedText(Global.LinuxAutostartConfig); + if (linuxConfig.IsNotEmpty()) + { + linuxConfig = linuxConfig.Replace("$ExecPath$", Utils.GetExePath()); + Logging.SaveLog(linuxConfig); + + var homePath = GetHomePathLinux(); + await File.WriteAllTextAsync(homePath, linuxConfig); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private static string GetHomePathLinux() + { + var homePath = Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop"); + Directory.CreateDirectory(Path.GetDirectoryName(homePath)); + return homePath; + } + + #endregion Linux + + #region macOS + + private static async Task ClearTaskOSX() + { + try + { + var launchAgentPath = GetLaunchAgentPathMacOS(); + if (File.Exists(launchAgentPath)) + { + var args = new[] { "-c", $"launchctl unload -w \"{launchAgentPath}\"" }; await Utils.GetCliWrapOutput(Global.LinuxBash, args); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + + File.Delete(launchAgentPath); } } - - private static string GetLaunchAgentPathMacOS() + catch (Exception ex) { - var homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - var launchAgentPath = Path.Combine(homePath, "Library", "LaunchAgents", $"{Global.AppName}-LaunchAgent.plist"); - Directory.CreateDirectory(Path.GetDirectoryName(launchAgentPath)); - return launchAgentPath; + Logging.SaveLog(_tag, ex); } + } - private static string GenerateLaunchAgentPlist() + private static async Task SetTaskOSX() + { + try { - var exePath = Utils.GetExePath(); - var appName = Path.GetFileNameWithoutExtension(exePath); - return $@" + var plistContent = GenerateLaunchAgentPlist(); + var launchAgentPath = GetLaunchAgentPathMacOS(); + await File.WriteAllTextAsync(launchAgentPath, plistContent); + + var args = new[] { "-c", $"launchctl load -w \"{launchAgentPath}\"" }; + await Utils.GetCliWrapOutput(Global.LinuxBash, args); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private static string GetLaunchAgentPathMacOS() + { + var homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var launchAgentPath = Path.Combine(homePath, "Library", "LaunchAgents", $"{Global.AppName}-LaunchAgent.plist"); + Directory.CreateDirectory(Path.GetDirectoryName(launchAgentPath)); + return launchAgentPath; + } + + private static string GenerateLaunchAgentPlist() + { + var exePath = Utils.GetExePath(); + var appName = Path.GetFileNameWithoutExtension(exePath); + return $@" @@ -235,8 +235,7 @@ namespace ServiceLib.Handler "; - } - - #endregion macOS } + + #endregion macOS } diff --git a/v2rayN/ServiceLib/Handler/ClashApiHandler.cs b/v2rayN/ServiceLib/Handler/ClashApiHandler.cs index 253bf1fa..af5b0c57 100644 --- a/v2rayN/ServiceLib/Handler/ClashApiHandler.cs +++ b/v2rayN/ServiceLib/Handler/ClashApiHandler.cs @@ -1,188 +1,187 @@ using static ServiceLib.Models.ClashProxies; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public sealed class ClashApiHandler { - public sealed class ClashApiHandler + private static readonly Lazy instance = new(() => new()); + public static ClashApiHandler Instance => instance.Value; + + private static readonly string _tag = "ClashApiHandler"; + private Dictionary? _proxies; + public Dictionary ProfileContent { get; set; } + + public async Task?> GetClashProxiesAsync() { - private static readonly Lazy instance = new(() => new()); - public static ClashApiHandler Instance => instance.Value; - - private static readonly string _tag = "ClashApiHandler"; - private Dictionary? _proxies; - public Dictionary ProfileContent { get; set; } - - public async Task?> GetClashProxiesAsync() + for (var i = 0; i < 3; i++) { - for (var i = 0; i < 3; i++) + var url = $"{GetApiUrl()}/proxies"; + var result = await HttpClientHelper.Instance.TryGetAsync(url); + var clashProxies = JsonUtils.Deserialize(result); + + var url2 = $"{GetApiUrl()}/providers/proxies"; + var result2 = await HttpClientHelper.Instance.TryGetAsync(url2); + var clashProviders = JsonUtils.Deserialize(result2); + + if (clashProxies != null || clashProviders != null) { - var url = $"{GetApiUrl()}/proxies"; - var result = await HttpClientHelper.Instance.TryGetAsync(url); - var clashProxies = JsonUtils.Deserialize(result); - - var url2 = $"{GetApiUrl()}/providers/proxies"; - var result2 = await HttpClientHelper.Instance.TryGetAsync(url2); - var clashProviders = JsonUtils.Deserialize(result2); - - if (clashProxies != null || clashProviders != null) - { - _proxies = clashProxies?.proxies; - return new Tuple(clashProxies, clashProviders); - } - - await Task.Delay(2000); + _proxies = clashProxies?.proxies; + return new Tuple(clashProxies, clashProviders); } - return null; + await Task.Delay(2000); } - public void ClashProxiesDelayTest(bool blAll, List lstProxy, Action updateFunc) + return null; + } + + public void ClashProxiesDelayTest(bool blAll, List lstProxy, Action updateFunc) + { + Task.Run(async () => { - Task.Run(async () => + if (blAll) { - if (blAll) + if (_proxies == null) { - if (_proxies == null) - { - await GetClashProxiesAsync(); - } - lstProxy = new List(); - foreach (var kv in _proxies ?? []) - { - if (Global.notAllowTestType.Contains(kv.Value.type?.ToLower())) - { - continue; - } - lstProxy.Add(new ClashProxyModel() - { - Name = kv.Value.name, - Type = kv.Value.type?.ToLower(), - }); - } + await GetClashProxiesAsync(); } - - if (lstProxy is not { Count: > 0 }) + lstProxy = new List(); + foreach (var kv in _proxies ?? []) { - return; - } - var urlBase = $"{GetApiUrl()}/proxies"; - urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl; - - var tasks = new List(); - foreach (var it in lstProxy) - { - if (Global.notAllowTestType.Contains(it.Type.ToLower())) + if (Global.notAllowTestType.Contains(kv.Value.type?.ToLower())) { continue; } - var name = it.Name; - var url = string.Format(urlBase, name); - tasks.Add(Task.Run(async () => + lstProxy.Add(new ClashProxyModel() { - var result = await HttpClientHelper.Instance.TryGetAsync(url); - updateFunc?.Invoke(it, result); - })); + Name = kv.Value.name, + Type = kv.Value.type?.ToLower(), + }); } - await Task.WhenAll(tasks); - await Task.Delay(1000); - updateFunc?.Invoke(null, ""); - }); - } + } - public List? GetClashProxyGroups() - { - try - { - var fileContent = ProfileContent; - if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false) - { - return null; - } - return JsonUtils.Deserialize>(JsonUtils.Serialize(fileContent["proxy-groups"])); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return null; - } - } - - public async Task ClashSetActiveProxy(string name, string nameNode) - { - try - { - var url = $"{GetApiUrl()}/proxies/{name}"; - var headers = new Dictionary(); - headers.Add("name", nameNode); - await HttpClientHelper.Instance.PutAsync(url, headers); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public async Task ClashConfigUpdate(Dictionary headers) - { - if (_proxies == null) + if (lstProxy is not { Count: > 0 }) { return; } + var urlBase = $"{GetApiUrl()}/proxies"; + urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl; - var urlBase = $"{GetApiUrl()}/configs"; + var tasks = new List(); + foreach (var it in lstProxy) + { + if (Global.notAllowTestType.Contains(it.Type.ToLower())) + { + continue; + } + var name = it.Name; + var url = string.Format(urlBase, name); + tasks.Add(Task.Run(async () => + { + var result = await HttpClientHelper.Instance.TryGetAsync(url); + updateFunc?.Invoke(it, result); + })); + } + await Task.WhenAll(tasks); + await Task.Delay(1000); + updateFunc?.Invoke(null, ""); + }); + } - await HttpClientHelper.Instance.PatchAsync(urlBase, headers); - } - - public async Task ClashConfigReload(string filePath) + public List? GetClashProxyGroups() + { + try { - await ClashConnectionClose(""); - try + var fileContent = ProfileContent; + if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false) { - var url = $"{GetApiUrl()}/configs?force=true"; - var headers = new Dictionary(); - headers.Add("path", filePath); - await HttpClientHelper.Instance.PutAsync(url, headers); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); + return null; } + return JsonUtils.Deserialize>(JsonUtils.Serialize(fileContent["proxy-groups"])); } - - public async Task GetClashConnectionsAsync() + catch (Exception ex) { - try - { - var url = $"{GetApiUrl()}/connections"; - var result = await HttpClientHelper.Instance.TryGetAsync(url); - var clashConnections = JsonUtils.Deserialize(result); - - return clashConnections; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - + Logging.SaveLog(_tag, ex); return null; } + } - public async Task ClashConnectionClose(string id) + public async Task ClashSetActiveProxy(string name, string nameNode) + { + try { - try - { - var url = $"{GetApiUrl()}/connections/{id}"; - await HttpClientHelper.Instance.DeleteAsync(url); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } + var url = $"{GetApiUrl()}/proxies/{name}"; + var headers = new Dictionary(); + headers.Add("name", nameNode); + await HttpClientHelper.Instance.PutAsync(url, headers); } - - private string GetApiUrl() + catch (Exception ex) { - return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}"; + Logging.SaveLog(_tag, ex); } } + + public async Task ClashConfigUpdate(Dictionary headers) + { + if (_proxies == null) + { + return; + } + + var urlBase = $"{GetApiUrl()}/configs"; + + await HttpClientHelper.Instance.PatchAsync(urlBase, headers); + } + + public async Task ClashConfigReload(string filePath) + { + await ClashConnectionClose(""); + try + { + var url = $"{GetApiUrl()}/configs?force=true"; + var headers = new Dictionary(); + headers.Add("path", filePath); + await HttpClientHelper.Instance.PutAsync(url, headers); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public async Task GetClashConnectionsAsync() + { + try + { + var url = $"{GetApiUrl()}/connections"; + var result = await HttpClientHelper.Instance.TryGetAsync(url); + var clashConnections = JsonUtils.Deserialize(result); + + return clashConnections; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return null; + } + + public async Task ClashConnectionClose(string id) + { + try + { + var url = $"{GetApiUrl()}/connections/{id}"; + await HttpClientHelper.Instance.DeleteAsync(url); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private string GetApiUrl() + { + return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}"; + } } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 2db2527f..03a0d9a5 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1,1926 +1,1925 @@ using System.Data; using System.Text.RegularExpressions; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +/// +/// 本软件配置文件处理类 +/// +public class ConfigHandler { + private static readonly string _configRes = Global.ConfigFileName; + private static readonly string _tag = "ConfigHandler"; + + #region ConfigHandler + /// - /// 本软件配置文件处理类 + /// 载入配置文件 /// - public class ConfigHandler + /// + /// + public static Config? LoadConfig() { - private static readonly string _configRes = Global.ConfigFileName; - private static readonly string _tag = "ConfigHandler"; - - #region ConfigHandler - - /// - /// 载入配置文件 - /// - /// - /// - public static Config? LoadConfig() + Config? config = null; + var result = EmbedUtils.LoadResource(Utils.GetConfigPath(_configRes)); + if (result.IsNotEmpty()) { - Config? config = null; - var result = EmbedUtils.LoadResource(Utils.GetConfigPath(_configRes)); - if (result.IsNotEmpty()) + config = JsonUtils.Deserialize(result); + } + else + { + if (File.Exists(Utils.GetConfigPath(_configRes))) { - config = JsonUtils.Deserialize(result); - } - else - { - if (File.Exists(Utils.GetConfigPath(_configRes))) - { - Logging.SaveLog("LoadConfig Exception"); - return null; - } + Logging.SaveLog("LoadConfig Exception"); + return null; } + } - config ??= new Config(); + config ??= new Config(); - config.CoreBasicItem ??= new() + config.CoreBasicItem ??= new() + { + LogEnabled = false, + Loglevel = "warning", + MuxEnabled = false, + }; + + if (config.Inbound == null) + { + config.Inbound = new List(); + InItem inItem = new() { - LogEnabled = false, - Loglevel = "warning", - MuxEnabled = false, + Protocol = EInboundProtocol.socks.ToString(), + LocalPort = 10808, + UdpEnabled = true, + SniffingEnabled = true, + RouteOnly = false, }; - if (config.Inbound == null) + config.Inbound.Add(inItem); + } + else + { + if (config.Inbound.Count > 0) { - config.Inbound = new List(); - InItem inItem = new() - { - Protocol = EInboundProtocol.socks.ToString(), - LocalPort = 10808, - UdpEnabled = true, - SniffingEnabled = true, - RouteOnly = false, - }; - - config.Inbound.Add(inItem); + config.Inbound.First().Protocol = EInboundProtocol.socks.ToString(); } - else - { - if (config.Inbound.Count > 0) - { - config.Inbound.First().Protocol = EInboundProtocol.socks.ToString(); - } - } - - config.RoutingBasicItem ??= new(); - if (config.RoutingBasicItem.DomainStrategy.IsNullOrEmpty()) - { - config.RoutingBasicItem.DomainStrategy = Global.DomainStrategies.First(); - } - - config.KcpItem ??= new KcpItem - { - Mtu = 1350, - Tti = 50, - UplinkCapacity = 12, - DownlinkCapacity = 100, - ReadBufferSize = 2, - WriteBufferSize = 2, - Congestion = false - }; - config.GrpcItem ??= new GrpcItem - { - IdleTimeout = 60, - HealthCheckTimeout = 20, - PermitWithoutStream = false, - InitialWindowsSize = 0, - }; - config.TunModeItem ??= new TunModeItem - { - EnableTun = false, - Mtu = 9000, - }; - config.GuiItem ??= new(); - config.MsgUIItem ??= new(); - - config.UiItem ??= new UIItem() - { - EnableAutoAdjustMainLvColWidth = true - }; - config.UiItem.MainColumnItem ??= new(); - - if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) - { - config.UiItem.CurrentLanguage = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.Equals("zh", StringComparison.CurrentCultureIgnoreCase) - ? Global.Languages.First() - : Global.Languages[2]; - } - - config.ConstItem ??= new ConstItem(); - - config.SpeedTestItem ??= new(); - if (config.SpeedTestItem.SpeedTestTimeout < 10) - { - config.SpeedTestItem.SpeedTestTimeout = 10; - } - if (config.SpeedTestItem.SpeedTestUrl.IsNullOrEmpty()) - { - config.SpeedTestItem.SpeedTestUrl = Global.SpeedTestUrls.First(); - } - if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty()) - { - config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrl; - } - if (config.SpeedTestItem.MixedConcurrencyCount < 1) - { - config.SpeedTestItem.MixedConcurrencyCount = 5; - } - - config.Mux4RayItem ??= new() - { - Concurrency = 8, - XudpConcurrency = 16, - XudpProxyUDP443 = "reject" - }; - - config.Mux4SboxItem ??= new() - { - Protocol = Global.SingboxMuxs.First(), - MaxConnections = 8 - }; - - config.HysteriaItem ??= new() - { - UpMbps = 100, - DownMbps = 100 - }; - config.ClashUIItem ??= new(); - config.SystemProxyItem ??= new(); - config.WebDavItem ??= new(); - config.CheckUpdateItem ??= new(); - config.Fragment4RayItem ??= new() - { - Packets = "tlshello", - Length = "100-200", - Interval = "10-20" - }; - config.GlobalHotkeys ??= new(); - - if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty()) - { - config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; - } - - return config; } - /// - /// 保参数 - /// - /// - /// - public static async Task SaveConfig(Config config) + config.RoutingBasicItem ??= new(); + if (config.RoutingBasicItem.DomainStrategy.IsNullOrEmpty()) { - try - { - //save temp file - var resPath = Utils.GetConfigPath(_configRes); - var tempPath = $"{resPath}_temp"; - - var content = JsonUtils.Serialize(config, true, true); - if (content.IsNullOrEmpty()) - { - return -1; - } - await File.WriteAllTextAsync(tempPath, content); - - //rename - File.Move(tempPath, resPath, true); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return -1; - } - - return 0; + config.RoutingBasicItem.DomainStrategy = Global.DomainStrategies.First(); } - #endregion ConfigHandler - - #region Server - - public static async Task AddServer(Config config, ProfileItem profileItem) + config.KcpItem ??= new KcpItem { - var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); - if (item is null) - { - item = profileItem; - } - else - { - item.CoreType = profileItem.CoreType; - item.Remarks = profileItem.Remarks; - item.Address = profileItem.Address; - item.Port = profileItem.Port; - item.Ports = profileItem.Ports; + Mtu = 1350, + Tti = 50, + UplinkCapacity = 12, + DownlinkCapacity = 100, + ReadBufferSize = 2, + WriteBufferSize = 2, + Congestion = false + }; + config.GrpcItem ??= new GrpcItem + { + IdleTimeout = 60, + HealthCheckTimeout = 20, + PermitWithoutStream = false, + InitialWindowsSize = 0, + }; + config.TunModeItem ??= new TunModeItem + { + EnableTun = false, + Mtu = 9000, + }; + config.GuiItem ??= new(); + config.MsgUIItem ??= new(); - item.Id = profileItem.Id; - item.AlterId = profileItem.AlterId; - item.Security = profileItem.Security; - item.Flow = profileItem.Flow; + config.UiItem ??= new UIItem() + { + EnableAutoAdjustMainLvColWidth = true + }; + config.UiItem.MainColumnItem ??= new(); - item.Network = profileItem.Network; - item.HeaderType = profileItem.HeaderType; - item.RequestHost = profileItem.RequestHost; - item.Path = profileItem.Path; - - item.StreamSecurity = profileItem.StreamSecurity; - item.Sni = profileItem.Sni; - item.AllowInsecure = profileItem.AllowInsecure; - item.Fingerprint = profileItem.Fingerprint; - item.Alpn = profileItem.Alpn; - - item.PublicKey = profileItem.PublicKey; - item.ShortId = profileItem.ShortId; - item.SpiderX = profileItem.SpiderX; - item.Extra = profileItem.Extra; - } - - var ret = item.ConfigType switch - { - EConfigType.VMess => await AddVMessServer(config, item), - EConfigType.Shadowsocks => await AddShadowsocksServer(config, item), - EConfigType.SOCKS => await AddSocksServer(config, item), - EConfigType.HTTP => await AddHttpServer(config, item), - EConfigType.Trojan => await AddTrojanServer(config, item), - EConfigType.VLESS => await AddVlessServer(config, item), - EConfigType.Hysteria2 => await AddHysteria2Server(config, item), - EConfigType.TUIC => await AddTuicServer(config, item), - EConfigType.WireGuard => await AddWireguardServer(config, item), - _ => -1, - }; - return ret; + if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) + { + config.UiItem.CurrentLanguage = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.Equals("zh", StringComparison.CurrentCultureIgnoreCase) + ? Global.Languages.First() + : Global.Languages[2]; } - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddVMessServer(Config config, ProfileItem profileItem, bool toFile = true) + config.ConstItem ??= new ConstItem(); + + config.SpeedTestItem ??= new(); + if (config.SpeedTestItem.SpeedTestTimeout < 10) { - profileItem.ConfigType = EConfigType.VMess; + config.SpeedTestItem.SpeedTestTimeout = 10; + } + if (config.SpeedTestItem.SpeedTestUrl.IsNullOrEmpty()) + { + config.SpeedTestItem.SpeedTestUrl = Global.SpeedTestUrls.First(); + } + if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty()) + { + config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrl; + } + if (config.SpeedTestItem.MixedConcurrencyCount < 1) + { + config.SpeedTestItem.MixedConcurrencyCount = 5; + } - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); - profileItem.Network = profileItem.Network.TrimEx(); - profileItem.HeaderType = profileItem.HeaderType.TrimEx(); - profileItem.RequestHost = profileItem.RequestHost.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); - profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); + config.Mux4RayItem ??= new() + { + Concurrency = 8, + XudpConcurrency = 16, + XudpProxyUDP443 = "reject" + }; - if (!Global.VmessSecurities.Contains(profileItem.Security)) - { - return -1; - } - if (profileItem.Id.IsNullOrEmpty()) + config.Mux4SboxItem ??= new() + { + Protocol = Global.SingboxMuxs.First(), + MaxConnections = 8 + }; + + config.HysteriaItem ??= new() + { + UpMbps = 100, + DownMbps = 100 + }; + config.ClashUIItem ??= new(); + config.SystemProxyItem ??= new(); + config.WebDavItem ??= new(); + config.CheckUpdateItem ??= new(); + config.Fragment4RayItem ??= new() + { + Packets = "tlshello", + Length = "100-200", + Interval = "10-20" + }; + config.GlobalHotkeys ??= new(); + + if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty()) + { + config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; + } + + return config; + } + + /// + /// 保参数 + /// + /// + /// + public static async Task SaveConfig(Config config) + { + try + { + //save temp file + var resPath = Utils.GetConfigPath(_configRes); + var tempPath = $"{resPath}_temp"; + + var content = JsonUtils.Serialize(config, true, true); + if (content.IsNullOrEmpty()) { return -1; } + await File.WriteAllTextAsync(tempPath, content); - await AddServerCommon(config, profileItem, toFile); - - return 0; + //rename + File.Move(tempPath, resPath, true); } - - /// - /// 移除服务器 - /// - /// - /// - /// - public static async Task RemoveServers(Config config, List indexes) + catch (Exception ex) { - var subid = "TempRemoveSubId"; - foreach (var item in indexes) - { - item.Subid = subid; - } - - await SQLiteHelper.Instance.UpdateAllAsync(indexes); - await RemoveServersViaSubid(config, subid, false); - - return 0; - } - - /// - /// 克隆服务器 - /// - /// - /// - /// - public static async Task CopyServer(Config config, List indexes) - { - foreach (var it in indexes) - { - var item = await AppHandler.Instance.GetProfileItem(it.IndexId); - if (item is null) - { - continue; - } - - var profileItem = JsonUtils.DeepCopy(item); - profileItem.IndexId = string.Empty; - profileItem.Remarks = $"{item.Remarks}-clone"; - - if (profileItem.ConfigType == EConfigType.Custom) - { - profileItem.Address = Utils.GetConfigPath(profileItem.Address); - if (await AddCustomServer(config, profileItem, false) == 0) - { - } - } - else - { - await AddServerCommon(config, profileItem, true); - } - } - - return 0; - } - - /// - /// 设置活动服务器 - /// - /// - /// - /// - public static async Task SetDefaultServerIndex(Config config, string? indexId) - { - if (indexId.IsNullOrEmpty()) - { - return -1; - } - - config.IndexId = indexId; - - await SaveConfig(config); - - return 0; - } - - public static async Task SetDefaultServer(Config config, List lstProfile) - { - if (lstProfile.Exists(t => t.IndexId == config.IndexId)) - { - return 0; - } - - if (await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.IndexId == config.IndexId) != null) - { - return 0; - } - if (lstProfile.Count > 0) - { - return await SetDefaultServerIndex(config, lstProfile.FirstOrDefault(t => t.Port > 0)?.IndexId); - } - - var item = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Port > 0); - return await SetDefaultServerIndex(config, item?.IndexId); - } - - public static async Task GetDefaultServer(Config config) - { - var item = await AppHandler.Instance.GetProfileItem(config.IndexId); - if (item is null) - { - var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); - await SetDefaultServerIndex(config, item2?.IndexId); - return item2; - } - - return item; - } - - /// - /// 移动服务器 - /// - /// - /// - /// - /// - /// - public static async Task MoveServer(Config config, List lstProfile, int index, EMove eMove, int pos = -1) - { - int count = lstProfile.Count; - if (index < 0 || index > lstProfile.Count - 1) - { - return -1; - } - - for (int i = 0; i < lstProfile.Count; i++) - { - ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); - } - - var sort = 0; - switch (eMove) - { - case EMove.Top: - { - if (index == 0) - { - return 0; - } - sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1; - - break; - } - case EMove.Up: - { - if (index == 0) - { - return 0; - } - sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1; - - break; - } - - case EMove.Down: - { - if (index == count - 1) - { - return 0; - } - sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1; - - break; - } - case EMove.Bottom: - { - if (index == count - 1) - { - return 0; - } - sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1; - - break; - } - case EMove.Position: - sort = (pos * 10) + 1; - break; - } - - ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort); - return await Task.FromResult(0); - } - - /// - /// 添加自定义服务器 - /// - /// - /// - /// - public static async Task AddCustomServer(Config config, ProfileItem profileItem, bool blDelete) - { - var fileName = profileItem.Address; - if (!File.Exists(fileName)) - { - return -1; - } - var ext = Path.GetExtension(fileName); - string newFileName = $"{Utils.GetGuid()}{ext}"; - //newFileName = Path.Combine(Utile.GetTempPath(), newFileName); - - try - { - File.Copy(fileName, Utils.GetConfigPath(newFileName)); - if (blDelete) - { - File.Delete(fileName); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return -1; - } - - profileItem.Address = newFileName; - profileItem.ConfigType = EConfigType.Custom; - if (profileItem.Remarks.IsNullOrEmpty()) - { - profileItem.Remarks = $"import custom@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}"; - } - - await AddServerCommon(config, profileItem, true); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task EditCustomServer(Config config, ProfileItem profileItem) - { - var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); - if (item is null) - { - item = profileItem; - } - else - { - item.Remarks = profileItem.Remarks; - item.Address = profileItem.Address; - item.CoreType = profileItem.CoreType; - item.DisplayLog = profileItem.DisplayLog; - item.PreSocksPort = profileItem.PreSocksPort; - } - - if (await SQLiteHelper.Instance.UpdateAsync(item) > 0) - { - return 0; - } - else - { - return -1; - } - - //ToJsonFile(config); - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddShadowsocksServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.Shadowsocks; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); - - if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) - { - return -1; - } - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddSocksServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.SOCKS; - - profileItem.Address = profileItem.Address.TrimEx(); - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.HTTP; - - profileItem.Address = profileItem.Address.TrimEx(); - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddTrojanServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.Trojan; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - if (profileItem.StreamSecurity.IsNullOrEmpty()) - { - profileItem.StreamSecurity = Global.StreamSecurity; - } - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.Hysteria2; - profileItem.CoreType = ECoreType.sing_box; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); - profileItem.Network = string.Empty; - - if (profileItem.StreamSecurity.IsNullOrEmpty()) - { - profileItem.StreamSecurity = Global.StreamSecurity; - } - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.TUIC; - profileItem.CoreType = ECoreType.sing_box; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); - profileItem.Network = string.Empty; - - if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) - { - profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!; - } - - if (profileItem.StreamSecurity.IsNullOrEmpty()) - { - profileItem.StreamSecurity = Global.StreamSecurity; - } - if (profileItem.Alpn.IsNullOrEmpty()) - { - profileItem.Alpn = "h3"; - } - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddWireguardServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.WireGuard; - profileItem.CoreType = ECoreType.sing_box; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.PublicKey = profileItem.PublicKey.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); - profileItem.RequestHost = profileItem.RequestHost.TrimEx(); - profileItem.Network = string.Empty; - if (profileItem.ShortId.IsNullOrEmpty()) - { - profileItem.ShortId = Global.TunMtus.First().ToString(); - } - - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - public static async Task SortServers(Config config, string subId, string colName, bool asc) - { - var lstModel = await AppHandler.Instance.ProfileItems(subId, ""); - if (lstModel.Count <= 0) - { - return -1; - } - var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); - var lstProfile = (from t in lstModel - join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b - from t33 in t3b.DefaultIfEmpty() - select new ProfileItemModel - { - IndexId = t.IndexId, - ConfigType = t.ConfigType, - Remarks = t.Remarks, - Address = t.Address, - Port = t.Port, - Security = t.Security, - Network = t.Network, - StreamSecurity = t.StreamSecurity, - Delay = t33?.Delay ?? 0, - Speed = t33?.Speed ?? 0, - Sort = t33?.Sort ?? 0 - }).ToList(); - - Enum.TryParse(colName, true, out EServerColName name); - - if (asc) - { - lstProfile = name switch - { - EServerColName.ConfigType => lstProfile.OrderBy(t => t.ConfigType).ToList(), - EServerColName.Remarks => lstProfile.OrderBy(t => t.Remarks).ToList(), - EServerColName.Address => lstProfile.OrderBy(t => t.Address).ToList(), - EServerColName.Port => lstProfile.OrderBy(t => t.Port).ToList(), - EServerColName.Network => lstProfile.OrderBy(t => t.Network).ToList(), - EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(), - EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), - EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(), - EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(), - _ => lstProfile - }; - } - else - { - lstProfile = name switch - { - EServerColName.ConfigType => lstProfile.OrderByDescending(t => t.ConfigType).ToList(), - EServerColName.Remarks => lstProfile.OrderByDescending(t => t.Remarks).ToList(), - EServerColName.Address => lstProfile.OrderByDescending(t => t.Address).ToList(), - EServerColName.Port => lstProfile.OrderByDescending(t => t.Port).ToList(), - EServerColName.Network => lstProfile.OrderByDescending(t => t.Network).ToList(), - EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(), - EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), - EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(), - EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(), - _ => lstProfile - }; - } - - for (var i = 0; i < lstProfile.Count; i++) - { - ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); - } - switch (name) - { - case EServerColName.DelayVal: - { - var maxSort = lstProfile.Max(t => t.Sort) + 10; - foreach (var item in lstProfile.Where(item => item.Delay <= 0)) - { - ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); - } - - break; - } - case EServerColName.SpeedVal: - { - var maxSort = lstProfile.Max(t => t.Sort) + 10; - foreach (var item in lstProfile.Where(item => item.Speed <= 0)) - { - ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); - } - - break; - } - } - - return 0; - } - - /// - /// Add or edit server - /// - /// - /// - /// - public static async Task AddVlessServer(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigType = EConfigType.VLESS; - - profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); - profileItem.Network = profileItem.Network.TrimEx(); - profileItem.HeaderType = profileItem.HeaderType.TrimEx(); - profileItem.RequestHost = profileItem.RequestHost.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); - profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); - - if (!Global.Flows.Contains(profileItem.Flow)) - { - profileItem.Flow = Global.Flows.First(); - } - if (profileItem.Id.IsNullOrEmpty()) - { - return -1; - } - if (profileItem.Security.IsNotEmpty() && profileItem.Security != Global.None) - { - profileItem.Security = Global.None; - } - - await AddServerCommon(config, profileItem, toFile); - - return 0; - } - - public static async Task> DedupServerList(Config config, string subId) - { - var lstProfile = await AppHandler.Instance.ProfileItems(subId); - if (lstProfile == null) - { - return new Tuple(0, 0); - } - - List lstKeep = new(); - List lstRemove = new(); - if (!config.GuiItem.KeepOlderDedupl) - { - lstProfile.Reverse(); - } - - foreach (var item in lstProfile) - { - if (!lstKeep.Exists(i => CompareProfileItem(i, item, false))) - { - lstKeep.Add(item); - } - else - { - lstRemove.Add(item); - } - } - await RemoveServers(config, lstRemove); - - return new Tuple(lstProfile.Count, lstKeep.Count); - } - - public static async Task AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) - { - profileItem.ConfigVersion = 2; - - if (profileItem.StreamSecurity.IsNotEmpty()) - { - if (profileItem.StreamSecurity != Global.StreamSecurity - && profileItem.StreamSecurity != Global.StreamSecurityReality) - { - profileItem.StreamSecurity = string.Empty; - } - else - { - if (profileItem.AllowInsecure.IsNullOrEmpty()) - { - profileItem.AllowInsecure = config.CoreBasicItem.DefAllowInsecure.ToString().ToLower(); - } - if (profileItem.Fingerprint.IsNullOrEmpty() && profileItem.StreamSecurity == Global.StreamSecurityReality) - { - profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint; - } - } - } - - if (profileItem.Network.IsNotEmpty() && !Global.Networks.Contains(profileItem.Network)) - { - profileItem.Network = Global.DefaultNetwork; - } - - var maxSort = -1; - if (profileItem.IndexId.IsNullOrEmpty()) - { - profileItem.IndexId = Utils.GetGuid(false); - maxSort = ProfileExHandler.Instance.GetMaxSort(); - } - if (!toFile && maxSort < 0) - { - maxSort = ProfileExHandler.Instance.GetMaxSort(); - } - if (maxSort > 0) - { - ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1); - } - - if (toFile) - { - await SQLiteHelper.Instance.ReplaceAsync(profileItem); - } - return 0; - } - - private static bool CompareProfileItem(ProfileItem? o, ProfileItem? n, bool remarks) - { - if (o == null || n == null) - { - return false; - } - - return o.ConfigType == n.ConfigType - && AreEqual(o.Address, n.Address) - && o.Port == n.Port - && AreEqual(o.Id, n.Id) - && AreEqual(o.Security, n.Security) - && AreEqual(o.Network, n.Network) - && AreEqual(o.HeaderType, n.HeaderType) - && AreEqual(o.RequestHost, n.RequestHost) - && AreEqual(o.Path, n.Path) - && (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity) - && AreEqual(o.Flow, n.Flow) - && AreEqual(o.Sni, n.Sni) - && AreEqual(o.Alpn, n.Alpn) - && AreEqual(o.Fingerprint, n.Fingerprint) - && AreEqual(o.PublicKey, n.PublicKey) - && AreEqual(o.ShortId, n.ShortId) - && (!remarks || o.Remarks == n.Remarks); - - static bool AreEqual(string? a, string? b) - { - return string.Equals(a, b) || (string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b)); - } - } - - private static async Task RemoveProfileItem(Config config, string indexId) - { - try - { - var item = await AppHandler.Instance.GetProfileItem(indexId); - if (item == null) - { - return 0; - } - if (item.ConfigType == EConfigType.Custom) - { - File.Delete(Utils.GetConfigPath(item.Address)); - } - - await SQLiteHelper.Instance.DeleteAsync(item); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - public static async Task AddCustomServer4Multiple(Config config, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad) - { - var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName); - var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName); - - var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad); - if (result.Success != true) - { - return result; - } - - if (!File.Exists(configPath)) - { - return result; - } - - var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new(); - profileItem.IndexId = indexId; - profileItem.Remarks = multipleLoad switch - { - EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom, - EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, - EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing, - EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad, - _ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, - }; - profileItem.Address = Global.CoreMultipleLoadConfigFileName; - profileItem.ConfigType = EConfigType.Custom; - profileItem.CoreType = coreType; - - await AddServerCommon(config, profileItem, true); - - result.Data = indexId; - return result; - } - - public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) - { - ProfileItem? itemSocks = null; - if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) - { - itemSocks = new ProfileItem() - { - CoreType = ECoreType.sing_box, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Sni = node.Address, //Tun2SocksAddress - Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) - }; - } - else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) - { - var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; - itemSocks = new ProfileItem() - { - CoreType = preCoreType, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Port = node.PreSocksPort.Value, - }; - } - await Task.CompletedTask; - return itemSocks; - } - - public static async Task RemoveInvalidServerResult(Config config, string subid) - { - var lstModel = await AppHandler.Instance.ProfileItems(subid, ""); - if (lstModel is { Count: <= 0 }) - { - return -1; - } - var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); - var lstProfile = (from t in lstModel - join t2 in lstProfileExs on t.IndexId equals t2.IndexId - where t2.Delay == -1 - select t).ToList(); - - await RemoveServers(config, JsonUtils.Deserialize>(JsonUtils.Serialize(lstProfile))); - - return lstProfile.Count; - } - - #endregion Server - - #region Batch add servers - - /// - /// 批量添加服务器 - /// - /// - /// - /// - /// 成功导入的数量 - private static async Task AddBatchServersCommon(Config config, string strData, string subid, bool isSub) - { - if (strData.IsNullOrEmpty()) - { - return -1; - } - - var subFilter = string.Empty; - //remove sub items - if (isSub && subid.IsNotEmpty()) - { - await RemoveServersViaSubid(config, subid, isSub); - subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? ""; - } - - var countServers = 0; - List lstAdd = new(); - var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty()); - if (isSub) - { - arrData = arrData.Distinct(); - } - foreach (var str in arrData) - { - //maybe sub - if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol))) - { - if (await AddSubItem(config, str) == 0) - { - countServers++; - } - continue; - } - var profileItem = FmtHandler.ResolveConfig(str, out string msg); - if (profileItem is null) - { - continue; - } - - //exist sub items //filter - if (isSub && subid.IsNotEmpty() && subFilter.IsNotEmpty()) - { - if (!Regex.IsMatch(profileItem.Remarks, subFilter)) - { - continue; - } - } - profileItem.Subid = subid; - profileItem.IsSub = isSub; - - var addStatus = profileItem.ConfigType switch - { - EConfigType.VMess => await AddVMessServer(config, profileItem, false), - EConfigType.Shadowsocks => await AddShadowsocksServer(config, profileItem, false), - EConfigType.SOCKS => await AddSocksServer(config, profileItem, false), - EConfigType.Trojan => await AddTrojanServer(config, profileItem, false), - EConfigType.VLESS => await AddVlessServer(config, profileItem, false), - EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), - EConfigType.TUIC => await AddTuicServer(config, profileItem, false), - EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), - _ => -1, - }; - - if (addStatus == 0) - { - countServers++; - lstAdd.Add(profileItem); - } - } - - if (lstAdd.Count > 0) - { - await SQLiteHelper.Instance.InsertAllAsync(lstAdd); - } - - await SaveConfig(config); - return countServers; - } - - private static async Task AddBatchServers4Custom(Config config, string strData, string subid, bool isSub) - { - if (strData.IsNullOrEmpty()) - { - return -1; - } - - var subItem = await AppHandler.Instance.GetSubItem(subid); - var subRemarks = subItem?.Remarks; - var preSocksPort = subItem?.PreSocksPort; - - List? lstProfiles = null; - //Is sing-box array configuration - if (lstProfiles is null || lstProfiles.Count <= 0) - { - lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks); - } - //Is v2ray array configuration - if (lstProfiles is null || lstProfiles.Count <= 0) - { - lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks); - } - if (lstProfiles != null && lstProfiles.Count > 0) - { - if (isSub && subid.IsNotEmpty()) - { - await RemoveServersViaSubid(config, subid, isSub); - } - int count = 0; - foreach (var it in lstProfiles) - { - it.Subid = subid; - it.IsSub = isSub; - it.PreSocksPort = preSocksPort; - if (await AddCustomServer(config, it, true) == 0) - { - count++; - } - } - if (count > 0) - { - return count; - } - } - - ProfileItem? profileItem = null; - //Is sing-box configuration - if (profileItem is null) - { - profileItem = SingboxFmt.ResolveFull(strData, subRemarks); - } - //Is v2ray configuration - if (profileItem is null) - { - profileItem = V2rayFmt.ResolveFull(strData, subRemarks); - } - //Is Clash configuration - if (profileItem is null) - { - profileItem = ClashFmt.ResolveFull(strData, subRemarks); - } - //Is hysteria configuration - if (profileItem is null) - { - profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks); - } - if (profileItem is null) - { - profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks); - } - //Is naiveproxy configuration - if (profileItem is null) - { - profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks); - } - if (profileItem is null || profileItem.Address.IsNullOrEmpty()) - { - return -1; - } - - if (isSub && subid.IsNotEmpty()) - { - await RemoveServersViaSubid(config, subid, isSub); - } - - profileItem.Subid = subid; - profileItem.IsSub = isSub; - profileItem.PreSocksPort = preSocksPort; - if (await AddCustomServer(config, profileItem, true) == 0) - { - return 1; - } - else - { - return -1; - } - } - - private static async Task AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub) - { - if (strData.IsNullOrEmpty()) - { - return -1; - } - - if (isSub && subid.IsNotEmpty()) - { - await RemoveServersViaSubid(config, subid, isSub); - } - - var lstSsServer = ShadowsocksFmt.ResolveSip008(strData); - if (lstSsServer?.Count > 0) - { - int counter = 0; - foreach (var ssItem in lstSsServer) - { - ssItem.Subid = subid; - ssItem.IsSub = isSub; - if (await AddShadowsocksServer(config, ssItem) == 0) - { - counter++; - } - } - await SaveConfig(config); - return counter; - } - + Logging.SaveLog(_tag, ex); return -1; } - public static async Task AddBatchServers(Config config, string strData, string subid, bool isSub) + return 0; + } + + #endregion ConfigHandler + + #region Server + + public static async Task AddServer(Config config, ProfileItem profileItem) + { + var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); + if (item is null) { - if (strData.IsNullOrEmpty()) - { - return -1; - } - List? lstOriSub = null; - ProfileItem? activeProfile = null; - if (isSub && subid.IsNotEmpty()) - { - lstOriSub = await AppHandler.Instance.ProfileItems(subid); - activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId); - } + item = profileItem; + } + else + { + item.CoreType = profileItem.CoreType; + item.Remarks = profileItem.Remarks; + item.Address = profileItem.Address; + item.Port = profileItem.Port; + item.Ports = profileItem.Ports; - var counter = 0; - if (Utils.IsBase64String(strData)) - { - counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); - } - if (counter < 1) - { - counter = await AddBatchServersCommon(config, strData, subid, isSub); - } - if (counter < 1) - { - counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); - } + item.Id = profileItem.Id; + item.AlterId = profileItem.AlterId; + item.Security = profileItem.Security; + item.Flow = profileItem.Flow; - if (counter < 1) - { - counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub); - } + item.Network = profileItem.Network; + item.HeaderType = profileItem.HeaderType; + item.RequestHost = profileItem.RequestHost; + item.Path = profileItem.Path; - //maybe other sub - if (counter < 1) - { - counter = await AddBatchServers4Custom(config, strData, subid, isSub); - } + item.StreamSecurity = profileItem.StreamSecurity; + item.Sni = profileItem.Sni; + item.AllowInsecure = profileItem.AllowInsecure; + item.Fingerprint = profileItem.Fingerprint; + item.Alpn = profileItem.Alpn; - //Select active node - if (activeProfile != null) - { - var lstSub = await AppHandler.Instance.ProfileItems(subid); - var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true)); - if (existItem != null) - { - await ConfigHandler.SetDefaultServerIndex(config, existItem.IndexId); - } - } - - //Keep the last traffic statistics - if (lstOriSub != null) - { - var lstSub = await AppHandler.Instance.ProfileItems(subid); - foreach (var item in lstSub) - { - var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true)); - if (existItem != null) - { - await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); - } - } - } - - return counter; + item.PublicKey = profileItem.PublicKey; + item.ShortId = profileItem.ShortId; + item.SpiderX = profileItem.SpiderX; + item.Extra = profileItem.Extra; } - #endregion Batch add servers - - #region Sub & Group - - /// - /// add sub - /// - /// - /// - /// - public static async Task AddSubItem(Config config, string url) + var ret = item.ConfigType switch { - //already exists - var count = await SQLiteHelper.Instance.TableAsync().CountAsync(e => e.Url == url); - if (count > 0) - { - return 0; - } - SubItem subItem = new() - { - Id = string.Empty, - Url = url - }; + EConfigType.VMess => await AddVMessServer(config, item), + EConfigType.Shadowsocks => await AddShadowsocksServer(config, item), + EConfigType.SOCKS => await AddSocksServer(config, item), + EConfigType.HTTP => await AddHttpServer(config, item), + EConfigType.Trojan => await AddTrojanServer(config, item), + EConfigType.VLESS => await AddVlessServer(config, item), + EConfigType.Hysteria2 => await AddHysteria2Server(config, item), + EConfigType.TUIC => await AddTuicServer(config, item), + EConfigType.WireGuard => await AddWireguardServer(config, item), + _ => -1, + }; + return ret; + } - var uri = Utils.TryUri(url); - if (uri == null) - return -1; - //Do not allow http protocol - if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) - { - //TODO Temporary reminder to be removed later - NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); - //return -1; - } + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddVMessServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.VMess; - var queryVars = Utils.ParseQueryString(uri.Query); - subItem.Remarks = queryVars["remarks"] ?? "import_sub"; + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = profileItem.Network.TrimEx(); + profileItem.HeaderType = profileItem.HeaderType.TrimEx(); + profileItem.RequestHost = profileItem.RequestHost.TrimEx(); + profileItem.Path = profileItem.Path.TrimEx(); + profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); - return await AddSubItem(config, subItem); + if (!Global.VmessSecurities.Contains(profileItem.Security)) + { + return -1; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; } - public static async Task AddSubItem(Config config, SubItem subItem) + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// 移除服务器 + /// + /// + /// + /// + public static async Task RemoveServers(Config config, List indexes) + { + var subid = "TempRemoveSubId"; + foreach (var item in indexes) { - var item = await AppHandler.Instance.GetSubItem(subItem.Id); + item.Subid = subid; + } + + await SQLiteHelper.Instance.UpdateAllAsync(indexes); + await RemoveServersViaSubid(config, subid, false); + + return 0; + } + + /// + /// 克隆服务器 + /// + /// + /// + /// + public static async Task CopyServer(Config config, List indexes) + { + foreach (var it in indexes) + { + var item = await AppHandler.Instance.GetProfileItem(it.IndexId); if (item is null) { - item = subItem; - } - else - { - item.Remarks = subItem.Remarks; - item.Url = subItem.Url; - item.MoreUrl = subItem.MoreUrl; - item.Enabled = subItem.Enabled; - item.AutoUpdateInterval = subItem.AutoUpdateInterval; - item.UserAgent = subItem.UserAgent; - item.Sort = subItem.Sort; - item.Filter = subItem.Filter; - item.UpdateTime = subItem.UpdateTime; - item.ConvertTarget = subItem.ConvertTarget; - item.PrevProfile = subItem.PrevProfile; - item.NextProfile = subItem.NextProfile; - item.PreSocksPort = subItem.PreSocksPort; - item.Memo = subItem.Memo; + continue; } - if (item.Id.IsNullOrEmpty()) - { - item.Id = Utils.GetGuid(false); + var profileItem = JsonUtils.DeepCopy(item); + profileItem.IndexId = string.Empty; + profileItem.Remarks = $"{item.Remarks}-clone"; - if (item.Sort <= 0) + if (profileItem.ConfigType == EConfigType.Custom) + { + profileItem.Address = Utils.GetConfigPath(profileItem.Address); + if (await AddCustomServer(config, profileItem, false) == 0) { - var maxSort = 0; - if (await SQLiteHelper.Instance.TableAsync().CountAsync() > 0) - { - var lstSubs = (await AppHandler.Instance.SubItems()); - maxSort = lstSubs.LastOrDefault()?.Sort ?? 0; - } - item.Sort = maxSort + 1; } } - if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) - { - return 0; - } else { - return -1; + await AddServerCommon(config, profileItem, true); } } - /// - /// 移除服务器 - /// - /// - /// - /// - public static async Task RemoveServersViaSubid(Config config, string subid, bool isSub) + return 0; + } + + /// + /// 设置活动服务器 + /// + /// + /// + /// + public static async Task SetDefaultServerIndex(Config config, string? indexId) + { + if (indexId.IsNullOrEmpty()) { - if (subid.IsNullOrEmpty()) + return -1; + } + + config.IndexId = indexId; + + await SaveConfig(config); + + return 0; + } + + public static async Task SetDefaultServer(Config config, List lstProfile) + { + if (lstProfile.Exists(t => t.IndexId == config.IndexId)) + { + return 0; + } + + if (await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.IndexId == config.IndexId) != null) + { + return 0; + } + if (lstProfile.Count > 0) + { + return await SetDefaultServerIndex(config, lstProfile.FirstOrDefault(t => t.Port > 0)?.IndexId); + } + + var item = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Port > 0); + return await SetDefaultServerIndex(config, item?.IndexId); + } + + public static async Task GetDefaultServer(Config config) + { + var item = await AppHandler.Instance.GetProfileItem(config.IndexId); + if (item is null) + { + var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); + await SetDefaultServerIndex(config, item2?.IndexId); + return item2; + } + + return item; + } + + /// + /// 移动服务器 + /// + /// + /// + /// + /// + /// + public static async Task MoveServer(Config config, List lstProfile, int index, EMove eMove, int pos = -1) + { + int count = lstProfile.Count; + if (index < 0 || index > lstProfile.Count - 1) + { + return -1; + } + + for (int i = 0; i < lstProfile.Count; i++) + { + ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); + } + + var sort = 0; + switch (eMove) + { + case EMove.Top: + { + if (index == 0) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1; + + break; + } + case EMove.Up: + { + if (index == 0) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1; + + break; + } + + case EMove.Down: + { + if (index == count - 1) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1; + + break; + } + case EMove.Bottom: + { + if (index == count - 1) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1; + + break; + } + case EMove.Position: + sort = (pos * 10) + 1; + break; + } + + ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort); + return await Task.FromResult(0); + } + + /// + /// 添加自定义服务器 + /// + /// + /// + /// + public static async Task AddCustomServer(Config config, ProfileItem profileItem, bool blDelete) + { + var fileName = profileItem.Address; + if (!File.Exists(fileName)) + { + return -1; + } + var ext = Path.GetExtension(fileName); + string newFileName = $"{Utils.GetGuid()}{ext}"; + //newFileName = Path.Combine(Utile.GetTempPath(), newFileName); + + try + { + File.Copy(fileName, Utils.GetConfigPath(newFileName)); + if (blDelete) { - return -1; + File.Delete(fileName); } - var customProfile = await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync(); - if (isSub) + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return -1; + } + + profileItem.Address = newFileName; + profileItem.ConfigType = EConfigType.Custom; + if (profileItem.Remarks.IsNullOrEmpty()) + { + profileItem.Remarks = $"import custom@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}"; + } + + await AddServerCommon(config, profileItem, true); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task EditCustomServer(Config config, ProfileItem profileItem) + { + var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId); + if (item is null) + { + item = profileItem; + } + else + { + item.Remarks = profileItem.Remarks; + item.Address = profileItem.Address; + item.CoreType = profileItem.CoreType; + item.DisplayLog = profileItem.DisplayLog; + item.PreSocksPort = profileItem.PreSocksPort; + } + + if (await SQLiteHelper.Instance.UpdateAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + + //ToJsonFile(config); + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddShadowsocksServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Shadowsocks; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + + if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) + { + return -1; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddSocksServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.SOCKS; + + profileItem.Address = profileItem.Address.TrimEx(); + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.HTTP; + + profileItem.Address = profileItem.Address.TrimEx(); + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddTrojanServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Trojan; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Hysteria2; + profileItem.CoreType = ECoreType.sing_box; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Path = profileItem.Path.TrimEx(); + profileItem.Network = string.Empty; + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.TUIC; + profileItem.CoreType = ECoreType.sing_box; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + + if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!; + } + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Alpn.IsNullOrEmpty()) + { + profileItem.Alpn = "h3"; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddWireguardServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.WireGuard; + profileItem.CoreType = ECoreType.sing_box; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.PublicKey = profileItem.PublicKey.TrimEx(); + profileItem.Path = profileItem.Path.TrimEx(); + profileItem.RequestHost = profileItem.RequestHost.TrimEx(); + profileItem.Network = string.Empty; + if (profileItem.ShortId.IsNullOrEmpty()) + { + profileItem.ShortId = Global.TunMtus.First().ToString(); + } + + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + public static async Task SortServers(Config config, string subId, string colName, bool asc) + { + var lstModel = await AppHandler.Instance.ProfileItems(subId, ""); + if (lstModel.Count <= 0) + { + return -1; + } + var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); + var lstProfile = (from t in lstModel + join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b + from t33 in t3b.DefaultIfEmpty() + select new ProfileItemModel + { + IndexId = t.IndexId, + ConfigType = t.ConfigType, + Remarks = t.Remarks, + Address = t.Address, + Port = t.Port, + Security = t.Security, + Network = t.Network, + StreamSecurity = t.StreamSecurity, + Delay = t33?.Delay ?? 0, + Speed = t33?.Speed ?? 0, + Sort = t33?.Sort ?? 0 + }).ToList(); + + Enum.TryParse(colName, true, out EServerColName name); + + if (asc) + { + lstProfile = name switch { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'"); + EServerColName.ConfigType => lstProfile.OrderBy(t => t.ConfigType).ToList(), + EServerColName.Remarks => lstProfile.OrderBy(t => t.Remarks).ToList(), + EServerColName.Address => lstProfile.OrderBy(t => t.Address).ToList(), + EServerColName.Port => lstProfile.OrderBy(t => t.Port).ToList(), + EServerColName.Network => lstProfile.OrderBy(t => t.Network).ToList(), + EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(), + EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), + EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(), + EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(), + _ => lstProfile + }; + } + else + { + lstProfile = name switch + { + EServerColName.ConfigType => lstProfile.OrderByDescending(t => t.ConfigType).ToList(), + EServerColName.Remarks => lstProfile.OrderByDescending(t => t.Remarks).ToList(), + EServerColName.Address => lstProfile.OrderByDescending(t => t.Address).ToList(), + EServerColName.Port => lstProfile.OrderByDescending(t => t.Port).ToList(), + EServerColName.Network => lstProfile.OrderByDescending(t => t.Network).ToList(), + EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(), + EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), + EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(), + EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(), + _ => lstProfile + }; + } + + for (var i = 0; i < lstProfile.Count; i++) + { + ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); + } + switch (name) + { + case EServerColName.DelayVal: + { + var maxSort = lstProfile.Max(t => t.Sort) + 10; + foreach (var item in lstProfile.Where(item => item.Delay <= 0)) + { + ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); + } + + break; + } + case EServerColName.SpeedVal: + { + var maxSort = lstProfile.Max(t => t.Sort) + 10; + foreach (var item in lstProfile.Where(item => item.Speed <= 0)) + { + ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); + } + + break; + } + } + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static async Task AddVlessServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.VLESS; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = profileItem.Network.TrimEx(); + profileItem.HeaderType = profileItem.HeaderType.TrimEx(); + profileItem.RequestHost = profileItem.RequestHost.TrimEx(); + profileItem.Path = profileItem.Path.TrimEx(); + profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); + + if (!Global.Flows.Contains(profileItem.Flow)) + { + profileItem.Flow = Global.Flows.First(); + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + if (profileItem.Security.IsNotEmpty() && profileItem.Security != Global.None) + { + profileItem.Security = Global.None; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + public static async Task> DedupServerList(Config config, string subId) + { + var lstProfile = await AppHandler.Instance.ProfileItems(subId); + if (lstProfile == null) + { + return new Tuple(0, 0); + } + + List lstKeep = new(); + List lstRemove = new(); + if (!config.GuiItem.KeepOlderDedupl) + { + lstProfile.Reverse(); + } + + foreach (var item in lstProfile) + { + if (!lstKeep.Exists(i => CompareProfileItem(i, item, false))) + { + lstKeep.Add(item); } else { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where subid = '{subid}'"); + lstRemove.Add(item); } - foreach (var item in customProfile) + } + await RemoveServers(config, lstRemove); + + return new Tuple(lstProfile.Count, lstKeep.Count); + } + + public static async Task AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigVersion = 2; + + if (profileItem.StreamSecurity.IsNotEmpty()) + { + if (profileItem.StreamSecurity != Global.StreamSecurity + && profileItem.StreamSecurity != Global.StreamSecurityReality) + { + profileItem.StreamSecurity = string.Empty; + } + else + { + if (profileItem.AllowInsecure.IsNullOrEmpty()) + { + profileItem.AllowInsecure = config.CoreBasicItem.DefAllowInsecure.ToString().ToLower(); + } + if (profileItem.Fingerprint.IsNullOrEmpty() && profileItem.StreamSecurity == Global.StreamSecurityReality) + { + profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint; + } + } + } + + if (profileItem.Network.IsNotEmpty() && !Global.Networks.Contains(profileItem.Network)) + { + profileItem.Network = Global.DefaultNetwork; + } + + var maxSort = -1; + if (profileItem.IndexId.IsNullOrEmpty()) + { + profileItem.IndexId = Utils.GetGuid(false); + maxSort = ProfileExHandler.Instance.GetMaxSort(); + } + if (!toFile && maxSort < 0) + { + maxSort = ProfileExHandler.Instance.GetMaxSort(); + } + if (maxSort > 0) + { + ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1); + } + + if (toFile) + { + await SQLiteHelper.Instance.ReplaceAsync(profileItem); + } + return 0; + } + + private static bool CompareProfileItem(ProfileItem? o, ProfileItem? n, bool remarks) + { + if (o == null || n == null) + { + return false; + } + + return o.ConfigType == n.ConfigType + && AreEqual(o.Address, n.Address) + && o.Port == n.Port + && AreEqual(o.Id, n.Id) + && AreEqual(o.Security, n.Security) + && AreEqual(o.Network, n.Network) + && AreEqual(o.HeaderType, n.HeaderType) + && AreEqual(o.RequestHost, n.RequestHost) + && AreEqual(o.Path, n.Path) + && (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity) + && AreEqual(o.Flow, n.Flow) + && AreEqual(o.Sni, n.Sni) + && AreEqual(o.Alpn, n.Alpn) + && AreEqual(o.Fingerprint, n.Fingerprint) + && AreEqual(o.PublicKey, n.PublicKey) + && AreEqual(o.ShortId, n.ShortId) + && (!remarks || o.Remarks == n.Remarks); + + static bool AreEqual(string? a, string? b) + { + return string.Equals(a, b) || (string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b)); + } + } + + private static async Task RemoveProfileItem(Config config, string indexId) + { + try + { + var item = await AppHandler.Instance.GetProfileItem(indexId); + if (item == null) + { + return 0; + } + if (item.ConfigType == EConfigType.Custom) { File.Delete(Utils.GetConfigPath(item.Address)); } - return 0; - } - - public static async Task DeleteSubItem(Config config, string id) - { - var item = await AppHandler.Instance.GetSubItem(id); - if (item is null) - { - return 0; - } await SQLiteHelper.Instance.DeleteAsync(item); - await RemoveServersViaSubid(config, id, false); - - return 0; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); } - public static async Task MoveToGroup(Config config, List lstProfile, string subid) - { - foreach (var item in lstProfile) - { - item.Subid = subid; - } - await SQLiteHelper.Instance.UpdateAllAsync(lstProfile); + return 0; + } - return 0; + public static async Task AddCustomServer4Multiple(Config config, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad) + { + var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName); + var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName); + + var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad); + if (result.Success != true) + { + return result; } - #endregion Sub & Group - - #region Routing - - public static async Task SaveRoutingItem(Config config, RoutingItem item) + if (!File.Exists(configPath)) { - if (item.Id.IsNullOrEmpty()) - { - item.Id = Utils.GetGuid(false); - } - - if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) - { - return 0; - } - else - { - return -1; - } + return result; } - /// - /// AddBatchRoutingRules - /// - /// - /// - /// - public static async Task AddBatchRoutingRules(RoutingItem routingItem, string strData) + var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new(); + profileItem.IndexId = indexId; + profileItem.Remarks = multipleLoad switch { - if (strData.IsNullOrEmpty()) - { - return -1; - } + EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom, + EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, + EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing, + EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad, + _ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin, + }; + profileItem.Address = Global.CoreMultipleLoadConfigFileName; + profileItem.ConfigType = EConfigType.Custom; + profileItem.CoreType = coreType; - var lstRules = JsonUtils.Deserialize>(strData); - if (lstRules == null) - { - return -1; - } + await AddServerCommon(config, profileItem, true); - foreach (var item in lstRules) - { - item.Id = Utils.GetGuid(false); - } - routingItem.RuleNum = lstRules.Count; - routingItem.RuleSet = JsonUtils.Serialize(lstRules, false); + result.Data = indexId; + return result; + } - if (routingItem.Id.IsNullOrEmpty()) + public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) + { + ProfileItem? itemSocks = null; + if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) + { + itemSocks = new ProfileItem() { - routingItem.Id = Utils.GetGuid(false); - } + CoreType = ECoreType.sing_box, + ConfigType = EConfigType.SOCKS, + Address = Global.Loopback, + Sni = node.Address, //Tun2SocksAddress + Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) + }; + } + else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) + { + var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; + itemSocks = new ProfileItem() + { + CoreType = preCoreType, + ConfigType = EConfigType.SOCKS, + Address = Global.Loopback, + Port = node.PreSocksPort.Value, + }; + } + await Task.CompletedTask; + return itemSocks; + } - if (await SQLiteHelper.Instance.ReplaceAsync(routingItem) > 0) - { - return 0; - } - else - { - return -1; - } + public static async Task RemoveInvalidServerResult(Config config, string subid) + { + var lstModel = await AppHandler.Instance.ProfileItems(subid, ""); + if (lstModel is { Count: <= 0 }) + { + return -1; + } + var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); + var lstProfile = (from t in lstModel + join t2 in lstProfileExs on t.IndexId equals t2.IndexId + where t2.Delay == -1 + select t).ToList(); + + await RemoveServers(config, JsonUtils.Deserialize>(JsonUtils.Serialize(lstProfile))); + + return lstProfile.Count; + } + + #endregion Server + + #region Batch add servers + + /// + /// 批量添加服务器 + /// + /// + /// + /// + /// 成功导入的数量 + private static async Task AddBatchServersCommon(Config config, string strData, string subid, bool isSub) + { + if (strData.IsNullOrEmpty()) + { + return -1; } - /// - /// MoveRoutingRule - /// - /// - /// - /// - /// - public static async Task MoveRoutingRule(List rules, int index, EMove eMove, int pos = -1) + var subFilter = string.Empty; + //remove sub items + if (isSub && subid.IsNotEmpty()) { - int count = rules.Count; - if (index < 0 || index > rules.Count - 1) - { - return -1; - } - switch (eMove) - { - case EMove.Top: - { - if (index == 0) - { - return 0; - } - var item = JsonUtils.DeepCopy(rules[index]); - rules.RemoveAt(index); - rules.Insert(0, item); - - break; - } - case EMove.Up: - { - if (index == 0) - { - return 0; - } - var item = JsonUtils.DeepCopy(rules[index]); - rules.RemoveAt(index); - rules.Insert(index - 1, item); - - break; - } - - case EMove.Down: - { - if (index == count - 1) - { - return 0; - } - var item = JsonUtils.DeepCopy(rules[index]); - rules.RemoveAt(index); - rules.Insert(index + 1, item); - - break; - } - case EMove.Bottom: - { - if (index == count - 1) - { - return 0; - } - var item = JsonUtils.DeepCopy(rules[index]); - rules.RemoveAt(index); - rules.Add(item); - - break; - } - case EMove.Position: - { - var removeItem = rules[index]; - var item = JsonUtils.DeepCopy(rules[index]); - rules.Insert(pos, item); - rules.Remove(removeItem); - break; - } - } - return await Task.FromResult(0); + await RemoveServersViaSubid(config, subid, isSub); + subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? ""; } - public static async Task SetDefaultRouting(Config config, RoutingItem routingItem) + var countServers = 0; + List lstAdd = new(); + var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty()); + if (isSub) { - if (await SQLiteHelper.Instance.TableAsync().Where(t => t.Id == routingItem.Id).CountAsync() > 0) - { - config.RoutingBasicItem.RoutingIndexId = routingItem.Id; - } - - await SaveConfig(config); - - return 0; + arrData = arrData.Distinct(); } - - public static async Task GetDefaultRouting(Config config) + foreach (var str in arrData) { - var item = await AppHandler.Instance.GetRoutingItem(config.RoutingBasicItem.RoutingIndexId); - if (item is null) + //maybe sub + if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol))) { - var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); - await SetDefaultRouting(config, item2); - return item2; - } - - return item; - } - - public static async Task InitRouting(Config config, bool blImportAdvancedRules = false) - { - if (config.ConstItem.RouteRulesTemplateSourceUrl.IsNullOrEmpty()) - { - await InitBuiltinRouting(config, blImportAdvancedRules); - } - else - { - await InitExternalRouting(config, blImportAdvancedRules); - } - - return 0; - } - - public static async Task InitExternalRouting(Config config, bool blImportAdvancedRules = false) - { - var downloadHandle = new DownloadService(); - var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, ""); - if (templateContent.IsNullOrEmpty()) - return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback - - var template = JsonUtils.Deserialize(templateContent); - if (template == null) - return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback - - var items = await AppHandler.Instance.RoutingItems(); - var maxSort = items.Count; - if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0) - { - return 0; - } - for (var i = 0; i < template.RoutingItems.Length; i++) - { - var item = template.RoutingItems[i]; - - if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty()) - continue; - - var ruleSetsString = !item.RuleSet.IsNullOrEmpty() - ? item.RuleSet - : await downloadHandle.TryDownloadString(item.Url, true, ""); - - if (ruleSetsString.IsNullOrEmpty()) - continue; - - item.Remarks = $"{template.Version}-{item.Remarks}"; - item.Enabled = true; - item.Sort = ++maxSort; - item.Url = string.Empty; - - await AddBatchRoutingRules(item, ruleSetsString); - - //first rule as default at first startup - if (!blImportAdvancedRules && i == 0) + if (await AddSubItem(config, str) == 0) { - await SetDefaultRouting(config, item); + countServers++; + } + continue; + } + var profileItem = FmtHandler.ResolveConfig(str, out string msg); + if (profileItem is null) + { + continue; + } + + //exist sub items //filter + if (isSub && subid.IsNotEmpty() && subFilter.IsNotEmpty()) + { + if (!Regex.IsMatch(profileItem.Remarks, subFilter)) + { + continue; } } + profileItem.Subid = subid; + profileItem.IsSub = isSub; - return 0; - } - - public static async Task InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) - { - var ver = "V3-"; - var items = await AppHandler.Instance.RoutingItems(); - - //TODO Temporary code to be removed later - var lockItem = items?.FirstOrDefault(t => t.Locked == true); - if (lockItem != null) + var addStatus = profileItem.ConfigType switch { - await ConfigHandler.RemoveRoutingItem(lockItem); - items = await AppHandler.Instance.RoutingItems(); - } - - if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0) - { - return 0; - } - - var maxSort = items.Count; - //Bypass the mainland - var item2 = new RoutingItem() - { - Remarks = $"{ver}绕过大陆(Whitelist)", - Url = string.Empty, - Sort = maxSort + 1, + EConfigType.VMess => await AddVMessServer(config, profileItem, false), + EConfigType.Shadowsocks => await AddShadowsocksServer(config, profileItem, false), + EConfigType.SOCKS => await AddSocksServer(config, profileItem, false), + EConfigType.Trojan => await AddTrojanServer(config, profileItem, false), + EConfigType.VLESS => await AddVlessServer(config, profileItem, false), + EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), + EConfigType.TUIC => await AddTuicServer(config, profileItem, false), + EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), + _ => -1, }; - await AddBatchRoutingRules(item2, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "white")); - //Blacklist - var item3 = new RoutingItem() + if (addStatus == 0) { - Remarks = $"{ver}黑名单(Blacklist)", - Url = string.Empty, - Sort = maxSort + 2, - }; - await AddBatchRoutingRules(item3, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "black")); - - //Global - var item1 = new RoutingItem() - { - Remarks = $"{ver}全局(Global)", - Url = string.Empty, - Sort = maxSort + 3, - }; - await AddBatchRoutingRules(item1, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "global")); - - if (!blImportAdvancedRules) - { - await SetDefaultRouting(config, item2); + countServers++; + lstAdd.Add(profileItem); } - return 0; } - public static async Task RemoveRoutingItem(RoutingItem routingItem) + if (lstAdd.Count > 0) { - await SQLiteHelper.Instance.DeleteAsync(routingItem); + await SQLiteHelper.Instance.InsertAllAsync(lstAdd); } - #endregion Routing - - #region DNS - - public static async Task InitBuiltinDNS(Config config) - { - var items = await AppHandler.Instance.DNSItems(); - if (items.Count <= 0) - { - var item = new DNSItem() - { - Remarks = "V2ray", - CoreType = ECoreType.Xray, - }; - await SaveDNSItems(config, item); - - var item2 = new DNSItem() - { - Remarks = "sing-box", - CoreType = ECoreType.sing_box, - }; - await SaveDNSItems(config, item2); - } - - return 0; - } - - public static async Task SaveDNSItems(Config config, DNSItem item) - { - if (item == null) - { - return -1; - } - - if (item.Id.IsNullOrEmpty()) - { - item.Id = Utils.GetGuid(false); - } - - if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) - { - return 0; - } - else - { - return -1; - } - } - - public static async Task GetExternalDNSItem(ECoreType type, string url) - { - var currentItem = await AppHandler.Instance.GetDNSItem(type); - - var downloadHandle = new DownloadService(); - var templateContent = await downloadHandle.TryDownloadString(url, true, ""); - if (templateContent.IsNullOrEmpty()) - return currentItem; - - var template = JsonUtils.Deserialize(templateContent); - if (template == null) - return currentItem; - - if (!template.NormalDNS.IsNullOrEmpty()) - template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, ""); - - if (!template.TunDNS.IsNullOrEmpty()) - template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, ""); - - template.Id = currentItem.Id; - template.Enabled = currentItem.Enabled; - template.Remarks = currentItem.Remarks; - template.CoreType = type; - - return template; - } - - #endregion DNS - - #region Regional Presets - - public static async Task ApplyRegionalPreset(Config config, EPresetType type) - { - switch (type) - { - case EPresetType.Default: - config.ConstItem.GeoSourceUrl = ""; - config.ConstItem.SrsSourceUrl = ""; - config.ConstItem.RouteRulesTemplateSourceUrl = ""; - - await SQLiteHelper.Instance.DeleteAllAsync(); - await InitBuiltinDNS(config); - - return true; - - case EPresetType.Russia: - config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1]; - config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1]; - config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; - - await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); - await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json")); - - return true; - - case EPresetType.Iran: - config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2]; - config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2]; - config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2]; - - await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); - await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); - - return true; - } - - return false; - } - - #endregion Regional Presets + await SaveConfig(config); + return countServers; } + + private static async Task AddBatchServers4Custom(Config config, string strData, string subid, bool isSub) + { + if (strData.IsNullOrEmpty()) + { + return -1; + } + + var subItem = await AppHandler.Instance.GetSubItem(subid); + var subRemarks = subItem?.Remarks; + var preSocksPort = subItem?.PreSocksPort; + + List? lstProfiles = null; + //Is sing-box array configuration + if (lstProfiles is null || lstProfiles.Count <= 0) + { + lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks); + } + //Is v2ray array configuration + if (lstProfiles is null || lstProfiles.Count <= 0) + { + lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks); + } + if (lstProfiles != null && lstProfiles.Count > 0) + { + if (isSub && subid.IsNotEmpty()) + { + await RemoveServersViaSubid(config, subid, isSub); + } + int count = 0; + foreach (var it in lstProfiles) + { + it.Subid = subid; + it.IsSub = isSub; + it.PreSocksPort = preSocksPort; + if (await AddCustomServer(config, it, true) == 0) + { + count++; + } + } + if (count > 0) + { + return count; + } + } + + ProfileItem? profileItem = null; + //Is sing-box configuration + if (profileItem is null) + { + profileItem = SingboxFmt.ResolveFull(strData, subRemarks); + } + //Is v2ray configuration + if (profileItem is null) + { + profileItem = V2rayFmt.ResolveFull(strData, subRemarks); + } + //Is Clash configuration + if (profileItem is null) + { + profileItem = ClashFmt.ResolveFull(strData, subRemarks); + } + //Is hysteria configuration + if (profileItem is null) + { + profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks); + } + if (profileItem is null) + { + profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks); + } + //Is naiveproxy configuration + if (profileItem is null) + { + profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks); + } + if (profileItem is null || profileItem.Address.IsNullOrEmpty()) + { + return -1; + } + + if (isSub && subid.IsNotEmpty()) + { + await RemoveServersViaSubid(config, subid, isSub); + } + + profileItem.Subid = subid; + profileItem.IsSub = isSub; + profileItem.PreSocksPort = preSocksPort; + if (await AddCustomServer(config, profileItem, true) == 0) + { + return 1; + } + else + { + return -1; + } + } + + private static async Task AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub) + { + if (strData.IsNullOrEmpty()) + { + return -1; + } + + if (isSub && subid.IsNotEmpty()) + { + await RemoveServersViaSubid(config, subid, isSub); + } + + var lstSsServer = ShadowsocksFmt.ResolveSip008(strData); + if (lstSsServer?.Count > 0) + { + int counter = 0; + foreach (var ssItem in lstSsServer) + { + ssItem.Subid = subid; + ssItem.IsSub = isSub; + if (await AddShadowsocksServer(config, ssItem) == 0) + { + counter++; + } + } + await SaveConfig(config); + return counter; + } + + return -1; + } + + public static async Task AddBatchServers(Config config, string strData, string subid, bool isSub) + { + if (strData.IsNullOrEmpty()) + { + return -1; + } + List? lstOriSub = null; + ProfileItem? activeProfile = null; + if (isSub && subid.IsNotEmpty()) + { + lstOriSub = await AppHandler.Instance.ProfileItems(subid); + activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId); + } + + var counter = 0; + if (Utils.IsBase64String(strData)) + { + counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); + } + if (counter < 1) + { + counter = await AddBatchServersCommon(config, strData, subid, isSub); + } + if (counter < 1) + { + counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); + } + + if (counter < 1) + { + counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub); + } + + //maybe other sub + if (counter < 1) + { + counter = await AddBatchServers4Custom(config, strData, subid, isSub); + } + + //Select active node + if (activeProfile != null) + { + var lstSub = await AppHandler.Instance.ProfileItems(subid); + var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true)); + if (existItem != null) + { + await ConfigHandler.SetDefaultServerIndex(config, existItem.IndexId); + } + } + + //Keep the last traffic statistics + if (lstOriSub != null) + { + var lstSub = await AppHandler.Instance.ProfileItems(subid); + foreach (var item in lstSub) + { + var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true)); + if (existItem != null) + { + await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); + } + } + } + + return counter; + } + + #endregion Batch add servers + + #region Sub & Group + + /// + /// add sub + /// + /// + /// + /// + public static async Task AddSubItem(Config config, string url) + { + //already exists + var count = await SQLiteHelper.Instance.TableAsync().CountAsync(e => e.Url == url); + if (count > 0) + { + return 0; + } + SubItem subItem = new() + { + Id = string.Empty, + Url = url + }; + + var uri = Utils.TryUri(url); + if (uri == null) + return -1; + //Do not allow http protocol + if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) + { + //TODO Temporary reminder to be removed later + NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); + //return -1; + } + + var queryVars = Utils.ParseQueryString(uri.Query); + subItem.Remarks = queryVars["remarks"] ?? "import_sub"; + + return await AddSubItem(config, subItem); + } + + public static async Task AddSubItem(Config config, SubItem subItem) + { + var item = await AppHandler.Instance.GetSubItem(subItem.Id); + if (item is null) + { + item = subItem; + } + else + { + item.Remarks = subItem.Remarks; + item.Url = subItem.Url; + item.MoreUrl = subItem.MoreUrl; + item.Enabled = subItem.Enabled; + item.AutoUpdateInterval = subItem.AutoUpdateInterval; + item.UserAgent = subItem.UserAgent; + item.Sort = subItem.Sort; + item.Filter = subItem.Filter; + item.UpdateTime = subItem.UpdateTime; + item.ConvertTarget = subItem.ConvertTarget; + item.PrevProfile = subItem.PrevProfile; + item.NextProfile = subItem.NextProfile; + item.PreSocksPort = subItem.PreSocksPort; + item.Memo = subItem.Memo; + } + + if (item.Id.IsNullOrEmpty()) + { + item.Id = Utils.GetGuid(false); + + if (item.Sort <= 0) + { + var maxSort = 0; + if (await SQLiteHelper.Instance.TableAsync().CountAsync() > 0) + { + var lstSubs = (await AppHandler.Instance.SubItems()); + maxSort = lstSubs.LastOrDefault()?.Sort ?? 0; + } + item.Sort = maxSort + 1; + } + } + if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// 移除服务器 + /// + /// + /// + /// + public static async Task RemoveServersViaSubid(Config config, string subid, bool isSub) + { + if (subid.IsNullOrEmpty()) + { + return -1; + } + var customProfile = await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync(); + if (isSub) + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'"); + } + else + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where subid = '{subid}'"); + } + foreach (var item in customProfile) + { + File.Delete(Utils.GetConfigPath(item.Address)); + } + + return 0; + } + + public static async Task DeleteSubItem(Config config, string id) + { + var item = await AppHandler.Instance.GetSubItem(id); + if (item is null) + { + return 0; + } + await SQLiteHelper.Instance.DeleteAsync(item); + await RemoveServersViaSubid(config, id, false); + + return 0; + } + + public static async Task MoveToGroup(Config config, List lstProfile, string subid) + { + foreach (var item in lstProfile) + { + item.Subid = subid; + } + await SQLiteHelper.Instance.UpdateAllAsync(lstProfile); + + return 0; + } + + #endregion Sub & Group + + #region Routing + + public static async Task SaveRoutingItem(Config config, RoutingItem item) + { + if (item.Id.IsNullOrEmpty()) + { + item.Id = Utils.GetGuid(false); + } + + if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// AddBatchRoutingRules + /// + /// + /// + /// + public static async Task AddBatchRoutingRules(RoutingItem routingItem, string strData) + { + if (strData.IsNullOrEmpty()) + { + return -1; + } + + var lstRules = JsonUtils.Deserialize>(strData); + if (lstRules == null) + { + return -1; + } + + foreach (var item in lstRules) + { + item.Id = Utils.GetGuid(false); + } + routingItem.RuleNum = lstRules.Count; + routingItem.RuleSet = JsonUtils.Serialize(lstRules, false); + + if (routingItem.Id.IsNullOrEmpty()) + { + routingItem.Id = Utils.GetGuid(false); + } + + if (await SQLiteHelper.Instance.ReplaceAsync(routingItem) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// MoveRoutingRule + /// + /// + /// + /// + /// + public static async Task MoveRoutingRule(List rules, int index, EMove eMove, int pos = -1) + { + int count = rules.Count; + if (index < 0 || index > rules.Count - 1) + { + return -1; + } + switch (eMove) + { + case EMove.Top: + { + if (index == 0) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(0, item); + + break; + } + case EMove.Up: + { + if (index == 0) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(index - 1, item); + + break; + } + + case EMove.Down: + { + if (index == count - 1) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(index + 1, item); + + break; + } + case EMove.Bottom: + { + if (index == count - 1) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Add(item); + + break; + } + case EMove.Position: + { + var removeItem = rules[index]; + var item = JsonUtils.DeepCopy(rules[index]); + rules.Insert(pos, item); + rules.Remove(removeItem); + break; + } + } + return await Task.FromResult(0); + } + + public static async Task SetDefaultRouting(Config config, RoutingItem routingItem) + { + if (await SQLiteHelper.Instance.TableAsync().Where(t => t.Id == routingItem.Id).CountAsync() > 0) + { + config.RoutingBasicItem.RoutingIndexId = routingItem.Id; + } + + await SaveConfig(config); + + return 0; + } + + public static async Task GetDefaultRouting(Config config) + { + var item = await AppHandler.Instance.GetRoutingItem(config.RoutingBasicItem.RoutingIndexId); + if (item is null) + { + var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); + await SetDefaultRouting(config, item2); + return item2; + } + + return item; + } + + public static async Task InitRouting(Config config, bool blImportAdvancedRules = false) + { + if (config.ConstItem.RouteRulesTemplateSourceUrl.IsNullOrEmpty()) + { + await InitBuiltinRouting(config, blImportAdvancedRules); + } + else + { + await InitExternalRouting(config, blImportAdvancedRules); + } + + return 0; + } + + public static async Task InitExternalRouting(Config config, bool blImportAdvancedRules = false) + { + var downloadHandle = new DownloadService(); + var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, ""); + if (templateContent.IsNullOrEmpty()) + return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback + + var template = JsonUtils.Deserialize(templateContent); + if (template == null) + return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback + + var items = await AppHandler.Instance.RoutingItems(); + var maxSort = items.Count; + if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0) + { + return 0; + } + for (var i = 0; i < template.RoutingItems.Length; i++) + { + var item = template.RoutingItems[i]; + + if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty()) + continue; + + var ruleSetsString = !item.RuleSet.IsNullOrEmpty() + ? item.RuleSet + : await downloadHandle.TryDownloadString(item.Url, true, ""); + + if (ruleSetsString.IsNullOrEmpty()) + continue; + + item.Remarks = $"{template.Version}-{item.Remarks}"; + item.Enabled = true; + item.Sort = ++maxSort; + item.Url = string.Empty; + + await AddBatchRoutingRules(item, ruleSetsString); + + //first rule as default at first startup + if (!blImportAdvancedRules && i == 0) + { + await SetDefaultRouting(config, item); + } + } + + return 0; + } + + public static async Task InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) + { + var ver = "V3-"; + var items = await AppHandler.Instance.RoutingItems(); + + //TODO Temporary code to be removed later + var lockItem = items?.FirstOrDefault(t => t.Locked == true); + if (lockItem != null) + { + await ConfigHandler.RemoveRoutingItem(lockItem); + items = await AppHandler.Instance.RoutingItems(); + } + + if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0) + { + return 0; + } + + var maxSort = items.Count; + //Bypass the mainland + var item2 = new RoutingItem() + { + Remarks = $"{ver}绕过大陆(Whitelist)", + Url = string.Empty, + Sort = maxSort + 1, + }; + await AddBatchRoutingRules(item2, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "white")); + + //Blacklist + var item3 = new RoutingItem() + { + Remarks = $"{ver}黑名单(Blacklist)", + Url = string.Empty, + Sort = maxSort + 2, + }; + await AddBatchRoutingRules(item3, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "black")); + + //Global + var item1 = new RoutingItem() + { + Remarks = $"{ver}全局(Global)", + Url = string.Empty, + Sort = maxSort + 3, + }; + await AddBatchRoutingRules(item1, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "global")); + + if (!blImportAdvancedRules) + { + await SetDefaultRouting(config, item2); + } + return 0; + } + + public static async Task RemoveRoutingItem(RoutingItem routingItem) + { + await SQLiteHelper.Instance.DeleteAsync(routingItem); + } + + #endregion Routing + + #region DNS + + public static async Task InitBuiltinDNS(Config config) + { + var items = await AppHandler.Instance.DNSItems(); + if (items.Count <= 0) + { + var item = new DNSItem() + { + Remarks = "V2ray", + CoreType = ECoreType.Xray, + }; + await SaveDNSItems(config, item); + + var item2 = new DNSItem() + { + Remarks = "sing-box", + CoreType = ECoreType.sing_box, + }; + await SaveDNSItems(config, item2); + } + + return 0; + } + + public static async Task SaveDNSItems(Config config, DNSItem item) + { + if (item == null) + { + return -1; + } + + if (item.Id.IsNullOrEmpty()) + { + item.Id = Utils.GetGuid(false); + } + + if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + public static async Task GetExternalDNSItem(ECoreType type, string url) + { + var currentItem = await AppHandler.Instance.GetDNSItem(type); + + var downloadHandle = new DownloadService(); + var templateContent = await downloadHandle.TryDownloadString(url, true, ""); + if (templateContent.IsNullOrEmpty()) + return currentItem; + + var template = JsonUtils.Deserialize(templateContent); + if (template == null) + return currentItem; + + if (!template.NormalDNS.IsNullOrEmpty()) + template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, ""); + + if (!template.TunDNS.IsNullOrEmpty()) + template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, ""); + + template.Id = currentItem.Id; + template.Enabled = currentItem.Enabled; + template.Remarks = currentItem.Remarks; + template.CoreType = type; + + return template; + } + + #endregion DNS + + #region Regional Presets + + public static async Task ApplyRegionalPreset(Config config, EPresetType type) + { + switch (type) + { + case EPresetType.Default: + config.ConstItem.GeoSourceUrl = ""; + config.ConstItem.SrsSourceUrl = ""; + config.ConstItem.RouteRulesTemplateSourceUrl = ""; + + await SQLiteHelper.Instance.DeleteAllAsync(); + await InitBuiltinDNS(config); + + return true; + + case EPresetType.Russia: + config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1]; + config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1]; + config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; + + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json")); + + return true; + + case EPresetType.Iran: + config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2]; + config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2]; + config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2]; + + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); + + return true; + } + + return false; + } + + #endregion Regional Presets } diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 00f8623b..40c5eacf 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -1,160 +1,155 @@ -using DynamicData; -using ServiceLib.Enums; -using ServiceLib.Models; +namespace ServiceLib.Handler; -namespace ServiceLib.Handler +/// +/// Core configuration file processing class +/// +public class CoreConfigHandler { - /// - /// Core configuration file processing class - /// - public class CoreConfigHandler + private static readonly string _tag = "CoreConfigHandler"; + + public static async Task GenerateClientConfig(ProfileItem node, string? fileName) { - private static readonly string _tag = "CoreConfigHandler"; + var config = AppHandler.Instance.Config; + var result = new RetResult(); - public static async Task GenerateClientConfig(ProfileItem node, string? fileName) + if (node.ConfigType == EConfigType.Custom) { - var config = AppHandler.Instance.Config; - var result = new RetResult(); - - if (node.ConfigType == EConfigType.Custom) + result = node.CoreType switch { - result = node.CoreType switch - { - ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), - ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), - _ => await GenerateClientCustomConfig(node, fileName) - }; - } - else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) - { - result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); - } - else - { - result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); - } - if (result.Success != true) - { - return result; - } - if (fileName.IsNotEmpty() && result.Data != null) - { - await File.WriteAllTextAsync(fileName, result.Data.ToString()); - } - + ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), + ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), + _ => await GenerateClientCustomConfig(node, fileName) + }; + } + else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + { + result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); + } + else + { + result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); + } + if (result.Success != true) + { return result; } - - private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + if (fileName.IsNotEmpty() && result.Data != null) { - var ret = new RetResult(); - try + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + } + + return result; + } + + private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + try + { + if (node == null || fileName is null) { - if (node == null || fileName is null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail - File.Delete(fileName); - } - - string addressFileName = node.Address; - if (!File.Exists(addressFileName)) - { - addressFileName = Utils.GetConfigPath(addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - File.Copy(addressFileName, fileName); - File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - return await Task.FromResult(ret); + ret.Msg = ResUI.CheckServerSettings; + return ret; } - catch (Exception ex) + + if (File.Exists(fileName)) + { + File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (!File.Exists(addressFileName)) + { + addressFileName = Utils.GetConfigPath(addressFileName); + } + if (!File.Exists(addressFileName)) { - Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } + File.Copy(addressFileName, fileName); + File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return await Task.FromResult(ret); } - - public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) + catch (Exception ex) { - var result = new RetResult(); - if (coreType == ECoreType.sing_box) - { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); - } - else if (coreType == ECoreType.Xray) - { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); - } - if (result.Success != true) - { - return result; - } - await File.WriteAllTextAsync(fileName, result.Data.ToString()); - return result; - } - - public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) - { - var result = new RetResult(); - var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); - var port = Utils.GetFreePort(initPort + testItem.QueueNum); - testItem.Port = port; - - if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) - { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); - } - else - { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); - } - if (result.Success != true) - { - return result; - } - - await File.WriteAllTextAsync(fileName, result.Data.ToString()); - return result; - } - - public static async Task GenerateClientMultipleLoadConfig(Config config, string fileName, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad) - { - var result = new RetResult(); - if (coreType == ECoreType.sing_box) - { - result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds); - } - else - { - result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad); - } - - if (result.Success != true) - { - return result; - } - await File.WriteAllTextAsync(fileName, result.Data.ToString()); - return result; + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; } } + + public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) + { + var result = new RetResult(); + if (coreType == ECoreType.sing_box) + { + result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); + } + else if (coreType == ECoreType.Xray) + { + result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); + } + if (result.Success != true) + { + return result; + } + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + return result; + } + + public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) + { + var result = new RetResult(); + var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); + var port = Utils.GetFreePort(initPort + testItem.QueueNum); + testItem.Port = port; + + if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + { + result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); + } + else + { + result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); + } + if (result.Success != true) + { + return result; + } + + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + return result; + } + + public static async Task GenerateClientMultipleLoadConfig(Config config, string fileName, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad) + { + var result = new RetResult(); + if (coreType == ECoreType.sing_box) + { + result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds); + } + else + { + result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad); + } + + if (result.Success != true) + { + return result; + } + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + return result; + } } diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 8e225cd4..c3535a4a 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -1,410 +1,409 @@ using System.Diagnostics; using System.Text; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +/// +/// Core process processing class +/// +public class CoreHandler { - /// - /// Core process processing class - /// - public class CoreHandler + private static readonly Lazy _instance = new(() => new()); + public static CoreHandler Instance => _instance.Value; + private Config _config; + private Process? _process; + private Process? _processPre; + private int _linuxSudoPid = -1; + private Action? _updateFunc; + private const string _tag = "CoreHandler"; + + public async Task Init(Config config, Action updateFunc) { - private static readonly Lazy _instance = new(() => new()); - public static CoreHandler Instance => _instance.Value; - private Config _config; - private Process? _process; - private Process? _processPre; - private int _linuxSudoPid = -1; - private Action? _updateFunc; - private const string _tag = "CoreHandler"; + _config = config; + _updateFunc = updateFunc; - public async Task Init(Config config, Action updateFunc) + Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); + + //Copy the bin folder to the storage location (for init) + if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") { - _config = config; - _updateFunc = updateFunc; - - Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); - - //Copy the bin folder to the storage location (for init) - if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") + var fromPath = Utils.GetBaseDirectory("bin"); + var toPath = Utils.GetBinPath(""); + if (fromPath != toPath) { - var fromPath = Utils.GetBaseDirectory("bin"); - var toPath = Utils.GetBinPath(""); - if (fromPath != toPath) - { - FileManager.CopyDirectory(fromPath, toPath, true, false); - } + FileManager.CopyDirectory(fromPath, toPath, true, false); } + } - if (Utils.IsNonWindows()) + if (Utils.IsNonWindows()) + { + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); + foreach (var it in coreInfo) { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); - foreach (var it in coreInfo) + if (it.CoreType == ECoreType.v2rayN) { - if (it.CoreType == ECoreType.v2rayN) + if (Utils.UpgradeAppExists(out var upgradeFileName)) { - if (Utils.UpgradeAppExists(out var upgradeFileName)) - { - await Utils.SetLinuxChmod(upgradeFileName); - } - continue; + await Utils.SetLinuxChmod(upgradeFileName); } + continue; + } - foreach (var name in it.CoreExes) + foreach (var name in it.CoreExes) + { + var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString()); + if (File.Exists(exe)) { - var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString()); - if (File.Exists(exe)) - { - await Utils.SetLinuxChmod(exe); - } + await Utils.SetLinuxChmod(exe); } } } } + } - public async Task LoadCore(ProfileItem? node) + public async Task LoadCore(ProfileItem? node) + { + if (node == null) { - if (node == null) - { - UpdateFunc(false, ResUI.CheckServerSettings); - return; - } + UpdateFunc(false, ResUI.CheckServerSettings); + return; + } - var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); - if (result.Success != true) - { - UpdateFunc(true, result.Msg); - return; - } + var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); + var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); + if (result.Success != true) + { + UpdateFunc(true, result.Msg); + return; + } - UpdateFunc(false, $"{node.GetSummary()}"); - UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); - UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); - await CoreStop(); + UpdateFunc(false, $"{node.GetSummary()}"); + UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); + UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); + await CoreStop(); + await Task.Delay(100); + + if (Utils.IsWindows() && _config.TunModeItem.EnableTun) + { await Task.Delay(100); + await WindowsUtils.RemoveTunDevice(); + } - if (Utils.IsWindows() && _config.TunModeItem.EnableTun) - { - await Task.Delay(100); - await WindowsUtils.RemoveTunDevice(); - } + await CoreStart(node); + await CoreStartPreService(node); + if (_process != null) + { + UpdateFunc(true, $"{node.GetSummary()}"); + } + } - await CoreStart(node); - await CoreStartPreService(node); + public async Task LoadCoreConfigSpeedtest(List selecteds) + { + var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard) ? ECoreType.sing_box : ECoreType.Xray; + var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); + var configPath = Utils.GetBinConfigPath(fileName); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); + UpdateFunc(false, result.Msg); + if (result.Success != true) + { + return -1; + } + + UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); + UpdateFunc(false, configPath); + + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); + var proc = await RunProcess(coreInfo, fileName, true, false); + if (proc is null) + { + return -1; + } + + return proc.Id; + } + + public async Task LoadCoreConfigSpeedtest(ServerTestItem testItem) + { + var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId); + if (node is null) + { + return -1; + } + + var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); + var configPath = Utils.GetBinConfigPath(fileName); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); + if (result.Success != true) + { + return -1; + } + + var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); + var proc = await RunProcess(coreInfo, fileName, true, false); + if (proc is null) + { + return -1; + } + + return proc.Id; + } + + public async Task CoreStop() + { + try + { if (_process != null) { - UpdateFunc(true, $"{node.GetSummary()}"); + await ProcUtils.ProcessKill(_process, true); + _process = null; } + + if (_processPre != null) + { + await ProcUtils.ProcessKill(_processPre, true); + _processPre = null; + } + + if (_linuxSudoPid > 0) + { + await KillProcessAsLinuxSudo(); + } + _linuxSudoPid = -1; } - - public async Task LoadCoreConfigSpeedtest(List selecteds) + catch (Exception ex) { - var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard) ? ECoreType.sing_box : ECoreType.Xray; - var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); - var configPath = Utils.GetBinConfigPath(fileName); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); - UpdateFunc(false, result.Msg); - if (result.Success != true) - { - return -1; - } - - UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); - UpdateFunc(false, configPath); - - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); - var proc = await RunProcess(coreInfo, fileName, true, false); - if (proc is null) - { - return -1; - } - - return proc.Id; + Logging.SaveLog(_tag, ex); } + } - public async Task LoadCoreConfigSpeedtest(ServerTestItem testItem) + #region Private + + private async Task CoreStart(ProfileItem node) + { + var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); + + var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; + var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); + if (proc is null) { - var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId); - if (node is null) - { - return -1; - } - - var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); - var configPath = Utils.GetBinConfigPath(fileName); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); - if (result.Success != true) - { - return -1; - } + return; + } + _process = proc; + } + private async Task CoreStartPreService(ProfileItem node) + { + if (_process != null && !_process.HasExited) + { var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); - var proc = await RunProcess(coreInfo, fileName, true, false); - if (proc is null) + var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); + if (itemSocks != null) { - return -1; - } - - return proc.Id; - } - - public async Task CoreStop() - { - try - { - if (_process != null) + var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); + var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); + if (result.Success) { - await ProcUtils.ProcessKill(_process, true); - _process = null; - } - - if (_processPre != null) - { - await ProcUtils.ProcessKill(_processPre, true); - _processPre = null; - } - - if (_linuxSudoPid > 0) - { - await KillProcessAsLinuxSudo(); - } - _linuxSudoPid = -1; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - #region Private - - private async Task CoreStart(ProfileItem node) - { - var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); - - var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; - var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); - if (proc is null) - { - return; - } - _process = proc; - } - - private async Task CoreStartPreService(ProfileItem node) - { - if (_process != null && !_process.HasExited) - { - var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); - if (itemSocks != null) - { - var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); - if (result.Success) + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType); + var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); + if (proc is null) { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); - if (proc is null) - { - return; - } - _processPre = proc; + return; } + _processPre = proc; } } } + } - private void UpdateFunc(bool notify, string msg) + private void UpdateFunc(bool notify, string msg) + { + _updateFunc?.Invoke(notify, msg); + } + + private bool IsNeedSudo(ECoreType eCoreType) + { + return _config.TunModeItem.EnableTun + && eCoreType == ECoreType.sing_box + && (Utils.IsNonWindows()) + //&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty() + ; + } + + #endregion Private + + #region Process + + private async Task RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) + { + var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg); + if (fileName.IsNullOrEmpty()) { - _updateFunc?.Invoke(notify, msg); + UpdateFunc(false, msg); + return null; } - private bool IsNeedSudo(ECoreType eCoreType) + try { - return _config.TunModeItem.EnableTun - && eCoreType == ECoreType.sing_box - && (Utils.IsNonWindows()) - //&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty() - ; - } - - #endregion Private - - #region Process - - private async Task RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) - { - var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg); - if (fileName.IsNullOrEmpty()) - { - UpdateFunc(false, msg); - return null; - } - - try - { - Process proc = new() - { - StartInfo = new() - { - FileName = fileName, - Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), - WorkingDirectory = Utils.GetBinConfigPath(), - UseShellExecute = false, - RedirectStandardOutput = displayLog, - RedirectStandardError = displayLog, - CreateNoWindow = true, - StandardOutputEncoding = displayLog ? Encoding.UTF8 : null, - StandardErrorEncoding = displayLog ? Encoding.UTF8 : null, - } - }; - - var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType); - if (isNeedSudo) - { - await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath); - } - - if (displayLog) - { - proc.OutputDataReceived += (sender, e) => - { - if (e.Data.IsNullOrEmpty()) - return; - UpdateFunc(false, e.Data + Environment.NewLine); - }; - proc.ErrorDataReceived += (sender, e) => - { - if (e.Data.IsNullOrEmpty()) - return; - UpdateFunc(false, e.Data + Environment.NewLine); - }; - } - proc.Start(); - - if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) - { - var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd); - await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(pwd); - await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(pwd); - } - if (isNeedSudo) - _linuxSudoPid = proc.Id; - - if (displayLog) - { - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - } - - await Task.Delay(500); - AppHandler.Instance.AddProcess(proc.Handle); - if (proc is null or { HasExited: true }) - { - throw new Exception(ResUI.FailedToRunCore); - } - return proc; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - UpdateFunc(mayNeedSudo, ex.Message); - return null; - } - } - - #endregion Process - - #region Linux - - private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath) - { - var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; - - var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh"); - proc.StartInfo.FileName = shFilePath; - proc.StartInfo.Arguments = ""; - proc.StartInfo.WorkingDirectory = ""; - if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) - { - proc.StartInfo.StandardInputEncoding = Encoding.UTF8; - proc.StartInfo.RedirectStandardInput = true; - } - } - - private async Task KillProcessAsLinuxSudo() - { - var cmdLine = $"kill {_linuxSudoPid}"; - var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh"); Process proc = new() { StartInfo = new() { - FileName = shFilePath, + FileName = fileName, + Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), + WorkingDirectory = Utils.GetBinConfigPath(), UseShellExecute = false, + RedirectStandardOutput = displayLog, + RedirectStandardError = displayLog, CreateNoWindow = true, - StandardInputEncoding = Encoding.UTF8, - RedirectStandardInput = true + StandardOutputEncoding = displayLog ? Encoding.UTF8 : null, + StandardErrorEncoding = displayLog ? Encoding.UTF8 : null, } }; + + var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType); + if (isNeedSudo) + { + await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath); + } + + if (displayLog) + { + proc.OutputDataReceived += (sender, e) => + { + if (e.Data.IsNullOrEmpty()) + return; + UpdateFunc(false, e.Data + Environment.NewLine); + }; + proc.ErrorDataReceived += (sender, e) => + { + if (e.Data.IsNullOrEmpty()) + return; + UpdateFunc(false, e.Data + Environment.NewLine); + }; + } proc.Start(); - if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) + if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) { - try - { - var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd); - await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(pwd); - await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(pwd); - } - catch (Exception) - { - // ignored - } + var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd); + await Task.Delay(10); + await proc.StandardInput.WriteLineAsync(pwd); + await Task.Delay(10); + await proc.StandardInput.WriteLineAsync(pwd); + } + if (isNeedSudo) + _linuxSudoPid = proc.Id; + + if (displayLog) + { + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); } - var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - await proc.WaitForExitAsync(timeout.Token); - await Task.Delay(3000); + await Task.Delay(500); + AppHandler.Instance.AddProcess(proc.Handle); + if (proc is null or { HasExited: true }) + { + throw new Exception(ResUI.FailedToRunCore); + } + return proc; } - - private async Task CreateLinuxShellFile(string cmdLine, string fileName) + catch (Exception ex) { - //Shell scripts - var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName); - File.Delete(shFilePath); - var sb = new StringBuilder(); - sb.AppendLine("#!/bin/sh"); - if (AppHandler.Instance.IsAdministrator) - { - sb.AppendLine($"{cmdLine}"); - } - else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty()) - { - sb.AppendLine($"pkexec {cmdLine}"); - } - else - { - sb.AppendLine($"sudo -S {cmdLine}"); - } + Logging.SaveLog(_tag, ex); + UpdateFunc(mayNeedSudo, ex.Message); + return null; + } + } - await File.WriteAllTextAsync(shFilePath, sb.ToString()); - await Utils.SetLinuxChmod(shFilePath); - Logging.SaveLog(shFilePath); + #endregion Process - return shFilePath; + #region Linux + + private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath) + { + var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; + + var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh"); + proc.StartInfo.FileName = shFilePath; + proc.StartInfo.Arguments = ""; + proc.StartInfo.WorkingDirectory = ""; + if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) + { + proc.StartInfo.StandardInputEncoding = Encoding.UTF8; + proc.StartInfo.RedirectStandardInput = true; + } + } + + private async Task KillProcessAsLinuxSudo() + { + var cmdLine = $"kill {_linuxSudoPid}"; + var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh"); + Process proc = new() + { + StartInfo = new() + { + FileName = shFilePath, + UseShellExecute = false, + CreateNoWindow = true, + StandardInputEncoding = Encoding.UTF8, + RedirectStandardInput = true + } + }; + proc.Start(); + + if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty()) + { + try + { + var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd); + await Task.Delay(10); + await proc.StandardInput.WriteLineAsync(pwd); + await Task.Delay(10); + await proc.StandardInput.WriteLineAsync(pwd); + } + catch (Exception) + { + // ignored + } } - #endregion Linux + var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + await proc.WaitForExitAsync(timeout.Token); + await Task.Delay(3000); } + + private async Task CreateLinuxShellFile(string cmdLine, string fileName) + { + //Shell scripts + var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName); + File.Delete(shFilePath); + var sb = new StringBuilder(); + sb.AppendLine("#!/bin/sh"); + if (AppHandler.Instance.IsAdministrator) + { + sb.AppendLine($"{cmdLine}"); + } + else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty()) + { + sb.AppendLine($"pkexec {cmdLine}"); + } + else + { + sb.AppendLine($"sudo -S {cmdLine}"); + } + + await File.WriteAllTextAsync(shFilePath, sb.ToString()); + await Utils.SetLinuxChmod(shFilePath); + Logging.SaveLog(shFilePath); + + return shFilePath; + } + + #endregion Linux } diff --git a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs index d3dbca50..356d6c1f 100644 --- a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs @@ -1,65 +1,65 @@ -namespace ServiceLib.Handler -{ - public sealed class CoreInfoHandler - { - private static readonly Lazy _instance = new(() => new()); - private List? _coreInfo; - public static CoreInfoHandler Instance => _instance.Value; +namespace ServiceLib.Handler; - public CoreInfoHandler() +public sealed class CoreInfoHandler +{ + private static readonly Lazy _instance = new(() => new()); + private List? _coreInfo; + public static CoreInfoHandler Instance => _instance.Value; + + public CoreInfoHandler() + { + InitCoreInfo(); + } + + public CoreInfo? GetCoreInfo(ECoreType coreType) + { + if (_coreInfo == null) { InitCoreInfo(); } + return _coreInfo?.FirstOrDefault(t => t.CoreType == coreType); + } - public CoreInfo? GetCoreInfo(ECoreType coreType) + public List GetCoreInfo() + { + if (_coreInfo == null) { - if (_coreInfo == null) - { - InitCoreInfo(); - } - return _coreInfo?.FirstOrDefault(t => t.CoreType == coreType); + InitCoreInfo(); } + return _coreInfo ?? []; + } - public List GetCoreInfo() + public string GetCoreExecFile(CoreInfo? coreInfo, out string msg) + { + var fileName = string.Empty; + msg = string.Empty; + foreach (var name in coreInfo?.CoreExes) { - if (_coreInfo == null) + var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); + if (File.Exists(vName)) { - InitCoreInfo(); + fileName = vName; + break; } - return _coreInfo ?? []; } - - public string GetCoreExecFile(CoreInfo? coreInfo, out string msg) + if (fileName.IsNullOrEmpty()) { - var fileName = string.Empty; - msg = string.Empty; - foreach (var name in coreInfo?.CoreExes) - { - var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); - if (File.Exists(vName)) - { - fileName = vName; - break; - } - } - if (fileName.IsNullOrEmpty()) - { - msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo?.CoreType.ToString()), coreInfo?.CoreExes?.LastOrDefault(), coreInfo?.Url); - Logging.SaveLog(msg); - } - return fileName; + msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo?.CoreType.ToString()), coreInfo?.CoreExes?.LastOrDefault(), coreInfo?.Url); + Logging.SaveLog(msg); } + return fileName; + } - private void InitCoreInfo() - { - var urlN = GetCoreUrl(ECoreType.v2rayN); - var urlXray = GetCoreUrl(ECoreType.Xray); - var urlMihomo = GetCoreUrl(ECoreType.mihomo); - var urlSingbox = GetCoreUrl(ECoreType.sing_box); + private void InitCoreInfo() + { + var urlN = GetCoreUrl(ECoreType.v2rayN); + var urlXray = GetCoreUrl(ECoreType.Xray); + var urlMihomo = GetCoreUrl(ECoreType.mihomo); + var urlSingbox = GetCoreUrl(ECoreType.sing_box); - _coreInfo = - [ - new CoreInfo + _coreInfo = + [ + new CoreInfo { CoreType = ECoreType.v2rayN, Url = GetCoreUrl(ECoreType.v2rayN), @@ -202,17 +202,16 @@ namespace ServiceLib.Handler AbsolutePath = false, } - ]; - } + ]; + } - private static string PortableMode() - { - return $" -d {Utils.GetBinPath("").AppendQuotes()}"; - } + private static string PortableMode() + { + return $" -d {Utils.GetBinPath("").AppendQuotes()}"; + } - private static string GetCoreUrl(ECoreType eCoreType) - { - return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases"; - } + private static string GetCoreUrl(ECoreType eCoreType) + { + return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases"; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index 2e52130e..31b2e02d 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -1,242 +1,241 @@ using System.Collections.Specialized; -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class BaseFmt { - public class BaseFmt + protected static string GetIpv6(string address) { - protected static string GetIpv6(string address) + if (Utils.IsIpv6(address)) { - if (Utils.IsIpv6(address)) - { - // 检查地址是否已经被方括号包围,如果没有,则添加方括号 - return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]"; - } - return address; // 如果不是IPv6地址,直接返回原地址 + // 检查地址是否已经被方括号包围,如果没有,则添加方括号 + return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]"; + } + return address; // 如果不是IPv6地址,直接返回原地址 + } + + protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary dicQuery) + { + if (item.Flow.IsNotEmpty()) + { + dicQuery.Add("flow", item.Flow); } - protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary dicQuery) + if (item.StreamSecurity.IsNotEmpty()) { - if (item.Flow.IsNotEmpty()) + dicQuery.Add("security", item.StreamSecurity); + } + else + { + if (securityDef != null) { - dicQuery.Add("flow", item.Flow); + dicQuery.Add("security", securityDef); } + } + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + if (item.Fingerprint.IsNotEmpty()) + { + dicQuery.Add("fp", Utils.UrlEncode(item.Fingerprint)); + } + if (item.PublicKey.IsNotEmpty()) + { + dicQuery.Add("pbk", Utils.UrlEncode(item.PublicKey)); + } + if (item.ShortId.IsNotEmpty()) + { + dicQuery.Add("sid", Utils.UrlEncode(item.ShortId)); + } + if (item.SpiderX.IsNotEmpty()) + { + dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX)); + } + if (item.AllowInsecure.Equals("true")) + { + dicQuery.Add("allowInsecure", "1"); + } - if (item.StreamSecurity.IsNotEmpty()) - { - dicQuery.Add("security", item.StreamSecurity); - } - else - { - if (securityDef != null) + dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); + + switch (item.Network) + { + case nameof(ETransport.tcp): + dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); + if (item.RequestHost.IsNotEmpty()) { - dicQuery.Add("security", securityDef); + dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); } - } - if (item.Sni.IsNotEmpty()) - { - dicQuery.Add("sni", item.Sni); - } - if (item.Alpn.IsNotEmpty()) - { - dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); - } - if (item.Fingerprint.IsNotEmpty()) - { - dicQuery.Add("fp", Utils.UrlEncode(item.Fingerprint)); - } - if (item.PublicKey.IsNotEmpty()) - { - dicQuery.Add("pbk", Utils.UrlEncode(item.PublicKey)); - } - if (item.ShortId.IsNotEmpty()) - { - dicQuery.Add("sid", Utils.UrlEncode(item.ShortId)); - } - if (item.SpiderX.IsNotEmpty()) - { - dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX)); - } - if (item.AllowInsecure.Equals("true")) - { - dicQuery.Add("allowInsecure", "1"); - } + break; - dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); + case nameof(ETransport.kcp): + dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("seed", Utils.UrlEncode(item.Path)); + } + break; - switch (item.Network) - { - case nameof(ETransport.tcp): - dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); - if (item.RequestHost.IsNotEmpty()) - { - dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); - } - break; + case nameof(ETransport.ws): + case nameof(ETransport.httpupgrade): + if (item.RequestHost.IsNotEmpty()) + { + dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); + } + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("path", Utils.UrlEncode(item.Path)); + } + break; - case nameof(ETransport.kcp): - dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("seed", Utils.UrlEncode(item.Path)); - } - break; + case nameof(ETransport.xhttp): + if (item.RequestHost.IsNotEmpty()) + { + dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); + } + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("path", Utils.UrlEncode(item.Path)); + } + if (item.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(item.HeaderType)) + { + dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType)); + } + if (item.Extra.IsNotEmpty()) + { + dicQuery.Add("extra", Utils.UrlEncode(item.Extra)); + } + break; - case nameof(ETransport.ws): - case nameof(ETransport.httpupgrade): - if (item.RequestHost.IsNotEmpty()) - { - dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); - } - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("path", Utils.UrlEncode(item.Path)); - } - break; + case nameof(ETransport.http): + case nameof(ETransport.h2): + dicQuery["type"] = nameof(ETransport.http); + if (item.RequestHost.IsNotEmpty()) + { + dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); + } + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("path", Utils.UrlEncode(item.Path)); + } + break; - case nameof(ETransport.xhttp): - if (item.RequestHost.IsNotEmpty()) - { - dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); - } - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("path", Utils.UrlEncode(item.Path)); - } - if (item.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(item.HeaderType)) + case nameof(ETransport.quic): + dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); + dicQuery.Add("quicSecurity", Utils.UrlEncode(item.RequestHost)); + dicQuery.Add("key", Utils.UrlEncode(item.Path)); + break; + + case nameof(ETransport.grpc): + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("authority", Utils.UrlEncode(item.RequestHost)); + dicQuery.Add("serviceName", Utils.UrlEncode(item.Path)); + if (item.HeaderType is Global.GrpcGunMode or Global.GrpcMultiMode) { dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType)); } - if (item.Extra.IsNotEmpty()) - { - dicQuery.Add("extra", Utils.UrlEncode(item.Extra)); - } - break; - - case nameof(ETransport.http): - case nameof(ETransport.h2): - dicQuery["type"] = nameof(ETransport.http); - if (item.RequestHost.IsNotEmpty()) - { - dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); - } - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("path", Utils.UrlEncode(item.Path)); - } - break; - - case nameof(ETransport.quic): - dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); - dicQuery.Add("quicSecurity", Utils.UrlEncode(item.RequestHost)); - dicQuery.Add("key", Utils.UrlEncode(item.Path)); - break; - - case nameof(ETransport.grpc): - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("authority", Utils.UrlEncode(item.RequestHost)); - dicQuery.Add("serviceName", Utils.UrlEncode(item.Path)); - if (item.HeaderType is Global.GrpcGunMode or Global.GrpcMultiMode) - { - dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType)); - } - } - break; - } - return 0; - } - - protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item) - { - item.Flow = query["flow"] ?? ""; - item.StreamSecurity = query["security"] ?? ""; - item.Sni = query["sni"] ?? ""; - item.Alpn = Utils.UrlDecode(query["alpn"] ?? ""); - item.Fingerprint = Utils.UrlDecode(query["fp"] ?? ""); - item.PublicKey = Utils.UrlDecode(query["pbk"] ?? ""); - item.ShortId = Utils.UrlDecode(query["sid"] ?? ""); - item.SpiderX = Utils.UrlDecode(query["spx"] ?? ""); - item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : ""; - - item.Network = query["type"] ?? nameof(ETransport.tcp); - switch (item.Network) - { - case nameof(ETransport.tcp): - item.HeaderType = query["headerType"] ?? Global.None; - item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); - - break; - - case nameof(ETransport.kcp): - item.HeaderType = query["headerType"] ?? Global.None; - item.Path = Utils.UrlDecode(query["seed"] ?? ""); - break; - - case nameof(ETransport.ws): - case nameof(ETransport.httpupgrade): - item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); - item.Path = Utils.UrlDecode(query["path"] ?? "/"); - break; - - case nameof(ETransport.xhttp): - item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); - item.Path = Utils.UrlDecode(query["path"] ?? "/"); - item.HeaderType = Utils.UrlDecode(query["mode"] ?? ""); - item.Extra = Utils.UrlDecode(query["extra"] ?? ""); - break; - - case nameof(ETransport.http): - case nameof(ETransport.h2): - item.Network = nameof(ETransport.h2); - item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); - item.Path = Utils.UrlDecode(query["path"] ?? "/"); - break; - - case nameof(ETransport.quic): - item.HeaderType = query["headerType"] ?? Global.None; - item.RequestHost = query["quicSecurity"] ?? Global.None; - item.Path = Utils.UrlDecode(query["key"] ?? ""); - break; - - case nameof(ETransport.grpc): - item.RequestHost = Utils.UrlDecode(query["authority"] ?? ""); - item.Path = Utils.UrlDecode(query["serviceName"] ?? ""); - item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode); - break; - - default: - break; - } - return 0; - } - - protected static bool Contains(string str, params string[] s) - { - foreach (var item in s) - { - if (str.Contains(item, StringComparison.OrdinalIgnoreCase)) - { - return true; } + break; + } + return 0; + } + + protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item) + { + item.Flow = query["flow"] ?? ""; + item.StreamSecurity = query["security"] ?? ""; + item.Sni = query["sni"] ?? ""; + item.Alpn = Utils.UrlDecode(query["alpn"] ?? ""); + item.Fingerprint = Utils.UrlDecode(query["fp"] ?? ""); + item.PublicKey = Utils.UrlDecode(query["pbk"] ?? ""); + item.ShortId = Utils.UrlDecode(query["sid"] ?? ""); + item.SpiderX = Utils.UrlDecode(query["spx"] ?? ""); + item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : ""; + + item.Network = query["type"] ?? nameof(ETransport.tcp); + switch (item.Network) + { + case nameof(ETransport.tcp): + item.HeaderType = query["headerType"] ?? Global.None; + item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); + + break; + + case nameof(ETransport.kcp): + item.HeaderType = query["headerType"] ?? Global.None; + item.Path = Utils.UrlDecode(query["seed"] ?? ""); + break; + + case nameof(ETransport.ws): + case nameof(ETransport.httpupgrade): + item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); + item.Path = Utils.UrlDecode(query["path"] ?? "/"); + break; + + case nameof(ETransport.xhttp): + item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); + item.Path = Utils.UrlDecode(query["path"] ?? "/"); + item.HeaderType = Utils.UrlDecode(query["mode"] ?? ""); + item.Extra = Utils.UrlDecode(query["extra"] ?? ""); + break; + + case nameof(ETransport.http): + case nameof(ETransport.h2): + item.Network = nameof(ETransport.h2); + item.RequestHost = Utils.UrlDecode(query["host"] ?? ""); + item.Path = Utils.UrlDecode(query["path"] ?? "/"); + break; + + case nameof(ETransport.quic): + item.HeaderType = query["headerType"] ?? Global.None; + item.RequestHost = query["quicSecurity"] ?? Global.None; + item.Path = Utils.UrlDecode(query["key"] ?? ""); + break; + + case nameof(ETransport.grpc): + item.RequestHost = Utils.UrlDecode(query["authority"] ?? ""); + item.Path = Utils.UrlDecode(query["serviceName"] ?? ""); + item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode); + break; + + default: + break; + } + return 0; + } + + protected static bool Contains(string str, params string[] s) + { + foreach (var item in s) + { + if (str.Contains(item, StringComparison.OrdinalIgnoreCase)) + { + return true; } - return false; } + return false; + } - protected static string WriteAllText(string strData, string ext = "json") - { - var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}.{ext}"); - File.WriteAllText(fileName, strData); - return fileName; - } + protected static string WriteAllText(string strData, string ext = "json") + { + var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}.{ext}"); + File.WriteAllText(fileName, strData); + return fileName; + } - protected static string ToUri(EConfigType eConfigType, string address, object port, string userInfo, Dictionary? dicQuery, string? remark) - { - var query = dicQuery != null - ? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray())) - : string.Empty; + protected static string ToUri(EConfigType eConfigType, string address, object port, string userInfo, Dictionary? dicQuery, string? remark) + { + var query = dicQuery != null + ? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray())) + : string.Empty; - var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}"; - return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}"; - } + var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}"; + return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}"; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs index ed6faf37..f711c51f 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs @@ -1,23 +1,22 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class ClashFmt : BaseFmt { - public class ClashFmt : BaseFmt + public static ProfileItem? ResolveFull(string strData, string? subRemarks) { - public static ProfileItem? ResolveFull(string strData, string? subRemarks) + if (Contains(strData, "port", "socks-port", "proxies")) { - if (Contains(strData, "port", "socks-port", "proxies")) + var fileName = WriteAllText(strData, "yaml"); + + var profileItem = new ProfileItem { - var fileName = WriteAllText(strData, "yaml"); - - var profileItem = new ProfileItem - { - CoreType = ECoreType.mihomo, - Address = fileName, - Remarks = subRemarks ?? "clash_custom" - }; - return profileItem; - } - - return null; + CoreType = ECoreType.mihomo, + Address = fileName, + Remarks = subRemarks ?? "clash_custom" + }; + return profileItem; } + + return null; } -} \ No newline at end of file +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index b2d5ac58..3e8ab2ae 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -1,92 +1,91 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class FmtHandler { - public class FmtHandler + private static readonly string _tag = "FmtHandler"; + + public static string? GetShareUri(ProfileItem item) { - private static readonly string _tag = "FmtHandler"; - - public static string? GetShareUri(ProfileItem item) + try { - try + var url = item.ConfigType switch { - var url = item.ConfigType switch - { - EConfigType.VMess => VmessFmt.ToUri(item), - EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item), - EConfigType.SOCKS => SocksFmt.ToUri(item), - EConfigType.Trojan => TrojanFmt.ToUri(item), - EConfigType.VLESS => VLESSFmt.ToUri(item), - EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), - EConfigType.TUIC => TuicFmt.ToUri(item), - EConfigType.WireGuard => WireguardFmt.ToUri(item), - _ => null, - }; + EConfigType.VMess => VmessFmt.ToUri(item), + EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item), + EConfigType.SOCKS => SocksFmt.ToUri(item), + EConfigType.Trojan => TrojanFmt.ToUri(item), + EConfigType.VLESS => VLESSFmt.ToUri(item), + EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), + EConfigType.TUIC => TuicFmt.ToUri(item), + EConfigType.WireGuard => WireguardFmt.ToUri(item), + _ => null, + }; - return url; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return ""; - } + return url; } - - public static ProfileItem? ResolveConfig(string config, out string msg) + catch (Exception ex) { - msg = ResUI.ConfigurationFormatIncorrect; + Logging.SaveLog(_tag, ex); + return ""; + } + } - try + public static ProfileItem? ResolveConfig(string config, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + try + { + string str = config.TrimEx(); + if (str.IsNullOrEmpty()) { - string str = config.TrimEx(); - if (str.IsNullOrEmpty()) - { - msg = ResUI.FailedReadConfiguration; - return null; - } - - if (str.StartsWith(Global.ProtocolShares[EConfigType.VMess])) - { - return VmessFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowsocks])) - { - return ShadowsocksFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])) - { - return SocksFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.Trojan])) - { - return TrojanFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.VLESS])) - { - return VLESSFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare)) - { - return Hysteria2Fmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC])) - { - return TuicFmt.Resolve(str, out msg); - } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.WireGuard])) - { - return WireguardFmt.Resolve(str, out msg); - } - else - { - msg = ResUI.NonvmessOrssProtocol; - return null; - } + msg = ResUI.FailedReadConfiguration; + return null; } - catch (Exception ex) + + if (str.StartsWith(Global.ProtocolShares[EConfigType.VMess])) { - Logging.SaveLog(_tag, ex); - msg = ResUI.Incorrectconfiguration; + return VmessFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowsocks])) + { + return ShadowsocksFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])) + { + return SocksFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Trojan])) + { + return TrojanFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.VLESS])) + { + return VLESSFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare)) + { + return Hysteria2Fmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC])) + { + return TuicFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.WireGuard])) + { + return WireguardFmt.Resolve(str, out msg); + } + else + { + msg = ResUI.NonvmessOrssProtocol; return null; } } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + msg = ResUI.Incorrectconfiguration; + return null; + } } -} \ No newline at end of file +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index 46e397b3..dfe58d5b 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -1,102 +1,101 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class Hysteria2Fmt : BaseFmt { - public class Hysteria2Fmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + ProfileItem item = new() { - msg = ResUI.ConfigurationFormatIncorrect; - ProfileItem item = new() + ConfigType = EConfigType.Hysteria2 + }; + + var url = Utils.TryUri(str); + if (url == null) + return null; + + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.Id = Utils.UrlDecode(url.UserInfo); + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.Path = Utils.UrlDecode(query["obfs-password"] ?? ""); + item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false"; + + item.Ports = Utils.UrlDecode(query["mport"] ?? "").Replace('-', ':'); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + return null; + string url = string.Empty; + + string remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("obfs", "salamander"); + dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path)); + } + dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0"); + if (item.Ports.IsNotEmpty()) + { + dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-'))); + } + + return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark); + } + + public static ProfileItem? ResolveFull(string strData, string? subRemarks) + { + if (Contains(strData, "server", "up", "down", "listen", "", "")) + { + var fileName = WriteAllText(strData); + + var profileItem = new ProfileItem { - ConfigType = EConfigType.Hysteria2 + CoreType = ECoreType.hysteria, + Address = fileName, + Remarks = subRemarks ?? "hysteria_custom" }; - - var url = Utils.TryUri(str); - if (url == null) - return null; - - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); - - var query = Utils.ParseQueryString(url.Query); - ResolveStdTransport(query, ref item); - item.Path = Utils.UrlDecode(query["obfs-password"] ?? ""); - item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false"; - - item.Ports = Utils.UrlDecode(query["mport"] ?? "").Replace('-', ':'); - - return item; + return profileItem; } - public static string? ToUri(ProfileItem? item) + return null; + } + + public static ProfileItem? ResolveFull2(string strData, string? subRemarks) + { + if (Contains(strData, "server", "auth", "up", "down", "listen")) { - if (item == null) - return null; - string url = string.Empty; + var fileName = WriteAllText(strData); - string remark = string.Empty; - if (item.Remarks.IsNotEmpty()) + var profileItem = new ProfileItem { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - var dicQuery = new Dictionary(); - if (item.Sni.IsNotEmpty()) - { - dicQuery.Add("sni", item.Sni); - } - if (item.Alpn.IsNotEmpty()) - { - dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); - } - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("obfs", "salamander"); - dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path)); - } - dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0"); - if (item.Ports.IsNotEmpty()) - { - dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-'))); - } - - return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark); + CoreType = ECoreType.hysteria2, + Address = fileName, + Remarks = subRemarks ?? "hysteria2_custom" + }; + return profileItem; } - public static ProfileItem? ResolveFull(string strData, string? subRemarks) - { - if (Contains(strData, "server", "up", "down", "listen", "", "")) - { - var fileName = WriteAllText(strData); - - var profileItem = new ProfileItem - { - CoreType = ECoreType.hysteria, - Address = fileName, - Remarks = subRemarks ?? "hysteria_custom" - }; - return profileItem; - } - - return null; - } - - public static ProfileItem? ResolveFull2(string strData, string? subRemarks) - { - if (Contains(strData, "server", "auth", "up", "down", "listen")) - { - var fileName = WriteAllText(strData); - - var profileItem = new ProfileItem - { - CoreType = ECoreType.hysteria2, - Address = fileName, - Remarks = subRemarks ?? "hysteria2_custom" - }; - return profileItem; - } - - return null; - } + return null; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs index 3846c3af..58f34d1f 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs @@ -1,23 +1,22 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class NaiveproxyFmt : BaseFmt { - public class NaiveproxyFmt : BaseFmt + public static ProfileItem? ResolveFull(string strData, string? subRemarks) { - public static ProfileItem? ResolveFull(string strData, string? subRemarks) + if (Contains(strData, "listen", "proxy", "", "")) { - if (Contains(strData, "listen", "proxy", "", "")) + var fileName = WriteAllText(strData); + + var profileItem = new ProfileItem { - var fileName = WriteAllText(strData); - - var profileItem = new ProfileItem - { - CoreType = ECoreType.naiveproxy, - Address = fileName, - Remarks = subRemarks ?? "naiveproxy_custom" - }; - return profileItem; - } - - return null; + CoreType = ECoreType.naiveproxy, + Address = fileName, + Remarks = subRemarks ?? "naiveproxy_custom" + }; + return profileItem; } + + return null; } -} \ No newline at end of file +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs index d35226e9..2c9898e9 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs @@ -1,180 +1,179 @@ using System.Text.RegularExpressions; -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class ShadowsocksFmt : BaseFmt { - public class ShadowsocksFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + ProfileItem? item; + + item = ResolveSSLegacy(str) ?? ResolveSip002(str); + if (item == null) { - msg = ResUI.ConfigurationFormatIncorrect; - ProfileItem? item; - - item = ResolveSSLegacy(str) ?? ResolveSip002(str); - if (item == null) - { - return null; - } - if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0) - { - return null; - } - - item.ConfigType = EConfigType.Shadowsocks; - - return item; + return null; + } + if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0) + { + return null; } - public static string? ToUri(ProfileItem? item) + item.ConfigType = EConfigType.Shadowsocks; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) { - if (item == null) - { - return null; - } - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - //url = string.Format("{0}:{1}@{2}:{3}", - // item.security, - // item.id, - // item.address, - // item.port); - //url = Utile.Base64Encode(url); - //new Sip002 - var pw = Utils.Base64Encode($"{item.Security}:{item.Id}"); - return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark); + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + //url = string.Format("{0}:{1}@{2}:{3}", + // item.security, + // item.id, + // item.address, + // item.port); + //url = Utile.Base64Encode(url); + //new Sip002 + var pw = Utils.Base64Encode($"{item.Security}:{item.Id}"); + return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark); + } + + private static readonly Regex UrlFinder = new(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex DetailsParser = new(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static ProfileItem? ResolveSSLegacy(string result) + { + var match = UrlFinder.Match(result); + if (!match.Success) + { + return null; } - private static readonly Regex UrlFinder = new(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex DetailsParser = new(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private static ProfileItem? ResolveSSLegacy(string result) + ProfileItem item = new(); + var base64 = match.Groups["base64"].Value.TrimEnd('/'); + var tag = match.Groups["tag"].Value; + if (tag.IsNotEmpty()) { - var match = UrlFinder.Match(result); - if (!match.Success) - { - return null; - } + item.Remarks = Utils.UrlDecode(tag); + } + Match details; + try + { + details = DetailsParser.Match(Utils.Base64Decode(base64)); + } + catch (FormatException) + { + return null; + } + if (!details.Success) + { + return null; + } + item.Security = details.Groups["method"].Value; + item.Id = details.Groups["password"].Value; + item.Address = details.Groups["hostname"].Value; + item.Port = details.Groups["port"].Value.ToInt(); + return item; + } - ProfileItem item = new(); - var base64 = match.Groups["base64"].Value.TrimEnd('/'); - var tag = match.Groups["tag"].Value; - if (tag.IsNotEmpty()) - { - item.Remarks = Utils.UrlDecode(tag); - } - Match details; - try - { - details = DetailsParser.Match(Utils.Base64Decode(base64)); - } - catch (FormatException) - { - return null; - } - if (!details.Success) - { - return null; - } - item.Security = details.Groups["method"].Value; - item.Id = details.Groups["password"].Value; - item.Address = details.Groups["hostname"].Value; - item.Port = details.Groups["port"].Value.ToInt(); - return item; + private static ProfileItem? ResolveSip002(string result) + { + var parsedUrl = Utils.TryUri(result); + if (parsedUrl == null) + { + return null; } - private static ProfileItem? ResolveSip002(string result) + ProfileItem item = new() { - var parsedUrl = Utils.TryUri(result); - if (parsedUrl == null) + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + //2022-blake3 + if (rawUserInfo.Contains(':')) + { + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length != 2) { return null; } + item.Security = userInfoParts.First(); + item.Id = Utils.UrlDecode(userInfoParts.Last()); + } + else + { + // parse base64 UserInfo + var userInfo = Utils.Base64Decode(rawUserInfo); + var userInfoParts = userInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length != 2) + { + return null; + } + item.Security = userInfoParts.First(); + item.Id = userInfoParts.Last(); + } - ProfileItem item = new() + var queryParameters = Utils.ParseQueryString(parsedUrl.Query); + if (queryParameters["plugin"] != null) + { + //obfs-host exists + var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host")); + if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty()) { - Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), - Address = parsedUrl.IdnHost, - Port = parsedUrl.Port, - }; - var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); - //2022-blake3 - if (rawUserInfo.Contains(':')) - { - var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); - if (userInfoParts.Length != 2) - { - return null; - } - item.Security = userInfoParts.First(); - item.Id = Utils.UrlDecode(userInfoParts.Last()); + obfsHost = obfsHost?.Replace("obfs-host=", ""); + item.Network = Global.DefaultNetwork; + item.HeaderType = Global.TcpHeaderHttp; + item.RequestHost = obfsHost ?? ""; } else { - // parse base64 UserInfo - var userInfo = Utils.Base64Decode(rawUserInfo); - var userInfoParts = userInfo.Split(new[] { ':' }, 2); - if (userInfoParts.Length != 2) - { - return null; - } - item.Security = userInfoParts.First(); - item.Id = userInfoParts.Last(); + return null; } - - var queryParameters = Utils.ParseQueryString(parsedUrl.Query); - if (queryParameters["plugin"] != null) - { - //obfs-host exists - var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host")); - if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty()) - { - obfsHost = obfsHost?.Replace("obfs-host=", ""); - item.Network = Global.DefaultNetwork; - item.HeaderType = Global.TcpHeaderHttp; - item.RequestHost = obfsHost ?? ""; - } - else - { - return null; - } - } - - return item; } - public static List? ResolveSip008(string result) + return item; + } + + public static List? ResolveSip008(string result) + { + //SsSIP008 + var lstSsServer = JsonUtils.Deserialize>(result); + if (lstSsServer?.Count <= 0) { - //SsSIP008 - var lstSsServer = JsonUtils.Deserialize>(result); - if (lstSsServer?.Count <= 0) + var ssSIP008 = JsonUtils.Deserialize(result); + if (ssSIP008?.servers?.Count > 0) { - var ssSIP008 = JsonUtils.Deserialize(result); - if (ssSIP008?.servers?.Count > 0) - { - lstSsServer = ssSIP008.servers; - } + lstSsServer = ssSIP008.servers; } - - if (lstSsServer?.Count > 0) - { - List lst = []; - foreach (var it in lstSsServer) - { - var ssItem = new ProfileItem() - { - Remarks = it.remarks, - Security = it.method, - Id = it.password, - Address = it.server, - Port = it.server_port.ToInt() - }; - lst.Add(ssItem); - } - return lst; - } - return null; } + + if (lstSsServer?.Count > 0) + { + List lst = []; + foreach (var it in lstSsServer) + { + var ssItem = new ProfileItem() + { + Remarks = it.remarks, + Security = it.method, + Id = it.password, + Address = it.server, + Port = it.server_port.ToInt() + }; + lst.Add(ssItem); + } + return lst; + } + return null; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs index c730ce6c..969895d6 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs @@ -1,48 +1,47 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class SingboxFmt : BaseFmt { - public class SingboxFmt : BaseFmt + public static List? ResolveFullArray(string strData, string? subRemarks) { - public static List? ResolveFullArray(string strData, string? subRemarks) + var configObjects = JsonUtils.Deserialize(strData); + if (configObjects is not { Length: > 0 }) { - var configObjects = JsonUtils.Deserialize(strData); - if (configObjects is not { Length: > 0 }) - { - return null; - } - - List lstResult = []; - foreach (var configObject in configObjects) - { - var objectString = JsonUtils.Serialize(configObject); - var profileIt = ResolveFull(objectString, subRemarks); - if (profileIt != null) - { - lstResult.Add(profileIt); - } - } - return lstResult; + return null; } - public static ProfileItem? ResolveFull(string strData, string? subRemarks) + List lstResult = []; + foreach (var configObject in configObjects) { - var config = JsonUtils.ParseJson(strData); - if (config?["inbounds"] == null - || config["outbounds"] == null - || config["route"] == null - || config["dns"] == null) + var objectString = JsonUtils.Serialize(configObject); + var profileIt = ResolveFull(objectString, subRemarks); + if (profileIt != null) { - return null; + lstResult.Add(profileIt); } - - var fileName = WriteAllText(strData); - var profileItem = new ProfileItem - { - CoreType = ECoreType.sing_box, - Address = fileName, - Remarks = subRemarks ?? "singbox_custom" - }; - - return profileItem; } + return lstResult; + } + + public static ProfileItem? ResolveFull(string strData, string? subRemarks) + { + var config = JsonUtils.ParseJson(strData); + if (config?["inbounds"] == null + || config["outbounds"] == null + || config["route"] == null + || config["dns"] == null) + { + return null; + } + + var fileName = WriteAllText(strData); + var profileItem = new ProfileItem + { + CoreType = ECoreType.sing_box, + Address = fileName, + Remarks = subRemarks ?? "singbox_custom" + }; + + return profileItem; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs index 9527bb56..6110d784 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs @@ -1,115 +1,114 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class SocksFmt : BaseFmt { - public class SocksFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + + var item = ResolveSocksNew(str) ?? ResolveSocks(str); + if (item == null) { - msg = ResUI.ConfigurationFormatIncorrect; - - var item = ResolveSocksNew(str) ?? ResolveSocks(str); - if (item == null) - { - return null; - } - if (item.Address.Length == 0 || item.Port == 0) - { - return null; - } - - item.ConfigType = EConfigType.SOCKS; - - return item; + return null; + } + if (item.Address.Length == 0 || item.Port == 0) + { + return null; } - public static string? ToUri(ProfileItem? item) + item.ConfigType = EConfigType.SOCKS; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) { - if (item == null) + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + //new + var pw = Utils.Base64Encode($"{item.Security}:{item.Id}"); + return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark); + } + + private static ProfileItem? ResolveSocks(string result) + { + ProfileItem item = new() + { + ConfigType = EConfigType.SOCKS + }; + result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..]; + //remark + var indexRemark = result.IndexOf("#"); + if (indexRemark > 0) + { + try { - return null; + item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1)); } - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - //new - var pw = Utils.Base64Encode($"{item.Security}:{item.Id}"); - return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark); + catch { } + result = result[..indexRemark]; + } + //part decode + var indexS = result.IndexOf("@"); + if (indexS > 0) + { + } + else + { + result = Utils.Base64Decode(result); } - private static ProfileItem? ResolveSocks(string result) + var arr1 = result.Split('@'); + if (arr1.Length != 2) { - ProfileItem item = new() - { - ConfigType = EConfigType.SOCKS - }; - result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..]; - //remark - var indexRemark = result.IndexOf("#"); - if (indexRemark > 0) - { - try - { - item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1)); - } - catch { } - result = result[..indexRemark]; - } - //part decode - var indexS = result.IndexOf("@"); - if (indexS > 0) - { - } - else - { - result = Utils.Base64Decode(result); - } + return null; + } + var arr21 = arr1.First().Split(':'); + var indexPort = arr1.Last().LastIndexOf(":"); + if (arr21.Length != 2 || indexPort < 0) + { + return null; + } + item.Address = arr1[1][..indexPort]; + item.Port = arr1[1][(indexPort + 1)..].ToInt(); + item.Security = arr21.First(); + item.Id = arr21[1]; - var arr1 = result.Split('@'); - if (arr1.Length != 2) - { - return null; - } - var arr21 = arr1.First().Split(':'); - var indexPort = arr1.Last().LastIndexOf(":"); - if (arr21.Length != 2 || indexPort < 0) - { - return null; - } - item.Address = arr1[1][..indexPort]; - item.Port = arr1[1][(indexPort + 1)..].ToInt(); - item.Security = arr21.First(); - item.Id = arr21[1]; + return item; + } - return item; + private static ProfileItem? ResolveSocksNew(string result) + { + var parsedUrl = Utils.TryUri(result); + if (parsedUrl == null) + { + return null; } - private static ProfileItem? ResolveSocksNew(string result) + ProfileItem item = new() { - var parsedUrl = Utils.TryUri(result); - if (parsedUrl == null) - { - return null; - } + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; - ProfileItem item = new() - { - Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), - Address = parsedUrl.IdnHost, - Port = parsedUrl.Port, - }; - - // parse base64 UserInfo - var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); - var userInfo = Utils.Base64Decode(rawUserInfo); - var userInfoParts = userInfo.Split([':'], 2); - if (userInfoParts.Length == 2) - { - item.Security = userInfoParts.First(); - item.Id = userInfoParts[1]; - } - - return item; + // parse base64 UserInfo + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + var userInfo = Utils.Base64Decode(rawUserInfo); + var userInfoParts = userInfo.Split([':'], 2); + if (userInfoParts.Length == 2) + { + item.Security = userInfoParts.First(); + item.Id = userInfoParts[1]; } + + return item; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs index 60f05764..3a4ee984 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs @@ -1,48 +1,47 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class TrojanFmt : BaseFmt { - public class TrojanFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() { - msg = ResUI.ConfigurationFormatIncorrect; + ConfigType = EConfigType.Trojan + }; - ProfileItem item = new() - { - ConfigType = EConfigType.Trojan - }; - - var url = Utils.TryUri(str); - if (url == null) - { - return null; - } - - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); - - var query = Utils.ParseQueryString(url.Query); - _ = ResolveStdTransport(query, ref item); - - return item; + var url = Utils.TryUri(str); + if (url == null) + { + return null; } - public static string? ToUri(ProfileItem? item) - { - if (item == null) - { - return null; - } - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - var dicQuery = new Dictionary(); - _ = GetStdTransport(item, null, ref dicQuery); + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.Id = Utils.UrlDecode(url.UserInfo); - return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark); + var query = Utils.ParseQueryString(url.Query); + _ = ResolveStdTransport(query, ref item); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, null, ref dicQuery); + + return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs index 39f52d7b..632f49c9 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs @@ -1,64 +1,63 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class TuicFmt : BaseFmt { - public class TuicFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() { - msg = ResUI.ConfigurationFormatIncorrect; + ConfigType = EConfigType.TUIC + }; - ProfileItem item = new() - { - ConfigType = EConfigType.TUIC - }; - - var url = Utils.TryUri(str); - if (url == null) - { - return null; - } - - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - var rawUserInfo = Utils.UrlDecode(url.UserInfo); - var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); - if (userInfoParts.Length == 2) - { - item.Id = userInfoParts.First(); - item.Security = userInfoParts.Last(); - } - - var query = Utils.ParseQueryString(url.Query); - ResolveStdTransport(query, ref item); - item.HeaderType = query["congestion_control"] ?? ""; - - return item; + var url = Utils.TryUri(str); + if (url == null) + { + return null; } - public static string? ToUri(ProfileItem? item) + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var rawUserInfo = Utils.UrlDecode(url.UserInfo); + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length == 2) { - if (item == null) - { - return null; - } - - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - var dicQuery = new Dictionary(); - if (item.Sni.IsNotEmpty()) - { - dicQuery.Add("sni", item.Sni); - } - if (item.Alpn.IsNotEmpty()) - { - dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); - } - dicQuery.Add("congestion_control", item.HeaderType); - - return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + item.Id = userInfoParts.First(); + item.Security = userInfoParts.Last(); } + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.HeaderType = query["congestion_control"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + dicQuery.Add("congestion_control", item.HeaderType); + + return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs index 9e4474b4..6db45fcd 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs @@ -1,49 +1,48 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class V2rayFmt : BaseFmt { - public class V2rayFmt : BaseFmt + public static List? ResolveFullArray(string strData, string? subRemarks) { - public static List? ResolveFullArray(string strData, string? subRemarks) + var configObjects = JsonUtils.Deserialize(strData); + if (configObjects is not { Length: > 0 }) { - var configObjects = JsonUtils.Deserialize(strData); - if (configObjects is not { Length: > 0 }) - { - return null; - } - - List lstResult = []; - foreach (var configObject in configObjects) - { - var objectString = JsonUtils.Serialize(configObject); - var profileIt = ResolveFull(objectString, subRemarks); - if (profileIt != null) - { - lstResult.Add(profileIt); - } - } - - return lstResult; + return null; } - public static ProfileItem? ResolveFull(string strData, string? subRemarks) + List lstResult = []; + foreach (var configObject in configObjects) { - var config = JsonUtils.ParseJson(strData); - if (config?["inbounds"] == null - || config["outbounds"] == null - || config["routing"] == null) + var objectString = JsonUtils.Serialize(configObject); + var profileIt = ResolveFull(objectString, subRemarks); + if (profileIt != null) { - return null; + lstResult.Add(profileIt); } - - var fileName = WriteAllText(strData); - - var profileItem = new ProfileItem - { - CoreType = ECoreType.Xray, - Address = fileName, - Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom" - }; - - return profileItem; } + + return lstResult; + } + + public static ProfileItem? ResolveFull(string strData, string? subRemarks) + { + var config = JsonUtils.ParseJson(strData); + if (config?["inbounds"] == null + || config["outbounds"] == null + || config["routing"] == null) + { + return null; + } + + var fileName = WriteAllText(strData); + + var profileItem = new ProfileItem + { + CoreType = ECoreType.Xray, + Address = fileName, + Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom" + }; + + return profileItem; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs index 819c50b6..4d3bcff0 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs @@ -1,60 +1,59 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class VLESSFmt : BaseFmt { - public class VLESSFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() { - msg = ResUI.ConfigurationFormatIncorrect; + ConfigType = EConfigType.VLESS, + Security = Global.None + }; - ProfileItem item = new() - { - ConfigType = EConfigType.VLESS, - Security = Global.None - }; - - var url = Utils.TryUri(str); - if (url == null) - { - return null; - } - - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); - - var query = Utils.ParseQueryString(url.Query); - item.Security = query["encryption"] ?? Global.None; - item.StreamSecurity = query["security"] ?? ""; - _ = ResolveStdTransport(query, ref item); - - return item; + var url = Utils.TryUri(str); + if (url == null) + { + return null; } - public static string? ToUri(ProfileItem? item) + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.Id = Utils.UrlDecode(url.UserInfo); + + var query = Utils.ParseQueryString(url.Query); + item.Security = query["encryption"] ?? Global.None; + item.StreamSecurity = query["security"] ?? ""; + _ = ResolveStdTransport(query, ref item); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) { - if (item == null) - { - return null; - } - - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - var dicQuery = new Dictionary(); - if (item.Security.IsNotEmpty()) - { - dicQuery.Add("encryption", item.Security); - } - else - { - dicQuery.Add("encryption", Global.None); - } - _ = GetStdTransport(item, Global.None, ref dicQuery); - - return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark); + return null; } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Security.IsNotEmpty()) + { + dicQuery.Add("encryption", item.Security); + } + else + { + dicQuery.Add("encryption", Global.None); + } + _ = GetStdTransport(item, Global.None, ref dicQuery); + + return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs index 47bf297d..71b4c0c2 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs @@ -1,126 +1,125 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class VmessFmt : BaseFmt { - public class VmessFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + ProfileItem? item; + if (str.IndexOf('?') > 0 && str.IndexOf('&') > 0) { - msg = ResUI.ConfigurationFormatIncorrect; - ProfileItem? item; - if (str.IndexOf('?') > 0 && str.IndexOf('&') > 0) - { - item = ResolveStdVmess(str); - } - else - { - item = ResolveVmess(str, out msg); - } - return item; + item = ResolveStdVmess(str); + } + else + { + item = ResolveVmess(str, out msg); + } + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var vmessQRCode = new VmessQRCode + { + v = item.ConfigVersion, + ps = item.Remarks.TrimEx(), + add = item.Address, + port = item.Port, + id = item.Id, + aid = item.AlterId, + scy = item.Security, + net = item.Network, + type = item.HeaderType, + host = item.RequestHost, + path = item.Path, + tls = item.StreamSecurity, + sni = item.Sni, + alpn = item.Alpn, + fp = item.Fingerprint + }; + + var url = JsonUtils.Serialize(vmessQRCode); + url = Utils.Base64Encode(url); + url = $"{Global.ProtocolShares[EConfigType.VMess]}{url}"; + + return url; + } + + private static ProfileItem? ResolveVmess(string result, out string msg) + { + msg = string.Empty; + var item = new ProfileItem + { + ConfigType = EConfigType.VMess + }; + + result = result[Global.ProtocolShares[EConfigType.VMess].Length..]; + result = Utils.Base64Decode(result); + + var vmessQRCode = JsonUtils.Deserialize(result); + if (vmessQRCode == null) + { + msg = ResUI.FailedConversionConfiguration; + return null; } - public static string? ToUri(ProfileItem? item) + item.Network = Global.DefaultNetwork; + item.HeaderType = Global.None; + + item.ConfigVersion = vmessQRCode.v; + item.Remarks = Utils.ToString(vmessQRCode.ps); + item.Address = Utils.ToString(vmessQRCode.add); + item.Port = vmessQRCode.port; + item.Id = Utils.ToString(vmessQRCode.id); + item.AlterId = vmessQRCode.aid; + item.Security = Utils.ToString(vmessQRCode.scy); + + item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity; + if (vmessQRCode.net.IsNotEmpty()) { - if (item == null) - { - return null; - } - var vmessQRCode = new VmessQRCode - { - v = item.ConfigVersion, - ps = item.Remarks.TrimEx(), - add = item.Address, - port = item.Port, - id = item.Id, - aid = item.AlterId, - scy = item.Security, - net = item.Network, - type = item.HeaderType, - host = item.RequestHost, - path = item.Path, - tls = item.StreamSecurity, - sni = item.Sni, - alpn = item.Alpn, - fp = item.Fingerprint - }; - - var url = JsonUtils.Serialize(vmessQRCode); - url = Utils.Base64Encode(url); - url = $"{Global.ProtocolShares[EConfigType.VMess]}{url}"; - - return url; + item.Network = vmessQRCode.net; + } + if (vmessQRCode.type.IsNotEmpty()) + { + item.HeaderType = vmessQRCode.type; } - private static ProfileItem? ResolveVmess(string result, out string msg) + item.RequestHost = Utils.ToString(vmessQRCode.host); + item.Path = Utils.ToString(vmessQRCode.path); + item.StreamSecurity = Utils.ToString(vmessQRCode.tls); + item.Sni = Utils.ToString(vmessQRCode.sni); + item.Alpn = Utils.ToString(vmessQRCode.alpn); + item.Fingerprint = Utils.ToString(vmessQRCode.fp); + + return item; + } + + public static ProfileItem? ResolveStdVmess(string str) + { + var item = new ProfileItem { - msg = string.Empty; - var item = new ProfileItem - { - ConfigType = EConfigType.VMess - }; + ConfigType = EConfigType.VMess, + Security = "auto" + }; - result = result[Global.ProtocolShares[EConfigType.VMess].Length..]; - result = Utils.Base64Decode(result); - - var vmessQRCode = JsonUtils.Deserialize(result); - if (vmessQRCode == null) - { - msg = ResUI.FailedConversionConfiguration; - return null; - } - - item.Network = Global.DefaultNetwork; - item.HeaderType = Global.None; - - item.ConfigVersion = vmessQRCode.v; - item.Remarks = Utils.ToString(vmessQRCode.ps); - item.Address = Utils.ToString(vmessQRCode.add); - item.Port = vmessQRCode.port; - item.Id = Utils.ToString(vmessQRCode.id); - item.AlterId = vmessQRCode.aid; - item.Security = Utils.ToString(vmessQRCode.scy); - - item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity; - if (vmessQRCode.net.IsNotEmpty()) - { - item.Network = vmessQRCode.net; - } - if (vmessQRCode.type.IsNotEmpty()) - { - item.HeaderType = vmessQRCode.type; - } - - item.RequestHost = Utils.ToString(vmessQRCode.host); - item.Path = Utils.ToString(vmessQRCode.path); - item.StreamSecurity = Utils.ToString(vmessQRCode.tls); - item.Sni = Utils.ToString(vmessQRCode.sni); - item.Alpn = Utils.ToString(vmessQRCode.alpn); - item.Fingerprint = Utils.ToString(vmessQRCode.fp); - - return item; + var url = Utils.TryUri(str); + if (url == null) + { + return null; } - public static ProfileItem? ResolveStdVmess(string str) - { - var item = new ProfileItem - { - ConfigType = EConfigType.VMess, - Security = "auto" - }; + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.Id = Utils.UrlDecode(url.UserInfo); - var url = Utils.TryUri(str); - if (url == null) - { - return null; - } + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); - - var query = Utils.ParseQueryString(url.Query); - ResolveStdTransport(query, ref item); - - return item; - } + return item; } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs index d7083963..8e78f036 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs @@ -1,68 +1,67 @@ -namespace ServiceLib.Handler.Fmt +namespace ServiceLib.Handler.Fmt; + +public class WireguardFmt : BaseFmt { - public class WireguardFmt : BaseFmt + public static ProfileItem? Resolve(string str, out string msg) { - public static ProfileItem? Resolve(string str, out string msg) + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() { - msg = ResUI.ConfigurationFormatIncorrect; + ConfigType = EConfigType.WireGuard + }; - ProfileItem item = new() - { - ConfigType = EConfigType.WireGuard - }; - - var url = Utils.TryUri(str); - if (url == null) - { - return null; - } - - item.Address = url.IdnHost; - item.Port = url.Port; - item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); - - var query = Utils.ParseQueryString(url.Query); - - item.PublicKey = Utils.UrlDecode(query["publickey"] ?? ""); - item.Path = Utils.UrlDecode(query["reserved"] ?? ""); - item.RequestHost = Utils.UrlDecode(query["address"] ?? ""); - item.ShortId = Utils.UrlDecode(query["mtu"] ?? ""); - - return item; + var url = Utils.TryUri(str); + if (url == null) + { + return null; } - public static string? ToUri(ProfileItem? item) + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + item.Id = Utils.UrlDecode(url.UserInfo); + + var query = Utils.ParseQueryString(url.Query); + + item.PublicKey = Utils.UrlDecode(query["publickey"] ?? ""); + item.Path = Utils.UrlDecode(query["reserved"] ?? ""); + item.RequestHost = Utils.UrlDecode(query["address"] ?? ""); + item.ShortId = Utils.UrlDecode(query["mtu"] ?? ""); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) { - if (item == null) - { - return null; - } - - var remark = string.Empty; - if (item.Remarks.IsNotEmpty()) - { - remark = "#" + Utils.UrlEncode(item.Remarks); - } - - var dicQuery = new Dictionary(); - if (item.PublicKey.IsNotEmpty()) - { - dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey)); - } - if (item.Path.IsNotEmpty()) - { - dicQuery.Add("reserved", Utils.UrlEncode(item.Path)); - } - if (item.RequestHost.IsNotEmpty()) - { - dicQuery.Add("address", Utils.UrlEncode(item.RequestHost)); - } - if (item.ShortId.IsNotEmpty()) - { - dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId)); - } - return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark); + return null; } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + + var dicQuery = new Dictionary(); + if (item.PublicKey.IsNotEmpty()) + { + dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey)); + } + if (item.Path.IsNotEmpty()) + { + dicQuery.Add("reserved", Utils.UrlEncode(item.Path)); + } + if (item.RequestHost.IsNotEmpty()) + { + dicQuery.Add("address", Utils.UrlEncode(item.RequestHost)); + } + if (item.ShortId.IsNotEmpty()) + { + dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId)); + } + return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/NoticeHandler.cs b/v2rayN/ServiceLib/Handler/NoticeHandler.cs index 948fc81c..31cb2204 100644 --- a/v2rayN/ServiceLib/Handler/NoticeHandler.cs +++ b/v2rayN/ServiceLib/Handler/NoticeHandler.cs @@ -1,44 +1,43 @@ -using ReactiveUI; +using ReactiveUI; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public class NoticeHandler { - public class NoticeHandler + private static readonly Lazy _instance = new(() => new()); + public static NoticeHandler Instance => _instance.Value; + + public void Enqueue(string? content) { - private static readonly Lazy _instance = new(() => new()); - public static NoticeHandler Instance => _instance.Value; - - public void Enqueue(string? content) + if (content.IsNullOrEmpty()) { - if (content.IsNullOrEmpty()) - { - return; - } - MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString()); - } - - public void SendMessage(string? content) - { - if (content.IsNullOrEmpty()) - { - return; - } - MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString()); - } - - public void SendMessageEx(string? content) - { - if (content.IsNullOrEmpty()) - { - return; - } - content = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} {content}"; - SendMessage(content); - } - - public void SendMessageAndEnqueue(string? msg) - { - Enqueue(msg); - SendMessage(msg); + return; } + MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString()); } -} \ No newline at end of file + + public void SendMessage(string? content) + { + if (content.IsNullOrEmpty()) + { + return; + } + MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString()); + } + + public void SendMessageEx(string? content) + { + if (content.IsNullOrEmpty()) + { + return; + } + content = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} {content}"; + SendMessage(content); + } + + public void SendMessageAndEnqueue(string? msg) + { + Enqueue(msg); + SendMessage(msg); + } +} diff --git a/v2rayN/ServiceLib/Handler/PacHandler.cs b/v2rayN/ServiceLib/Handler/PacHandler.cs index 5a9e31aa..6d2e1f19 100644 --- a/v2rayN/ServiceLib/Handler/PacHandler.cs +++ b/v2rayN/ServiceLib/Handler/PacHandler.cs @@ -1,115 +1,114 @@ using System.Net.Sockets; using System.Text; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public class PacHandler { - public class PacHandler + private static string _configPath; + private static int _httpPort; + private static int _pacPort; + private static TcpListener? _tcpListener; + private static byte[] _writeContent; + private static bool _isRunning; + private static bool _needRestart = true; + + public static async Task Start(string configPath, int httpPort, int pacPort) { - private static string _configPath; - private static int _httpPort; - private static int _pacPort; - private static TcpListener? _tcpListener; - private static byte[] _writeContent; - private static bool _isRunning; - private static bool _needRestart = true; + _needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning; - public static async Task Start(string configPath, int httpPort, int pacPort) + _configPath = configPath; + _httpPort = httpPort; + _pacPort = pacPort; + + await InitText(); + + if (_needRestart) { - _needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning; + Stop(); + RunListener(); + } + } - _configPath = configPath; - _httpPort = httpPort; - _pacPort = pacPort; + private static async Task InitText() + { + var path = Path.Combine(_configPath, "pac.txt"); - await InitText(); - - if (_needRestart) - { - Stop(); - RunListener(); - } + // Delete the old pac file + if (File.Exists(path) && Utils.GetFileHash(path).Equals("b590c07280f058ef05d5394aa2f927fe")) + { + File.Delete(path); } - private static async Task InitText() + if (!File.Exists(path)) { - var path = Path.Combine(_configPath, "pac.txt"); - - // Delete the old pac file - if (File.Exists(path) && Utils.GetFileHash(path).Equals("b590c07280f058ef05d5394aa2f927fe")) - { - File.Delete(path); - } - - if (!File.Exists(path)) - { - var pac = EmbedUtils.GetEmbedText(Global.PacFileName); - await File.AppendAllTextAsync(path, pac); - } - - var pacText = - (await File.ReadAllTextAsync(path)).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;"); - - var sb = new StringBuilder(); - sb.AppendLine("HTTP/1.0 200 OK"); - sb.AppendLine("Content-type:application/x-ns-proxy-autoconfig"); - sb.AppendLine("Connection:close"); - sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText)); - sb.AppendLine(); - sb.Append(pacText); - _writeContent = Encoding.UTF8.GetBytes(sb.ToString()); + var pac = EmbedUtils.GetEmbedText(Global.PacFileName); + await File.AppendAllTextAsync(path, pac); } - private static void RunListener() + var pacText = + (await File.ReadAllTextAsync(path)).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;"); + + var sb = new StringBuilder(); + sb.AppendLine("HTTP/1.0 200 OK"); + sb.AppendLine("Content-type:application/x-ns-proxy-autoconfig"); + sb.AppendLine("Connection:close"); + sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText)); + sb.AppendLine(); + sb.Append(pacText); + _writeContent = Encoding.UTF8.GetBytes(sb.ToString()); + } + + private static void RunListener() + { + _tcpListener = TcpListener.Create(_pacPort); + _isRunning = true; + _tcpListener.Start(); + Task.Factory.StartNew(async () => { - _tcpListener = TcpListener.Create(_pacPort); - _isRunning = true; - _tcpListener.Start(); - Task.Factory.StartNew(async () => + while (_isRunning) { - while (_isRunning) + try { - try + if (!_tcpListener.Pending()) { - if (!_tcpListener.Pending()) - { - await Task.Delay(10); - continue; - } + await Task.Delay(10); + continue; + } - var client = await _tcpListener.AcceptTcpClientAsync(); - await Task.Run(() => WriteContent(client)); - } - catch - { - // ignored - } + var client = await _tcpListener.AcceptTcpClientAsync(); + await Task.Run(() => WriteContent(client)); } - }, TaskCreationOptions.LongRunning); - } + catch + { + // ignored + } + } + }, TaskCreationOptions.LongRunning); + } - private static void WriteContent(TcpClient client) - { - var stream = client.GetStream(); - stream.Write(_writeContent, 0, _writeContent.Length); - stream.Flush(); - } + private static void WriteContent(TcpClient client) + { + var stream = client.GetStream(); + stream.Write(_writeContent, 0, _writeContent.Length); + stream.Flush(); + } - public static void Stop() + public static void Stop() + { + if (_tcpListener == null) { - if (_tcpListener == null) - { - return; - } - try - { - _isRunning = false; - _tcpListener.Stop(); - _tcpListener = null; - } - catch - { - // ignored - } + return; + } + try + { + _isRunning = false; + _tcpListener.Stop(); + _tcpListener = null; + } + catch + { + // ignored } } } diff --git a/v2rayN/ServiceLib/Handler/ProfileExHandler.cs b/v2rayN/ServiceLib/Handler/ProfileExHandler.cs index b9bc8735..5e8ff2e2 100644 --- a/v2rayN/ServiceLib/Handler/ProfileExHandler.cs +++ b/v2rayN/ServiceLib/Handler/ProfileExHandler.cs @@ -2,181 +2,180 @@ using System.Collections.Concurrent; //using System.Reactive.Linq; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public class ProfileExHandler { - public class ProfileExHandler + private static readonly Lazy _instance = new(() => new()); + private ConcurrentBag _lstProfileEx = []; + private readonly Queue _queIndexIds = new(); + public static ProfileExHandler Instance => _instance.Value; + private static readonly string _tag = "ProfileExHandler"; + + public ProfileExHandler() { - private static readonly Lazy _instance = new(() => new()); - private ConcurrentBag _lstProfileEx = []; - private readonly Queue _queIndexIds = new(); - public static ProfileExHandler Instance => _instance.Value; - private static readonly string _tag = "ProfileExHandler"; + //Init(); + } - public ProfileExHandler() + public async Task Init() + { + await InitData(); + } + + public async Task> GetProfileExs() + { + return await Task.FromResult(_lstProfileEx); + } + + private async Task InitData() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )"); + + _lstProfileEx = new(await SQLiteHelper.Instance.TableAsync().ToListAsync()); + } + + private void IndexIdEnqueue(string indexId) + { + if (indexId.IsNotEmpty() && !_queIndexIds.Contains(indexId)) { - //Init(); + _queIndexIds.Enqueue(indexId); } + } - public async Task Init() + private async Task SaveQueueIndexIds() + { + var cnt = _queIndexIds.Count; + if (cnt > 0) { - await InitData(); - } + var lstExists = await SQLiteHelper.Instance.TableAsync().ToListAsync(); + List lstInserts = []; + List lstUpdates = []; - public async Task> GetProfileExs() - { - return await Task.FromResult(_lstProfileEx); - } - - private async Task InitData() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )"); - - _lstProfileEx = new(await SQLiteHelper.Instance.TableAsync().ToListAsync()); - } - - private void IndexIdEnqueue(string indexId) - { - if (indexId.IsNotEmpty() && !_queIndexIds.Contains(indexId)) + for (var i = 0; i < cnt; i++) { - _queIndexIds.Enqueue(indexId); - } - } - - private async Task SaveQueueIndexIds() - { - var cnt = _queIndexIds.Count; - if (cnt > 0) - { - var lstExists = await SQLiteHelper.Instance.TableAsync().ToListAsync(); - List lstInserts = []; - List lstUpdates = []; - - for (var i = 0; i < cnt; i++) + var id = _queIndexIds.Dequeue(); + var item = lstExists.FirstOrDefault(t => t.IndexId == id); + var itemNew = _lstProfileEx?.FirstOrDefault(t => t.IndexId == id); + if (itemNew is null) { - var id = _queIndexIds.Dequeue(); - var item = lstExists.FirstOrDefault(t => t.IndexId == id); - var itemNew = _lstProfileEx?.FirstOrDefault(t => t.IndexId == id); - if (itemNew is null) - { - continue; - } - - if (item is not null) - { - lstUpdates.Add(itemNew); - } - else - { - lstInserts.Add(itemNew); - } + continue; } - try + if (item is not null) { - if (lstInserts.Count > 0) - { - await SQLiteHelper.Instance.InsertAllAsync(lstInserts); - } - - if (lstUpdates.Count > 0) - { - await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates); - } + lstUpdates.Add(itemNew); } - catch (Exception ex) + else { - Logging.SaveLog(_tag, ex); + lstInserts.Add(itemNew); } } - } - private ProfileExItem AddProfileEx(string indexId) - { - var profileEx = new ProfileExItem() - { - IndexId = indexId, - Delay = 0, - Speed = 0, - Sort = 0, - Message = string.Empty - }; - _lstProfileEx.Add(profileEx); - IndexIdEnqueue(indexId); - return profileEx; - } - - private ProfileExItem GetProfileExItem(string? indexId) - { - return _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId) ?? AddProfileEx(indexId); - } - - public async Task ClearAll() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem "); - _lstProfileEx = new(); - } - - public async Task SaveTo() - { try { - await SaveQueueIndexIds(); + if (lstInserts.Count > 0) + { + await SQLiteHelper.Instance.InsertAllAsync(lstInserts); + } + + if (lstUpdates.Count > 0) + { + await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates); + } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } + } - public void SetTestDelay(string indexId, int delay) + private ProfileExItem AddProfileEx(string indexId) + { + var profileEx = new ProfileExItem() { - var profileEx = GetProfileExItem(indexId); + IndexId = indexId, + Delay = 0, + Speed = 0, + Sort = 0, + Message = string.Empty + }; + _lstProfileEx.Add(profileEx); + IndexIdEnqueue(indexId); + return profileEx; + } - profileEx.Delay = delay; - IndexIdEnqueue(indexId); + private ProfileExItem GetProfileExItem(string? indexId) + { + return _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId) ?? AddProfileEx(indexId); + } + + public async Task ClearAll() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem "); + _lstProfileEx = new(); + } + + public async Task SaveTo() + { + try + { + await SaveQueueIndexIds(); } - - public void SetTestSpeed(string indexId, decimal speed) + catch (Exception ex) { - var profileEx = GetProfileExItem(indexId); - - profileEx.Speed = speed; - IndexIdEnqueue(indexId); - } - - public void SetTestMessage(string indexId, string message) - { - var profileEx = GetProfileExItem(indexId); - - profileEx.Message = message; - IndexIdEnqueue(indexId); - } - - public void SetSort(string indexId, int sort) - { - var profileEx = GetProfileExItem(indexId); - - profileEx.Sort = sort; - IndexIdEnqueue(indexId); - } - - public int GetSort(string indexId) - { - var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId); - if (profileEx == null) - { - return 0; - } - return profileEx.Sort; - } - - public int GetMaxSort() - { - if (_lstProfileEx.Count <= 0) - { - return 0; - } - return _lstProfileEx.Max(t => t == null ? 0 : t.Sort); + Logging.SaveLog(_tag, ex); } } + + public void SetTestDelay(string indexId, int delay) + { + var profileEx = GetProfileExItem(indexId); + + profileEx.Delay = delay; + IndexIdEnqueue(indexId); + } + + public void SetTestSpeed(string indexId, decimal speed) + { + var profileEx = GetProfileExItem(indexId); + + profileEx.Speed = speed; + IndexIdEnqueue(indexId); + } + + public void SetTestMessage(string indexId, string message) + { + var profileEx = GetProfileExItem(indexId); + + profileEx.Message = message; + IndexIdEnqueue(indexId); + } + + public void SetSort(string indexId, int sort) + { + var profileEx = GetProfileExItem(indexId); + + profileEx.Sort = sort; + IndexIdEnqueue(indexId); + } + + public int GetSort(string indexId) + { + var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId); + if (profileEx == null) + { + return 0; + } + return profileEx.Sort; + } + + public int GetMaxSort() + { + if (_lstProfileEx.Count <= 0) + { + return 0; + } + return _lstProfileEx.Max(t => t == null ? 0 : t.Sort); + } } diff --git a/v2rayN/ServiceLib/Handler/StatisticsHandler.cs b/v2rayN/ServiceLib/Handler/StatisticsHandler.cs index 8b900b0a..890c047d 100644 --- a/v2rayN/ServiceLib/Handler/StatisticsHandler.cs +++ b/v2rayN/ServiceLib/Handler/StatisticsHandler.cs @@ -1,164 +1,163 @@ -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public class StatisticsHandler { - public class StatisticsHandler + private static readonly Lazy instance = new(() => new()); + public static StatisticsHandler Instance => instance.Value; + + private Config _config; + private ServerStatItem? _serverStatItem; + private List _lstServerStat; + private Action? _updateFunc; + + private StatisticsXrayService? _statisticsXray; + private StatisticsSingboxService? _statisticsSingbox; + private static readonly string _tag = "StatisticsHandler"; + public List ServerStat => _lstServerStat; + + public async Task Init(Config config, Action updateFunc) { - private static readonly Lazy instance = new(() => new()); - public static StatisticsHandler Instance => instance.Value; - - private Config _config; - private ServerStatItem? _serverStatItem; - private List _lstServerStat; - private Action? _updateFunc; - - private StatisticsXrayService? _statisticsXray; - private StatisticsSingboxService? _statisticsSingbox; - private static readonly string _tag = "StatisticsHandler"; - public List ServerStat => _lstServerStat; - - public async Task Init(Config config, Action updateFunc) + _config = config; + _updateFunc = updateFunc; + if (config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { - _config = config; - _updateFunc = updateFunc; - if (config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) - { - await InitData(); + await InitData(); - _statisticsXray = new StatisticsXrayService(config, UpdateServerStatHandler); - _statisticsSingbox = new StatisticsSingboxService(config, UpdateServerStatHandler); - } - } - - public void Close() - { - try - { - _statisticsXray?.Close(); - _statisticsSingbox?.Close(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public async Task ClearAllServerStatistics() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem "); - _serverStatItem = null; - _lstServerStat = new(); - } - - public async Task SaveTo() - { - try - { - if (_lstServerStat != null) - { - await SQLiteHelper.Instance.UpdateAllAsync(_lstServerStat); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public async Task CloneServerStatItem(string indexId, string toIndexId) - { - if (_lstServerStat == null) - { - return; - } - - if (indexId == toIndexId) - { - return; - } - - var stat = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); - if (stat == null) - { - return; - } - - var toStat = JsonUtils.DeepCopy(stat); - toStat.IndexId = toIndexId; - await SQLiteHelper.Instance.ReplaceAsync(toStat); - _lstServerStat.Add(toStat); - } - - private async Task InitData() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); - - long ticks = DateTime.Now.Date.Ticks; - await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}"); - - _lstServerStat = await SQLiteHelper.Instance.TableAsync().ToListAsync(); - } - - private void UpdateServerStatHandler(ServerSpeedItem server) - { - _ = UpdateServerStat(server); - } - - private async Task UpdateServerStat(ServerSpeedItem server) - { - await GetServerStatItem(_config.IndexId); - - if (_serverStatItem is null) - { - return; - } - if (server.ProxyUp != 0 || server.ProxyDown != 0) - { - _serverStatItem.TodayUp += server.ProxyUp; - _serverStatItem.TodayDown += server.ProxyDown; - _serverStatItem.TotalUp += server.ProxyUp; - _serverStatItem.TotalDown += server.ProxyDown; - } - - server.IndexId = _config.IndexId; - server.TodayUp = _serverStatItem.TodayUp; - server.TodayDown = _serverStatItem.TodayDown; - server.TotalUp = _serverStatItem.TotalUp; - server.TotalDown = _serverStatItem.TotalDown; - _updateFunc?.Invoke(server); - } - - private async Task GetServerStatItem(string indexId) - { - long ticks = DateTime.Now.Date.Ticks; - if (_serverStatItem != null && _serverStatItem.IndexId != indexId) - { - _serverStatItem = null; - } - - if (_serverStatItem == null) - { - _serverStatItem = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); - if (_serverStatItem == null) - { - _serverStatItem = new ServerStatItem - { - IndexId = indexId, - TotalUp = 0, - TotalDown = 0, - TodayUp = 0, - TodayDown = 0, - DateNow = ticks - }; - await SQLiteHelper.Instance.ReplaceAsync(_serverStatItem); - _lstServerStat.Add(_serverStatItem); - } - } - - if (_serverStatItem.DateNow != ticks) - { - _serverStatItem.TodayUp = 0; - _serverStatItem.TodayDown = 0; - _serverStatItem.DateNow = ticks; - } + _statisticsXray = new StatisticsXrayService(config, UpdateServerStatHandler); + _statisticsSingbox = new StatisticsSingboxService(config, UpdateServerStatHandler); } } -} \ No newline at end of file + + public void Close() + { + try + { + _statisticsXray?.Close(); + _statisticsSingbox?.Close(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public async Task ClearAllServerStatistics() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem "); + _serverStatItem = null; + _lstServerStat = new(); + } + + public async Task SaveTo() + { + try + { + if (_lstServerStat != null) + { + await SQLiteHelper.Instance.UpdateAllAsync(_lstServerStat); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public async Task CloneServerStatItem(string indexId, string toIndexId) + { + if (_lstServerStat == null) + { + return; + } + + if (indexId == toIndexId) + { + return; + } + + var stat = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); + if (stat == null) + { + return; + } + + var toStat = JsonUtils.DeepCopy(stat); + toStat.IndexId = toIndexId; + await SQLiteHelper.Instance.ReplaceAsync(toStat); + _lstServerStat.Add(toStat); + } + + private async Task InitData() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); + + long ticks = DateTime.Now.Date.Ticks; + await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}"); + + _lstServerStat = await SQLiteHelper.Instance.TableAsync().ToListAsync(); + } + + private void UpdateServerStatHandler(ServerSpeedItem server) + { + _ = UpdateServerStat(server); + } + + private async Task UpdateServerStat(ServerSpeedItem server) + { + await GetServerStatItem(_config.IndexId); + + if (_serverStatItem is null) + { + return; + } + if (server.ProxyUp != 0 || server.ProxyDown != 0) + { + _serverStatItem.TodayUp += server.ProxyUp; + _serverStatItem.TodayDown += server.ProxyDown; + _serverStatItem.TotalUp += server.ProxyUp; + _serverStatItem.TotalDown += server.ProxyDown; + } + + server.IndexId = _config.IndexId; + server.TodayUp = _serverStatItem.TodayUp; + server.TodayDown = _serverStatItem.TodayDown; + server.TotalUp = _serverStatItem.TotalUp; + server.TotalDown = _serverStatItem.TotalDown; + _updateFunc?.Invoke(server); + } + + private async Task GetServerStatItem(string indexId) + { + long ticks = DateTime.Now.Date.Ticks; + if (_serverStatItem != null && _serverStatItem.IndexId != indexId) + { + _serverStatItem = null; + } + + if (_serverStatItem == null) + { + _serverStatItem = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); + if (_serverStatItem == null) + { + _serverStatItem = new ServerStatItem + { + IndexId = indexId, + TotalUp = 0, + TotalDown = 0, + TodayUp = 0, + TodayDown = 0, + DateNow = ticks + }; + await SQLiteHelper.Instance.ReplaceAsync(_serverStatItem); + _lstServerStat.Add(_serverStatItem); + } + } + + if (_serverStatItem.DateNow != ticks) + { + _serverStatItem.TodayUp = 0; + _serverStatItem.TodayDown = 0; + _serverStatItem.DateNow = ticks; + } + } +} diff --git a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs index f76df12a..9c5a53a8 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs @@ -1,33 +1,32 @@ -namespace ServiceLib.Handler.SysProxy +namespace ServiceLib.Handler.SysProxy; + +public class ProxySettingLinux { - public class ProxySettingLinux + private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; + + public static async Task SetProxy(string host, int port, string exceptions) { - private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; + List args = ["manual", host, port.ToString(), exceptions]; + await ExecCmd(args); + } - public static async Task SetProxy(string host, int port, string exceptions) + public static async Task UnsetProxy() + { + List args = ["none"]; + await ExecCmd(args); + } + + private static async Task ExecCmd(List args) + { + var fileName = Utils.GetBinConfigPath(_proxySetFileName); + if (!File.Exists(fileName)) { - List args = ["manual", host, port.ToString(), exceptions]; - await ExecCmd(args); + var contents = EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName); + await File.AppendAllTextAsync(fileName, contents); + + await Utils.SetLinuxChmod(fileName); } - public static async Task UnsetProxy() - { - List args = ["none"]; - await ExecCmd(args); - } - - private static async Task ExecCmd(List args) - { - var fileName = Utils.GetBinConfigPath(_proxySetFileName); - if (!File.Exists(fileName)) - { - var contents = EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName); - await File.AppendAllTextAsync(fileName, contents); - - await Utils.SetLinuxChmod(fileName); - } - - await Utils.GetCliWrapOutput(fileName, args); - } + await Utils.GetCliWrapOutput(fileName, args); } } diff --git a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs index 228c880a..c18cd728 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs @@ -1,38 +1,37 @@ -namespace ServiceLib.Handler.SysProxy +namespace ServiceLib.Handler.SysProxy; + +public class ProxySettingOSX { - public class ProxySettingOSX + private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; + + public static async Task SetProxy(string host, int port, string exceptions) { - private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; - - public static async Task SetProxy(string host, int port, string exceptions) + List args = ["set", host, port.ToString()]; + if (exceptions.IsNotEmpty()) { - List args = ["set", host, port.ToString()]; - if (exceptions.IsNotEmpty()) - { - args.AddRange(exceptions.Split(',')); - } - - await ExecCmd(args); + args.AddRange(exceptions.Split(',')); } - public static async Task UnsetProxy() + await ExecCmd(args); + } + + public static async Task UnsetProxy() + { + List args = ["clear"]; + await ExecCmd(args); + } + + private static async Task ExecCmd(List args) + { + var fileName = Utils.GetBinConfigPath(_proxySetFileName); + if (!File.Exists(fileName)) { - List args = ["clear"]; - await ExecCmd(args); + var contents = EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName); + await File.AppendAllTextAsync(fileName, contents); + + await Utils.SetLinuxChmod(fileName); } - private static async Task ExecCmd(List args) - { - var fileName = Utils.GetBinConfigPath(_proxySetFileName); - if (!File.Exists(fileName)) - { - var contents = EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName); - await File.AppendAllTextAsync(fileName, contents); - - await Utils.SetLinuxChmod(fileName); - } - - await Utils.GetCliWrapOutput(fileName, args); - } + await Utils.GetCliWrapOutput(fileName, args); } } diff --git a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs index 614bd656..79b860b0 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs @@ -1,360 +1,359 @@ using System.Runtime.InteropServices; using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionOption; -namespace ServiceLib.Handler.SysProxy +namespace ServiceLib.Handler.SysProxy; + +public class ProxySettingWindows { - public class ProxySettingWindows + private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings"; + + private static bool SetProxyFallback(string? strProxy, string? exceptions, int type) { - private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings"; - - private static bool SetProxyFallback(string? strProxy, string? exceptions, int type) + if (type == 1) { - if (type == 1) - { - WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); - WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); - WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); - WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); - } - if (type == 2) - { - WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 1); - WindowsUtils.RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty); - WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty); - WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); - } - else if (type == 4) - { - WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); - WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); - WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); - WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty); - } - return true; + WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); + WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); + WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); + WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); } - - /// - // set to use no proxy - /// - /// Error message with win32 error code - public static bool UnsetProxy() + if (type == 2) { - return SetProxy(null, null, 1); + WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 1); + WindowsUtils.RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty); + WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty); + WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); } - - /// - /// Set system proxy settings - /// - /// proxy address - /// exception addresses that do not use proxy - /// type of proxy defined in PerConnFlags - /// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy) - /// PROXY_TYPE_PROXY = 0x00000002, // via named proxy - /// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL - /// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection - /// - /// Error message with win32 error code - /// true: one of connection is successfully updated proxy settings - public static bool SetProxy(string? strProxy, string? exceptions, int type) + else if (type == 4) { - try - { - // set proxy for LAN - var result = SetConnectionProxy(null, strProxy, exceptions, type); - // set proxy for dial up connections - var connections = EnumerateRasEntries(); - foreach (var connection in connections) - { - result |= SetConnectionProxy(connection, strProxy, exceptions, type); - } - return result; - } - catch - { - _ = SetProxyFallback(strProxy, exceptions, type); - return false; - } + WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); + WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); + WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); + WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty); } + return true; + } - private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type) + /// + // set to use no proxy + /// + /// Error message with win32 error code + public static bool UnsetProxy() + { + return SetProxy(null, null, 1); + } + + /// + /// Set system proxy settings + /// + /// proxy address + /// exception addresses that do not use proxy + /// type of proxy defined in PerConnFlags + /// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy) + /// PROXY_TYPE_PROXY = 0x00000002, // via named proxy + /// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL + /// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection + /// + /// Error message with win32 error code + /// true: one of connection is successfully updated proxy settings + public static bool SetProxy(string? strProxy, string? exceptions, int type) + { + try { - var list = new InternetPerConnOptionList(); - - var optionCount = 1; - if (type == 1) // No proxy + // set proxy for LAN + var result = SetConnectionProxy(null, strProxy, exceptions, type); + // set proxy for dial up connections + var connections = EnumerateRasEntries(); + foreach (var connection in connections) { - optionCount = 1; + result |= SetConnectionProxy(connection, strProxy, exceptions, type); } - else if (type is 2 or 4) // named proxy or autoproxy script URL - { - optionCount = exceptions.IsNullOrEmpty() ? 2 : 3; - } - - var m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT; - var m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; - if (type == 2) // named proxy - { - m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY); - m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER; - } - else if (type == 4) // autoproxy script url - { - m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_AUTO_PROXY_URL); - m_Option = PerConnOption.INTERNET_PER_CONN_AUTOCONFIG_URL; - } - - var options = new InternetConnectionOption[optionCount]; - // USE a proxy server ... - options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; - options[0].m_Value.m_Int = m_Int; - // use THIS proxy server - if (optionCount > 1) - { - options[1].m_Option = m_Option; - options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); // !! remember to deallocate memory 1 - // except for these addresses ... - if (optionCount > 2) - { - options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS; - options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); // !! remember to deallocate memory 2 - } - } - - // default stuff - list.dwSize = Marshal.SizeOf(list); - if (connectionName != null) - { - list.szConnection = Marshal.StringToHGlobalAuto(connectionName); // !! remember to deallocate memory 3 - } - else - { - list.szConnection = nint.Zero; - } - list.dwOptionCount = options.Length; - list.dwOptionError = 0; - - var optSize = Marshal.SizeOf(typeof(InternetConnectionOption)); - // make a pointer out of all that ... - var optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4 - // copy the array over into that spot in memory ... - for (var i = 0; i < options.Length; ++i) - { - if (Environment.Is64BitOperatingSystem) - { - var opt = new nint(optionsPtr.ToInt64() + (i * optSize)); - Marshal.StructureToPtr(options[i], opt, false); - } - else - { - var opt = new nint(optionsPtr.ToInt32() + (i * optSize)); - Marshal.StructureToPtr(options[i], opt, false); - } - } - - list.options = optionsPtr; - - // and then make a pointer out of the whole list - var ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5 - Marshal.StructureToPtr(list, ipcoListPtr, false); - - // and finally, call the API method! - var isSuccess = NativeMethods.InternetSetOption(nint.Zero, - InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION, - ipcoListPtr, list.dwSize); - var returnvalue = 0; // ERROR_SUCCESS - if (!isSuccess) - { // get the error codes, they might be helpful - returnvalue = Marshal.GetLastPInvokeError(); - } - else - { - // Notify the system that the registry settings have been changed and cause them to be refreshed - _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, nint.Zero, 0); - _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_REFRESH, nint.Zero, 0); - } - - // FREE the data ASAP - if (list.szConnection != nint.Zero) - { - Marshal.FreeHGlobal(list.szConnection); // release mem 3 - } - if (optionCount > 1) - { - Marshal.FreeHGlobal(options[1].m_Value.m_StringPtr); // release mem 1 - if (optionCount > 2) - { - Marshal.FreeHGlobal(options[2].m_Value.m_StringPtr); // release mem 2 - } - } - Marshal.FreeCoTaskMem(optionsPtr); // release mem 4 - Marshal.FreeCoTaskMem(ipcoListPtr); // release mem 5 - if (returnvalue != 0) - { - // throw the error codes, they might be helpful - throw new ApplicationException($"Set Internet Proxy failed with error code: {Marshal.GetLastWin32Error()}"); - } - - return true; + return result; } - - /// - /// Retrieve list of connections including LAN and WAN to support PPPoE connection - /// - /// A list of RAS connection names. May be empty list if no dial up connection. - /// Error message with win32 error code - private static IEnumerable EnumerateRasEntries() + catch { - var entries = 0; - // attempt to query with 1 entry buffer - var rasEntryNames = new RASENTRYNAME[1]; - var bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME)); - rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); - - var result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); - // increase buffer if the buffer is not large enough - if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL) - { - rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))]; - for (var i = 0; i < rasEntryNames.Length; i++) - { - rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); - } - - result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); - } - if (result == 0) - { - var entryNames = new List(); - for (var i = 0; i < entries; i++) - { - entryNames.Add(rasEntryNames[i].szEntryName); - } - - return entryNames; - } - throw new ApplicationException($"RasEnumEntries failed with error code: {result}"); - } - - #region WinInet structures - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct InternetPerConnOptionList - { - public int dwSize; // size of the INTERNET_PER_CONN_OPTION_LIST struct - public nint szConnection; // connection name to set/query options - public int dwOptionCount; // number of options to set/query - public int dwOptionError; // on error, which option failed - - //[MarshalAs(UnmanagedType.)] - public nint options; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct InternetConnectionOption - { - private static readonly int Size; - public PerConnOption m_Option; - public InternetConnectionOptionValue m_Value; - - static InternetConnectionOption() - { - Size = Marshal.SizeOf(typeof(InternetConnectionOption)); - } - - // Nested Types - [StructLayout(LayoutKind.Explicit)] - public struct InternetConnectionOptionValue - { - // Fields - [FieldOffset(0)] - public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime; - - [FieldOffset(0)] - public int m_Int; - - [FieldOffset(0)] - public nint m_StringPtr; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct RASENTRYNAME - { - public int dwSize; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS_MaxEntryName + 1)] - public string szEntryName; - - public int dwFlags; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)] - public string szPhonebookPath; - } - - // Constants - public const int RAS_MaxEntryName = 256; - - public const int MAX_PATH = 260; // Standard MAX_PATH value in Windows - } - - #endregion WinInet structures - - #region WinInet enums - - // - // options manifests for Internet{Query|Set}Option - // - public enum InternetOption : uint - { - INTERNET_OPTION_PER_CONNECTION_OPTION = 75, - INTERNET_OPTION_REFRESH = 37, - INTERNET_OPTION_SETTINGS_CHANGED = 39 - } - - // - // Options used in INTERNET_PER_CONN_OPTON struct - // - public enum PerConnOption - { - INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags - INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers. - INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server. - INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script. - } - - // - // PER_CONN_FLAGS - // - [Flags] - public enum PerConnFlags - { - PROXY_TYPE_DIRECT = 0x00000001, // direct to net - PROXY_TYPE_PROXY = 0x00000002, // via named proxy - PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL - PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection - } - - public enum ErrorCode : uint - { - ERROR_BUFFER_TOO_SMALL = 603, - ERROR_INVALID_SIZE = 632 - } - - #endregion WinInet enums - - internal static class NativeMethods - { - [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool InternetSetOption(nint hInternet, InternetOption dwOption, nint lpBuffer, int dwBufferLength); - - [DllImport("Rasapi32.dll", CharSet = CharSet.Auto)] - public static extern uint RasEnumEntries( - string? reserved, // Reserved, must be null - string? lpszPhonebook, // Pointer to full path and filename of phone-book file. If this parameter is NULL, the entries are enumerated from all the remote access phone-book files - [In, Out] RASENTRYNAME[]? lprasentryname, // Buffer to receive RAS entry names - ref int lpcb, // Size of the buffer - ref int lpcEntries // Number of entries written to the buffer - ); + _ = SetProxyFallback(strProxy, exceptions, type); + return false; } } + + private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type) + { + var list = new InternetPerConnOptionList(); + + var optionCount = 1; + if (type == 1) // No proxy + { + optionCount = 1; + } + else if (type is 2 or 4) // named proxy or autoproxy script URL + { + optionCount = exceptions.IsNullOrEmpty() ? 2 : 3; + } + + var m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT; + var m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; + if (type == 2) // named proxy + { + m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY); + m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER; + } + else if (type == 4) // autoproxy script url + { + m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_AUTO_PROXY_URL); + m_Option = PerConnOption.INTERNET_PER_CONN_AUTOCONFIG_URL; + } + + var options = new InternetConnectionOption[optionCount]; + // USE a proxy server ... + options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; + options[0].m_Value.m_Int = m_Int; + // use THIS proxy server + if (optionCount > 1) + { + options[1].m_Option = m_Option; + options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); // !! remember to deallocate memory 1 + // except for these addresses ... + if (optionCount > 2) + { + options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS; + options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); // !! remember to deallocate memory 2 + } + } + + // default stuff + list.dwSize = Marshal.SizeOf(list); + if (connectionName != null) + { + list.szConnection = Marshal.StringToHGlobalAuto(connectionName); // !! remember to deallocate memory 3 + } + else + { + list.szConnection = nint.Zero; + } + list.dwOptionCount = options.Length; + list.dwOptionError = 0; + + var optSize = Marshal.SizeOf(typeof(InternetConnectionOption)); + // make a pointer out of all that ... + var optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4 + // copy the array over into that spot in memory ... + for (var i = 0; i < options.Length; ++i) + { + if (Environment.Is64BitOperatingSystem) + { + var opt = new nint(optionsPtr.ToInt64() + (i * optSize)); + Marshal.StructureToPtr(options[i], opt, false); + } + else + { + var opt = new nint(optionsPtr.ToInt32() + (i * optSize)); + Marshal.StructureToPtr(options[i], opt, false); + } + } + + list.options = optionsPtr; + + // and then make a pointer out of the whole list + var ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5 + Marshal.StructureToPtr(list, ipcoListPtr, false); + + // and finally, call the API method! + var isSuccess = NativeMethods.InternetSetOption(nint.Zero, + InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION, + ipcoListPtr, list.dwSize); + var returnvalue = 0; // ERROR_SUCCESS + if (!isSuccess) + { // get the error codes, they might be helpful + returnvalue = Marshal.GetLastPInvokeError(); + } + else + { + // Notify the system that the registry settings have been changed and cause them to be refreshed + _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, nint.Zero, 0); + _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_REFRESH, nint.Zero, 0); + } + + // FREE the data ASAP + if (list.szConnection != nint.Zero) + { + Marshal.FreeHGlobal(list.szConnection); // release mem 3 + } + if (optionCount > 1) + { + Marshal.FreeHGlobal(options[1].m_Value.m_StringPtr); // release mem 1 + if (optionCount > 2) + { + Marshal.FreeHGlobal(options[2].m_Value.m_StringPtr); // release mem 2 + } + } + Marshal.FreeCoTaskMem(optionsPtr); // release mem 4 + Marshal.FreeCoTaskMem(ipcoListPtr); // release mem 5 + if (returnvalue != 0) + { + // throw the error codes, they might be helpful + throw new ApplicationException($"Set Internet Proxy failed with error code: {Marshal.GetLastWin32Error()}"); + } + + return true; + } + + /// + /// Retrieve list of connections including LAN and WAN to support PPPoE connection + /// + /// A list of RAS connection names. May be empty list if no dial up connection. + /// Error message with win32 error code + private static IEnumerable EnumerateRasEntries() + { + var entries = 0; + // attempt to query with 1 entry buffer + var rasEntryNames = new RASENTRYNAME[1]; + var bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + + var result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); + // increase buffer if the buffer is not large enough + if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL) + { + rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))]; + for (var i = 0; i < rasEntryNames.Length; i++) + { + rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); + } + + result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); + } + if (result == 0) + { + var entryNames = new List(); + for (var i = 0; i < entries; i++) + { + entryNames.Add(rasEntryNames[i].szEntryName); + } + + return entryNames; + } + throw new ApplicationException($"RasEnumEntries failed with error code: {result}"); + } + + #region WinInet structures + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct InternetPerConnOptionList + { + public int dwSize; // size of the INTERNET_PER_CONN_OPTION_LIST struct + public nint szConnection; // connection name to set/query options + public int dwOptionCount; // number of options to set/query + public int dwOptionError; // on error, which option failed + + //[MarshalAs(UnmanagedType.)] + public nint options; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct InternetConnectionOption + { + private static readonly int Size; + public PerConnOption m_Option; + public InternetConnectionOptionValue m_Value; + + static InternetConnectionOption() + { + Size = Marshal.SizeOf(typeof(InternetConnectionOption)); + } + + // Nested Types + [StructLayout(LayoutKind.Explicit)] + public struct InternetConnectionOptionValue + { + // Fields + [FieldOffset(0)] + public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime; + + [FieldOffset(0)] + public int m_Int; + + [FieldOffset(0)] + public nint m_StringPtr; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct RASENTRYNAME + { + public int dwSize; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS_MaxEntryName + 1)] + public string szEntryName; + + public int dwFlags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)] + public string szPhonebookPath; + } + + // Constants + public const int RAS_MaxEntryName = 256; + + public const int MAX_PATH = 260; // Standard MAX_PATH value in Windows + } + + #endregion WinInet structures + + #region WinInet enums + + // + // options manifests for Internet{Query|Set}Option + // + public enum InternetOption : uint + { + INTERNET_OPTION_PER_CONNECTION_OPTION = 75, + INTERNET_OPTION_REFRESH = 37, + INTERNET_OPTION_SETTINGS_CHANGED = 39 + } + + // + // Options used in INTERNET_PER_CONN_OPTON struct + // + public enum PerConnOption + { + INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags + INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers. + INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server. + INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script. + } + + // + // PER_CONN_FLAGS + // + [Flags] + public enum PerConnFlags + { + PROXY_TYPE_DIRECT = 0x00000001, // direct to net + PROXY_TYPE_PROXY = 0x00000002, // via named proxy + PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL + PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection + } + + public enum ErrorCode : uint + { + ERROR_BUFFER_TOO_SMALL = 603, + ERROR_INVALID_SIZE = 632 + } + + #endregion WinInet enums + + internal static class NativeMethods + { + [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool InternetSetOption(nint hInternet, InternetOption dwOption, nint lpBuffer, int dwBufferLength); + + [DllImport("Rasapi32.dll", CharSet = CharSet.Auto)] + public static extern uint RasEnumEntries( + string? reserved, // Reserved, must be null + string? lpszPhonebook, // Pointer to full path and filename of phone-book file. If this parameter is NULL, the entries are enumerated from all the remote access phone-book files + [In, Out] RASENTRYNAME[]? lprasentryname, // Buffer to receive RAS entry names + ref int lpcb, // Size of the buffer + ref int lpcEntries // Number of entries written to the buffer + ); + } } diff --git a/v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs b/v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs index 6d8a9b44..01361a92 100644 --- a/v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs +++ b/v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs @@ -1,99 +1,98 @@ -namespace ServiceLib.Handler.SysProxy +namespace ServiceLib.Handler.SysProxy; + +public static class SysProxyHandler { - public static class SysProxyHandler + private static readonly string _tag = "SysProxyHandler"; + + public static async Task UpdateSysProxy(Config config, bool forceDisable) { - private static readonly string _tag = "SysProxyHandler"; + var type = config.SystemProxyItem.SysProxyType; - public static async Task UpdateSysProxy(Config config, bool forceDisable) + if (forceDisable && type != ESysProxyType.Unchanged) { - var type = config.SystemProxyItem.SysProxyType; - - if (forceDisable && type != ESysProxyType.Unchanged) - { - type = ESysProxyType.ForcedClear; - } - - try - { - var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); - if (port <= 0) - { - return false; - } - switch (type) - { - case ESysProxyType.ForcedChange when Utils.IsWindows(): - { - GetWindowsProxyString(config, port, out var strProxy, out var strExceptions); - ProxySettingWindows.SetProxy(strProxy, strExceptions, 2); - break; - } - case ESysProxyType.ForcedChange when Utils.IsLinux(): - await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions); - break; - - case ESysProxyType.ForcedChange when Utils.IsOSX(): - await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions); - break; - - case ESysProxyType.ForcedClear when Utils.IsWindows(): - ProxySettingWindows.UnsetProxy(); - break; - - case ESysProxyType.ForcedClear when Utils.IsLinux(): - await ProxySettingLinux.UnsetProxy(); - break; - - case ESysProxyType.ForcedClear when Utils.IsOSX(): - await ProxySettingOSX.UnsetProxy(); - break; - - case ESysProxyType.Pac when Utils.IsWindows(): - await SetWindowsProxyPac(port); - break; - } - - if (type != ESysProxyType.Pac && Utils.IsWindows()) - { - PacHandler.Stop(); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return true; + type = ESysProxyType.ForcedClear; } - private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions) + try { - strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); - if (config.SystemProxyItem.NotProxyLocalAddress) + var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); + var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); + if (port <= 0) { - strExceptions = $";{strExceptions}"; + return false; + } + switch (type) + { + case ESysProxyType.ForcedChange when Utils.IsWindows(): + { + GetWindowsProxyString(config, port, out var strProxy, out var strExceptions); + ProxySettingWindows.SetProxy(strProxy, strExceptions, 2); + break; + } + case ESysProxyType.ForcedChange when Utils.IsLinux(): + await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions); + break; + + case ESysProxyType.ForcedChange when Utils.IsOSX(): + await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions); + break; + + case ESysProxyType.ForcedClear when Utils.IsWindows(): + ProxySettingWindows.UnsetProxy(); + break; + + case ESysProxyType.ForcedClear when Utils.IsLinux(): + await ProxySettingLinux.UnsetProxy(); + break; + + case ESysProxyType.ForcedClear when Utils.IsOSX(): + await ProxySettingOSX.UnsetProxy(); + break; + + case ESysProxyType.Pac when Utils.IsWindows(): + await SetWindowsProxyPac(port); + break; } - strProxy = string.Empty; - if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty()) + if (type != ESysProxyType.Pac && Utils.IsWindows()) { - strProxy = $"{Global.Loopback}:{port}"; - } - else - { - strProxy = config.SystemProxyItem.SystemProxyAdvancedProtocol - .Replace("{ip}", Global.Loopback) - .Replace("{http_port}", port.ToString()) - .Replace("{socks_port}", port.ToString()); + PacHandler.Stop(); } } - - private static async Task SetWindowsProxyPac(int port) + catch (Exception ex) { - var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac); - await PacHandler.Start(Utils.GetConfigPath(), port, portPac); - var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; - ProxySettingWindows.SetProxy(strProxy, "", 4); + Logging.SaveLog(_tag, ex); + } + return true; + } + + private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions) + { + strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); + if (config.SystemProxyItem.NotProxyLocalAddress) + { + strExceptions = $";{strExceptions}"; + } + + strProxy = string.Empty; + if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty()) + { + strProxy = $"{Global.Loopback}:{port}"; + } + else + { + strProxy = config.SystemProxyItem.SystemProxyAdvancedProtocol + .Replace("{ip}", Global.Loopback) + .Replace("{http_port}", port.ToString()) + .Replace("{socks_port}", port.ToString()); } } -} \ No newline at end of file + + private static async Task SetWindowsProxyPac(int port) + { + var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac); + await PacHandler.Start(Utils.GetConfigPath(), port, portPac); + var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; + ProxySettingWindows.SetProxy(strProxy, "", 4); + } +} diff --git a/v2rayN/ServiceLib/Handler/TaskHandler.cs b/v2rayN/ServiceLib/Handler/TaskHandler.cs index 08c01a46..ef1d4e38 100644 --- a/v2rayN/ServiceLib/Handler/TaskHandler.cs +++ b/v2rayN/ServiceLib/Handler/TaskHandler.cs @@ -1,98 +1,97 @@ -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public class TaskHandler { - public class TaskHandler + private static readonly Lazy _instance = new(() => new()); + public static TaskHandler Instance => _instance.Value; + + public void RegUpdateTask(Config config, Action updateFunc) { - private static readonly Lazy _instance = new(() => new()); - public static TaskHandler Instance => _instance.Value; + Task.Run(() => ScheduledTasks(config, updateFunc)); + } - public void RegUpdateTask(Config config, Action updateFunc) + private async Task ScheduledTasks(Config config, Action updateFunc) + { + Logging.SaveLog("Setup Scheduled Tasks"); + + var numOfExecuted = 1; + while (true) { - Task.Run(() => ScheduledTasks(config, updateFunc)); - } + //1 minute + await Task.Delay(1000 * 60); - private async Task ScheduledTasks(Config config, Action updateFunc) - { - Logging.SaveLog("Setup Scheduled Tasks"); + //Execute once 1 minute + await UpdateTaskRunSubscription(config, updateFunc); - var numOfExecuted = 1; - while (true) + //Execute once 20 minute + if (numOfExecuted % 20 == 0) { - //1 minute - await Task.Delay(1000 * 60); + //Logging.SaveLog("Execute save config"); - //Execute once 1 minute - await UpdateTaskRunSubscription(config, updateFunc); - - //Execute once 20 minute - if (numOfExecuted % 20 == 0) - { - //Logging.SaveLog("Execute save config"); - - await ConfigHandler.SaveConfig(config); - await ProfileExHandler.Instance.SaveTo(); - } - - //Execute once 1 hour - if (numOfExecuted % 60 == 0) - { - //Logging.SaveLog("Execute delete expired files"); - - FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1)); - FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1)); - FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); - - //Check once 1 hour - await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc); - } - - numOfExecuted++; - } - } - - private async Task UpdateTaskRunSubscription(Config config, Action updateFunc) - { - var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); - var lstSubs = (await AppHandler.Instance.SubItems())? - .Where(t => t.AutoUpdateInterval > 0) - .Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60) - .ToList(); - - if (lstSubs is not { Count: > 0 }) - { - return; + await ConfigHandler.SaveConfig(config); + await ProfileExHandler.Instance.SaveTo(); } - Logging.SaveLog("Execute update subscription"); + //Execute once 1 hour + if (numOfExecuted % 60 == 0) + { + //Logging.SaveLog("Execute delete expired files"); + + FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1)); + FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1)); + FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); + + //Check once 1 hour + await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc); + } + + numOfExecuted++; + } + } + + private async Task UpdateTaskRunSubscription(Config config, Action updateFunc) + { + var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); + var lstSubs = (await AppHandler.Instance.SubItems())? + .Where(t => t.AutoUpdateInterval > 0) + .Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60) + .ToList(); + + if (lstSubs is not { Count: > 0 }) + { + return; + } + + Logging.SaveLog("Execute update subscription"); + var updateHandle = new UpdateService(); + + foreach (var item in lstSubs) + { + await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) => + { + updateFunc?.Invoke(success, msg); + if (success) + { + Logging.SaveLog($"Update subscription end. {msg}"); + } + }); + item.UpdateTime = updateTime; + await ConfigHandler.AddSubItem(config, item); + await Task.Delay(1000); + } + } + + private async Task UpdateTaskRunGeo(Config config, int hours, Action updateFunc) + { + if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0) + { + Logging.SaveLog("Execute update geo files"); + var updateHandle = new UpdateService(); - - foreach (var item in lstSubs) + await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) => { - await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) => - { - updateFunc?.Invoke(success, msg); - if (success) - { - Logging.SaveLog($"Update subscription end. {msg}"); - } - }); - item.UpdateTime = updateTime; - await ConfigHandler.AddSubItem(config, item); - await Task.Delay(1000); - } - } - - private async Task UpdateTaskRunGeo(Config config, int hours, Action updateFunc) - { - if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0) - { - Logging.SaveLog("Execute update geo files"); - - var updateHandle = new UpdateService(); - await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) => - { - updateFunc?.Invoke(false, msg); - }); - } + updateFunc?.Invoke(false, msg); + }); } } } diff --git a/v2rayN/ServiceLib/Handler/WebDavHandler.cs b/v2rayN/ServiceLib/Handler/WebDavHandler.cs index ac9a626f..865f4588 100644 --- a/v2rayN/ServiceLib/Handler/WebDavHandler.cs +++ b/v2rayN/ServiceLib/Handler/WebDavHandler.cs @@ -1,182 +1,181 @@ using System.Net; using WebDav; -namespace ServiceLib.Handler +namespace ServiceLib.Handler; + +public sealed class WebDavHandler { - public sealed class WebDavHandler + private static readonly Lazy _instance = new(() => new()); + public static WebDavHandler Instance => _instance.Value; + + private readonly Config? _config; + private WebDavClient? _client; + private string? _lastDescription; + private string _webDir = Global.AppName + "_backup"; + private readonly string _webFileName = "backup.zip"; + private readonly string _tag = "WebDav--"; + + public WebDavHandler() { - private static readonly Lazy _instance = new(() => new()); - public static WebDavHandler Instance => _instance.Value; + _config = AppHandler.Instance.Config; + } - private readonly Config? _config; - private WebDavClient? _client; - private string? _lastDescription; - private string _webDir = Global.AppName + "_backup"; - private readonly string _webFileName = "backup.zip"; - private readonly string _tag = "WebDav--"; - - public WebDavHandler() + private async Task GetClient() + { + try { - _config = AppHandler.Instance.Config; + if (_config.WebDavItem.Url.IsNullOrEmpty() + || _config.WebDavItem.UserName.IsNullOrEmpty() + || _config.WebDavItem.Password.IsNullOrEmpty()) + { + throw new ArgumentException("webdav parameter error or null"); + } + if (_client != null) + { + _client?.Dispose(); + _client = null; + } + if (_config.WebDavItem.DirName.IsNullOrEmpty()) + { + _webDir = Global.AppName + "_backup"; + } + else + { + _webDir = _config.WebDavItem.DirName.TrimEx(); + } + + var clientParams = new WebDavClientParams + { + BaseAddress = new Uri(_config.WebDavItem.Url), + Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password) + }; + _client = new WebDavClient(clientParams); } - - private async Task GetClient() + catch (Exception ex) { - try - { - if (_config.WebDavItem.Url.IsNullOrEmpty() - || _config.WebDavItem.UserName.IsNullOrEmpty() - || _config.WebDavItem.Password.IsNullOrEmpty()) - { - throw new ArgumentException("webdav parameter error or null"); - } - if (_client != null) - { - _client?.Dispose(); - _client = null; - } - if (_config.WebDavItem.DirName.IsNullOrEmpty()) - { - _webDir = Global.AppName + "_backup"; - } - else - { - _webDir = _config.WebDavItem.DirName.TrimEx(); - } - - var clientParams = new WebDavClientParams - { - BaseAddress = new Uri(_config.WebDavItem.Url), - Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password) - }; - _client = new WebDavClient(clientParams); - } - catch (Exception ex) - { - SaveLog(ex); - return false; - } - return await Task.FromResult(true); - } - - private async Task TryCreateDir() - { - if (_client is null) - { - return false; - } - try - { - var result2 = await _client.Mkcol(_webDir); - if (result2.IsSuccessful) - { - return true; - } - SaveLog(result2.Description); - } - catch (Exception ex) - { - SaveLog(ex); - } + SaveLog(ex); return false; } + return await Task.FromResult(true); + } - private void SaveLog(string desc) + private async Task TryCreateDir() + { + if (_client is null) { - _lastDescription = desc; - Logging.SaveLog(_tag + desc); - } - - private void SaveLog(Exception ex) - { - _lastDescription = ex.Message; - Logging.SaveLog(_tag, ex); - } - - public async Task CheckConnection() - { - if (await GetClient() == false) - { - return false; - } - await TryCreateDir(); - - try - { - var testName = "readme_test"; - var myContent = new StringContent(testName); - var result = await _client.PutFile($"{_webDir}/{testName}", myContent); - if (result.IsSuccessful) - { - await _client.Delete($"{_webDir}/{testName}"); - return true; - } - else - { - SaveLog(result.Description); - } - } - catch (Exception ex) - { - SaveLog(ex); - } return false; } - - public async Task PutFile(string fileName) + try { - if (await GetClient() == false) + var result2 = await _client.Mkcol(_webDir); + if (result2.IsSuccessful) { - return false; - } - await TryCreateDir(); - - try - { - await using var fs = File.OpenRead(fileName); - var result = await _client.PutFile($"{_webDir}/{_webFileName}", fs); // upload a resource - if (result.IsSuccessful) - { - return true; - } - - SaveLog(result.Description); - } - catch (Exception ex) - { - SaveLog(ex); - } - return false; - } - - public async Task GetRawFile(string fileName) - { - if (await GetClient() == false) - { - return false; - } - await TryCreateDir(); - - try - { - var response = await _client.GetRawFile($"{_webDir}/{_webFileName}"); - if (!response.IsSuccessful) - { - SaveLog(response.Description); - return false; - } - - await using var outputFileStream = new FileStream(fileName, FileMode.Create); - await response.Stream.CopyToAsync(outputFileStream); return true; } - catch (Exception ex) - { - SaveLog(ex); - } + SaveLog(result2.Description); + } + catch (Exception ex) + { + SaveLog(ex); + } + return false; + } + + private void SaveLog(string desc) + { + _lastDescription = desc; + Logging.SaveLog(_tag + desc); + } + + private void SaveLog(Exception ex) + { + _lastDescription = ex.Message; + Logging.SaveLog(_tag, ex); + } + + public async Task CheckConnection() + { + if (await GetClient() == false) + { return false; } + await TryCreateDir(); - public string GetLastError() => _lastDescription ?? string.Empty; + try + { + var testName = "readme_test"; + var myContent = new StringContent(testName); + var result = await _client.PutFile($"{_webDir}/{testName}", myContent); + if (result.IsSuccessful) + { + await _client.Delete($"{_webDir}/{testName}"); + return true; + } + else + { + SaveLog(result.Description); + } + } + catch (Exception ex) + { + SaveLog(ex); + } + return false; } + + public async Task PutFile(string fileName) + { + if (await GetClient() == false) + { + return false; + } + await TryCreateDir(); + + try + { + await using var fs = File.OpenRead(fileName); + var result = await _client.PutFile($"{_webDir}/{_webFileName}", fs); // upload a resource + if (result.IsSuccessful) + { + return true; + } + + SaveLog(result.Description); + } + catch (Exception ex) + { + SaveLog(ex); + } + return false; + } + + public async Task GetRawFile(string fileName) + { + if (await GetClient() == false) + { + return false; + } + await TryCreateDir(); + + try + { + var response = await _client.GetRawFile($"{_webDir}/{_webFileName}"); + if (!response.IsSuccessful) + { + SaveLog(response.Description); + return false; + } + + await using var outputFileStream = new FileStream(fileName, FileMode.Create); + await response.Stream.CopyToAsync(outputFileStream); + return true; + } + catch (Exception ex) + { + SaveLog(ex); + } + return false; + } + + public string GetLastError() => _lastDescription ?? string.Empty; } diff --git a/v2rayN/ServiceLib/Models/CheckUpdateModel.cs b/v2rayN/ServiceLib/Models/CheckUpdateModel.cs index 03080c79..85225bc1 100644 --- a/v2rayN/ServiceLib/Models/CheckUpdateModel.cs +++ b/v2rayN/ServiceLib/Models/CheckUpdateModel.cs @@ -1,11 +1,10 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class CheckUpdateModel { - public class CheckUpdateModel - { - public bool? IsSelected { get; set; } - public string? CoreType { get; set; } - public string? Remarks { get; set; } - public string? FileName { get; set; } - public bool? IsFinished { get; set; } - } -} \ No newline at end of file + public bool? IsSelected { get; set; } + public string? CoreType { get; set; } + public string? Remarks { get; set; } + public string? FileName { get; set; } + public bool? IsFinished { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ClashConnectionModel.cs b/v2rayN/ServiceLib/Models/ClashConnectionModel.cs index 3a68feb2..12411852 100644 --- a/v2rayN/ServiceLib/Models/ClashConnectionModel.cs +++ b/v2rayN/ServiceLib/Models/ClashConnectionModel.cs @@ -1,17 +1,16 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class ClashConnectionModel { - public class ClashConnectionModel - { - public string? Id { get; set; } - public string? Network { get; set; } - public string? Type { get; set; } - public string? Host { get; set; } - public ulong Upload { get; set; } - public ulong Download { get; set; } - public string? UploadTraffic { get; set; } - public string? DownloadTraffic { get; set; } - public double Time { get; set; } - public string? Elapsed { get; set; } - public string? Chain { get; set; } - } -} \ No newline at end of file + public string? Id { get; set; } + public string? Network { get; set; } + public string? Type { get; set; } + public string? Host { get; set; } + public ulong Upload { get; set; } + public ulong Download { get; set; } + public string? UploadTraffic { get; set; } + public string? DownloadTraffic { get; set; } + public double Time { get; set; } + public string? Elapsed { get; set; } + public string? Chain { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ClashConnections.cs b/v2rayN/ServiceLib/Models/ClashConnections.cs index 1db83786..7ac2bbd2 100644 --- a/v2rayN/ServiceLib/Models/ClashConnections.cs +++ b/v2rayN/ServiceLib/Models/ClashConnections.cs @@ -1,37 +1,36 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class ClashConnections { - public class ClashConnections - { - public ulong downloadTotal { get; set; } - public ulong uploadTotal { get; set; } - public List? connections { get; set; } - } + public ulong downloadTotal { get; set; } + public ulong uploadTotal { get; set; } + public List? connections { get; set; } +} - public class ConnectionItem - { - public string? id { get; set; } - public MetadataItem? metadata { get; set; } - public ulong upload { get; set; } - public ulong download { get; set; } - public DateTime start { get; set; } - public List? chains { get; set; } - public string? rule { get; set; } - public string? rulePayload { get; set; } - } +public class ConnectionItem +{ + public string? id { get; set; } + public MetadataItem? metadata { get; set; } + public ulong upload { get; set; } + public ulong download { get; set; } + public DateTime start { get; set; } + public List? chains { get; set; } + public string? rule { get; set; } + public string? rulePayload { get; set; } +} - public class MetadataItem - { - public string? network { get; set; } - public string? type { get; set; } - public string? sourceIP { get; set; } - public string? destinationIP { get; set; } - public string? sourcePort { get; set; } - public string? destinationPort { get; set; } - public string? host { get; set; } - public string? nsMode { get; set; } - public object? uid { get; set; } - public string? process { get; set; } - public string? processPath { get; set; } - public string? remoteDestination { get; set; } - } -} \ No newline at end of file +public class MetadataItem +{ + public string? network { get; set; } + public string? type { get; set; } + public string? sourceIP { get; set; } + public string? destinationIP { get; set; } + public string? sourcePort { get; set; } + public string? destinationPort { get; set; } + public string? host { get; set; } + public string? nsMode { get; set; } + public object? uid { get; set; } + public string? process { get; set; } + public string? processPath { get; set; } + public string? remoteDestination { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ClashProviders.cs b/v2rayN/ServiceLib/Models/ClashProviders.cs index 1832f028..402add5d 100644 --- a/v2rayN/ServiceLib/Models/ClashProviders.cs +++ b/v2rayN/ServiceLib/Models/ClashProviders.cs @@ -1,17 +1,16 @@ -using static ServiceLib.Models.ClashProxies; +using static ServiceLib.Models.ClashProxies; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class ClashProviders { - public class ClashProviders - { - public Dictionary? providers { get; set; } + public Dictionary? providers { get; set; } - public class ProvidersItem - { - public string? name { get; set; } - public List? proxies { get; set; } - public string? type { get; set; } - public string? vehicleType { get; set; } - } + public class ProvidersItem + { + public string? name { get; set; } + public List? proxies { get; set; } + public string? type { get; set; } + public string? vehicleType { get; set; } } -} \ No newline at end of file +} diff --git a/v2rayN/ServiceLib/Models/ClashProxies.cs b/v2rayN/ServiceLib/Models/ClashProxies.cs index b5a5de6c..97028b70 100644 --- a/v2rayN/ServiceLib/Models/ClashProxies.cs +++ b/v2rayN/ServiceLib/Models/ClashProxies.cs @@ -1,24 +1,23 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class ClashProxies { - public class ClashProxies + public Dictionary? proxies { get; set; } + + public class ProxiesItem { - public Dictionary? proxies { get; set; } - - public class ProxiesItem - { - public List? all { get; set; } - public List? history { get; set; } - public string? name { get; set; } - public string? type { get; set; } - public bool udp { get; set; } - public string? now { get; set; } - public int delay { get; set; } - } - - public class HistoryItem - { - public string? time { get; set; } - public int delay { get; set; } - } + public List? all { get; set; } + public List? history { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public bool udp { get; set; } + public string? now { get; set; } + public int delay { get; set; } } -} \ No newline at end of file + + public class HistoryItem + { + public string? time { get; set; } + public int delay { get; set; } + } +} diff --git a/v2rayN/ServiceLib/Models/ClashProxyModel.cs b/v2rayN/ServiceLib/Models/ClashProxyModel.cs index a19de253..fd534309 100644 --- a/v2rayN/ServiceLib/Models/ClashProxyModel.cs +++ b/v2rayN/ServiceLib/Models/ClashProxyModel.cs @@ -1,18 +1,17 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ClashProxyModel { - [Serializable] - public class ClashProxyModel - { - public string? Name { get; set; } + public string? Name { get; set; } - public string? Type { get; set; } + public string? Type { get; set; } - public string? Now { get; set; } + public string? Now { get; set; } - public int Delay { get; set; } + public int Delay { get; set; } - public string? DelayName { get; set; } + public string? DelayName { get; set; } - public bool IsActive { get; set; } - } -} \ No newline at end of file + public bool IsActive { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/CmdItem.cs b/v2rayN/ServiceLib/Models/CmdItem.cs index d9c1a4a5..a660208e 100644 --- a/v2rayN/ServiceLib/Models/CmdItem.cs +++ b/v2rayN/ServiceLib/Models/CmdItem.cs @@ -1,8 +1,7 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class CmdItem { - public class CmdItem - { - public string? Cmd { get; set; } - public List? Arguments { get; set; } - } -} \ No newline at end of file + public string? Cmd { get; set; } + public List? Arguments { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ComboItem.cs b/v2rayN/ServiceLib/Models/ComboItem.cs index 12f7ddba..c092f437 100644 --- a/v2rayN/ServiceLib/Models/ComboItem.cs +++ b/v2rayN/ServiceLib/Models/ComboItem.cs @@ -1,15 +1,14 @@ -namespace ServiceLib.Models -{ - public class ComboItem - { - public string? ID - { - get; set; - } +namespace ServiceLib.Models; - public string? Text - { - get; set; - } +public class ComboItem +{ + public string? ID + { + get; set; } -} \ No newline at end of file + + public string? Text + { + get; set; + } +} diff --git a/v2rayN/ServiceLib/Models/Config.cs b/v2rayN/ServiceLib/Models/Config.cs index a74f58d6..7a1e423c 100644 --- a/v2rayN/ServiceLib/Models/Config.cs +++ b/v2rayN/ServiceLib/Models/Config.cs @@ -1,57 +1,56 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +/// +/// 本软件配置文件实体类 +/// +[Serializable] +public class Config { - /// - /// 本软件配置文件实体类 - /// - [Serializable] - public class Config + #region property + + public string IndexId { get; set; } + public string SubIndexId { get; set; } + + public ECoreType RunningCoreType { get; set; } + + public bool IsRunningCore(ECoreType type) { - #region property - - public string IndexId { get; set; } - public string SubIndexId { get; set; } - - public ECoreType RunningCoreType { get; set; } - - public bool IsRunningCore(ECoreType type) + switch (type) { - switch (type) - { - case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5: - case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo: - return true; + case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5: + case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo: + return true; - default: - return false; - } + default: + return false; } - - #endregion property - - #region other entities - - public CoreBasicItem CoreBasicItem { get; set; } - public TunModeItem TunModeItem { get; set; } - public KcpItem KcpItem { get; set; } - public GrpcItem GrpcItem { get; set; } - public RoutingBasicItem RoutingBasicItem { get; set; } - public GUIItem GuiItem { get; set; } - public MsgUIItem MsgUIItem { get; set; } - public UIItem UiItem { get; set; } - public ConstItem ConstItem { get; set; } - public SpeedTestItem SpeedTestItem { get; set; } - public Mux4RayItem Mux4RayItem { get; set; } - public Mux4SboxItem Mux4SboxItem { get; set; } - public HysteriaItem HysteriaItem { get; set; } - public ClashUIItem ClashUIItem { get; set; } - public SystemProxyItem SystemProxyItem { get; set; } - public WebDavItem WebDavItem { get; set; } - public CheckUpdateItem CheckUpdateItem { get; set; } - public Fragment4RayItem? Fragment4RayItem { get; set; } - public List Inbound { get; set; } - public List GlobalHotkeys { get; set; } - public List CoreTypeItem { get; set; } - - #endregion other entities } -} \ No newline at end of file + + #endregion property + + #region other entities + + public CoreBasicItem CoreBasicItem { get; set; } + public TunModeItem TunModeItem { get; set; } + public KcpItem KcpItem { get; set; } + public GrpcItem GrpcItem { get; set; } + public RoutingBasicItem RoutingBasicItem { get; set; } + public GUIItem GuiItem { get; set; } + public MsgUIItem MsgUIItem { get; set; } + public UIItem UiItem { get; set; } + public ConstItem ConstItem { get; set; } + public SpeedTestItem SpeedTestItem { get; set; } + public Mux4RayItem Mux4RayItem { get; set; } + public Mux4SboxItem Mux4SboxItem { get; set; } + public HysteriaItem HysteriaItem { get; set; } + public ClashUIItem ClashUIItem { get; set; } + public SystemProxyItem SystemProxyItem { get; set; } + public WebDavItem WebDavItem { get; set; } + public CheckUpdateItem CheckUpdateItem { get; set; } + public Fragment4RayItem? Fragment4RayItem { get; set; } + public List Inbound { get; set; } + public List GlobalHotkeys { get; set; } + public List CoreTypeItem { get; set; } + + #endregion other entities +} diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 8daf09c6..b3fa1b98 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -1,248 +1,247 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class CoreBasicItem { - [Serializable] - public class CoreBasicItem - { - public bool LogEnabled { get; set; } + public bool LogEnabled { get; set; } - public string Loglevel { get; set; } + public string Loglevel { get; set; } - public bool MuxEnabled { get; set; } + public bool MuxEnabled { get; set; } - public bool DefAllowInsecure { get; set; } + public bool DefAllowInsecure { get; set; } - public string DefFingerprint { get; set; } + public string DefFingerprint { get; set; } - public string DefUserAgent { get; set; } + public string DefUserAgent { get; set; } - public bool EnableFragment { get; set; } + public bool EnableFragment { get; set; } - public bool EnableCacheFile4Sbox { get; set; } = true; - } - - [Serializable] - public class InItem - { - public int LocalPort { get; set; } - public string Protocol { get; set; } - public bool UdpEnabled { get; set; } - public bool SniffingEnabled { get; set; } = true; - public List? DestOverride { get; set; } = ["http", "tls"]; - public bool RouteOnly { get; set; } - public bool AllowLANConn { get; set; } - public bool NewPort4LAN { get; set; } - public string User { get; set; } - public string Pass { get; set; } - public bool SecondLocalPortEnabled { get; set; } - } - - [Serializable] - public class KcpItem - { - public int Mtu { get; set; } - - public int Tti { get; set; } - - public int UplinkCapacity { get; set; } - - public int DownlinkCapacity { get; set; } - - public bool Congestion { get; set; } - - public int ReadBufferSize { get; set; } - - public int WriteBufferSize { get; set; } - } - - [Serializable] - public class GrpcItem - { - public int? IdleTimeout { get; set; } - public int? HealthCheckTimeout { get; set; } - public bool? PermitWithoutStream { get; set; } - public int? InitialWindowsSize { get; set; } - } - - [Serializable] - public class GUIItem - { - public bool AutoRun { get; set; } - public bool EnableStatistics { get; set; } - public bool DisplayRealTimeSpeed { get; set; } - public bool KeepOlderDedupl { get; set; } - public int AutoUpdateInterval { get; set; } - public bool EnableSecurityProtocolTls13 { get; set; } - public int TrayMenuServersLimit { get; set; } = 20; - public bool EnableHWA { get; set; } = false; - public bool EnableLog { get; set; } = true; - } - - [Serializable] - public class MsgUIItem - { - public string? MainMsgFilter { get; set; } - public bool? AutoRefresh { get; set; } - } - - [Serializable] - public class UIItem - { - public bool EnableAutoAdjustMainLvColWidth { get; set; } - public bool EnableUpdateSubOnlyRemarksExist { get; set; } - public double MainWidth { get; set; } - public double MainHeight { get; set; } - public double MainGirdHeight1 { get; set; } - public double MainGirdHeight2 { get; set; } - public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; - public string? ColorPrimaryName { get; set; } - public string? CurrentTheme { get; set; } - public string CurrentLanguage { get; set; } - public string CurrentFontFamily { get; set; } - public int CurrentFontSize { get; set; } - public bool EnableDragDropSort { get; set; } - public bool DoubleClick2Activate { get; set; } - public bool AutoHideStartup { get; set; } - public bool Hide2TrayWhenClose { get; set; } - public List MainColumnItem { get; set; } - public bool ShowInTaskbar { get; set; } - } - - [Serializable] - public class ConstItem - { - public string? SubConvertUrl { get; set; } - public string? GeoSourceUrl { get; set; } - public string? SrsSourceUrl { get; set; } - public string? RouteRulesTemplateSourceUrl { get; set; } - } - - [Serializable] - public class KeyEventItem - { - public EGlobalHotkey EGlobalHotkey { get; set; } - - public bool Alt { get; set; } - - public bool Control { get; set; } - - public bool Shift { get; set; } - - public int? KeyCode { get; set; } - } - - [Serializable] - public class CoreTypeItem - { - public EConfigType ConfigType { get; set; } - - public ECoreType CoreType { get; set; } - } - - [Serializable] - public class TunModeItem - { - public bool EnableTun { get; set; } - public bool StrictRoute { get; set; } = true; - public string Stack { get; set; } - public int Mtu { get; set; } - public bool EnableExInbound { get; set; } - public bool EnableIPv6Address { get; set; } - public string? LinuxSudoPwd { get; set; } - } - - [Serializable] - public class SpeedTestItem - { - public int SpeedTestTimeout { get; set; } - public string SpeedTestUrl { get; set; } - public string SpeedPingTestUrl { get; set; } - public int MixedConcurrencyCount { get; set; } - } - - [Serializable] - public class RoutingBasicItem - { - public string DomainStrategy { get; set; } - public string DomainStrategy4Singbox { get; set; } - public string DomainMatcher { get; set; } - public string RoutingIndexId { get; set; } - } - - [Serializable] - public class ColumnItem - { - public string Name { get; set; } - public int Width { get; set; } - public int Index { get; set; } - } - - [Serializable] - public class Mux4RayItem - { - public int? Concurrency { get; set; } - public int? XudpConcurrency { get; set; } - public string? XudpProxyUDP443 { get; set; } - } - - [Serializable] - public class Mux4SboxItem - { - public string Protocol { get; set; } - public int MaxConnections { get; set; } - public bool? Padding { get; set; } - } - - [Serializable] - public class HysteriaItem - { - public int UpMbps { get; set; } - public int DownMbps { get; set; } - public int HopInterval { get; set; } = 30; - } - - [Serializable] - public class ClashUIItem - { - public ERuleMode RuleMode { get; set; } - public bool EnableIPv6 { get; set; } - public bool EnableMixinContent { get; set; } - public int ProxiesSorting { get; set; } - public bool ProxiesAutoRefresh { get; set; } - public int ProxiesAutoDelayTestInterval { get; set; } = 10; - public bool ConnectionsAutoRefresh { get; set; } - public int ConnectionsRefreshInterval { get; set; } = 2; - } - - [Serializable] - public class SystemProxyItem - { - public ESysProxyType SysProxyType { get; set; } - public string SystemProxyExceptions { get; set; } - public bool NotProxyLocalAddress { get; set; } = true; - public string SystemProxyAdvancedProtocol { get; set; } - } - - [Serializable] - public class WebDavItem - { - public string? Url { get; set; } - public string? UserName { get; set; } - public string? Password { get; set; } - public string? DirName { get; set; } - } - - [Serializable] - public class CheckUpdateItem - { - public bool CheckPreReleaseUpdate { get; set; } - public List? SelectedCoreTypes { get; set; } - } - - [Serializable] - public class Fragment4RayItem - { - public string? Packets { get; set; } - public string? Length { get; set; } - public string? Interval { get; set; } - } + public bool EnableCacheFile4Sbox { get; set; } = true; +} + +[Serializable] +public class InItem +{ + public int LocalPort { get; set; } + public string Protocol { get; set; } + public bool UdpEnabled { get; set; } + public bool SniffingEnabled { get; set; } = true; + public List? DestOverride { get; set; } = ["http", "tls"]; + public bool RouteOnly { get; set; } + public bool AllowLANConn { get; set; } + public bool NewPort4LAN { get; set; } + public string User { get; set; } + public string Pass { get; set; } + public bool SecondLocalPortEnabled { get; set; } +} + +[Serializable] +public class KcpItem +{ + public int Mtu { get; set; } + + public int Tti { get; set; } + + public int UplinkCapacity { get; set; } + + public int DownlinkCapacity { get; set; } + + public bool Congestion { get; set; } + + public int ReadBufferSize { get; set; } + + public int WriteBufferSize { get; set; } +} + +[Serializable] +public class GrpcItem +{ + public int? IdleTimeout { get; set; } + public int? HealthCheckTimeout { get; set; } + public bool? PermitWithoutStream { get; set; } + public int? InitialWindowsSize { get; set; } +} + +[Serializable] +public class GUIItem +{ + public bool AutoRun { get; set; } + public bool EnableStatistics { get; set; } + public bool DisplayRealTimeSpeed { get; set; } + public bool KeepOlderDedupl { get; set; } + public int AutoUpdateInterval { get; set; } + public bool EnableSecurityProtocolTls13 { get; set; } + public int TrayMenuServersLimit { get; set; } = 20; + public bool EnableHWA { get; set; } = false; + public bool EnableLog { get; set; } = true; +} + +[Serializable] +public class MsgUIItem +{ + public string? MainMsgFilter { get; set; } + public bool? AutoRefresh { get; set; } +} + +[Serializable] +public class UIItem +{ + public bool EnableAutoAdjustMainLvColWidth { get; set; } + public bool EnableUpdateSubOnlyRemarksExist { get; set; } + public double MainWidth { get; set; } + public double MainHeight { get; set; } + public double MainGirdHeight1 { get; set; } + public double MainGirdHeight2 { get; set; } + public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; + public string? ColorPrimaryName { get; set; } + public string? CurrentTheme { get; set; } + public string CurrentLanguage { get; set; } + public string CurrentFontFamily { get; set; } + public int CurrentFontSize { get; set; } + public bool EnableDragDropSort { get; set; } + public bool DoubleClick2Activate { get; set; } + public bool AutoHideStartup { get; set; } + public bool Hide2TrayWhenClose { get; set; } + public List MainColumnItem { get; set; } + public bool ShowInTaskbar { get; set; } +} + +[Serializable] +public class ConstItem +{ + public string? SubConvertUrl { get; set; } + public string? GeoSourceUrl { get; set; } + public string? SrsSourceUrl { get; set; } + public string? RouteRulesTemplateSourceUrl { get; set; } +} + +[Serializable] +public class KeyEventItem +{ + public EGlobalHotkey EGlobalHotkey { get; set; } + + public bool Alt { get; set; } + + public bool Control { get; set; } + + public bool Shift { get; set; } + + public int? KeyCode { get; set; } +} + +[Serializable] +public class CoreTypeItem +{ + public EConfigType ConfigType { get; set; } + + public ECoreType CoreType { get; set; } +} + +[Serializable] +public class TunModeItem +{ + public bool EnableTun { get; set; } + public bool StrictRoute { get; set; } = true; + public string Stack { get; set; } + public int Mtu { get; set; } + public bool EnableExInbound { get; set; } + public bool EnableIPv6Address { get; set; } + public string? LinuxSudoPwd { get; set; } +} + +[Serializable] +public class SpeedTestItem +{ + public int SpeedTestTimeout { get; set; } + public string SpeedTestUrl { get; set; } + public string SpeedPingTestUrl { get; set; } + public int MixedConcurrencyCount { get; set; } +} + +[Serializable] +public class RoutingBasicItem +{ + public string DomainStrategy { get; set; } + public string DomainStrategy4Singbox { get; set; } + public string DomainMatcher { get; set; } + public string RoutingIndexId { get; set; } +} + +[Serializable] +public class ColumnItem +{ + public string Name { get; set; } + public int Width { get; set; } + public int Index { get; set; } +} + +[Serializable] +public class Mux4RayItem +{ + public int? Concurrency { get; set; } + public int? XudpConcurrency { get; set; } + public string? XudpProxyUDP443 { get; set; } +} + +[Serializable] +public class Mux4SboxItem +{ + public string Protocol { get; set; } + public int MaxConnections { get; set; } + public bool? Padding { get; set; } +} + +[Serializable] +public class HysteriaItem +{ + public int UpMbps { get; set; } + public int DownMbps { get; set; } + public int HopInterval { get; set; } = 30; +} + +[Serializable] +public class ClashUIItem +{ + public ERuleMode RuleMode { get; set; } + public bool EnableIPv6 { get; set; } + public bool EnableMixinContent { get; set; } + public int ProxiesSorting { get; set; } + public bool ProxiesAutoRefresh { get; set; } + public int ProxiesAutoDelayTestInterval { get; set; } = 10; + public bool ConnectionsAutoRefresh { get; set; } + public int ConnectionsRefreshInterval { get; set; } = 2; +} + +[Serializable] +public class SystemProxyItem +{ + public ESysProxyType SysProxyType { get; set; } + public string SystemProxyExceptions { get; set; } + public bool NotProxyLocalAddress { get; set; } = true; + public string SystemProxyAdvancedProtocol { get; set; } +} + +[Serializable] +public class WebDavItem +{ + public string? Url { get; set; } + public string? UserName { get; set; } + public string? Password { get; set; } + public string? DirName { get; set; } +} + +[Serializable] +public class CheckUpdateItem +{ + public bool CheckPreReleaseUpdate { get; set; } + public List? SelectedCoreTypes { get; set; } +} + +[Serializable] +public class Fragment4RayItem +{ + public string? Packets { get; set; } + public string? Length { get; set; } + public string? Interval { get; set; } } diff --git a/v2rayN/ServiceLib/Models/CoreInfo.cs b/v2rayN/ServiceLib/Models/CoreInfo.cs index c6d6113c..faf899a9 100644 --- a/v2rayN/ServiceLib/Models/CoreInfo.cs +++ b/v2rayN/ServiceLib/Models/CoreInfo.cs @@ -1,21 +1,20 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class CoreInfo { - [Serializable] - public class CoreInfo - { - public ECoreType CoreType { get; set; } - public List? CoreExes { get; set; } - public string? Arguments { get; set; } - public string? Url { get; set; } - public string? ReleaseApiUrl { get; set; } - public string? DownloadUrlWin64 { get; set; } - public string? DownloadUrlWinArm64 { get; set; } - public string? DownloadUrlLinux64 { get; set; } - public string? DownloadUrlLinuxArm64 { get; set; } - public string? DownloadUrlOSX64 { get; set; } - public string? DownloadUrlOSXArm64 { get; set; } - public string? Match { get; set; } - public string? VersionArg { get; set; } - public bool AbsolutePath { get; set; } - } + public ECoreType CoreType { get; set; } + public List? CoreExes { get; set; } + public string? Arguments { get; set; } + public string? Url { get; set; } + public string? ReleaseApiUrl { get; set; } + public string? DownloadUrlWin64 { get; set; } + public string? DownloadUrlWinArm64 { get; set; } + public string? DownloadUrlLinux64 { get; set; } + public string? DownloadUrlLinuxArm64 { get; set; } + public string? DownloadUrlOSX64 { get; set; } + public string? DownloadUrlOSXArm64 { get; set; } + public string? Match { get; set; } + public string? VersionArg { get; set; } + public bool AbsolutePath { get; set; } } diff --git a/v2rayN/ServiceLib/Models/DNSItem.cs b/v2rayN/ServiceLib/Models/DNSItem.cs index 44acb4fb..59ab9c9b 100644 --- a/v2rayN/ServiceLib/Models/DNSItem.cs +++ b/v2rayN/ServiceLib/Models/DNSItem.cs @@ -1,20 +1,19 @@ -using SQLite; +using SQLite; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class DNSItem { - [Serializable] - public class DNSItem - { - [PrimaryKey] - public string Id { get; set; } + [PrimaryKey] + public string Id { get; set; } - public string Remarks { get; set; } - public bool Enabled { get; set; } = true; - public ECoreType CoreType { get; set; } - public bool UseSystemHosts { get; set; } - public string? NormalDNS { get; set; } - public string? TunDNS { get; set; } - public string? DomainStrategy4Freedom { get; set; } - public string? DomainDNSAddress { get; set; } - } -} \ No newline at end of file + public string Remarks { get; set; } + public bool Enabled { get; set; } = true; + public ECoreType CoreType { get; set; } + public bool UseSystemHosts { get; set; } + public string? NormalDNS { get; set; } + public string? TunDNS { get; set; } + public string? DomainStrategy4Freedom { get; set; } + public string? DomainDNSAddress { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/GitHubRelease.cs b/v2rayN/ServiceLib/Models/GitHubRelease.cs index 8f062b40..9875c0a0 100644 --- a/v2rayN/ServiceLib/Models/GitHubRelease.cs +++ b/v2rayN/ServiceLib/Models/GitHubRelease.cs @@ -1,68 +1,67 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class GitHubReleaseAsset { - public class GitHubReleaseAsset - { - [JsonPropertyName("url")] public string? Url { get; set; } + [JsonPropertyName("url")] public string? Url { get; set; } - [JsonPropertyName("id")] public int Id { get; set; } + [JsonPropertyName("id")] public int Id { get; set; } - [JsonPropertyName("node_id")] public string? NodeId { get; set; } + [JsonPropertyName("node_id")] public string? NodeId { get; set; } - [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } - [JsonPropertyName("label")] public object Label { get; set; } + [JsonPropertyName("label")] public object Label { get; set; } - [JsonPropertyName("content_type")] public string? ContentType { get; set; } + [JsonPropertyName("content_type")] public string? ContentType { get; set; } - [JsonPropertyName("state")] public string? State { get; set; } + [JsonPropertyName("state")] public string? State { get; set; } - [JsonPropertyName("size")] public int Size { get; set; } + [JsonPropertyName("size")] public int Size { get; set; } - [JsonPropertyName("download_count")] public int DownloadCount { get; set; } + [JsonPropertyName("download_count")] public int DownloadCount { get; set; } - [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } + [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } - [JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; } + [JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; } - [JsonPropertyName("browser_download_url")] public string? BrowserDownloadUrl { get; set; } - } + [JsonPropertyName("browser_download_url")] public string? BrowserDownloadUrl { get; set; } +} - public class GitHubRelease - { - [JsonPropertyName("url")] public string? Url { get; set; } +public class GitHubRelease +{ + [JsonPropertyName("url")] public string? Url { get; set; } - [JsonPropertyName("assets_url")] public string? AssetsUrl { get; set; } + [JsonPropertyName("assets_url")] public string? AssetsUrl { get; set; } - [JsonPropertyName("upload_url")] public string? UploadUrl { get; set; } + [JsonPropertyName("upload_url")] public string? UploadUrl { get; set; } - [JsonPropertyName("html_url")] public string? HtmlUrl { get; set; } + [JsonPropertyName("html_url")] public string? HtmlUrl { get; set; } - [JsonPropertyName("id")] public int Id { get; set; } + [JsonPropertyName("id")] public int Id { get; set; } - [JsonPropertyName("node_id")] public string? NodeId { get; set; } + [JsonPropertyName("node_id")] public string? NodeId { get; set; } - [JsonPropertyName("tag_name")] public string? TagName { get; set; } + [JsonPropertyName("tag_name")] public string? TagName { get; set; } - [JsonPropertyName("target_commitish")] public string? TargetCommitish { get; set; } + [JsonPropertyName("target_commitish")] public string? TargetCommitish { get; set; } - [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } - [JsonPropertyName("draft")] public bool Draft { get; set; } + [JsonPropertyName("draft")] public bool Draft { get; set; } - [JsonPropertyName("prerelease")] public bool Prerelease { get; set; } + [JsonPropertyName("prerelease")] public bool Prerelease { get; set; } - [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } + [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } - [JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; } + [JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; } - [JsonPropertyName("assets")] public List Assets { get; set; } + [JsonPropertyName("assets")] public List Assets { get; set; } - [JsonPropertyName("tarball_url")] public string? TarballUrl { get; set; } + [JsonPropertyName("tarball_url")] public string? TarballUrl { get; set; } - [JsonPropertyName("zipball_url")] public string? ZipballUrl { get; set; } + [JsonPropertyName("zipball_url")] public string? ZipballUrl { get; set; } - [JsonPropertyName("body")] public string? Body { get; set; } - } -} \ No newline at end of file + [JsonPropertyName("body")] public string? Body { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/IPAPIInfo.cs b/v2rayN/ServiceLib/Models/IPAPIInfo.cs index 07259d95..21063946 100644 --- a/v2rayN/ServiceLib/Models/IPAPIInfo.cs +++ b/v2rayN/ServiceLib/Models/IPAPIInfo.cs @@ -1,13 +1,12 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +internal class IPAPIInfo { - internal class IPAPIInfo - { - public string? ip { get; set; } - public string? city { get; set; } - public string? region { get; set; } - public string? region_code { get; set; } - public string? country { get; set; } - public string? country_name { get; set; } - public string? country_code { get; set; } - } -} \ No newline at end of file + public string? ip { get; set; } + public string? city { get; set; } + public string? region { get; set; } + public string? region_code { get; set; } + public string? country { get; set; } + public string? country_name { get; set; } + public string? country_code { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ProfileExItem.cs b/v2rayN/ServiceLib/Models/ProfileExItem.cs index c1c7174c..49a85749 100644 --- a/v2rayN/ServiceLib/Models/ProfileExItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileExItem.cs @@ -1,16 +1,15 @@ using SQLite; -namespace ServiceLib.Models -{ - [Serializable] - public class ProfileExItem - { - [PrimaryKey] - public string IndexId { get; set; } +namespace ServiceLib.Models; - public int Delay { get; set; } - public decimal Speed { get; set; } - public int Sort { get; set; } - public string? Message { get; set; } - } +[Serializable] +public class ProfileExItem +{ + [PrimaryKey] + public string IndexId { get; set; } + + public int Delay { get; set; } + public decimal Speed { get; set; } + public int Sort { get; set; } + public string? Message { get; set; } } diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index db752a75..58e9414a 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -1,96 +1,96 @@ using SQLite; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ProfileItem { - [Serializable] - public class ProfileItem + public ProfileItem() { - public ProfileItem() - { - IndexId = string.Empty; - ConfigType = EConfigType.VMess; - ConfigVersion = 2; - Address = string.Empty; - Port = 0; - Id = string.Empty; - AlterId = 0; - Security = string.Empty; - Network = string.Empty; - Remarks = string.Empty; - HeaderType = string.Empty; - RequestHost = string.Empty; - Path = string.Empty; - StreamSecurity = string.Empty; - AllowInsecure = string.Empty; - Subid = string.Empty; - Flow = string.Empty; - } - - #region function - - public string GetSummary() - { - var summary = $"[{(ConfigType).ToString()}] "; - var arrAddr = Address.Split('.'); - var addr = arrAddr.Length switch - { - > 2 => $"{arrAddr.First()}***{arrAddr.Last()}", - > 1 => $"***{arrAddr.Last()}", - _ => Address - }; - summary += ConfigType switch - { - EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}", - _ => $"{Remarks}({addr}:{Port})" - }; - return summary; - } - - public List? GetAlpn() - { - return Alpn.IsNullOrEmpty() ? null : Utils.String2List(Alpn); - } - - public string GetNetwork() - { - if (Network.IsNullOrEmpty() || !Global.Networks.Contains(Network)) - { - return Global.DefaultNetwork; - } - return Network.TrimEx(); - } - - #endregion function - - [PrimaryKey] - public string IndexId { get; set; } - public EConfigType ConfigType { get; set; } - public int ConfigVersion { get; set; } - public string Address { get; set; } - public int Port { get; set; } - public string Ports { get; set; } - public string Id { get; set; } - public int AlterId { get; set; } - public string Security { get; set; } - public string Network { get; set; } - public string Remarks { get; set; } - public string HeaderType { get; set; } - public string RequestHost { get; set; } - public string Path { get; set; } - public string StreamSecurity { get; set; } - public string AllowInsecure { get; set; } - public string Subid { get; set; } - public bool IsSub { get; set; } = true; - public string Flow { get; set; } - public string Sni { get; set; } - public string Alpn { get; set; } = string.Empty; - public ECoreType? CoreType { get; set; } - public int? PreSocksPort { get; set; } - public string Fingerprint { get; set; } - public bool DisplayLog { get; set; } = true; - public string PublicKey { get; set; } - public string ShortId { get; set; } - public string SpiderX { get; set; } - public string Extra { get; set; } + IndexId = string.Empty; + ConfigType = EConfigType.VMess; + ConfigVersion = 2; + Address = string.Empty; + Port = 0; + Id = string.Empty; + AlterId = 0; + Security = string.Empty; + Network = string.Empty; + Remarks = string.Empty; + HeaderType = string.Empty; + RequestHost = string.Empty; + Path = string.Empty; + StreamSecurity = string.Empty; + AllowInsecure = string.Empty; + Subid = string.Empty; + Flow = string.Empty; } + + #region function + + public string GetSummary() + { + var summary = $"[{(ConfigType).ToString()}] "; + var arrAddr = Address.Split('.'); + var addr = arrAddr.Length switch + { + > 2 => $"{arrAddr.First()}***{arrAddr.Last()}", + > 1 => $"***{arrAddr.Last()}", + _ => Address + }; + summary += ConfigType switch + { + EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}", + _ => $"{Remarks}({addr}:{Port})" + }; + return summary; + } + + public List? GetAlpn() + { + return Alpn.IsNullOrEmpty() ? null : Utils.String2List(Alpn); + } + + public string GetNetwork() + { + if (Network.IsNullOrEmpty() || !Global.Networks.Contains(Network)) + { + return Global.DefaultNetwork; + } + return Network.TrimEx(); + } + + #endregion function + + [PrimaryKey] + public string IndexId { get; set; } + + public EConfigType ConfigType { get; set; } + public int ConfigVersion { get; set; } + public string Address { get; set; } + public int Port { get; set; } + public string Ports { get; set; } + public string Id { get; set; } + public int AlterId { get; set; } + public string Security { get; set; } + public string Network { get; set; } + public string Remarks { get; set; } + public string HeaderType { get; set; } + public string RequestHost { get; set; } + public string Path { get; set; } + public string StreamSecurity { get; set; } + public string AllowInsecure { get; set; } + public string Subid { get; set; } + public bool IsSub { get; set; } = true; + public string Flow { get; set; } + public string Sni { get; set; } + public string Alpn { get; set; } = string.Empty; + public ECoreType? CoreType { get; set; } + public int? PreSocksPort { get; set; } + public string Fingerprint { get; set; } + public bool DisplayLog { get; set; } = true; + public string PublicKey { get; set; } + public string ShortId { get; set; } + public string SpiderX { get; set; } + public string Extra { get; set; } } diff --git a/v2rayN/ServiceLib/Models/ProfileItemModel.cs b/v2rayN/ServiceLib/Models/ProfileItemModel.cs index 731316fe..a1e81ca2 100644 --- a/v2rayN/ServiceLib/Models/ProfileItemModel.cs +++ b/v2rayN/ServiceLib/Models/ProfileItemModel.cs @@ -1,18 +1,17 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ProfileItemModel : ProfileItem { - [Serializable] - public class ProfileItemModel : ProfileItem - { - public bool IsActive { get; set; } - public string SubRemarks { get; set; } - public int Delay { get; set; } - public decimal Speed { get; set; } - public int Sort { get; set; } - public string DelayVal { get; set; } - public string SpeedVal { get; set; } - public string TodayUp { get; set; } - public string TodayDown { get; set; } - public string TotalUp { get; set; } - public string TotalDown { get; set; } - } -} \ No newline at end of file + public bool IsActive { get; set; } + public string SubRemarks { get; set; } + public int Delay { get; set; } + public decimal Speed { get; set; } + public int Sort { get; set; } + public string DelayVal { get; set; } + public string SpeedVal { get; set; } + public string TodayUp { get; set; } + public string TodayDown { get; set; } + public string TotalUp { get; set; } + public string TotalDown { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/RetResult.cs b/v2rayN/ServiceLib/Models/RetResult.cs index 9c6122c0..688eca3e 100644 --- a/v2rayN/ServiceLib/Models/RetResult.cs +++ b/v2rayN/ServiceLib/Models/RetResult.cs @@ -1,27 +1,26 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class RetResult { - public class RetResult + public bool Success { get; set; } + public string? Msg { get; set; } + public object? Data { get; set; } + + public RetResult(bool success = false) { - public bool Success { get; set; } - public string? Msg { get; set; } - public object? Data { get; set; } - - public RetResult(bool success = false) - { - Success = success; - } - - public RetResult(bool success, string? msg) - { - Success = success; - Msg = msg; - } - - public RetResult(bool success, string? msg, object? data) - { - Success = success; - Msg = msg; - Data = data; - } + Success = success; } -} \ No newline at end of file + + public RetResult(bool success, string? msg) + { + Success = success; + Msg = msg; + } + + public RetResult(bool success, string? msg, object? data) + { + Success = success; + Msg = msg; + Data = data; + } +} diff --git a/v2rayN/ServiceLib/Models/RoutingItem.cs b/v2rayN/ServiceLib/Models/RoutingItem.cs index 5212c359..287e37fb 100644 --- a/v2rayN/ServiceLib/Models/RoutingItem.cs +++ b/v2rayN/ServiceLib/Models/RoutingItem.cs @@ -1,23 +1,22 @@ -using SQLite; +using SQLite; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class RoutingItem { - [Serializable] - public class RoutingItem - { - [PrimaryKey] - public string Id { get; set; } + [PrimaryKey] + public string Id { get; set; } - public string Remarks { get; set; } - public string Url { get; set; } - public string RuleSet { get; set; } - public int RuleNum { get; set; } - public bool Enabled { get; set; } = true; - public bool Locked { get; set; } - public string CustomIcon { get; set; } - public string CustomRulesetPath4Singbox { get; set; } - public string DomainStrategy { get; set; } - public string DomainStrategy4Singbox { get; set; } - public int Sort { get; set; } - } -} \ No newline at end of file + public string Remarks { get; set; } + public string Url { get; set; } + public string RuleSet { get; set; } + public int RuleNum { get; set; } + public bool Enabled { get; set; } = true; + public bool Locked { get; set; } + public string CustomIcon { get; set; } + public string CustomRulesetPath4Singbox { get; set; } + public string DomainStrategy { get; set; } + public string DomainStrategy4Singbox { get; set; } + public int Sort { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/RoutingItemModel.cs b/v2rayN/ServiceLib/Models/RoutingItemModel.cs index 23dcab83..76b35c55 100644 --- a/v2rayN/ServiceLib/Models/RoutingItemModel.cs +++ b/v2rayN/ServiceLib/Models/RoutingItemModel.cs @@ -1,8 +1,7 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class RoutingItemModel : RoutingItem { - [Serializable] - public class RoutingItemModel : RoutingItem - { - public bool IsActive { get; set; } - } -} \ No newline at end of file + public bool IsActive { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/RoutingTemplate.cs b/v2rayN/ServiceLib/Models/RoutingTemplate.cs index 43ebd81c..0cacc1bb 100644 --- a/v2rayN/ServiceLib/Models/RoutingTemplate.cs +++ b/v2rayN/ServiceLib/Models/RoutingTemplate.cs @@ -1,9 +1,8 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class RoutingTemplate { - [Serializable] - public class RoutingTemplate - { - public string Version { get; set; } - public RoutingItem[] RoutingItems { get; set; } - } -} \ No newline at end of file + public string Version { get; set; } + public RoutingItem[] RoutingItems { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/RulesItem.cs b/v2rayN/ServiceLib/Models/RulesItem.cs index fde8d7db..2aedae08 100644 --- a/v2rayN/ServiceLib/Models/RulesItem.cs +++ b/v2rayN/ServiceLib/Models/RulesItem.cs @@ -1,19 +1,18 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class RulesItem { - [Serializable] - public class RulesItem - { - public string Id { get; set; } - public string? Type { get; set; } - public string? Port { get; set; } - public string? Network { get; set; } - public List? InboundTag { get; set; } - public string? OutboundTag { get; set; } - public List? Ip { get; set; } - public List? Domain { get; set; } - public List? Protocol { get; set; } - public List? Process { get; set; } - public bool Enabled { get; set; } = true; - public string? Remarks { get; set; } - } -} \ No newline at end of file + public string Id { get; set; } + public string? Type { get; set; } + public string? Port { get; set; } + public string? Network { get; set; } + public List? InboundTag { get; set; } + public string? OutboundTag { get; set; } + public List? Ip { get; set; } + public List? Domain { get; set; } + public List? Protocol { get; set; } + public List? Process { get; set; } + public bool Enabled { get; set; } = true; + public string? Remarks { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/RulesItemModel.cs b/v2rayN/ServiceLib/Models/RulesItemModel.cs index c11ccdd8..973f2b84 100644 --- a/v2rayN/ServiceLib/Models/RulesItemModel.cs +++ b/v2rayN/ServiceLib/Models/RulesItemModel.cs @@ -1,11 +1,10 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class RulesItemModel : RulesItem { - [Serializable] - public class RulesItemModel : RulesItem - { - public string InboundTags { get; set; } - public string Ips { get; set; } - public string Domains { get; set; } - public string Protocols { get; set; } - } -} \ No newline at end of file + public string InboundTags { get; set; } + public string Ips { get; set; } + public string Domains { get; set; } + public string Protocols { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ServerSpeedItem.cs b/v2rayN/ServiceLib/Models/ServerSpeedItem.cs index 5781a6ea..0a859af6 100644 --- a/v2rayN/ServiceLib/Models/ServerSpeedItem.cs +++ b/v2rayN/ServiceLib/Models/ServerSpeedItem.cs @@ -1,22 +1,21 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ServerSpeedItem : ServerStatItem { - [Serializable] - public class ServerSpeedItem : ServerStatItem - { - public long ProxyUp { get; set; } + public long ProxyUp { get; set; } - public long ProxyDown { get; set; } + public long ProxyDown { get; set; } - public long DirectUp { get; set; } + public long DirectUp { get; set; } - public long DirectDown { get; set; } - } + public long DirectDown { get; set; } +} - [Serializable] - public class TrafficItem - { - public ulong Up { get; set; } +[Serializable] +public class TrafficItem +{ + public ulong Up { get; set; } - public ulong Down { get; set; } - } -} \ No newline at end of file + public ulong Down { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ServerStatItem.cs b/v2rayN/ServiceLib/Models/ServerStatItem.cs index 16f6695f..b5008465 100644 --- a/v2rayN/ServiceLib/Models/ServerStatItem.cs +++ b/v2rayN/ServiceLib/Models/ServerStatItem.cs @@ -1,21 +1,20 @@ -using SQLite; +using SQLite; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ServerStatItem { - [Serializable] - public class ServerStatItem - { - [PrimaryKey] - public string IndexId { get; set; } + [PrimaryKey] + public string IndexId { get; set; } - public long TotalUp { get; set; } + public long TotalUp { get; set; } - public long TotalDown { get; set; } + public long TotalDown { get; set; } - public long TodayUp { get; set; } + public long TodayUp { get; set; } - public long TodayDown { get; set; } + public long TodayDown { get; set; } - public long DateNow { get; set; } - } -} \ No newline at end of file + public long DateNow { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/ServerTestItem.cs b/v2rayN/ServiceLib/Models/ServerTestItem.cs index 99e45d2b..c16a598f 100644 --- a/v2rayN/ServiceLib/Models/ServerTestItem.cs +++ b/v2rayN/ServiceLib/Models/ServerTestItem.cs @@ -1,13 +1,12 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class ServerTestItem { - [Serializable] - public class ServerTestItem - { - public string? IndexId { get; set; } - public string? Address { get; set; } - public int Port { get; set; } - public EConfigType ConfigType { get; set; } - public bool AllowTest { get; set; } - public int QueueNum { get; set; } - } + public string? IndexId { get; set; } + public string? Address { get; set; } + public int Port { get; set; } + public EConfigType ConfigType { get; set; } + public bool AllowTest { get; set; } + public int QueueNum { get; set; } } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index bcd4b59b..9ea1157d 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -1,257 +1,256 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class SingboxConfig { - public class SingboxConfig - { - public Log4Sbox log { get; set; } - public Dns4Sbox? dns { get; set; } - public List inbounds { get; set; } - public List outbounds { get; set; } - public Route4Sbox route { get; set; } - public Experimental4Sbox? experimental { get; set; } - } - - public class Log4Sbox - { - public bool? disabled { get; set; } - public string level { get; set; } - public string output { get; set; } - public bool? timestamp { get; set; } - } - - public class Dns4Sbox - { - public List servers { get; set; } - public List rules { get; set; } - public string? final { get; set; } - public string? strategy { get; set; } - public bool? disable_cache { get; set; } - public bool? disable_expire { get; set; } - public bool? independent_cache { get; set; } - public bool? reverse_mapping { get; set; } - public string? client_subnet { get; set; } - public Fakeip4Sbox? fakeip { get; set; } - } - - public class Route4Sbox - { - public bool? auto_detect_interface { get; set; } - public List rules { get; set; } - public List? rule_set { get; set; } - } - - [Serializable] - public class Rule4Sbox - { - public string? outbound { get; set; } - public string? server { get; set; } - public bool? disable_cache { get; set; } - public string? type { get; set; } - public string? mode { get; set; } - public bool? ip_is_private { get; set; } - public string? client_subnet { get; set; } - public bool? invert { get; set; } - public string? clash_mode { get; set; } - public List? inbound { get; set; } - public List? protocol { get; set; } - public List? network { get; set; } - public List? port { get; set; } - public List? port_range { get; set; } - public List? geosite { get; set; } - public List? domain { get; set; } - public List? domain_suffix { get; set; } - public List? domain_keyword { get; set; } - public List? domain_regex { get; set; } - public List? geoip { get; set; } - public List? ip_cidr { get; set; } - public List? source_ip_cidr { get; set; } - public List? process_name { get; set; } - public List? rule_set { get; set; } - public List? rules { get; set; } - } - - [Serializable] - public class Inbound4Sbox - { - public string type { get; set; } - public string tag { get; set; } - public string listen { get; set; } - public int? listen_port { get; set; } - public string? domain_strategy { get; set; } - public string interface_name { get; set; } - public List? address { get; set; } - public int? mtu { get; set; } - public bool? auto_route { get; set; } - public bool? strict_route { get; set; } - public bool? endpoint_independent_nat { get; set; } - public string? stack { get; set; } - public bool? sniff { get; set; } - public bool? sniff_override_destination { get; set; } - public List users { get; set; } - } - - public class User4Sbox - { - public string username { get; set; } - public string password { get; set; } - } - - public class Outbound4Sbox - { - public string type { get; set; } - public string tag { get; set; } - public string? server { get; set; } - public int? server_port { get; set; } - public List? server_ports { get; set; } - public string? uuid { get; set; } - public string? security { get; set; } - public int? alter_id { get; set; } - public string? flow { get; set; } - public string? hop_interval { get; set; } - public int? up_mbps { get; set; } - public int? down_mbps { get; set; } - public string? auth_str { get; set; } - public int? recv_window_conn { get; set; } - public int? recv_window { get; set; } - public bool? disable_mtu_discovery { get; set; } - public string? detour { get; set; } - public string? method { get; set; } - public string? username { get; set; } - public string? password { get; set; } - public string? congestion_control { get; set; } - public string? version { get; set; } - public string? network { get; set; } - public string? packet_encoding { get; set; } - public List? local_address { get; set; } - public string? private_key { get; set; } - public string? peer_public_key { get; set; } - public List? reserved { get; set; } - public int? mtu { get; set; } - public string? plugin { get; set; } - public string? plugin_opts { get; set; } - public Tls4Sbox? tls { get; set; } - public Multiplex4Sbox? multiplex { get; set; } - public Transport4Sbox? transport { get; set; } - public HyObfs4Sbox? obfs { get; set; } - public List? outbounds { get; set; } - public bool? interrupt_exist_connections { get; set; } - } - - public class Tls4Sbox - { - public bool enabled { get; set; } - public string? server_name { get; set; } - public bool? insecure { get; set; } - public List? alpn { get; set; } - public Utls4Sbox? utls { get; set; } - public Reality4Sbox? reality { get; set; } - } - - public class Multiplex4Sbox - { - public bool enabled { get; set; } - public string protocol { get; set; } - public int max_connections { get; set; } - public bool? padding { get; set; } - } - - public class Utls4Sbox - { - public bool enabled { get; set; } - public string fingerprint { get; set; } - } - - public class Reality4Sbox - { - public bool enabled { get; set; } - public string public_key { get; set; } - public string short_id { get; set; } - } - - public class Transport4Sbox - { - public string? type { get; set; } - public object? host { get; set; } - public string? path { get; set; } - public Headers4Sbox? headers { get; set; } - - public string? service_name { get; set; } - public string? idle_timeout { get; set; } - public string? ping_timeout { get; set; } - public bool? permit_without_stream { get; set; } - } - - public class Headers4Sbox - { - public string? Host { get; set; } - } - - public class HyObfs4Sbox - { - public string? type { get; set; } - public string? password { get; set; } - } - - public class Server4Sbox - { - public string? tag { get; set; } - public string? address { get; set; } - public string? address_resolver { get; set; } - public string? address_strategy { get; set; } - public string? strategy { get; set; } - public string? detour { get; set; } - public string? client_subnet { get; set; } - } - - public class Experimental4Sbox - { - public CacheFile4Sbox? cache_file { get; set; } - public V2ray_Api4Sbox? v2ray_api { get; set; } - public Clash_Api4Sbox? clash_api { get; set; } - } - - public class V2ray_Api4Sbox - { - public string listen { get; set; } - public Stats4Sbox stats { get; set; } - } - - public class Clash_Api4Sbox - { - public string? external_controller { get; set; } - public bool? store_selected { get; set; } - } - - public class Stats4Sbox - { - public bool enabled { get; set; } - public List? inbounds { get; set; } - public List? outbounds { get; set; } - public List? users { get; set; } - } - - public class Fakeip4Sbox - { - public bool enabled { get; set; } - public string inet4_range { get; set; } - public string inet6_range { get; set; } - } - - public class CacheFile4Sbox - { - public bool enabled { get; set; } - public string? path { get; set; } - public string? cache_id { get; set; } - public bool? store_fakeip { get; set; } - } - - public class Ruleset4Sbox - { - public string? tag { get; set; } - public string? type { get; set; } - public string? format { get; set; } - public string? path { get; set; } - public string? url { get; set; } - public string? download_detour { get; set; } - public string? update_interval { get; set; } - } + public Log4Sbox log { get; set; } + public Dns4Sbox? dns { get; set; } + public List inbounds { get; set; } + public List outbounds { get; set; } + public Route4Sbox route { get; set; } + public Experimental4Sbox? experimental { get; set; } +} + +public class Log4Sbox +{ + public bool? disabled { get; set; } + public string level { get; set; } + public string output { get; set; } + public bool? timestamp { get; set; } +} + +public class Dns4Sbox +{ + public List servers { get; set; } + public List rules { get; set; } + public string? final { get; set; } + public string? strategy { get; set; } + public bool? disable_cache { get; set; } + public bool? disable_expire { get; set; } + public bool? independent_cache { get; set; } + public bool? reverse_mapping { get; set; } + public string? client_subnet { get; set; } + public Fakeip4Sbox? fakeip { get; set; } +} + +public class Route4Sbox +{ + public bool? auto_detect_interface { get; set; } + public List rules { get; set; } + public List? rule_set { get; set; } +} + +[Serializable] +public class Rule4Sbox +{ + public string? outbound { get; set; } + public string? server { get; set; } + public bool? disable_cache { get; set; } + public string? type { get; set; } + public string? mode { get; set; } + public bool? ip_is_private { get; set; } + public string? client_subnet { get; set; } + public bool? invert { get; set; } + public string? clash_mode { get; set; } + public List? inbound { get; set; } + public List? protocol { get; set; } + public List? network { get; set; } + public List? port { get; set; } + public List? port_range { get; set; } + public List? geosite { get; set; } + public List? domain { get; set; } + public List? domain_suffix { get; set; } + public List? domain_keyword { get; set; } + public List? domain_regex { get; set; } + public List? geoip { get; set; } + public List? ip_cidr { get; set; } + public List? source_ip_cidr { get; set; } + public List? process_name { get; set; } + public List? rule_set { get; set; } + public List? rules { get; set; } +} + +[Serializable] +public class Inbound4Sbox +{ + public string type { get; set; } + public string tag { get; set; } + public string listen { get; set; } + public int? listen_port { get; set; } + public string? domain_strategy { get; set; } + public string interface_name { get; set; } + public List? address { get; set; } + public int? mtu { get; set; } + public bool? auto_route { get; set; } + public bool? strict_route { get; set; } + public bool? endpoint_independent_nat { get; set; } + public string? stack { get; set; } + public bool? sniff { get; set; } + public bool? sniff_override_destination { get; set; } + public List users { get; set; } +} + +public class User4Sbox +{ + public string username { get; set; } + public string password { get; set; } +} + +public class Outbound4Sbox +{ + public string type { get; set; } + public string tag { get; set; } + public string? server { get; set; } + public int? server_port { get; set; } + public List? server_ports { get; set; } + public string? uuid { get; set; } + public string? security { get; set; } + public int? alter_id { get; set; } + public string? flow { get; set; } + public string? hop_interval { get; set; } + public int? up_mbps { get; set; } + public int? down_mbps { get; set; } + public string? auth_str { get; set; } + public int? recv_window_conn { get; set; } + public int? recv_window { get; set; } + public bool? disable_mtu_discovery { get; set; } + public string? detour { get; set; } + public string? method { get; set; } + public string? username { get; set; } + public string? password { get; set; } + public string? congestion_control { get; set; } + public string? version { get; set; } + public string? network { get; set; } + public string? packet_encoding { get; set; } + public List? local_address { get; set; } + public string? private_key { get; set; } + public string? peer_public_key { get; set; } + public List? reserved { get; set; } + public int? mtu { get; set; } + public string? plugin { get; set; } + public string? plugin_opts { get; set; } + public Tls4Sbox? tls { get; set; } + public Multiplex4Sbox? multiplex { get; set; } + public Transport4Sbox? transport { get; set; } + public HyObfs4Sbox? obfs { get; set; } + public List? outbounds { get; set; } + public bool? interrupt_exist_connections { get; set; } +} + +public class Tls4Sbox +{ + public bool enabled { get; set; } + public string? server_name { get; set; } + public bool? insecure { get; set; } + public List? alpn { get; set; } + public Utls4Sbox? utls { get; set; } + public Reality4Sbox? reality { get; set; } +} + +public class Multiplex4Sbox +{ + public bool enabled { get; set; } + public string protocol { get; set; } + public int max_connections { get; set; } + public bool? padding { get; set; } +} + +public class Utls4Sbox +{ + public bool enabled { get; set; } + public string fingerprint { get; set; } +} + +public class Reality4Sbox +{ + public bool enabled { get; set; } + public string public_key { get; set; } + public string short_id { get; set; } +} + +public class Transport4Sbox +{ + public string? type { get; set; } + public object? host { get; set; } + public string? path { get; set; } + public Headers4Sbox? headers { get; set; } + + public string? service_name { get; set; } + public string? idle_timeout { get; set; } + public string? ping_timeout { get; set; } + public bool? permit_without_stream { get; set; } +} + +public class Headers4Sbox +{ + public string? Host { get; set; } +} + +public class HyObfs4Sbox +{ + public string? type { get; set; } + public string? password { get; set; } +} + +public class Server4Sbox +{ + public string? tag { get; set; } + public string? address { get; set; } + public string? address_resolver { get; set; } + public string? address_strategy { get; set; } + public string? strategy { get; set; } + public string? detour { get; set; } + public string? client_subnet { get; set; } +} + +public class Experimental4Sbox +{ + public CacheFile4Sbox? cache_file { get; set; } + public V2ray_Api4Sbox? v2ray_api { get; set; } + public Clash_Api4Sbox? clash_api { get; set; } +} + +public class V2ray_Api4Sbox +{ + public string listen { get; set; } + public Stats4Sbox stats { get; set; } +} + +public class Clash_Api4Sbox +{ + public string? external_controller { get; set; } + public bool? store_selected { get; set; } +} + +public class Stats4Sbox +{ + public bool enabled { get; set; } + public List? inbounds { get; set; } + public List? outbounds { get; set; } + public List? users { get; set; } +} + +public class Fakeip4Sbox +{ + public bool enabled { get; set; } + public string inet4_range { get; set; } + public string inet6_range { get; set; } +} + +public class CacheFile4Sbox +{ + public bool enabled { get; set; } + public string? path { get; set; } + public string? cache_id { get; set; } + public bool? store_fakeip { get; set; } +} + +public class Ruleset4Sbox +{ + public string? tag { get; set; } + public string? type { get; set; } + public string? format { get; set; } + public string? path { get; set; } + public string? url { get; set; } + public string? download_detour { get; set; } + public string? update_interval { get; set; } } diff --git a/v2rayN/ServiceLib/Models/SpeedTestResult.cs b/v2rayN/ServiceLib/Models/SpeedTestResult.cs index 63016bfc..6e0ce70f 100644 --- a/v2rayN/ServiceLib/Models/SpeedTestResult.cs +++ b/v2rayN/ServiceLib/Models/SpeedTestResult.cs @@ -1,12 +1,11 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class SpeedTestResult { - [Serializable] - public class SpeedTestResult - { - public string? IndexId { get; set; } + public string? IndexId { get; set; } - public string? Delay { get; set; } + public string? Delay { get; set; } - public string? Speed { get; set; } - } -} \ No newline at end of file + public string? Speed { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/SsSIP008.cs b/v2rayN/ServiceLib/Models/SsSIP008.cs index 9c5f1a22..66077471 100644 --- a/v2rayN/ServiceLib/Models/SsSIP008.cs +++ b/v2rayN/ServiceLib/Models/SsSIP008.cs @@ -1,18 +1,17 @@ -namespace ServiceLib.Models -{ - public class SsSIP008 - { - public List? servers { get; set; } - } +namespace ServiceLib.Models; - [Serializable] - public class SsServer - { - public string? remarks { get; set; } - public string? server { get; set; } - public string? server_port { get; set; } - public string? method { get; set; } - public string? password { get; set; } - public string? plugin { get; set; } - } -} \ No newline at end of file +public class SsSIP008 +{ + public List? servers { get; set; } +} + +[Serializable] +public class SsServer +{ + public string? remarks { get; set; } + public string? server { get; set; } + public string? server_port { get; set; } + public string? method { get; set; } + public string? password { get; set; } + public string? plugin { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/SubItem.cs b/v2rayN/ServiceLib/Models/SubItem.cs index b6caa374..cc1fb518 100644 --- a/v2rayN/ServiceLib/Models/SubItem.cs +++ b/v2rayN/ServiceLib/Models/SubItem.cs @@ -1,39 +1,38 @@ -using SQLite; +using SQLite; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +[Serializable] +public class SubItem { - [Serializable] - public class SubItem - { - [PrimaryKey] - public string Id { get; set; } + [PrimaryKey] + public string Id { get; set; } - public string Remarks { get; set; } + public string Remarks { get; set; } - public string Url { get; set; } + public string Url { get; set; } - public string MoreUrl { get; set; } + public string MoreUrl { get; set; } - public bool Enabled { get; set; } = true; + public bool Enabled { get; set; } = true; - public string UserAgent { get; set; } = string.Empty; + public string UserAgent { get; set; } = string.Empty; - public int Sort { get; set; } + public int Sort { get; set; } - public string? Filter { get; set; } + public string? Filter { get; set; } - public int AutoUpdateInterval { get; set; } + public int AutoUpdateInterval { get; set; } - public long UpdateTime { get; set; } + public long UpdateTime { get; set; } - public string? ConvertTarget { get; set; } + public string? ConvertTarget { get; set; } - public string? PrevProfile { get; set; } + public string? PrevProfile { get; set; } - public string? NextProfile { get; set; } + public string? NextProfile { get; set; } - public int? PreSocksPort { get; set; } + public int? PreSocksPort { get; set; } - public string? Memo { get; set; } - } -} \ No newline at end of file + public string? Memo { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 1f441bb8..ca6636b7 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -1,437 +1,436 @@ using System.Text.Json.Serialization; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +public class V2rayConfig { - public class V2rayConfig - { - public Log4Ray log { get; set; } - public object dns { get; set; } - public List inbounds { get; set; } - public List outbounds { get; set; } - public Routing4Ray routing { get; set; } - public Metrics4Ray? metrics { get; set; } - public Policy4Ray? policy { get; set; } - public Stats4Ray? stats { get; set; } - public Observatory4Ray? observatory { get; set; } - public BurstObservatory4Ray? burstObservatory { get; set; } - public string? remarks { get; set; } - } - - public class Stats4Ray - { } - - public class Metrics4Ray - { - public string tag { get; set; } - } - - public class Policy4Ray - { - public SystemPolicy4Ray system { get; set; } - } - - public class SystemPolicy4Ray - { - public bool statsOutboundUplink { get; set; } - public bool statsOutboundDownlink { get; set; } - } - - public class Log4Ray - { - public string? access { get; set; } - - public string? error { get; set; } - - public string? loglevel { get; set; } - } - - public class Inbounds4Ray - { - public string tag { get; set; } - - public int port { get; set; } - - public string listen { get; set; } - - public string protocol { get; set; } - - public Sniffing4Ray sniffing { get; set; } - - public Inboundsettings4Ray settings { get; set; } - } - - public class Inboundsettings4Ray - { - public string? auth { get; set; } - - public bool? udp { get; set; } - - public string? ip { get; set; } - - public string? address { get; set; } - - public List? clients { get; set; } - - public string? decryption { get; set; } - - public bool? allowTransparent { get; set; } - - public List? accounts { get; set; } - } - - public class UsersItem4Ray - { - public string? id { get; set; } - - public int? alterId { get; set; } - - public string? email { get; set; } - - public string? security { get; set; } - - public string? encryption { get; set; } - - public string? flow { get; set; } - } - - public class Sniffing4Ray - { - public bool enabled { get; set; } - public List? destOverride { get; set; } - public bool routeOnly { get; set; } - } - - public class Outbounds4Ray - { - public string tag { get; set; } - - public string protocol { get; set; } - - public Outboundsettings4Ray settings { get; set; } - - public StreamSettings4Ray streamSettings { get; set; } - - public Mux4Ray mux { get; set; } - } - - public class Outboundsettings4Ray - { - public List? vnext { get; set; } - - public List? servers { get; set; } - - public Response4Ray? response { get; set; } - - public string domainStrategy { get; set; } - - public int? userLevel { get; set; } - - public FragmentItem4Ray? fragment { get; set; } - } - - public class VnextItem4Ray - { - public string address { get; set; } - - public int port { get; set; } - - public List users { get; set; } - } - - public class ServersItem4Ray - { - public string email { get; set; } - - public string address { get; set; } - - public string? method { get; set; } - - public bool? ota { get; set; } - - public string? password { get; set; } - - public int port { get; set; } - - public int? level { get; set; } - - public string flow { get; set; } - - public List users { get; set; } - } - - public class SocksUsersItem4Ray - { - public string user { get; set; } - - public string pass { get; set; } - - public int? level { get; set; } - } - - public class Mux4Ray - { - public bool enabled { get; set; } - public int? concurrency { get; set; } - public int? xudpConcurrency { get; set; } - public string? xudpProxyUDP443 { get; set; } - } - - public class Response4Ray - { - public string type { get; set; } - } - - public class Dns4Ray - { - public List servers { get; set; } - } - - public class DnsServer4Ray - { - public string? address { get; set; } - public List? domains { get; set; } - } - - public class Routing4Ray - { - public string domainStrategy { get; set; } - - public string? domainMatcher { get; set; } - - public List rules { get; set; } - - public List? balancers { get; set; } - } - - [Serializable] - public class RulesItem4Ray - { - public string? type { get; set; } - - public string? port { get; set; } - public string? network { get; set; } - - public List? inboundTag { get; set; } - - public string? outboundTag { get; set; } - - public string? balancerTag { get; set; } - - public List? ip { get; set; } - - public List? domain { get; set; } - - public List? protocol { get; set; } - } - - public class BalancersItem4Ray - { - public List? selector { get; set; } - public BalancersStrategy4Ray? strategy { get; set; } - public string? tag { get; set; } - } - - public class BalancersStrategy4Ray - { - public string? type { get; set; } - public BalancersStrategySettings4Ray? settings { get; set; } - } - - public class BalancersStrategySettings4Ray - { - public int? expected { get; set; } - public string? maxRTT { get; set; } - public float? tolerance { get; set; } - public List? baselines { get; set; } - public List? costs { get; set; } - } - - public class BalancersStrategySettingsCosts4Ray - { - public bool? regexp { get; set; } - public string? match { get; set; } - public float? value { get; set; } - } - - public class Observatory4Ray - { - public List? subjectSelector { get; set; } - public string? probeUrl { get; set; } - public string? probeInterval { get; set; } - public bool? enableConcurrency { get; set; } - } - - public class BurstObservatory4Ray - { - public List? subjectSelector { get; set; } - public BurstObservatoryPingConfig4Ray? pingConfig { get; set; } - } - - public class BurstObservatoryPingConfig4Ray - { - public string? destination { get; set; } - public string? connectivity { get; set; } - public string? interval { get; set; } - public int? sampling { get; set; } - public string? timeout { get; set; } - } - - public class StreamSettings4Ray - { - public string network { get; set; } - - public string security { get; set; } - - public TlsSettings4Ray? tlsSettings { get; set; } - - public TcpSettings4Ray? tcpSettings { get; set; } - - public KcpSettings4Ray? kcpSettings { get; set; } - - public WsSettings4Ray? wsSettings { get; set; } - - public HttpupgradeSettings4Ray? httpupgradeSettings { get; set; } - - public XhttpSettings4Ray? xhttpSettings { get; set; } - - public HttpSettings4Ray? httpSettings { get; set; } - - public QuicSettings4Ray? quicSettings { get; set; } - - public TlsSettings4Ray? realitySettings { get; set; } - - public GrpcSettings4Ray? grpcSettings { get; set; } - - public Sockopt4Ray? sockopt { get; set; } - } - - public class TlsSettings4Ray - { - public bool? allowInsecure { get; set; } - - public string? serverName { get; set; } - - public List? alpn { get; set; } - - public string? fingerprint { get; set; } - - public bool? show { get; set; } - public string? publicKey { get; set; } - public string? shortId { get; set; } - public string? spiderX { get; set; } - } - - public class TcpSettings4Ray - { - public Header4Ray header { get; set; } - } - - public class Header4Ray - { - public string type { get; set; } - - public object request { get; set; } - - public object response { get; set; } - - public string? domain { get; set; } - } - - public class KcpSettings4Ray - { - public int mtu { get; set; } - - public int tti { get; set; } - - public int uplinkCapacity { get; set; } - - public int downlinkCapacity { get; set; } - - public bool congestion { get; set; } - - public int readBufferSize { get; set; } - - public int writeBufferSize { get; set; } - - public Header4Ray header { get; set; } - - public string seed { get; set; } - } - - public class WsSettings4Ray - { - public string? path { get; set; } - public string? host { get; set; } - - public Headers4Ray headers { get; set; } - } - - public class Headers4Ray - { - public string Host { get; set; } - - [JsonPropertyName("User-Agent")] - public string UserAgent { get; set; } - } - - public class HttpupgradeSettings4Ray - { - public string? path { get; set; } - - public string? host { get; set; } - } - - public class XhttpSettings4Ray - { - public string? path { get; set; } - public string? host { get; set; } - public string? mode { get; set; } - public object? extra { get; set; } - } - - public class HttpSettings4Ray - { - public string? path { get; set; } - - public List? host { get; set; } - } - - public class QuicSettings4Ray - { - public string security { get; set; } - - public string key { get; set; } - - public Header4Ray header { get; set; } - } - - public class GrpcSettings4Ray - { - public string? authority { get; set; } - public string? serviceName { get; set; } - public bool multiMode { get; set; } - public int? idle_timeout { get; set; } - public int? health_check_timeout { get; set; } - public bool? permit_without_stream { get; set; } - public int? initial_windows_size { get; set; } - } - - public class AccountsItem4Ray - { - public string user { get; set; } - - public string pass { get; set; } - } - - public class Sockopt4Ray - { - public string? dialerProxy { get; set; } - } - - public class FragmentItem4Ray - { - public string? packets { get; set; } - public string? length { get; set; } - public string? interval { get; set; } - } + public Log4Ray log { get; set; } + public object dns { get; set; } + public List inbounds { get; set; } + public List outbounds { get; set; } + public Routing4Ray routing { get; set; } + public Metrics4Ray? metrics { get; set; } + public Policy4Ray? policy { get; set; } + public Stats4Ray? stats { get; set; } + public Observatory4Ray? observatory { get; set; } + public BurstObservatory4Ray? burstObservatory { get; set; } + public string? remarks { get; set; } +} + +public class Stats4Ray +{ } + +public class Metrics4Ray +{ + public string tag { get; set; } +} + +public class Policy4Ray +{ + public SystemPolicy4Ray system { get; set; } +} + +public class SystemPolicy4Ray +{ + public bool statsOutboundUplink { get; set; } + public bool statsOutboundDownlink { get; set; } +} + +public class Log4Ray +{ + public string? access { get; set; } + + public string? error { get; set; } + + public string? loglevel { get; set; } +} + +public class Inbounds4Ray +{ + public string tag { get; set; } + + public int port { get; set; } + + public string listen { get; set; } + + public string protocol { get; set; } + + public Sniffing4Ray sniffing { get; set; } + + public Inboundsettings4Ray settings { get; set; } +} + +public class Inboundsettings4Ray +{ + public string? auth { get; set; } + + public bool? udp { get; set; } + + public string? ip { get; set; } + + public string? address { get; set; } + + public List? clients { get; set; } + + public string? decryption { get; set; } + + public bool? allowTransparent { get; set; } + + public List? accounts { get; set; } +} + +public class UsersItem4Ray +{ + public string? id { get; set; } + + public int? alterId { get; set; } + + public string? email { get; set; } + + public string? security { get; set; } + + public string? encryption { get; set; } + + public string? flow { get; set; } +} + +public class Sniffing4Ray +{ + public bool enabled { get; set; } + public List? destOverride { get; set; } + public bool routeOnly { get; set; } +} + +public class Outbounds4Ray +{ + public string tag { get; set; } + + public string protocol { get; set; } + + public Outboundsettings4Ray settings { get; set; } + + public StreamSettings4Ray streamSettings { get; set; } + + public Mux4Ray mux { get; set; } +} + +public class Outboundsettings4Ray +{ + public List? vnext { get; set; } + + public List? servers { get; set; } + + public Response4Ray? response { get; set; } + + public string domainStrategy { get; set; } + + public int? userLevel { get; set; } + + public FragmentItem4Ray? fragment { get; set; } +} + +public class VnextItem4Ray +{ + public string address { get; set; } + + public int port { get; set; } + + public List users { get; set; } +} + +public class ServersItem4Ray +{ + public string email { get; set; } + + public string address { get; set; } + + public string? method { get; set; } + + public bool? ota { get; set; } + + public string? password { get; set; } + + public int port { get; set; } + + public int? level { get; set; } + + public string flow { get; set; } + + public List users { get; set; } +} + +public class SocksUsersItem4Ray +{ + public string user { get; set; } + + public string pass { get; set; } + + public int? level { get; set; } +} + +public class Mux4Ray +{ + public bool enabled { get; set; } + public int? concurrency { get; set; } + public int? xudpConcurrency { get; set; } + public string? xudpProxyUDP443 { get; set; } +} + +public class Response4Ray +{ + public string type { get; set; } +} + +public class Dns4Ray +{ + public List servers { get; set; } +} + +public class DnsServer4Ray +{ + public string? address { get; set; } + public List? domains { get; set; } +} + +public class Routing4Ray +{ + public string domainStrategy { get; set; } + + public string? domainMatcher { get; set; } + + public List rules { get; set; } + + public List? balancers { get; set; } +} + +[Serializable] +public class RulesItem4Ray +{ + public string? type { get; set; } + + public string? port { get; set; } + public string? network { get; set; } + + public List? inboundTag { get; set; } + + public string? outboundTag { get; set; } + + public string? balancerTag { get; set; } + + public List? ip { get; set; } + + public List? domain { get; set; } + + public List? protocol { get; set; } +} + +public class BalancersItem4Ray +{ + public List? selector { get; set; } + public BalancersStrategy4Ray? strategy { get; set; } + public string? tag { get; set; } +} + +public class BalancersStrategy4Ray +{ + public string? type { get; set; } + public BalancersStrategySettings4Ray? settings { get; set; } +} + +public class BalancersStrategySettings4Ray +{ + public int? expected { get; set; } + public string? maxRTT { get; set; } + public float? tolerance { get; set; } + public List? baselines { get; set; } + public List? costs { get; set; } +} + +public class BalancersStrategySettingsCosts4Ray +{ + public bool? regexp { get; set; } + public string? match { get; set; } + public float? value { get; set; } +} + +public class Observatory4Ray +{ + public List? subjectSelector { get; set; } + public string? probeUrl { get; set; } + public string? probeInterval { get; set; } + public bool? enableConcurrency { get; set; } +} + +public class BurstObservatory4Ray +{ + public List? subjectSelector { get; set; } + public BurstObservatoryPingConfig4Ray? pingConfig { get; set; } +} + +public class BurstObservatoryPingConfig4Ray +{ + public string? destination { get; set; } + public string? connectivity { get; set; } + public string? interval { get; set; } + public int? sampling { get; set; } + public string? timeout { get; set; } +} + +public class StreamSettings4Ray +{ + public string network { get; set; } + + public string security { get; set; } + + public TlsSettings4Ray? tlsSettings { get; set; } + + public TcpSettings4Ray? tcpSettings { get; set; } + + public KcpSettings4Ray? kcpSettings { get; set; } + + public WsSettings4Ray? wsSettings { get; set; } + + public HttpupgradeSettings4Ray? httpupgradeSettings { get; set; } + + public XhttpSettings4Ray? xhttpSettings { get; set; } + + public HttpSettings4Ray? httpSettings { get; set; } + + public QuicSettings4Ray? quicSettings { get; set; } + + public TlsSettings4Ray? realitySettings { get; set; } + + public GrpcSettings4Ray? grpcSettings { get; set; } + + public Sockopt4Ray? sockopt { get; set; } +} + +public class TlsSettings4Ray +{ + public bool? allowInsecure { get; set; } + + public string? serverName { get; set; } + + public List? alpn { get; set; } + + public string? fingerprint { get; set; } + + public bool? show { get; set; } + public string? publicKey { get; set; } + public string? shortId { get; set; } + public string? spiderX { get; set; } +} + +public class TcpSettings4Ray +{ + public Header4Ray header { get; set; } +} + +public class Header4Ray +{ + public string type { get; set; } + + public object request { get; set; } + + public object response { get; set; } + + public string? domain { get; set; } +} + +public class KcpSettings4Ray +{ + public int mtu { get; set; } + + public int tti { get; set; } + + public int uplinkCapacity { get; set; } + + public int downlinkCapacity { get; set; } + + public bool congestion { get; set; } + + public int readBufferSize { get; set; } + + public int writeBufferSize { get; set; } + + public Header4Ray header { get; set; } + + public string seed { get; set; } +} + +public class WsSettings4Ray +{ + public string? path { get; set; } + public string? host { get; set; } + + public Headers4Ray headers { get; set; } +} + +public class Headers4Ray +{ + public string Host { get; set; } + + [JsonPropertyName("User-Agent")] + public string UserAgent { get; set; } +} + +public class HttpupgradeSettings4Ray +{ + public string? path { get; set; } + + public string? host { get; set; } +} + +public class XhttpSettings4Ray +{ + public string? path { get; set; } + public string? host { get; set; } + public string? mode { get; set; } + public object? extra { get; set; } +} + +public class HttpSettings4Ray +{ + public string? path { get; set; } + + public List? host { get; set; } +} + +public class QuicSettings4Ray +{ + public string security { get; set; } + + public string key { get; set; } + + public Header4Ray header { get; set; } +} + +public class GrpcSettings4Ray +{ + public string? authority { get; set; } + public string? serviceName { get; set; } + public bool multiMode { get; set; } + public int? idle_timeout { get; set; } + public int? health_check_timeout { get; set; } + public bool? permit_without_stream { get; set; } + public int? initial_windows_size { get; set; } +} + +public class AccountsItem4Ray +{ + public string user { get; set; } + + public string pass { get; set; } +} + +public class Sockopt4Ray +{ + public string? dialerProxy { get; set; } +} + +public class FragmentItem4Ray +{ + public string? packets { get; set; } + public string? length { get; set; } + public string? interval { get; set; } } diff --git a/v2rayN/ServiceLib/Models/V2rayMetricsVars.cs b/v2rayN/ServiceLib/Models/V2rayMetricsVars.cs index acd4ea45..cc8d64bd 100644 --- a/v2rayN/ServiceLib/Models/V2rayMetricsVars.cs +++ b/v2rayN/ServiceLib/Models/V2rayMetricsVars.cs @@ -1,11 +1,10 @@ -using System.Collections; +using System.Collections; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +internal class V2rayMetricsVars { - internal class V2rayMetricsVars - { - public V2rayMetricsVarsStats? stats { get; set; } - } + public V2rayMetricsVarsStats? stats { get; set; } } public class V2rayMetricsVarsStats @@ -17,4 +16,4 @@ public class V2rayMetricsVarsLink { public long downlink { get; set; } public long uplink { get; set; } -} \ No newline at end of file +} diff --git a/v2rayN/ServiceLib/Models/V2rayTcpRequest.cs b/v2rayN/ServiceLib/Models/V2rayTcpRequest.cs index 98564415..07a31d3d 100644 --- a/v2rayN/ServiceLib/Models/V2rayTcpRequest.cs +++ b/v2rayN/ServiceLib/Models/V2rayTcpRequest.cs @@ -1,21 +1,20 @@ -namespace ServiceLib.Models +namespace ServiceLib.Models; + +/// +/// Tcp伪装http的Request,只要Host +/// +public class V2rayTcpRequest { /// - /// Tcp伪装http的Request,只要Host + /// /// - public class V2rayTcpRequest - { - /// - /// - /// - public RequestHeaders headers { get; set; } - } + public RequestHeaders headers { get; set; } +} - public class RequestHeaders - { - /// - /// - /// - public List Host { get; set; } - } -} \ No newline at end of file +public class RequestHeaders +{ + /// + /// + /// + public List Host { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/VmessQRCode.cs b/v2rayN/ServiceLib/Models/VmessQRCode.cs index 92f93bf3..c51d9986 100644 --- a/v2rayN/ServiceLib/Models/VmessQRCode.cs +++ b/v2rayN/ServiceLib/Models/VmessQRCode.cs @@ -1,44 +1,43 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace ServiceLib.Models +namespace ServiceLib.Models; + +/// +/// https://github.com/2dust/v2rayN/wiki/ +/// +[Serializable] +public class VmessQRCode { - /// - /// https://github.com/2dust/v2rayN/wiki/ - /// - [Serializable] - public class VmessQRCode - { - [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] - public int v { get; set; } = 2; + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] + public int v { get; set; } = 2; - public string ps { get; set; } = string.Empty; + public string ps { get; set; } = string.Empty; - public string add { get; set; } = string.Empty; + public string add { get; set; } = string.Empty; - [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] - public int port { get; set; } = 0; + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] + public int port { get; set; } = 0; - public string id { get; set; } = string.Empty; + public string id { get; set; } = string.Empty; - [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] - public int aid { get; set; } = 0; + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] + public int aid { get; set; } = 0; - public string scy { get; set; } = string.Empty; + public string scy { get; set; } = string.Empty; - public string net { get; set; } = string.Empty; + public string net { get; set; } = string.Empty; - public string type { get; set; } = string.Empty; + public string type { get; set; } = string.Empty; - public string host { get; set; } = string.Empty; + public string host { get; set; } = string.Empty; - public string path { get; set; } = string.Empty; + public string path { get; set; } = string.Empty; - public string tls { get; set; } = string.Empty; + public string tls { get; set; } = string.Empty; - public string sni { get; set; } = string.Empty; + public string sni { get; set; } = string.Empty; - public string alpn { get; set; } = string.Empty; + public string alpn { get; set; } = string.Empty; - public string fp { get; set; } = string.Empty; - } -} \ No newline at end of file + public string fp { get; set; } = string.Empty; +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs index 4358b5a9..d6256e6a 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs @@ -1,269 +1,268 @@ -namespace ServiceLib.Services.CoreConfig -{ - /// - /// Core configuration file processing class - /// - public class CoreConfigClashService - { - private Config _config; - private static readonly string _tag = "CoreConfigClashService"; +namespace ServiceLib.Services.CoreConfig; - public CoreConfigClashService(Config config) +/// +/// Core configuration file processing class +/// +public class CoreConfigClashService +{ + private Config _config; + private static readonly string _tag = "CoreConfigClashService"; + + public CoreConfigClashService(Config config) + { + _config = config; + } + + /// + /// 生成配置文件 + /// + /// + /// + /// + /// + public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + if (node == null || fileName is null) { - _config = config; + ret.Msg = ResUI.CheckServerSettings; + return ret; } - /// - /// 生成配置文件 - /// - /// - /// - /// - /// - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + ret.Msg = ResUI.InitialConfiguration; + + try { - var ret = new RetResult(); - if (node == null || fileName is null) + if (node == null) { ret.Msg = ResUI.CheckServerSettings; return ret; } - ret.Msg = ResUI.InitialConfiguration; + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + var addressFileName = node.Address; + if (addressFileName.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + if (!File.Exists(addressFileName)) + { + addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedReadConfiguration + "1"; + return ret; + } + + var tagYamlStr1 = "!"; + var tagYamlStr2 = "__strn__"; + var tagYamlStr3 = "!!str"; + var txtFile = File.ReadAllText(addressFileName); + txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); + + //YAML anchors + if (txtFile.Contains("<<:") && txtFile.Contains('*') && txtFile.Contains('&')) + { + txtFile = YamlUtils.PreprocessYaml(txtFile); + } + + var fileContent = YamlUtils.FromYaml>(txtFile); + if (fileContent == null) + { + ret.Msg = ResUI.FailedConversionConfiguration; + return ret; + } + + //mixed-port + fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); + //log-level + fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel); + + //external-controller + fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}"; + //allow-lan + if (_config.Inbound.First().AllowLANConn) + { + fileContent["allow-lan"] = "true"; + fileContent["bind-address"] = "*"; + } + else + { + fileContent["allow-lan"] = "false"; + } + + //ipv6 + fileContent["ipv6"] = _config.ClashUIItem.EnableIPv6; + + //mode + if (!fileContent.ContainsKey("mode")) + { + fileContent["mode"] = ERuleMode.Rule.ToString().ToLower(); + } + else + { + if (_config.ClashUIItem.RuleMode != ERuleMode.Unchanged) + { + fileContent["mode"] = _config.ClashUIItem.RuleMode.ToString().ToLower(); + } + } + + //enable tun mode + if (_config.TunModeItem.EnableTun) + { + var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml); + if (tun.IsNotEmpty()) + { + var tunContent = YamlUtils.FromYaml>(tun); + if (tunContent != null) + { + fileContent["tun"] = tunContent["tun"]; + } + } + } + + //Mixin try { - if (node == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - var addressFileName = node.Address; - if (addressFileName.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - if (!File.Exists(addressFileName)) - { - addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "1"; - return ret; - } - - var tagYamlStr1 = "!"; - var tagYamlStr2 = "__strn__"; - var tagYamlStr3 = "!!str"; - var txtFile = File.ReadAllText(addressFileName); - txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); - - //YAML anchors - if (txtFile.Contains("<<:") && txtFile.Contains('*') && txtFile.Contains('&')) - { - txtFile = YamlUtils.PreprocessYaml(txtFile); - } - - var fileContent = YamlUtils.FromYaml>(txtFile); - if (fileContent == null) - { - ret.Msg = ResUI.FailedConversionConfiguration; - return ret; - } - - //mixed-port - fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - //log-level - fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel); - - //external-controller - fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}"; - //allow-lan - if (_config.Inbound.First().AllowLANConn) - { - fileContent["allow-lan"] = "true"; - fileContent["bind-address"] = "*"; - } - else - { - fileContent["allow-lan"] = "false"; - } - - //ipv6 - fileContent["ipv6"] = _config.ClashUIItem.EnableIPv6; - - //mode - if (!fileContent.ContainsKey("mode")) - { - fileContent["mode"] = ERuleMode.Rule.ToString().ToLower(); - } - else - { - if (_config.ClashUIItem.RuleMode != ERuleMode.Unchanged) - { - fileContent["mode"] = _config.ClashUIItem.RuleMode.ToString().ToLower(); - } - } - - //enable tun mode - if (_config.TunModeItem.EnableTun) - { - var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml); - if (tun.IsNotEmpty()) - { - var tunContent = YamlUtils.FromYaml>(tun); - if (tunContent != null) - { - fileContent["tun"] = tunContent["tun"]; - } - } - } - - //Mixin - try - { - await MixinContent(fileContent, node); - } - catch (Exception ex) - { - Logging.SaveLog($"{_tag}-Mixin", ex); - } - - var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3); - await File.WriteAllTextAsync(fileName, txtFileNew); - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "2"; - return ret; - } - - ClashApiHandler.Instance.ProfileContent = fileContent; - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}"); - ret.Success = true; - return ret; + await MixinContent(fileContent, node); } catch (Exception ex) { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; + Logging.SaveLog($"{_tag}-Mixin", ex); + } + + var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3); + await File.WriteAllTextAsync(fileName, txtFileNew); + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedReadConfiguration + "2"; return ret; } + + ClashApiHandler.Instance.ProfileContent = fileContent; + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}"); + ret.Success = true; + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + private async Task MixinContent(Dictionary fileContent, ProfileItem node) + { + if (!_config.ClashUIItem.EnableMixinContent) + { + return; } - private async Task MixinContent(Dictionary fileContent, ProfileItem node) + var path = Utils.GetConfigPath(Global.ClashMixinConfigFileName); + if (!File.Exists(path)) { - if (!_config.ClashUIItem.EnableMixinContent) + var mixin = EmbedUtils.GetEmbedText(Global.ClashMixinYaml); + await File.AppendAllTextAsync(path, mixin); + } + + var txtFile = await File.ReadAllTextAsync(Utils.GetConfigPath(Global.ClashMixinConfigFileName)); + + var mixinContent = YamlUtils.FromYaml>(txtFile); + if (mixinContent == null) + { + return; + } + foreach (var item in mixinContent) + { + if (!_config.TunModeItem.EnableTun && item.Key == "tun") { - return; + continue; } - var path = Utils.GetConfigPath(Global.ClashMixinConfigFileName); - if (!File.Exists(path)) + if (item.Key.StartsWith("prepend-") + || item.Key.StartsWith("append-") + || item.Key.StartsWith("removed-")) { - var mixin = EmbedUtils.GetEmbedText(Global.ClashMixinYaml); - await File.AppendAllTextAsync(path, mixin); + ModifyContentMerge(fileContent, item.Key, item.Value); } - - var txtFile = await File.ReadAllTextAsync(Utils.GetConfigPath(Global.ClashMixinConfigFileName)); - - var mixinContent = YamlUtils.FromYaml>(txtFile); - if (mixinContent == null) + else { - return; + fileContent[item.Key] = item.Value; } - foreach (var item in mixinContent) - { - if (!_config.TunModeItem.EnableTun && item.Key == "tun") - { - continue; - } + } + return; + } - if (item.Key.StartsWith("prepend-") - || item.Key.StartsWith("append-") - || item.Key.StartsWith("removed-")) - { - ModifyContentMerge(fileContent, item.Key, item.Value); - } - else - { - fileContent[item.Key] = item.Value; - } + private void ModifyContentMerge(Dictionary fileContent, string key, object value) + { + var blPrepend = false; + var blRemoved = false; + if (key.StartsWith("prepend-")) + { + blPrepend = true; + key = key.Replace("prepend-", ""); + } + else if (key.StartsWith("append-")) + { + blPrepend = false; + key = key.Replace("append-", ""); + } + else if (key.StartsWith("removed-")) + { + blRemoved = true; + key = key.Replace("removed-", ""); + } + else + { + return; + } + + if (!blRemoved && !fileContent.ContainsKey(key)) + { + fileContent.Add(key, value); + return; + } + var lstOri = (List)fileContent[key]; + var lstValue = (List)value; + + if (blRemoved) + { + foreach (var item in lstValue) + { + lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString())); } return; } - private void ModifyContentMerge(Dictionary fileContent, string key, object value) + if (blPrepend) { - var blPrepend = false; - var blRemoved = false; - if (key.StartsWith("prepend-")) - { - blPrepend = true; - key = key.Replace("prepend-", ""); - } - else if (key.StartsWith("append-")) - { - blPrepend = false; - key = key.Replace("append-", ""); - } - else if (key.StartsWith("removed-")) - { - blRemoved = true; - key = key.Replace("removed-", ""); - } - else - { - return; - } - - if (!blRemoved && !fileContent.ContainsKey(key)) - { - fileContent.Add(key, value); - return; - } - var lstOri = (List)fileContent[key]; - var lstValue = (List)value; - - if (blRemoved) - { - foreach (var item in lstValue) - { - lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString())); - } - return; - } - - if (blPrepend) - { - lstValue.Reverse(); - lstValue.ForEach(item => lstOri.Insert(0, item)); - } - else - { - lstValue.ForEach(item => lstOri.Add(item)); - } + lstValue.Reverse(); + lstValue.ForEach(item => lstOri.Insert(0, item)); } - - private string GetLogLevel(string level) + else { - if (level == "none") - { - return "silent"; - } - else - { - return level; - } + lstValue.ForEach(item => lstOri.Add(item)); + } + } + + private string GetLogLevel(string level) + { + if (level == "none") + { + return "silent"; + } + else + { + return level; } } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 29ff8a42..34ae6f79 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -2,426 +2,88 @@ using System.Data; using System.Net; using System.Net.NetworkInformation; -namespace ServiceLib.Services.CoreConfig +namespace ServiceLib.Services.CoreConfig; + +public class CoreConfigSingboxService { - public class CoreConfigSingboxService + private Config _config; + private static readonly string _tag = "CoreConfigSingboxService"; + + public CoreConfigSingboxService(Config config) { - private Config _config; - private static readonly string _tag = "CoreConfigSingboxService"; + _config = config; + } - public CoreConfigSingboxService(Config config) + #region public gen function + + public async Task GenerateClientConfigContent(ProfileItem node) + { + var ret = new RetResult(); + try { - _config = config; - } - - #region public gen function - - public async Task GenerateClientConfigContent(ProfileItem node) - { - var ret = new RetResult(); - try + if (node == null + || node.Port <= 0) { - if (node == null - || node.Port <= 0) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) - { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - if (result.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(singboxConfig); - - await GenInbounds(singboxConfig); - - await GenOutbound(node, singboxConfig.outbounds.First()); - - await GenMoreOutbounds(node, singboxConfig); - - await GenRouting(singboxConfig); - - await GenDns(node, singboxConfig); - - await GenExperimental(singboxConfig); - - await ConvertGeo2Ruleset(singboxConfig); - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + ret.Msg = ResUI.CheckServerSettings; return ret; } - catch (Exception ex) + if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) { - Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } + + await GenLog(singboxConfig); + + await GenInbounds(singboxConfig); + + await GenOutbound(node, singboxConfig.outbounds.First()); + + await GenMoreOutbounds(node, singboxConfig); + + await GenRouting(singboxConfig); + + await GenDns(node, singboxConfig); + + await GenExperimental(singboxConfig); + + await ConvertGeo2Ruleset(singboxConfig); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(singboxConfig); + return ret; } - - public async Task GenerateClientSpeedtestConfig(List selecteds) + catch (Exception ex) { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - List lstIpEndPoints = new(); - List lstTcpConns = new(); - try - { - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - await GenLog(singboxConfig); - //GenDns(new(), singboxConfig); - singboxConfig.inbounds.Clear(); - singboxConfig.outbounds.RemoveAt(0); - - var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); - - foreach (var it in selecteds) - { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.Port <= 0) - { - continue; - } - var item = await AppHandler.Instance.GetProfileItem(it.IndexId); - if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) - { - if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) - { - continue; - } - } - - //find unused port - var port = initPort; - for (int k = initPort; k < Global.MaxPort; k++) - { - if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) - { - continue; - } - if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) - { - continue; - } - //found - port = k; - initPort = port + 1; - break; - } - - //Port In Used - if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) - { - continue; - } - it.Port = port; - it.AllowTest = true; - - //inbound - Inbound4Sbox inbound = new() - { - listen = Global.Loopback, - listen_port = port, - type = EInboundProtocol.mixed.ToString(), - }; - inbound.tag = inbound.type + inbound.listen_port.ToString(); - singboxConfig.inbounds.Add(inbound); - - //outbound - if (item is null) - { - continue; - } - if (item.ConfigType == EConfigType.Shadowsocks - && !Global.SsSecuritiesInSingbox.Contains(item.Security)) - { - continue; - } - if (item.ConfigType == EConfigType.VLESS - && !Global.Flows.Contains(item.Flow)) - { - continue; - } - if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan - && item.StreamSecurity == Global.StreamSecurityReality - && item.PublicKey.IsNullOrEmpty()) - { - continue; - } - - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); - singboxConfig.outbounds.Add(outbound); - - //rule - Rule4Sbox rule = new() - { - inbound = new List { inbound.tag }, - outbound = outbound.tag - }; - singboxConfig.route.rules.Add(rule); - } - - await GenDnsDomains(null, singboxConfig, null); - //var dnsServer = singboxConfig.dns?.servers.FirstOrDefault(); - //if (dnsServer != null) - //{ - // dnsServer.detour = singboxConfig.route.rules.LastOrDefault()?.outbound; - //} - //var dnsRule = singboxConfig.dns?.rules.Where(t => t.outbound != null).FirstOrDefault(); - //if (dnsRule != null) - //{ - // singboxConfig.dns.rules = []; - // singboxConfig.dns.rules.Add(dnsRule); - //} - - //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; } + } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public async Task GenerateClientSpeedtestConfig(List selecteds) + { + var ret = new RetResult(); + try { - var ret = new RetResult(); - try - { - if (node is not { Port: > 0 }) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) - { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - if (result.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(singboxConfig); - await GenOutbound(node, singboxConfig.outbounds.First()); - await GenMoreOutbounds(node, singboxConfig); - await GenDnsDomains(null, singboxConfig, null); - - singboxConfig.route.rules.Clear(); - singboxConfig.inbounds.Clear(); - singboxConfig.inbounds.Add(new() - { - tag = $"{EInboundProtocol.mixed}{port}", - listen = Global.Loopback, - listen_port = port, - type = EInboundProtocol.mixed.ToString(), - }); - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientMultipleLoadConfig(List selecteds) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - string txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(singboxConfig); - await GenInbounds(singboxConfig); - await GenRouting(singboxConfig); - await GenExperimental(singboxConfig); - singboxConfig.outbounds.RemoveAt(0); - - var tagProxy = new List(); - foreach (var it in selecteds) - { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.Port <= 0) - { - continue; - } - var item = await AppHandler.Instance.GetProfileItem(it.IndexId); - if (item is null) - { - continue; - } - if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) - { - if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) - { - continue; - } - } - if (item.ConfigType == EConfigType.Shadowsocks - && !Global.SsSecuritiesInSingbox.Contains(item.Security)) - { - continue; - } - if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow)) - { - continue; - } - - //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; - singboxConfig.outbounds.Insert(0, outbound); - tagProxy.Add(outbound.tag); - } - if (tagProxy.Count <= 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenDns(null, singboxConfig); - await ConvertGeo2Ruleset(singboxConfig); - - //add urltest outbound - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{Global.ProxyTag}-auto", - outbounds = tagProxy, - interrupt_exist_connections = false, - }; - singboxConfig.outbounds.Insert(0, outUrltest); - - //add selector outbound - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = Global.ProxyTag, - outbounds = JsonUtils.DeepCopy(tagProxy), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - singboxConfig.outbounds.Insert(0, outSelector); - - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) - { - var ret = new RetResult(); - if (node == null || fileName is null) + if (_config == null) { ret.Msg = ResUI.CheckServerSettings; return ret; @@ -429,1029 +91,1366 @@ namespace ServiceLib.Services.CoreConfig ret.Msg = ResUI.InitialConfiguration; - try + var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) { - if (node == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - string addressFileName = node.Address; - if (addressFileName.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - if (!File.Exists(addressFileName)) - { - addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "1"; - return ret; - } - - if (node.Address == Global.CoreMultipleLoadConfigFileName) - { - var txtFile = File.ReadAllText(addressFileName); - var singboxConfig = JsonUtils.Deserialize(txtFile); - if (singboxConfig == null) - { - File.Copy(addressFileName, fileName); - } - else - { - await GenInbounds(singboxConfig); - await GenExperimental(singboxConfig); - - var content = JsonUtils.Serialize(singboxConfig, true); - await File.WriteAllTextAsync(fileName, content); - } - } - else - { - File.Copy(addressFileName, fileName); - } - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "2"; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; + ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } - catch (Exception ex) + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) { - Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - } - - #endregion public gen function - - #region private gen function - - private async Task GenLog(SingboxConfig singboxConfig) - { + List lstIpEndPoints = new(); + List lstTcpConns = new(); try { - switch (_config.CoreBasicItem.Loglevel) - { - case "debug": - case "info": - case "error": - singboxConfig.log.level = _config.CoreBasicItem.Loglevel; - break; - - case "warning": - singboxConfig.log.level = "warn"; - break; - - default: - break; - } - if (_config.CoreBasicItem.Loglevel == Global.None) - { - singboxConfig.log.disabled = true; - } - if (_config.CoreBasicItem.LogEnabled) - { - var dtNow = DateTime.Now; - singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private async Task GenInbounds(SingboxConfig singboxConfig) - { - try - { - var listen = "::"; - singboxConfig.inbounds = []; - - if (!_config.TunModeItem.EnableTun - || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box)) - { - var inbound = new Inbound4Sbox() - { - type = EInboundProtocol.mixed.ToString(), - tag = EInboundProtocol.socks.ToString(), - listen = Global.Loopback, - }; - singboxConfig.inbounds.Add(inbound); - - inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - inbound.sniff = _config.Inbound.First().SniffingEnabled; - inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled; - inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing.DomainStrategy4Singbox.IsNotEmpty()) - { - inbound.domain_strategy = routing.DomainStrategy4Singbox; - } - - if (_config.Inbound.First().SecondLocalPortEnabled) - { - var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true); - singboxConfig.inbounds.Add(inbound2); - } - - if (_config.Inbound.First().AllowLANConn) - { - if (_config.Inbound.First().NewPort4LAN) - { - var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true); - inbound3.listen = listen; - singboxConfig.inbounds.Add(inbound3); - - //auth - if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) - { - inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } }; - } - } - else - { - inbound.listen = listen; - } - } - } - - if (_config.TunModeItem.EnableTun) - { - if (_config.TunModeItem.Mtu <= 0) - { - _config.TunModeItem.Mtu = Global.TunMtus.First(); - } - if (_config.TunModeItem.Stack.IsNullOrEmpty()) - { - _config.TunModeItem.Stack = Global.TunStacks.First(); - } - - var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; - tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun"; - tunInbound.mtu = _config.TunModeItem.Mtu; - tunInbound.strict_route = _config.TunModeItem.StrictRoute; - tunInbound.stack = _config.TunModeItem.Stack; - tunInbound.sniff = _config.Inbound.First().SniffingEnabled; - //tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled; - if (_config.TunModeItem.EnableIPv6Address == false) - { - tunInbound.address = ["172.18.0.1/30"]; - } - - singboxConfig.inbounds.Add(tunInbound); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) - { - var inbound = JsonUtils.DeepCopy(inItem); - inbound.tag = protocol.ToString(); - inbound.listen_port = inItem.listen_port + (int)protocol; - inbound.type = EInboundProtocol.mixed.ToString(); - return inbound; - } - - private async Task GenOutbound(ProfileItem node, Outbound4Sbox outbound) - { - try - { - outbound.server = node.Address; - outbound.server_port = node.Port; - outbound.type = Global.ProtocolTypes[node.ConfigType]; - - switch (node.ConfigType) - { - case EConfigType.VMess: - { - outbound.uuid = node.Id; - outbound.alter_id = node.AlterId; - if (Global.VmessSecurities.Contains(node.Security)) - { - outbound.security = node.Security; - } - else - { - outbound.security = Global.DefaultSecurity; - } - - await GenOutboundMux(node, outbound); - break; - } - case EConfigType.Shadowsocks: - { - outbound.method = AppHandler.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None; - outbound.password = node.Id; - - await GenOutboundMux(node, outbound); - break; - } - case EConfigType.SOCKS: - { - outbound.version = "5"; - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) - { - outbound.username = node.Security; - outbound.password = node.Id; - } - break; - } - case EConfigType.HTTP: - { - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) - { - outbound.username = node.Security; - outbound.password = node.Id; - } - break; - } - case EConfigType.VLESS: - { - outbound.uuid = node.Id; - - outbound.packet_encoding = "xudp"; - - if (node.Flow.IsNullOrEmpty()) - { - await GenOutboundMux(node, outbound); - } - else - { - outbound.flow = node.Flow; - } - break; - } - case EConfigType.Trojan: - { - outbound.password = node.Id; - - await GenOutboundMux(node, outbound); - break; - } - case EConfigType.Hysteria2: - { - outbound.password = node.Id; - - if (node.Path.IsNotEmpty()) - { - outbound.obfs = new() - { - type = "salamander", - password = node.Path.TrimEx(), - }; - } - - outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null; - outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null; - if (node.Ports.IsNotEmpty()) - { - outbound.server_port = null; - outbound.server_ports = node.Ports.Split(',') - .Where(p => p.Trim().IsNotEmpty()) - .Select(p => p.Replace('-', ':')) - .ToList(); - outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null; - } - - break; - } - case EConfigType.TUIC: - { - outbound.uuid = node.Id; - outbound.password = node.Security; - outbound.congestion_control = node.HeaderType; - break; - } - case EConfigType.WireGuard: - { - outbound.private_key = node.Id; - outbound.peer_public_key = node.PublicKey; - outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(); - outbound.local_address = Utils.String2List(node.RequestHost); - outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); - break; - } - } - - await GenOutboundTls(node, outbound); - - await GenOutboundTransport(node, outbound); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) - { - try - { - if (_config.CoreBasicItem.MuxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) - { - var mux = new Multiplex4Sbox() - { - enabled = true, - protocol = _config.Mux4SboxItem.Protocol, - max_connections = _config.Mux4SboxItem.MaxConnections, - padding = _config.Mux4SboxItem.Padding, - }; - outbound.multiplex = mux; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private async Task GenOutboundTls(ProfileItem node, Outbound4Sbox outbound) - { - try - { - if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity) - { - var server_name = string.Empty; - if (node.Sni.IsNotEmpty()) - { - server_name = node.Sni; - } - else if (node.RequestHost.IsNotEmpty()) - { - server_name = Utils.String2List(node.RequestHost)?.First(); - } - var tls = new Tls4Sbox() - { - enabled = true, - server_name = server_name, - insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), - }; - if (node.Fingerprint.IsNotEmpty()) - { - tls.utls = new Utls4Sbox() - { - enabled = true, - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint - }; - } - if (node.StreamSecurity == Global.StreamSecurityReality) - { - tls.reality = new Reality4Sbox() - { - enabled = true, - public_key = node.PublicKey, - short_id = node.ShortId - }; - tls.insecure = false; - } - outbound.tls = tls; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private async Task GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound) - { - try - { - var transport = new Transport4Sbox(); - - switch (node.GetNetwork()) - { - case nameof(ETransport.h2): - transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; - break; - - case nameof(ETransport.tcp): //http - if (node.HeaderType == Global.TcpHeaderHttp) - { - if (node.ConfigType == EConfigType.Shadowsocks) - { - outbound.plugin = "obfs-local"; - outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};"; - } - else - { - transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; - } - } - break; - - case nameof(ETransport.ws): - transport.type = nameof(ETransport.ws); - transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; - if (node.RequestHost.IsNotEmpty()) - { - transport.headers = new() - { - Host = node.RequestHost - }; - } - break; - - case nameof(ETransport.httpupgrade): - transport.type = nameof(ETransport.httpupgrade); - transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; - transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost; - - break; - - case nameof(ETransport.quic): - transport.type = nameof(ETransport.quic); - break; - - case nameof(ETransport.grpc): - transport.type = nameof(ETransport.grpc); - transport.service_name = node.Path; - transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s"); - transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s"); - transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream; - break; - - default: - break; - } - if (transport.type != null) - { - outbound.transport = transport; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private async Task GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) - { - if (node.Subid.IsNullOrEmpty()) - { - return 0; - } - try - { - var subItem = await AppHandler.Instance.GetSubItem(node.Subid); - if (subItem is null) - { - return 0; - } - - //current proxy - var outbound = singboxConfig.outbounds.First(); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - - //Previous proxy - var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; - singboxConfig.outbounds.Add(prevOutbound); - - outbound.detour = prevOutbound.tag; - } - - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) - { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; - singboxConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.detour = outbound.tag; - } + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; - } + await GenLog(singboxConfig); + //GenDns(new(), singboxConfig); + singboxConfig.inbounds.Clear(); + singboxConfig.outbounds.RemoveAt(0); - private async Task GenRouting(SingboxConfig singboxConfig) - { - try + var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); + + foreach (var it in selecteds) { - var dnsOutbound = "dns_out"; - if (!_config.Inbound.First().SniffingEnabled) - { - singboxConfig.route.rules.Add(new() - { - port = [53], - network = ["udp"], - outbound = dnsOutbound - }); - } - - singboxConfig.route.rules.Insert(0, new() - { - outbound = Global.DirectTag, - clash_mode = ERuleMode.Direct.ToString() - }); - singboxConfig.route.rules.Insert(0, new() - { - outbound = Global.ProxyTag, - clash_mode = ERuleMode.Global.ToString() - }); - - if (_config.TunModeItem.EnableTun) - { - singboxConfig.route.auto_detect_interface = true; - - var tunRules = JsonUtils.Deserialize>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); - if (tunRules != null) - { - singboxConfig.route.rules.AddRange(tunRules); - } - - GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe); - singboxConfig.route.rules.Add(new() - { - port = new() { 53 }, - outbound = dnsOutbound, - process_name = lstDnsExe - }); - - singboxConfig.route.rules.Add(new() - { - outbound = Global.DirectTag, - process_name = lstDirectExe - }); - } - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing != null) - { - var rules = JsonUtils.Deserialize>(routing.RuleSet); - foreach (var item in rules ?? []) - { - if (item.Enabled) - { - await GenRoutingUserRule(item, singboxConfig.route.rules); - } - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private void GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe) - { - lstDnsExe = new(); - lstDirectExe = new(); - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); - foreach (var it in coreInfo) - { - if (it.CoreType == ECoreType.v2rayN) + if (it.ConfigType == EConfigType.Custom) { continue; } - foreach (var it2 in it.CoreExes) + if (it.Port <= 0) { - if (!lstDnsExe.Contains(it2) && it.CoreType != ECoreType.sing_box) + continue; + } + var item = await AppHandler.Instance.GetProfileItem(it.IndexId); + if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) + { + if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) { - lstDnsExe.Add($"{it2}.exe"); - } - - if (!lstDirectExe.Contains(it2)) - { - lstDirectExe.Add($"{it2}.exe"); + continue; } } - } - } - private async Task GenRoutingUserRule(RulesItem item, List rules) - { - try - { - if (item == null) + //find unused port + var port = initPort; + for (int k = initPort; k < Global.MaxPort; k++) { - return 0; + if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) + { + continue; + } + if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) + { + continue; + } + //found + port = k; + initPort = port + 1; + break; } - var rule = new Rule4Sbox() + //Port In Used + if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) { - outbound = item.OutboundTag, + continue; + } + it.Port = port; + it.AllowTest = true; + + //inbound + Inbound4Sbox inbound = new() + { + listen = Global.Loopback, + listen_port = port, + type = EInboundProtocol.mixed.ToString(), }; + inbound.tag = inbound.type + inbound.listen_port.ToString(); + singboxConfig.inbounds.Add(inbound); - if (item.Port.IsNotEmpty()) + //outbound + if (item is null) { - if (item.Port.Contains("-")) - { - rule.port_range = new List { item.Port.Replace("-", ":") }; - } - else - { - rule.port = new List { item.Port.ToInt() }; - } + continue; } - if (item.Network.IsNotEmpty()) + if (item.ConfigType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInSingbox.Contains(item.Security)) { - rule.network = Utils.String2List(item.Network); + continue; } - if (item.Protocol?.Count > 0) + if (item.ConfigType == EConfigType.VLESS + && !Global.Flows.Contains(item.Flow)) { - rule.protocol = item.Protocol; + continue; } - if (item.InboundTag?.Count >= 0) + if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan + && item.StreamSecurity == Global.StreamSecurityReality + && item.PublicKey.IsNullOrEmpty()) { - rule.inbound = item.InboundTag; - } - var rule1 = JsonUtils.DeepCopy(rule); - var rule2 = JsonUtils.DeepCopy(rule); - var rule3 = JsonUtils.DeepCopy(rule); - - var hasDomainIp = false; - if (item.Domain?.Count > 0) - { - var countDomain = 0; - foreach (var it in item.Domain) - { - if (ParseV2Domain(it, rule1)) - countDomain++; - } - if (countDomain > 0) - { - rules.Add(rule1); - hasDomainIp = true; - } + continue; } - if (item.Ip?.Count > 0) - { - var countIp = 0; - foreach (var it in item.Ip) - { - if (ParseV2Address(it, rule2)) - countIp++; - } - if (countIp > 0) - { - rules.Add(rule2); - hasDomainIp = true; - } - } + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(item, outbound); + outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); + singboxConfig.outbounds.Add(outbound); - if (_config.TunModeItem.EnableTun && item.Process?.Count > 0) + //rule + Rule4Sbox rule = new() { - rule3.process_name = item.Process; - rules.Add(rule3); - hasDomainIp = true; - } - - if (!hasDomainIp - && (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null)) - { - rules.Add(rule); - } + inbound = new List { inbound.tag }, + outbound = outbound.tag + }; + singboxConfig.route.rules.Add(rule); } - catch (Exception ex) + + await GenDnsDomains(null, singboxConfig, null); + //var dnsServer = singboxConfig.dns?.servers.FirstOrDefault(); + //if (dnsServer != null) + //{ + // dnsServer.detour = singboxConfig.route.rules.LastOrDefault()?.outbound; + //} + //var dnsRule = singboxConfig.dns?.rules.Where(t => t.outbound != null).FirstOrDefault(); + //if (dnsRule != null) + //{ + // singboxConfig.dns.rules = []; + // singboxConfig.dns.rules.Add(dnsRule); + //} + + //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); + ret.Success = true; + ret.Data = JsonUtils.Serialize(singboxConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node is not { Port: > 0 }) { - Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.CheckServerSettings; + return ret; } - return await Task.FromResult(0); + if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(singboxConfig); + await GenOutbound(node, singboxConfig.outbounds.First()); + await GenMoreOutbounds(node, singboxConfig); + await GenDnsDomains(null, singboxConfig, null); + + singboxConfig.route.rules.Clear(); + singboxConfig.inbounds.Clear(); + singboxConfig.inbounds.Add(new() + { + tag = $"{EInboundProtocol.mixed}{port}", + listen = Global.Loopback, + listen_port = port, + type = EInboundProtocol.mixed.ToString(), + }); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(singboxConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientMultipleLoadConfig(List selecteds) + { + var ret = new RetResult(); + try + { + if (_config == null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + string txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(singboxConfig); + await GenInbounds(singboxConfig); + await GenRouting(singboxConfig); + await GenExperimental(singboxConfig); + singboxConfig.outbounds.RemoveAt(0); + + var tagProxy = new List(); + foreach (var it in selecteds) + { + if (it.ConfigType == EConfigType.Custom) + { + continue; + } + if (it.Port <= 0) + { + continue; + } + var item = await AppHandler.Instance.GetProfileItem(it.IndexId); + if (item is null) + { + continue; + } + if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) + { + if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) + { + continue; + } + } + if (item.ConfigType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInSingbox.Contains(item.Security)) + { + continue; + } + if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow)) + { + continue; + } + + //outbound + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(item, outbound); + outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; + singboxConfig.outbounds.Insert(0, outbound); + tagProxy.Add(outbound.tag); + } + if (tagProxy.Count <= 0) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenDns(null, singboxConfig); + await ConvertGeo2Ruleset(singboxConfig); + + //add urltest outbound + var outUrltest = new Outbound4Sbox + { + type = "urltest", + tag = $"{Global.ProxyTag}-auto", + outbounds = tagProxy, + interrupt_exist_connections = false, + }; + singboxConfig.outbounds.Insert(0, outUrltest); + + //add selector outbound + var outSelector = new Outbound4Sbox + { + type = "selector", + tag = Global.ProxyTag, + outbounds = JsonUtils.DeepCopy(tagProxy), + interrupt_exist_connections = false, + }; + outSelector.outbounds.Insert(0, outUrltest.tag); + singboxConfig.outbounds.Insert(0, outSelector); + + ret.Success = true; + ret.Data = JsonUtils.Serialize(singboxConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + if (node == null || fileName is null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; } - private bool ParseV2Domain(string domain, Rule4Sbox rule) - { - if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) - { - return false; - } - else if (domain.StartsWith("geosite:")) - { - rule.geosite ??= []; - rule.geosite?.Add(domain.Substring(8)); - } - else if (domain.StartsWith("regexp:")) - { - rule.domain_regex ??= []; - rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7)); - } - else if (domain.StartsWith("domain:")) - { - rule.domain ??= []; - rule.domain_suffix ??= []; - rule.domain?.Add(domain.Substring(7)); - rule.domain_suffix?.Add("." + domain.Substring(7)); - } - else if (domain.StartsWith("full:")) - { - rule.domain ??= []; - rule.domain?.Add(domain.Substring(5)); - } - else if (domain.StartsWith("keyword:")) - { - rule.domain_keyword ??= []; - rule.domain_keyword?.Add(domain.Substring(8)); - } - else - { - rule.domain_keyword ??= []; - rule.domain_keyword?.Add(domain); - } - return true; - } + ret.Msg = ResUI.InitialConfiguration; - private bool ParseV2Address(string address, Rule4Sbox rule) + try { - if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) + if (node == null) { - return false; + ret.Msg = ResUI.CheckServerSettings; + return ret; } - else if (address.StartsWith("geoip:!")) - { - return false; - } - else if (address.Equals("geoip:private")) - { - rule.ip_is_private = true; - } - else if (address.StartsWith("geoip:")) - { - if (rule.geoip is null) - { rule.geoip = new(); } - rule.geoip?.Add(address.Substring(6)); - } - else - { - if (rule.ip_cidr is null) - { rule.ip_cidr = new(); } - rule.ip_cidr?.Add(address); - } - return true; - } - private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) - { - try + if (File.Exists(fileName)) { - var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); - var strDNS = string.Empty; - if (_config.TunModeItem.EnableTun) + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (addressFileName.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + if (!File.Exists(addressFileName)) + { + addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedReadConfiguration + "1"; + return ret; + } + + if (node.Address == Global.CoreMultipleLoadConfigFileName) + { + var txtFile = File.ReadAllText(addressFileName); + var singboxConfig = JsonUtils.Deserialize(txtFile); + if (singboxConfig == null) { - strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; + File.Copy(addressFileName, fileName); } else { - strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS; + await GenInbounds(singboxConfig); + await GenExperimental(singboxConfig); + + var content = JsonUtils.Serialize(singboxConfig, true); + await File.WriteAllTextAsync(fileName, content); } - - var dns4Sbox = JsonUtils.Deserialize(strDNS); - if (dns4Sbox is null) - { - return 0; - } - singboxConfig.dns = dns4Sbox; - - await GenDnsDomains(node, singboxConfig, item); } - catch (Exception ex) + else { - Logging.SaveLog(_tag, ex); + File.Copy(addressFileName, fileName); } - return 0; + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedReadConfiguration + "2"; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return ret; } - - private async Task GenDnsDomains(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem) + catch (Exception ex) { - var dns4Sbox = singboxConfig.dns ?? new(); - dns4Sbox.servers ??= []; - dns4Sbox.rules ??= []; - - var tag = "local_local"; - dns4Sbox.servers.Add(new() - { - tag = tag, - address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, - detour = Global.DirectTag, - strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom, - }); - dns4Sbox.rules.Insert(0, new() - { - server = tag, - clash_mode = ERuleMode.Direct.ToString() - }); - dns4Sbox.rules.Insert(0, new() - { - server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", - clash_mode = ERuleMode.Global.ToString() - }); - - var lstDomain = singboxConfig.outbounds - .Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server)) - .Select(t => t.server) - .Distinct() - .ToList(); - if (lstDomain != null && lstDomain.Count > 0) - { - dns4Sbox.rules.Insert(0, new() - { - server = tag, - domain = lstDomain - }); - } - - //Tun2SocksAddress - if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni)) - { - dns4Sbox.rules.Insert(0, new() - { - server = tag, - domain = [node?.Sni] - }); - } - - singboxConfig.dns = dns4Sbox; - return await Task.FromResult(0); + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; } + } - private async Task GenExperimental(SingboxConfig singboxConfig) + #endregion public gen function + + #region private gen function + + private async Task GenLog(SingboxConfig singboxConfig) + { + try { - //if (_config.guiItem.enableStatistics) + switch (_config.CoreBasicItem.Loglevel) { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.clash_api = new Clash_Api4Sbox() + case "debug": + case "info": + case "error": + singboxConfig.log.level = _config.CoreBasicItem.Loglevel; + break; + + case "warning": + singboxConfig.log.level = "warn"; + break; + + default: + break; + } + if (_config.CoreBasicItem.Loglevel == Global.None) + { + singboxConfig.log.disabled = true; + } + if (_config.CoreBasicItem.LogEnabled) + { + var dtNow = DateTime.Now; + singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenInbounds(SingboxConfig singboxConfig) + { + try + { + var listen = "::"; + singboxConfig.inbounds = []; + + if (!_config.TunModeItem.EnableTun + || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box)) + { + var inbound = new Inbound4Sbox() { - external_controller = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}", + type = EInboundProtocol.mixed.ToString(), + tag = EInboundProtocol.socks.ToString(), + listen = Global.Loopback, }; - } + singboxConfig.inbounds.Add(inbound); - if (_config.CoreBasicItem.EnableCacheFile4Sbox) - { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.cache_file = new CacheFile4Sbox() + inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); + inbound.sniff = _config.Inbound.First().SniffingEnabled; + inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled; + inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing.DomainStrategy4Singbox.IsNotEmpty()) { - enabled = true, - path = Utils.GetBinPath("cache.db") - }; - } - - return await Task.FromResult(0); - } - - private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) - { - static void AddRuleSets(List ruleSets, List? rule_set) - { - if (rule_set != null) - ruleSets.AddRange(rule_set); - } - var geosite = "geosite"; - var geoip = "geoip"; - var ruleSets = new List(); - - //convert route geosite & geoip to ruleset - foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) - { - rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); - rule.geosite = null; - AddRuleSets(ruleSets, rule.rule_set); - } - foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) - { - rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); - rule.geoip = null; - AddRuleSets(ruleSets, rule.rule_set); - } - - //convert dns geosite & geoip to ruleset - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) - { - rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); - rule.geosite = null; - } - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) - { - rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); - rule.geoip = null; - } - foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) - { - AddRuleSets(ruleSets, dnsRule.rule_set); - } - //rules in rules - foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) - { - foreach (var item2 in item ?? []) - { - AddRuleSets(ruleSets, item2.rule_set); + inbound.domain_strategy = routing.DomainStrategy4Singbox; } - } - //load custom ruleset file - List customRulesets = []; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) - { - var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); - if (result.IsNotEmpty()) + if (_config.Inbound.First().SecondLocalPortEnabled) { - customRulesets = (JsonUtils.Deserialize>(result) ?? []) - .Where(t => t.tag != null) - .Where(t => t.type != null) - .Where(t => t.format != null) - .ToList(); + var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true); + singboxConfig.inbounds.Add(inbound2); } - } - //Local srs files address - var localSrss = Utils.GetBinPath("srss"); - - //Add ruleset srs - singboxConfig.route.rule_set = []; - foreach (var item in new HashSet(ruleSets)) - { - if (item.IsNullOrEmpty()) - { continue; } - var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item)); - if (customRuleset is null) + if (_config.Inbound.First().AllowLANConn) { - var pathSrs = Path.Combine(localSrss, $"{item}.srs"); - if (File.Exists(pathSrs)) + if (_config.Inbound.First().NewPort4LAN) { - customRuleset = new() + var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true); + inbound3.listen = listen; + singboxConfig.inbounds.Add(inbound3); + + //auth + if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { - type = "local", - format = "binary", - tag = item, - path = pathSrs - }; + inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } }; + } } else { - var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) - ? Global.SingboxRulesetUrl - : _config.ConstItem.SrsSourceUrl; - - customRuleset = new() - { - type = "remote", - format = "binary", - tag = item, - url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item), - download_detour = Global.ProxyTag - }; + inbound.listen = listen; } } - singboxConfig.route.rule_set.Add(customRuleset); } + if (_config.TunModeItem.EnableTun) + { + if (_config.TunModeItem.Mtu <= 0) + { + _config.TunModeItem.Mtu = Global.TunMtus.First(); + } + if (_config.TunModeItem.Stack.IsNullOrEmpty()) + { + _config.TunModeItem.Stack = Global.TunStacks.First(); + } + + var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; + tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun"; + tunInbound.mtu = _config.TunModeItem.Mtu; + tunInbound.strict_route = _config.TunModeItem.StrictRoute; + tunInbound.stack = _config.TunModeItem.Stack; + tunInbound.sniff = _config.Inbound.First().SniffingEnabled; + //tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled; + if (_config.TunModeItem.EnableIPv6Address == false) + { + tunInbound.address = ["172.18.0.1/30"]; + } + + singboxConfig.inbounds.Add(tunInbound); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) + { + var inbound = JsonUtils.DeepCopy(inItem); + inbound.tag = protocol.ToString(); + inbound.listen_port = inItem.listen_port + (int)protocol; + inbound.type = EInboundProtocol.mixed.ToString(); + return inbound; + } + + private async Task GenOutbound(ProfileItem node, Outbound4Sbox outbound) + { + try + { + outbound.server = node.Address; + outbound.server_port = node.Port; + outbound.type = Global.ProtocolTypes[node.ConfigType]; + + switch (node.ConfigType) + { + case EConfigType.VMess: + { + outbound.uuid = node.Id; + outbound.alter_id = node.AlterId; + if (Global.VmessSecurities.Contains(node.Security)) + { + outbound.security = node.Security; + } + else + { + outbound.security = Global.DefaultSecurity; + } + + await GenOutboundMux(node, outbound); + break; + } + case EConfigType.Shadowsocks: + { + outbound.method = AppHandler.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None; + outbound.password = node.Id; + + await GenOutboundMux(node, outbound); + break; + } + case EConfigType.SOCKS: + { + outbound.version = "5"; + if (node.Security.IsNotEmpty() + && node.Id.IsNotEmpty()) + { + outbound.username = node.Security; + outbound.password = node.Id; + } + break; + } + case EConfigType.HTTP: + { + if (node.Security.IsNotEmpty() + && node.Id.IsNotEmpty()) + { + outbound.username = node.Security; + outbound.password = node.Id; + } + break; + } + case EConfigType.VLESS: + { + outbound.uuid = node.Id; + + outbound.packet_encoding = "xudp"; + + if (node.Flow.IsNullOrEmpty()) + { + await GenOutboundMux(node, outbound); + } + else + { + outbound.flow = node.Flow; + } + break; + } + case EConfigType.Trojan: + { + outbound.password = node.Id; + + await GenOutboundMux(node, outbound); + break; + } + case EConfigType.Hysteria2: + { + outbound.password = node.Id; + + if (node.Path.IsNotEmpty()) + { + outbound.obfs = new() + { + type = "salamander", + password = node.Path.TrimEx(), + }; + } + + outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null; + outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null; + if (node.Ports.IsNotEmpty()) + { + outbound.server_port = null; + outbound.server_ports = node.Ports.Split(',') + .Where(p => p.Trim().IsNotEmpty()) + .Select(p => p.Replace('-', ':')) + .ToList(); + outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null; + } + + break; + } + case EConfigType.TUIC: + { + outbound.uuid = node.Id; + outbound.password = node.Security; + outbound.congestion_control = node.HeaderType; + break; + } + case EConfigType.WireGuard: + { + outbound.private_key = node.Id; + outbound.peer_public_key = node.PublicKey; + outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(); + outbound.local_address = Utils.String2List(node.RequestHost); + outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); + break; + } + } + + await GenOutboundTls(node, outbound); + + await GenOutboundTransport(node, outbound); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) + { + try + { + if (_config.CoreBasicItem.MuxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) + { + var mux = new Multiplex4Sbox() + { + enabled = true, + protocol = _config.Mux4SboxItem.Protocol, + max_connections = _config.Mux4SboxItem.MaxConnections, + padding = _config.Mux4SboxItem.Padding, + }; + outbound.multiplex = mux; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenOutboundTls(ProfileItem node, Outbound4Sbox outbound) + { + try + { + if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity) + { + var server_name = string.Empty; + if (node.Sni.IsNotEmpty()) + { + server_name = node.Sni; + } + else if (node.RequestHost.IsNotEmpty()) + { + server_name = Utils.String2List(node.RequestHost)?.First(); + } + var tls = new Tls4Sbox() + { + enabled = true, + server_name = server_name, + insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), + alpn = node.GetAlpn(), + }; + if (node.Fingerprint.IsNotEmpty()) + { + tls.utls = new Utls4Sbox() + { + enabled = true, + fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint + }; + } + if (node.StreamSecurity == Global.StreamSecurityReality) + { + tls.reality = new Reality4Sbox() + { + enabled = true, + public_key = node.PublicKey, + short_id = node.ShortId + }; + tls.insecure = false; + } + outbound.tls = tls; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound) + { + try + { + var transport = new Transport4Sbox(); + + switch (node.GetNetwork()) + { + case nameof(ETransport.h2): + transport.type = nameof(ETransport.http); + transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); + transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; + break; + + case nameof(ETransport.tcp): //http + if (node.HeaderType == Global.TcpHeaderHttp) + { + if (node.ConfigType == EConfigType.Shadowsocks) + { + outbound.plugin = "obfs-local"; + outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};"; + } + else + { + transport.type = nameof(ETransport.http); + transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); + transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; + } + } + break; + + case nameof(ETransport.ws): + transport.type = nameof(ETransport.ws); + transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; + if (node.RequestHost.IsNotEmpty()) + { + transport.headers = new() + { + Host = node.RequestHost + }; + } + break; + + case nameof(ETransport.httpupgrade): + transport.type = nameof(ETransport.httpupgrade); + transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; + transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost; + + break; + + case nameof(ETransport.quic): + transport.type = nameof(ETransport.quic); + break; + + case nameof(ETransport.grpc): + transport.type = nameof(ETransport.grpc); + transport.service_name = node.Path; + transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s"); + transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s"); + transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream; + break; + + default: + break; + } + if (transport.type != null) + { + outbound.transport = transport; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) + { + if (node.Subid.IsNullOrEmpty()) + { return 0; } + try + { + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + if (subItem is null) + { + return 0; + } - #endregion private gen function + //current proxy + var outbound = singboxConfig.outbounds.First(); + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + + //Previous proxy + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevOutbound.tag = $"{Global.ProxyTag}2"; + singboxConfig.outbounds.Add(prevOutbound); + + outbound.detour = prevOutbound.tag; + } + + //Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom) + { + var nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = Global.ProxyTag; + singboxConfig.outbounds.Insert(0, nextOutbound); + + outbound.tag = $"{Global.ProxyTag}1"; + nextOutbound.detour = outbound.tag; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return 0; } + + private async Task GenRouting(SingboxConfig singboxConfig) + { + try + { + var dnsOutbound = "dns_out"; + if (!_config.Inbound.First().SniffingEnabled) + { + singboxConfig.route.rules.Add(new() + { + port = [53], + network = ["udp"], + outbound = dnsOutbound + }); + } + + singboxConfig.route.rules.Insert(0, new() + { + outbound = Global.DirectTag, + clash_mode = ERuleMode.Direct.ToString() + }); + singboxConfig.route.rules.Insert(0, new() + { + outbound = Global.ProxyTag, + clash_mode = ERuleMode.Global.ToString() + }); + + if (_config.TunModeItem.EnableTun) + { + singboxConfig.route.auto_detect_interface = true; + + var tunRules = JsonUtils.Deserialize>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); + if (tunRules != null) + { + singboxConfig.route.rules.AddRange(tunRules); + } + + GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe); + singboxConfig.route.rules.Add(new() + { + port = new() { 53 }, + outbound = dnsOutbound, + process_name = lstDnsExe + }); + + singboxConfig.route.rules.Add(new() + { + outbound = Global.DirectTag, + process_name = lstDirectExe + }); + } + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing != null) + { + var rules = JsonUtils.Deserialize>(routing.RuleSet); + foreach (var item in rules ?? []) + { + if (item.Enabled) + { + await GenRoutingUserRule(item, singboxConfig.route.rules); + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private void GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe) + { + lstDnsExe = new(); + lstDirectExe = new(); + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(); + foreach (var it in coreInfo) + { + if (it.CoreType == ECoreType.v2rayN) + { + continue; + } + foreach (var it2 in it.CoreExes) + { + if (!lstDnsExe.Contains(it2) && it.CoreType != ECoreType.sing_box) + { + lstDnsExe.Add($"{it2}.exe"); + } + + if (!lstDirectExe.Contains(it2)) + { + lstDirectExe.Add($"{it2}.exe"); + } + } + } + } + + private async Task GenRoutingUserRule(RulesItem item, List rules) + { + try + { + if (item == null) + { + return 0; + } + + var rule = new Rule4Sbox() + { + outbound = item.OutboundTag, + }; + + if (item.Port.IsNotEmpty()) + { + if (item.Port.Contains("-")) + { + rule.port_range = new List { item.Port.Replace("-", ":") }; + } + else + { + rule.port = new List { item.Port.ToInt() }; + } + } + if (item.Network.IsNotEmpty()) + { + rule.network = Utils.String2List(item.Network); + } + if (item.Protocol?.Count > 0) + { + rule.protocol = item.Protocol; + } + if (item.InboundTag?.Count >= 0) + { + rule.inbound = item.InboundTag; + } + var rule1 = JsonUtils.DeepCopy(rule); + var rule2 = JsonUtils.DeepCopy(rule); + var rule3 = JsonUtils.DeepCopy(rule); + + var hasDomainIp = false; + if (item.Domain?.Count > 0) + { + var countDomain = 0; + foreach (var it in item.Domain) + { + if (ParseV2Domain(it, rule1)) + countDomain++; + } + if (countDomain > 0) + { + rules.Add(rule1); + hasDomainIp = true; + } + } + + if (item.Ip?.Count > 0) + { + var countIp = 0; + foreach (var it in item.Ip) + { + if (ParseV2Address(it, rule2)) + countIp++; + } + if (countIp > 0) + { + rules.Add(rule2); + hasDomainIp = true; + } + } + + if (_config.TunModeItem.EnableTun && item.Process?.Count > 0) + { + rule3.process_name = item.Process; + rules.Add(rule3); + hasDomainIp = true; + } + + if (!hasDomainIp + && (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null)) + { + rules.Add(rule); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private bool ParseV2Domain(string domain, Rule4Sbox rule) + { + if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) + { + return false; + } + else if (domain.StartsWith("geosite:")) + { + rule.geosite ??= []; + rule.geosite?.Add(domain.Substring(8)); + } + else if (domain.StartsWith("regexp:")) + { + rule.domain_regex ??= []; + rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7)); + } + else if (domain.StartsWith("domain:")) + { + rule.domain ??= []; + rule.domain_suffix ??= []; + rule.domain?.Add(domain.Substring(7)); + rule.domain_suffix?.Add("." + domain.Substring(7)); + } + else if (domain.StartsWith("full:")) + { + rule.domain ??= []; + rule.domain?.Add(domain.Substring(5)); + } + else if (domain.StartsWith("keyword:")) + { + rule.domain_keyword ??= []; + rule.domain_keyword?.Add(domain.Substring(8)); + } + else + { + rule.domain_keyword ??= []; + rule.domain_keyword?.Add(domain); + } + return true; + } + + private bool ParseV2Address(string address, Rule4Sbox rule) + { + if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) + { + return false; + } + else if (address.StartsWith("geoip:!")) + { + return false; + } + else if (address.Equals("geoip:private")) + { + rule.ip_is_private = true; + } + else if (address.StartsWith("geoip:")) + { + if (rule.geoip is null) + { rule.geoip = new(); } + rule.geoip?.Add(address.Substring(6)); + } + else + { + if (rule.ip_cidr is null) + { rule.ip_cidr = new(); } + rule.ip_cidr?.Add(address); + } + return true; + } + + private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) + { + try + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + var strDNS = string.Empty; + if (_config.TunModeItem.EnableTun) + { + strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; + } + else + { + strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS; + } + + var dns4Sbox = JsonUtils.Deserialize(strDNS); + if (dns4Sbox is null) + { + return 0; + } + singboxConfig.dns = dns4Sbox; + + await GenDnsDomains(node, singboxConfig, item); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsDomains(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + + var tag = "local_local"; + dns4Sbox.servers.Add(new() + { + tag = tag, + address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, + detour = Global.DirectTag, + strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom, + }); + dns4Sbox.rules.Insert(0, new() + { + server = tag, + clash_mode = ERuleMode.Direct.ToString() + }); + dns4Sbox.rules.Insert(0, new() + { + server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", + clash_mode = ERuleMode.Global.ToString() + }); + + var lstDomain = singboxConfig.outbounds + .Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server)) + .Select(t => t.server) + .Distinct() + .ToList(); + if (lstDomain != null && lstDomain.Count > 0) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = lstDomain + }); + } + + //Tun2SocksAddress + if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni)) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = [node?.Sni] + }); + } + + singboxConfig.dns = dns4Sbox; + return await Task.FromResult(0); + } + + private async Task GenExperimental(SingboxConfig singboxConfig) + { + //if (_config.guiItem.enableStatistics) + { + singboxConfig.experimental ??= new Experimental4Sbox(); + singboxConfig.experimental.clash_api = new Clash_Api4Sbox() + { + external_controller = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}", + }; + } + + if (_config.CoreBasicItem.EnableCacheFile4Sbox) + { + singboxConfig.experimental ??= new Experimental4Sbox(); + singboxConfig.experimental.cache_file = new CacheFile4Sbox() + { + enabled = true, + path = Utils.GetBinPath("cache.db") + }; + } + + return await Task.FromResult(0); + } + + private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) + { + static void AddRuleSets(List ruleSets, List? rule_set) + { + if (rule_set != null) + ruleSets.AddRange(rule_set); + } + var geosite = "geosite"; + var geoip = "geoip"; + var ruleSets = new List(); + + //convert route geosite & geoip to ruleset + foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.geosite = null; + AddRuleSets(ruleSets, rule.rule_set); + } + foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.geoip = null; + AddRuleSets(ruleSets, rule.rule_set); + } + + //convert dns geosite & geoip to ruleset + foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.geosite = null; + } + foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.geoip = null; + } + foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) + { + AddRuleSets(ruleSets, dnsRule.rule_set); + } + //rules in rules + foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) + { + foreach (var item2 in item ?? []) + { + AddRuleSets(ruleSets, item2.rule_set); + } + } + + //load custom ruleset file + List customRulesets = []; + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) + { + var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); + if (result.IsNotEmpty()) + { + customRulesets = (JsonUtils.Deserialize>(result) ?? []) + .Where(t => t.tag != null) + .Where(t => t.type != null) + .Where(t => t.format != null) + .ToList(); + } + } + + //Local srs files address + var localSrss = Utils.GetBinPath("srss"); + + //Add ruleset srs + singboxConfig.route.rule_set = []; + foreach (var item in new HashSet(ruleSets)) + { + if (item.IsNullOrEmpty()) + { continue; } + var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item)); + if (customRuleset is null) + { + var pathSrs = Path.Combine(localSrss, $"{item}.srs"); + if (File.Exists(pathSrs)) + { + customRuleset = new() + { + type = "local", + format = "binary", + tag = item, + path = pathSrs + }; + } + else + { + var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) + ? Global.SingboxRulesetUrl + : _config.ConstItem.SrsSourceUrl; + + customRuleset = new() + { + type = "remote", + format = "binary", + tag = item, + url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item), + download_detour = Global.ProxyTag + }; + } + } + singboxConfig.route.rule_set.Add(customRuleset); + } + + return 0; + } + + #endregion private gen function } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 4c2cbde6..c09d325d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -2,1346 +2,1345 @@ using System.Net; using System.Net.NetworkInformation; using System.Text.Json.Nodes; -namespace ServiceLib.Services.CoreConfig +namespace ServiceLib.Services.CoreConfig; + +public class CoreConfigV2rayService { - public class CoreConfigV2rayService + private Config _config; + private static readonly string _tag = "CoreConfigV2rayService"; + + public CoreConfigV2rayService(Config config) { - private Config _config; - private static readonly string _tag = "CoreConfigV2rayService"; + _config = config; + } - public CoreConfigV2rayService(Config config) + #region public gen function + + public async Task GenerateClientConfigContent(ProfileItem node) + { + var ret = new RetResult(); + try { - _config = config; + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(v2rayConfig); + + await GenInbounds(v2rayConfig); + + await GenRouting(v2rayConfig); + + await GenOutbound(node, v2rayConfig.outbounds.First()); + + await GenMoreOutbounds(node, v2rayConfig); + + await GenDns(node, v2rayConfig); + + await GenStatistic(v2rayConfig); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(v2rayConfig); + return ret; } - - #region public gen function - - public async Task GenerateClientConfigContent(ProfileItem node) + catch (Exception ex) { - var ret = new RetResult(); + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) + { + var ret = new RetResult(); + + try + { + if (_config == null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(v2rayConfig); + await GenInbounds(v2rayConfig); + await GenRouting(v2rayConfig); + await GenDns(null, v2rayConfig); + await GenStatistic(v2rayConfig); + v2rayConfig.outbounds.RemoveAt(0); + + var tagProxy = new List(); + foreach (var it in selecteds) + { + if (it.ConfigType == EConfigType.Custom) + { + continue; + } + if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard) + { + continue; + } + if (it.Port <= 0) + { + continue; + } + var item = await AppHandler.Instance.GetProfileItem(it.IndexId); + if (item is null) + { + continue; + } + if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) + { + if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) + { + continue; + } + } + if (item.ConfigType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInSingbox.Contains(item.Security)) + { + continue; + } + if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow)) + { + continue; + } + + //outbound + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(item, outbound); + outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; + v2rayConfig.outbounds.Insert(0, outbound); + tagProxy.Add(outbound.tag); + } + if (tagProxy.Count <= 0) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + //add balancers + await GenBalancer(v2rayConfig, multipleLoad); + + var balancer = v2rayConfig.routing.balancers.First(); + + //add rule + var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList(); + if (rules?.Count > 0) + { + foreach (var rule in rules) + { + rule.outboundTag = null; + rule.balancerTag = balancer.tag; + } + } + if (v2rayConfig.routing.domainStrategy == "IPIfNonMatch") + { + v2rayConfig.routing.rules.Add(new() + { + ip = ["0.0.0.0/0", "::/0"], + balancerTag = balancer.tag, + type = "field" + }); + } + else + { + v2rayConfig.routing.rules.Add(new() + { + network = "tcp,udp", + balancerTag = balancer.tag, + type = "field" + }); + } + + ret.Success = true; + ret.Data = JsonUtils.Serialize(v2rayConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientSpeedtestConfig(List selecteds) + { + var ret = new RetResult(); + try + { + if (_config == null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + List lstIpEndPoints = new(); + List lstTcpConns = new(); try { - if (node == null - || node.Port <= 0) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (node.GetNetwork() is nameof(ETransport.quic)) - { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - if (result.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(v2rayConfig); - - await GenInbounds(v2rayConfig); - - await GenRouting(v2rayConfig); - - await GenOutbound(node, v2rayConfig.outbounds.First()); - - await GenMoreOutbounds(node, v2rayConfig); - - await GenDns(node, v2rayConfig); - - await GenStatistic(v2rayConfig); - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); - return ret; + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; } - } - public async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) - { - var ret = new RetResult(); + await GenLog(v2rayConfig); + v2rayConfig.inbounds.Clear(); + v2rayConfig.outbounds.Clear(); + v2rayConfig.routing.rules.Clear(); - try + var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); + + foreach (var it in selecteds) { - if (_config == null) + if (it.ConfigType == EConfigType.Custom) { - ret.Msg = ResUI.CheckServerSettings; - return ret; + continue; } - - ret.Msg = ResUI.InitialConfiguration; - - string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) + if (it.Port <= 0) { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; + continue; } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + var item = await AppHandler.Instance.GetProfileItem(it.IndexId); + if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(v2rayConfig); - await GenInbounds(v2rayConfig); - await GenRouting(v2rayConfig); - await GenDns(null, v2rayConfig); - await GenStatistic(v2rayConfig); - v2rayConfig.outbounds.RemoveAt(0); - - var tagProxy = new List(); - foreach (var it in selecteds) - { - if (it.ConfigType == EConfigType.Custom) + if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) { continue; } - if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard) + } + + //find unused port + var port = initPort; + for (var k = initPort; k < Global.MaxPort; k++) + { + if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) { continue; } - if (it.Port <= 0) + if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) { continue; } - var item = await AppHandler.Instance.GetProfileItem(it.IndexId); - if (item is null) - { - continue; - } - if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) - { - if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) - { - continue; - } - } - if (item.ConfigType == EConfigType.Shadowsocks - && !Global.SsSecuritiesInSingbox.Contains(item.Security)) - { - continue; - } - if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow)) - { - continue; - } - - //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; - v2rayConfig.outbounds.Insert(0, outbound); - tagProxy.Add(outbound.tag); - } - if (tagProxy.Count <= 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; + //found + port = k; + initPort = port + 1; + break; } - //add balancers - await GenBalancer(v2rayConfig, multipleLoad); - - var balancer = v2rayConfig.routing.balancers.First(); - - //add rule - var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList(); - if (rules?.Count > 0) + //Port In Used + if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) { - foreach (var rule in rules) - { - rule.outboundTag = null; - rule.balancerTag = balancer.tag; - } + continue; } - if (v2rayConfig.routing.domainStrategy == "IPIfNonMatch") + it.Port = port; + it.AllowTest = true; + + //outbound + if (item is null) { - v2rayConfig.routing.rules.Add(new() - { - ip = ["0.0.0.0/0", "::/0"], - balancerTag = balancer.tag, - type = "field" - }); + continue; } - else + if (item.ConfigType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInXray.Contains(item.Security)) { - v2rayConfig.routing.rules.Add(new() - { - network = "tcp,udp", - balancerTag = balancer.tag, - type = "field" - }); + continue; + } + if (item.ConfigType == EConfigType.VLESS + && !Global.Flows.Contains(item.Flow)) + { + continue; + } + if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan + && item.StreamSecurity == Global.StreamSecurityReality + && item.PublicKey.IsNullOrEmpty()) + { + continue; } - ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientSpeedtestConfig(List selecteds) - { - var ret = new RetResult(); - try - { - if (_config == null) + //inbound + Inbounds4Ray inbound = new() { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - List lstIpEndPoints = new(); - List lstTcpConns = new(); - try - { - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - await GenLog(v2rayConfig); - v2rayConfig.inbounds.Clear(); - v2rayConfig.outbounds.Clear(); - v2rayConfig.routing.rules.Clear(); - - var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); - - foreach (var it in selecteds) - { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.Port <= 0) - { - continue; - } - var item = await AppHandler.Instance.GetProfileItem(it.IndexId); - if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS) - { - if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) - { - continue; - } - } - - //find unused port - var port = initPort; - for (var k = initPort; k < Global.MaxPort; k++) - { - if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) - { - continue; - } - if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) - { - continue; - } - //found - port = k; - initPort = port + 1; - break; - } - - //Port In Used - if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) - { - continue; - } - it.Port = port; - it.AllowTest = true; - - //outbound - if (item is null) - { - continue; - } - if (item.ConfigType == EConfigType.Shadowsocks - && !Global.SsSecuritiesInXray.Contains(item.Security)) - { - continue; - } - if (item.ConfigType == EConfigType.VLESS - && !Global.Flows.Contains(item.Flow)) - { - continue; - } - if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan - && item.StreamSecurity == Global.StreamSecurityReality - && item.PublicKey.IsNullOrEmpty()) - { - continue; - } - - //inbound - Inbounds4Ray inbound = new() - { - listen = Global.Loopback, - port = port, - protocol = EInboundProtocol.socks.ToString(), - }; - inbound.tag = inbound.protocol + inbound.port.ToString(); - v2rayConfig.inbounds.Add(inbound); - - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.port.ToString(); - v2rayConfig.outbounds.Add(outbound); - - //rule - RulesItem4Ray rule = new() - { - inboundTag = new List { inbound.tag }, - outboundTag = outbound.tag, - type = "field" - }; - v2rayConfig.routing.rules.Add(rule); - } - - //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); - ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) - { - var ret = new RetResult(); - try - { - if (node is not { Port: > 0 }) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (node.GetNetwork() is nameof(ETransport.quic)) - { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; - return ret; - } - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - if (result.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenLog(v2rayConfig); - await GenOutbound(node, v2rayConfig.outbounds.First()); - await GenMoreOutbounds(node, v2rayConfig); - - v2rayConfig.routing.rules.Clear(); - v2rayConfig.inbounds.Clear(); - v2rayConfig.inbounds.Add(new() - { - tag = $"{EInboundProtocol.socks}{port}", listen = Global.Loopback, port = port, protocol = EInboundProtocol.socks.ToString(), - }); + }; + inbound.tag = inbound.protocol + inbound.port.ToString(); + v2rayConfig.inbounds.Add(inbound); - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(item, outbound); + outbound.tag = Global.ProxyTag + inbound.port.ToString(); + v2rayConfig.outbounds.Add(outbound); + + //rule + RulesItem4Ray rule = new() + { + inboundTag = new List { inbound.tag }, + outboundTag = outbound.tag, + type = "field" + }; + v2rayConfig.routing.rules.Add(rule); + } + + //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); + ret.Success = true; + ret.Data = JsonUtils.Serialize(v2rayConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node is not { Port: > 0 }) + { + ret.Msg = ResUI.CheckServerSettings; return ret; } - catch (Exception ex) + + if (node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) { - Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - } - #endregion public gen function + await GenLog(v2rayConfig); + await GenOutbound(node, v2rayConfig.outbounds.First()); + await GenMoreOutbounds(node, v2rayConfig); - #region private gen function - - private async Task GenLog(V2rayConfig v2rayConfig) - { - try + v2rayConfig.routing.rules.Clear(); + v2rayConfig.inbounds.Clear(); + v2rayConfig.inbounds.Add(new() { - if (_config.CoreBasicItem.LogEnabled) + tag = $"{EInboundProtocol.socks}{port}", + listen = Global.Loopback, + port = port, + protocol = EInboundProtocol.socks.ToString(), + }); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(v2rayConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + #endregion public gen function + + #region private gen function + + private async Task GenLog(V2rayConfig v2rayConfig) + { + try + { + if (_config.CoreBasicItem.LogEnabled) + { + var dtNow = DateTime.Now; + v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); + v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); + } + else + { + v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + v2rayConfig.log.access = null; + v2rayConfig.log.error = null; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenInbounds(V2rayConfig v2rayConfig) + { + try + { + var listen = "0.0.0.0"; + v2rayConfig.inbounds = []; + + var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true); + v2rayConfig.inbounds.Add(inbound); + + if (_config.Inbound.First().SecondLocalPortEnabled) + { + var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); + v2rayConfig.inbounds.Add(inbound2); + } + + if (_config.Inbound.First().AllowLANConn) + { + if (_config.Inbound.First().NewPort4LAN) { - var dtNow = DateTime.Now; - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); - v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); + var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); + inbound3.listen = listen; + v2rayConfig.inbounds.Add(inbound3); + + //auth + if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) + { + inbound3.settings.auth = "password"; + inbound3.settings.accounts = new List { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; + } } else { - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = null; - v2rayConfig.log.error = null; + inbound.listen = listen; } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) + { + string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); + if (result.IsNullOrEmpty()) + { + return new(); } - private async Task GenInbounds(V2rayConfig v2rayConfig) + var inbound = JsonUtils.Deserialize(result); + if (inbound == null) { - try + return new(); + } + inbound.tag = protocol.ToString(); + inbound.port = inItem.LocalPort + (int)protocol; + inbound.protocol = EInboundProtocol.socks.ToString(); + inbound.settings.udp = inItem.UdpEnabled; + inbound.sniffing.enabled = inItem.SniffingEnabled; + inbound.sniffing.destOverride = inItem.DestOverride; + inbound.sniffing.routeOnly = inItem.RouteOnly; + + return inbound; + } + + private async Task GenRouting(V2rayConfig v2rayConfig) + { + try + { + if (v2rayConfig.routing?.rules != null) { - var listen = "0.0.0.0"; - v2rayConfig.inbounds = []; + v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; + v2rayConfig.routing.domainMatcher = _config.RoutingBasicItem.DomainMatcher.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainMatcher; - var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true); - v2rayConfig.inbounds.Add(inbound); - - if (_config.Inbound.First().SecondLocalPortEnabled) + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing != null) { - var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); - v2rayConfig.inbounds.Add(inbound2); - } - - if (_config.Inbound.First().AllowLANConn) - { - if (_config.Inbound.First().NewPort4LAN) + if (routing.DomainStrategy.IsNotEmpty()) { - var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); - inbound3.listen = listen; - v2rayConfig.inbounds.Add(inbound3); - - //auth - if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) - { - inbound3.settings.auth = "password"; - inbound3.settings.accounts = new List { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; - } + v2rayConfig.routing.domainStrategy = routing.DomainStrategy; } - else + var rules = JsonUtils.Deserialize>(routing.RuleSet); + foreach (var item in rules) { - inbound.listen = listen; - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) - { - string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); - if (result.IsNullOrEmpty()) - { - return new(); - } - - var inbound = JsonUtils.Deserialize(result); - if (inbound == null) - { - return new(); - } - inbound.tag = protocol.ToString(); - inbound.port = inItem.LocalPort + (int)protocol; - inbound.protocol = EInboundProtocol.socks.ToString(); - inbound.settings.udp = inItem.UdpEnabled; - inbound.sniffing.enabled = inItem.SniffingEnabled; - inbound.sniffing.destOverride = inItem.DestOverride; - inbound.sniffing.routeOnly = inItem.RouteOnly; - - return inbound; - } - - private async Task GenRouting(V2rayConfig v2rayConfig) - { - try - { - if (v2rayConfig.routing?.rules != null) - { - v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; - v2rayConfig.routing.domainMatcher = _config.RoutingBasicItem.DomainMatcher.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainMatcher; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing != null) - { - if (routing.DomainStrategy.IsNotEmpty()) + if (item.Enabled) { - v2rayConfig.routing.domainStrategy = routing.DomainStrategy; - } - var rules = JsonUtils.Deserialize>(routing.RuleSet); - foreach (var item in rules) - { - if (item.Enabled) - { - var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); - await GenRoutingUserRule(item2, v2rayConfig); - } + var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); + await GenRoutingUserRule(item2, v2rayConfig); } } } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; } - - private async Task GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) + catch (Exception ex) { - try - { - if (rule == null) - { - return 0; - } - if (rule.port.IsNullOrEmpty()) - { - rule.port = null; - } - if (rule.network.IsNullOrEmpty()) - { - rule.network = null; - } - if (rule.domain?.Count == 0) - { - rule.domain = null; - } - if (rule.ip?.Count == 0) - { - rule.ip = null; - } - if (rule.protocol?.Count == 0) - { - rule.protocol = null; - } - if (rule.inboundTag?.Count == 0) - { - rule.inboundTag = null; - } + Logging.SaveLog(_tag, ex); + } + return 0; + } - var hasDomainIp = false; - if (rule.domain?.Count > 0) + private async Task GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) + { + try + { + if (rule == null) + { + return 0; + } + if (rule.port.IsNullOrEmpty()) + { + rule.port = null; + } + if (rule.network.IsNullOrEmpty()) + { + rule.network = null; + } + if (rule.domain?.Count == 0) + { + rule.domain = null; + } + if (rule.ip?.Count == 0) + { + rule.ip = null; + } + if (rule.protocol?.Count == 0) + { + rule.protocol = null; + } + if (rule.inboundTag?.Count == 0) + { + rule.inboundTag = null; + } + + var hasDomainIp = false; + if (rule.domain?.Count > 0) + { + var it = JsonUtils.DeepCopy(rule); + it.ip = null; + it.type = "field"; + for (var k = it.domain.Count - 1; k >= 0; k--) + { + if (it.domain[k].StartsWith("#")) + { + it.domain.RemoveAt(k); + } + it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); + } + v2rayConfig.routing.rules.Add(it); + hasDomainIp = true; + } + if (rule.ip?.Count > 0) + { + var it = JsonUtils.DeepCopy(rule); + it.domain = null; + it.type = "field"; + v2rayConfig.routing.rules.Add(it); + hasDomainIp = true; + } + if (!hasDomainIp) + { + if (rule.port.IsNotEmpty() + || rule.protocol?.Count > 0 + || rule.inboundTag?.Count > 0 + ) { var it = JsonUtils.DeepCopy(rule); - it.ip = null; - it.type = "field"; - for (var k = it.domain.Count - 1; k >= 0; k--) - { - if (it.domain[k].StartsWith("#")) - { - it.domain.RemoveAt(k); - } - it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); - } - v2rayConfig.routing.rules.Add(it); - hasDomainIp = true; - } - if (rule.ip?.Count > 0) - { - var it = JsonUtils.DeepCopy(rule); - it.domain = null; it.type = "field"; v2rayConfig.routing.rules.Add(it); - hasDomainIp = true; - } - if (!hasDomainIp) - { - if (rule.port.IsNotEmpty() - || rule.protocol?.Count > 0 - || rule.inboundTag?.Count > 0 - ) - { - var it = JsonUtils.DeepCopy(rule); - it.type = "field"; - v2rayConfig.routing.rules.Add(it); - } } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); } - - private async Task GenOutbound(ProfileItem node, Outbounds4Ray outbound) + catch (Exception ex) { - try - { - var muxEnabled = _config.CoreBasicItem.MuxEnabled; - switch (node.ConfigType) - { - case EConfigType.VMess: - { - VnextItem4Ray vnextItem; - if (outbound.settings.vnext.Count <= 0) - { - vnextItem = new VnextItem4Ray(); - outbound.settings.vnext.Add(vnextItem); - } - else - { - vnextItem = outbound.settings.vnext.First(); - } - vnextItem.address = node.Address; - vnextItem.port = node.Port; - - UsersItem4Ray usersItem; - if (vnextItem.users.Count <= 0) - { - usersItem = new UsersItem4Ray(); - vnextItem.users.Add(usersItem); - } - else - { - usersItem = vnextItem.users.First(); - } - //远程服务器用户ID - usersItem.id = node.Id; - usersItem.alterId = node.AlterId; - usersItem.email = Global.UserEMail; - if (Global.VmessSecurities.Contains(node.Security)) - { - usersItem.security = node.Security; - } - else - { - usersItem.security = Global.DefaultSecurity; - } - - await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); - - outbound.settings.servers = null; - break; - } - case EConfigType.Shadowsocks: - { - ServersItem4Ray serversItem; - if (outbound.settings.servers.Count <= 0) - { - serversItem = new ServersItem4Ray(); - outbound.settings.servers.Add(serversItem); - } - else - { - serversItem = outbound.settings.servers.First(); - } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Id; - serversItem.method = AppHandler.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none"; - - serversItem.ota = false; - serversItem.level = 1; - - await GenOutboundMux(node, outbound); - - outbound.settings.vnext = null; - break; - } - case EConfigType.SOCKS: - case EConfigType.HTTP: - { - ServersItem4Ray serversItem; - if (outbound.settings.servers.Count <= 0) - { - serversItem = new ServersItem4Ray(); - outbound.settings.servers.Add(serversItem); - } - else - { - serversItem = outbound.settings.servers.First(); - } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.method = null; - serversItem.password = null; - - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) - { - SocksUsersItem4Ray socksUsersItem = new() - { - user = node.Security, - pass = node.Id, - level = 1 - }; - - serversItem.users = new List() { socksUsersItem }; - } - - await GenOutboundMux(node, outbound); - - outbound.settings.vnext = null; - break; - } - case EConfigType.VLESS: - { - VnextItem4Ray vnextItem; - if (outbound.settings.vnext?.Count <= 0) - { - vnextItem = new VnextItem4Ray(); - outbound.settings.vnext.Add(vnextItem); - } - else - { - vnextItem = outbound.settings.vnext.First(); - } - vnextItem.address = node.Address; - vnextItem.port = node.Port; - - UsersItem4Ray usersItem; - if (vnextItem.users.Count <= 0) - { - usersItem = new UsersItem4Ray(); - vnextItem.users.Add(usersItem); - } - else - { - usersItem = vnextItem.users.First(); - } - usersItem.id = node.Id; - usersItem.email = Global.UserEMail; - usersItem.encryption = node.Security; - - if (node.Flow.IsNullOrEmpty()) - { - await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); - } - else - { - usersItem.flow = node.Flow; - await GenOutboundMux(node, outbound, false, muxEnabled); - } - outbound.settings.servers = null; - break; - } - case EConfigType.Trojan: - { - ServersItem4Ray serversItem; - if (outbound.settings.servers.Count <= 0) - { - serversItem = new ServersItem4Ray(); - outbound.settings.servers.Add(serversItem); - } - else - { - serversItem = outbound.settings.servers.First(); - } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Id; - - serversItem.ota = false; - serversItem.level = 1; - - await GenOutboundMux(node, outbound); - - outbound.settings.vnext = null; - break; - } - } - - outbound.protocol = Global.ProtocolTypes[node.ConfigType]; - await GenBoundStreamSettings(node, outbound); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; + Logging.SaveLog(_tag, ex); } + return await Task.FromResult(0); + } - private async Task GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) + private async Task GenOutbound(ProfileItem node, Outbounds4Ray outbound) + { + try { - try + var muxEnabled = _config.CoreBasicItem.MuxEnabled; + switch (node.ConfigType) { - outbound.mux.enabled = false; - outbound.mux.concurrency = -1; - - if (enabledTCP) - { - outbound.mux.enabled = true; - outbound.mux.concurrency = _config.Mux4RayItem.Concurrency; - } - else if (enabledUDP) - { - outbound.mux.enabled = true; - outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency; - outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(0); - } - - private async Task GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound) - { - try - { - var streamSettings = outbound.streamSettings; - streamSettings.network = node.GetNetwork(); - var host = node.RequestHost.TrimEx(); - var path = node.Path.TrimEx(); - var sni = node.Sni.TrimEx(); - var useragent = ""; - if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) - { - try + case EConfigType.VMess: { - useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent]; - } - catch (KeyNotFoundException) - { - useragent = _config.CoreBasicItem.DefUserAgent; - } - } - - //if tls - if (node.StreamSecurity == Global.StreamSecurity) - { - streamSettings.security = node.StreamSecurity; - - TlsSettings4Ray tlsSettings = new() - { - allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint - }; - if (sni.IsNotEmpty()) - { - tlsSettings.serverName = sni; - } - else if (host.IsNotEmpty()) - { - tlsSettings.serverName = Utils.String2List(host)?.First(); - } - streamSettings.tlsSettings = tlsSettings; - } - - //if Reality - if (node.StreamSecurity == Global.StreamSecurityReality) - { - streamSettings.security = node.StreamSecurity; - - TlsSettings4Ray realitySettings = new() - { - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, - serverName = sni, - publicKey = node.PublicKey, - shortId = node.ShortId, - spiderX = node.SpiderX, - show = false, - }; - - streamSettings.realitySettings = realitySettings; - } - - //streamSettings - switch (node.GetNetwork()) - { - case nameof(ETransport.kcp): - KcpSettings4Ray kcpSettings = new() + VnextItem4Ray vnextItem; + if (outbound.settings.vnext.Count <= 0) { - mtu = _config.KcpItem.Mtu, - tti = _config.KcpItem.Tti - }; - - kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity; - kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity; - - kcpSettings.congestion = _config.KcpItem.Congestion; - kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; - kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; - kcpSettings.header = new Header4Ray - { - type = node.HeaderType, - domain = host.IsNullOrEmpty() ? null : host - }; - if (path.IsNotEmpty()) - { - kcpSettings.seed = path; + vnextItem = new VnextItem4Ray(); + outbound.settings.vnext.Add(vnextItem); } - streamSettings.kcpSettings = kcpSettings; + else + { + vnextItem = outbound.settings.vnext.First(); + } + vnextItem.address = node.Address; + vnextItem.port = node.Port; + + UsersItem4Ray usersItem; + if (vnextItem.users.Count <= 0) + { + usersItem = new UsersItem4Ray(); + vnextItem.users.Add(usersItem); + } + else + { + usersItem = vnextItem.users.First(); + } + //远程服务器用户ID + usersItem.id = node.Id; + usersItem.alterId = node.AlterId; + usersItem.email = Global.UserEMail; + if (Global.VmessSecurities.Contains(node.Security)) + { + usersItem.security = node.Security; + } + else + { + usersItem.security = Global.DefaultSecurity; + } + + await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); + + outbound.settings.servers = null; break; - //ws - case nameof(ETransport.ws): - WsSettings4Ray wsSettings = new(); - wsSettings.headers = new Headers4Ray(); + } + case EConfigType.Shadowsocks: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) + { + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); + } + else + { + serversItem = outbound.settings.servers.First(); + } + serversItem.address = node.Address; + serversItem.port = node.Port; + serversItem.password = node.Id; + serversItem.method = AppHandler.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none"; - if (host.IsNotEmpty()) - { - wsSettings.host = host; - wsSettings.headers.Host = host; - } - if (path.IsNotEmpty()) - { - wsSettings.path = path; - } - if (useragent.IsNotEmpty()) - { - wsSettings.headers.UserAgent = useragent; - } - streamSettings.wsSettings = wsSettings; + serversItem.ota = false; + serversItem.level = 1; - break; - //httpupgrade - case nameof(ETransport.httpupgrade): - HttpupgradeSettings4Ray httpupgradeSettings = new(); - - if (path.IsNotEmpty()) - { - httpupgradeSettings.path = path; - } - if (host.IsNotEmpty()) - { - httpupgradeSettings.host = host; - } - streamSettings.httpupgradeSettings = httpupgradeSettings; - - break; - //xhttp - case nameof(ETransport.xhttp): - streamSettings.network = ETransport.xhttp.ToString(); - XhttpSettings4Ray xhttpSettings = new(); - - if (path.IsNotEmpty()) - { - xhttpSettings.path = path; - } - if (host.IsNotEmpty()) - { - xhttpSettings.host = host; - } - if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType)) - { - xhttpSettings.mode = node.HeaderType; - } - if (node.Extra.IsNotEmpty()) - { - xhttpSettings.extra = JsonUtils.ParseJson(node.Extra); - } - - streamSettings.xhttpSettings = xhttpSettings; await GenOutboundMux(node, outbound); + outbound.settings.vnext = null; break; - //h2 - case nameof(ETransport.h2): - HttpSettings4Ray httpSettings = new(); - - if (host.IsNotEmpty()) + } + case EConfigType.SOCKS: + case EConfigType.HTTP: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) { - httpSettings.host = Utils.String2List(host); + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); } - httpSettings.path = path; - - streamSettings.httpSettings = httpSettings; - - break; - //quic - case nameof(ETransport.quic): - QuicSettings4Ray quicsettings = new() + else + { + serversItem = outbound.settings.servers.First(); + } + serversItem.address = node.Address; + serversItem.port = node.Port; + serversItem.method = null; + serversItem.password = null; + + if (node.Security.IsNotEmpty() + && node.Id.IsNotEmpty()) + { + SocksUsersItem4Ray socksUsersItem = new() + { + user = node.Security, + pass = node.Id, + level = 1 + }; + + serversItem.users = new List() { socksUsersItem }; + } + + await GenOutboundMux(node, outbound); + + outbound.settings.vnext = null; + break; + } + case EConfigType.VLESS: + { + VnextItem4Ray vnextItem; + if (outbound.settings.vnext?.Count <= 0) + { + vnextItem = new VnextItem4Ray(); + outbound.settings.vnext.Add(vnextItem); + } + else + { + vnextItem = outbound.settings.vnext.First(); + } + vnextItem.address = node.Address; + vnextItem.port = node.Port; + + UsersItem4Ray usersItem; + if (vnextItem.users.Count <= 0) + { + usersItem = new UsersItem4Ray(); + vnextItem.users.Add(usersItem); + } + else + { + usersItem = vnextItem.users.First(); + } + usersItem.id = node.Id; + usersItem.email = Global.UserEMail; + usersItem.encryption = node.Security; + + if (node.Flow.IsNullOrEmpty()) + { + await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); + } + else + { + usersItem.flow = node.Flow; + await GenOutboundMux(node, outbound, false, muxEnabled); + } + outbound.settings.servers = null; + break; + } + case EConfigType.Trojan: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) + { + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); + } + else + { + serversItem = outbound.settings.servers.First(); + } + serversItem.address = node.Address; + serversItem.port = node.Port; + serversItem.password = node.Id; + + serversItem.ota = false; + serversItem.level = 1; + + await GenOutboundMux(node, outbound); + + outbound.settings.vnext = null; + break; + } + } + + outbound.protocol = Global.ProtocolTypes[node.ConfigType]; + await GenBoundStreamSettings(node, outbound); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) + { + try + { + outbound.mux.enabled = false; + outbound.mux.concurrency = -1; + + if (enabledTCP) + { + outbound.mux.enabled = true; + outbound.mux.concurrency = _config.Mux4RayItem.Concurrency; + } + else if (enabledUDP) + { + outbound.mux.enabled = true; + outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency; + outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound) + { + try + { + var streamSettings = outbound.streamSettings; + streamSettings.network = node.GetNetwork(); + var host = node.RequestHost.TrimEx(); + var path = node.Path.TrimEx(); + var sni = node.Sni.TrimEx(); + var useragent = ""; + if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) + { + try + { + useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent]; + } + catch (KeyNotFoundException) + { + useragent = _config.CoreBasicItem.DefUserAgent; + } + } + + //if tls + if (node.StreamSecurity == Global.StreamSecurity) + { + streamSettings.security = node.StreamSecurity; + + TlsSettings4Ray tlsSettings = new() + { + allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), + alpn = node.GetAlpn(), + fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint + }; + if (sni.IsNotEmpty()) + { + tlsSettings.serverName = sni; + } + else if (host.IsNotEmpty()) + { + tlsSettings.serverName = Utils.String2List(host)?.First(); + } + streamSettings.tlsSettings = tlsSettings; + } + + //if Reality + if (node.StreamSecurity == Global.StreamSecurityReality) + { + streamSettings.security = node.StreamSecurity; + + TlsSettings4Ray realitySettings = new() + { + fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, + serverName = sni, + publicKey = node.PublicKey, + shortId = node.ShortId, + spiderX = node.SpiderX, + show = false, + }; + + streamSettings.realitySettings = realitySettings; + } + + //streamSettings + switch (node.GetNetwork()) + { + case nameof(ETransport.kcp): + KcpSettings4Ray kcpSettings = new() + { + mtu = _config.KcpItem.Mtu, + tti = _config.KcpItem.Tti + }; + + kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity; + kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity; + + kcpSettings.congestion = _config.KcpItem.Congestion; + kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; + kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; + kcpSettings.header = new Header4Ray + { + type = node.HeaderType, + domain = host.IsNullOrEmpty() ? null : host + }; + if (path.IsNotEmpty()) + { + kcpSettings.seed = path; + } + streamSettings.kcpSettings = kcpSettings; + break; + //ws + case nameof(ETransport.ws): + WsSettings4Ray wsSettings = new(); + wsSettings.headers = new Headers4Ray(); + + if (host.IsNotEmpty()) + { + wsSettings.host = host; + wsSettings.headers.Host = host; + } + if (path.IsNotEmpty()) + { + wsSettings.path = path; + } + if (useragent.IsNotEmpty()) + { + wsSettings.headers.UserAgent = useragent; + } + streamSettings.wsSettings = wsSettings; + + break; + //httpupgrade + case nameof(ETransport.httpupgrade): + HttpupgradeSettings4Ray httpupgradeSettings = new(); + + if (path.IsNotEmpty()) + { + httpupgradeSettings.path = path; + } + if (host.IsNotEmpty()) + { + httpupgradeSettings.host = host; + } + streamSettings.httpupgradeSettings = httpupgradeSettings; + + break; + //xhttp + case nameof(ETransport.xhttp): + streamSettings.network = ETransport.xhttp.ToString(); + XhttpSettings4Ray xhttpSettings = new(); + + if (path.IsNotEmpty()) + { + xhttpSettings.path = path; + } + if (host.IsNotEmpty()) + { + xhttpSettings.host = host; + } + if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType)) + { + xhttpSettings.mode = node.HeaderType; + } + if (node.Extra.IsNotEmpty()) + { + xhttpSettings.extra = JsonUtils.ParseJson(node.Extra); + } + + streamSettings.xhttpSettings = xhttpSettings; + await GenOutboundMux(node, outbound); + + break; + //h2 + case nameof(ETransport.h2): + HttpSettings4Ray httpSettings = new(); + + if (host.IsNotEmpty()) + { + httpSettings.host = Utils.String2List(host); + } + httpSettings.path = path; + + streamSettings.httpSettings = httpSettings; + + break; + //quic + case nameof(ETransport.quic): + QuicSettings4Ray quicsettings = new() + { + security = host, + key = path, + header = new Header4Ray + { + type = node.HeaderType + } + }; + streamSettings.quicSettings = quicsettings; + if (node.StreamSecurity == Global.StreamSecurity) + { + if (sni.IsNotEmpty()) + { + streamSettings.tlsSettings.serverName = sni; + } + else + { + streamSettings.tlsSettings.serverName = node.Address; + } + } + break; + + case nameof(ETransport.grpc): + GrpcSettings4Ray grpcSettings = new() + { + authority = host.IsNullOrEmpty() ? null : host, + serviceName = path, + multiMode = node.HeaderType == Global.GrpcMultiMode, + idle_timeout = _config.GrpcItem.IdleTimeout, + health_check_timeout = _config.GrpcItem.HealthCheckTimeout, + permit_without_stream = _config.GrpcItem.PermitWithoutStream, + initial_windows_size = _config.GrpcItem.InitialWindowsSize, + }; + streamSettings.grpcSettings = grpcSettings; + break; + + default: + //tcp + if (node.HeaderType == Global.TcpHeaderHttp) + { + TcpSettings4Ray tcpSettings = new() { - security = host, - key = path, header = new Header4Ray { type = node.HeaderType } }; - streamSettings.quicSettings = quicsettings; - if (node.StreamSecurity == Global.StreamSecurity) + + //request Host + string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName); + string[] arrHost = host.Split(','); + string host2 = string.Join(",".AppendQuotes(), arrHost); + request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}"); + request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}"); + //Path + string pathHttp = @"/"; + if (path.IsNotEmpty()) { - if (sni.IsNotEmpty()) - { - streamSettings.tlsSettings.serverName = sni; - } - else - { - streamSettings.tlsSettings.serverName = node.Address; - } + string[] arrPath = path.Split(','); + pathHttp = string.Join(",".AppendQuotes(), arrPath); } - break; + request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}"); + tcpSettings.header.request = JsonUtils.Deserialize(request); - case nameof(ETransport.grpc): - GrpcSettings4Ray grpcSettings = new() - { - authority = host.IsNullOrEmpty() ? null : host, - serviceName = path, - multiMode = node.HeaderType == Global.GrpcMultiMode, - idle_timeout = _config.GrpcItem.IdleTimeout, - health_check_timeout = _config.GrpcItem.HealthCheckTimeout, - permit_without_stream = _config.GrpcItem.PermitWithoutStream, - initial_windows_size = _config.GrpcItem.InitialWindowsSize, - }; - streamSettings.grpcSettings = grpcSettings; - break; - - default: - //tcp - if (node.HeaderType == Global.TcpHeaderHttp) - { - TcpSettings4Ray tcpSettings = new() - { - header = new Header4Ray - { - type = node.HeaderType - } - }; - - //request Host - string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName); - string[] arrHost = host.Split(','); - string host2 = string.Join(",".AppendQuotes(), arrHost); - request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}"); - request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}"); - //Path - string pathHttp = @"/"; - if (path.IsNotEmpty()) - { - string[] arrPath = path.Split(','); - pathHttp = string.Join(",".AppendQuotes(), arrPath); - } - request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}"); - tcpSettings.header.request = JsonUtils.Deserialize(request); - - streamSettings.tcpSettings = tcpSettings; - } - break; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) - { - try - { - var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); - var normalDNS = item?.NormalDNS; - var domainStrategy4Freedom = item?.DomainStrategy4Freedom; - if (normalDNS.IsNullOrEmpty()) - { - normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); - } - - //Outbound Freedom domainStrategy - if (domainStrategy4Freedom.IsNotEmpty()) - { - var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); - if (outbound != null) - { - outbound.settings = new(); - outbound.settings.domainStrategy = domainStrategy4Freedom; - outbound.settings.userLevel = 0; + streamSettings.tcpSettings = tcpSettings; } - } - - var obj = JsonUtils.ParseJson(normalDNS); - if (obj is null) - { - List servers = []; - string[] arrDNS = normalDNS.Split(','); - foreach (string str in arrDNS) - { - servers.Add(str); - } - obj = JsonUtils.ParseJson("{}"); - obj["servers"] = JsonUtils.SerializeToNode(servers); - } - - // 追加至 dns 设置 - if (item.UseSystemHosts) - { - var systemHosts = Utils.GetSystemHosts(); - if (systemHosts.Count > 0) - { - var normalHost = obj["hosts"]; - if (normalHost != null) - { - foreach (var host in systemHosts) - { - if (normalHost[host.Key] != null) - continue; - normalHost[host.Key] = host.Value; - } - } - } - } - - await GenDnsDomains(node, obj, item); - - v2rayConfig.dns = obj; + break; } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; } - - private async Task GenDnsDomains(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) + catch (Exception ex) { - if (node == null) - { return 0; } - var servers = dns["servers"]; - if (servers != null) - { - if (Utils.IsDomain(node.Address)) - { - var dnsServer = new DnsServer4Ray() - { - address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, - domains = [node.Address] - }; - servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); - } - } - return await Task.FromResult(0); + Logging.SaveLog(_tag, ex); } - - private async Task GenStatistic(V2rayConfig v2rayConfig) - { - if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) - { - string tag = EInboundProtocol.api.ToString(); - Metrics4Ray apiObj = new(); - Policy4Ray policyObj = new(); - SystemPolicy4Ray policySystemSetting = new(); - - v2rayConfig.stats = new Stats4Ray(); - - apiObj.tag = tag; - v2rayConfig.metrics = apiObj; - - policySystemSetting.statsOutboundDownlink = true; - policySystemSetting.statsOutboundUplink = true; - policyObj.system = policySystemSetting; - v2rayConfig.policy = policyObj; - - if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) - { - Inbounds4Ray apiInbound = new(); - Inboundsettings4Ray apiInboundSettings = new(); - apiInbound.tag = tag; - apiInbound.listen = Global.Loopback; - apiInbound.port = AppHandler.Instance.StatePort; - apiInbound.protocol = Global.InboundAPIProtocol; - apiInboundSettings.address = Global.Loopback; - apiInbound.settings = apiInboundSettings; - v2rayConfig.inbounds.Add(apiInbound); - } - - if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) - { - RulesItem4Ray apiRoutingRule = new() - { - inboundTag = new List { tag }, - outboundTag = tag, - type = "field" - }; - - v2rayConfig.routing.rules.Add(apiRoutingRule); - } - } - return await Task.FromResult(0); - } - - private async Task GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig) - { - //fragment proxy - if (_config.CoreBasicItem.EnableFragment - && v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false) - { - var fragmentOutbound = new Outbounds4Ray - { - protocol = "freedom", - tag = $"{Global.ProxyTag}3", - settings = new() - { - fragment = new() - { - packets = _config.Fragment4RayItem?.Packets, - length = _config.Fragment4RayItem?.Length, - interval = _config.Fragment4RayItem?.Interval - } - } - }; - - v2rayConfig.outbounds.Add(fragmentOutbound); - v2rayConfig.outbounds.First().streamSettings.sockopt = new() - { - dialerProxy = fragmentOutbound.tag - }; - return 0; - } - - if (node.Subid.IsNullOrEmpty()) - { - return 0; - } - try - { - var subItem = await AppHandler.Instance.GetSubItem(node.Subid); - if (subItem is null) - { - return 0; - } - - //current proxy - var outbound = v2rayConfig.outbounds.First(); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - - //Previous proxy - var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.WireGuard) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; - v2rayConfig.outbounds.Add(prevOutbound); - - outbound.streamSettings.sockopt = new() - { - dialerProxy = prevOutbound.tag - }; - } - - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.WireGuard) - { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; - v2rayConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.streamSettings.sockopt = new() - { - dialerProxy = outbound.tag - }; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) - { - if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.LeastPing) - { - var observatory = new Observatory4Ray - { - subjectSelector = [Global.ProxyTag], - probeUrl = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl, - probeInterval = "3m" - }; - v2rayConfig.observatory = observatory; - } - var strategyType = multipleLoad switch - { - EMultipleLoad.Random => "random", - EMultipleLoad.RoundRobin => "roundRobin", - EMultipleLoad.LeastPing => "leastPing", - EMultipleLoad.LeastLoad => "leastLoad", - _ => "roundRobin", - }; - var balancer = new BalancersItem4Ray - { - selector = [Global.ProxyTag], - strategy = new() { type = strategyType }, - tag = $"{Global.ProxyTag}-round", - }; - v2rayConfig.routing.balancers = [balancer]; - return await Task.FromResult(0); - } - - #endregion private gen function + return 0; } + + private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) + { + try + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + var normalDNS = item?.NormalDNS; + var domainStrategy4Freedom = item?.DomainStrategy4Freedom; + if (normalDNS.IsNullOrEmpty()) + { + normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); + } + + //Outbound Freedom domainStrategy + if (domainStrategy4Freedom.IsNotEmpty()) + { + var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + if (outbound != null) + { + outbound.settings = new(); + outbound.settings.domainStrategy = domainStrategy4Freedom; + outbound.settings.userLevel = 0; + } + } + + var obj = JsonUtils.ParseJson(normalDNS); + if (obj is null) + { + List servers = []; + string[] arrDNS = normalDNS.Split(','); + foreach (string str in arrDNS) + { + servers.Add(str); + } + obj = JsonUtils.ParseJson("{}"); + obj["servers"] = JsonUtils.SerializeToNode(servers); + } + + // 追加至 dns 设置 + if (item.UseSystemHosts) + { + var systemHosts = Utils.GetSystemHosts(); + if (systemHosts.Count > 0) + { + var normalHost = obj["hosts"]; + if (normalHost != null) + { + foreach (var host in systemHosts) + { + if (normalHost[host.Key] != null) + continue; + normalHost[host.Key] = host.Value; + } + } + } + } + + await GenDnsDomains(node, obj, item); + + v2rayConfig.dns = obj; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsDomains(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) + { + if (node == null) + { return 0; } + var servers = dns["servers"]; + if (servers != null) + { + if (Utils.IsDomain(node.Address)) + { + var dnsServer = new DnsServer4Ray() + { + address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, + domains = [node.Address] + }; + servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); + } + } + return await Task.FromResult(0); + } + + private async Task GenStatistic(V2rayConfig v2rayConfig) + { + if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) + { + string tag = EInboundProtocol.api.ToString(); + Metrics4Ray apiObj = new(); + Policy4Ray policyObj = new(); + SystemPolicy4Ray policySystemSetting = new(); + + v2rayConfig.stats = new Stats4Ray(); + + apiObj.tag = tag; + v2rayConfig.metrics = apiObj; + + policySystemSetting.statsOutboundDownlink = true; + policySystemSetting.statsOutboundUplink = true; + policyObj.system = policySystemSetting; + v2rayConfig.policy = policyObj; + + if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) + { + Inbounds4Ray apiInbound = new(); + Inboundsettings4Ray apiInboundSettings = new(); + apiInbound.tag = tag; + apiInbound.listen = Global.Loopback; + apiInbound.port = AppHandler.Instance.StatePort; + apiInbound.protocol = Global.InboundAPIProtocol; + apiInboundSettings.address = Global.Loopback; + apiInbound.settings = apiInboundSettings; + v2rayConfig.inbounds.Add(apiInbound); + } + + if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) + { + RulesItem4Ray apiRoutingRule = new() + { + inboundTag = new List { tag }, + outboundTag = tag, + type = "field" + }; + + v2rayConfig.routing.rules.Add(apiRoutingRule); + } + } + return await Task.FromResult(0); + } + + private async Task GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig) + { + //fragment proxy + if (_config.CoreBasicItem.EnableFragment + && v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false) + { + var fragmentOutbound = new Outbounds4Ray + { + protocol = "freedom", + tag = $"{Global.ProxyTag}3", + settings = new() + { + fragment = new() + { + packets = _config.Fragment4RayItem?.Packets, + length = _config.Fragment4RayItem?.Length, + interval = _config.Fragment4RayItem?.Interval + } + } + }; + + v2rayConfig.outbounds.Add(fragmentOutbound); + v2rayConfig.outbounds.First().streamSettings.sockopt = new() + { + dialerProxy = fragmentOutbound.tag + }; + return 0; + } + + if (node.Subid.IsNullOrEmpty()) + { + return 0; + } + try + { + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + if (subItem is null) + { + return 0; + } + + //current proxy + var outbound = v2rayConfig.outbounds.First(); + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + + //Previous proxy + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom + && prevNode.ConfigType != EConfigType.Hysteria2 + && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.WireGuard) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevOutbound.tag = $"{Global.ProxyTag}2"; + v2rayConfig.outbounds.Add(prevOutbound); + + outbound.streamSettings.sockopt = new() + { + dialerProxy = prevOutbound.tag + }; + } + + //Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom + && nextNode.ConfigType != EConfigType.Hysteria2 + && nextNode.ConfigType != EConfigType.TUIC + && nextNode.ConfigType != EConfigType.WireGuard) + { + var nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = Global.ProxyTag; + v2rayConfig.outbounds.Insert(0, nextOutbound); + + outbound.tag = $"{Global.ProxyTag}1"; + nextOutbound.streamSettings.sockopt = new() + { + dialerProxy = outbound.tag + }; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return 0; + } + + private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) + { + if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.LeastPing) + { + var observatory = new Observatory4Ray + { + subjectSelector = [Global.ProxyTag], + probeUrl = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl, + probeInterval = "3m" + }; + v2rayConfig.observatory = observatory; + } + var strategyType = multipleLoad switch + { + EMultipleLoad.Random => "random", + EMultipleLoad.RoundRobin => "roundRobin", + EMultipleLoad.LeastPing => "leastPing", + EMultipleLoad.LeastLoad => "leastLoad", + _ => "roundRobin", + }; + var balancer = new BalancersItem4Ray + { + selector = [Global.ProxyTag], + strategy = new() { type = strategyType }, + tag = $"{Global.ProxyTag}-round", + }; + v2rayConfig.routing.balancers = [balancer]; + return await Task.FromResult(0); + } + + #endregion private gen function } diff --git a/v2rayN/ServiceLib/Services/DownloadService.cs b/v2rayN/ServiceLib/Services/DownloadService.cs index aab888c9..51dee97a 100644 --- a/v2rayN/ServiceLib/Services/DownloadService.cs +++ b/v2rayN/ServiceLib/Services/DownloadService.cs @@ -3,311 +3,310 @@ using System.Net; using System.Net.Http.Headers; using System.Net.Sockets; -namespace ServiceLib.Services +namespace ServiceLib.Services; + +/// +///Download +/// +public class DownloadService { - /// - ///Download - /// - public class DownloadService + public event EventHandler? UpdateCompleted; + + public event ErrorEventHandler? Error; + + private static readonly string _tag = "DownloadService"; + + public async Task DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action updateFunc) { - public event EventHandler? UpdateCompleted; - - public event ErrorEventHandler? Error; - - private static readonly string _tag = "DownloadService"; - - public async Task DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action updateFunc) - { - try - { - SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); - - var progress = new Progress(); - progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}"); - - await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy, - url, - progress, - downloadTimeout); - } - catch (Exception ex) - { - updateFunc?.Invoke(false, ex.Message); - if (ex.InnerException != null) - { - updateFunc?.Invoke(false, ex.InnerException.Message); - } - } - return 0; - } - - public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) - { - try - { - SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); - UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}")); - - var progress = new Progress(); - progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new RetResult(value > 100, $"...{value}%")); - - var webProxy = await GetWebProxy(blProxy); - await DownloaderHelper.Instance.DownloadFileAsync(webProxy, - url, - fileName, - progress, - downloadTimeout); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - - Error?.Invoke(this, new ErrorEventArgs(ex)); - if (ex.InnerException != null) - { - Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); - } - } - } - - public async Task UrlRedirectAsync(string url, bool blProxy) + try { SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); - var webRequestHandler = new SocketsHttpHandler - { - AllowAutoRedirect = false, - Proxy = await GetWebProxy(blProxy) - }; - HttpClient client = new(webRequestHandler); - var response = await client.GetAsync(url); - if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null) + var progress = new Progress(); + progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}"); + + await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy, + url, + progress, + downloadTimeout); + } + catch (Exception ex) + { + updateFunc?.Invoke(false, ex.Message); + if (ex.InnerException != null) { - return response.Headers.Location.ToString(); - } - else - { - Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode))); - Logging.SaveLog("StatusCode error: " + url); - return null; + updateFunc?.Invoke(false, ex.InnerException.Message); } } + return 0; + } - public async Task TryDownloadString(string url, bool blProxy, string userAgent) + public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) + { + try { - try - { - var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); - if (result1.IsNotEmpty()) - { - return result1; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - Error?.Invoke(this, new ErrorEventArgs(ex)); - if (ex.InnerException != null) - { - Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); - } - } + SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); + UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}")); - try - { - var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); - if (result2.IsNotEmpty()) - { - return result2; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - Error?.Invoke(this, new ErrorEventArgs(ex)); - if (ex.InnerException != null) - { - Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); - } - } + var progress = new Progress(); + progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new RetResult(value > 100, $"...{value}%")); - return null; + var webProxy = await GetWebProxy(blProxy); + await DownloaderHelper.Instance.DownloadFileAsync(webProxy, + url, + fileName, + progress, + downloadTimeout); } - - /// - /// DownloadString - /// - /// - private async Task DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout) + catch (Exception ex) { - try - { - SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); - var webProxy = await GetWebProxy(blProxy); - var client = new HttpClient(new SocketsHttpHandler() - { - Proxy = webProxy, - UseProxy = webProxy != null - }); + Logging.SaveLog(_tag, ex); - if (userAgent.IsNullOrEmpty()) - { - userAgent = Utils.GetVersion(false); - } - client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); - - Uri uri = new(url); - //Authorization Header - if (uri.UserInfo.IsNotEmpty()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo)); - } - - using var cts = new CancellationTokenSource(); - var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); - return result; - } - catch (Exception ex) + Error?.Invoke(this, new ErrorEventArgs(ex)); + if (ex.InnerException != null) { - Logging.SaveLog(_tag, ex); - Error?.Invoke(this, new ErrorEventArgs(ex)); - if (ex.InnerException != null) - { - Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); - } + Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } - return null; - } - - /// - /// DownloadString - /// - /// - private async Task DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout) - { - try - { - SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); - - var webProxy = await GetWebProxy(blProxy); - - if (userAgent.IsNullOrEmpty()) - { - userAgent = Utils.GetVersion(false); - } - var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, timeout); - return result; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - Error?.Invoke(this, new ErrorEventArgs(ex)); - if (ex.InnerException != null) - { - Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); - } - } - return null; - } - - public async Task RunAvailabilityCheck(IWebProxy? webProxy) - { - var responseTime = -1; - try - { - webProxy ??= await GetWebProxy(true); - var config = AppHandler.Instance.Config; - - for (var i = 0; i < 2; i++) - { - responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); - if (responseTime > 0) - { - break; - } - await Task.Delay(500); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - return -1; - } - return responseTime; - } - - public async Task GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout) - { - var responseTime = -1; - try - { - using var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout)); - using var client = new HttpClient(new SocketsHttpHandler() - { - Proxy = webProxy, - UseProxy = webProxy != null - }); - - List oneTime = new(); - for (var i = 0; i < 2; i++) - { - var timer = Stopwatch.StartNew(); - await client.GetAsync(url, cts.Token).ConfigureAwait(false); - timer.Stop(); - oneTime.Add((int)timer.Elapsed.TotalMilliseconds); - await Task.Delay(100); - } - responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault(); - } - catch //(Exception ex) - { - //Utile.SaveLog(ex.Message, ex); - } - return responseTime; - } - - private async Task GetWebProxy(bool blProxy) - { - if (!blProxy) - { - return null; - } - var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - if (await SocketCheck(Global.Loopback, port) == false) - { - return null; - } - - return new WebProxy($"socks5://{Global.Loopback}:{port}"); - } - - private async Task SocketCheck(string ip, int port) - { - try - { - IPEndPoint point = new(IPAddress.Parse(ip), port); - using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await sock.ConnectAsync(point); - return true; - } - catch (Exception) - { - return false; - } - } - - private static void SetSecurityProtocol(bool enableSecurityProtocolTls13) - { - if (enableSecurityProtocolTls13) - { - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; - } - else - { - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - } - ServicePointManager.DefaultConnectionLimit = 256; } } + + public async Task UrlRedirectAsync(string url, bool blProxy) + { + SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); + var webRequestHandler = new SocketsHttpHandler + { + AllowAutoRedirect = false, + Proxy = await GetWebProxy(blProxy) + }; + HttpClient client = new(webRequestHandler); + + var response = await client.GetAsync(url); + if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null) + { + return response.Headers.Location.ToString(); + } + else + { + Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode))); + Logging.SaveLog("StatusCode error: " + url); + return null; + } + } + + public async Task TryDownloadString(string url, bool blProxy, string userAgent) + { + try + { + var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); + if (result1.IsNotEmpty()) + { + return result1; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + Error?.Invoke(this, new ErrorEventArgs(ex)); + if (ex.InnerException != null) + { + Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); + } + } + + try + { + var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); + if (result2.IsNotEmpty()) + { + return result2; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + Error?.Invoke(this, new ErrorEventArgs(ex)); + if (ex.InnerException != null) + { + Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); + } + } + + return null; + } + + /// + /// DownloadString + /// + /// + private async Task DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout) + { + try + { + SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); + var webProxy = await GetWebProxy(blProxy); + var client = new HttpClient(new SocketsHttpHandler() + { + Proxy = webProxy, + UseProxy = webProxy != null + }); + + if (userAgent.IsNullOrEmpty()) + { + userAgent = Utils.GetVersion(false); + } + client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); + + Uri uri = new(url); + //Authorization Header + if (uri.UserInfo.IsNotEmpty()) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo)); + } + + using var cts = new CancellationTokenSource(); + var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); + return result; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + Error?.Invoke(this, new ErrorEventArgs(ex)); + if (ex.InnerException != null) + { + Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); + } + } + return null; + } + + /// + /// DownloadString + /// + /// + private async Task DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout) + { + try + { + SetSecurityProtocol(AppHandler.Instance.Config.GuiItem.EnableSecurityProtocolTls13); + + var webProxy = await GetWebProxy(blProxy); + + if (userAgent.IsNullOrEmpty()) + { + userAgent = Utils.GetVersion(false); + } + var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, timeout); + return result; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + Error?.Invoke(this, new ErrorEventArgs(ex)); + if (ex.InnerException != null) + { + Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); + } + } + return null; + } + + public async Task RunAvailabilityCheck(IWebProxy? webProxy) + { + var responseTime = -1; + try + { + webProxy ??= await GetWebProxy(true); + var config = AppHandler.Instance.Config; + + for (var i = 0; i < 2; i++) + { + responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); + if (responseTime > 0) + { + break; + } + await Task.Delay(500); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + return -1; + } + return responseTime; + } + + public async Task GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout) + { + var responseTime = -1; + try + { + using var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout)); + using var client = new HttpClient(new SocketsHttpHandler() + { + Proxy = webProxy, + UseProxy = webProxy != null + }); + + List oneTime = new(); + for (var i = 0; i < 2; i++) + { + var timer = Stopwatch.StartNew(); + await client.GetAsync(url, cts.Token).ConfigureAwait(false); + timer.Stop(); + oneTime.Add((int)timer.Elapsed.TotalMilliseconds); + await Task.Delay(100); + } + responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault(); + } + catch //(Exception ex) + { + //Utile.SaveLog(ex.Message, ex); + } + return responseTime; + } + + private async Task GetWebProxy(bool blProxy) + { + if (!blProxy) + { + return null; + } + var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); + if (await SocketCheck(Global.Loopback, port) == false) + { + return null; + } + + return new WebProxy($"socks5://{Global.Loopback}:{port}"); + } + + private async Task SocketCheck(string ip, int port) + { + try + { + IPEndPoint point = new(IPAddress.Parse(ip), port); + using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await sock.ConnectAsync(point); + return true; + } + catch (Exception) + { + return false; + } + } + + private static void SetSecurityProtocol(bool enableSecurityProtocolTls13) + { + if (enableSecurityProtocolTls13) + { + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; + } + else + { + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + } + ServicePointManager.DefaultConnectionLimit = 256; + } } diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index e9ab1344..2a605d36 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -3,382 +3,381 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; -namespace ServiceLib.Services +namespace ServiceLib.Services; + +public class SpeedtestService { - public class SpeedtestService + private static readonly string _tag = "SpeedtestService"; + private Config? _config; + private Action? _updateFunc; + private static readonly ConcurrentBag _lstExitLoop = new(); + + public SpeedtestService(Config config, Action updateFunc) { - private static readonly string _tag = "SpeedtestService"; - private Config? _config; - private Action? _updateFunc; - private static readonly ConcurrentBag _lstExitLoop = new(); + _config = config; + _updateFunc = updateFunc; + } - public SpeedtestService(Config config, Action updateFunc) + public void RunLoop(ESpeedActionType actionType, List selecteds) + { + Task.Run(async () => { - _config = config; - _updateFunc = updateFunc; + await RunAsync(actionType, selecteds); + await ProfileExHandler.Instance.SaveTo(); + UpdateFunc("", ResUI.SpeedtestingCompleted); + }); + } + + public void ExitLoop() + { + if (_lstExitLoop.Count > 0) + { + UpdateFunc("", ResUI.SpeedtestingStop); + + _lstExitLoop.Clear(); } + } - public void RunLoop(ESpeedActionType actionType, List selecteds) + private async Task RunAsync(ESpeedActionType actionType, List selecteds) + { + var exitLoopKey = Utils.GetGuid(false); + _lstExitLoop.Add(exitLoopKey); + + var lstSelected = GetClearItem(actionType, selecteds); + + switch (actionType) { - Task.Run(async () => + case ESpeedActionType.Tcping: + await RunTcpingAsync(lstSelected); + break; + + case ESpeedActionType.Realping: + await RunRealPingBatchAsync(lstSelected, exitLoopKey); + break; + + case ESpeedActionType.Speedtest: + await RunMixedTestAsync(lstSelected, 1, true, exitLoopKey); + break; + + case ESpeedActionType.Mixedtest: + await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, true, exitLoopKey); + break; + } + } + + private List GetClearItem(ESpeedActionType actionType, List selecteds) + { + var lstSelected = new List(); + foreach (var it in selecteds) + { + if (it.ConfigType == EConfigType.Custom) { - await RunAsync(actionType, selecteds); - await ProfileExHandler.Instance.SaveTo(); - UpdateFunc("", ResUI.SpeedtestingCompleted); + continue; + } + + if (it.Port <= 0) + { + continue; + } + + lstSelected.Add(new ServerTestItem() + { + IndexId = it.IndexId, + Address = it.Address, + Port = it.Port, + ConfigType = it.ConfigType, + QueueNum = selecteds.IndexOf(it) }); } - public void ExitLoop() + //clear test result + foreach (var it in lstSelected) { - if (_lstExitLoop.Count > 0) - { - UpdateFunc("", ResUI.SpeedtestingStop); - - _lstExitLoop.Clear(); - } - } - - private async Task RunAsync(ESpeedActionType actionType, List selecteds) - { - var exitLoopKey = Utils.GetGuid(false); - _lstExitLoop.Add(exitLoopKey); - - var lstSelected = GetClearItem(actionType, selecteds); - switch (actionType) { case ESpeedActionType.Tcping: - await RunTcpingAsync(lstSelected); - break; - case ESpeedActionType.Realping: - await RunRealPingBatchAsync(lstSelected, exitLoopKey); + UpdateFunc(it.IndexId, ResUI.Speedtesting, ""); + ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); break; case ESpeedActionType.Speedtest: - await RunMixedTestAsync(lstSelected, 1, true, exitLoopKey); + UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait); + ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); break; case ESpeedActionType.Mixedtest: - await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, true, exitLoopKey); + UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait); + ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); + ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); break; } } - private List GetClearItem(ESpeedActionType actionType, List selecteds) + return lstSelected; + } + + private async Task RunTcpingAsync(List selecteds) + { + List tasks = []; + foreach (var it in selecteds) { - var lstSelected = new List(); - foreach (var it in selecteds) + if (it.ConfigType == EConfigType.Custom) { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - - if (it.Port <= 0) - { - continue; - } - - lstSelected.Add(new ServerTestItem() - { - IndexId = it.IndexId, - Address = it.Address, - Port = it.Port, - ConfigType = it.ConfigType, - QueueNum = selecteds.IndexOf(it) - }); + continue; } - - //clear test result - foreach (var it in lstSelected) + tasks.Add(Task.Run(async () => { - switch (actionType) + try { - case ESpeedActionType.Tcping: - case ESpeedActionType.Realping: - UpdateFunc(it.IndexId, ResUI.Speedtesting, ""); - ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); - break; + var responseTime = await GetTcpingTime(it.Address, it.Port); - case ESpeedActionType.Speedtest: - UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait); - ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); - break; - - case ESpeedActionType.Mixedtest: - UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait); - ProfileExHandler.Instance.SetTestDelay(it.IndexId, 0); - ProfileExHandler.Instance.SetTestSpeed(it.IndexId, 0); - break; + ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); + UpdateFunc(it.IndexId, responseTime.ToString()); } - } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + })); + } + await Task.WhenAll(tasks); + } - return lstSelected; + private async Task RunRealPingBatchAsync(List lstSelected, string exitLoopKey, int pageSize = 0) + { + if (pageSize <= 0) + { + pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; + } + var lstTest = GetTestBatchItem(lstSelected, pageSize); + + List lstFailed = new(); + foreach (var lst in lstTest) + { + var ret = await RunRealPingAsync(lst, exitLoopKey); + if (ret == false) + { + lstFailed.AddRange(lst); + } + await Task.Delay(100); } - private async Task RunTcpingAsync(List selecteds) + //Retest the failed part + var pageSizeNext = pageSize / 2; + if (lstFailed.Count > 0 && pageSizeNext > 0) { - List tasks = []; - foreach (var it in selecteds) + if (_lstExitLoop.Any(p => p == exitLoopKey) == false) { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - tasks.Add(Task.Run(async () => - { - try - { - var responseTime = await GetTcpingTime(it.Address, it.Port); - - ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); - UpdateFunc(it.IndexId, responseTime.ToString()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - })); - } - await Task.WhenAll(tasks); - } - - private async Task RunRealPingBatchAsync(List lstSelected, string exitLoopKey, int pageSize = 0) - { - if (pageSize <= 0) - { - pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; - } - var lstTest = GetTestBatchItem(lstSelected, pageSize); - - List lstFailed = new(); - foreach (var lst in lstTest) - { - var ret = await RunRealPingAsync(lst, exitLoopKey); - if (ret == false) - { - lstFailed.AddRange(lst); - } - await Task.Delay(100); + UpdateFunc("", ResUI.SpeedtestingSkip); + return; } - //Retest the failed part - var pageSizeNext = pageSize / 2; - if (lstFailed.Count > 0 && pageSizeNext > 0) + UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count)); + + if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount) { - if (_lstExitLoop.Any(p => p == exitLoopKey) == false) - { - UpdateFunc("", ResUI.SpeedtestingSkip); - return; - } - - UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count)); - - if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount) - { - await RunRealPingBatchAsync(lstFailed, exitLoopKey, pageSizeNext); - } - else - { - await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, false, exitLoopKey); - } + await RunRealPingBatchAsync(lstFailed, exitLoopKey, pageSizeNext); } - } - - private async Task RunRealPingAsync(List selecteds, string exitLoopKey) - { - var pid = -1; - try + else { - pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(selecteds); - if (pid < 0) - { - return false; - } - - var downloadHandle = new DownloadService(); - - List tasks = new(); - foreach (var it in selecteds) - { - if (!it.AllowTest) - { - continue; - } - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - tasks.Add(Task.Run(async () => - { - await DoRealPing(downloadHandle, it); - })); - } - await Task.WhenAll(tasks); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - finally - { - if (pid > 0) - { - await ProcUtils.ProcessKill(pid); - } - } - return true; - } - - private async Task RunMixedTestAsync(List selecteds, int concurrencyCount, bool blSpeedTest, string exitLoopKey) - { - using var concurrencySemaphore = new SemaphoreSlim(concurrencyCount); - var downloadHandle = new DownloadService(); - List tasks = new(); - foreach (var it in selecteds) - { - if (_lstExitLoop.Any(p => p == exitLoopKey) == false) - { - UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); - continue; - } - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - await concurrencySemaphore.WaitAsync(); - - tasks.Add(Task.Run(async () => - { - var pid = -1; - try - { - pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it); - if (pid > 0) - { - await Task.Delay(500); - var delay = await DoRealPing(downloadHandle, it); - if (blSpeedTest) - { - if (delay > 0) - { - await DoSpeedTest(downloadHandle, it); - } - else - { - UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); - } - } - } - else - { - UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - finally - { - if (pid > 0) - { - await ProcUtils.ProcessKill(pid); - } - concurrencySemaphore.Release(); - } - })); - } - await Task.WhenAll(tasks); - } - - private async Task DoRealPing(DownloadService downloadHandle, ServerTestItem it) - { - var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); - var responseTime = await downloadHandle.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); - - ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); - UpdateFunc(it.IndexId, responseTime.ToString()); - return responseTime; - } - - private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it) - { - UpdateFunc(it.IndexId, "", ResUI.Speedtesting); - - var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); - var url = _config.SpeedTestItem.SpeedTestUrl; - var timeout = _config.SpeedTestItem.SpeedTestTimeout; - await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (success, msg) => - { - decimal.TryParse(msg, out var dec); - if (dec > 0) - { - ProfileExHandler.Instance.SetTestSpeed(it.IndexId, dec); - } - UpdateFunc(it.IndexId, "", msg); - }); - } - - private async Task GetTcpingTime(string url, int port) - { - var responseTime = -1; - - try - { - if (!IPAddress.TryParse(url, out var ipAddress)) - { - var ipHostInfo = await Dns.GetHostEntryAsync(url); - ipAddress = ipHostInfo.AddressList.First(); - } - - IPEndPoint endPoint = new(ipAddress, port); - using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - var timer = Stopwatch.StartNew(); - var result = clientSocket.BeginConnect(endPoint, null, null); - if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5))) - { - throw new TimeoutException("connect timeout (5s): " + url); - } - timer.Stop(); - responseTime = (int)timer.Elapsed.TotalMilliseconds; - - clientSocket.EndConnect(result); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return responseTime; - } - - private List> GetTestBatchItem(List lstSelected, int pageSize) - { - List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard).ToList(); - - for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) - { - lstTest.Add(lst1.Skip(num * pageSize).Take(pageSize).ToList()); - } - for (var num = 0; num < (int)Math.Ceiling(lst2.Count * 1.0 / pageSize); num++) - { - lstTest.Add(lst2.Skip(num * pageSize).Take(pageSize).ToList()); - } - - return lstTest; - } - - private void UpdateFunc(string indexId, string delay, string speed = "") - { - _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed }); - if (indexId.IsNotEmpty() && speed.IsNotEmpty()) - { - ProfileExHandler.Instance.SetTestMessage(indexId, speed); + await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, false, exitLoopKey); } } } + + private async Task RunRealPingAsync(List selecteds, string exitLoopKey) + { + var pid = -1; + try + { + pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(selecteds); + if (pid < 0) + { + return false; + } + + var downloadHandle = new DownloadService(); + + List tasks = new(); + foreach (var it in selecteds) + { + if (!it.AllowTest) + { + continue; + } + if (it.ConfigType == EConfigType.Custom) + { + continue; + } + tasks.Add(Task.Run(async () => + { + await DoRealPing(downloadHandle, it); + })); + } + await Task.WhenAll(tasks); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + finally + { + if (pid > 0) + { + await ProcUtils.ProcessKill(pid); + } + } + return true; + } + + private async Task RunMixedTestAsync(List selecteds, int concurrencyCount, bool blSpeedTest, string exitLoopKey) + { + using var concurrencySemaphore = new SemaphoreSlim(concurrencyCount); + var downloadHandle = new DownloadService(); + List tasks = new(); + foreach (var it in selecteds) + { + if (_lstExitLoop.Any(p => p == exitLoopKey) == false) + { + UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); + continue; + } + if (it.ConfigType == EConfigType.Custom) + { + continue; + } + await concurrencySemaphore.WaitAsync(); + + tasks.Add(Task.Run(async () => + { + var pid = -1; + try + { + pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it); + if (pid > 0) + { + await Task.Delay(500); + var delay = await DoRealPing(downloadHandle, it); + if (blSpeedTest) + { + if (delay > 0) + { + await DoSpeedTest(downloadHandle, it); + } + else + { + UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); + } + } + } + else + { + UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + finally + { + if (pid > 0) + { + await ProcUtils.ProcessKill(pid); + } + concurrencySemaphore.Release(); + } + })); + } + await Task.WhenAll(tasks); + } + + private async Task DoRealPing(DownloadService downloadHandle, ServerTestItem it) + { + var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); + var responseTime = await downloadHandle.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); + + ProfileExHandler.Instance.SetTestDelay(it.IndexId, responseTime); + UpdateFunc(it.IndexId, responseTime.ToString()); + return responseTime; + } + + private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it) + { + UpdateFunc(it.IndexId, "", ResUI.Speedtesting); + + var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); + var url = _config.SpeedTestItem.SpeedTestUrl; + var timeout = _config.SpeedTestItem.SpeedTestTimeout; + await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (success, msg) => + { + decimal.TryParse(msg, out var dec); + if (dec > 0) + { + ProfileExHandler.Instance.SetTestSpeed(it.IndexId, dec); + } + UpdateFunc(it.IndexId, "", msg); + }); + } + + private async Task GetTcpingTime(string url, int port) + { + var responseTime = -1; + + try + { + if (!IPAddress.TryParse(url, out var ipAddress)) + { + var ipHostInfo = await Dns.GetHostEntryAsync(url); + ipAddress = ipHostInfo.AddressList.First(); + } + + IPEndPoint endPoint = new(ipAddress, port); + using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + var timer = Stopwatch.StartNew(); + var result = clientSocket.BeginConnect(endPoint, null, null); + if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5))) + { + throw new TimeoutException("connect timeout (5s): " + url); + } + timer.Stop(); + responseTime = (int)timer.Elapsed.TotalMilliseconds; + + clientSocket.EndConnect(result); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return responseTime; + } + + private List> GetTestBatchItem(List lstSelected, int pageSize) + { + List> lstTest = new(); + var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard)).ToList(); + var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard).ToList(); + + for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) + { + lstTest.Add(lst1.Skip(num * pageSize).Take(pageSize).ToList()); + } + for (var num = 0; num < (int)Math.Ceiling(lst2.Count * 1.0 / pageSize); num++) + { + lstTest.Add(lst2.Skip(num * pageSize).Take(pageSize).ToList()); + } + + return lstTest; + } + + private void UpdateFunc(string indexId, string delay, string speed = "") + { + _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed }); + if (indexId.IsNotEmpty() && speed.IsNotEmpty()) + { + ProfileExHandler.Instance.SetTestMessage(indexId, speed); + } + } } diff --git a/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs b/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs index b09402a0..f419f831 100644 --- a/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs +++ b/v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs @@ -1,127 +1,126 @@ using System.Net.WebSockets; using System.Text; -namespace ServiceLib.Services.Statistics +namespace ServiceLib.Services.Statistics; + +public class StatisticsSingboxService { - public class StatisticsSingboxService + private readonly Config _config; + private bool _exitFlag; + private ClientWebSocket? webSocket; + private Action? _updateFunc; + private string Url => $"ws://{Global.Loopback}:{AppHandler.Instance.StatePort2}/traffic"; + private static readonly string _tag = "StatisticsSingboxService"; + + public StatisticsSingboxService(Config config, Action updateFunc) { - private readonly Config _config; - private bool _exitFlag; - private ClientWebSocket? webSocket; - private Action? _updateFunc; - private string Url => $"ws://{Global.Loopback}:{AppHandler.Instance.StatePort2}/traffic"; - private static readonly string _tag = "StatisticsSingboxService"; + _config = config; + _updateFunc = updateFunc; + _exitFlag = false; - public StatisticsSingboxService(Config config, Action updateFunc) + _ = Task.Run(Run); + } + + private async Task Init() + { + await Task.Delay(5000); + + try { - _config = config; - _updateFunc = updateFunc; - _exitFlag = false; - - _ = Task.Run(Run); - } - - private async Task Init() - { - await Task.Delay(5000); - - try + if (webSocket == null) { - if (webSocket == null) - { - webSocket = new ClientWebSocket(); - await webSocket.ConnectAsync(new Uri(Url), CancellationToken.None); - } + webSocket = new ClientWebSocket(); + await webSocket.ConnectAsync(new Uri(Url), CancellationToken.None); } - catch { } } + catch { } + } - public void Close() + public void Close() + { + try { + _exitFlag = true; + if (webSocket != null) + { + webSocket.Abort(); + webSocket = null; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private async Task Run() + { + await Init(); + + while (!_exitFlag) + { + await Task.Delay(1000); try { - _exitFlag = true; + if (!_config.IsRunningCore(ECoreType.sing_box)) + { + continue; + } if (webSocket != null) { - webSocket.Abort(); - webSocket = null; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } + if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed) + { + webSocket.Abort(); + webSocket = null; + await Init(); + continue; + } - private async Task Run() - { - await Init(); - - while (!_exitFlag) - { - await Task.Delay(1000); - try - { - if (!_config.IsRunningCore(ECoreType.sing_box)) + if (webSocket.State != WebSocketState.Open) { continue; } - if (webSocket != null) + + var buffer = new byte[1024]; + var res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + while (!res.CloseStatus.HasValue) { - if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed) + var result = Encoding.UTF8.GetString(buffer, 0, res.Count); + if (result.IsNotEmpty()) { - webSocket.Abort(); - webSocket = null; - await Init(); - continue; - } + ParseOutput(result, out var up, out var down); - if (webSocket.State != WebSocketState.Open) - { - continue; - } - - var buffer = new byte[1024]; - var res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - while (!res.CloseStatus.HasValue) - { - var result = Encoding.UTF8.GetString(buffer, 0, res.Count); - if (result.IsNotEmpty()) + _updateFunc?.Invoke(new ServerSpeedItem() { - ParseOutput(result, out var up, out var down); - - _updateFunc?.Invoke(new ServerSpeedItem() - { - ProxyUp = (long)(up / 1000), - ProxyDown = (long)(down / 1000) - }); - } - res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + ProxyUp = (long)(up / 1000), + ProxyDown = (long)(down / 1000) + }); } + res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); } } - catch - { - } - } - } - - private void ParseOutput(string source, out ulong up, out ulong down) - { - up = 0; - down = 0; - try - { - var trafficItem = JsonUtils.Deserialize(source); - if (trafficItem != null) - { - up = trafficItem.Up; - down = trafficItem.Down; - } } catch { } } } + + private void ParseOutput(string source, out ulong up, out ulong down) + { + up = 0; + down = 0; + try + { + var trafficItem = JsonUtils.Deserialize(source); + if (trafficItem != null) + { + up = trafficItem.Up; + down = trafficItem.Down; + } + } + catch + { + } + } } diff --git a/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs b/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs index d74a3e48..743114c6 100644 --- a/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs +++ b/v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs @@ -1,108 +1,107 @@ -namespace ServiceLib.Services.Statistics +namespace ServiceLib.Services.Statistics; + +public class StatisticsXrayService { - public class StatisticsXrayService + private const long linkBase = 1024; + private ServerSpeedItem _serverSpeedItem = new(); + private readonly Config _config; + private bool _exitFlag; + private Action? _updateFunc; + private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort}/debug/vars"; + + public StatisticsXrayService(Config config, Action updateFunc) { - private const long linkBase = 1024; - private ServerSpeedItem _serverSpeedItem = new(); - private readonly Config _config; - private bool _exitFlag; - private Action? _updateFunc; - private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort}/debug/vars"; + _config = config; + _updateFunc = updateFunc; + _exitFlag = false; - public StatisticsXrayService(Config config, Action updateFunc) - { - _config = config; - _updateFunc = updateFunc; - _exitFlag = false; - - _ = Task.Run(Run); - } - - public void Close() - { - _exitFlag = true; - } - - private async Task Run() - { - while (!_exitFlag) - { - await Task.Delay(1000); - try - { - if (_config.RunningCoreType != ECoreType.Xray) - { - continue; - } - - var result = await HttpClientHelper.Instance.TryGetAsync(Url); - if (result != null) - { - var server = ParseOutput(result) ?? new ServerSpeedItem(); - _updateFunc?.Invoke(server); - } - } - catch - { - // ignored - } - } - } - - private ServerSpeedItem? ParseOutput(string result) + _ = Task.Run(Run); + } + + public void Close() + { + _exitFlag = true; + } + + private async Task Run() + { + while (!_exitFlag) { + await Task.Delay(1000); try { - var source = JsonUtils.Deserialize(result); - if (source?.stats?.outbound == null) + if (_config.RunningCoreType != ECoreType.Xray) { - return null; + continue; } - ServerSpeedItem server = new(); - foreach (var key in source.stats.outbound.Keys.Cast()) + var result = await HttpClientHelper.Instance.TryGetAsync(Url); + if (result != null) { - var value = source.stats.outbound[key]; - if (value == null) - { - continue; - } - var state = JsonUtils.Deserialize(value.ToString()); - - if (key.StartsWith(Global.ProxyTag)) - { - server.ProxyUp += state.uplink / linkBase; - server.ProxyDown += state.downlink / linkBase; - } - else if (key == Global.DirectTag) - { - server.DirectUp = state.uplink / linkBase; - server.DirectDown = state.downlink / linkBase; - } + var server = ParseOutput(result) ?? new ServerSpeedItem(); + _updateFunc?.Invoke(server); } - - if (server.DirectDown < _serverSpeedItem.DirectDown || server.ProxyDown < _serverSpeedItem.ProxyDown) - { - _serverSpeedItem = new(); - return null; - } - - ServerSpeedItem curItem = new() - { - ProxyUp = server.ProxyUp - _serverSpeedItem.ProxyUp, - ProxyDown = server.ProxyDown - _serverSpeedItem.ProxyDown, - DirectUp = server.DirectUp - _serverSpeedItem.DirectUp, - DirectDown = server.DirectDown - _serverSpeedItem.DirectDown, - }; - _serverSpeedItem = server; - return curItem; } catch { // ignored } - - return null; } } + + private ServerSpeedItem? ParseOutput(string result) + { + try + { + var source = JsonUtils.Deserialize(result); + if (source?.stats?.outbound == null) + { + return null; + } + + ServerSpeedItem server = new(); + foreach (var key in source.stats.outbound.Keys.Cast()) + { + var value = source.stats.outbound[key]; + if (value == null) + { + continue; + } + var state = JsonUtils.Deserialize(value.ToString()); + + if (key.StartsWith(Global.ProxyTag)) + { + server.ProxyUp += state.uplink / linkBase; + server.ProxyDown += state.downlink / linkBase; + } + else if (key == Global.DirectTag) + { + server.DirectUp = state.uplink / linkBase; + server.DirectDown = state.downlink / linkBase; + } + } + + if (server.DirectDown < _serverSpeedItem.DirectDown || server.ProxyDown < _serverSpeedItem.ProxyDown) + { + _serverSpeedItem = new(); + return null; + } + + ServerSpeedItem curItem = new() + { + ProxyUp = server.ProxyUp - _serverSpeedItem.ProxyUp, + ProxyDown = server.ProxyDown - _serverSpeedItem.ProxyDown, + DirectUp = server.DirectUp - _serverSpeedItem.DirectUp, + DirectDown = server.DirectDown - _serverSpeedItem.DirectDown, + }; + _serverSpeedItem = server; + return curItem; + } + catch + { + // ignored + } + + return null; + } } diff --git a/v2rayN/ServiceLib/Services/UpdateService.cs b/v2rayN/ServiceLib/Services/UpdateService.cs index 0452f087..f896e4ad 100644 --- a/v2rayN/ServiceLib/Services/UpdateService.cs +++ b/v2rayN/ServiceLib/Services/UpdateService.cs @@ -1,618 +1,617 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; -namespace ServiceLib.Services +namespace ServiceLib.Services; + +public class UpdateService { - public class UpdateService + private Action? _updateFunc; + private int _timeout = 30; + private static readonly string _tag = "UpdateService"; + + public async Task CheckUpdateGuiN(Config config, Action updateFunc, bool preRelease) { - private Action? _updateFunc; - private int _timeout = 30; - private static readonly string _tag = "UpdateService"; + _updateFunc = updateFunc; + var url = string.Empty; + var fileName = string.Empty; - public async Task CheckUpdateGuiN(Config config, Action updateFunc, bool preRelease) + DownloadService downloadHandle = new(); + downloadHandle.UpdateCompleted += (sender2, args) => { - _updateFunc = updateFunc; - var url = string.Empty; - var fileName = string.Empty; - - DownloadService downloadHandle = new(); - downloadHandle.UpdateCompleted += (sender2, args) => + if (args.Success) { - if (args.Success) - { - _updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully); - _updateFunc?.Invoke(true, Utils.UrlEncode(fileName)); - } - else - { - _updateFunc?.Invoke(false, args.Msg); - } - }; - downloadHandle.Error += (sender2, args) => - { - _updateFunc?.Invoke(false, args.GetException().Message); - }; - - _updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN)); - var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease); - if (result.Success) - { - _updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN)); - _updateFunc?.Invoke(false, result.Msg); - - url = result.Data?.ToString(); - fileName = Utils.GetTempPath(Utils.GetGuid()); - await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); + _updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully); + _updateFunc?.Invoke(true, Utils.UrlEncode(fileName)); } else { - _updateFunc?.Invoke(false, result.Msg); + _updateFunc?.Invoke(false, args.Msg); } - } - - public async Task CheckUpdateCore(ECoreType type, Config config, Action updateFunc, bool preRelease) + }; + downloadHandle.Error += (sender2, args) => { - _updateFunc = updateFunc; - var url = string.Empty; - var fileName = string.Empty; + _updateFunc?.Invoke(false, args.GetException().Message); + }; - DownloadService downloadHandle = new(); - downloadHandle.UpdateCompleted += (sender2, args) => + _updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN)); + var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease); + if (result.Success) + { + _updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN)); + _updateFunc?.Invoke(false, result.Msg); + + url = result.Data?.ToString(); + fileName = Utils.GetTempPath(Utils.GetGuid()); + await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); + } + else + { + _updateFunc?.Invoke(false, result.Msg); + } + } + + public async Task CheckUpdateCore(ECoreType type, Config config, Action updateFunc, bool preRelease) + { + _updateFunc = updateFunc; + var url = string.Empty; + var fileName = string.Empty; + + DownloadService downloadHandle = new(); + downloadHandle.UpdateCompleted += (sender2, args) => + { + if (args.Success) { - if (args.Success) + _updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully); + _updateFunc?.Invoke(false, ResUI.MsgUnpacking); + + try { - _updateFunc?.Invoke(false, ResUI.MsgDownloadV2rayCoreSuccessfully); - _updateFunc?.Invoke(false, ResUI.MsgUnpacking); - - try - { - _updateFunc?.Invoke(true, fileName); - } - catch (Exception ex) - { - _updateFunc?.Invoke(false, ex.Message); - } + _updateFunc?.Invoke(true, fileName); } - else + catch (Exception ex) { - _updateFunc?.Invoke(false, args.Msg); + _updateFunc?.Invoke(false, ex.Message); } - }; - downloadHandle.Error += (sender2, args) => - { - _updateFunc?.Invoke(false, args.GetException().Message); - }; - - _updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, type)); - var result = await CheckUpdateAsync(downloadHandle, type, preRelease); - if (result.Success) - { - _updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, type)); - _updateFunc?.Invoke(false, result.Msg); - - url = result.Data?.ToString(); - var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url); - fileName = Utils.GetTempPath(Utils.GetGuid() + ext); - await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); } else { - if (!result.Msg.IsNullOrEmpty()) - { - _updateFunc?.Invoke(false, result.Msg); - } + _updateFunc?.Invoke(false, args.Msg); } - } - - public async Task UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action updateFunc) + }; + downloadHandle.Error += (sender2, args) => { - _updateFunc = updateFunc; + _updateFunc?.Invoke(false, args.GetException().Message); + }; - _updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart); - var subItem = await AppHandler.Instance.SubItems(); + _updateFunc?.Invoke(false, string.Format(ResUI.MsgStartUpdating, type)); + var result = await CheckUpdateAsync(downloadHandle, type, preRelease); + if (result.Success) + { + _updateFunc?.Invoke(false, string.Format(ResUI.MsgParsingSuccessfully, type)); + _updateFunc?.Invoke(false, result.Msg); - if (subItem is not { Count: > 0 }) + url = result.Data?.ToString(); + var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url); + fileName = Utils.GetTempPath(Utils.GetGuid() + ext); + await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); + } + else + { + if (!result.Msg.IsNullOrEmpty()) { - _updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription); - return; + _updateFunc?.Invoke(false, result.Msg); } + } + } - foreach (var item in subItem) + public async Task UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action updateFunc) + { + _updateFunc = updateFunc; + + _updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart); + var subItem = await AppHandler.Instance.SubItems(); + + if (subItem is not { Count: > 0 }) + { + _updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription); + return; + } + + foreach (var item in subItem) + { + var id = item.Id.TrimEx(); + var url = item.Url.TrimEx(); + var userAgent = item.UserAgent.TrimEx(); + var hashCode = $"{item.Remarks}->"; + if (id.IsNullOrEmpty() || url.IsNullOrEmpty() || (subId.IsNotEmpty() && item.Id != subId)) { - var id = item.Id.TrimEx(); - var url = item.Url.TrimEx(); - var userAgent = item.UserAgent.TrimEx(); - var hashCode = $"{item.Remarks}->"; - if (id.IsNullOrEmpty() || url.IsNullOrEmpty() || (subId.IsNotEmpty() && item.Id != subId)) - { - //_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}"); - continue; - } - if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol)) - { - continue; - } - if (item.Enabled == false) - { - _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); - continue; - } - - var downloadHandle = new DownloadService(); - downloadHandle.Error += (sender2, args) => - { - _updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); - }; - - _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); - - //one url - url = Utils.GetPunycode(url); - //convert - if (item.ConvertTarget.IsNotEmpty()) - { - var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl; - url = string.Format(subConvertUrl!, Utils.UrlEncode(url)); - if (!url.Contains("target=")) - { - url += string.Format("&target={0}", item.ConvertTarget); - } - if (!url.Contains("config=")) - { - url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); - } - } - var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); - if (blProxy && result.IsNullOrEmpty()) - { - result = await downloadHandle.TryDownloadString(url, false, userAgent); - } - - //more url - if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) - { - if (result.IsNotEmpty() && Utils.IsBase64String(result)) - { - result = Utils.Base64Decode(result); - } - - var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; - foreach (var it in lstUrl) - { - var url2 = Utils.GetPunycode(it); - if (url2.IsNullOrEmpty()) - { - continue; - } - - var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent); - if (blProxy && result2.IsNullOrEmpty()) - { - result2 = await downloadHandle.TryDownloadString(url2, false, userAgent); - } - if (result2.IsNotEmpty()) - { - if (Utils.IsBase64String(result2)) - { - result += Utils.Base64Decode(result2); - } - else - { - result += result2; - } - } - } - } - - if (result.IsNullOrEmpty()) - { - _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); - } - else - { - _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); - if (result?.Length < 99) - { - _updateFunc?.Invoke(false, $"{hashCode}{result}"); - } - - var ret = await ConfigHandler.AddBatchServers(config, result, id, true); - if (ret <= 0) - { - Logging.SaveLog("FailedImportSubscription"); - Logging.SaveLog(result); - } - _updateFunc?.Invoke(false, - ret > 0 - ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" - : $"{hashCode}{ResUI.MsgFailedImportSubscription}"); - } - _updateFunc?.Invoke(false, "-------------------------------------------------------"); - - //await ConfigHandler.DedupServerList(config, id); + //_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}"); + continue; + } + if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol)) + { + continue; + } + if (item.Enabled == false) + { + _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); + continue; } - _updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}"); - } - - public async Task UpdateGeoFileAll(Config config, Action updateFunc) - { - await UpdateGeoFiles(config, updateFunc); - await UpdateOtherFiles(config, updateFunc); - await UpdateSrsFileAll(config, updateFunc); - _updateFunc?.Invoke(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo")); - } - - public async Task RunAvailabilityCheck() - { var downloadHandle = new DownloadService(); - var time = await downloadHandle.RunAvailabilityCheck(null); - var ip = Global.None; - if (time > 0) + downloadHandle.Error += (sender2, args) => { - var result = await downloadHandle.TryDownloadString(Global.IPAPIUrl, true, Global.IPAPIUrl); - var ipInfo = JsonUtils.Deserialize(result); - ip = $"({ipInfo?.country_code}) {ipInfo?.ip}"; - } + _updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); + }; - return string.Format(ResUI.TestMeOutput, time, ip); - } + _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); - #region CheckUpdate private - - private async Task CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease) - { - try + //one url + url = Utils.GetPunycode(url); + //convert + if (item.ConvertTarget.IsNotEmpty()) { - var result = await GetRemoteVersion(downloadHandle, type, preRelease); - if (!result.Success || result.Data is null) + var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl; + url = string.Format(subConvertUrl!, Utils.UrlEncode(url)); + if (!url.Contains("target=")) { - return result; + url += string.Format("&target={0}", item.ConvertTarget); + } + if (!url.Contains("config=")) + { + url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); } - return await ParseDownloadUrl(type, (SemanticVersion)result.Data); } - catch (Exception ex) + var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); + if (blProxy && result.IsNullOrEmpty()) { - Logging.SaveLog(_tag, ex); - _updateFunc?.Invoke(false, ex.Message); - return new RetResult(false, ex.Message); + result = await downloadHandle.TryDownloadString(url, false, userAgent); } + + //more url + if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) + { + if (result.IsNotEmpty() && Utils.IsBase64String(result)) + { + result = Utils.Base64Decode(result); + } + + var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; + foreach (var it in lstUrl) + { + var url2 = Utils.GetPunycode(it); + if (url2.IsNullOrEmpty()) + { + continue; + } + + var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent); + if (blProxy && result2.IsNullOrEmpty()) + { + result2 = await downloadHandle.TryDownloadString(url2, false, userAgent); + } + if (result2.IsNotEmpty()) + { + if (Utils.IsBase64String(result2)) + { + result += Utils.Base64Decode(result2); + } + else + { + result += result2; + } + } + } + } + + if (result.IsNullOrEmpty()) + { + _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); + } + else + { + _updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); + if (result?.Length < 99) + { + _updateFunc?.Invoke(false, $"{hashCode}{result}"); + } + + var ret = await ConfigHandler.AddBatchServers(config, result, id, true); + if (ret <= 0) + { + Logging.SaveLog("FailedImportSubscription"); + Logging.SaveLog(result); + } + _updateFunc?.Invoke(false, + ret > 0 + ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" + : $"{hashCode}{ResUI.MsgFailedImportSubscription}"); + } + _updateFunc?.Invoke(false, "-------------------------------------------------------"); + + //await ConfigHandler.DedupServerList(config, id); } - private async Task GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease) + _updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}"); + } + + public async Task UpdateGeoFileAll(Config config, Action updateFunc) + { + await UpdateGeoFiles(config, updateFunc); + await UpdateOtherFiles(config, updateFunc); + await UpdateSrsFileAll(config, updateFunc); + _updateFunc?.Invoke(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo")); + } + + public async Task RunAvailabilityCheck() + { + var downloadHandle = new DownloadService(); + var time = await downloadHandle.RunAvailabilityCheck(null); + var ip = Global.None; + if (time > 0) + { + var result = await downloadHandle.TryDownloadString(Global.IPAPIUrl, true, Global.IPAPIUrl); + var ipInfo = JsonUtils.Deserialize(result); + ip = $"({ipInfo?.country_code}) {ipInfo?.ip}"; + } + + return string.Format(ResUI.TestMeOutput, time, ip); + } + + #region CheckUpdate private + + private async Task CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease) + { + try + { + var result = await GetRemoteVersion(downloadHandle, type, preRelease); + if (!result.Success || result.Data is null) + { + return result; + } + return await ParseDownloadUrl(type, (SemanticVersion)result.Data); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + _updateFunc?.Invoke(false, ex.Message); + return new RetResult(false, ex.Message); + } + } + + private async Task GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease) + { + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); + var tagName = string.Empty; + if (preRelease) + { + var url = coreInfo?.ReleaseApiUrl; + var result = await downloadHandle.TryDownloadString(url, true, Global.AppName); + if (result.IsNullOrEmpty()) + { + return new RetResult(false, ""); + } + + var gitHubReleases = JsonUtils.Deserialize>(result); + var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false); + tagName = gitHubRelease?.TagName; + //var body = gitHubRelease?.Body; + } + else + { + var url = Path.Combine(coreInfo.Url, "latest"); + var lastUrl = await downloadHandle.UrlRedirectAsync(url, true); + if (lastUrl == null) + { + return new RetResult(false, ""); + } + + tagName = lastUrl?.Split("/tag/").LastOrDefault(); + } + return new RetResult(true, "", new SemanticVersion(tagName)); + } + + private async Task GetCoreVersion(ECoreType type) + { + try { var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); - var tagName = string.Empty; - if (preRelease) + string filePath = string.Empty; + foreach (var name in coreInfo.CoreExes) { - var url = coreInfo?.ReleaseApiUrl; - var result = await downloadHandle.TryDownloadString(url, true, Global.AppName); - if (result.IsNullOrEmpty()) + var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); + if (File.Exists(vName)) { - return new RetResult(false, ""); + filePath = vName; + break; } - - var gitHubReleases = JsonUtils.Deserialize>(result); - var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false); - tagName = gitHubRelease?.TagName; - //var body = gitHubRelease?.Body; } - else + + if (!File.Exists(filePath)) { - var url = Path.Combine(coreInfo.Url, "latest"); - var lastUrl = await downloadHandle.UrlRedirectAsync(url, true); - if (lastUrl == null) - { - return new RetResult(false, ""); - } - - tagName = lastUrl?.Split("/tag/").LastOrDefault(); - } - return new RetResult(true, "", new SemanticVersion(tagName)); - } - - private async Task GetCoreVersion(ECoreType type) - { - try - { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); - string filePath = string.Empty; - foreach (var name in coreInfo.CoreExes) - { - var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); - if (File.Exists(vName)) - { - filePath = vName; - break; - } - } - - if (!File.Exists(filePath)) - { - string msg = string.Format(ResUI.NotFoundCore, @"", "", ""); - //ShowMsg(true, msg); - return new SemanticVersion(""); - } - - var result = await Utils.GetCliWrapOutput(filePath, coreInfo.VersionArg); - var echo = result ?? ""; - string version = string.Empty; - switch (type) - { - case ECoreType.v2fly: - case ECoreType.Xray: - case ECoreType.v2fly_v5: - version = Regex.Match(echo, $"{coreInfo.Match} ([0-9.]+) \\(").Groups[1].Value; - break; - - case ECoreType.mihomo: - version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value; - break; - - case ECoreType.sing_box: - version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value; - break; - } - return new SemanticVersion(version); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - _updateFunc?.Invoke(false, ex.Message); + string msg = string.Format(ResUI.NotFoundCore, @"", "", ""); + //ShowMsg(true, msg); return new SemanticVersion(""); } - } - private async Task ParseDownloadUrl(ECoreType type, SemanticVersion version) - { - try + var result = await Utils.GetCliWrapOutput(filePath, coreInfo.VersionArg); + var echo = result ?? ""; + string version = string.Empty; + switch (type) { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); - var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty; - SemanticVersion curVersion; - string message; - string? url; - switch (type) - { - case ECoreType.v2fly: - case ECoreType.Xray: - case ECoreType.v2fly_v5: - { - curVersion = await GetCoreVersion(type); - message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); - url = string.Format(coreUrl, version.ToVersionString("v")); - break; - } - case ECoreType.mihomo: - { - curVersion = await GetCoreVersion(type); - message = string.Format(ResUI.IsLatestCore, type, curVersion); - url = string.Format(coreUrl, version.ToVersionString("v")); - break; - } - case ECoreType.sing_box: - { - curVersion = await GetCoreVersion(type); - message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); - url = string.Format(coreUrl, version.ToVersionString("v"), version); - break; - } - case ECoreType.v2rayN: - { - curVersion = new SemanticVersion(Utils.GetVersionInfo()); - message = string.Format(ResUI.IsLatestN, type, curVersion); - url = string.Format(coreUrl, version); - break; - } - default: - throw new ArgumentException("Type"); - } + case ECoreType.v2fly: + case ECoreType.Xray: + case ECoreType.v2fly_v5: + version = Regex.Match(echo, $"{coreInfo.Match} ([0-9.]+) \\(").Groups[1].Value; + break; - if (curVersion >= version && version != new SemanticVersion(0, 0, 0)) - { - return new RetResult(false, message); - } + case ECoreType.mihomo: + version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value; + break; - return new RetResult(true, "", url); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - _updateFunc?.Invoke(false, ex.Message); - return new RetResult(false, ex.Message); + case ECoreType.sing_box: + version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value; + break; } + return new SemanticVersion(version); } - - private async Task GetUrlFromCore(CoreInfo? coreInfo) + catch (Exception ex) { - if (Utils.IsWindows()) + Logging.SaveLog(_tag, ex); + _updateFunc?.Invoke(false, ex.Message); + return new SemanticVersion(""); + } + } + + private async Task ParseDownloadUrl(ECoreType type, SemanticVersion version) + { + try + { + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type); + var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty; + SemanticVersion curVersion; + string message; + string? url; + switch (type) { - var url = RuntimeInformation.ProcessArchitecture switch - { - Architecture.Arm64 => coreInfo?.DownloadUrlWinArm64, - Architecture.X64 => coreInfo?.DownloadUrlWin64, - _ => null, - }; + case ECoreType.v2fly: + case ECoreType.Xray: + case ECoreType.v2fly_v5: + { + curVersion = await GetCoreVersion(type); + message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); + url = string.Format(coreUrl, version.ToVersionString("v")); + break; + } + case ECoreType.mihomo: + { + curVersion = await GetCoreVersion(type); + message = string.Format(ResUI.IsLatestCore, type, curVersion); + url = string.Format(coreUrl, version.ToVersionString("v")); + break; + } + case ECoreType.sing_box: + { + curVersion = await GetCoreVersion(type); + message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); + url = string.Format(coreUrl, version.ToVersionString("v"), version); + break; + } + case ECoreType.v2rayN: + { + curVersion = new SemanticVersion(Utils.GetVersionInfo()); + message = string.Format(ResUI.IsLatestN, type, curVersion); + url = string.Format(coreUrl, version); + break; + } + default: + throw new ArgumentException("Type"); + } - if (coreInfo?.CoreType != ECoreType.v2rayN) - { - return url; - } + if (curVersion >= version && version != new SemanticVersion(0, 0, 0)) + { + return new RetResult(false, message); + } - //Check for standalone windows .Net version - if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll")) - && File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll"))) - { - return url?.Replace(".zip", "-SelfContained.zip"); - } + return new RetResult(true, "", url); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + _updateFunc?.Invoke(false, ex.Message); + return new RetResult(false, ex.Message); + } + } - //Check for avalonia desktop windows version - if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll"))) - { - return url?.Replace(".zip", "-desktop.zip"); - } + private async Task GetUrlFromCore(CoreInfo? coreInfo) + { + if (Utils.IsWindows()) + { + var url = RuntimeInformation.ProcessArchitecture switch + { + Architecture.Arm64 => coreInfo?.DownloadUrlWinArm64, + Architecture.X64 => coreInfo?.DownloadUrlWin64, + _ => null, + }; + if (coreInfo?.CoreType != ECoreType.v2rayN) + { return url; } - else if (Utils.IsLinux()) + + //Check for standalone windows .Net version + if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll")) + && File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll"))) { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64, - Architecture.X64 => coreInfo?.DownloadUrlLinux64, - _ => null, - }; + return url?.Replace(".zip", "-SelfContained.zip"); } - else if (Utils.IsOSX()) + + //Check for avalonia desktop windows version + if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll"))) { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.Arm64 => coreInfo?.DownloadUrlOSXArm64, - Architecture.X64 => coreInfo?.DownloadUrlOSX64, - _ => null, - }; + return url?.Replace(".zip", "-desktop.zip"); } - return await Task.FromResult(""); + + return url; } - - #endregion CheckUpdate private - - #region Geo private - - private async Task UpdateGeoFiles(Config config, Action updateFunc) + else if (Utils.IsLinux()) { - _updateFunc = updateFunc; - - var geoUrl = string.IsNullOrEmpty(config?.ConstItem.GeoSourceUrl) - ? Global.GeoUrl - : config.ConstItem.GeoSourceUrl; - - List files = ["geosite", "geoip"]; - foreach (var geoName in files) + return RuntimeInformation.ProcessArchitecture switch { - var fileName = $"{geoName}.dat"; - var targetPath = Utils.GetBinPath($"{fileName}"); - var url = string.Format(geoUrl, geoName); - - await DownloadGeoFile(url, fileName, targetPath, updateFunc); - } + Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64, + Architecture.X64 => coreInfo?.DownloadUrlLinux64, + _ => null, + }; } - - private async Task UpdateOtherFiles(Config config, Action updateFunc) + else if (Utils.IsOSX()) { - _updateFunc = updateFunc; - - foreach (var url in Global.OtherGeoUrls) + return RuntimeInformation.ProcessArchitecture switch { - var fileName = Path.GetFileName(url); - var targetPath = Utils.GetBinPath($"{fileName}"); - - await DownloadGeoFile(url, fileName, targetPath, updateFunc); - } + Architecture.Arm64 => coreInfo?.DownloadUrlOSXArm64, + Architecture.X64 => coreInfo?.DownloadUrlOSX64, + _ => null, + }; } + return await Task.FromResult(""); + } - private async Task UpdateSrsFileAll(Config config, Action updateFunc) + #endregion CheckUpdate private + + #region Geo private + + private async Task UpdateGeoFiles(Config config, Action updateFunc) + { + _updateFunc = updateFunc; + + var geoUrl = string.IsNullOrEmpty(config?.ConstItem.GeoSourceUrl) + ? Global.GeoUrl + : config.ConstItem.GeoSourceUrl; + + List files = ["geosite", "geoip"]; + foreach (var geoName in files) { - _updateFunc = updateFunc; - - var geoipFiles = new List(); - var geoSiteFiles = new List(); - - //Collect used files list - var routingItems = await AppHandler.Instance.RoutingItems(); - foreach (var routing in routingItems) - { - var rules = JsonUtils.Deserialize>(routing.RuleSet); - foreach (var item in rules ?? []) - { - foreach (var ip in item.Ip ?? []) - { - var prefix = "geoip:"; - if (ip.StartsWith(prefix)) - { - geoipFiles.Add(ip.Substring(prefix.Length)); - } - } - - foreach (var domain in item.Domain ?? []) - { - var prefix = "geosite:"; - if (domain.StartsWith(prefix)) - { - geoSiteFiles.Add(domain.Substring(prefix.Length)); - } - } - } - } - - var path = Utils.GetBinPath("srss"); - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - foreach (var item in geoipFiles.Distinct()) - { - await UpdateSrsFile("geoip", item, config, updateFunc); - } - - foreach (var item in geoSiteFiles.Distinct()) - { - await UpdateSrsFile("geosite", item, config, updateFunc); - } - } - - private async Task UpdateSrsFile(string type, string srsName, Config config, Action updateFunc) - { - var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl) - ? Global.SingboxRulesetUrl - : config.ConstItem.SrsSourceUrl; - - var fileName = $"{type}-{srsName}.srs"; - var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName); - var url = string.Format(srsUrl, type, $"{type}-{srsName}"); + var fileName = $"{geoName}.dat"; + var targetPath = Utils.GetBinPath($"{fileName}"); + var url = string.Format(geoUrl, geoName); await DownloadGeoFile(url, fileName, targetPath, updateFunc); } + } - private async Task DownloadGeoFile(string url, string fileName, string targetPath, Action updateFunc) + private async Task UpdateOtherFiles(Config config, Action updateFunc) + { + _updateFunc = updateFunc; + + foreach (var url in Global.OtherGeoUrls) { - var tmpFileName = Utils.GetTempPath(Utils.GetGuid()); + var fileName = Path.GetFileName(url); + var targetPath = Utils.GetBinPath($"{fileName}"); - DownloadService downloadHandle = new(); - downloadHandle.UpdateCompleted += (sender2, args) => + await DownloadGeoFile(url, fileName, targetPath, updateFunc); + } + } + + private async Task UpdateSrsFileAll(Config config, Action updateFunc) + { + _updateFunc = updateFunc; + + var geoipFiles = new List(); + var geoSiteFiles = new List(); + + //Collect used files list + var routingItems = await AppHandler.Instance.RoutingItems(); + foreach (var routing in routingItems) + { + var rules = JsonUtils.Deserialize>(routing.RuleSet); + foreach (var item in rules ?? []) { - if (args.Success) + foreach (var ip in item.Ip ?? []) { - _updateFunc?.Invoke(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName)); - - try + var prefix = "geoip:"; + if (ip.StartsWith(prefix)) { - if (File.Exists(tmpFileName)) - { - File.Copy(tmpFileName, targetPath, true); - - File.Delete(tmpFileName); - //_updateFunc?.Invoke(true, ""); - } - } - catch (Exception ex) - { - _updateFunc?.Invoke(false, ex.Message); + geoipFiles.Add(ip.Substring(prefix.Length)); } } - else - { - _updateFunc?.Invoke(false, args.Msg); - } - }; - downloadHandle.Error += (sender2, args) => - { - _updateFunc?.Invoke(false, args.GetException().Message); - }; - await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout); + foreach (var domain in item.Domain ?? []) + { + var prefix = "geosite:"; + if (domain.StartsWith(prefix)) + { + geoSiteFiles.Add(domain.Substring(prefix.Length)); + } + } + } } - #endregion Geo private + var path = Utils.GetBinPath("srss"); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + foreach (var item in geoipFiles.Distinct()) + { + await UpdateSrsFile("geoip", item, config, updateFunc); + } + + foreach (var item in geoSiteFiles.Distinct()) + { + await UpdateSrsFile("geosite", item, config, updateFunc); + } } + + private async Task UpdateSrsFile(string type, string srsName, Config config, Action updateFunc) + { + var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl) + ? Global.SingboxRulesetUrl + : config.ConstItem.SrsSourceUrl; + + var fileName = $"{type}-{srsName}.srs"; + var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName); + var url = string.Format(srsUrl, type, $"{type}-{srsName}"); + + await DownloadGeoFile(url, fileName, targetPath, updateFunc); + } + + private async Task DownloadGeoFile(string url, string fileName, string targetPath, Action updateFunc) + { + var tmpFileName = Utils.GetTempPath(Utils.GetGuid()); + + DownloadService downloadHandle = new(); + downloadHandle.UpdateCompleted += (sender2, args) => + { + if (args.Success) + { + _updateFunc?.Invoke(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName)); + + try + { + if (File.Exists(tmpFileName)) + { + File.Copy(tmpFileName, targetPath, true); + + File.Delete(tmpFileName); + //_updateFunc?.Invoke(true, ""); + } + } + catch (Exception ex) + { + _updateFunc?.Invoke(false, ex.Message); + } + } + else + { + _updateFunc?.Invoke(false, args.Msg); + } + }; + downloadHandle.Error += (sender2, args) => + { + _updateFunc?.Invoke(false, args.GetException().Message); + }; + + await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout); + } + + #endregion Geo private } diff --git a/v2rayN/ServiceLib/ViewModels/AddServer2ViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServer2ViewModel.cs index 9ea17199..46c5298c 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServer2ViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServer2ViewModel.cs @@ -2,115 +2,114 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class AddServer2ViewModel : MyReactiveObject { - public class AddServer2ViewModel : MyReactiveObject + [Reactive] + public ProfileItem SelectedSource { get; set; } + + [Reactive] + public string? CoreType { get; set; } + + public ReactiveCommand BrowseServerCmd { get; } + public ReactiveCommand EditServerCmd { get; } + public ReactiveCommand SaveServerCmd { get; } + public bool IsModified { get; set; } + + public AddServer2ViewModel(ProfileItem profileItem, Func>? updateView) { - [Reactive] - public ProfileItem SelectedSource { get; set; } + _config = AppHandler.Instance.Config; + _updateView = updateView; - [Reactive] - public string? CoreType { get; set; } - - public ReactiveCommand BrowseServerCmd { get; } - public ReactiveCommand EditServerCmd { get; } - public ReactiveCommand SaveServerCmd { get; } - public bool IsModified { get; set; } - - public AddServer2ViewModel(ProfileItem profileItem, Func>? updateView) + BrowseServerCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; - - BrowseServerCmd = ReactiveCommand.CreateFromTask(async () => - { - _updateView?.Invoke(EViewAction.BrowseServer, null); - await Task.CompletedTask; - }); - EditServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditServer(); - }); - SaveServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveServerAsync(); - }); - - SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); - CoreType = SelectedSource?.CoreType?.ToString(); - } - - private async Task SaveServerAsync() - { - var remarks = SelectedSource.Remarks; - if (remarks.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); - return; - } - - if (SelectedSource.Address.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); - return; - } - SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); - - if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } - } - - public async Task BrowseServer(string fileName) - { - if (fileName.IsNullOrEmpty()) - { - return; - } - - var item = await AppHandler.Instance.GetProfileItem(SelectedSource.IndexId); - item ??= SelectedSource; - item.Address = fileName; - if (await ConfigHandler.AddCustomServer(_config, item, false) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer); - if (item.IndexId.IsNotEmpty()) - { - SelectedSource = JsonUtils.DeepCopy(item); - } - IsModified = true; - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.FailedImportedCustomServer); - } - } - - private async Task EditServer() - { - var address = SelectedSource.Address; - if (address.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); - return; - } - - address = Utils.GetConfigPath(address); - if (File.Exists(address)) - { - ProcUtils.ProcessStart(address); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.FailedReadConfiguration); - } + _updateView?.Invoke(EViewAction.BrowseServer, null); await Task.CompletedTask; + }); + EditServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await EditServer(); + }); + SaveServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await SaveServerAsync(); + }); + + SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); + CoreType = SelectedSource?.CoreType?.ToString(); + } + + private async Task SaveServerAsync() + { + var remarks = SelectedSource.Remarks; + if (remarks.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + return; + } + + if (SelectedSource.Address.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); + return; + } + SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); + + if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0) + { + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } + + public async Task BrowseServer(string fileName) + { + if (fileName.IsNullOrEmpty()) + { + return; + } + + var item = await AppHandler.Instance.GetProfileItem(SelectedSource.IndexId); + item ??= SelectedSource; + item.Address = fileName; + if (await ConfigHandler.AddCustomServer(_config, item, false) == 0) + { + NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer); + if (item.IndexId.IsNotEmpty()) + { + SelectedSource = JsonUtils.DeepCopy(item); + } + IsModified = true; + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.FailedImportedCustomServer); + } + } + + private async Task EditServer() + { + var address = SelectedSource.Address; + if (address.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.FillServerAddressCustom); + return; + } + + address = Utils.GetConfigPath(address); + if (File.Exists(address)) + { + ProcUtils.ProcessStart(address); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.FailedReadConfiguration); + } + await Task.CompletedTask; + } } diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index a1ff122b..c268ca42 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -2,95 +2,94 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class AddServerViewModel : MyReactiveObject { - public class AddServerViewModel : MyReactiveObject + [Reactive] + public ProfileItem SelectedSource { get; set; } + + [Reactive] + public string? CoreType { get; set; } + + public ReactiveCommand SaveCmd { get; } + + public AddServerViewModel(ProfileItem profileItem, Func>? updateView) { - [Reactive] - public ProfileItem SelectedSource { get; set; } + _config = AppHandler.Instance.Config; + _updateView = updateView; - [Reactive] - public string? CoreType { get; set; } - - public ReactiveCommand SaveCmd { get; } - - public AddServerViewModel(ProfileItem profileItem, Func>? updateView) + SaveCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await SaveServerAsync(); + }); - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveServerAsync(); - }); + if (profileItem.IndexId.IsNullOrEmpty()) + { + profileItem.Network = Global.DefaultNetwork; + profileItem.HeaderType = Global.None; + profileItem.RequestHost = ""; + profileItem.StreamSecurity = ""; + SelectedSource = profileItem; + } + else + { + SelectedSource = JsonUtils.DeepCopy(profileItem); + } + CoreType = SelectedSource?.CoreType?.ToString(); + } - if (profileItem.IndexId.IsNullOrEmpty()) - { - profileItem.Network = Global.DefaultNetwork; - profileItem.HeaderType = Global.None; - profileItem.RequestHost = ""; - profileItem.StreamSecurity = ""; - SelectedSource = profileItem; - } - else - { - SelectedSource = JsonUtils.DeepCopy(profileItem); - } - CoreType = SelectedSource?.CoreType?.ToString(); + private async Task SaveServerAsync() + { + if (SelectedSource.Remarks.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + return; } - private async Task SaveServerAsync() + if (SelectedSource.Address.IsNullOrEmpty()) { - if (SelectedSource.Remarks.IsNullOrEmpty()) + NoticeHandler.Instance.Enqueue(ResUI.FillServerAddress); + return; + } + var port = SelectedSource.Port.ToString(); + if (port.IsNullOrEmpty() || !Utils.IsNumeric(port) + || SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectServerPort); + return; + } + if (SelectedSource.ConfigType == EConfigType.Shadowsocks) + { + if (SelectedSource.Id.IsNullOrEmpty()) { - NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + NoticeHandler.Instance.Enqueue(ResUI.FillPassword); return; } + if (SelectedSource.Security.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectEncryption); + return; + } + } + if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP) + { + if (SelectedSource.Id.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.FillUUID); + return; + } + } + SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); - if (SelectedSource.Address.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.FillServerAddress); - return; - } - var port = SelectedSource.Port.ToString(); - if (port.IsNullOrEmpty() || !Utils.IsNumeric(port) - || SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort) - { - NoticeHandler.Instance.Enqueue(ResUI.FillCorrectServerPort); - return; - } - if (SelectedSource.ConfigType == EConfigType.Shadowsocks) - { - if (SelectedSource.Id.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.FillPassword); - return; - } - if (SelectedSource.Security.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectEncryption); - return; - } - } - if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP) - { - if (SelectedSource.Id.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.FillUUID); - return; - } - } - SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); - - if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } + if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) + { + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } } diff --git a/v2rayN/ServiceLib/ViewModels/BackupAndRestoreViewModel.cs b/v2rayN/ServiceLib/ViewModels/BackupAndRestoreViewModel.cs index 61ce834f..82fd80d7 100644 --- a/v2rayN/ServiceLib/ViewModels/BackupAndRestoreViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/BackupAndRestoreViewModel.cs @@ -3,180 +3,179 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class BackupAndRestoreViewModel : MyReactiveObject { - public class BackupAndRestoreViewModel : MyReactiveObject + private readonly string _guiConfigs = "guiConfigs"; + private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip"; + + public ReactiveCommand RemoteBackupCmd { get; } + public ReactiveCommand RemoteRestoreCmd { get; } + public ReactiveCommand WebDavCheckCmd { get; } + + [Reactive] + public WebDavItem SelectedSource { get; set; } + + [Reactive] + public string OperationMsg { get; set; } + + public BackupAndRestoreViewModel(Func>? updateView) { - private readonly string _guiConfigs = "guiConfigs"; - private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip"; + _config = AppHandler.Instance.Config; + _updateView = updateView; - public ReactiveCommand RemoteBackupCmd { get; } - public ReactiveCommand RemoteRestoreCmd { get; } - public ReactiveCommand WebDavCheckCmd { get; } - - [Reactive] - public WebDavItem SelectedSource { get; set; } - - [Reactive] - public string OperationMsg { get; set; } - - public BackupAndRestoreViewModel(Func>? updateView) + WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await WebDavCheck(); + }); + RemoteBackupCmd = ReactiveCommand.CreateFromTask(async () => + { + await RemoteBackup(); + }); + RemoteRestoreCmd = ReactiveCommand.CreateFromTask(async () => + { + await RemoteRestore(); + }); - WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () => - { - await WebDavCheck(); - }); - RemoteBackupCmd = ReactiveCommand.CreateFromTask(async () => - { - await RemoteBackup(); - }); - RemoteRestoreCmd = ReactiveCommand.CreateFromTask(async () => - { - await RemoteRestore(); - }); + SelectedSource = JsonUtils.DeepCopy(_config.WebDavItem); + } - SelectedSource = JsonUtils.DeepCopy(_config.WebDavItem); + private void DisplayOperationMsg(string msg = "") + { + OperationMsg = msg; + } + + private async Task WebDavCheck() + { + DisplayOperationMsg(); + _config.WebDavItem = SelectedSource; + _ = await ConfigHandler.SaveConfig(_config); + + var result = await WebDavHandler.Instance.CheckConnection(); + if (result) + { + DisplayOperationMsg(ResUI.OperationSuccess); } - - private void DisplayOperationMsg(string msg = "") + else { - OperationMsg = msg; - } - - private async Task WebDavCheck() - { - DisplayOperationMsg(); - _config.WebDavItem = SelectedSource; - _ = await ConfigHandler.SaveConfig(_config); - - var result = await WebDavHandler.Instance.CheckConnection(); - if (result) - { - DisplayOperationMsg(ResUI.OperationSuccess); - } - else - { - DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); - } - } - - private async Task RemoteBackup() - { - DisplayOperationMsg(); - var fileName = Utils.GetBackupPath(BackupFileName); - var result = await CreateZipFileFromDirectory(fileName); - if (result) - { - var result2 = await WebDavHandler.Instance.PutFile(fileName); - if (result2) - { - DisplayOperationMsg(ResUI.OperationSuccess); - return; - } - } - DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); } - - private async Task RemoteRestore() - { - DisplayOperationMsg(); - var fileName = Utils.GetTempPath(Utils.GetGuid()); - var result = await WebDavHandler.Instance.GetRawFile(fileName); - if (result) - { - await LocalRestore(fileName); - return; - } - - DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); - } - - public async Task LocalBackup(string fileName) - { - DisplayOperationMsg(); - var result = await CreateZipFileFromDirectory(fileName); - if (result) - { - DisplayOperationMsg(ResUI.OperationSuccess); - } - else - { - DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); - } - - return result; - } - - public async Task LocalRestore(string fileName) - { - DisplayOperationMsg(); - if (fileName.IsNullOrEmpty()) - { - return; - } - //exist - if (!File.Exists(fileName)) - { - return; - } - //check - var lstFiles = FileManager.GetFilesFromZip(fileName); - if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs))) - { - DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips); - return; - } - - //backup first - var fileBackup = Utils.GetBackupPath(BackupFileName); - var result = await CreateZipFileFromDirectory(fileBackup); - if (result) - { - var service = Locator.Current.GetService(); - await service?.MyAppExitAsync(true); - await SQLiteHelper.Instance.DisposeDbConnectionAsync(); - - var toPath = Utils.GetConfigPath(); - FileManager.ZipExtractToFile(fileName, toPath, ""); - - if (Utils.IsWindows()) - { - ProcUtils.RebootAsAdmin(false); - } - else - { - if (Utils.UpgradeAppExists(out var upgradeFileName)) - { - _ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath()); - } - } - service?.Shutdown(true); - } - else - { - DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); - } - } - - private async Task CreateZipFileFromDirectory(string fileName) - { - if (fileName.IsNullOrEmpty()) - { - return false; - } - - var configDir = Utils.GetConfigPath(); - var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}"); - var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs); - - FileManager.CopyDirectory(configDir, configDirTemp, false, true, ""); - var ret = FileManager.CreateFromDirectory(configDirZipTemp, fileName); - Directory.Delete(configDirZipTemp, true); - return await Task.FromResult(ret); - } + } + + private async Task RemoteBackup() + { + DisplayOperationMsg(); + var fileName = Utils.GetBackupPath(BackupFileName); + var result = await CreateZipFileFromDirectory(fileName); + if (result) + { + var result2 = await WebDavHandler.Instance.PutFile(fileName); + if (result2) + { + DisplayOperationMsg(ResUI.OperationSuccess); + return; + } + } + + DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); + } + + private async Task RemoteRestore() + { + DisplayOperationMsg(); + var fileName = Utils.GetTempPath(Utils.GetGuid()); + var result = await WebDavHandler.Instance.GetRawFile(fileName); + if (result) + { + await LocalRestore(fileName); + return; + } + + DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); + } + + public async Task LocalBackup(string fileName) + { + DisplayOperationMsg(); + var result = await CreateZipFileFromDirectory(fileName); + if (result) + { + DisplayOperationMsg(ResUI.OperationSuccess); + } + else + { + DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); + } + + return result; + } + + public async Task LocalRestore(string fileName) + { + DisplayOperationMsg(); + if (fileName.IsNullOrEmpty()) + { + return; + } + //exist + if (!File.Exists(fileName)) + { + return; + } + //check + var lstFiles = FileManager.GetFilesFromZip(fileName); + if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs))) + { + DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips); + return; + } + + //backup first + var fileBackup = Utils.GetBackupPath(BackupFileName); + var result = await CreateZipFileFromDirectory(fileBackup); + if (result) + { + var service = Locator.Current.GetService(); + await service?.MyAppExitAsync(true); + await SQLiteHelper.Instance.DisposeDbConnectionAsync(); + + var toPath = Utils.GetConfigPath(); + FileManager.ZipExtractToFile(fileName, toPath, ""); + + if (Utils.IsWindows()) + { + ProcUtils.RebootAsAdmin(false); + } + else + { + if (Utils.UpgradeAppExists(out var upgradeFileName)) + { + _ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath()); + } + } + service?.Shutdown(true); + } + else + { + DisplayOperationMsg(WebDavHandler.Instance.GetLastError()); + } + } + + private async Task CreateZipFileFromDirectory(string fileName) + { + if (fileName.IsNullOrEmpty()) + { + return false; + } + + var configDir = Utils.GetConfigPath(); + var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}"); + var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs); + + FileManager.CopyDirectory(configDir, configDirTemp, false, true, ""); + var ret = FileManager.CreateFromDirectory(configDirZipTemp, fileName); + Directory.Delete(configDirZipTemp, true); + return await Task.FromResult(ret); } } diff --git a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index 0640d437..f3247798 100644 --- a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -6,311 +6,310 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class CheckUpdateViewModel : MyReactiveObject { - public class CheckUpdateViewModel : MyReactiveObject + private const string _geo = "GeoFiles"; + private string _v2rayN = ECoreType.v2rayN.ToString(); + private List _lstUpdated = []; + + private IObservableCollection _checkUpdateModel = new ObservableCollectionExtended(); + public IObservableCollection CheckUpdateModels => _checkUpdateModel; + public ReactiveCommand CheckUpdateCmd { get; } + [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } + + public CheckUpdateViewModel(Func>? updateView) { - private const string _geo = "GeoFiles"; - private string _v2rayN = ECoreType.v2rayN.ToString(); - private List _lstUpdated = []; + _config = AppHandler.Instance.Config; + _updateView = updateView; - private IObservableCollection _checkUpdateModel = new ObservableCollectionExtended(); - public IObservableCollection CheckUpdateModels => _checkUpdateModel; - public ReactiveCommand CheckUpdateCmd { get; } - [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } - - public CheckUpdateViewModel(Func>? updateView) + CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await CheckUpdate(); + }); - CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () => - { - await CheckUpdate(); - }); + EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; - EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; + this.WhenAnyValue( + x => x.EnableCheckPreReleaseUpdate, + y => y == true) + .Subscribe(c => { _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate; }); - this.WhenAnyValue( - x => x.EnableCheckPreReleaseUpdate, - y => y == true) - .Subscribe(c => { _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate; }); + RefreshCheckUpdateItems(); + } - RefreshCheckUpdateItems(); - } + private void RefreshCheckUpdateItems() + { + _checkUpdateModel.Clear(); - private void RefreshCheckUpdateItems() + if (RuntimeInformation.ProcessArchitecture != Architecture.X86) { - _checkUpdateModel.Clear(); - - if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + _checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN)); + //Not Windows and under Win10 + if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) { - _checkUpdateModel.Add(GetCheckUpdateModel(_v2rayN)); - //Not Windows and under Win10 - if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) - { - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); - _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); - } - } - _checkUpdateModel.Add(GetCheckUpdateModel(_geo)); - } - - private CheckUpdateModel GetCheckUpdateModel(string coreType) - { - return new() - { - IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true, - CoreType = coreType, - Remarks = ResUI.menuCheckUpdate, - }; - } - - private async Task SaveSelectedCoreTypes() - { - _config.CheckUpdateItem.SelectedCoreTypes = _checkUpdateModel.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList(); - await ConfigHandler.SaveConfig(_config); - } - - private async Task CheckUpdate() - { - await Task.Run(async () => - { - await CheckUpdateTask(); - }); - } - - private async Task CheckUpdateTask() - { - _lstUpdated.Clear(); - _lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true) - .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); - await SaveSelectedCoreTypes(); - - for (var k = _checkUpdateModel.Count - 1; k >= 0; k--) - { - var item = _checkUpdateModel[k]; - if (item.IsSelected != true) - continue; - - UpdateView(item.CoreType, "..."); - if (item.CoreType == _geo) - { - await CheckUpdateGeo(); - } - else if (item.CoreType == _v2rayN) - { - await CheckUpdateN(EnableCheckPreReleaseUpdate); - } - else if (item.CoreType == ECoreType.Xray.ToString()) - { - await CheckUpdateCore(item, EnableCheckPreReleaseUpdate); - } - else - { - await CheckUpdateCore(item, false); - } - } - - await UpdateFinished(); - } - - private void UpdatedPlusPlus(string coreType, string fileName) - { - var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType); - if (item == null) - { - return; - } - item.IsFinished = true; - if (!fileName.IsNullOrEmpty()) - { - item.FileName = fileName; + _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); + _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); + _checkUpdateModel.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); } } + _checkUpdateModel.Add(GetCheckUpdateModel(_geo)); + } - private async Task CheckUpdateGeo() + private CheckUpdateModel GetCheckUpdateModel(string coreType) + { + return new() { - void _updateUI(bool success, string msg) + IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true, + CoreType = coreType, + Remarks = ResUI.menuCheckUpdate, + }; + } + + private async Task SaveSelectedCoreTypes() + { + _config.CheckUpdateItem.SelectedCoreTypes = _checkUpdateModel.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList(); + await ConfigHandler.SaveConfig(_config); + } + + private async Task CheckUpdate() + { + await Task.Run(async () => + { + await CheckUpdateTask(); + }); + } + + private async Task CheckUpdateTask() + { + _lstUpdated.Clear(); + _lstUpdated = _checkUpdateModel.Where(x => x.IsSelected == true) + .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); + await SaveSelectedCoreTypes(); + + for (var k = _checkUpdateModel.Count - 1; k >= 0; k--) + { + var item = _checkUpdateModel[k]; + if (item.IsSelected != true) + continue; + + UpdateView(item.CoreType, "..."); + if (item.CoreType == _geo) { - UpdateView(_geo, msg); - if (success) - { - UpdatedPlusPlus(_geo, ""); - } + await CheckUpdateGeo(); } - await (new UpdateService()).UpdateGeoFileAll(_config, _updateUI) - .ContinueWith(t => - { - UpdatedPlusPlus(_geo, ""); - }); - } - - private async Task CheckUpdateN(bool preRelease) - { - void _updateUI(bool success, string msg) + else if (item.CoreType == _v2rayN) { - UpdateView(_v2rayN, msg); - if (success) - { - UpdateView(_v2rayN, ResUI.OperationSuccess); - UpdatedPlusPlus(_v2rayN, msg); - } + await CheckUpdateN(EnableCheckPreReleaseUpdate); } - await (new UpdateService()).CheckUpdateGuiN(_config, _updateUI, preRelease) - .ContinueWith(t => - { - UpdatedPlusPlus(_v2rayN, ""); - }); - } - - private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease) - { - void _updateUI(bool success, string msg) + else if (item.CoreType == ECoreType.Xray.ToString()) { - UpdateView(model.CoreType, msg); - if (success) - { - UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore); - - UpdatedPlusPlus(model.CoreType, msg); - } - } - var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType); - await (new UpdateService()).CheckUpdateCore(type, _config, _updateUI, preRelease) - .ContinueWith(t => - { - UpdatedPlusPlus(model.CoreType, ""); - }); - } - - private async Task UpdateFinished() - { - if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count) - { - _updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, false); - await Task.Delay(2000); - await UpgradeCore(); - - if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true)) - { - await Task.Delay(1000); - UpgradeN(); - } - await Task.Delay(1000); - _updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, true); - } - } - - public void UpdateFinishedResult(bool blReload) - { - if (blReload) - { - Locator.Current.GetService()?.Reload(); + await CheckUpdateCore(item, EnableCheckPreReleaseUpdate); } else { - Locator.Current.GetService()?.CloseCore(); + await CheckUpdateCore(item, false); } } - private void UpgradeN() + await UpdateFinished(); + } + + private void UpdatedPlusPlus(string coreType, string fileName) + { + var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType); + if (item == null) { - try - { - var fileName = _lstUpdated.FirstOrDefault(x => x.CoreType == _v2rayN)?.FileName; - if (fileName.IsNullOrEmpty()) - { - return; - } - if (!Utils.UpgradeAppExists(out _)) - { - UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip); - return; - } - Locator.Current.GetService()?.UpgradeApp(fileName); - } - catch (Exception ex) - { - UpdateView(_v2rayN, ex.Message); - } + return; } - - private async Task UpgradeCore() + item.IsFinished = true; + if (!fileName.IsNullOrEmpty()) { - foreach (var item in _lstUpdated) - { - if (item.FileName.IsNullOrEmpty()) - { - continue; - } - - var fileName = item.FileName; - if (!File.Exists(fileName)) - { - continue; - } - var toPath = Utils.GetBinPath("", item.CoreType); - - if (fileName.Contains(".tar.gz")) - { - FileManager.DecompressTarFile(fileName, toPath); - var dir = new DirectoryInfo(toPath); - if (dir.Exists) - { - foreach (var subDir in dir.GetDirectories()) - { - FileManager.CopyDirectory(subDir.FullName, toPath, false, true); - subDir.Delete(true); - } - } - } - else if (fileName.Contains(".gz")) - { - FileManager.DecompressFile(fileName, toPath, item.CoreType); - } - else - { - FileManager.ZipExtractToFile(fileName, toPath, "geo"); - } - - if (Utils.IsNonWindows()) - { - var filesList = (new DirectoryInfo(toPath)).GetFiles().Select(u => u.FullName).ToList(); - foreach (var file in filesList) - { - await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower())); - } - } - - UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully); - - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - } - } - - private void UpdateView(string coreType, string msg) - { - var item = new CheckUpdateModel() - { - CoreType = coreType, - Remarks = msg, - }; - _updateView?.Invoke(EViewAction.DispatcherCheckUpdate, item); - } - - public void UpdateViewResult(CheckUpdateModel model) - { - var found = _checkUpdateModel.FirstOrDefault(t => t.CoreType == model.CoreType); - if (found == null) - return; - var itemCopy = JsonUtils.DeepCopy(found); - itemCopy.Remarks = model.Remarks; - _checkUpdateModel.Replace(found, itemCopy); + item.FileName = fileName; } } + + private async Task CheckUpdateGeo() + { + void _updateUI(bool success, string msg) + { + UpdateView(_geo, msg); + if (success) + { + UpdatedPlusPlus(_geo, ""); + } + } + await (new UpdateService()).UpdateGeoFileAll(_config, _updateUI) + .ContinueWith(t => + { + UpdatedPlusPlus(_geo, ""); + }); + } + + private async Task CheckUpdateN(bool preRelease) + { + void _updateUI(bool success, string msg) + { + UpdateView(_v2rayN, msg); + if (success) + { + UpdateView(_v2rayN, ResUI.OperationSuccess); + UpdatedPlusPlus(_v2rayN, msg); + } + } + await (new UpdateService()).CheckUpdateGuiN(_config, _updateUI, preRelease) + .ContinueWith(t => + { + UpdatedPlusPlus(_v2rayN, ""); + }); + } + + private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease) + { + void _updateUI(bool success, string msg) + { + UpdateView(model.CoreType, msg); + if (success) + { + UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore); + + UpdatedPlusPlus(model.CoreType, msg); + } + } + var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType); + await (new UpdateService()).CheckUpdateCore(type, _config, _updateUI, preRelease) + .ContinueWith(t => + { + UpdatedPlusPlus(model.CoreType, ""); + }); + } + + private async Task UpdateFinished() + { + if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count) + { + _updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, false); + await Task.Delay(2000); + await UpgradeCore(); + + if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true)) + { + await Task.Delay(1000); + UpgradeN(); + } + await Task.Delay(1000); + _updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, true); + } + } + + public void UpdateFinishedResult(bool blReload) + { + if (blReload) + { + Locator.Current.GetService()?.Reload(); + } + else + { + Locator.Current.GetService()?.CloseCore(); + } + } + + private void UpgradeN() + { + try + { + var fileName = _lstUpdated.FirstOrDefault(x => x.CoreType == _v2rayN)?.FileName; + if (fileName.IsNullOrEmpty()) + { + return; + } + if (!Utils.UpgradeAppExists(out _)) + { + UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip); + return; + } + Locator.Current.GetService()?.UpgradeApp(fileName); + } + catch (Exception ex) + { + UpdateView(_v2rayN, ex.Message); + } + } + + private async Task UpgradeCore() + { + foreach (var item in _lstUpdated) + { + if (item.FileName.IsNullOrEmpty()) + { + continue; + } + + var fileName = item.FileName; + if (!File.Exists(fileName)) + { + continue; + } + var toPath = Utils.GetBinPath("", item.CoreType); + + if (fileName.Contains(".tar.gz")) + { + FileManager.DecompressTarFile(fileName, toPath); + var dir = new DirectoryInfo(toPath); + if (dir.Exists) + { + foreach (var subDir in dir.GetDirectories()) + { + FileManager.CopyDirectory(subDir.FullName, toPath, false, true); + subDir.Delete(true); + } + } + } + else if (fileName.Contains(".gz")) + { + FileManager.DecompressFile(fileName, toPath, item.CoreType); + } + else + { + FileManager.ZipExtractToFile(fileName, toPath, "geo"); + } + + if (Utils.IsNonWindows()) + { + var filesList = (new DirectoryInfo(toPath)).GetFiles().Select(u => u.FullName).ToList(); + foreach (var file in filesList) + { + await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower())); + } + } + + UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully); + + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + } + } + + private void UpdateView(string coreType, string msg) + { + var item = new CheckUpdateModel() + { + CoreType = coreType, + Remarks = msg, + }; + _updateView?.Invoke(EViewAction.DispatcherCheckUpdate, item); + } + + public void UpdateViewResult(CheckUpdateModel model) + { + var found = _checkUpdateModel.FirstOrDefault(t => t.CoreType == model.CoreType); + if (found == null) + return; + var itemCopy = JsonUtils.DeepCopy(found); + itemCopy.Remarks = model.Remarks; + _checkUpdateModel.Replace(found, itemCopy); + } } diff --git a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs index a9bb4eae..a042da51 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs @@ -5,151 +5,150 @@ using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class ClashConnectionsViewModel : MyReactiveObject { - public class ClashConnectionsViewModel : MyReactiveObject + private IObservableCollection _connectionItems = new ObservableCollectionExtended(); + public IObservableCollection ConnectionItems => _connectionItems; + + [Reactive] + public ClashConnectionModel SelectedSource { get; set; } + + public ReactiveCommand ConnectionCloseCmd { get; } + public ReactiveCommand ConnectionCloseAllCmd { get; } + + [Reactive] + public string HostFilter { get; set; } + + [Reactive] + public bool AutoRefresh { get; set; } + + public ClashConnectionsViewModel(Func>? updateView) { - private IObservableCollection _connectionItems = new ObservableCollectionExtended(); - public IObservableCollection ConnectionItems => _connectionItems; + _config = AppHandler.Instance.Config; + _updateView = updateView; + AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh; - [Reactive] - public ClashConnectionModel SelectedSource { get; set; } + var canEditRemove = this.WhenAnyValue( + x => x.SelectedSource, + selectedSource => selectedSource != null && selectedSource.Id.IsNotEmpty()); - public ReactiveCommand ConnectionCloseCmd { get; } - public ReactiveCommand ConnectionCloseAllCmd { get; } - - [Reactive] - public string HostFilter { get; set; } - - [Reactive] - public bool AutoRefresh { get; set; } - - public ClashConnectionsViewModel(Func>? updateView) + this.WhenAnyValue( + x => x.AutoRefresh, + y => y == true) + .Subscribe(c => { _config.ClashUIItem.ConnectionsAutoRefresh = AutoRefresh; }); + ConnectionCloseCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; - AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh; + await ClashConnectionClose(false); + }, canEditRemove); - var canEditRemove = this.WhenAnyValue( - x => x.SelectedSource, - selectedSource => selectedSource != null && selectedSource.Id.IsNotEmpty()); + ConnectionCloseAllCmd = ReactiveCommand.CreateFromTask(async () => + { + await ClashConnectionClose(true); + }); - this.WhenAnyValue( - x => x.AutoRefresh, - y => y == true) - .Subscribe(c => { _config.ClashUIItem.ConnectionsAutoRefresh = AutoRefresh; }); - ConnectionCloseCmd = ReactiveCommand.CreateFromTask(async () => - { - await ClashConnectionClose(false); - }, canEditRemove); + _ = Init(); + } - ConnectionCloseAllCmd = ReactiveCommand.CreateFromTask(async () => - { - await ClashConnectionClose(true); - }); + private async Task Init() + { + await DelayTestTask(); + } - _ = Init(); + private async Task GetClashConnections() + { + var ret = await ClashApiHandler.Instance.GetClashConnectionsAsync(); + if (ret == null) + { + return; } - private async Task Init() + _ = _updateView?.Invoke(EViewAction.DispatcherRefreshConnections, ret?.connections); + } + + public void RefreshConnections(List? connections) + { + _connectionItems.Clear(); + + var dtNow = DateTime.Now; + var lstModel = new List(); + foreach (var item in connections ?? new()) { - await DelayTestTask(); + var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}"; + if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter)) + { + continue; + } + + var model = new ClashConnectionModel + { + Id = item.id, + Network = item.metadata.network, + Type = item.metadata.type, + Host = host, + Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds, + Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"), + Chain = $"{item.rule} , {string.Join("->", item.chains ?? new())}" + }; + + lstModel.Add(model); + } + if (lstModel.Count <= 0) + { + return; } - private async Task GetClashConnections() + _connectionItems.AddRange(lstModel); + } + + public async Task ClashConnectionClose(bool all) + { + var id = string.Empty; + if (!all) { - var ret = await ClashApiHandler.Instance.GetClashConnectionsAsync(); - if (ret == null) + var item = SelectedSource; + if (item is null) { return; } - - _ = _updateView?.Invoke(EViewAction.DispatcherRefreshConnections, ret?.connections); + id = item.Id; } - - public void RefreshConnections(List? connections) + else { _connectionItems.Clear(); + } + await ClashApiHandler.Instance.ClashConnectionClose(id); + await GetClashConnections(); + } - var dtNow = DateTime.Now; - var lstModel = new List(); - foreach (var item in connections ?? new()) + public async Task DelayTestTask() + { + _ = Task.Run(async () => + { + var numOfExecuted = 1; + while (true) { - var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}"; - if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter)) + await Task.Delay(1000 * 5); + numOfExecuted++; + if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box))) { continue; } - var model = new ClashConnectionModel + if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0) { - Id = item.id, - Network = item.metadata.network, - Type = item.metadata.type, - Host = host, - Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds, - Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"), - Chain = $"{item.rule} , {string.Join("->", item.chains ?? new())}" - }; - - lstModel.Add(model); - } - if (lstModel.Count <= 0) - { - return; - } - - _connectionItems.AddRange(lstModel); - } - - public async Task ClashConnectionClose(bool all) - { - var id = string.Empty; - if (!all) - { - var item = SelectedSource; - if (item is null) - { - return; + continue; } - id = item.Id; - } - else - { - _connectionItems.Clear(); - } - await ClashApiHandler.Instance.ClashConnectionClose(id); - await GetClashConnections(); - } - public async Task DelayTestTask() - { - _ = Task.Run(async () => - { - var numOfExecuted = 1; - while (true) + if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0) { - await Task.Delay(1000 * 5); - numOfExecuted++; - if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box))) - { - continue; - } - - if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0) - { - continue; - } - - if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0) - { - continue; - } - await GetClashConnections(); + continue; } - }); + await GetClashConnections(); + } + }); - await Task.CompletedTask; - } + await Task.CompletedTask; } } diff --git a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs index 45339bdf..8f684782 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs @@ -7,436 +7,435 @@ using ReactiveUI.Fody.Helpers; using static ServiceLib.Models.ClashProviders; using static ServiceLib.Models.ClashProxies; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class ClashProxiesViewModel : MyReactiveObject { - public class ClashProxiesViewModel : MyReactiveObject + private Dictionary? _proxies; + private Dictionary? _providers; + private readonly int _delayTimeout = 99999999; + + private IObservableCollection _proxyGroups = new ObservableCollectionExtended(); + private IObservableCollection _proxyDetails = new ObservableCollectionExtended(); + + public IObservableCollection ProxyGroups => _proxyGroups; + public IObservableCollection ProxyDetails => _proxyDetails; + + [Reactive] + public ClashProxyModel SelectedGroup { get; set; } + + [Reactive] + public ClashProxyModel SelectedDetail { get; set; } + + public ReactiveCommand ProxiesReloadCmd { get; } + public ReactiveCommand ProxiesDelayTestCmd { get; } + public ReactiveCommand ProxiesDelayTestPartCmd { get; } + public ReactiveCommand ProxiesSelectActivityCmd { get; } + + [Reactive] + public int RuleModeSelected { get; set; } + + [Reactive] + public int SortingSelected { get; set; } + + [Reactive] + public bool AutoRefresh { get; set; } + + public ClashProxiesViewModel(Func>? updateView) { - private Dictionary? _proxies; - private Dictionary? _providers; - private readonly int _delayTimeout = 99999999; + _config = AppHandler.Instance.Config; + _updateView = updateView; - private IObservableCollection _proxyGroups = new ObservableCollectionExtended(); - private IObservableCollection _proxyDetails = new ObservableCollectionExtended(); - - public IObservableCollection ProxyGroups => _proxyGroups; - public IObservableCollection ProxyDetails => _proxyDetails; - - [Reactive] - public ClashProxyModel SelectedGroup { get; set; } - - [Reactive] - public ClashProxyModel SelectedDetail { get; set; } - - public ReactiveCommand ProxiesReloadCmd { get; } - public ReactiveCommand ProxiesDelayTestCmd { get; } - public ReactiveCommand ProxiesDelayTestPartCmd { get; } - public ReactiveCommand ProxiesSelectActivityCmd { get; } - - [Reactive] - public int RuleModeSelected { get; set; } - - [Reactive] - public int SortingSelected { get; set; } - - [Reactive] - public bool AutoRefresh { get; set; } - - public ClashProxiesViewModel(Func>? updateView) + ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await ProxiesReload(); + }); + ProxiesDelayTestCmd = ReactiveCommand.CreateFromTask(async () => + { + await ProxiesDelayTest(true); + }); - ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () => - { - await ProxiesReload(); - }); - ProxiesDelayTestCmd = ReactiveCommand.CreateFromTask(async () => - { - await ProxiesDelayTest(true); - }); + ProxiesDelayTestPartCmd = ReactiveCommand.CreateFromTask(async () => + { + await ProxiesDelayTest(false); + }); + ProxiesSelectActivityCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetActiveProxy(); + }); - ProxiesDelayTestPartCmd = ReactiveCommand.CreateFromTask(async () => - { - await ProxiesDelayTest(false); - }); - ProxiesSelectActivityCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetActiveProxy(); - }); + SelectedGroup = new(); + SelectedDetail = new(); + AutoRefresh = _config.ClashUIItem.ProxiesAutoRefresh; + SortingSelected = _config.ClashUIItem.ProxiesSorting; + RuleModeSelected = (int)_config.ClashUIItem.RuleMode; - SelectedGroup = new(); - SelectedDetail = new(); - AutoRefresh = _config.ClashUIItem.ProxiesAutoRefresh; - SortingSelected = _config.ClashUIItem.ProxiesSorting; - RuleModeSelected = (int)_config.ClashUIItem.RuleMode; + this.WhenAnyValue( + x => x.SelectedGroup, + y => y != null && y.Name.IsNotEmpty()) + .Subscribe(c => RefreshProxyDetails(c)); - this.WhenAnyValue( - x => x.SelectedGroup, - y => y != null && y.Name.IsNotEmpty()) - .Subscribe(c => RefreshProxyDetails(c)); + this.WhenAnyValue( + x => x.RuleModeSelected, + y => y >= 0) + .Subscribe(async c => await DoRuleModeSelected(c)); - this.WhenAnyValue( - x => x.RuleModeSelected, - y => y >= 0) - .Subscribe(async c => await DoRuleModeSelected(c)); + this.WhenAnyValue( + x => x.SortingSelected, + y => y >= 0) + .Subscribe(c => DoSortingSelected(c)); - this.WhenAnyValue( - x => x.SortingSelected, - y => y >= 0) - .Subscribe(c => DoSortingSelected(c)); + this.WhenAnyValue( + x => x.AutoRefresh, + y => y == true) + .Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; }); - this.WhenAnyValue( - x => x.AutoRefresh, - y => y == true) - .Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; }); + _ = Init(); + } - _ = Init(); + private async Task Init() + { + await DelayTestTask(); + } + + private async Task DoRuleModeSelected(bool c) + { + if (!c) + { + return; + } + if (_config.ClashUIItem.RuleMode == (ERuleMode)RuleModeSelected) + { + return; + } + await SetRuleModeCheck((ERuleMode)RuleModeSelected); + } + + public async Task SetRuleModeCheck(ERuleMode mode) + { + if (_config.ClashUIItem.RuleMode == mode) + { + return; + } + await SetRuleMode(mode); + } + + private void DoSortingSelected(bool c) + { + if (!c) + { + return; + } + if (SortingSelected != _config.ClashUIItem.ProxiesSorting) + { + _config.ClashUIItem.ProxiesSorting = SortingSelected; } - private async Task Init() + RefreshProxyDetails(c); + } + + public async Task ProxiesReload() + { + await GetClashProxies(true); + await ProxiesDelayTest(); + } + + #region proxy function + + private async Task SetRuleMode(ERuleMode mode) + { + _config.ClashUIItem.RuleMode = mode; + + if (mode != ERuleMode.Unchanged) { - await DelayTestTask(); - } - - private async Task DoRuleModeSelected(bool c) - { - if (!c) - { - return; - } - if (_config.ClashUIItem.RuleMode == (ERuleMode)RuleModeSelected) - { - return; - } - await SetRuleModeCheck((ERuleMode)RuleModeSelected); - } - - public async Task SetRuleModeCheck(ERuleMode mode) - { - if (_config.ClashUIItem.RuleMode == mode) - { - return; - } - await SetRuleMode(mode); - } - - private void DoSortingSelected(bool c) - { - if (!c) - { - return; - } - if (SortingSelected != _config.ClashUIItem.ProxiesSorting) - { - _config.ClashUIItem.ProxiesSorting = SortingSelected; - } - - RefreshProxyDetails(c); - } - - public async Task ProxiesReload() - { - await GetClashProxies(true); - await ProxiesDelayTest(); - } - - #region proxy function - - private async Task SetRuleMode(ERuleMode mode) - { - _config.ClashUIItem.RuleMode = mode; - - if (mode != ERuleMode.Unchanged) - { - Dictionary headers = new() + Dictionary headers = new() { { "mode", mode.ToString().ToLower() } }; - await ClashApiHandler.Instance.ClashConfigUpdate(headers); - } + await ClashApiHandler.Instance.ClashConfigUpdate(headers); } + } - private async Task GetClashProxies(bool refreshUI) + private async Task GetClashProxies(bool refreshUI) + { + var ret = await ClashApiHandler.Instance.GetClashProxiesAsync(); + if (ret?.Item1 == null || ret.Item2 == null) { - var ret = await ClashApiHandler.Instance.GetClashProxiesAsync(); - if (ret?.Item1 == null || ret.Item2 == null) - { - return; - } - _proxies = ret.Item1.proxies; - _providers = ret?.Item2.providers; - - if (refreshUI) - { - _updateView?.Invoke(EViewAction.DispatcherRefreshProxyGroups, null); - } + return; } + _proxies = ret.Item1.proxies; + _providers = ret?.Item2.providers; - public void RefreshProxyGroups() + if (refreshUI) { - var selectedName = SelectedGroup?.Name; - _proxyGroups.Clear(); + _updateView?.Invoke(EViewAction.DispatcherRefreshProxyGroups, null); + } + } - var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups(); - if (proxyGroups != null && proxyGroups.Count > 0) - { - foreach (var it in proxyGroups) - { - if (it.name.IsNullOrEmpty() || !_proxies.ContainsKey(it.name)) - { - continue; - } - var item = _proxies[it.name]; - if (!Global.allowSelectType.Contains(item.type.ToLower())) - { - continue; - } - _proxyGroups.Add(new ClashProxyModel() - { - Now = item.now, - Name = item.name, - Type = item.type - }); - } - } + public void RefreshProxyGroups() + { + var selectedName = SelectedGroup?.Name; + _proxyGroups.Clear(); - //from api - foreach (KeyValuePair kv in _proxies) + var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups(); + if (proxyGroups != null && proxyGroups.Count > 0) + { + foreach (var it in proxyGroups) { - if (!Global.allowSelectType.Contains(kv.Value.type.ToLower())) + if (it.name.IsNullOrEmpty() || !_proxies.ContainsKey(it.name)) { continue; } - var item = _proxyGroups.FirstOrDefault(t => t.Name == kv.Key); - if (item != null && item.Name.IsNotEmpty()) + var item = _proxies[it.name]; + if (!Global.allowSelectType.Contains(item.type.ToLower())) { continue; } _proxyGroups.Add(new ClashProxyModel() { - Now = kv.Value.now, - Name = kv.Key, - Type = kv.Value.type + Now = item.now, + Name = item.name, + Type = item.type }); } + } - if (_proxyGroups != null && _proxyGroups.Count > 0) + //from api + foreach (KeyValuePair kv in _proxies) + { + if (!Global.allowSelectType.Contains(kv.Value.type.ToLower())) { - if (selectedName != null && _proxyGroups.Any(t => t.Name == selectedName)) - { - SelectedGroup = _proxyGroups.FirstOrDefault(t => t.Name == selectedName); - } - else - { - SelectedGroup = _proxyGroups.First(); - } + continue; + } + var item = _proxyGroups.FirstOrDefault(t => t.Name == kv.Key); + if (item != null && item.Name.IsNotEmpty()) + { + continue; + } + _proxyGroups.Add(new ClashProxyModel() + { + Now = kv.Value.now, + Name = kv.Key, + Type = kv.Value.type + }); + } + + if (_proxyGroups != null && _proxyGroups.Count > 0) + { + if (selectedName != null && _proxyGroups.Any(t => t.Name == selectedName)) + { + SelectedGroup = _proxyGroups.FirstOrDefault(t => t.Name == selectedName); } else { - SelectedGroup = new(); + SelectedGroup = _proxyGroups.First(); } } - - private void RefreshProxyDetails(bool c) + else { - _proxyDetails.Clear(); - if (!c) - { - return; - } - var name = SelectedGroup?.Name; - if (name.IsNullOrEmpty()) - { - return; - } - if (_proxies == null) - { - return; - } + SelectedGroup = new(); + } + } - _proxies.TryGetValue(name, out var proxy); - if (proxy?.all == null) - { - return; - } - var lstDetails = new List(); - foreach (var item in proxy.all) - { - var proxy2 = TryGetProxy(item); - if (proxy2 == null) - { - continue; - } - var delay = proxy2.history?.Count > 0 ? proxy2.history.Last().delay : -1; - - lstDetails.Add(new ClashProxyModel() - { - IsActive = item == proxy.now, - Name = item, - Type = proxy2.type, - Delay = delay <= 0 ? _delayTimeout : delay, - DelayName = delay <= 0 ? string.Empty : $"{delay}ms", - }); - } - //sort - switch (SortingSelected) - { - case 0: - lstDetails = lstDetails.OrderBy(t => t.Delay).ToList(); - break; - - case 1: - lstDetails = lstDetails.OrderBy(t => t.Name).ToList(); - break; - - default: - break; - } - _proxyDetails.AddRange(lstDetails); + private void RefreshProxyDetails(bool c) + { + _proxyDetails.Clear(); + if (!c) + { + return; + } + var name = SelectedGroup?.Name; + if (name.IsNullOrEmpty()) + { + return; + } + if (_proxies == null) + { + return; } - private ProxiesItem? TryGetProxy(string name) + _proxies.TryGetValue(name, out var proxy); + if (proxy?.all == null) { - if (_proxies is null) - return null; - _proxies.TryGetValue(name, out var proxy2); - if (proxy2 != null) + return; + } + var lstDetails = new List(); + foreach (var item in proxy.all) + { + var proxy2 = TryGetProxy(item); + if (proxy2 == null) { - return proxy2; + continue; } - //from providers - if (_providers != null) + var delay = proxy2.history?.Count > 0 ? proxy2.history.Last().delay : -1; + + lstDetails.Add(new ClashProxyModel() { - foreach (KeyValuePair kv in _providers) + IsActive = item == proxy.now, + Name = item, + Type = proxy2.type, + Delay = delay <= 0 ? _delayTimeout : delay, + DelayName = delay <= 0 ? string.Empty : $"{delay}ms", + }); + } + //sort + switch (SortingSelected) + { + case 0: + lstDetails = lstDetails.OrderBy(t => t.Delay).ToList(); + break; + + case 1: + lstDetails = lstDetails.OrderBy(t => t.Name).ToList(); + break; + + default: + break; + } + _proxyDetails.AddRange(lstDetails); + } + + private ProxiesItem? TryGetProxy(string name) + { + if (_proxies is null) + return null; + _proxies.TryGetValue(name, out var proxy2); + if (proxy2 != null) + { + return proxy2; + } + //from providers + if (_providers != null) + { + foreach (KeyValuePair kv in _providers) + { + if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower())) { - if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower())) + var proxy3 = kv.Value.proxies.FirstOrDefault(t => t.name == name); + if (proxy3 != null) { - var proxy3 = kv.Value.proxies.FirstOrDefault(t => t.name == name); - if (proxy3 != null) - { - return proxy3; - } + return proxy3; } } } - return null; } - - public async Task SetActiveProxy() - { - if (SelectedGroup == null || SelectedGroup.Name.IsNullOrEmpty()) - { - return; - } - if (SelectedDetail == null || SelectedDetail.Name.IsNullOrEmpty()) - { - return; - } - var name = SelectedGroup.Name; - if (name.IsNullOrEmpty()) - { - return; - } - var nameNode = SelectedDetail.Name; - if (nameNode.IsNullOrEmpty()) - { - return; - } - var selectedProxy = TryGetProxy(name); - if (selectedProxy == null || selectedProxy.type != "Selector") - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - return; - } - - await ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode); - - selectedProxy.now = nameNode; - var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name); - if (group != null) - { - group.Now = nameNode; - var group2 = JsonUtils.DeepCopy(group); - _proxyGroups.Replace(group, group2); - - SelectedGroup = group2; - } - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } - - private async Task ProxiesDelayTest(bool blAll = true) - { - ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) => - { - if (item == null || result.IsNullOrEmpty()) - { - return; - } - - _updateView?.Invoke(EViewAction.DispatcherProxiesDelayTest, new SpeedTestResult() { IndexId = item.Name, Delay = result }); - }); - await Task.CompletedTask; - } - - public void ProxiesDelayTestResult(SpeedTestResult result) - { - //UpdateHandler(false, $"{item.name}={result}"); - var detail = _proxyDetails.FirstOrDefault(it => it.Name == result.IndexId); - if (detail == null) - { - return; - } - - var dicResult = JsonUtils.Deserialize>(result.Delay); - if (dicResult != null && dicResult.TryGetValue("delay", out var value)) - { - detail.Delay = Convert.ToInt32(value.ToString()); - detail.DelayName = $"{detail.Delay}ms"; - } - else if (dicResult != null && dicResult.TryGetValue("message", out var value1)) - { - detail.Delay = _delayTimeout; - detail.DelayName = $"{value1}"; - } - else - { - detail.Delay = _delayTimeout; - detail.DelayName = string.Empty; - } - _proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail)); - } - - #endregion proxy function - - #region task - - public async Task DelayTestTask() - { - _ = Task.Run(async () => - { - var numOfExecuted = 1; - while (true) - { - await Task.Delay(1000 * 60); - numOfExecuted++; - if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box))) - { - continue; - } - if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0) - { - continue; - } - if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0) - { - continue; - } - await ProxiesDelayTest(); - } - }); - await Task.CompletedTask; - } - - #endregion task + return null; } + + public async Task SetActiveProxy() + { + if (SelectedGroup == null || SelectedGroup.Name.IsNullOrEmpty()) + { + return; + } + if (SelectedDetail == null || SelectedDetail.Name.IsNullOrEmpty()) + { + return; + } + var name = SelectedGroup.Name; + if (name.IsNullOrEmpty()) + { + return; + } + var nameNode = SelectedDetail.Name; + if (nameNode.IsNullOrEmpty()) + { + return; + } + var selectedProxy = TryGetProxy(name); + if (selectedProxy == null || selectedProxy.type != "Selector") + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); + return; + } + + await ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode); + + selectedProxy.now = nameNode; + var group = _proxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name); + if (group != null) + { + group.Now = nameNode; + var group2 = JsonUtils.DeepCopy(group); + _proxyGroups.Replace(group, group2); + + SelectedGroup = group2; + } + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + } + + private async Task ProxiesDelayTest(bool blAll = true) + { + ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) => + { + if (item == null || result.IsNullOrEmpty()) + { + return; + } + + _updateView?.Invoke(EViewAction.DispatcherProxiesDelayTest, new SpeedTestResult() { IndexId = item.Name, Delay = result }); + }); + await Task.CompletedTask; + } + + public void ProxiesDelayTestResult(SpeedTestResult result) + { + //UpdateHandler(false, $"{item.name}={result}"); + var detail = _proxyDetails.FirstOrDefault(it => it.Name == result.IndexId); + if (detail == null) + { + return; + } + + var dicResult = JsonUtils.Deserialize>(result.Delay); + if (dicResult != null && dicResult.TryGetValue("delay", out var value)) + { + detail.Delay = Convert.ToInt32(value.ToString()); + detail.DelayName = $"{detail.Delay}ms"; + } + else if (dicResult != null && dicResult.TryGetValue("message", out var value1)) + { + detail.Delay = _delayTimeout; + detail.DelayName = $"{value1}"; + } + else + { + detail.Delay = _delayTimeout; + detail.DelayName = string.Empty; + } + _proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail)); + } + + #endregion proxy function + + #region task + + public async Task DelayTestTask() + { + _ = Task.Run(async () => + { + var numOfExecuted = 1; + while (true) + { + await Task.Delay(1000 * 60); + numOfExecuted++; + if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box))) + { + continue; + } + if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0) + { + continue; + } + if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0) + { + continue; + } + await ProxiesDelayTest(); + } + }); + await Task.CompletedTask; + } + + #endregion task } diff --git a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs index b4d9bb83..1d6829b8 100644 --- a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs @@ -2,116 +2,115 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class DNSSettingViewModel : MyReactiveObject { - public class DNSSettingViewModel : MyReactiveObject + [Reactive] public bool UseSystemHosts { get; set; } + [Reactive] public string DomainStrategy4Freedom { get; set; } + [Reactive] public string DomainDNSAddress { get; set; } + [Reactive] public string NormalDNS { get; set; } + + [Reactive] public string DomainStrategy4Freedom2 { get; set; } + [Reactive] public string DomainDNSAddress2 { get; set; } + [Reactive] public string NormalDNS2 { get; set; } + [Reactive] public string TunDNS2 { get; set; } + + public ReactiveCommand SaveCmd { get; } + public ReactiveCommand ImportDefConfig4V2rayCmd { get; } + public ReactiveCommand ImportDefConfig4SingboxCmd { get; } + + public DNSSettingViewModel(Func>? updateView) { - [Reactive] public bool UseSystemHosts { get; set; } - [Reactive] public string DomainStrategy4Freedom { get; set; } - [Reactive] public string DomainDNSAddress { get; set; } - [Reactive] public string NormalDNS { get; set; } - - [Reactive] public string DomainStrategy4Freedom2 { get; set; } - [Reactive] public string DomainDNSAddress2 { get; set; } - [Reactive] public string NormalDNS2 { get; set; } - [Reactive] public string TunDNS2 { get; set; } - - public ReactiveCommand SaveCmd { get; } - public ReactiveCommand ImportDefConfig4V2rayCmd { get; } - public ReactiveCommand ImportDefConfig4SingboxCmd { get; } - - public DNSSettingViewModel(Func>? updateView) + _config = AppHandler.Instance.Config; + _updateView = updateView; + SaveCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveSettingAsync(); - }); + await SaveSettingAsync(); + }); - ImportDefConfig4V2rayCmd = ReactiveCommand.CreateFromTask(async () => - { - NormalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); - await Task.CompletedTask; - }); - - ImportDefConfig4SingboxCmd = ReactiveCommand.CreateFromTask(async () => - { - NormalDNS2 = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); - TunDNS2 = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); - await Task.CompletedTask; - }); - - _ = Init(); - } - - private async Task Init() + ImportDefConfig4V2rayCmd = ReactiveCommand.CreateFromTask(async () => { - var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); - UseSystemHosts = item.UseSystemHosts; - DomainStrategy4Freedom = item?.DomainStrategy4Freedom ?? string.Empty; - DomainDNSAddress = item?.DomainDNSAddress ?? string.Empty; - NormalDNS = item?.NormalDNS ?? string.Empty; + NormalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); + await Task.CompletedTask; + }); - var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); - DomainStrategy4Freedom2 = item2?.DomainStrategy4Freedom ?? string.Empty; - DomainDNSAddress2 = item2?.DomainDNSAddress ?? string.Empty; - NormalDNS2 = item2?.NormalDNS ?? string.Empty; - TunDNS2 = item2?.TunDNS ?? string.Empty; - } - - private async Task SaveSettingAsync() + ImportDefConfig4SingboxCmd = ReactiveCommand.CreateFromTask(async () => { - if (NormalDNS.IsNotEmpty()) + NormalDNS2 = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); + TunDNS2 = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); + await Task.CompletedTask; + }); + + _ = Init(); + } + + private async Task Init() + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + UseSystemHosts = item.UseSystemHosts; + DomainStrategy4Freedom = item?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddress = item?.DomainDNSAddress ?? string.Empty; + NormalDNS = item?.NormalDNS ?? string.Empty; + + var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + DomainStrategy4Freedom2 = item2?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddress2 = item2?.DomainDNSAddress ?? string.Empty; + NormalDNS2 = item2?.NormalDNS ?? string.Empty; + TunDNS2 = item2?.TunDNS ?? string.Empty; + } + + private async Task SaveSettingAsync() + { + if (NormalDNS.IsNotEmpty()) + { + var obj = JsonUtils.ParseJson(NormalDNS); + if (obj != null && obj["servers"] != null) { - var obj = JsonUtils.ParseJson(NormalDNS); - if (obj != null && obj["servers"] != null) - { - } - else - { - if (NormalDNS.Contains('{') || NormalDNS.Contains('}')) - { - NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); - return; - } - } } - if (NormalDNS2.IsNotEmpty()) + else { - var obj2 = JsonUtils.Deserialize(NormalDNS2); - if (obj2 == null) + if (NormalDNS.Contains('{') || NormalDNS.Contains('}')) { NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } - if (TunDNS2.IsNotEmpty()) - { - var obj2 = JsonUtils.Deserialize(TunDNS2); - if (obj2 == null) - { - NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); - return; - } - } - - var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); - item.DomainStrategy4Freedom = DomainStrategy4Freedom; - item.DomainDNSAddress = DomainDNSAddress; - item.UseSystemHosts = UseSystemHosts; - item.NormalDNS = NormalDNS; - await ConfigHandler.SaveDNSItems(_config, item); - - var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); - item2.DomainStrategy4Freedom = DomainStrategy4Freedom2; - item2.DomainDNSAddress = DomainDNSAddress2; - item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2)); - item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2)); - await ConfigHandler.SaveDNSItems(_config, item2); - - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _ = _updateView?.Invoke(EViewAction.CloseWindow, null); } + if (NormalDNS2.IsNotEmpty()) + { + var obj2 = JsonUtils.Deserialize(NormalDNS2); + if (obj2 == null) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); + return; + } + } + if (TunDNS2.IsNotEmpty()) + { + var obj2 = JsonUtils.Deserialize(TunDNS2); + if (obj2 == null) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); + return; + } + } + + var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + item.DomainStrategy4Freedom = DomainStrategy4Freedom; + item.DomainDNSAddress = DomainDNSAddress; + item.UseSystemHosts = UseSystemHosts; + item.NormalDNS = NormalDNS; + await ConfigHandler.SaveDNSItems(_config, item); + + var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + item2.DomainStrategy4Freedom = DomainStrategy4Freedom2; + item2.DomainDNSAddress = DomainDNSAddress2; + item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2)); + item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2)); + await ConfigHandler.SaveDNSItems(_config, item2); + + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _ = _updateView?.Invoke(EViewAction.CloseWindow, null); } } diff --git a/v2rayN/ServiceLib/ViewModels/GlobalHotkeySettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/GlobalHotkeySettingViewModel.cs index 33a222fb..ba3293c9 100644 --- a/v2rayN/ServiceLib/ViewModels/GlobalHotkeySettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/GlobalHotkeySettingViewModel.cs @@ -1,65 +1,64 @@ using System.Reactive; using ReactiveUI; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class GlobalHotkeySettingViewModel : MyReactiveObject { - public class GlobalHotkeySettingViewModel : MyReactiveObject + private readonly List _globalHotkeys; + + public ReactiveCommand SaveCmd { get; } + + public GlobalHotkeySettingViewModel(Func>? updateView) { - private readonly List _globalHotkeys; + _config = AppHandler.Instance.Config; + _updateView = updateView; - public ReactiveCommand SaveCmd { get; } + _globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys); - public GlobalHotkeySettingViewModel(Func>? updateView) + SaveCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await SaveSettingAsync(); + }); + } - _globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys); - - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveSettingAsync(); - }); - } - - public KeyEventItem GetKeyEventItem(EGlobalHotkey eg) + public KeyEventItem GetKeyEventItem(EGlobalHotkey eg) + { + var item = _globalHotkeys.FirstOrDefault((it) => it.EGlobalHotkey == eg); + if (item != null) { - var item = _globalHotkeys.FirstOrDefault((it) => it.EGlobalHotkey == eg); - if (item != null) - { - return item; - } - - item = new() - { - EGlobalHotkey = eg, - Control = false, - Alt = false, - Shift = false, - KeyCode = null - }; - _globalHotkeys.Add(item); - return item; } - public void ResetKeyEventItem() + item = new() { - _globalHotkeys.Clear(); + EGlobalHotkey = eg, + Control = false, + Alt = false, + Shift = false, + KeyCode = null + }; + _globalHotkeys.Add(item); + + return item; + } + + public void ResetKeyEventItem() + { + _globalHotkeys.Clear(); + } + + private async Task SaveSettingAsync() + { + _config.GlobalHotkeys = _globalHotkeys; + + if (await ConfigHandler.SaveConfig(_config) == 0) + { + _updateView?.Invoke(EViewAction.CloseWindow, null); } - - private async Task SaveSettingAsync() + else { - _config.GlobalHotkeys = _globalHotkeys; - - if (await ConfigHandler.SaveConfig(_config) == 0) - { - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } } diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 263f2315..ec07b825 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -3,614 +3,613 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class MainWindowViewModel : MyReactiveObject { - public class MainWindowViewModel : MyReactiveObject + #region Menu + + //servers + public ReactiveCommand AddVmessServerCmd { get; } + + public ReactiveCommand AddVlessServerCmd { get; } + public ReactiveCommand AddShadowsocksServerCmd { get; } + public ReactiveCommand AddSocksServerCmd { get; } + public ReactiveCommand AddHttpServerCmd { get; } + public ReactiveCommand AddTrojanServerCmd { get; } + public ReactiveCommand AddHysteria2ServerCmd { get; } + public ReactiveCommand AddTuicServerCmd { get; } + public ReactiveCommand AddWireguardServerCmd { get; } + public ReactiveCommand AddCustomServerCmd { get; } + public ReactiveCommand AddServerViaClipboardCmd { get; } + public ReactiveCommand AddServerViaScanCmd { get; } + public ReactiveCommand AddServerViaImageCmd { get; } + + //Subscription + public ReactiveCommand SubSettingCmd { get; } + + public ReactiveCommand SubUpdateCmd { get; } + public ReactiveCommand SubUpdateViaProxyCmd { get; } + public ReactiveCommand SubGroupUpdateCmd { get; } + public ReactiveCommand SubGroupUpdateViaProxyCmd { get; } + + //Setting + public ReactiveCommand OptionSettingCmd { get; } + + public ReactiveCommand RoutingSettingCmd { get; } + public ReactiveCommand DNSSettingCmd { get; } + public ReactiveCommand GlobalHotkeySettingCmd { get; } + public ReactiveCommand RebootAsAdminCmd { get; } + public ReactiveCommand ClearServerStatisticsCmd { get; } + public ReactiveCommand OpenTheFileLocationCmd { get; } + + //Presets + public ReactiveCommand RegionalPresetDefaultCmd { get; } + + public ReactiveCommand RegionalPresetRussiaCmd { get; } + + public ReactiveCommand RegionalPresetIranCmd { get; } + + public ReactiveCommand ReloadCmd { get; } + + [Reactive] + public bool BlReloadEnabled { get; set; } + + [Reactive] + public bool ShowClashUI { get; set; } + + [Reactive] + public int TabMainSelectedIndex { get; set; } + + #endregion Menu + + private bool _hasNextReloadJob = false; + + #region Init + + public MainWindowViewModel(Func>? updateView) { - #region Menu + _config = AppHandler.Instance.Config; + _updateView = updateView; + + #region WhenAnyValue && ReactiveCommand //servers - public ReactiveCommand AddVmessServerCmd { get; } - - public ReactiveCommand AddVlessServerCmd { get; } - public ReactiveCommand AddShadowsocksServerCmd { get; } - public ReactiveCommand AddSocksServerCmd { get; } - public ReactiveCommand AddHttpServerCmd { get; } - public ReactiveCommand AddTrojanServerCmd { get; } - public ReactiveCommand AddHysteria2ServerCmd { get; } - public ReactiveCommand AddTuicServerCmd { get; } - public ReactiveCommand AddWireguardServerCmd { get; } - public ReactiveCommand AddCustomServerCmd { get; } - public ReactiveCommand AddServerViaClipboardCmd { get; } - public ReactiveCommand AddServerViaScanCmd { get; } - public ReactiveCommand AddServerViaImageCmd { get; } + AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.VMess); + }); + AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.VLESS); + }); + AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Shadowsocks); + }); + AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.SOCKS); + }); + AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.HTTP); + }); + AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Trojan); + }); + AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Hysteria2); + }); + AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.TUIC); + }); + AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.WireGuard); + }); + AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Custom); + }); + AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerViaClipboardAsync(null); + }); + AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerViaScanAsync(); + }); + AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerViaImageAsync(); + }); //Subscription - public ReactiveCommand SubSettingCmd { get; } + SubSettingCmd = ReactiveCommand.CreateFromTask(async () => + { + await SubSettingAsync(); + }); - public ReactiveCommand SubUpdateCmd { get; } - public ReactiveCommand SubUpdateViaProxyCmd { get; } - public ReactiveCommand SubGroupUpdateCmd { get; } - public ReactiveCommand SubGroupUpdateViaProxyCmd { get; } + SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess("", false); + }); + SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess("", true); + }); + SubGroupUpdateCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess(_config.SubIndexId, false); + }); + SubGroupUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess(_config.SubIndexId, true); + }); //Setting - public ReactiveCommand OptionSettingCmd { get; } - - public ReactiveCommand RoutingSettingCmd { get; } - public ReactiveCommand DNSSettingCmd { get; } - public ReactiveCommand GlobalHotkeySettingCmd { get; } - public ReactiveCommand RebootAsAdminCmd { get; } - public ReactiveCommand ClearServerStatisticsCmd { get; } - public ReactiveCommand OpenTheFileLocationCmd { get; } - - //Presets - public ReactiveCommand RegionalPresetDefaultCmd { get; } - - public ReactiveCommand RegionalPresetRussiaCmd { get; } - - public ReactiveCommand RegionalPresetIranCmd { get; } - - public ReactiveCommand ReloadCmd { get; } - - [Reactive] - public bool BlReloadEnabled { get; set; } - - [Reactive] - public bool ShowClashUI { get; set; } - - [Reactive] - public int TabMainSelectedIndex { get; set; } - - #endregion Menu - - private bool _hasNextReloadJob = false; - - #region Init - - public MainWindowViewModel(Func>? updateView) + OptionSettingCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await OptionSettingAsync(); + }); + RoutingSettingCmd = ReactiveCommand.CreateFromTask(async () => + { + await RoutingSettingAsync(); + }); + DNSSettingCmd = ReactiveCommand.CreateFromTask(async () => + { + await DNSSettingAsync(); + }); + GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () => + { + if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) + { + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + } + }); + RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () => + { + await RebootAsAdmin(); + }); + ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () => + { + await ClearServerStatistics(); + }); + OpenTheFileLocationCmd = ReactiveCommand.CreateFromTask(async () => + { + await OpenTheFileLocation(); + }); - #region WhenAnyValue && ReactiveCommand + ReloadCmd = ReactiveCommand.CreateFromTask(async () => + { + await Reload(); + }); - //servers - AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.VMess); - }); - AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.VLESS); - }); - AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.Shadowsocks); - }); - AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.SOCKS); - }); - AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.HTTP); - }); - AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.Trojan); - }); - AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.Hysteria2); - }); - AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.TUIC); - }); - AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.WireGuard); - }); - AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerAsync(true, EConfigType.Custom); - }); - AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaClipboardAsync(null); - }); - AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaScanAsync(); - }); - AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaImageAsync(); - }); + RegionalPresetDefaultCmd = ReactiveCommand.CreateFromTask(async () => + { + await ApplyRegionalPreset(EPresetType.Default); + }); - //Subscription - SubSettingCmd = ReactiveCommand.CreateFromTask(async () => - { - await SubSettingAsync(); - }); + RegionalPresetRussiaCmd = ReactiveCommand.CreateFromTask(async () => + { + await ApplyRegionalPreset(EPresetType.Russia); + }); - SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess("", false); - }); - SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess("", true); - }); - SubGroupUpdateCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess(_config.SubIndexId, false); - }); - SubGroupUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess(_config.SubIndexId, true); - }); + RegionalPresetIranCmd = ReactiveCommand.CreateFromTask(async () => + { + await ApplyRegionalPreset(EPresetType.Iran); + }); - //Setting - OptionSettingCmd = ReactiveCommand.CreateFromTask(async () => - { - await OptionSettingAsync(); - }); - RoutingSettingCmd = ReactiveCommand.CreateFromTask(async () => - { - await RoutingSettingAsync(); - }); - DNSSettingCmd = ReactiveCommand.CreateFromTask(async () => - { - await DNSSettingAsync(); - }); - GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () => - { - if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } - }); - RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () => - { - await RebootAsAdmin(); - }); - ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () => - { - await ClearServerStatistics(); - }); - OpenTheFileLocationCmd = ReactiveCommand.CreateFromTask(async () => - { - await OpenTheFileLocation(); - }); + #endregion WhenAnyValue && ReactiveCommand - ReloadCmd = ReactiveCommand.CreateFromTask(async () => + _ = Init(); + } + + private async Task Init() + { + _config.UiItem.ShowInTaskbar = true; + + await ConfigHandler.InitBuiltinRouting(_config); + await ConfigHandler.InitBuiltinDNS(_config); + await ProfileExHandler.Instance.Init(); + await CoreHandler.Instance.Init(_config, UpdateHandler); + TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); + + if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) + { + await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler); + } + + BlReloadEnabled = true; + await Reload(); + await AutoHideStartup(); + Locator.Current.GetService()?.RefreshRoutingsMenu(); + } + + #endregion Init + + #region Actions + + private void UpdateHandler(bool notify, string msg) + { + NoticeHandler.Instance.SendMessage(msg); + if (notify) + { + NoticeHandler.Instance.Enqueue(msg); + } + } + + private void UpdateTaskHandler(bool success, string msg) + { + NoticeHandler.Instance.SendMessageEx(msg); + if (success) + { + var indexIdOld = _config.IndexId; + RefreshServers(); + if (indexIdOld != _config.IndexId) + { + _ = Reload(); + } + if (_config.UiItem.EnableAutoAdjustMainLvColWidth) + { + _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null); + } + } + } + + private void UpdateStatisticsHandler(ServerSpeedItem update) + { + if (!_config.UiItem.ShowInTaskbar) + { + return; + } + _updateView?.Invoke(EViewAction.DispatcherStatistics, update); + } + + public void SetStatisticsResult(ServerSpeedItem update) + { + if (_config.GuiItem.DisplayRealTimeSpeed) + { + Locator.Current.GetService()?.UpdateStatistics(update); + } + if (_config.GuiItem.EnableStatistics && (update.ProxyUp + update.ProxyDown) > 0 && DateTime.Now.Second % 9 == 0) + { + Locator.Current.GetService()?.UpdateStatistics(update); + } + } + + public async Task MyAppExitAsync(bool blWindowsShutDown) + { + try + { + Logging.SaveLog("MyAppExitAsync Begin"); + + await SysProxyHandler.UpdateSysProxy(_config, true); + MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString()); + + await ConfigHandler.SaveConfig(_config); + await ProfileExHandler.Instance.SaveTo(); + await StatisticsHandler.Instance.SaveTo(); + await CoreHandler.Instance.CoreStop(); + StatisticsHandler.Instance.Close(); + + Logging.SaveLog("MyAppExitAsync End"); + } + catch { } + finally + { + if (!blWindowsShutDown) + { + _updateView?.Invoke(EViewAction.Shutdown, false); + } + } + } + + public async Task UpgradeApp(string arg) + { + if (!Utils.UpgradeAppExists(out var upgradeFileName)) + { + NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip); + Logging.SaveLog("UpgradeApp does not exist"); + return; + } + + var id = ProcUtils.ProcessStart(upgradeFileName, arg, Utils.StartupPath()); + if (id > 0) + { + await MyAppExitAsync(false); + } + } + + public void ShowHideWindow(bool? blShow) + { + _updateView?.Invoke(EViewAction.ShowHideWindow, blShow); + } + + public void Shutdown(bool byUser) + { + _updateView?.Invoke(EViewAction.Shutdown, byUser); + } + + #endregion Actions + + #region Servers && Groups + + private void RefreshServers() + { + MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString()); + } + + private void RefreshSubscriptions() + { + Locator.Current.GetService()?.RefreshSubscriptions(); + } + + #endregion Servers && Groups + + #region Add Servers + + public async Task AddServerAsync(bool blNew, EConfigType eConfigType) + { + ProfileItem item = new() + { + Subid = _config.SubIndexId, + ConfigType = eConfigType, + IsSub = false, + }; + + bool? ret = false; + if (eConfigType == EConfigType.Custom) + { + ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); + } + else + { + ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); + } + if (ret == true) + { + RefreshServers(); + if (item.IndexId == _config.IndexId) { await Reload(); - }); - - RegionalPresetDefaultCmd = ReactiveCommand.CreateFromTask(async () => - { - await ApplyRegionalPreset(EPresetType.Default); - }); - - RegionalPresetRussiaCmd = ReactiveCommand.CreateFromTask(async () => - { - await ApplyRegionalPreset(EPresetType.Russia); - }); - - RegionalPresetIranCmd = ReactiveCommand.CreateFromTask(async () => - { - await ApplyRegionalPreset(EPresetType.Iran); - }); - - #endregion WhenAnyValue && ReactiveCommand - - _ = Init(); - } - - private async Task Init() - { - _config.UiItem.ShowInTaskbar = true; - - await ConfigHandler.InitBuiltinRouting(_config); - await ConfigHandler.InitBuiltinDNS(_config); - await ProfileExHandler.Instance.Init(); - await CoreHandler.Instance.Init(_config, UpdateHandler); - TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); - - if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) - { - await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler); - } - - BlReloadEnabled = true; - await Reload(); - await AutoHideStartup(); - Locator.Current.GetService()?.RefreshRoutingsMenu(); - } - - #endregion Init - - #region Actions - - private void UpdateHandler(bool notify, string msg) - { - NoticeHandler.Instance.SendMessage(msg); - if (notify) - { - NoticeHandler.Instance.Enqueue(msg); } } + } - private void UpdateTaskHandler(bool success, string msg) + public async Task AddServerViaClipboardAsync(string? clipboardData) + { + if (clipboardData == null) { - NoticeHandler.Instance.SendMessageEx(msg); - if (success) - { - var indexIdOld = _config.IndexId; - RefreshServers(); - if (indexIdOld != _config.IndexId) - { - _ = Reload(); - } - if (_config.UiItem.EnableAutoAdjustMainLvColWidth) - { - _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null); - } - } + await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null); + return; + } + int ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false); + if (ret > 0) + { + RefreshSubscriptions(); + RefreshServers(); + NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); + } + } + + public async Task AddServerViaScanAsync() + { + _updateView?.Invoke(EViewAction.ScanScreenTask, null); + await Task.CompletedTask; + } + + public async Task ScanScreenResult(byte[]? bytes) + { + var result = QRCodeHelper.ParseBarcode(bytes); + await AddScanResultAsync(result); + } + + public async Task AddServerViaImageAsync() + { + _updateView?.Invoke(EViewAction.ScanImageTask, null); + await Task.CompletedTask; + } + + public async Task ScanImageResult(string fileName) + { + if (fileName.IsNullOrEmpty()) + { + return; } - private void UpdateStatisticsHandler(ServerSpeedItem update) + var result = QRCodeHelper.ParseBarcode(fileName); + await AddScanResultAsync(result); + } + + private async Task AddScanResultAsync(string? result) + { + if (result.IsNullOrEmpty()) { - if (!_config.UiItem.ShowInTaskbar) - { - return; - } - _updateView?.Invoke(EViewAction.DispatcherStatistics, update); + NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound); } - - public void SetStatisticsResult(ServerSpeedItem update) + else { - if (_config.GuiItem.DisplayRealTimeSpeed) - { - Locator.Current.GetService()?.UpdateStatistics(update); - } - if (_config.GuiItem.EnableStatistics && (update.ProxyUp + update.ProxyDown) > 0 && DateTime.Now.Second % 9 == 0) - { - Locator.Current.GetService()?.UpdateStatistics(update); - } - } - - public async Task MyAppExitAsync(bool blWindowsShutDown) - { - try - { - Logging.SaveLog("MyAppExitAsync Begin"); - - await SysProxyHandler.UpdateSysProxy(_config, true); - MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString()); - - await ConfigHandler.SaveConfig(_config); - await ProfileExHandler.Instance.SaveTo(); - await StatisticsHandler.Instance.SaveTo(); - await CoreHandler.Instance.CoreStop(); - StatisticsHandler.Instance.Close(); - - Logging.SaveLog("MyAppExitAsync End"); - } - catch { } - finally - { - if (!blWindowsShutDown) - { - _updateView?.Invoke(EViewAction.Shutdown, false); - } - } - } - - public async Task UpgradeApp(string arg) - { - if (!Utils.UpgradeAppExists(out var upgradeFileName)) - { - NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip); - Logging.SaveLog("UpgradeApp does not exist"); - return; - } - - var id = ProcUtils.ProcessStart(upgradeFileName, arg, Utils.StartupPath()); - if (id > 0) - { - await MyAppExitAsync(false); - } - } - - public void ShowHideWindow(bool? blShow) - { - _updateView?.Invoke(EViewAction.ShowHideWindow, blShow); - } - - public void Shutdown(bool byUser) - { - _updateView?.Invoke(EViewAction.Shutdown, byUser); - } - - #endregion Actions - - #region Servers && Groups - - private void RefreshServers() - { - MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString()); - } - - private void RefreshSubscriptions() - { - Locator.Current.GetService()?.RefreshSubscriptions(); - } - - #endregion Servers && Groups - - #region Add Servers - - public async Task AddServerAsync(bool blNew, EConfigType eConfigType) - { - ProfileItem item = new() - { - Subid = _config.SubIndexId, - ConfigType = eConfigType, - IsSub = false, - }; - - bool? ret = false; - if (eConfigType == EConfigType.Custom) - { - ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); - } - else - { - ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); - } - if (ret == true) - { - RefreshServers(); - if (item.IndexId == _config.IndexId) - { - await Reload(); - } - } - } - - public async Task AddServerViaClipboardAsync(string? clipboardData) - { - if (clipboardData == null) - { - await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null); - return; - } - int ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false); + int ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false); if (ret > 0) { RefreshSubscriptions(); RefreshServers(); - NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); + NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan); } else { NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } + } - public async Task AddServerViaScanAsync() + #endregion Add Servers + + #region Subscription + + private async Task SubSettingAsync() + { + if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true) { - _updateView?.Invoke(EViewAction.ScanScreenTask, null); - await Task.CompletedTask; + RefreshSubscriptions(); } + } - public async Task ScanScreenResult(byte[]? bytes) + public async Task UpdateSubscriptionProcess(string subId, bool blProxy) + { + await (new UpdateService()).UpdateSubscriptionProcess(_config, subId, blProxy, UpdateTaskHandler); + } + + #endregion Subscription + + #region Setting + + private async Task OptionSettingAsync() + { + var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); + if (ret == true) { - var result = QRCodeHelper.ParseBarcode(bytes); - await AddScanResultAsync(result); - } - - public async Task AddServerViaImageAsync() - { - _updateView?.Invoke(EViewAction.ScanImageTask, null); - await Task.CompletedTask; - } - - public async Task ScanImageResult(string fileName) - { - if (fileName.IsNullOrEmpty()) - { - return; - } - - var result = QRCodeHelper.ParseBarcode(fileName); - await AddScanResultAsync(result); - } - - private async Task AddScanResultAsync(string? result) - { - if (result.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound); - } - else - { - int ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false); - if (ret > 0) - { - RefreshSubscriptions(); - RefreshServers(); - NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } - } - } - - #endregion Add Servers - - #region Subscription - - private async Task SubSettingAsync() - { - if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true) - { - RefreshSubscriptions(); - } - } - - public async Task UpdateSubscriptionProcess(string subId, bool blProxy) - { - await (new UpdateService()).UpdateSubscriptionProcess(_config, subId, blProxy, UpdateTaskHandler); - } - - #endregion Subscription - - #region Setting - - private async Task OptionSettingAsync() - { - var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); - if (ret == true) - { - Locator.Current.GetService()?.InboundDisplayStatus(); - await Reload(); - } - } - - private async Task RoutingSettingAsync() - { - var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null); - if (ret == true) - { - await ConfigHandler.InitBuiltinRouting(_config); - Locator.Current.GetService()?.RefreshRoutingsMenu(); - await Reload(); - } - } - - private async Task DNSSettingAsync() - { - var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null); - if (ret == true) - { - await Reload(); - } - } - - public async Task RebootAsAdmin() - { - ProcUtils.RebootAsAdmin(); - await MyAppExitAsync(false); - } - - private async Task ClearServerStatistics() - { - await StatisticsHandler.Instance.ClearAllServerStatistics(); - RefreshServers(); - } - - private async Task OpenTheFileLocation() - { - var path = Utils.StartupPath(); - if (Utils.IsWindows()) - { - ProcUtils.ProcessStart(path); - } - else if (Utils.IsLinux()) - { - ProcUtils.ProcessStart("nautilus", path); - } - else if (Utils.IsOSX()) - { - ProcUtils.ProcessStart("open", path); - } - await Task.CompletedTask; - } - - #endregion Setting - - #region core job - - public async Task Reload() - { - //If there are unfinished reload job, marked with next job. - if (!BlReloadEnabled) - { - _hasNextReloadJob = true; - return; - } - - BlReloadEnabled = false; - - await LoadCore(); - await SysProxyHandler.UpdateSysProxy(_config, false); - Locator.Current.GetService()?.TestServerAvailability(); - - _updateView?.Invoke(EViewAction.DispatcherReload, null); - - BlReloadEnabled = true; - if (_hasNextReloadJob) - { - _hasNextReloadJob = false; - await Reload(); - } - } - - public void ReloadResult() - { - // BlReloadEnabled = true; - //Locator.Current.GetService()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false); - ShowClashUI = _config.IsRunningCore(ECoreType.sing_box); - if (ShowClashUI) - { - Locator.Current.GetService()?.ProxiesReload(); - } - else - { TabMainSelectedIndex = 0; } - } - - private async Task LoadCore() - { - var node = await ConfigHandler.GetDefaultServer(_config); - await CoreHandler.Instance.LoadCore(node); - } - - public async Task CloseCore() - { - await ConfigHandler.SaveConfig(_config); - await CoreHandler.Instance.CoreStop(); - } - - private async Task AutoHideStartup() - { - if (_config.UiItem.AutoHideStartup) - { - ShowHideWindow(false); - } - await Task.CompletedTask; - } - - #endregion core job - - #region Presets - - public async Task ApplyRegionalPreset(EPresetType type) - { - await ConfigHandler.ApplyRegionalPreset(_config, type); - await ConfigHandler.InitRouting(_config); - Locator.Current.GetService()?.RefreshRoutingsMenu(); - - await ConfigHandler.SaveConfig(_config); - await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler); + Locator.Current.GetService()?.InboundDisplayStatus(); await Reload(); } - - #endregion Presets } + + private async Task RoutingSettingAsync() + { + var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null); + if (ret == true) + { + await ConfigHandler.InitBuiltinRouting(_config); + Locator.Current.GetService()?.RefreshRoutingsMenu(); + await Reload(); + } + } + + private async Task DNSSettingAsync() + { + var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null); + if (ret == true) + { + await Reload(); + } + } + + public async Task RebootAsAdmin() + { + ProcUtils.RebootAsAdmin(); + await MyAppExitAsync(false); + } + + private async Task ClearServerStatistics() + { + await StatisticsHandler.Instance.ClearAllServerStatistics(); + RefreshServers(); + } + + private async Task OpenTheFileLocation() + { + var path = Utils.StartupPath(); + if (Utils.IsWindows()) + { + ProcUtils.ProcessStart(path); + } + else if (Utils.IsLinux()) + { + ProcUtils.ProcessStart("nautilus", path); + } + else if (Utils.IsOSX()) + { + ProcUtils.ProcessStart("open", path); + } + await Task.CompletedTask; + } + + #endregion Setting + + #region core job + + public async Task Reload() + { + //If there are unfinished reload job, marked with next job. + if (!BlReloadEnabled) + { + _hasNextReloadJob = true; + return; + } + + BlReloadEnabled = false; + + await LoadCore(); + await SysProxyHandler.UpdateSysProxy(_config, false); + Locator.Current.GetService()?.TestServerAvailability(); + + _updateView?.Invoke(EViewAction.DispatcherReload, null); + + BlReloadEnabled = true; + if (_hasNextReloadJob) + { + _hasNextReloadJob = false; + await Reload(); + } + } + + public void ReloadResult() + { + // BlReloadEnabled = true; + //Locator.Current.GetService()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false); + ShowClashUI = _config.IsRunningCore(ECoreType.sing_box); + if (ShowClashUI) + { + Locator.Current.GetService()?.ProxiesReload(); + } + else + { TabMainSelectedIndex = 0; } + } + + private async Task LoadCore() + { + var node = await ConfigHandler.GetDefaultServer(_config); + await CoreHandler.Instance.LoadCore(node); + } + + public async Task CloseCore() + { + await ConfigHandler.SaveConfig(_config); + await CoreHandler.Instance.CoreStop(); + } + + private async Task AutoHideStartup() + { + if (_config.UiItem.AutoHideStartup) + { + ShowHideWindow(false); + } + await Task.CompletedTask; + } + + #endregion core job + + #region Presets + + public async Task ApplyRegionalPreset(EPresetType type) + { + await ConfigHandler.ApplyRegionalPreset(_config, type); + await ConfigHandler.InitRouting(_config); + Locator.Current.GetService()?.RefreshRoutingsMenu(); + + await ConfigHandler.SaveConfig(_config); + await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler); + await Reload(); + } + + #endregion Presets } diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index ba3d85e9..80c19ebd 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -3,120 +3,119 @@ using System.Text.RegularExpressions; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class MsgViewModel : MyReactiveObject { - public class MsgViewModel : MyReactiveObject + private ConcurrentQueue _queueMsg = new(); + private int _numMaxMsg = 500; + private bool _lastMsgFilterNotAvailable; + private bool _blLockShow = false; + + [Reactive] + public string MsgFilter { get; set; } + + [Reactive] + public bool AutoRefresh { get; set; } + + public MsgViewModel(Func>? updateView) { - private ConcurrentQueue _queueMsg = new(); - private int _numMaxMsg = 500; - private bool _lastMsgFilterNotAvailable; - private bool _blLockShow = false; + _config = AppHandler.Instance.Config; + _updateView = updateView; + MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; + AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; - [Reactive] - public string MsgFilter { get; set; } + this.WhenAnyValue( + x => x.MsgFilter) + .Subscribe(c => DoMsgFilter()); - [Reactive] - public bool AutoRefresh { get; set; } + this.WhenAnyValue( + x => x.AutoRefresh, + y => y == true) + .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); - public MsgViewModel(Func>? updateView) + MessageBus.Current.Listen(EMsgCommand.SendMsgView.ToString()).Subscribe(OnNext); + } + + private async void OnNext(string x) + { + await AppendQueueMsg(x); + } + + private async Task AppendQueueMsg(string msg) + { + //if (msg == Global.CommandClearMsg) + //{ + // ClearMsg(); + // return; + //} + if (AutoRefresh == false) { - _config = AppHandler.Instance.Config; - _updateView = updateView; - MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; - AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; + return; + } + _ = EnqueueQueueMsg(msg); - this.WhenAnyValue( - x => x.MsgFilter) - .Subscribe(c => DoMsgFilter()); - - this.WhenAnyValue( - x => x.AutoRefresh, - y => y == true) - .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); - - MessageBus.Current.Listen(EMsgCommand.SendMsgView.ToString()).Subscribe(OnNext); + if (_blLockShow) + { + return; + } + if (!_config.UiItem.ShowInTaskbar) + { + return; } - private async void OnNext(string x) + _blLockShow = true; + + await Task.Delay(500); + var txt = string.Join("", _queueMsg.ToArray()); + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt); + + _blLockShow = false; + } + + private async Task EnqueueQueueMsg(string msg) + { + //filter msg + if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) { - await AppendQueueMsg(x); - } - - private async Task AppendQueueMsg(string msg) - { - //if (msg == Global.CommandClearMsg) - //{ - // ClearMsg(); - // return; - //} - if (AutoRefresh == false) + try { - return; - } - _ = EnqueueQueueMsg(msg); - - if (_blLockShow) - { - return; - } - if (!_config.UiItem.ShowInTaskbar) - { - return; - } - - _blLockShow = true; - - await Task.Delay(500); - var txt = string.Join("", _queueMsg.ToArray()); - await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt); - - _blLockShow = false; - } - - private async Task EnqueueQueueMsg(string msg) - { - //filter msg - if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) - { - try + if (!Regex.IsMatch(msg, MsgFilter)) { - if (!Regex.IsMatch(msg, MsgFilter)) - { - return; - } - } - catch (Exception ex) - { - _queueMsg.Enqueue(ex.Message); - _lastMsgFilterNotAvailable = true; + return; } } - - //Enqueue - if (_queueMsg.Count > _numMaxMsg) + catch (Exception ex) { - for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) - { - _queueMsg.TryDequeue(out _); - } + _queueMsg.Enqueue(ex.Message); + _lastMsgFilterNotAvailable = true; } - _queueMsg.Enqueue(msg); - if (!msg.EndsWith(Environment.NewLine)) + } + + //Enqueue + if (_queueMsg.Count > _numMaxMsg) + { + for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) { - _queueMsg.Enqueue(Environment.NewLine); + _queueMsg.TryDequeue(out _); } - await Task.CompletedTask; } - - public void ClearMsg() + _queueMsg.Enqueue(msg); + if (!msg.EndsWith(Environment.NewLine)) { - _queueMsg.Clear(); + _queueMsg.Enqueue(Environment.NewLine); } + await Task.CompletedTask; + } - private void DoMsgFilter() - { - _config.MsgUIItem.MainMsgFilter = MsgFilter; - _lastMsgFilterNotAvailable = false; - } + public void ClearMsg() + { + _queueMsg.Clear(); + } + + private void DoMsgFilter() + { + _config.MsgUIItem.MainMsgFilter = MsgFilter; + _lastMsgFilterNotAvailable = false; } } diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 85b01144..040d12ed 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -2,417 +2,416 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class OptionSettingViewModel : MyReactiveObject { - public class OptionSettingViewModel : MyReactiveObject + #region Core + + [Reactive] public int localPort { get; set; } + [Reactive] public bool SecondLocalPortEnabled { get; set; } + [Reactive] public bool udpEnabled { get; set; } + [Reactive] public bool sniffingEnabled { get; set; } + public IList destOverride { get; set; } + [Reactive] public bool routeOnly { get; set; } + [Reactive] public bool allowLANConn { get; set; } + [Reactive] public bool newPort4LAN { get; set; } + [Reactive] public string user { get; set; } + [Reactive] public string pass { get; set; } + [Reactive] public bool muxEnabled { get; set; } + [Reactive] public bool logEnabled { get; set; } + [Reactive] public string loglevel { get; set; } + [Reactive] public bool defAllowInsecure { get; set; } + [Reactive] public string defFingerprint { get; set; } + [Reactive] public string defUserAgent { get; set; } + [Reactive] public string mux4SboxProtocol { get; set; } + [Reactive] public bool enableCacheFile4Sbox { get; set; } + [Reactive] public int hyUpMbps { get; set; } + [Reactive] public int hyDownMbps { get; set; } + [Reactive] public bool enableFragment { get; set; } + + #endregion Core + + #region Core KCP + + //[Reactive] public int Kcpmtu { get; set; } + //[Reactive] public int Kcptti { get; set; } + //[Reactive] public int KcpuplinkCapacity { get; set; } + //[Reactive] public int KcpdownlinkCapacity { get; set; } + //[Reactive] public int KcpreadBufferSize { get; set; } + //[Reactive] public int KcpwriteBufferSize { get; set; } + //[Reactive] public bool Kcpcongestion { get; set; } + + #endregion Core KCP + + #region UI + + [Reactive] public bool AutoRun { get; set; } + [Reactive] public bool EnableStatistics { get; set; } + [Reactive] public bool KeepOlderDedupl { get; set; } + [Reactive] public bool DisplayRealTimeSpeed { get; set; } + [Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; } + [Reactive] public bool EnableUpdateSubOnlyRemarksExist { get; set; } + [Reactive] public bool EnableSecurityProtocolTls13 { get; set; } + [Reactive] public bool AutoHideStartup { get; set; } + [Reactive] public bool Hide2TrayWhenClose { get; set; } + [Reactive] public bool EnableDragDropSort { get; set; } + [Reactive] public bool DoubleClick2Activate { get; set; } + [Reactive] public int AutoUpdateInterval { get; set; } + [Reactive] public int TrayMenuServersLimit { get; set; } + [Reactive] public string CurrentFontFamily { get; set; } + [Reactive] public int SpeedTestTimeout { get; set; } + [Reactive] public string SpeedTestUrl { get; set; } + [Reactive] public string SpeedPingTestUrl { get; set; } + [Reactive] public int MixedConcurrencyCount { get; set; } + [Reactive] public bool EnableHWA { get; set; } + [Reactive] public string SubConvertUrl { get; set; } + [Reactive] public int MainGirdOrientation { get; set; } + [Reactive] public string GeoFileSourceUrl { get; set; } + [Reactive] public string SrsFileSourceUrl { get; set; } + [Reactive] public string RoutingRulesSourceUrl { get; set; } + + #endregion UI + + #region System proxy + + [Reactive] public bool notProxyLocalAddress { get; set; } + [Reactive] public string systemProxyAdvancedProtocol { get; set; } + [Reactive] public string systemProxyExceptions { get; set; } + + #endregion System proxy + + #region Tun mode + + [Reactive] public bool TunStrictRoute { get; set; } + [Reactive] public string TunStack { get; set; } + [Reactive] public int TunMtu { get; set; } + [Reactive] public bool TunEnableExInbound { get; set; } + [Reactive] public bool TunEnableIPv6Address { get; set; } + [Reactive] public string TunLinuxSudoPassword { get; set; } + + #endregion Tun mode + + #region CoreType + + [Reactive] public string CoreType1 { get; set; } + [Reactive] public string CoreType2 { get; set; } + [Reactive] public string CoreType3 { get; set; } + [Reactive] public string CoreType4 { get; set; } + [Reactive] public string CoreType5 { get; set; } + [Reactive] public string CoreType6 { get; set; } + + #endregion CoreType + + public ReactiveCommand SaveCmd { get; } + + public OptionSettingViewModel(Func>? updateView) { + _config = AppHandler.Instance.Config; + _updateView = updateView; + + SaveCmd = ReactiveCommand.CreateFromTask(async () => + { + await SaveSettingAsync(); + }); + + _ = Init(); + } + + private async Task Init() + { + await _updateView?.Invoke(EViewAction.InitSettingFont, null); + #region Core - [Reactive] public int localPort { get; set; } - [Reactive] public bool SecondLocalPortEnabled { get; set; } - [Reactive] public bool udpEnabled { get; set; } - [Reactive] public bool sniffingEnabled { get; set; } - public IList destOverride { get; set; } - [Reactive] public bool routeOnly { get; set; } - [Reactive] public bool allowLANConn { get; set; } - [Reactive] public bool newPort4LAN { get; set; } - [Reactive] public string user { get; set; } - [Reactive] public string pass { get; set; } - [Reactive] public bool muxEnabled { get; set; } - [Reactive] public bool logEnabled { get; set; } - [Reactive] public string loglevel { get; set; } - [Reactive] public bool defAllowInsecure { get; set; } - [Reactive] public string defFingerprint { get; set; } - [Reactive] public string defUserAgent { get; set; } - [Reactive] public string mux4SboxProtocol { get; set; } - [Reactive] public bool enableCacheFile4Sbox { get; set; } - [Reactive] public int hyUpMbps { get; set; } - [Reactive] public int hyDownMbps { get; set; } - [Reactive] public bool enableFragment { get; set; } + var inbound = _config.Inbound.First(); + localPort = inbound.LocalPort; + SecondLocalPortEnabled = inbound.SecondLocalPortEnabled; + udpEnabled = inbound.UdpEnabled; + sniffingEnabled = inbound.SniffingEnabled; + routeOnly = inbound.RouteOnly; + allowLANConn = inbound.AllowLANConn; + newPort4LAN = inbound.NewPort4LAN; + user = inbound.User; + pass = inbound.Pass; + muxEnabled = _config.CoreBasicItem.MuxEnabled; + logEnabled = _config.CoreBasicItem.LogEnabled; + loglevel = _config.CoreBasicItem.Loglevel; + defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; + defFingerprint = _config.CoreBasicItem.DefFingerprint; + defUserAgent = _config.CoreBasicItem.DefUserAgent; + mux4SboxProtocol = _config.Mux4SboxItem.Protocol; + enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; + hyUpMbps = _config.HysteriaItem.UpMbps; + hyDownMbps = _config.HysteriaItem.DownMbps; + enableFragment = _config.CoreBasicItem.EnableFragment; #endregion Core #region Core KCP - //[Reactive] public int Kcpmtu { get; set; } - //[Reactive] public int Kcptti { get; set; } - //[Reactive] public int KcpuplinkCapacity { get; set; } - //[Reactive] public int KcpdownlinkCapacity { get; set; } - //[Reactive] public int KcpreadBufferSize { get; set; } - //[Reactive] public int KcpwriteBufferSize { get; set; } - //[Reactive] public bool Kcpcongestion { get; set; } + //Kcpmtu = _config.kcpItem.mtu; + //Kcptti = _config.kcpItem.tti; + //KcpuplinkCapacity = _config.kcpItem.uplinkCapacity; + //KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity; + //KcpreadBufferSize = _config.kcpItem.readBufferSize; + //KcpwriteBufferSize = _config.kcpItem.writeBufferSize; + //Kcpcongestion = _config.kcpItem.congestion; #endregion Core KCP #region UI - [Reactive] public bool AutoRun { get; set; } - [Reactive] public bool EnableStatistics { get; set; } - [Reactive] public bool KeepOlderDedupl { get; set; } - [Reactive] public bool DisplayRealTimeSpeed { get; set; } - [Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; } - [Reactive] public bool EnableUpdateSubOnlyRemarksExist { get; set; } - [Reactive] public bool EnableSecurityProtocolTls13 { get; set; } - [Reactive] public bool AutoHideStartup { get; set; } - [Reactive] public bool Hide2TrayWhenClose { get; set; } - [Reactive] public bool EnableDragDropSort { get; set; } - [Reactive] public bool DoubleClick2Activate { get; set; } - [Reactive] public int AutoUpdateInterval { get; set; } - [Reactive] public int TrayMenuServersLimit { get; set; } - [Reactive] public string CurrentFontFamily { get; set; } - [Reactive] public int SpeedTestTimeout { get; set; } - [Reactive] public string SpeedTestUrl { get; set; } - [Reactive] public string SpeedPingTestUrl { get; set; } - [Reactive] public int MixedConcurrencyCount { get; set; } - [Reactive] public bool EnableHWA { get; set; } - [Reactive] public string SubConvertUrl { get; set; } - [Reactive] public int MainGirdOrientation { get; set; } - [Reactive] public string GeoFileSourceUrl { get; set; } - [Reactive] public string SrsFileSourceUrl { get; set; } - [Reactive] public string RoutingRulesSourceUrl { get; set; } + AutoRun = _config.GuiItem.AutoRun; + EnableStatistics = _config.GuiItem.EnableStatistics; + DisplayRealTimeSpeed = _config.GuiItem.DisplayRealTimeSpeed; + KeepOlderDedupl = _config.GuiItem.KeepOlderDedupl; + EnableAutoAdjustMainLvColWidth = _config.UiItem.EnableAutoAdjustMainLvColWidth; + EnableUpdateSubOnlyRemarksExist = _config.UiItem.EnableUpdateSubOnlyRemarksExist; + EnableSecurityProtocolTls13 = _config.GuiItem.EnableSecurityProtocolTls13; + AutoHideStartup = _config.UiItem.AutoHideStartup; + Hide2TrayWhenClose = _config.UiItem.Hide2TrayWhenClose; + EnableDragDropSort = _config.UiItem.EnableDragDropSort; + DoubleClick2Activate = _config.UiItem.DoubleClick2Activate; + AutoUpdateInterval = _config.GuiItem.AutoUpdateInterval; + TrayMenuServersLimit = _config.GuiItem.TrayMenuServersLimit; + CurrentFontFamily = _config.UiItem.CurrentFontFamily; + SpeedTestTimeout = _config.SpeedTestItem.SpeedTestTimeout; + SpeedTestUrl = _config.SpeedTestItem.SpeedTestUrl; + MixedConcurrencyCount = _config.SpeedTestItem.MixedConcurrencyCount; + SpeedPingTestUrl = _config.SpeedTestItem.SpeedPingTestUrl; + EnableHWA = _config.GuiItem.EnableHWA; + SubConvertUrl = _config.ConstItem.SubConvertUrl; + MainGirdOrientation = (int)_config.UiItem.MainGirdOrientation; + GeoFileSourceUrl = _config.ConstItem.GeoSourceUrl; + SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl; + RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl; #endregion UI #region System proxy - [Reactive] public bool notProxyLocalAddress { get; set; } - [Reactive] public string systemProxyAdvancedProtocol { get; set; } - [Reactive] public string systemProxyExceptions { get; set; } + notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; + systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; + systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; #endregion System proxy #region Tun mode - [Reactive] public bool TunStrictRoute { get; set; } - [Reactive] public string TunStack { get; set; } - [Reactive] public int TunMtu { get; set; } - [Reactive] public bool TunEnableExInbound { get; set; } - [Reactive] public bool TunEnableIPv6Address { get; set; } - [Reactive] public string TunLinuxSudoPassword { get; set; } + TunStrictRoute = _config.TunModeItem.StrictRoute; + TunStack = _config.TunModeItem.Stack; + TunMtu = _config.TunModeItem.Mtu; + TunEnableExInbound = _config.TunModeItem.EnableExInbound; + TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; + TunLinuxSudoPassword = _config.TunModeItem.LinuxSudoPwd; #endregion Tun mode - #region CoreType + await InitCoreType(); + } - [Reactive] public string CoreType1 { get; set; } - [Reactive] public string CoreType2 { get; set; } - [Reactive] public string CoreType3 { get; set; } - [Reactive] public string CoreType4 { get; set; } - [Reactive] public string CoreType5 { get; set; } - [Reactive] public string CoreType6 { get; set; } - - #endregion CoreType - - public ReactiveCommand SaveCmd { get; } - - public OptionSettingViewModel(Func>? updateView) + private async Task InitCoreType() + { + if (_config.CoreTypeItem == null) { - _config = AppHandler.Instance.Config; - _updateView = updateView; + _config.CoreTypeItem = new List(); + } - SaveCmd = ReactiveCommand.CreateFromTask(async () => + foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) + { + if (_config.CoreTypeItem.FindIndex(t => t.ConfigType == it) >= 0) { - await SaveSettingAsync(); + continue; + } + + _config.CoreTypeItem.Add(new CoreTypeItem() + { + ConfigType = it, + CoreType = ECoreType.Xray }); + } + _config.CoreTypeItem.ForEach(it => + { + var type = it.CoreType.ToString(); + switch ((int)it.ConfigType) + { + case 1: + CoreType1 = type; + break; - _ = Init(); + case 2: + CoreType2 = type; + break; + + case 3: + CoreType3 = type; + break; + + case 4: + CoreType4 = type; + break; + + case 5: + CoreType5 = type; + break; + + case 6: + CoreType6 = type; + break; + } + }); + await Task.CompletedTask; + } + + private async Task SaveSettingAsync() + { + if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) + || localPort <= 0 || localPort >= Global.MaxPort) + { + NoticeHandler.Instance.Enqueue(ResUI.FillLocalListeningPort); + return; + } + var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics + || DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed + || EnableDragDropSort != _config.UiItem.EnableDragDropSort + || EnableHWA != _config.GuiItem.EnableHWA + || CurrentFontFamily != _config.UiItem.CurrentFontFamily + || MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation); + + //if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString()) + // || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString()) + // || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString()) + // || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString()) + // || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString()) + // || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString())) + //{ + // NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters); + // return; + //} + + //Core + _config.Inbound.First().LocalPort = localPort; + _config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled; + _config.Inbound.First().UdpEnabled = udpEnabled; + _config.Inbound.First().SniffingEnabled = sniffingEnabled; + _config.Inbound.First().DestOverride = destOverride?.ToList(); + _config.Inbound.First().RouteOnly = routeOnly; + _config.Inbound.First().AllowLANConn = allowLANConn; + _config.Inbound.First().NewPort4LAN = newPort4LAN; + _config.Inbound.First().User = user; + _config.Inbound.First().Pass = pass; + if (_config.Inbound.Count > 1) + { + _config.Inbound.RemoveAt(1); + } + _config.CoreBasicItem.LogEnabled = logEnabled; + _config.CoreBasicItem.Loglevel = loglevel; + _config.CoreBasicItem.MuxEnabled = muxEnabled; + _config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; + _config.CoreBasicItem.DefFingerprint = defFingerprint; + _config.CoreBasicItem.DefUserAgent = defUserAgent; + _config.Mux4SboxItem.Protocol = mux4SboxProtocol; + _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; + _config.HysteriaItem.UpMbps = hyUpMbps; + _config.HysteriaItem.DownMbps = hyDownMbps; + _config.CoreBasicItem.EnableFragment = enableFragment; + + _config.GuiItem.AutoRun = AutoRun; + _config.GuiItem.EnableStatistics = EnableStatistics; + _config.GuiItem.DisplayRealTimeSpeed = DisplayRealTimeSpeed; + _config.GuiItem.KeepOlderDedupl = KeepOlderDedupl; + _config.UiItem.EnableAutoAdjustMainLvColWidth = EnableAutoAdjustMainLvColWidth; + _config.UiItem.EnableUpdateSubOnlyRemarksExist = EnableUpdateSubOnlyRemarksExist; + _config.GuiItem.EnableSecurityProtocolTls13 = EnableSecurityProtocolTls13; + _config.UiItem.AutoHideStartup = AutoHideStartup; + _config.UiItem.Hide2TrayWhenClose = Hide2TrayWhenClose; + _config.GuiItem.AutoUpdateInterval = AutoUpdateInterval; + _config.UiItem.EnableDragDropSort = EnableDragDropSort; + _config.UiItem.DoubleClick2Activate = DoubleClick2Activate; + _config.GuiItem.TrayMenuServersLimit = TrayMenuServersLimit; + _config.UiItem.CurrentFontFamily = CurrentFontFamily; + _config.SpeedTestItem.SpeedTestTimeout = SpeedTestTimeout; + _config.SpeedTestItem.MixedConcurrencyCount = MixedConcurrencyCount; + _config.SpeedTestItem.SpeedTestUrl = SpeedTestUrl; + _config.SpeedTestItem.SpeedPingTestUrl = SpeedPingTestUrl; + _config.GuiItem.EnableHWA = EnableHWA; + _config.ConstItem.SubConvertUrl = SubConvertUrl; + _config.UiItem.MainGirdOrientation = (EGirdOrientation)MainGirdOrientation; + _config.ConstItem.GeoSourceUrl = GeoFileSourceUrl; + _config.ConstItem.SrsSourceUrl = SrsFileSourceUrl; + _config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl; + + //systemProxy + _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; + _config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; + _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; + + //tun mode + _config.TunModeItem.StrictRoute = TunStrictRoute; + _config.TunModeItem.Stack = TunStack; + _config.TunModeItem.Mtu = TunMtu; + _config.TunModeItem.EnableExInbound = TunEnableExInbound; + _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; + if (TunLinuxSudoPassword != _config.TunModeItem.LinuxSudoPwd) + { + _config.TunModeItem.LinuxSudoPwd = DesUtils.Encrypt(TunLinuxSudoPassword); } - private async Task Init() + //coreType + await SaveCoreType(); + + if (await ConfigHandler.SaveConfig(_config) == 0) { - await _updateView?.Invoke(EViewAction.InitSettingFont, null); + await AutoStartupHandler.UpdateTask(_config); + AppHandler.Instance.Reset(); - #region Core - - var inbound = _config.Inbound.First(); - localPort = inbound.LocalPort; - SecondLocalPortEnabled = inbound.SecondLocalPortEnabled; - udpEnabled = inbound.UdpEnabled; - sniffingEnabled = inbound.SniffingEnabled; - routeOnly = inbound.RouteOnly; - allowLANConn = inbound.AllowLANConn; - newPort4LAN = inbound.NewPort4LAN; - user = inbound.User; - pass = inbound.Pass; - muxEnabled = _config.CoreBasicItem.MuxEnabled; - logEnabled = _config.CoreBasicItem.LogEnabled; - loglevel = _config.CoreBasicItem.Loglevel; - defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; - defFingerprint = _config.CoreBasicItem.DefFingerprint; - defUserAgent = _config.CoreBasicItem.DefUserAgent; - mux4SboxProtocol = _config.Mux4SboxItem.Protocol; - enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; - hyUpMbps = _config.HysteriaItem.UpMbps; - hyDownMbps = _config.HysteriaItem.DownMbps; - enableFragment = _config.CoreBasicItem.EnableFragment; - - #endregion Core - - #region Core KCP - - //Kcpmtu = _config.kcpItem.mtu; - //Kcptti = _config.kcpItem.tti; - //KcpuplinkCapacity = _config.kcpItem.uplinkCapacity; - //KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity; - //KcpreadBufferSize = _config.kcpItem.readBufferSize; - //KcpwriteBufferSize = _config.kcpItem.writeBufferSize; - //Kcpcongestion = _config.kcpItem.congestion; - - #endregion Core KCP - - #region UI - - AutoRun = _config.GuiItem.AutoRun; - EnableStatistics = _config.GuiItem.EnableStatistics; - DisplayRealTimeSpeed = _config.GuiItem.DisplayRealTimeSpeed; - KeepOlderDedupl = _config.GuiItem.KeepOlderDedupl; - EnableAutoAdjustMainLvColWidth = _config.UiItem.EnableAutoAdjustMainLvColWidth; - EnableUpdateSubOnlyRemarksExist = _config.UiItem.EnableUpdateSubOnlyRemarksExist; - EnableSecurityProtocolTls13 = _config.GuiItem.EnableSecurityProtocolTls13; - AutoHideStartup = _config.UiItem.AutoHideStartup; - Hide2TrayWhenClose = _config.UiItem.Hide2TrayWhenClose; - EnableDragDropSort = _config.UiItem.EnableDragDropSort; - DoubleClick2Activate = _config.UiItem.DoubleClick2Activate; - AutoUpdateInterval = _config.GuiItem.AutoUpdateInterval; - TrayMenuServersLimit = _config.GuiItem.TrayMenuServersLimit; - CurrentFontFamily = _config.UiItem.CurrentFontFamily; - SpeedTestTimeout = _config.SpeedTestItem.SpeedTestTimeout; - SpeedTestUrl = _config.SpeedTestItem.SpeedTestUrl; - MixedConcurrencyCount = _config.SpeedTestItem.MixedConcurrencyCount; - SpeedPingTestUrl = _config.SpeedTestItem.SpeedPingTestUrl; - EnableHWA = _config.GuiItem.EnableHWA; - SubConvertUrl = _config.ConstItem.SubConvertUrl; - MainGirdOrientation = (int)_config.UiItem.MainGirdOrientation; - GeoFileSourceUrl = _config.ConstItem.GeoSourceUrl; - SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl; - RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl; - - #endregion UI - - #region System proxy - - notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; - systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; - systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; - - #endregion System proxy - - #region Tun mode - - TunStrictRoute = _config.TunModeItem.StrictRoute; - TunStack = _config.TunModeItem.Stack; - TunMtu = _config.TunModeItem.Mtu; - TunEnableExInbound = _config.TunModeItem.EnableExInbound; - TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; - TunLinuxSudoPassword = _config.TunModeItem.LinuxSudoPwd; - - #endregion Tun mode - - await InitCoreType(); + NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); } - - private async Task InitCoreType() + else { - if (_config.CoreTypeItem == null) - { - _config.CoreTypeItem = new List(); - } - - foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) - { - if (_config.CoreTypeItem.FindIndex(t => t.ConfigType == it) >= 0) - { - continue; - } - - _config.CoreTypeItem.Add(new CoreTypeItem() - { - ConfigType = it, - CoreType = ECoreType.Xray - }); - } - _config.CoreTypeItem.ForEach(it => - { - var type = it.CoreType.ToString(); - switch ((int)it.ConfigType) - { - case 1: - CoreType1 = type; - break; - - case 2: - CoreType2 = type; - break; - - case 3: - CoreType3 = type; - break; - - case 4: - CoreType4 = type; - break; - - case 5: - CoreType5 = type; - break; - - case 6: - CoreType6 = type; - break; - } - }); - await Task.CompletedTask; - } - - private async Task SaveSettingAsync() - { - if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) - || localPort <= 0 || localPort >= Global.MaxPort) - { - NoticeHandler.Instance.Enqueue(ResUI.FillLocalListeningPort); - return; - } - var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics - || DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed - || EnableDragDropSort != _config.UiItem.EnableDragDropSort - || EnableHWA != _config.GuiItem.EnableHWA - || CurrentFontFamily != _config.UiItem.CurrentFontFamily - || MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation); - - //if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString()) - // || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString()) - // || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString()) - // || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString()) - // || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString()) - // || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString())) - //{ - // NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters); - // return; - //} - - //Core - _config.Inbound.First().LocalPort = localPort; - _config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled; - _config.Inbound.First().UdpEnabled = udpEnabled; - _config.Inbound.First().SniffingEnabled = sniffingEnabled; - _config.Inbound.First().DestOverride = destOverride?.ToList(); - _config.Inbound.First().RouteOnly = routeOnly; - _config.Inbound.First().AllowLANConn = allowLANConn; - _config.Inbound.First().NewPort4LAN = newPort4LAN; - _config.Inbound.First().User = user; - _config.Inbound.First().Pass = pass; - if (_config.Inbound.Count > 1) - { - _config.Inbound.RemoveAt(1); - } - _config.CoreBasicItem.LogEnabled = logEnabled; - _config.CoreBasicItem.Loglevel = loglevel; - _config.CoreBasicItem.MuxEnabled = muxEnabled; - _config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; - _config.CoreBasicItem.DefFingerprint = defFingerprint; - _config.CoreBasicItem.DefUserAgent = defUserAgent; - _config.Mux4SboxItem.Protocol = mux4SboxProtocol; - _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; - _config.HysteriaItem.UpMbps = hyUpMbps; - _config.HysteriaItem.DownMbps = hyDownMbps; - _config.CoreBasicItem.EnableFragment = enableFragment; - - _config.GuiItem.AutoRun = AutoRun; - _config.GuiItem.EnableStatistics = EnableStatistics; - _config.GuiItem.DisplayRealTimeSpeed = DisplayRealTimeSpeed; - _config.GuiItem.KeepOlderDedupl = KeepOlderDedupl; - _config.UiItem.EnableAutoAdjustMainLvColWidth = EnableAutoAdjustMainLvColWidth; - _config.UiItem.EnableUpdateSubOnlyRemarksExist = EnableUpdateSubOnlyRemarksExist; - _config.GuiItem.EnableSecurityProtocolTls13 = EnableSecurityProtocolTls13; - _config.UiItem.AutoHideStartup = AutoHideStartup; - _config.UiItem.Hide2TrayWhenClose = Hide2TrayWhenClose; - _config.GuiItem.AutoUpdateInterval = AutoUpdateInterval; - _config.UiItem.EnableDragDropSort = EnableDragDropSort; - _config.UiItem.DoubleClick2Activate = DoubleClick2Activate; - _config.GuiItem.TrayMenuServersLimit = TrayMenuServersLimit; - _config.UiItem.CurrentFontFamily = CurrentFontFamily; - _config.SpeedTestItem.SpeedTestTimeout = SpeedTestTimeout; - _config.SpeedTestItem.MixedConcurrencyCount = MixedConcurrencyCount; - _config.SpeedTestItem.SpeedTestUrl = SpeedTestUrl; - _config.SpeedTestItem.SpeedPingTestUrl = SpeedPingTestUrl; - _config.GuiItem.EnableHWA = EnableHWA; - _config.ConstItem.SubConvertUrl = SubConvertUrl; - _config.UiItem.MainGirdOrientation = (EGirdOrientation)MainGirdOrientation; - _config.ConstItem.GeoSourceUrl = GeoFileSourceUrl; - _config.ConstItem.SrsSourceUrl = SrsFileSourceUrl; - _config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl; - - //systemProxy - _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; - _config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; - _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; - - //tun mode - _config.TunModeItem.StrictRoute = TunStrictRoute; - _config.TunModeItem.Stack = TunStack; - _config.TunModeItem.Mtu = TunMtu; - _config.TunModeItem.EnableExInbound = TunEnableExInbound; - _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; - if (TunLinuxSudoPassword != _config.TunModeItem.LinuxSudoPwd) - { - _config.TunModeItem.LinuxSudoPwd = DesUtils.Encrypt(TunLinuxSudoPassword); - } - - //coreType - await SaveCoreType(); - - if (await ConfigHandler.SaveConfig(_config) == 0) - { - await AutoStartupHandler.UpdateTask(_config); - AppHandler.Instance.Reset(); - - NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } - } - - private async Task SaveCoreType() - { - for (int k = 1; k <= _config.CoreTypeItem.Count; k++) - { - var item = _config.CoreTypeItem[k - 1]; - var type = string.Empty; - switch ((int)item.ConfigType) - { - case 1: - type = CoreType1; - break; - - case 2: - type = CoreType2; - break; - - case 3: - type = CoreType3; - break; - - case 4: - type = CoreType4; - break; - - case 5: - type = CoreType5; - break; - - case 6: - type = CoreType6; - break; - - default: - continue; - } - item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); - } - await Task.CompletedTask; + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } + + private async Task SaveCoreType() + { + for (int k = 1; k <= _config.CoreTypeItem.Count; k++) + { + var item = _config.CoreTypeItem[k - 1]; + var type = string.Empty; + switch ((int)item.ConfigType) + { + case 1: + type = CoreType1; + break; + + case 2: + type = CoreType2; + break; + + case 3: + type = CoreType3; + break; + + case 4: + type = CoreType4; + break; + + case 5: + type = CoreType5; + break; + + case 6: + type = CoreType6; + break; + + default: + continue; + } + item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); + } + await Task.CompletedTask; + } } diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 5420478f..a955a9aa 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -7,862 +7,861 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class ProfilesViewModel : MyReactiveObject { - public class ProfilesViewModel : MyReactiveObject + #region private prop + + private List _lstProfile; + private string _serverFilter = string.Empty; + private Dictionary _dicHeaderSort = new(); + private SpeedtestService? _speedtestService; + + #endregion private prop + + #region ObservableCollection + + private IObservableCollection _profileItems = new ObservableCollectionExtended(); + public IObservableCollection ProfileItems => _profileItems; + + private IObservableCollection _subItems = new ObservableCollectionExtended(); + public IObservableCollection SubItems => _subItems; + + private IObservableCollection _servers = new ObservableCollectionExtended(); + + [Reactive] + public ProfileItemModel SelectedProfile { get; set; } + + public IList SelectedProfiles { get; set; } + + [Reactive] + public SubItem SelectedSub { get; set; } + + [Reactive] + public SubItem SelectedMoveToGroup { get; set; } + + [Reactive] + public ComboItem SelectedServer { get; set; } + + [Reactive] + public string ServerFilter { get; set; } + + [Reactive] + public bool BlServers { get; set; } + + #endregion ObservableCollection + + #region Menu + + //servers delete + public ReactiveCommand EditServerCmd { get; } + + public ReactiveCommand RemoveServerCmd { get; } + public ReactiveCommand RemoveDuplicateServerCmd { get; } + public ReactiveCommand CopyServerCmd { get; } + public ReactiveCommand SetDefaultServerCmd { get; } + public ReactiveCommand ShareServerCmd { get; } + public ReactiveCommand SetDefaultMultipleServerXrayRandomCmd { get; } + public ReactiveCommand SetDefaultMultipleServerXrayRoundRobinCmd { get; } + public ReactiveCommand SetDefaultMultipleServerXrayLeastPingCmd { get; } + public ReactiveCommand SetDefaultMultipleServerXrayLeastLoadCmd { get; } + public ReactiveCommand SetDefaultMultipleServerSingBoxLeastPingCmd { get; } + + //servers move + public ReactiveCommand MoveTopCmd { get; } + + public ReactiveCommand MoveUpCmd { get; } + public ReactiveCommand MoveDownCmd { get; } + public ReactiveCommand MoveBottomCmd { get; } + + //servers ping + public ReactiveCommand MixedTestServerCmd { get; } + + public ReactiveCommand TcpingServerCmd { get; } + public ReactiveCommand RealPingServerCmd { get; } + public ReactiveCommand SpeedServerCmd { get; } + public ReactiveCommand SortServerResultCmd { get; } + public ReactiveCommand RemoveInvalidServerResultCmd { get; } + + //servers export + public ReactiveCommand Export2ClientConfigCmd { get; } + + public ReactiveCommand Export2ClientConfigClipboardCmd { get; } + public ReactiveCommand Export2ShareUrlCmd { get; } + public ReactiveCommand Export2ShareUrlBase64Cmd { get; } + + public ReactiveCommand AddSubCmd { get; } + public ReactiveCommand EditSubCmd { get; } + + #endregion Menu + + #region Init + + public ProfilesViewModel(Func>? updateView) { - #region private prop + _config = AppHandler.Instance.Config; + _updateView = updateView; - private List _lstProfile; - private string _serverFilter = string.Empty; - private Dictionary _dicHeaderSort = new(); - private SpeedtestService? _speedtestService; + #region WhenAnyValue && ReactiveCommand - #endregion private prop + var canEditRemove = this.WhenAnyValue( + x => x.SelectedProfile, + selectedSource => selectedSource != null && !selectedSource.IndexId.IsNullOrEmpty()); - #region ObservableCollection + this.WhenAnyValue( + x => x.SelectedSub, + y => y != null && !y.Remarks.IsNullOrEmpty() && _config.SubIndexId != y.Id) + .Subscribe(async c => await SubSelectedChangedAsync(c)); + this.WhenAnyValue( + x => x.SelectedMoveToGroup, + y => y != null && !y.Remarks.IsNullOrEmpty()) + .Subscribe(async c => await MoveToGroup(c)); - private IObservableCollection _profileItems = new ObservableCollectionExtended(); - public IObservableCollection ProfileItems => _profileItems; + this.WhenAnyValue( + x => x.SelectedServer, + y => y != null && !y.Text.IsNullOrEmpty()) + .Subscribe(async c => await ServerSelectedChanged(c)); - private IObservableCollection _subItems = new ObservableCollectionExtended(); - public IObservableCollection SubItems => _subItems; - - private IObservableCollection _servers = new ObservableCollectionExtended(); - - [Reactive] - public ProfileItemModel SelectedProfile { get; set; } - - public IList SelectedProfiles { get; set; } - - [Reactive] - public SubItem SelectedSub { get; set; } - - [Reactive] - public SubItem SelectedMoveToGroup { get; set; } - - [Reactive] - public ComboItem SelectedServer { get; set; } - - [Reactive] - public string ServerFilter { get; set; } - - [Reactive] - public bool BlServers { get; set; } - - #endregion ObservableCollection - - #region Menu + this.WhenAnyValue( + x => x.ServerFilter, + y => y != null && _serverFilter != y) + .Subscribe(c => ServerFilterChanged(c)); //servers delete - public ReactiveCommand EditServerCmd { get; } - - public ReactiveCommand RemoveServerCmd { get; } - public ReactiveCommand RemoveDuplicateServerCmd { get; } - public ReactiveCommand CopyServerCmd { get; } - public ReactiveCommand SetDefaultServerCmd { get; } - public ReactiveCommand ShareServerCmd { get; } - public ReactiveCommand SetDefaultMultipleServerXrayRandomCmd { get; } - public ReactiveCommand SetDefaultMultipleServerXrayRoundRobinCmd { get; } - public ReactiveCommand SetDefaultMultipleServerXrayLeastPingCmd { get; } - public ReactiveCommand SetDefaultMultipleServerXrayLeastLoadCmd { get; } - public ReactiveCommand SetDefaultMultipleServerSingBoxLeastPingCmd { get; } + EditServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await EditServerAsync(EConfigType.Custom); + }, canEditRemove); + RemoveServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await RemoveServerAsync(); + }, canEditRemove); + RemoveDuplicateServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await RemoveDuplicateServer(); + }); + CopyServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await CopyServer(); + }, canEditRemove); + SetDefaultServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultServer(); + }, canEditRemove); + ShareServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await ShareServerAsync(); + }, canEditRemove); + SetDefaultMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.Random); + }, canEditRemove); + SetDefaultMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin); + }, canEditRemove); + SetDefaultMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing); + }, canEditRemove); + SetDefaultMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); + }, canEditRemove); + SetDefaultMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetDefaultMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); + }, canEditRemove); //servers move - public ReactiveCommand MoveTopCmd { get; } - - public ReactiveCommand MoveUpCmd { get; } - public ReactiveCommand MoveDownCmd { get; } - public ReactiveCommand MoveBottomCmd { get; } + MoveTopCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveServer(EMove.Top); + }, canEditRemove); + MoveUpCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveServer(EMove.Up); + }, canEditRemove); + MoveDownCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveServer(EMove.Down); + }, canEditRemove); + MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveServer(EMove.Bottom); + }, canEditRemove); //servers ping - public ReactiveCommand MixedTestServerCmd { get; } - - public ReactiveCommand TcpingServerCmd { get; } - public ReactiveCommand RealPingServerCmd { get; } - public ReactiveCommand SpeedServerCmd { get; } - public ReactiveCommand SortServerResultCmd { get; } - public ReactiveCommand RemoveInvalidServerResultCmd { get; } - + MixedTestServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await ServerSpeedtest(ESpeedActionType.Mixedtest); + }); + TcpingServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await ServerSpeedtest(ESpeedActionType.Tcping); + }, canEditRemove); + RealPingServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await ServerSpeedtest(ESpeedActionType.Realping); + }, canEditRemove); + SpeedServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await ServerSpeedtest(ESpeedActionType.Speedtest); + }, canEditRemove); + SortServerResultCmd = ReactiveCommand.CreateFromTask(async () => + { + await SortServer(EServerColName.DelayVal.ToString()); + }); + RemoveInvalidServerResultCmd = ReactiveCommand.CreateFromTask(async () => + { + await RemoveInvalidServerResult(); + }); //servers export - public ReactiveCommand Export2ClientConfigCmd { get; } - - public ReactiveCommand Export2ClientConfigClipboardCmd { get; } - public ReactiveCommand Export2ShareUrlCmd { get; } - public ReactiveCommand Export2ShareUrlBase64Cmd { get; } - - public ReactiveCommand AddSubCmd { get; } - public ReactiveCommand EditSubCmd { get; } - - #endregion Menu - - #region Init - - public ProfilesViewModel(Func>? updateView) + Export2ClientConfigCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await Export2ClientConfigAsync(false); + }, canEditRemove); + Export2ClientConfigClipboardCmd = ReactiveCommand.CreateFromTask(async () => + { + await Export2ClientConfigAsync(true); + }, canEditRemove); + Export2ShareUrlCmd = ReactiveCommand.CreateFromTask(async () => + { + await Export2ShareUrlAsync(false); + }, canEditRemove); + Export2ShareUrlBase64Cmd = ReactiveCommand.CreateFromTask(async () => + { + await Export2ShareUrlAsync(true); + }, canEditRemove); - #region WhenAnyValue && ReactiveCommand + //Subscription + AddSubCmd = ReactiveCommand.CreateFromTask(async () => + { + await EditSubAsync(true); + }); + EditSubCmd = ReactiveCommand.CreateFromTask(async () => + { + await EditSubAsync(false); + }); - var canEditRemove = this.WhenAnyValue( - x => x.SelectedProfile, - selectedSource => selectedSource != null && !selectedSource.IndexId.IsNullOrEmpty()); + #endregion WhenAnyValue && ReactiveCommand - this.WhenAnyValue( - x => x.SelectedSub, - y => y != null && !y.Remarks.IsNullOrEmpty() && _config.SubIndexId != y.Id) - .Subscribe(async c => await SubSelectedChangedAsync(c)); - this.WhenAnyValue( - x => x.SelectedMoveToGroup, - y => y != null && !y.Remarks.IsNullOrEmpty()) - .Subscribe(async c => await MoveToGroup(c)); - - this.WhenAnyValue( - x => x.SelectedServer, - y => y != null && !y.Text.IsNullOrEmpty()) - .Subscribe(async c => await ServerSelectedChanged(c)); - - this.WhenAnyValue( - x => x.ServerFilter, - y => y != null && _serverFilter != y) - .Subscribe(c => ServerFilterChanged(c)); - - //servers delete - EditServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditServerAsync(EConfigType.Custom); - }, canEditRemove); - RemoveServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await RemoveServerAsync(); - }, canEditRemove); - RemoveDuplicateServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await RemoveDuplicateServer(); - }); - CopyServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await CopyServer(); - }, canEditRemove); - SetDefaultServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultServer(); - }, canEditRemove); - ShareServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await ShareServerAsync(); - }, canEditRemove); - SetDefaultMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.Random); - }, canEditRemove); - SetDefaultMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin); - }, canEditRemove); - SetDefaultMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing); - }, canEditRemove); - SetDefaultMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); - }, canEditRemove); - SetDefaultMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetDefaultMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); - }, canEditRemove); - - //servers move - MoveTopCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveServer(EMove.Top); - }, canEditRemove); - MoveUpCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveServer(EMove.Up); - }, canEditRemove); - MoveDownCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveServer(EMove.Down); - }, canEditRemove); - MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveServer(EMove.Bottom); - }, canEditRemove); - - //servers ping - MixedTestServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await ServerSpeedtest(ESpeedActionType.Mixedtest); - }); - TcpingServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await ServerSpeedtest(ESpeedActionType.Tcping); - }, canEditRemove); - RealPingServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await ServerSpeedtest(ESpeedActionType.Realping); - }, canEditRemove); - SpeedServerCmd = ReactiveCommand.CreateFromTask(async () => - { - await ServerSpeedtest(ESpeedActionType.Speedtest); - }, canEditRemove); - SortServerResultCmd = ReactiveCommand.CreateFromTask(async () => - { - await SortServer(EServerColName.DelayVal.ToString()); - }); - RemoveInvalidServerResultCmd = ReactiveCommand.CreateFromTask(async () => - { - await RemoveInvalidServerResult(); - }); - //servers export - Export2ClientConfigCmd = ReactiveCommand.CreateFromTask(async () => - { - await Export2ClientConfigAsync(false); - }, canEditRemove); - Export2ClientConfigClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await Export2ClientConfigAsync(true); - }, canEditRemove); - Export2ShareUrlCmd = ReactiveCommand.CreateFromTask(async () => - { - await Export2ShareUrlAsync(false); - }, canEditRemove); - Export2ShareUrlBase64Cmd = ReactiveCommand.CreateFromTask(async () => - { - await Export2ShareUrlAsync(true); - }, canEditRemove); - - //Subscription - AddSubCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditSubAsync(true); - }); - EditSubCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditSubAsync(false); - }); - - #endregion WhenAnyValue && ReactiveCommand - - if (_updateView != null) - { - MessageBus.Current.Listen(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext); - } - - _ = Init(); + if (_updateView != null) + { + MessageBus.Current.Listen(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext); } - private async Task Init() - { - SelectedProfile = new(); - SelectedSub = new(); - SelectedMoveToGroup = new(); - SelectedServer = new(); + _ = Init(); + } - await RefreshSubscriptions(); - RefreshServers(); + private async Task Init() + { + SelectedProfile = new(); + SelectedSub = new(); + SelectedMoveToGroup = new(); + SelectedServer = new(); + + await RefreshSubscriptions(); + RefreshServers(); + } + + #endregion Init + + #region Actions + + private async void OnNext(string x) + { + await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); + } + + private void Reload() + { + Locator.Current.GetService()?.Reload(); + } + + public void SetSpeedTestResult(SpeedTestResult result) + { + if (result.IndexId.IsNullOrEmpty()) + { + NoticeHandler.Instance.SendMessageEx(result.Delay); + NoticeHandler.Instance.Enqueue(result.Delay); + return; + } + var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId); + if (item == null) + { + return; } - #endregion Init - - #region Actions - - private async void OnNext(string x) + if (result.Delay.IsNotEmpty()) { - await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); + int.TryParse(result.Delay, out var temp); + item.Delay = temp; + item.DelayVal = result.Delay ?? string.Empty; } - - private void Reload() + if (result.Speed.IsNotEmpty()) { - Locator.Current.GetService()?.Reload(); + item.SpeedVal = result.Speed ?? string.Empty; } + _profileItems.Replace(item, JsonUtils.DeepCopy(item)); + } - public void SetSpeedTestResult(SpeedTestResult result) + public void UpdateStatistics(ServerSpeedItem update) + { + try { - if (result.IndexId.IsNullOrEmpty()) + var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId); + if (item != null) { - NoticeHandler.Instance.SendMessageEx(result.Delay); - NoticeHandler.Instance.Enqueue(result.Delay); - return; - } - var item = _profileItems.FirstOrDefault(it => it.IndexId == result.IndexId); - if (item == null) - { - return; - } + item.TodayDown = Utils.HumanFy(update.TodayDown); + item.TodayUp = Utils.HumanFy(update.TodayUp); + item.TotalDown = Utils.HumanFy(update.TotalDown); + item.TotalUp = Utils.HumanFy(update.TotalUp); - if (result.Delay.IsNotEmpty()) - { - int.TryParse(result.Delay, out var temp); - item.Delay = temp; - item.DelayVal = result.Delay ?? string.Empty; - } - if (result.Speed.IsNotEmpty()) - { - item.SpeedVal = result.Speed ?? string.Empty; - } - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); - } - - public void UpdateStatistics(ServerSpeedItem update) - { - try - { - var item = _profileItems.FirstOrDefault(it => it.IndexId == update.IndexId); - if (item != null) + if (SelectedProfile?.IndexId == item.IndexId) { - item.TodayDown = Utils.HumanFy(update.TodayDown); - item.TodayUp = Utils.HumanFy(update.TodayUp); - item.TotalDown = Utils.HumanFy(update.TotalDown); - item.TotalUp = Utils.HumanFy(update.TotalUp); - - if (SelectedProfile?.IndexId == item.IndexId) - { - var temp = JsonUtils.DeepCopy(item); - _profileItems.Replace(item, temp); - SelectedProfile = temp; - } - else - { - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); - } + var temp = JsonUtils.DeepCopy(item); + _profileItems.Replace(item, temp); + SelectedProfile = temp; + } + else + { + _profileItems.Replace(item, JsonUtils.DeepCopy(item)); } } - catch - { - } } - - public async Task AutofitColumnWidthAsync() + catch { - await _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null); } + } - #endregion Actions + public async Task AutofitColumnWidthAsync() + { + await _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null); + } - #region Servers && Groups + #endregion Actions - private async Task SubSelectedChangedAsync(bool c) + #region Servers && Groups + + private async Task SubSelectedChangedAsync(bool c) + { + if (!c) { - if (!c) - { - return; - } - _config.SubIndexId = SelectedSub?.Id; + return; + } + _config.SubIndexId = SelectedSub?.Id; + RefreshServers(); + + await _updateView?.Invoke(EViewAction.ProfilesFocus, null); + } + + private void ServerFilterChanged(bool c) + { + if (!c) + { + return; + } + _serverFilter = ServerFilter; + if (_serverFilter.IsNullOrEmpty()) + { RefreshServers(); - - await _updateView?.Invoke(EViewAction.ProfilesFocus, null); } + } - private void ServerFilterChanged(bool c) + public void RefreshServers() + { + MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString()); + } + + public async Task RefreshServersBiz() + { + var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); + _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)) ?? []; + + _profileItems.Clear(); + _profileItems.AddRange(lstModel); + if (lstModel.Count > 0) { - if (!c) + var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); + if (selected != null) { - return; + SelectedProfile = selected; } - _serverFilter = ServerFilter; - if (_serverFilter.IsNullOrEmpty()) + else { - RefreshServers(); + SelectedProfile = lstModel.First(); } } + } - public void RefreshServers() + public async Task RefreshSubscriptions() + { + _subItems.Clear(); + + _subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); + + foreach (var item in await AppHandler.Instance.SubItems()) { - MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString()); + _subItems.Add(item); + } + if (_config.SubIndexId != null && _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null) + { + SelectedSub = _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId); + } + else + { + SelectedSub = _subItems.First(); + } + } + + private async Task?> GetProfileItemsEx(string subid, string filter) + { + var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, filter); + + await ConfigHandler.SetDefaultServer(_config, lstModel); + + var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? []; + var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); + lstModel = (from t in lstModel + join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b + from t22 in t2b.DefaultIfEmpty() + join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b + from t33 in t3b.DefaultIfEmpty() + select new ProfileItemModel + { + IndexId = t.IndexId, + ConfigType = t.ConfigType, + Remarks = t.Remarks, + Address = t.Address, + Port = t.Port, + Security = t.Security, + Network = t.Network, + StreamSecurity = t.StreamSecurity, + Subid = t.Subid, + SubRemarks = t.SubRemarks, + IsActive = t.IndexId == _config.IndexId, + Sort = t33?.Sort ?? 0, + Delay = t33?.Delay ?? 0, + Speed = t33?.Speed ?? 0, + DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty, + SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty, + TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown), + TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp), + TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown), + TotalUp = t22 == null ? "" : Utils.HumanFy(t22.TotalUp) + }).OrderBy(t => t.Sort).ToList(); + + return lstModel; + } + + #endregion Servers && Groups + + #region Add Servers + + private async Task?> GetProfileItems(bool latest) + { + var lstSelected = new List(); + if (SelectedProfiles == null || SelectedProfiles.Count <= 0) + { + return null; } - public async Task RefreshServersBiz() + var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort); + if (latest) { - var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); - _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)) ?? []; + foreach (var profile in orderProfiles) + { + var item = await AppHandler.Instance.GetProfileItem(profile.IndexId); + if (item is not null) + { + lstSelected.Add(item); + } + } + } + else + { + lstSelected = JsonUtils.Deserialize>(JsonUtils.Serialize(orderProfiles)); + } + return lstSelected; + } + + public async Task EditServerAsync(EConfigType eConfigType) + { + if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) + { + return; + } + var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); + if (item is null) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); + return; + } + eConfigType = item.ConfigType; + + bool? ret = false; + if (eConfigType == EConfigType.Custom) + { + ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); + } + else + { + ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); + } + if (ret == true) + { + RefreshServers(); + if (item.IndexId == _config.IndexId) + { + Reload(); + } + } + } + + public async Task RemoveServerAsync() + { + var lstSelected = await GetProfileItems(true); + if (lstSelected == null) + { + return; + } + if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + { + return; + } + var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId); + + await ConfigHandler.RemoveServers(_config, lstSelected); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + if (lstSelected.Count == _profileItems.Count) + { _profileItems.Clear(); - _profileItems.AddRange(lstModel); - if (lstModel.Count > 0) - { - var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); - if (selected != null) - { - SelectedProfile = selected; - } - else - { - SelectedProfile = lstModel.First(); - } - } } - - public async Task RefreshSubscriptions() + RefreshServers(); + if (exists) { - _subItems.Clear(); - - _subItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); - - foreach (var item in await AppHandler.Instance.SubItems()) - { - _subItems.Add(item); - } - if (_config.SubIndexId != null && _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null) - { - SelectedSub = _subItems.FirstOrDefault(t => t.Id == _config.SubIndexId); - } - else - { - SelectedSub = _subItems.First(); - } + Reload(); } + } - private async Task?> GetProfileItemsEx(string subid, string filter) + private async Task RemoveDuplicateServer() + { + var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId); + if (tuple.Item1 > 0 || tuple.Item2 > 0) { - var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, filter); - - await ConfigHandler.SetDefaultServer(_config, lstModel); - - var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? []; - var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); - lstModel = (from t in lstModel - join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b - from t22 in t2b.DefaultIfEmpty() - join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b - from t33 in t3b.DefaultIfEmpty() - select new ProfileItemModel - { - IndexId = t.IndexId, - ConfigType = t.ConfigType, - Remarks = t.Remarks, - Address = t.Address, - Port = t.Port, - Security = t.Security, - Network = t.Network, - StreamSecurity = t.StreamSecurity, - Subid = t.Subid, - SubRemarks = t.SubRemarks, - IsActive = t.IndexId == _config.IndexId, - Sort = t33?.Sort ?? 0, - Delay = t33?.Delay ?? 0, - Speed = t33?.Speed ?? 0, - DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty, - SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty, - TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown), - TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp), - TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown), - TotalUp = t22 == null ? "" : Utils.HumanFy(t22.TotalUp) - }).OrderBy(t => t.Sort).ToList(); - - return lstModel; + RefreshServers(); + Reload(); } + NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); + } - #endregion Servers && Groups - - #region Add Servers - - private async Task?> GetProfileItems(bool latest) + private async Task CopyServer() + { + var lstSelected = await GetProfileItems(false); + if (lstSelected == null) { - var lstSelected = new List(); - if (SelectedProfiles == null || SelectedProfiles.Count <= 0) - { - return null; - } - - var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort); - if (latest) - { - foreach (var profile in orderProfiles) - { - var item = await AppHandler.Instance.GetProfileItem(profile.IndexId); - if (item is not null) - { - lstSelected.Add(item); - } - } - } - else - { - lstSelected = JsonUtils.Deserialize>(JsonUtils.Serialize(orderProfiles)); - } - - return lstSelected; + return; } - - public async Task EditServerAsync(EConfigType eConfigType) + if (await ConfigHandler.CopyServer(_config, lstSelected) == 0) { - if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) - { - return; - } - var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); - if (item is null) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); - return; - } - eConfigType = item.ConfigType; - - bool? ret = false; - if (eConfigType == EConfigType.Custom) - { - ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); - } - else - { - ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); - } - if (ret == true) - { - RefreshServers(); - if (item.IndexId == _config.IndexId) - { - Reload(); - } - } - } - - public async Task RemoveServerAsync() - { - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } - if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) - { - return; - } - var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId); - - await ConfigHandler.RemoveServers(_config, lstSelected); + RefreshServers(); NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - if (lstSelected.Count == _profileItems.Count) - { - _profileItems.Clear(); - } + } + } + + public async Task SetDefaultServer() + { + if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) + { + return; + } + await SetDefaultServer(SelectedProfile.IndexId); + } + + public async Task SetDefaultServer(string? indexId) + { + if (indexId.IsNullOrEmpty()) + { + return; + } + if (indexId == _config.IndexId) + { + return; + } + var item = await AppHandler.Instance.GetProfileItem(indexId); + if (item is null) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); + return; + } + + if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0) + { RefreshServers(); - if (exists) - { - Reload(); - } + Reload(); + } + } + + private async Task ServerSelectedChanged(bool c) + { + if (!c) + { + return; + } + if (SelectedServer == null || SelectedServer.ID.IsNullOrEmpty()) + { + return; + } + await SetDefaultServer(SelectedServer.ID); + } + + public async Task ShareServerAsync() + { + var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); + if (item is null) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); + return; + } + var url = FmtHandler.GetShareUri(item); + if (url.IsNullOrEmpty()) + { + return; } - private async Task RemoveDuplicateServer() + await _updateView?.Invoke(EViewAction.ShareServer, url); + } + + private async Task SetDefaultMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad) + { + var lstSelected = await GetProfileItems(true); + if (lstSelected == null) { - var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId); - if (tuple.Item1 > 0 || tuple.Item2 > 0) - { - RefreshServers(); - Reload(); - } - NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); + return; } - private async Task CopyServer() + var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad); + if (ret.Success != true) { - var lstSelected = await GetProfileItems(false); - if (lstSelected == null) - { - return; - } - if (await ConfigHandler.CopyServer(_config, lstSelected) == 0) - { - RefreshServers(); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); + return; + } + if (ret?.Data?.ToString() == _config.IndexId) + { + RefreshServers(); + Reload(); + } + else + { + await SetDefaultServer(ret?.Data?.ToString()); + } + } + + public async Task SortServer(string colName) + { + if (colName.IsNullOrEmpty()) + { + return; } - public async Task SetDefaultServer() + _dicHeaderSort.TryAdd(colName, true); + _dicHeaderSort.TryGetValue(colName, out bool asc); + if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0) { - if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) - { - return; - } - await SetDefaultServer(SelectedProfile.IndexId); + return; + } + _dicHeaderSort[colName] = !asc; + RefreshServers(); + } + + public async Task RemoveInvalidServerResult() + { + var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId); + RefreshServers(); + NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count)); + } + + //move server + private async Task MoveToGroup(bool c) + { + if (!c) + { + return; } - public async Task SetDefaultServer(string? indexId) + var lstSelected = await GetProfileItems(true); + if (lstSelected == null) { - if (indexId.IsNullOrEmpty()) - { - return; - } - if (indexId == _config.IndexId) - { - return; - } - var item = await AppHandler.Instance.GetProfileItem(indexId); - if (item is null) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); - return; - } - - if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0) - { - RefreshServers(); - Reload(); - } + return; } - private async Task ServerSelectedChanged(bool c) + await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + + RefreshServers(); + SelectedMoveToGroup = null; + SelectedMoveToGroup = new(); + } + + public async Task MoveServer(EMove eMove) + { + var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId); + if (item is null) { - if (!c) - { - return; - } - if (SelectedServer == null || SelectedServer.ID.IsNullOrEmpty()) - { - return; - } - await SetDefaultServer(SelectedServer.ID); + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); + return; } - public async Task ShareServerAsync() + var index = _lstProfile.IndexOf(item); + if (index < 0) { - var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); - if (item is null) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); - return; - } - var url = FmtHandler.GetShareUri(item); - if (url.IsNullOrEmpty()) - { - return; - } - - await _updateView?.Invoke(EViewAction.ShareServer, url); + return; } - - private async Task SetDefaultMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad) + if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0) { - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } - - var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad); - if (ret.Success != true) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - return; - } - if (ret?.Data?.ToString() == _config.IndexId) - { - RefreshServers(); - Reload(); - } - else - { - await SetDefaultServer(ret?.Data?.ToString()); - } - } - - public async Task SortServer(string colName) - { - if (colName.IsNullOrEmpty()) - { - return; - } - - _dicHeaderSort.TryAdd(colName, true); - _dicHeaderSort.TryGetValue(colName, out bool asc); - if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0) - { - return; - } - _dicHeaderSort[colName] = !asc; RefreshServers(); } + } - public async Task RemoveInvalidServerResult() + public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem) + { + var targetIndex = _profileItems.IndexOf(targetItem); + if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) { - var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId); - RefreshServers(); - NoticeHandler.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count)); - } - - //move server - private async Task MoveToGroup(bool c) - { - if (!c) - { - return; - } - - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } - - await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - - RefreshServers(); - SelectedMoveToGroup = null; - SelectedMoveToGroup = new(); - } - - public async Task MoveServer(EMove eMove) - { - var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId); - if (item is null) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); - return; - } - - var index = _lstProfile.IndexOf(item); - if (index < 0) - { - return; - } - if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0) + if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0) { RefreshServers(); } } + } - public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem) + public async Task ServerSpeedtest(ESpeedActionType actionType) + { + if (actionType == ESpeedActionType.Mixedtest) { - var targetIndex = _profileItems.IndexOf(targetItem); - if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) - { - if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0) - { - RefreshServers(); - } - } + SelectedProfiles = _profileItems; + } + var lstSelected = await GetProfileItems(false); + if (lstSelected == null) + { + return; } - public async Task ServerSpeedtest(ESpeedActionType actionType) - { - if (actionType == ESpeedActionType.Mixedtest) - { - SelectedProfiles = _profileItems; - } - var lstSelected = await GetProfileItems(false); - if (lstSelected == null) - { - return; - } + _speedtestService ??= new SpeedtestService(_config, (SpeedTestResult result) => _updateView?.Invoke(EViewAction.DispatcherSpeedTest, result)); + _speedtestService?.RunLoop(actionType, lstSelected); + } - _speedtestService ??= new SpeedtestService(_config, (SpeedTestResult result) => _updateView?.Invoke(EViewAction.DispatcherSpeedTest, result)); - _speedtestService?.RunLoop(actionType, lstSelected); + public void ServerSpeedtestStop() + { + _speedtestService?.ExitLoop(); + } + + private async Task Export2ClientConfigAsync(bool blClipboard) + { + var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); + if (item is null) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); + return; } - - public void ServerSpeedtestStop() + if (blClipboard) { - _speedtestService?.ExitLoop(); - } - - private async Task Export2ClientConfigAsync(bool blClipboard) - { - var item = await AppHandler.Instance.GetProfileItem(SelectedProfile.IndexId); - if (item is null) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectServer); - return; - } - if (blClipboard) - { - var result = await CoreConfigHandler.GenerateClientConfig(item, null); - if (result.Success != true) - { - NoticeHandler.Instance.Enqueue(result.Msg); - } - else - { - await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data); - NoticeHandler.Instance.SendMessage(ResUI.OperationSuccess); - } - } - else - { - await _updateView?.Invoke(EViewAction.SaveFileDialog, item); - } - } - - public async Task Export2ClientConfigResult(string fileName, ProfileItem item) - { - if (fileName.IsNullOrEmpty()) - { - return; - } - var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + var result = await CoreConfigHandler.GenerateClientConfig(item, null); if (result.Success != true) { NoticeHandler.Instance.Enqueue(result.Msg); } else { - NoticeHandler.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName)); + await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data); + NoticeHandler.Instance.SendMessage(ResUI.OperationSuccess); } } - - public async Task Export2ShareUrlAsync(bool blEncode) + else { - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } + await _updateView?.Invoke(EViewAction.SaveFileDialog, item); + } + } - StringBuilder sb = new(); - foreach (var it in lstSelected) - { - var url = FmtHandler.GetShareUri(it); - if (url.IsNullOrEmpty()) - { - continue; - } - sb.Append(url); - sb.AppendLine(); - } - if (sb.Length > 0) - { - if (blEncode) - { - await _updateView?.Invoke(EViewAction.SetClipboardData, Utils.Base64Encode(sb.ToString())); - } - else - { - await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); - } - NoticeHandler.Instance.SendMessage(ResUI.BatchExportURLSuccessfully); - } + public async Task Export2ClientConfigResult(string fileName, ProfileItem item) + { + if (fileName.IsNullOrEmpty()) + { + return; + } + var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + if (result.Success != true) + { + NoticeHandler.Instance.Enqueue(result.Msg); + } + else + { + NoticeHandler.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName)); + } + } + + public async Task Export2ShareUrlAsync(bool blEncode) + { + var lstSelected = await GetProfileItems(true); + if (lstSelected == null) + { + return; } - #endregion Add Servers - - #region Subscription - - private async Task EditSubAsync(bool blNew) + StringBuilder sb = new(); + foreach (var it in lstSelected) { - SubItem item; - if (blNew) + var url = FmtHandler.GetShareUri(it); + if (url.IsNullOrEmpty()) { - item = new(); + continue; + } + sb.Append(url); + sb.AppendLine(); + } + if (sb.Length > 0) + { + if (blEncode) + { + await _updateView?.Invoke(EViewAction.SetClipboardData, Utils.Base64Encode(sb.ToString())); } else { - item = await AppHandler.Instance.GetSubItem(_config.SubIndexId); - if (item is null) - { - return; - } + await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); } - if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) + NoticeHandler.Instance.SendMessage(ResUI.BatchExportURLSuccessfully); + } + } + + #endregion Add Servers + + #region Subscription + + private async Task EditSubAsync(bool blNew) + { + SubItem item; + if (blNew) + { + item = new(); + } + else + { + item = await AppHandler.Instance.GetSubItem(_config.SubIndexId); + if (item is null) { - await RefreshSubscriptions(); - await SubSelectedChangedAsync(true); + return; } } - - #endregion Subscription + if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) + { + await RefreshSubscriptions(); + await SubSelectedChangedAsync(true); + } } + + #endregion Subscription } diff --git a/v2rayN/ServiceLib/ViewModels/RoutingRuleDetailsViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingRuleDetailsViewModel.cs index 507336c9..c4970f84 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingRuleDetailsViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingRuleDetailsViewModel.cs @@ -2,92 +2,91 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class RoutingRuleDetailsViewModel : MyReactiveObject { - public class RoutingRuleDetailsViewModel : MyReactiveObject + public IList ProtocolItems { get; set; } + public IList InboundTagItems { get; set; } + + [Reactive] + public RulesItem SelectedSource { get; set; } + + [Reactive] + public string Domain { get; set; } + + [Reactive] + public string IP { get; set; } + + [Reactive] + public string Process { get; set; } + + [Reactive] + public bool AutoSort { get; set; } + + public ReactiveCommand SaveCmd { get; } + + public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func>? updateView) { - public IList ProtocolItems { get; set; } - public IList InboundTagItems { get; set; } + _config = AppHandler.Instance.Config; + _updateView = updateView; - [Reactive] - public RulesItem SelectedSource { get; set; } - - [Reactive] - public string Domain { get; set; } - - [Reactive] - public string IP { get; set; } - - [Reactive] - public string Process { get; set; } - - [Reactive] - public bool AutoSort { get; set; } - - public ReactiveCommand SaveCmd { get; } - - public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func>? updateView) + SaveCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await SaveRulesAsync(); + }); - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveRulesAsync(); - }); - - if (rulesItem.Id.IsNullOrEmpty()) - { - rulesItem.Id = Utils.GetGuid(false); - rulesItem.OutboundTag = Global.ProxyTag; - rulesItem.Enabled = true; - SelectedSource = rulesItem; - } - else - { - SelectedSource = rulesItem; - } - - Domain = Utils.List2String(SelectedSource.Domain, true); - IP = Utils.List2String(SelectedSource.Ip, true); - Process = Utils.List2String(SelectedSource.Process, true); + if (rulesItem.Id.IsNullOrEmpty()) + { + rulesItem.Id = Utils.GetGuid(false); + rulesItem.OutboundTag = Global.ProxyTag; + rulesItem.Enabled = true; + SelectedSource = rulesItem; + } + else + { + SelectedSource = rulesItem; } - private async Task SaveRulesAsync() + Domain = Utils.List2String(SelectedSource.Domain, true); + IP = Utils.List2String(SelectedSource.Ip, true); + Process = Utils.List2String(SelectedSource.Process, true); + } + + private async Task SaveRulesAsync() + { + Domain = Utils.Convert2Comma(Domain); + IP = Utils.Convert2Comma(IP); + Process = Utils.Convert2Comma(Process); + + if (AutoSort) { - Domain = Utils.Convert2Comma(Domain); - IP = Utils.Convert2Comma(IP); - Process = Utils.Convert2Comma(Process); - - if (AutoSort) - { - SelectedSource.Domain = Utils.String2ListSorted(Domain); - SelectedSource.Ip = Utils.String2ListSorted(IP); - SelectedSource.Process = Utils.String2ListSorted(Process); - } - else - { - SelectedSource.Domain = Utils.String2List(Domain); - SelectedSource.Ip = Utils.String2List(IP); - SelectedSource.Process = Utils.String2List(Process); - } - SelectedSource.Protocol = ProtocolItems?.ToList(); - SelectedSource.InboundTag = InboundTagItems?.ToList(); - - var hasRule = SelectedSource.Domain?.Count > 0 - || SelectedSource.Ip?.Count > 0 - || SelectedSource.Protocol?.Count > 0 - || SelectedSource.Process?.Count > 0 - || SelectedSource.Port.IsNotEmpty() - || SelectedSource.Network.IsNotEmpty(); - - if (!hasRule) - { - NoticeHandler.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process")); - return; - } - //NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - await _updateView?.Invoke(EViewAction.CloseWindow, null); + SelectedSource.Domain = Utils.String2ListSorted(Domain); + SelectedSource.Ip = Utils.String2ListSorted(IP); + SelectedSource.Process = Utils.String2ListSorted(Process); } + else + { + SelectedSource.Domain = Utils.String2List(Domain); + SelectedSource.Ip = Utils.String2List(IP); + SelectedSource.Process = Utils.String2List(Process); + } + SelectedSource.Protocol = ProtocolItems?.ToList(); + SelectedSource.InboundTag = InboundTagItems?.ToList(); + + var hasRule = SelectedSource.Domain?.Count > 0 + || SelectedSource.Ip?.Count > 0 + || SelectedSource.Protocol?.Count > 0 + || SelectedSource.Process?.Count > 0 + || SelectedSource.Port.IsNotEmpty() + || SelectedSource.Network.IsNotEmpty(); + + if (!hasRule) + { + NoticeHandler.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process")); + return; + } + //NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + await _updateView?.Invoke(EViewAction.CloseWindow, null); } } diff --git a/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs index c449d9b5..93658a07 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs @@ -5,337 +5,336 @@ using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class RoutingRuleSettingViewModel : MyReactiveObject { - public class RoutingRuleSettingViewModel : MyReactiveObject + private List _rules; + + [Reactive] + public RoutingItem SelectedRouting { get; set; } + + private IObservableCollection _rulesItems = new ObservableCollectionExtended(); + public IObservableCollection RulesItems => _rulesItems; + + [Reactive] + public RulesItemModel SelectedSource { get; set; } + + public IList SelectedSources { get; set; } + + public ReactiveCommand RuleAddCmd { get; } + public ReactiveCommand ImportRulesFromFileCmd { get; } + public ReactiveCommand ImportRulesFromClipboardCmd { get; } + public ReactiveCommand ImportRulesFromUrlCmd { get; } + public ReactiveCommand RuleRemoveCmd { get; } + public ReactiveCommand RuleExportSelectedCmd { get; } + public ReactiveCommand MoveTopCmd { get; } + public ReactiveCommand MoveUpCmd { get; } + public ReactiveCommand MoveDownCmd { get; } + public ReactiveCommand MoveBottomCmd { get; } + + public ReactiveCommand SaveCmd { get; } + + public RoutingRuleSettingViewModel(RoutingItem routingItem, Func>? updateView) { - private List _rules; + _config = AppHandler.Instance.Config; + _updateView = updateView; - [Reactive] - public RoutingItem SelectedRouting { get; set; } + var canEditRemove = this.WhenAnyValue( + x => x.SelectedSource, + selectedSource => selectedSource != null && !selectedSource.OutboundTag.IsNullOrEmpty()); - private IObservableCollection _rulesItems = new ObservableCollectionExtended(); - public IObservableCollection RulesItems => _rulesItems; - - [Reactive] - public RulesItemModel SelectedSource { get; set; } - - public IList SelectedSources { get; set; } - - public ReactiveCommand RuleAddCmd { get; } - public ReactiveCommand ImportRulesFromFileCmd { get; } - public ReactiveCommand ImportRulesFromClipboardCmd { get; } - public ReactiveCommand ImportRulesFromUrlCmd { get; } - public ReactiveCommand RuleRemoveCmd { get; } - public ReactiveCommand RuleExportSelectedCmd { get; } - public ReactiveCommand MoveTopCmd { get; } - public ReactiveCommand MoveUpCmd { get; } - public ReactiveCommand MoveDownCmd { get; } - public ReactiveCommand MoveBottomCmd { get; } - - public ReactiveCommand SaveCmd { get; } - - public RoutingRuleSettingViewModel(RoutingItem routingItem, Func>? updateView) + RuleAddCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await RuleEditAsync(true); + }); + ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () => + { + await _updateView?.Invoke(EViewAction.ImportRulesFromFile, null); + }); + ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () => + { + await ImportRulesFromClipboardAsync(null); + }); + ImportRulesFromUrlCmd = ReactiveCommand.CreateFromTask(async () => + { + await ImportRulesFromUrl(); + }); - var canEditRemove = this.WhenAnyValue( - x => x.SelectedSource, - selectedSource => selectedSource != null && !selectedSource.OutboundTag.IsNullOrEmpty()); + RuleRemoveCmd = ReactiveCommand.CreateFromTask(async () => + { + await RuleRemoveAsync(); + }, canEditRemove); + RuleExportSelectedCmd = ReactiveCommand.CreateFromTask(async () => + { + await RuleExportSelectedAsync(); + }, canEditRemove); - RuleAddCmd = ReactiveCommand.CreateFromTask(async () => - { - await RuleEditAsync(true); - }); - ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () => - { - await _updateView?.Invoke(EViewAction.ImportRulesFromFile, null); - }); - ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await ImportRulesFromClipboardAsync(null); - }); - ImportRulesFromUrlCmd = ReactiveCommand.CreateFromTask(async () => - { - await ImportRulesFromUrl(); - }); + MoveTopCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveRule(EMove.Top); + }, canEditRemove); + MoveUpCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveRule(EMove.Up); + }, canEditRemove); + MoveDownCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveRule(EMove.Down); + }, canEditRemove); + MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => + { + await MoveRule(EMove.Bottom); + }, canEditRemove); - RuleRemoveCmd = ReactiveCommand.CreateFromTask(async () => - { - await RuleRemoveAsync(); - }, canEditRemove); - RuleExportSelectedCmd = ReactiveCommand.CreateFromTask(async () => - { - await RuleExportSelectedAsync(); - }, canEditRemove); + SaveCmd = ReactiveCommand.CreateFromTask(async () => + { + await SaveRoutingAsync(); + }); - MoveTopCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveRule(EMove.Top); - }, canEditRemove); - MoveUpCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveRule(EMove.Up); - }, canEditRemove); - MoveDownCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveRule(EMove.Down); - }, canEditRemove); - MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => - { - await MoveRule(EMove.Bottom); - }, canEditRemove); + SelectedSource = new(); + SelectedRouting = routingItem; + _rules = routingItem.Id.IsNullOrEmpty() ? new() : JsonUtils.Deserialize>(SelectedRouting.RuleSet); - SaveCmd = ReactiveCommand.CreateFromTask(async () => + RefreshRulesItems(); + } + + public void RefreshRulesItems() + { + _rulesItems.Clear(); + + foreach (var item in _rules) + { + var it = new RulesItemModel() { - await SaveRoutingAsync(); - }); - - SelectedSource = new(); - SelectedRouting = routingItem; - _rules = routingItem.Id.IsNullOrEmpty() ? new() : JsonUtils.Deserialize>(SelectedRouting.RuleSet); - - RefreshRulesItems(); + Id = item.Id, + OutboundTag = item.OutboundTag, + Port = item.Port, + Network = item.Network, + Protocols = Utils.List2String(item.Protocol), + InboundTags = Utils.List2String(item.InboundTag), + Domains = Utils.List2String(item.Domain), + Ips = Utils.List2String(item.Ip), + Enabled = item.Enabled, + Remarks = item.Remarks, + }; + _rulesItems.Add(it); } + } - public void RefreshRulesItems() + public async Task RuleEditAsync(bool blNew) + { + RulesItem? item; + if (blNew) { - _rulesItems.Clear(); - - foreach (var item in _rules) + item = new(); + } + else + { + item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); + if (item is null) { - var it = new RulesItemModel() - { - Id = item.Id, - OutboundTag = item.OutboundTag, - Port = item.Port, - Network = item.Network, - Protocols = Utils.List2String(item.Protocol), - InboundTags = Utils.List2String(item.InboundTag), - Domains = Utils.List2String(item.Domain), - Ips = Utils.List2String(item.Ip), - Enabled = item.Enabled, - Remarks = item.Remarks, - }; - _rulesItems.Add(it); + return; } } - - public async Task RuleEditAsync(bool blNew) + if (await _updateView?.Invoke(EViewAction.RoutingRuleDetailsWindow, item) == true) { - RulesItem? item; if (blNew) { - item = new(); + _rules.Insert(0, item); } - else - { - item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); - if (item is null) - { - return; - } - } - if (await _updateView?.Invoke(EViewAction.RoutingRuleDetailsWindow, item) == true) - { - if (blNew) - { - _rules.Insert(0, item); - } - RefreshRulesItems(); - } - } - - public async Task RuleRemoveAsync() - { - if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); - return; - } - if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) - { - return; - } - foreach (var it in SelectedSources ?? [SelectedSource]) - { - var item = _rules.FirstOrDefault(t => t.Id == it?.Id); - if (item != null) - { - _rules.Remove(item); - } - } - RefreshRulesItems(); } - - public async Task RuleExportSelectedAsync() - { - if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); - return; - } - - var lst = new List(); - var sources = SelectedSources ?? [SelectedSource]; - foreach (var it in _rules) - { - if (sources.Any(t => t.Id == it?.Id)) - { - var item2 = JsonUtils.DeepCopy(it); - item2.Id = null; - lst.Add(item2 ?? new()); - } - } - if (lst.Count > 0) - { - var options = new JsonSerializerOptions - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - await _updateView?.Invoke(EViewAction.SetClipboardData, JsonUtils.Serialize(lst, options)); - } - } - - public async Task MoveRule(EMove eMove) - { - if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); - return; - } - - var item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); - if (item == null) - { - return; - } - var index = _rules.IndexOf(item); - if (await ConfigHandler.MoveRoutingRule(_rules, index, eMove) == 0) - { - RefreshRulesItems(); - } - } - - private async Task SaveRoutingAsync() - { - string remarks = SelectedRouting.Remarks; - if (remarks.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); - return; - } - var item = SelectedRouting; - foreach (var it in _rules) - { - it.Id = Utils.GetGuid(false); - } - item.RuleNum = _rules.Count; - item.RuleSet = JsonUtils.Serialize(_rules, false); - - if (await ConfigHandler.SaveRoutingItem(_config, item) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } - } - - #region Import rules - - public async Task ImportRulesFromFileAsync(string fileName) - { - if (fileName.IsNullOrEmpty()) - { - return; - } - - var result = EmbedUtils.LoadResource(fileName); - if (result.IsNullOrEmpty()) - { - return; - } - var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); - if (ret == 0) - { - RefreshRulesItems(); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } - } - - public async Task ImportRulesFromClipboardAsync(string? clipboardData) - { - if (clipboardData == null) - { - await _updateView?.Invoke(EViewAction.ImportRulesFromClipboard, null); - return; - } - var ret = await AddBatchRoutingRulesAsync(SelectedRouting, clipboardData); - if (ret == 0) - { - RefreshRulesItems(); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } - } - - private async Task ImportRulesFromUrl() - { - var url = SelectedRouting.Url; - if (url.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.MsgNeedUrl); - return; - } - - DownloadService downloadHandle = new DownloadService(); - var result = await downloadHandle.TryDownloadString(url, true, ""); - var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); - if (ret == 0) - { - RefreshRulesItems(); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - } - } - - private async Task AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData) - { - bool blReplace = false; - if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false) - { - blReplace = true; - } - if (clipboardData.IsNullOrEmpty()) - { - return -1; - } - var lstRules = JsonUtils.Deserialize>(clipboardData); - if (lstRules == null) - { - return -1; - } - foreach (var rule in lstRules) - { - rule.Id = Utils.GetGuid(false); - } - - if (blReplace) - { - _rules = lstRules; - } - else - { - _rules.AddRange(lstRules); - } - return 0; - } - - #endregion Import rules } + + public async Task RuleRemoveAsync() + { + if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); + return; + } + if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + { + return; + } + foreach (var it in SelectedSources ?? [SelectedSource]) + { + var item = _rules.FirstOrDefault(t => t.Id == it?.Id); + if (item != null) + { + _rules.Remove(item); + } + } + + RefreshRulesItems(); + } + + public async Task RuleExportSelectedAsync() + { + if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); + return; + } + + var lst = new List(); + var sources = SelectedSources ?? [SelectedSource]; + foreach (var it in _rules) + { + if (sources.Any(t => t.Id == it?.Id)) + { + var item2 = JsonUtils.DeepCopy(it); + item2.Id = null; + lst.Add(item2 ?? new()); + } + } + if (lst.Count > 0) + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + await _updateView?.Invoke(EViewAction.SetClipboardData, JsonUtils.Serialize(lst, options)); + } + } + + public async Task MoveRule(EMove eMove) + { + if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); + return; + } + + var item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); + if (item == null) + { + return; + } + var index = _rules.IndexOf(item); + if (await ConfigHandler.MoveRoutingRule(_rules, index, eMove) == 0) + { + RefreshRulesItems(); + } + } + + private async Task SaveRoutingAsync() + { + string remarks = SelectedRouting.Remarks; + if (remarks.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + return; + } + var item = SelectedRouting; + foreach (var it in _rules) + { + it.Id = Utils.GetGuid(false); + } + item.RuleNum = _rules.Count; + item.RuleSet = JsonUtils.Serialize(_rules, false); + + if (await ConfigHandler.SaveRoutingItem(_config, item) == 0) + { + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); + } + } + + #region Import rules + + public async Task ImportRulesFromFileAsync(string fileName) + { + if (fileName.IsNullOrEmpty()) + { + return; + } + + var result = EmbedUtils.LoadResource(fileName); + if (result.IsNullOrEmpty()) + { + return; + } + var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); + if (ret == 0) + { + RefreshRulesItems(); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + } + } + + public async Task ImportRulesFromClipboardAsync(string? clipboardData) + { + if (clipboardData == null) + { + await _updateView?.Invoke(EViewAction.ImportRulesFromClipboard, null); + return; + } + var ret = await AddBatchRoutingRulesAsync(SelectedRouting, clipboardData); + if (ret == 0) + { + RefreshRulesItems(); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + } + } + + private async Task ImportRulesFromUrl() + { + var url = SelectedRouting.Url; + if (url.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.MsgNeedUrl); + return; + } + + DownloadService downloadHandle = new DownloadService(); + var result = await downloadHandle.TryDownloadString(url, true, ""); + var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); + if (ret == 0) + { + RefreshRulesItems(); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + } + } + + private async Task AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData) + { + bool blReplace = false; + if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false) + { + blReplace = true; + } + if (clipboardData.IsNullOrEmpty()) + { + return -1; + } + var lstRules = JsonUtils.Deserialize>(clipboardData); + if (lstRules == null) + { + return -1; + } + foreach (var rule in lstRules) + { + rule.Id = Utils.GetGuid(false); + } + + if (blReplace) + { + _rules = lstRules; + } + else + { + _rules.AddRange(lstRules); + } + return 0; + } + + #endregion Import rules } diff --git a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs index b494b9d1..dc2d7eda 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -3,199 +3,198 @@ using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class RoutingSettingViewModel : MyReactiveObject { - public class RoutingSettingViewModel : MyReactiveObject + #region Reactive + + private IObservableCollection _routingItems = new ObservableCollectionExtended(); + public IObservableCollection RoutingItems => _routingItems; + + [Reactive] + public RoutingItemModel SelectedSource { get; set; } + + public IList SelectedSources { get; set; } + + [Reactive] + public string DomainStrategy { get; set; } + + [Reactive] + public string DomainMatcher { get; set; } + + [Reactive] + public string DomainStrategy4Singbox { get; set; } + + public ReactiveCommand RoutingAdvancedAddCmd { get; } + public ReactiveCommand RoutingAdvancedRemoveCmd { get; } + public ReactiveCommand RoutingAdvancedSetDefaultCmd { get; } + public ReactiveCommand RoutingAdvancedImportRulesCmd { get; } + + public ReactiveCommand SaveCmd { get; } + public bool IsModified { get; set; } + + #endregion Reactive + + public RoutingSettingViewModel(Func>? updateView) { - #region Reactive + _config = AppHandler.Instance.Config; + _updateView = updateView; - private IObservableCollection _routingItems = new ObservableCollectionExtended(); - public IObservableCollection RoutingItems => _routingItems; + var canEditRemove = this.WhenAnyValue( + x => x.SelectedSource, + selectedSource => selectedSource != null && !selectedSource.Remarks.IsNullOrEmpty()); - [Reactive] - public RoutingItemModel SelectedSource { get; set; } - - public IList SelectedSources { get; set; } - - [Reactive] - public string DomainStrategy { get; set; } - - [Reactive] - public string DomainMatcher { get; set; } - - [Reactive] - public string DomainStrategy4Singbox { get; set; } - - public ReactiveCommand RoutingAdvancedAddCmd { get; } - public ReactiveCommand RoutingAdvancedRemoveCmd { get; } - public ReactiveCommand RoutingAdvancedSetDefaultCmd { get; } - public ReactiveCommand RoutingAdvancedImportRulesCmd { get; } - - public ReactiveCommand SaveCmd { get; } - public bool IsModified { get; set; } - - #endregion Reactive - - public RoutingSettingViewModel(Func>? updateView) + RoutingAdvancedAddCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await RoutingAdvancedEditAsync(true); + }); + RoutingAdvancedRemoveCmd = ReactiveCommand.CreateFromTask(async () => + { + await RoutingAdvancedRemoveAsync(); + }, canEditRemove); + RoutingAdvancedSetDefaultCmd = ReactiveCommand.CreateFromTask(async () => + { + await RoutingAdvancedSetDefault(); + }, canEditRemove); + RoutingAdvancedImportRulesCmd = ReactiveCommand.CreateFromTask(async () => + { + await RoutingAdvancedImportRules(); + }); - var canEditRemove = this.WhenAnyValue( - x => x.SelectedSource, - selectedSource => selectedSource != null && !selectedSource.Remarks.IsNullOrEmpty()); + SaveCmd = ReactiveCommand.CreateFromTask(async () => + { + await SaveRoutingAsync(); + }); - RoutingAdvancedAddCmd = ReactiveCommand.CreateFromTask(async () => - { - await RoutingAdvancedEditAsync(true); - }); - RoutingAdvancedRemoveCmd = ReactiveCommand.CreateFromTask(async () => - { - await RoutingAdvancedRemoveAsync(); - }, canEditRemove); - RoutingAdvancedSetDefaultCmd = ReactiveCommand.CreateFromTask(async () => - { - await RoutingAdvancedSetDefault(); - }, canEditRemove); - RoutingAdvancedImportRulesCmd = ReactiveCommand.CreateFromTask(async () => - { - await RoutingAdvancedImportRules(); - }); + _ = Init(); + } - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveRoutingAsync(); - }); + private async Task Init() + { + SelectedSource = new(); - _ = Init(); + DomainStrategy = _config.RoutingBasicItem.DomainStrategy; + DomainMatcher = _config.RoutingBasicItem.DomainMatcher; + DomainStrategy4Singbox = _config.RoutingBasicItem.DomainStrategy4Singbox; + + await ConfigHandler.InitBuiltinRouting(_config); + await RefreshRoutingItems(); + } + + #region Refresh Save + + public async Task RefreshRoutingItems() + { + _routingItems.Clear(); + + var routings = await AppHandler.Instance.RoutingItems(); + foreach (var item in routings) + { + var def = item.Id == _config.RoutingBasicItem.RoutingIndexId; + + var it = new RoutingItemModel() + { + IsActive = def, + RuleNum = item.RuleNum, + Id = item.Id, + Remarks = item.Remarks, + Url = item.Url, + CustomIcon = item.CustomIcon, + CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox, + Sort = item.Sort, + }; + _routingItems.Add(it); } + } - private async Task Init() + private async Task SaveRoutingAsync() + { + _config.RoutingBasicItem.DomainStrategy = DomainStrategy; + _config.RoutingBasicItem.DomainMatcher = DomainMatcher; + _config.RoutingBasicItem.DomainStrategy4Singbox = DomainStrategy4Singbox; + + if (await ConfigHandler.SaveConfig(_config) == 0) { - SelectedSource = new(); - - DomainStrategy = _config.RoutingBasicItem.DomainStrategy; - DomainMatcher = _config.RoutingBasicItem.DomainMatcher; - DomainStrategy4Singbox = _config.RoutingBasicItem.DomainStrategy4Singbox; - - await ConfigHandler.InitBuiltinRouting(_config); - await RefreshRoutingItems(); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); } - - #region Refresh Save - - public async Task RefreshRoutingItems() + else { - _routingItems.Clear(); - - var routings = await AppHandler.Instance.RoutingItems(); - foreach (var item in routings) - { - var def = item.Id == _config.RoutingBasicItem.RoutingIndexId; - - var it = new RoutingItemModel() - { - IsActive = def, - RuleNum = item.RuleNum, - Id = item.Id, - Remarks = item.Remarks, - Url = item.Url, - CustomIcon = item.CustomIcon, - CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox, - Sort = item.Sort, - }; - _routingItems.Add(it); - } + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } + } - private async Task SaveRoutingAsync() + #endregion Refresh Save + + public async Task RoutingAdvancedEditAsync(bool blNew) + { + RoutingItem item; + if (blNew) { - _config.RoutingBasicItem.DomainStrategy = DomainStrategy; - _config.RoutingBasicItem.DomainMatcher = DomainMatcher; - _config.RoutingBasicItem.DomainStrategy4Singbox = DomainStrategy4Singbox; - - if (await ConfigHandler.SaveConfig(_config) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } + item = new(); } - - #endregion Refresh Save - - public async Task RoutingAdvancedEditAsync(bool blNew) + else { - RoutingItem item; - if (blNew) - { - item = new(); - } - else - { - item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); - if (item is null) - { - return; - } - } - if (await _updateView?.Invoke(EViewAction.RoutingRuleSettingWindow, item) == true) - { - await RefreshRoutingItems(); - IsModified = true; - } - } - - public async Task RoutingAdvancedRemoveAsync() - { - if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty()) - { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); - return; - } - if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); + if (item is null) { return; } - foreach (var it in SelectedSources ?? [SelectedSource]) - { - var item = await AppHandler.Instance.GetRoutingItem(it?.Id); - if (item != null) - { - await ConfigHandler.RemoveRoutingItem(item); - } - } - + } + if (await _updateView?.Invoke(EViewAction.RoutingRuleSettingWindow, item) == true) + { await RefreshRoutingItems(); IsModified = true; } + } - public async Task RoutingAdvancedSetDefault() + public async Task RoutingAdvancedRemoveAsync() + { + if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty()) { - var item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); - if (item is null) + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); + return; + } + if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + { + return; + } + foreach (var it in SelectedSources ?? [SelectedSource]) + { + var item = await AppHandler.Instance.GetRoutingItem(it?.Id); + if (item != null) { - NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); - return; - } - - if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) - { - await RefreshRoutingItems(); - IsModified = true; + await ConfigHandler.RemoveRoutingItem(item); } } - private async Task RoutingAdvancedImportRules() + await RefreshRoutingItems(); + IsModified = true; + } + + public async Task RoutingAdvancedSetDefault() + { + var item = await AppHandler.Instance.GetRoutingItem(SelectedSource?.Id); + if (item is null) { - if (await ConfigHandler.InitRouting(_config, true) == 0) - { - await RefreshRoutingItems(); - IsModified = true; - } + NoticeHandler.Instance.Enqueue(ResUI.PleaseSelectRules); + return; + } + + if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) + { + await RefreshRoutingItems(); + IsModified = true; + } + } + + private async Task RoutingAdvancedImportRules() + { + if (await ConfigHandler.InitRouting(_config, true) == 0) + { + await RefreshRoutingItems(); + IsModified = true; } } } diff --git a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index 2be412d4..4c7a5c1c 100644 --- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -5,504 +5,503 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class StatusBarViewModel : MyReactiveObject { - public class StatusBarViewModel : MyReactiveObject + #region ObservableCollection + + private IObservableCollection _routingItems = new ObservableCollectionExtended(); + public IObservableCollection RoutingItems => _routingItems; + + private IObservableCollection _servers = new ObservableCollectionExtended(); + public IObservableCollection Servers => _servers; + + [Reactive] + public RoutingItem SelectedRouting { get; set; } + + [Reactive] + public ComboItem SelectedServer { get; set; } + + [Reactive] + public bool BlServers { get; set; } + + #endregion ObservableCollection + + public ReactiveCommand AddServerViaClipboardCmd { get; } + public ReactiveCommand AddServerViaScanCmd { get; } + public ReactiveCommand SubUpdateCmd { get; } + public ReactiveCommand SubUpdateViaProxyCmd { get; } + public ReactiveCommand CopyProxyCmdToClipboardCmd { get; } + public ReactiveCommand NotifyLeftClickCmd { get; } + + #region System Proxy + + [Reactive] + public bool BlSystemProxyClear { get; set; } + + [Reactive] + public bool BlSystemProxySet { get; set; } + + [Reactive] + public bool BlSystemProxyNothing { get; set; } + + [Reactive] + public bool BlSystemProxyPac { get; set; } + + public ReactiveCommand SystemProxyClearCmd { get; } + public ReactiveCommand SystemProxySetCmd { get; } + public ReactiveCommand SystemProxyNothingCmd { get; } + public ReactiveCommand SystemProxyPacCmd { get; } + + [Reactive] + public bool BlRouting { get; set; } + + [Reactive] + public int SystemProxySelected { get; set; } + + [Reactive] + public bool BlSystemProxyPacVisible { get; set; } + + #endregion System Proxy + + #region UI + + [Reactive] + public string InboundDisplay { get; set; } + + [Reactive] + public string InboundLanDisplay { get; set; } + + [Reactive] + public string RunningServerDisplay { get; set; } + + [Reactive] + public string RunningServerToolTipText { get; set; } + + [Reactive] + public string RunningInfoDisplay { get; set; } + + [Reactive] + public string SpeedProxyDisplay { get; set; } + + [Reactive] + public string SpeedDirectDisplay { get; set; } + + [Reactive] + public bool EnableTun { get; set; } + + #endregion UI + + public StatusBarViewModel(Func>? updateView) { - #region ObservableCollection + _config = AppHandler.Instance.Config; + SelectedRouting = new(); + SelectedServer = new(); + RunningServerToolTipText = "-"; + BlSystemProxyPacVisible = Utils.IsWindows(); - private IObservableCollection _routingItems = new ObservableCollectionExtended(); - public IObservableCollection RoutingItems => _routingItems; - - private IObservableCollection _servers = new ObservableCollectionExtended(); - public IObservableCollection Servers => _servers; - - [Reactive] - public RoutingItem SelectedRouting { get; set; } - - [Reactive] - public ComboItem SelectedServer { get; set; } - - [Reactive] - public bool BlServers { get; set; } - - #endregion ObservableCollection - - public ReactiveCommand AddServerViaClipboardCmd { get; } - public ReactiveCommand AddServerViaScanCmd { get; } - public ReactiveCommand SubUpdateCmd { get; } - public ReactiveCommand SubUpdateViaProxyCmd { get; } - public ReactiveCommand CopyProxyCmdToClipboardCmd { get; } - public ReactiveCommand NotifyLeftClickCmd { get; } - - #region System Proxy - - [Reactive] - public bool BlSystemProxyClear { get; set; } - - [Reactive] - public bool BlSystemProxySet { get; set; } - - [Reactive] - public bool BlSystemProxyNothing { get; set; } - - [Reactive] - public bool BlSystemProxyPac { get; set; } - - public ReactiveCommand SystemProxyClearCmd { get; } - public ReactiveCommand SystemProxySetCmd { get; } - public ReactiveCommand SystemProxyNothingCmd { get; } - public ReactiveCommand SystemProxyPacCmd { get; } - - [Reactive] - public bool BlRouting { get; set; } - - [Reactive] - public int SystemProxySelected { get; set; } - - [Reactive] - public bool BlSystemProxyPacVisible { get; set; } - - #endregion System Proxy - - #region UI - - [Reactive] - public string InboundDisplay { get; set; } - - [Reactive] - public string InboundLanDisplay { get; set; } - - [Reactive] - public string RunningServerDisplay { get; set; } - - [Reactive] - public string RunningServerToolTipText { get; set; } - - [Reactive] - public string RunningInfoDisplay { get; set; } - - [Reactive] - public string SpeedProxyDisplay { get; set; } - - [Reactive] - public string SpeedDirectDisplay { get; set; } - - [Reactive] - public bool EnableTun { get; set; } - - #endregion UI - - public StatusBarViewModel(Func>? updateView) + if (_config.TunModeItem.EnableTun && AllowEnableTun()) { - _config = AppHandler.Instance.Config; - SelectedRouting = new(); - SelectedServer = new(); - RunningServerToolTipText = "-"; - BlSystemProxyPacVisible = Utils.IsWindows(); - - if (_config.TunModeItem.EnableTun && AllowEnableTun()) - { - EnableTun = true; - } - else - { - _config.TunModeItem.EnableTun = EnableTun = false; - } - - #region WhenAnyValue && ReactiveCommand - - this.WhenAnyValue( - x => x.SelectedRouting, - y => y != null && !y.Remarks.IsNullOrEmpty()) - .Subscribe(async c => await RoutingSelectedChangedAsync(c)); - - this.WhenAnyValue( - x => x.SelectedServer, - y => y != null && !y.Text.IsNullOrEmpty()) - .Subscribe(c => ServerSelectedChanged(c)); - - SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; - this.WhenAnyValue( - x => x.SystemProxySelected, - y => y >= 0) - .Subscribe(async c => await DoSystemProxySelected(c)); - - this.WhenAnyValue( - x => x.EnableTun, - y => y == true) - .Subscribe(async c => await DoEnableTun(c)); - - CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await CopyProxyCmdToClipboard(); - }); - - NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => - { - Locator.Current.GetService()?.ShowHideWindow(null); - await Task.CompletedTask; - }); - - AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaClipboard(); - }); - AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaScan(); - }); - SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess(false); - }); - SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => - { - await UpdateSubscriptionProcess(true); - }); - - //System proxy - SystemProxyClearCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetListenerType(ESysProxyType.ForcedClear); - }); - SystemProxySetCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetListenerType(ESysProxyType.ForcedChange); - }); - SystemProxyNothingCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetListenerType(ESysProxyType.Unchanged); - }); - SystemProxyPacCmd = ReactiveCommand.CreateFromTask(async () => - { - await SetListenerType(ESysProxyType.Pac); - }); - - #endregion WhenAnyValue && ReactiveCommand - - if (updateView != null) - { - InitUpdateView(updateView); - } - _ = Init(); + EnableTun = true; + } + else + { + _config.TunModeItem.EnableTun = EnableTun = false; } - private async Task Init() + #region WhenAnyValue && ReactiveCommand + + this.WhenAnyValue( + x => x.SelectedRouting, + y => y != null && !y.Remarks.IsNullOrEmpty()) + .Subscribe(async c => await RoutingSelectedChangedAsync(c)); + + this.WhenAnyValue( + x => x.SelectedServer, + y => y != null && !y.Text.IsNullOrEmpty()) + .Subscribe(c => ServerSelectedChanged(c)); + + SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; + this.WhenAnyValue( + x => x.SystemProxySelected, + y => y >= 0) + .Subscribe(async c => await DoSystemProxySelected(c)); + + this.WhenAnyValue( + x => x.EnableTun, + y => y == true) + .Subscribe(async c => await DoEnableTun(c)); + + CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () => { - await RefreshRoutingsMenu(); - await InboundDisplayStatus(); - await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true); - } + await CopyProxyCmdToClipboard(); + }); - public void InitUpdateView(Func>? updateView) + NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => { - _updateView = updateView; - if (_updateView != null) - { - MessageBus.Current.Listen(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext); - } - } - - private async void OnNext(string x) - { - await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); - } - - private async Task CopyProxyCmdToClipboard() - { - var cmd = Utils.IsWindows() ? "set" : "export"; - var address = $"{Global.Loopback}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"; - - var sb = new StringBuilder(); - sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}"); - sb.AppendLine($"{cmd} https_proxy={Global.HttpProtocol}{address}"); - sb.AppendLine($"{cmd} all_proxy={Global.Socks5Protocol}{address}"); - sb.AppendLine(""); - sb.AppendLine($"{cmd} HTTP_PROXY={Global.HttpProtocol}{address}"); - sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}"); - sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}"); - - await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); - } - - private async Task AddServerViaClipboard() - { - var service = Locator.Current.GetService(); - if (service != null) - await service.AddServerViaClipboardAsync(null); - } - - private async Task AddServerViaScan() - { - var service = Locator.Current.GetService(); - if (service != null) - await service.AddServerViaScanAsync(); - } - - private async Task UpdateSubscriptionProcess(bool blProxy) - { - var service = Locator.Current.GetService(); - if (service != null) - await service.UpdateSubscriptionProcess("", blProxy); - } - - public async Task RefreshServersBiz() - { - await RefreshServersMenu(); - - //display running server - var running = await ConfigHandler.GetDefaultServer(_config); - if (running != null) - { - RunningServerDisplay = - RunningServerToolTipText = running.GetSummary(); - } - else - { - RunningServerDisplay = - RunningServerToolTipText = ResUI.CheckServerSettings; - } - } - - private async Task RefreshServersMenu() - { - var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, ""); - - _servers.Clear(); - if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) - { - BlServers = false; - return; - } - - BlServers = true; - for (int k = 0; k < lstModel.Count; k++) - { - ProfileItem it = lstModel[k]; - string name = it.GetSummary(); - - var item = new ComboItem() { ID = it.IndexId, Text = name }; - _servers.Add(item); - if (_config.IndexId == it.IndexId) - { - SelectedServer = item; - } - } - } - - private void ServerSelectedChanged(bool c) - { - if (!c) - { - return; - } - if (SelectedServer == null) - { - return; - } - if (SelectedServer.ID.IsNullOrEmpty()) - { - return; - } - Locator.Current.GetService()?.SetDefaultServer(SelectedServer.ID); - } - - public async Task TestServerAvailability() - { - var item = await ConfigHandler.GetDefaultServer(_config); - if (item == null) - { - return; - } - - _updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting); - - var msg = await (new UpdateService()).RunAvailabilityCheck(); - - NoticeHandler.Instance.SendMessageEx(msg); - _updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg); - } - - public void TestServerAvailabilityResult(string msg) - { - RunningInfoDisplay = msg; - } - - #region System proxy and Routings - - public async Task SetListenerType(ESysProxyType type) - { - if (_config.SystemProxyItem.SysProxyType == type) - { - return; - } - _config.SystemProxyItem.SysProxyType = type; - await ChangeSystemProxyAsync(type, true); - NoticeHandler.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}"); - - SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; - await ConfigHandler.SaveConfig(_config); - } - - public async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange) - { - await SysProxyHandler.UpdateSysProxy(_config, false); - - BlSystemProxyClear = (type == ESysProxyType.ForcedClear); - BlSystemProxySet = (type == ESysProxyType.ForcedChange); - BlSystemProxyNothing = (type == ESysProxyType.Unchanged); - BlSystemProxyPac = (type == ESysProxyType.Pac); - - if (blChange) - { - _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); - } - } - - public async Task RefreshRoutingsMenu() - { - _routingItems.Clear(); - - BlRouting = true; - var routings = await AppHandler.Instance.RoutingItems(); - foreach (var item in routings) - { - _routingItems.Add(item); - if (item.Id == _config.RoutingBasicItem.RoutingIndexId) - { - SelectedRouting = item; - } - } - } - - private async Task RoutingSelectedChangedAsync(bool c) - { - if (!c) - { - return; - } - - if (SelectedRouting == null) - { - return; - } - - var item = await AppHandler.Instance.GetRoutingItem(SelectedRouting?.Id); - if (item is null) - { - return; - } - if (_config.RoutingBasicItem.RoutingIndexId == item.Id) - { - return; - } - - if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) - { - NoticeHandler.Instance.SendMessageEx(ResUI.TipChangeRouting); - Locator.Current.GetService()?.Reload(); - _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); - } - } - - private async Task DoSystemProxySelected(bool c) - { - if (!c) - { - return; - } - if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected) - { - return; - } - await SetListenerType((ESysProxyType)SystemProxySelected); - } - - private async Task DoEnableTun(bool c) - { - if (_config.TunModeItem.EnableTun != EnableTun) - { - _config.TunModeItem.EnableTun = EnableTun; - // When running as a non-administrator, reboot to administrator mode - if (EnableTun && AllowEnableTun() == false) - { - if (Utils.IsWindows()) - { - _config.TunModeItem.EnableTun = false; - Locator.Current.GetService()?.RebootAsAdmin(); - return; - } - else if (Utils.IsOSX()) - { - _config.TunModeItem.EnableTun = false; - NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordIsEmpty); - return; - } - } - await ConfigHandler.SaveConfig(_config); - Locator.Current.GetService()?.Reload(); - } - } - - private bool AllowEnableTun() - { - if (Utils.IsWindows()) - { - return AppHandler.Instance.IsAdministrator; - } - else if (Utils.IsLinux()) - { - return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty(); - } - else if (Utils.IsOSX()) - { - return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty(); - } - return false; - } - - #endregion System proxy and Routings - - #region UI - - public async Task InboundDisplayStatus() - { - StringBuilder sb = new(); - sb.Append($"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"); - if (_config.Inbound.First().SecondLocalPortEnabled) - { - sb.Append($",{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks2)}"); - } - sb.Append(']'); - InboundDisplay = $"{ResUI.LabLocal}:{sb}"; - - if (_config.Inbound.First().AllowLANConn) - { - var lan = _config.Inbound.First().NewPort4LAN - ? $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks3)}]" - : $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}]"; - InboundLanDisplay = $"{ResUI.LabLAN}:{lan}"; - } - else - { - InboundLanDisplay = $"{ResUI.LabLAN}:{Global.None}"; - } + Locator.Current.GetService()?.ShowHideWindow(null); await Task.CompletedTask; - } + }); - public void UpdateStatistics(ServerSpeedItem update) + AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => { - try - { - SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); - SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); - } - catch - { - } + await AddServerViaClipboard(); + }); + AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerViaScan(); + }); + SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess(false); + }); + SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => + { + await UpdateSubscriptionProcess(true); + }); + + //System proxy + SystemProxyClearCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetListenerType(ESysProxyType.ForcedClear); + }); + SystemProxySetCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetListenerType(ESysProxyType.ForcedChange); + }); + SystemProxyNothingCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetListenerType(ESysProxyType.Unchanged); + }); + SystemProxyPacCmd = ReactiveCommand.CreateFromTask(async () => + { + await SetListenerType(ESysProxyType.Pac); + }); + + #endregion WhenAnyValue && ReactiveCommand + + if (updateView != null) + { + InitUpdateView(updateView); + } + _ = Init(); + } + + private async Task Init() + { + await RefreshRoutingsMenu(); + await InboundDisplayStatus(); + await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true); + } + + public void InitUpdateView(Func>? updateView) + { + _updateView = updateView; + if (_updateView != null) + { + MessageBus.Current.Listen(EMsgCommand.RefreshProfiles.ToString()).Subscribe(OnNext); + } + } + + private async void OnNext(string x) + { + await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); + } + + private async Task CopyProxyCmdToClipboard() + { + var cmd = Utils.IsWindows() ? "set" : "export"; + var address = $"{Global.Loopback}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"; + + var sb = new StringBuilder(); + sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}"); + sb.AppendLine($"{cmd} https_proxy={Global.HttpProtocol}{address}"); + sb.AppendLine($"{cmd} all_proxy={Global.Socks5Protocol}{address}"); + sb.AppendLine(""); + sb.AppendLine($"{cmd} HTTP_PROXY={Global.HttpProtocol}{address}"); + sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}"); + sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}"); + + await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); + } + + private async Task AddServerViaClipboard() + { + var service = Locator.Current.GetService(); + if (service != null) + await service.AddServerViaClipboardAsync(null); + } + + private async Task AddServerViaScan() + { + var service = Locator.Current.GetService(); + if (service != null) + await service.AddServerViaScanAsync(); + } + + private async Task UpdateSubscriptionProcess(bool blProxy) + { + var service = Locator.Current.GetService(); + if (service != null) + await service.UpdateSubscriptionProcess("", blProxy); + } + + public async Task RefreshServersBiz() + { + await RefreshServersMenu(); + + //display running server + var running = await ConfigHandler.GetDefaultServer(_config); + if (running != null) + { + RunningServerDisplay = + RunningServerToolTipText = running.GetSummary(); + } + else + { + RunningServerDisplay = + RunningServerToolTipText = ResUI.CheckServerSettings; + } + } + + private async Task RefreshServersMenu() + { + var lstModel = await AppHandler.Instance.ProfileItems(_config.SubIndexId, ""); + + _servers.Clear(); + if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) + { + BlServers = false; + return; } - #endregion UI + BlServers = true; + for (int k = 0; k < lstModel.Count; k++) + { + ProfileItem it = lstModel[k]; + string name = it.GetSummary(); + + var item = new ComboItem() { ID = it.IndexId, Text = name }; + _servers.Add(item); + if (_config.IndexId == it.IndexId) + { + SelectedServer = item; + } + } } + + private void ServerSelectedChanged(bool c) + { + if (!c) + { + return; + } + if (SelectedServer == null) + { + return; + } + if (SelectedServer.ID.IsNullOrEmpty()) + { + return; + } + Locator.Current.GetService()?.SetDefaultServer(SelectedServer.ID); + } + + public async Task TestServerAvailability() + { + var item = await ConfigHandler.GetDefaultServer(_config); + if (item == null) + { + return; + } + + _updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting); + + var msg = await (new UpdateService()).RunAvailabilityCheck(); + + NoticeHandler.Instance.SendMessageEx(msg); + _updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg); + } + + public void TestServerAvailabilityResult(string msg) + { + RunningInfoDisplay = msg; + } + + #region System proxy and Routings + + public async Task SetListenerType(ESysProxyType type) + { + if (_config.SystemProxyItem.SysProxyType == type) + { + return; + } + _config.SystemProxyItem.SysProxyType = type; + await ChangeSystemProxyAsync(type, true); + NoticeHandler.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}"); + + SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; + await ConfigHandler.SaveConfig(_config); + } + + public async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange) + { + await SysProxyHandler.UpdateSysProxy(_config, false); + + BlSystemProxyClear = (type == ESysProxyType.ForcedClear); + BlSystemProxySet = (type == ESysProxyType.ForcedChange); + BlSystemProxyNothing = (type == ESysProxyType.Unchanged); + BlSystemProxyPac = (type == ESysProxyType.Pac); + + if (blChange) + { + _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); + } + } + + public async Task RefreshRoutingsMenu() + { + _routingItems.Clear(); + + BlRouting = true; + var routings = await AppHandler.Instance.RoutingItems(); + foreach (var item in routings) + { + _routingItems.Add(item); + if (item.Id == _config.RoutingBasicItem.RoutingIndexId) + { + SelectedRouting = item; + } + } + } + + private async Task RoutingSelectedChangedAsync(bool c) + { + if (!c) + { + return; + } + + if (SelectedRouting == null) + { + return; + } + + var item = await AppHandler.Instance.GetRoutingItem(SelectedRouting?.Id); + if (item is null) + { + return; + } + if (_config.RoutingBasicItem.RoutingIndexId == item.Id) + { + return; + } + + if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) + { + NoticeHandler.Instance.SendMessageEx(ResUI.TipChangeRouting); + Locator.Current.GetService()?.Reload(); + _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); + } + } + + private async Task DoSystemProxySelected(bool c) + { + if (!c) + { + return; + } + if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected) + { + return; + } + await SetListenerType((ESysProxyType)SystemProxySelected); + } + + private async Task DoEnableTun(bool c) + { + if (_config.TunModeItem.EnableTun != EnableTun) + { + _config.TunModeItem.EnableTun = EnableTun; + // When running as a non-administrator, reboot to administrator mode + if (EnableTun && AllowEnableTun() == false) + { + if (Utils.IsWindows()) + { + _config.TunModeItem.EnableTun = false; + Locator.Current.GetService()?.RebootAsAdmin(); + return; + } + else if (Utils.IsOSX()) + { + _config.TunModeItem.EnableTun = false; + NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordIsEmpty); + return; + } + } + await ConfigHandler.SaveConfig(_config); + Locator.Current.GetService()?.Reload(); + } + } + + private bool AllowEnableTun() + { + if (Utils.IsWindows()) + { + return AppHandler.Instance.IsAdministrator; + } + else if (Utils.IsLinux()) + { + return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty(); + } + else if (Utils.IsOSX()) + { + return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty(); + } + return false; + } + + #endregion System proxy and Routings + + #region UI + + public async Task InboundDisplayStatus() + { + StringBuilder sb = new(); + sb.Append($"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}"); + if (_config.Inbound.First().SecondLocalPortEnabled) + { + sb.Append($",{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks2)}"); + } + sb.Append(']'); + InboundDisplay = $"{ResUI.LabLocal}:{sb}"; + + if (_config.Inbound.First().AllowLANConn) + { + var lan = _config.Inbound.First().NewPort4LAN + ? $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks3)}]" + : $"[{EInboundProtocol.mixed}:{AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)}]"; + InboundLanDisplay = $"{ResUI.LabLAN}:{lan}"; + } + else + { + InboundLanDisplay = $"{ResUI.LabLAN}:{Global.None}"; + } + await Task.CompletedTask; + } + + public void UpdateStatistics(ServerSpeedItem update) + { + try + { + SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); + SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); + } + catch + { + } + } + + #endregion UI } diff --git a/v2rayN/ServiceLib/ViewModels/SubEditViewModel.cs b/v2rayN/ServiceLib/ViewModels/SubEditViewModel.cs index 0c1c3d7a..e98a8a34 100644 --- a/v2rayN/ServiceLib/ViewModels/SubEditViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/SubEditViewModel.cs @@ -2,63 +2,62 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class SubEditViewModel : MyReactiveObject { - public class SubEditViewModel : MyReactiveObject + [Reactive] + public SubItem SelectedSource { get; set; } + + public ReactiveCommand SaveCmd { get; } + + public SubEditViewModel(SubItem subItem, Func>? updateView) { - [Reactive] - public SubItem SelectedSource { get; set; } + _config = AppHandler.Instance.Config; + _updateView = updateView; - public ReactiveCommand SaveCmd { get; } - - public SubEditViewModel(SubItem subItem, Func>? updateView) + SaveCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await SaveSubAsync(); + }); - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveSubAsync(); - }); + SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem); + } - SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem); + private async Task SaveSubAsync() + { + var remarks = SelectedSource.Remarks; + if (remarks.IsNullOrEmpty()) + { + NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + return; } - private async Task SaveSubAsync() + var url = SelectedSource.Url; + if (url.IsNotEmpty()) { - var remarks = SelectedSource.Remarks; - if (remarks.IsNullOrEmpty()) + var uri = Utils.TryUri(url); + if (uri == null) { - NoticeHandler.Instance.Enqueue(ResUI.PleaseFillRemarks); + NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip); return; } + //Do not allow http protocol + if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) + { + NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); + //return; + } + } - var url = SelectedSource.Url; - if (url.IsNotEmpty()) - { - var uri = Utils.TryUri(url); - if (uri == null) - { - NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip); - return; - } - //Do not allow http protocol - if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) - { - NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); - //return; - } - } - - if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0) - { - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _updateView?.Invoke(EViewAction.CloseWindow, null); - } - else - { - NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); - } + if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0) + { + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _updateView?.Invoke(EViewAction.CloseWindow, null); + } + else + { + NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } } diff --git a/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs index 8b8f964b..71cf1c61 100644 --- a/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs @@ -4,102 +4,101 @@ using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; -namespace ServiceLib.ViewModels +namespace ServiceLib.ViewModels; + +public class SubSettingViewModel : MyReactiveObject { - public class SubSettingViewModel : MyReactiveObject + private IObservableCollection _subItems = new ObservableCollectionExtended(); + public IObservableCollection SubItems => _subItems; + + [Reactive] + public SubItem SelectedSource { get; set; } + + public IList SelectedSources { get; set; } + + public ReactiveCommand SubAddCmd { get; } + public ReactiveCommand SubDeleteCmd { get; } + public ReactiveCommand SubEditCmd { get; } + public ReactiveCommand SubShareCmd { get; } + public bool IsModified { get; set; } + + public SubSettingViewModel(Func>? updateView) { - private IObservableCollection _subItems = new ObservableCollectionExtended(); - public IObservableCollection SubItems => _subItems; + _config = AppHandler.Instance.Config; + _updateView = updateView; - [Reactive] - public SubItem SelectedSource { get; set; } + var canEditRemove = this.WhenAnyValue( + x => x.SelectedSource, + selectedSource => selectedSource != null && !selectedSource.Id.IsNullOrEmpty()); - public IList SelectedSources { get; set; } - - public ReactiveCommand SubAddCmd { get; } - public ReactiveCommand SubDeleteCmd { get; } - public ReactiveCommand SubEditCmd { get; } - public ReactiveCommand SubShareCmd { get; } - public bool IsModified { get; set; } - - public SubSettingViewModel(Func>? updateView) + SubAddCmd = ReactiveCommand.CreateFromTask(async () => { - _config = AppHandler.Instance.Config; - _updateView = updateView; + await EditSubAsync(true); + }); + SubDeleteCmd = ReactiveCommand.CreateFromTask(async () => + { + await DeleteSubAsync(); + }, canEditRemove); + SubEditCmd = ReactiveCommand.CreateFromTask(async () => + { + await EditSubAsync(false); + }, canEditRemove); + SubShareCmd = ReactiveCommand.CreateFromTask(async () => + { + await _updateView?.Invoke(EViewAction.ShareSub, SelectedSource?.Url); + }, canEditRemove); - var canEditRemove = this.WhenAnyValue( - x => x.SelectedSource, - selectedSource => selectedSource != null && !selectedSource.Id.IsNullOrEmpty()); + _ = Init(); + } - SubAddCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditSubAsync(true); - }); - SubDeleteCmd = ReactiveCommand.CreateFromTask(async () => - { - await DeleteSubAsync(); - }, canEditRemove); - SubEditCmd = ReactiveCommand.CreateFromTask(async () => - { - await EditSubAsync(false); - }, canEditRemove); - SubShareCmd = ReactiveCommand.CreateFromTask(async () => - { - await _updateView?.Invoke(EViewAction.ShareSub, SelectedSource?.Url); - }, canEditRemove); + private async Task Init() + { + SelectedSource = new(); - _ = Init(); + await RefreshSubItems(); + } + + public async Task RefreshSubItems() + { + _subItems.Clear(); + _subItems.AddRange(await AppHandler.Instance.SubItems()); + } + + public async Task EditSubAsync(bool blNew) + { + SubItem item; + if (blNew) + { + item = new(); } - - private async Task Init() + else { - SelectedSource = new(); - - await RefreshSubItems(); - } - - public async Task RefreshSubItems() - { - _subItems.Clear(); - _subItems.AddRange(await AppHandler.Instance.SubItems()); - } - - public async Task EditSubAsync(bool blNew) - { - SubItem item; - if (blNew) - { - item = new(); - } - else - { - item = await AppHandler.Instance.GetSubItem(SelectedSource?.Id); - if (item is null) - { - return; - } - } - if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) - { - await RefreshSubItems(); - IsModified = true; - } - } - - private async Task DeleteSubAsync() - { - if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + item = await AppHandler.Instance.GetSubItem(SelectedSource?.Id); + if (item is null) { return; } - - foreach (var it in SelectedSources ?? [SelectedSource]) - { - await ConfigHandler.DeleteSubItem(_config, it.Id); - } + } + if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) + { await RefreshSubItems(); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); IsModified = true; } } + + private async Task DeleteSubAsync() + { + if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) + { + return; + } + + foreach (var it in SelectedSources ?? [SelectedSource]) + { + await ConfigHandler.DeleteSubItem(_config, it.Id); + } + await RefreshSubItems(); + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + IsModified = true; + } } diff --git a/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs b/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs index 36669ad0..3c5169cc 100644 --- a/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs +++ b/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs @@ -1,18 +1,17 @@ using Avalonia; using Avalonia.Media; -namespace v2rayN.Desktop.Common +namespace v2rayN.Desktop.Common; + +public static class AppBuilderExtension { - public static class AppBuilderExtension + public static AppBuilder WithFontByDefault(this AppBuilder appBuilder) { - public static AppBuilder WithFontByDefault(this AppBuilder appBuilder) + var uri = Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"); + return appBuilder.With(new FontManagerOptions() { - var uri = Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"); - return appBuilder.With(new FontManagerOptions() - { - //DefaultFamilyName = uri, - FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily(uri) } } - }); - } + //DefaultFamilyName = uri, + FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily(uri) } } + }); } } diff --git a/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs b/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs index 5b56f277..87c974d4 100644 --- a/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs +++ b/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs @@ -4,56 +4,55 @@ using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Platform; -namespace v2rayN.Desktop.Common -{ - internal class AvaUtils - { - public static async Task GetClipboardData(Window owner) - { - try - { - var clipboard = TopLevel.GetTopLevel(owner)?.Clipboard; - if (clipboard == null) - { - return null; - } +namespace v2rayN.Desktop.Common; - return await clipboard.GetTextAsync(); - } - catch +internal class AvaUtils +{ + public static async Task GetClipboardData(Window owner) + { + try + { + var clipboard = TopLevel.GetTopLevel(owner)?.Clipboard; + if (clipboard == null) { return null; } + + return await clipboard.GetTextAsync(); } - - public static async Task SetClipboardData(Visual? visual, string strData) + catch { - try - { - var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard; - if (clipboard == null) - return; - var dataObject = new DataObject(); - dataObject.Set(DataFormats.Text, strData); - await clipboard.SetDataObjectAsync(dataObject); - } - catch - { - } - } - - public static WindowIcon GetAppIcon(ESysProxyType sysProxyType) - { - var index = (int)sysProxyType + 1; - var fileName = Utils.GetPath($"NotifyIcon{index}.ico"); - if (File.Exists(fileName)) - { - return new(fileName); - } - - var uri = new Uri(Path.Combine(Global.AvaAssets, $"NotifyIcon{index}.ico")); - using var bitmap = new Bitmap(AssetLoader.Open(uri)); - return new(bitmap); + return null; } } + + public static async Task SetClipboardData(Visual? visual, string strData) + { + try + { + var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard; + if (clipboard == null) + return; + var dataObject = new DataObject(); + dataObject.Set(DataFormats.Text, strData); + await clipboard.SetDataObjectAsync(dataObject); + } + catch + { + } + } + + public static WindowIcon GetAppIcon(ESysProxyType sysProxyType) + { + var index = (int)sysProxyType + 1; + var fileName = Utils.GetPath($"NotifyIcon{index}.ico"); + if (File.Exists(fileName)) + { + return new(fileName); + } + + var uri = new Uri(Path.Combine(Global.AvaAssets, $"NotifyIcon{index}.ico")); + using var bitmap = new Bitmap(AssetLoader.Open(uri)); + return new(bitmap); + } } diff --git a/v2rayN/v2rayN.Desktop/Common/UI.cs b/v2rayN/v2rayN.Desktop/Common/UI.cs index e52970e4..e9ddc36f 100644 --- a/v2rayN/v2rayN.Desktop/Common/UI.cs +++ b/v2rayN/v2rayN.Desktop/Common/UI.cs @@ -3,56 +3,55 @@ using Avalonia.Platform.Storage; using MsBox.Avalonia; using MsBox.Avalonia.Enums; -namespace v2rayN.Desktop.Common +namespace v2rayN.Desktop.Common; + +internal class UI { - internal class UI + private static readonly string caption = Global.AppName; + + public static async Task ShowYesNo(Window owner, string msg) { - private static readonly string caption = Global.AppName; + var box = MessageBoxManager.GetMessageBoxStandard(caption, msg, ButtonEnum.YesNo); + return await box.ShowWindowDialogAsync(owner); + } - public static async Task ShowYesNo(Window owner, string msg) + public static async Task OpenFileDialog(Window owner, FilePickerFileType? filter) + { + var sp = GetStorageProvider(owner); + if (sp is null) { - var box = MessageBoxManager.GetMessageBoxStandard(caption, msg, ButtonEnum.YesNo); - return await box.ShowWindowDialogAsync(owner); + return null; } - public static async Task OpenFileDialog(Window owner, FilePickerFileType? filter) + // Start async operation to open the dialog. + var files = await sp.OpenFilePickerAsync(new FilePickerOpenOptions { - var sp = GetStorageProvider(owner); - if (sp is null) - { - return null; - } + AllowMultiple = false, + FileTypeFilter = filter is null ? [FilePickerFileTypes.All, FilePickerFileTypes.ImagePng] : [filter] + }); - // Start async operation to open the dialog. - var files = await sp.OpenFilePickerAsync(new FilePickerOpenOptions - { - AllowMultiple = false, - FileTypeFilter = filter is null ? [FilePickerFileTypes.All, FilePickerFileTypes.ImagePng] : [filter] - }); + return files.FirstOrDefault()?.TryGetLocalPath(); + } - return files.FirstOrDefault()?.TryGetLocalPath(); + public static async Task SaveFileDialog(Window owner, string filter) + { + var sp = GetStorageProvider(owner); + if (sp is null) + { + return null; } - public static async Task SaveFileDialog(Window owner, string filter) + // Start async operation to open the dialog. + var files = await sp.SaveFilePickerAsync(new FilePickerSaveOptions { - var sp = GetStorageProvider(owner); - if (sp is null) - { - return null; - } + }); - // Start async operation to open the dialog. - var files = await sp.SaveFilePickerAsync(new FilePickerSaveOptions - { - }); + return files?.TryGetLocalPath(); + } - return files?.TryGetLocalPath(); - } - - private static IStorageProvider? GetStorageProvider(Window owner) - { - var topLevel = TopLevel.GetTopLevel(owner); - return topLevel?.StorageProvider; - } + private static IStorageProvider? GetStorageProvider(Window owner) + { + var topLevel = TopLevel.GetTopLevel(owner); + return topLevel?.StorageProvider; } } diff --git a/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs b/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs index 1f3f4a2b..05364578 100644 --- a/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs +++ b/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs @@ -2,25 +2,24 @@ using System.Globalization; using Avalonia.Data.Converters; using Avalonia.Media; -namespace v2rayN.Desktop.Converters +namespace v2rayN.Desktop.Converters; + +public class DelayColorConverter : IValueConverter { - public class DelayColorConverter : IValueConverter + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - _ = int.TryParse(value?.ToString(), out var delay); + _ = int.TryParse(value?.ToString(), out var delay); - return delay switch - { - <= 0 => new SolidColorBrush(Colors.Red), - <= 500 => new SolidColorBrush(Colors.Green), - _ => new SolidColorBrush(Colors.IndianRed) - }; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + return delay switch { - return null; - } + <= 0 => new SolidColorBrush(Colors.Red), + <= 500 => new SolidColorBrush(Colors.Green), + _ => new SolidColorBrush(Colors.IndianRed) + }; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; } } diff --git a/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs b/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs index 89724337..a8d257c8 100644 --- a/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs +++ b/v2rayN/v2rayN.Desktop/Handler/HotkeyHandler.cs @@ -4,88 +4,87 @@ using Avalonia.ReactiveUI; using Avalonia.Win32.Input; using GlobalHotKeys; -namespace v2rayN.Desktop.Handler +namespace v2rayN.Desktop.Handler; + +public sealed class HotkeyHandler { - public sealed class HotkeyHandler + private static readonly Lazy _instance = new(() => new()); + public static HotkeyHandler Instance = _instance.Value; + private readonly Dictionary _hotkeyTriggerDic = new(); + private HotKeyManager? _hotKeyManager; + + private Config? _config; + + private event Action? _updateFunc; + + public bool IsPause { get; set; } = false; + + public void Init(Config config, Action updateFunc) { - private static readonly Lazy _instance = new(() => new()); - public static HotkeyHandler Instance = _instance.Value; - private readonly Dictionary _hotkeyTriggerDic = new(); - private HotKeyManager? _hotKeyManager; + _config = config; + _updateFunc = updateFunc; - private Config? _config; + Register(); + } - private event Action? _updateFunc; + public void Dispose() + { + _hotKeyManager?.Dispose(); + } - public bool IsPause { get; set; } = false; - - public void Init(Config config, Action updateFunc) + private void Register() + { + if (_config.GlobalHotkeys.Any(t => t.KeyCode > 0) == false) { - _config = config; - _updateFunc = updateFunc; + return; + } + _hotKeyManager ??= new GlobalHotKeys.HotKeyManager(); + _hotkeyTriggerDic.Clear(); - Register(); + foreach (var item in _config.GlobalHotkeys) + { + if (item.KeyCode is null or 0) + { + continue; + } + + var vKey = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode); + var modifiers = Modifiers.None; + if (item.Control) + { + modifiers |= Modifiers.Control; + } + if (item.Shift) + { + modifiers |= Modifiers.Shift; + } + if (item.Alt) + { + modifiers |= Modifiers.Alt; + } + + var result = _hotKeyManager?.Register((VirtualKeyCode)vKey, modifiers); + if (result?.IsSuccessful == true) + { + _hotkeyTriggerDic.Add(result.Id, item.EGlobalHotkey); + } } - public void Dispose() + _hotKeyManager?.HotKeyPressed + .ObserveOn(AvaloniaScheduler.Instance) + .Subscribe(OnNext); + } + + private void OnNext(HotKey key) + { + if (_updateFunc == null || IsPause) { - _hotKeyManager?.Dispose(); + return; } - private void Register() + if (_hotkeyTriggerDic.TryGetValue(key.Id, out var value)) { - if (_config.GlobalHotkeys.Any(t => t.KeyCode > 0) == false) - { - return; - } - _hotKeyManager ??= new GlobalHotKeys.HotKeyManager(); - _hotkeyTriggerDic.Clear(); - - foreach (var item in _config.GlobalHotkeys) - { - if (item.KeyCode is null or 0) - { - continue; - } - - var vKey = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode); - var modifiers = Modifiers.None; - if (item.Control) - { - modifiers |= Modifiers.Control; - } - if (item.Shift) - { - modifiers |= Modifiers.Shift; - } - if (item.Alt) - { - modifiers |= Modifiers.Alt; - } - - var result = _hotKeyManager?.Register((VirtualKeyCode)vKey, modifiers); - if (result?.IsSuccessful == true) - { - _hotkeyTriggerDic.Add(result.Id, item.EGlobalHotkey); - } - } - - _hotKeyManager?.HotKeyPressed - .ObserveOn(AvaloniaScheduler.Instance) - .Subscribe(OnNext); - } - - private void OnNext(HotKey key) - { - if (_updateFunc == null || IsPause) - { - return; - } - - if (_hotkeyTriggerDic.TryGetValue(key.Id, out var value)) - { - _updateFunc?.Invoke(value); - } + _updateFunc?.Invoke(value); } } } diff --git a/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs index 455414b8..ea1a3b66 100644 --- a/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs +++ b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs @@ -9,100 +9,139 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Semi.Avalonia; -namespace v2rayN.Desktop.ViewModels +namespace v2rayN.Desktop.ViewModels; + +public class ThemeSettingViewModel : MyReactiveObject { - public class ThemeSettingViewModel : MyReactiveObject + [Reactive] public string CurrentTheme { get; set; } + + [Reactive] public int CurrentFontSize { get; set; } + + [Reactive] public string CurrentLanguage { get; set; } + + public ThemeSettingViewModel() { - [Reactive] public string CurrentTheme { get; set; } + _config = AppHandler.Instance.Config; - [Reactive] public int CurrentFontSize { get; set; } + BindingUI(); + RestoreUI(); + } - [Reactive] public string CurrentLanguage { get; set; } + private void RestoreUI() + { + ModifyTheme(); + ModifyFontFamily(); + ModifyFontSize(); + } - public ThemeSettingViewModel() - { - _config = AppHandler.Instance.Config; + private void BindingUI() + { + CurrentTheme = _config.UiItem.CurrentTheme; + CurrentFontSize = _config.UiItem.CurrentFontSize; + CurrentLanguage = _config.UiItem.CurrentLanguage; - BindingUI(); - RestoreUI(); - } - - private void RestoreUI() - { - ModifyTheme(); - ModifyFontFamily(); - ModifyFontSize(); - } - - private void BindingUI() - { - CurrentTheme = _config.UiItem.CurrentTheme; - CurrentFontSize = _config.UiItem.CurrentFontSize; - CurrentLanguage = _config.UiItem.CurrentLanguage; - - this.WhenAnyValue(x => x.CurrentTheme) - .Subscribe(c => - { - if (_config.UiItem.CurrentTheme != CurrentTheme) - { - _config.UiItem.CurrentTheme = CurrentTheme; - ModifyTheme(); - ConfigHandler.SaveConfig(_config); - } - }); - - this.WhenAnyValue( - x => x.CurrentFontSize, - y => y > 0) - .Subscribe(c => - { - if (_config.UiItem.CurrentFontSize != CurrentFontSize && CurrentFontSize >= Global.MinFontSize) - { - _config.UiItem.CurrentFontSize = CurrentFontSize; - ModifyFontSize(); - ConfigHandler.SaveConfig(_config); - } - }); - - this.WhenAnyValue( - x => x.CurrentLanguage, - y => y != null && !y.IsNullOrEmpty()) - .Subscribe(c => - { - if (CurrentLanguage.IsNotEmpty() && _config.UiItem.CurrentLanguage != CurrentLanguage) - { - _config.UiItem.CurrentLanguage = CurrentLanguage; - Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); - ConfigHandler.SaveConfig(_config); - NoticeHandler.Instance.Enqueue(ResUI.NeedRebootTips); - } - }); - } - - private void ModifyTheme() - { - var app = Application.Current; - if (app is not null) + this.WhenAnyValue(x => x.CurrentTheme) + .Subscribe(c => { - app.RequestedThemeVariant = CurrentTheme switch + if (_config.UiItem.CurrentTheme != CurrentTheme) { - nameof(ETheme.Dark) => ThemeVariant.Dark, - nameof(ETheme.Light) => ThemeVariant.Light, - nameof(ETheme.Aquatic) => SemiTheme.Aquatic, - nameof(ETheme.Desert) => SemiTheme.Desert, - nameof(ETheme.Dusk) => SemiTheme.Dusk, - nameof(ETheme.NightSky) => SemiTheme.NightSky, - _ => ThemeVariant.Default, - }; - } + _config.UiItem.CurrentTheme = CurrentTheme; + ModifyTheme(); + ConfigHandler.SaveConfig(_config); + } + }); + + this.WhenAnyValue( + x => x.CurrentFontSize, + y => y > 0) + .Subscribe(c => + { + if (_config.UiItem.CurrentFontSize != CurrentFontSize && CurrentFontSize >= Global.MinFontSize) + { + _config.UiItem.CurrentFontSize = CurrentFontSize; + ModifyFontSize(); + ConfigHandler.SaveConfig(_config); + } + }); + + this.WhenAnyValue( + x => x.CurrentLanguage, + y => y != null && !y.IsNullOrEmpty()) + .Subscribe(c => + { + if (CurrentLanguage.IsNotEmpty() && _config.UiItem.CurrentLanguage != CurrentLanguage) + { + _config.UiItem.CurrentLanguage = CurrentLanguage; + Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); + ConfigHandler.SaveConfig(_config); + NoticeHandler.Instance.Enqueue(ResUI.NeedRebootTips); + } + }); + } + + private void ModifyTheme() + { + var app = Application.Current; + if (app is not null) + { + app.RequestedThemeVariant = CurrentTheme switch + { + nameof(ETheme.Dark) => ThemeVariant.Dark, + nameof(ETheme.Light) => ThemeVariant.Light, + nameof(ETheme.Aquatic) => SemiTheme.Aquatic, + nameof(ETheme.Desert) => SemiTheme.Desert, + nameof(ETheme.Dusk) => SemiTheme.Dusk, + nameof(ETheme.NightSky) => SemiTheme.NightSky, + _ => ThemeVariant.Default, + }; + } + } + + private void ModifyFontSize() + { + double size = CurrentFontSize; + if (size < Global.MinFontSize) + return; + + Style style = new(x => Selectors.Or( + x.OfType