From c79e2e3ad4158561204dc57195f9b6cd72ab0b19 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:13:28 +0800 Subject: [PATCH] Add functionality multi-server set as active Implemented using sing-box Selector, which can be switched using the clash api --- v2rayN/v2rayN/Global.cs | 1 + v2rayN/v2rayN/Handler/ConfigHandler.cs | 28 ++++ .../Handler/CoreConfig/CoreConfigHandler.cs | 11 ++ .../Handler/CoreConfig/CoreConfigSingbox.cs | 121 +++++++++++++++++- v2rayN/v2rayN/Handler/CoreHandler.cs | 5 +- v2rayN/v2rayN/Models/SingboxConfig.cs | 2 + v2rayN/v2rayN/Resx/ResUI.Designer.cs | 11 +- v2rayN/v2rayN/Resx/ResUI.fa-Ir.resx | 3 - v2rayN/v2rayN/Resx/ResUI.resx | 5 +- v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx | 5 +- .../v2rayN/ViewModels/MainWindowViewModel.cs | 32 ++++- v2rayN/v2rayN/Views/MainWindow.xaml | 4 + v2rayN/v2rayN/Views/MainWindow.xaml.cs | 3 +- 13 files changed, 218 insertions(+), 13 deletions(-) diff --git a/v2rayN/v2rayN/Global.cs b/v2rayN/v2rayN/Global.cs index df2d3d98..09569b03 100644 --- a/v2rayN/v2rayN/Global.cs +++ b/v2rayN/v2rayN/Global.cs @@ -30,6 +30,7 @@ namespace v2rayN public const string CoreConfigFileName = "config.json"; public const string CorePreConfigFileName = "configPre.json"; public const string CoreSpeedtestConfigFileName = "configSpeedtest.json"; + public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string V2raySampleClient = "v2rayN.Sample.SampleClientConfig"; public const string SingboxSampleClient = "v2rayN.Sample.SingboxSampleClientConfig"; diff --git a/v2rayN/v2rayN/Handler/ConfigHandler.cs b/v2rayN/v2rayN/Handler/ConfigHandler.cs index 0f789d96..b87981ab 100644 --- a/v2rayN/v2rayN/Handler/ConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/ConfigHandler.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text.RegularExpressions; using System.Web; using v2rayN.Enums; +using v2rayN.Handler.CoreConfig; using v2rayN.Handler.Fmt; using v2rayN.Models; @@ -1065,6 +1066,33 @@ namespace v2rayN.Handler return 0; } + public static int AddCustomServer4Multiple(Config config, List selecteds, out string indexId) + { + indexId = Utils.GetMD5(Global.CoreMultipleLoadConfigFileName); + string configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName); + if (CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, out string msg) != 0) + { + return -1; + } + + var fileName = configPath; + if (!File.Exists(fileName)) + { + return -1; + } + + var profileItem = LazyConfig.Instance.GetProfileItem(config.indexId) ?? new(); + profileItem.indexId = indexId; + profileItem.remarks = "Multi-server Config"; + profileItem.address = Global.CoreMultipleLoadConfigFileName; + profileItem.configType = EConfigType.Custom; + profileItem.coreType = ECoreType.sing_box; + + AddServerCommon(config, profileItem, true); + + return 0; + } + #endregion Server #region Batch add servers diff --git a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs index 533b7a91..12d5b458 100644 --- a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs @@ -144,5 +144,16 @@ namespace v2rayN.Handler.CoreConfig } return 0; } + + public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List selecteds, out string msg) + { + if (new CoreConfigSingbox(config).GenerateClientMultipleLoadConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0) + { + return -1; + } + JsonUtils.ToFile(singboxConfig, fileName, false); + + return 0; + } } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigSingbox.cs b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigSingbox.cs index 9f629723..a391d878 100644 --- a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigSingbox.cs +++ b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigSingbox.cs @@ -804,7 +804,7 @@ namespace v2rayN.Handler.CoreConfig return true; } - private int GenDns(ProfileItem node, SingboxConfig singboxConfig) + private int GenDns(ProfileItem? node, SingboxConfig singboxConfig) { try { @@ -853,6 +853,7 @@ namespace v2rayN.Handler.CoreConfig var lstDomain = singboxConfig.outbounds .Where(t => !Utils.IsNullOrEmpty(t.server) && Utils.IsDomain(t.server)) .Select(t => t.server) + .Distinct() .ToList(); if (lstDomain != null && lstDomain.Count > 0) { @@ -1169,5 +1170,123 @@ namespace v2rayN.Handler.CoreConfig } #endregion Gen speedtest config + + #region Gen Multiple Load config + + public int GenerateClientMultipleLoadConfig(List selecteds, out SingboxConfig? singboxConfig, out string msg) + { + singboxConfig = null; + try + { + if (_config == null) + { + msg = ResUI.CheckServerSettings; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.SingboxSampleClient); + string txtOutbound = Utils.GetEmbedText(Global.SingboxSampleOutbound); + if (Utils.IsNullOrEmpty(result) || txtOutbound.IsNullOrEmpty()) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + GenLog(singboxConfig); + GenInbounds(singboxConfig); + GenRouting(singboxConfig); + 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 = LazyConfig.Instance.GetProfileItem(it.indexId); + if (item is null) + { + continue; + } + if (it.configType is EConfigType.VMess or EConfigType.VLESS) + { + if (Utils.IsNullOrEmpty(item.id) || !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); + GenOutbound(item, outbound); + outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; + singboxConfig.outbounds.Add(outbound); + tagProxy.Add(outbound.tag); + } + if (tagProxy.Count <= 0) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + GenDns(null, singboxConfig); + ConvertGeo2Ruleset(singboxConfig); + + //add urltest outbound + var outUrltest = new Outbound4Sbox + { + type = "urltest", + tag = $"{Global.ProxyTag}-auto", + outbounds = tagProxy, + interrupt_exist_connections = false, + }; + singboxConfig.outbounds.Add(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.Add(outSelector); + + return 0; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + } + + #endregion Gen Multiple config } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Handler/CoreHandler.cs b/v2rayN/v2rayN/Handler/CoreHandler.cs index 2c4b4ae7..45cd76f0 100644 --- a/v2rayN/v2rayN/Handler/CoreHandler.cs +++ b/v2rayN/v2rayN/Handler/CoreHandler.cs @@ -27,9 +27,8 @@ namespace v2rayN.Handler Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process); } - public void LoadCore() - { - var node = ConfigHandler.GetDefaultServer(_config); + public void LoadCore(ProfileItem? node) { + if (node == null) { ShowMsg(false, ResUI.CheckServerSettings); diff --git a/v2rayN/v2rayN/Models/SingboxConfig.cs b/v2rayN/v2rayN/Models/SingboxConfig.cs index e7b531ad..e712f74d 100644 --- a/v2rayN/v2rayN/Models/SingboxConfig.cs +++ b/v2rayN/v2rayN/Models/SingboxConfig.cs @@ -130,6 +130,8 @@ 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 diff --git a/v2rayN/v2rayN/Resx/ResUI.Designer.cs b/v2rayN/v2rayN/Resx/ResUI.Designer.cs index f0a21dcc..6fd8ee18 100644 --- a/v2rayN/v2rayN/Resx/ResUI.Designer.cs +++ b/v2rayN/v2rayN/Resx/ResUI.Designer.cs @@ -1078,7 +1078,7 @@ namespace v2rayN.Resx { } /// - /// 查找类似 Refresh Proxies (F5) 的本地化字符串。 + /// 查找类似 Refresh Proxies 的本地化字符串。 /// public static string menuProxiesReload { get { @@ -1302,6 +1302,15 @@ namespace v2rayN.Resx { } } + /// + /// 查找类似 Multi-server set to active 的本地化字符串。 + /// + public static string menuSetDefaultMultipleServer { + get { + return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture); + } + } + /// /// 查找类似 Set as active server (Enter) 的本地化字符串。 /// diff --git a/v2rayN/v2rayN/Resx/ResUI.fa-Ir.resx b/v2rayN/v2rayN/Resx/ResUI.fa-Ir.resx index dd9b5d9d..83fd500e 100644 --- a/v2rayN/v2rayN/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/v2rayN/Resx/ResUI.fa-Ir.resx @@ -1066,9 +1066,6 @@ Part Node Latency Test - - Refresh Proxies (F5) - Select active node (Enter) diff --git a/v2rayN/v2rayN/Resx/ResUI.resx b/v2rayN/v2rayN/Resx/ResUI.resx index 5c21c5a3..b83b6a7f 100644 --- a/v2rayN/v2rayN/Resx/ResUI.resx +++ b/v2rayN/v2rayN/Resx/ResUI.resx @@ -1292,7 +1292,7 @@ Part Node Latency Test - Refresh Proxies (F5) + Refresh Proxies Select active node (Enter) @@ -1300,4 +1300,7 @@ Default domain strategy for outbound + + Multi-server set to active + \ No newline at end of file diff --git a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx index 57a36652..e041b2ca 100644 --- a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx @@ -1289,7 +1289,7 @@ 当前部分节点延迟测试 - 刷新 (F5) + 刷新 设为活动节点 (Enter) @@ -1297,4 +1297,7 @@ Outbound默认解析策略 + + 多服务器设为活动(多选) + \ No newline at end of file diff --git a/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs b/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs index 8f3f8daa..7331d9cf 100644 --- a/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/v2rayN/ViewModels/MainWindowViewModel.cs @@ -106,6 +106,7 @@ namespace v2rayN.ViewModels public ReactiveCommand CopyServerCmd { get; } public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } + public ReactiveCommand SetDefaultMultipleServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -398,6 +399,10 @@ namespace v2rayN.ViewModels { ShareServer(); }, canEditRemove); + SetDefaultMultipleServerCmd = ReactiveCommand.Create(() => + { + SetDefaultMultipleServer(); + }, canEditRemove); //servers move MoveTopCmd = ReactiveCommand.Create(() => { @@ -1166,6 +1171,28 @@ namespace v2rayN.ViewModels await DialogHost.Show(dialog, "RootDialog"); } + private void SetDefaultMultipleServer() + { + if (GetProfileItems(out List lstSelecteds, true) < 0) + { + return; + } + + if (ConfigHandler.AddCustomServer4Multiple(_config, lstSelecteds, out string indexId) != 0) + { + _noticeHandler?.Enqueue(ResUI.OperationFailed); + return; + } + if (indexId == _config.indexId) + { + Reload(); + } + else + { + SetDefaultServer(indexId); + } + } + public void SortServer(string colName) { if (Utils.IsNullOrEmpty(colName)) @@ -1519,7 +1546,7 @@ namespace v2rayN.ViewModels Application.Current?.Dispatcher.Invoke((Action)(() => { BlReloadEnabled = true; - ShowCalshUI = (_config.runningCoreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo); + ShowCalshUI = (_config.runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo); if (ShowCalshUI) { Locator.Current.GetService()?.ProxiesReload(); @@ -1532,7 +1559,8 @@ namespace v2rayN.ViewModels { await Task.Run(() => { - _coreHandler.LoadCore(); + var node = ConfigHandler.GetDefaultServer(_config); + _coreHandler.LoadCore(node); //ConfigHandler.SaveConfig(_config, false); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 3c1df543..5a463f88 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -581,6 +581,10 @@ x:Name="menuShareServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuShareServer}" /> + vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); - + this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).DisposeWith(disposables); + //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);