Add functionality multi-server set as active

Implemented using sing-box Selector, which can be switched using the clash api
pull/5377/head
2dust 2024-07-14 16:13:28 +08:00
parent 7c33c1c322
commit c79e2e3ad4
13 changed files with 218 additions and 13 deletions

View File

@ -30,6 +30,7 @@ namespace v2rayN
public const string CoreConfigFileName = "config.json"; public const string CoreConfigFileName = "config.json";
public const string CorePreConfigFileName = "configPre.json"; public const string CorePreConfigFileName = "configPre.json";
public const string CoreSpeedtestConfigFileName = "configSpeedtest.json"; public const string CoreSpeedtestConfigFileName = "configSpeedtest.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string V2raySampleClient = "v2rayN.Sample.SampleClientConfig"; public const string V2raySampleClient = "v2rayN.Sample.SampleClientConfig";
public const string SingboxSampleClient = "v2rayN.Sample.SingboxSampleClientConfig"; public const string SingboxSampleClient = "v2rayN.Sample.SingboxSampleClientConfig";

View File

@ -3,6 +3,7 @@ using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web; using System.Web;
using v2rayN.Enums; using v2rayN.Enums;
using v2rayN.Handler.CoreConfig;
using v2rayN.Handler.Fmt; using v2rayN.Handler.Fmt;
using v2rayN.Models; using v2rayN.Models;
@ -1065,6 +1066,33 @@ namespace v2rayN.Handler
return 0; return 0;
} }
public static int AddCustomServer4Multiple(Config config, List<ProfileItem> 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 #endregion Server
#region Batch add servers #region Batch add servers

View File

@ -144,5 +144,16 @@ namespace v2rayN.Handler.CoreConfig
} }
return 0; return 0;
} }
public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> 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;
}
} }
} }

View File

@ -804,7 +804,7 @@ namespace v2rayN.Handler.CoreConfig
return true; return true;
} }
private int GenDns(ProfileItem node, SingboxConfig singboxConfig) private int GenDns(ProfileItem? node, SingboxConfig singboxConfig)
{ {
try try
{ {
@ -853,6 +853,7 @@ namespace v2rayN.Handler.CoreConfig
var lstDomain = singboxConfig.outbounds var lstDomain = singboxConfig.outbounds
.Where(t => !Utils.IsNullOrEmpty(t.server) && Utils.IsDomain(t.server)) .Where(t => !Utils.IsNullOrEmpty(t.server) && Utils.IsDomain(t.server))
.Select(t => t.server) .Select(t => t.server)
.Distinct()
.ToList(); .ToList();
if (lstDomain != null && lstDomain.Count > 0) if (lstDomain != null && lstDomain.Count > 0)
{ {
@ -1169,5 +1170,123 @@ namespace v2rayN.Handler.CoreConfig
} }
#endregion Gen speedtest config #endregion Gen speedtest config
#region Gen Multiple Load config
public int GenerateClientMultipleLoadConfig(List<ProfileItem> 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<SingboxConfig>(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<string>();
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<Outbound4Sbox>(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
} }
} }

View File

@ -27,9 +27,8 @@ namespace v2rayN.Handler
Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
} }
public void LoadCore() public void LoadCore(ProfileItem? node) {
{
var node = ConfigHandler.GetDefaultServer(_config);
if (node == null) if (node == null)
{ {
ShowMsg(false, ResUI.CheckServerSettings); ShowMsg(false, ResUI.CheckServerSettings);

View File

@ -130,6 +130,8 @@
public Multiplex4Sbox? multiplex { get; set; } public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; } public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; } public HyObfs4Sbox? obfs { get; set; }
public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; }
} }
public class Tls4Sbox public class Tls4Sbox

View File

