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 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";

View File

@ -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<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
#region Batch add servers

View File

@ -144,5 +144,16 @@ namespace v2rayN.Handler.CoreConfig
}
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;
}
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<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);
}
public void LoadCore()
{
var node = ConfigHandler.GetDefaultServer(_config);
public void LoadCore(ProfileItem? node) {
if (node == null)
{
ShowMsg(false, ResUI.CheckServerSettings);

View File

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

View File

@ -1078,7 +1078,7 @@ namespace v2rayN.Resx {
}
/// <summary>
/// 查找类似 Refresh Proxies (F5) 的本地化字符串。
/// 查找类似 Refresh Proxies 的本地化字符串。
/// </summary>
public static string menuProxiesReload {
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>
/// 查找类似 Set as active server (Enter) 的本地化字符串。
/// </summary>

View File

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

View File

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

View File

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

View File

@ -106,6 +106,7 @@ namespace v2rayN.ViewModels
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerCmd { get; }
//servers move
public ReactiveCommand<Unit, Unit> 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<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)
{
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<ClashProxiesViewModel>()?.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);

View File

@ -581,6 +581,10 @@
x:Name="menuShareServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuShareServer}" />
<MenuItem
x:Name="menuSetDefaultMultipleServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}" />
<Separator />
<MenuItem
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.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);