@ -1078,7 +1078,7 @@ namespace v2rayN.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Refresh Proxies (F5) 的本地化字符串。 /// 查找类似 Refresh Proxies 的本地化字符串。
/// </summary> /// </summary>
public static string menuProxiesReload { public static string menuProxiesReload {
get { get {
@ -1302,6 +1302,15 @@ namespace v2rayN.Resx {
} }
} }
/// <summary>
/// 查找类似 Multi-server set to active 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServer {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Set as active server (Enter) 的本地化字符串。 /// 查找类似 Set as active server (Enter) 的本地化字符串。
/// </summary> /// </summary>

View File

@ -1066,9 +1066,6 @@
<data name="menuProxiesDelaytestPart" xml:space="preserve"> <data name="menuProxiesDelaytestPart" xml:space="preserve">
<value>Part Node Latency Test</value> <value>Part Node Latency Test</value>
</data> </data>
<data name="menuProxiesReload" xml:space="preserve">
<value>Refresh Proxies (F5)</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve"> <data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Select active node (Enter)</value> <value>Select active node (Enter)</value>
</data> </data>

View File

@ -1292,7 +1292,7 @@
<value>Part Node Latency Test</value> <value>Part Node Latency Test</value>
</data> </data>
<data name="menuProxiesReload" xml:space="preserve"> <data name="menuProxiesReload" xml:space="preserve">
<value>Refresh Proxies (F5)</value> <value>Refresh Proxies</value>
</data> </data>
<data name="menuProxiesSelectActivity" xml:space="preserve"> <data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Select active node (Enter)</value> <value>Select active node (Enter)</value>
@ -1300,4 +1300,7 @@
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve"> <data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Default domain strategy for outbound</value> <value>Default domain strategy for outbound</value>
</data> </data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Multi-server set to active</value>
</data>
</root> </root>

View File

@ -1289,7 +1289,7 @@
<value>当前部分节点延迟测试</value> <value>当前部分节点延迟测试</value>
</data> </data>
<data name="menuProxiesReload" xml:space="preserve"> <data name="menuProxiesReload" xml:space="preserve">
<value>刷新 (F5)</value> <value>刷新</value>
</data> </data>
<data name="menuProxiesSelectActivity" xml:space="preserve"> <data name="menuProxiesSelectActivity" xml:space="preserve">
<value>设为活动节点 (Enter)</value> <value>设为活动节点 (Enter)</value>
@ -1297,4 +1297,7 @@
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve"> <data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound默认解析策略</value> <value>Outbound默认解析策略</value>
</data> </data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多服务器设为活动(多选)</value>
</data>
</root> </root>

View File

@ -106,6 +106,7 @@ namespace v2rayN.ViewModels
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; } public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; } public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; } public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerCmd { get; }
//servers move //servers move
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; } public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
@ -398,6 +399,10 @@ namespace v2rayN.ViewModels
{ {
ShareServer(); ShareServer();
}, canEditRemove); }, canEditRemove);
SetDefaultMultipleServerCmd = ReactiveCommand.Create(() =>
{
SetDefaultMultipleServer();
}, canEditRemove);
//servers move //servers move
MoveTopCmd = ReactiveCommand.Create(() => MoveTopCmd = ReactiveCommand.Create(() =>
{ {
@ -1166,6 +1171,28 @@ namespace v2rayN.ViewModels
await DialogHost.Show(dialog, "RootDialog"); await DialogHost.Show(dialog, "RootDialog");
} }
private void SetDefaultMultipleServer()
{
if (GetProfileItems(out List<ProfileItem> 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) public void SortServer(string colName)
{ {
if (Utils.IsNullOrEmpty(colName)) if (Utils.IsNullOrEmpty(colName))
@ -1519,7 +1546,7 @@ namespace v2rayN.ViewModels
Application.Current?.Dispatcher.Invoke((Action)(() => Application.Current?.Dispatcher.Invoke((Action)(() =>
{ {
BlReloadEnabled = true; 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) if (ShowCalshUI)
{ {
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload(); Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
@ -1532,7 +1559,8 @@ namespace v2rayN.ViewModels
{ {
await Task.Run(() => await Task.Run(() =>
{ {
_coreHandler.LoadCore(); var node = ConfigHandler.GetDefaultServer(_config);
_coreHandler.LoadCore(node);
//ConfigHandler.SaveConfig(_config, false); //ConfigHandler.SaveConfig(_config, false);

View File

@ -581,6 +581,10 @@
x:Name="menuShareServer" x:Name="menuShareServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuShareServer}" /> Header="{x:Static resx:ResUI.menuShareServer}" />
<MenuItem
x:Name="menuSetDefaultMultipleServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}" />
<Separator /> <Separator />
<MenuItem <MenuItem
x:Name="menuMixedTestServer" x:Name="menuMixedTestServer"

View File

@ -104,7 +104,8 @@ namespace v2rayN.Views
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).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.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).DisposeWith(disposables);
//servers move //servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);