Compare commits

..

24 commits

Author SHA1 Message Date
DHR60
ac7ace312e PreCheck 2025-09-13 11:10:24 +08:00
DHR60
8438f99b3b Refactor 2025-09-13 11:10:24 +08:00
DHR60
ccee805045 Avoid duplicate tags 2025-09-13 11:10:24 +08:00
DHR60
c53362f000 Add group in traffic splitting support 2025-09-13 11:10:24 +08:00
DHR60
952a347b9c Add PolicyGroup include other Group support 2025-09-13 11:10:24 +08:00
DHR60
71d9d7227b Add fallback support 2025-09-13 11:10:24 +08:00
DHR60
17e3a16dec Fix 2025-09-13 11:10:24 +08:00
DHR60
6e3b2afcc7 Add Proxy Chain support 2025-09-13 11:10:24 +08:00
DHR60
d8cf86e695 Adjust UI 2025-09-13 11:10:24 +08:00
DHR60
ee3575fa95 Add generate policy group 2025-09-13 11:10:24 +08:00
DHR60
cc95734f11 Add Policy Group support 2025-09-13 11:10:24 +08:00
DHR60
c786e30f89 Rename 2025-09-13 11:10:24 +08:00
DHR60
0bd4a3d48a Exclude specific profile types from selection 2025-09-13 11:10:23 +08:00
DHR60
879ac65caf Fix right click not working 2025-09-13 11:10:23 +08:00
DHR60
347b63def8 avalonia 2025-09-13 11:10:23 +08:00
DHR60
72d377ad2e VM and wpf 2025-09-13 11:10:23 +08:00
DHR60
85842dfb8c Multi Profile 2025-09-13 11:10:23 +08:00
DHR60
70fe733ea3 Add global fakeip and fakeip filter 2025-09-13 11:10:23 +08:00
2dust
ec627bdb82 up 7.14.10 2025-09-13 09:53:03 +08:00
2dust
4606e78570 Update Directory.Packages.props 2025-09-13 09:46:28 +08:00
2dust
f00e968b8f Bug fix
https://github.com/2dust/v2rayN/issues/7944
2025-09-13 09:41:34 +08:00
DHR60
a87a015c03
Fix some minor UI bugs (#7941)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-12 20:28:24 +08:00
2dust
c559914ff7 Fix
https://github.com/2dust/v2rayN/issues/7938
2025-09-12 17:01:53 +08:00
2dust
436d95576e Optimization and improvement JsonUtils 2025-09-12 16:45:55 +08:00
16 changed files with 147 additions and 33 deletions

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.14.9</Version> <Version>7.14.10</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -5,10 +5,10 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.5" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.5" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.5" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.5" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.6" />
<PackageVersion Include="CliWrap" Version="3.9.0" /> <PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="4.0.3" /> <PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />

View file

@ -9,6 +9,31 @@ public class JsonUtils
{ {
private static readonly string _tag = "JsonUtils"; private static readonly string _tag = "JsonUtils";
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip
};
/// <summary> /// <summary>
/// DeepCopy /// DeepCopy
/// </summary> /// </summary>
@ -34,11 +59,7 @@ public class JsonUtils
{ {
return default; return default;
} }
var options = new JsonSerializerOptions return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<T>(strJson, options);
} }
catch catch
{ {
@ -59,7 +80,7 @@ public class JsonUtils
{ {
return null; return null;
} }
return JsonNode.Parse(strJson); return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions);
} }
catch catch
{ {
@ -84,12 +105,7 @@ public class JsonUtils
{ {
return result; return result;
} }
var options = new JsonSerializerOptions var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
{
WriteIndented = indented,
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
result = JsonSerializer.Serialize(obj, options); result = JsonSerializer.Serialize(obj, options);
} }
catch (Exception ex) catch (Exception ex)

View file

@ -4,7 +4,7 @@ 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, "external-controller", "-port", "proxies"))
{ {
var fileName = WriteAllText(strData, "yaml"); var fileName = WriteAllText(strData, "yaml");

View file

@ -451,7 +451,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenOutboundsList(proxyProfiles, singboxConfig, multipleLoad); await GenOutboundsListWithChain(proxyProfiles, singboxConfig, multipleLoad);
await GenDns(null, singboxConfig); await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig); await ConvertGeo2Ruleset(singboxConfig);

View file

@ -179,13 +179,21 @@ public partial class CoreConfigSingboxService
if (node.ConfigType == EConfigType.WireGuard) if (node.ConfigType == EConfigType.WireGuard)
{ {
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound); var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
await GenEndpoint(node, endpoint); var ret = await GenEndpoint(node, endpoint);
if (ret != 0)
{
return null;
}
return endpoint; return endpoint;
} }
else else
{ {
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(node, outbound); var ret = await GenOutbound(node, outbound);
if (ret != 0)
{
return null;
}
return outbound; return outbound;
} }
} }
@ -410,7 +418,7 @@ public partial class CoreConfigSingboxService
return 0; return 0;
} }
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) private async Task<int> GenOutboundsListWithChain(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{ {
try try
{ {
@ -458,7 +466,7 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
@ -612,6 +620,66 @@ public partial class CoreConfigSingboxService
return null; return null;
} }
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{
var resultOutbounds = new List<Outbound4Sbox>();
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
var proxyTags = new List<string>(); // For selector and urltest outbounds
for (var i = 0; i < nodes.Count; i++)
{
var node = nodes[i];
var server = await GenServer(node);
if (server is null)
{
break;
}
server.tag = baseTagName + (i + 1).ToString();
if (server is Endpoints4Sbox endpoint)
{
resultEndpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
resultOutbounds.Add(outbound);
}
proxyTags.Add(server.tag);
}
// Add urltest outbound (auto selection based on latency)
if (proxyTags.Count > 0)
{
var outUrltest = new Outbound4Sbox
{
type = "urltest",
tag = $"{baseTagName}-auto",
outbounds = proxyTags,
interrupt_exist_connections = false,
};
if (multipleLoad == EMultipleLoad.Fallback)
{
outUrltest.tolerance = 5000;
}
// Add selector outbound (manual selection)
var outSelector = new Outbound4Sbox
{
type = "selector",
tag = baseTagName,
outbounds = JsonUtils.DeepCopy(proxyTags),
interrupt_exist_connections = false,
};
outSelector.outbounds.Insert(0, outUrltest.tag);
// Insert these at the beginning
resultOutbounds.Insert(0, outUrltest);
resultOutbounds.Insert(0, outSelector);
}
singboxConfig.outbounds ??= new();
resultOutbounds.AddRange(singboxConfig.outbounds);
singboxConfig.outbounds = resultOutbounds;
singboxConfig.endpoints ??= new();
resultEndpoints.AddRange(singboxConfig.endpoints);
singboxConfig.endpoints = resultEndpoints;
return await Task.FromResult(0);
}
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag) private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag)
{ {
var resultOutbounds = new List<Outbound4Sbox>(); var resultOutbounds = new List<Outbound4Sbox>();

View file

@ -397,7 +397,7 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()

View file

@ -180,7 +180,7 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenOutboundsList(proxyProfiles, v2rayConfig); await GenOutboundsListWithChain(proxyProfiles, v2rayConfig);
//add balancers //add balancers
await GenObservatory(v2rayConfig, multipleLoad); await GenObservatory(v2rayConfig, multipleLoad);

View file

@ -2,13 +2,13 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{ {
if (multipleLoad == EMultipleLoad.LeastPing) if (multipleLoad == EMultipleLoad.LeastPing)
{ {
var observatory = new Observatory4Ray var observatory = new Observatory4Ray
{ {
subjectSelector = [Global.ProxyTag], subjectSelector = [baseTagName],
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
probeInterval = "3m", probeInterval = "3m",
enableConcurrency = true, enableConcurrency = true,
@ -19,7 +19,7 @@ public partial class CoreConfigV2rayService
{ {
var burstObservatory = new BurstObservatory4Ray var burstObservatory = new BurstObservatory4Ray
{ {
subjectSelector = [Global.ProxyTag], subjectSelector = [baseTagName],
pingConfig = new() pingConfig = new()
{ {
destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,

View file

@ -552,7 +552,7 @@ public partial class CoreConfigV2rayService
return 0; return 0;
} }
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) private async Task<int> GenOutboundsListWithChain(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{ {
try try
{ {
@ -597,7 +597,7 @@ public partial class CoreConfigV2rayService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, v2rayConfig, childBaseTagName), await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
@ -721,6 +721,32 @@ public partial class CoreConfigV2rayService
return null; return null;
} }
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{
var resultOutbounds = new List<Outbounds4Ray>();
for (var i = 0; i < nodes.Count; i++)
{
var node = nodes[i];
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (txtOutbound.IsNullOrEmpty())
{
break;
}
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var result = await GenOutbound(node, outbound);
if (result != 0)
{
break;
}
outbound.tag = baseTagName + (i + 1).ToString();
resultOutbounds.Add(outbound);
}
v2rayConfig.outbounds ??= new();
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
return await Task.FromResult(0);
}
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2RayConfig, string baseTagName = Global.ProxyTag) private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2RayConfig, string baseTagName = Global.ProxyTag)
{ {
var resultOutbounds = new List<Outbounds4Ray>(); var resultOutbounds = new List<Outbounds4Ray>();

View file

@ -159,7 +159,7 @@ public partial class CoreConfigV2rayService
var ret = node.ConfigType switch var ret = node.ConfigType switch
{ {
EConfigType.PolicyGroup => EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, v2rayConfig, childBaseTagName), await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain => EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()

View file

@ -400,7 +400,7 @@
Grid.Column="1" Grid.Column="1"
Width="400" Width="400"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Watermark="1000:2000,3000:4000" /> Watermark="1000-2000,3000,4000" />
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="3"
Grid.Column="2" Grid.Column="2"

View file

@ -176,6 +176,7 @@
<DataGrid <DataGrid
x:Name="lstRules" x:Name="lstRules"
AutoGenerateColumns="False" AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1" BorderThickness="1"
CanUserResizeColumns="True" CanUserResizeColumns="True"
GridLinesVisibility="All" GridLinesVisibility="All"

View file

@ -92,6 +92,7 @@
<DataGrid <DataGrid
x:Name="lstRoutings" x:Name="lstRoutings"
AutoGenerateColumns="False" AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1" BorderThickness="1"
CanUserResizeColumns="True" CanUserResizeColumns="True"
GridLinesVisibility="All" GridLinesVisibility="All"

View file

@ -538,7 +538,7 @@
Width="400" Width="400"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="1000:2000,3000:4000" materialDesign:HintAssist.Hint="1000-2000,3000,4000"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}" />
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="3"

View file

@ -122,6 +122,8 @@ public partial class RoutingRuleSettingWindow
private void RoutingRuleSettingWindow_PreviewKeyDown(object sender, KeyEventArgs e) private void RoutingRuleSettingWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{ {
if (!lstRules.IsKeyboardFocusWithin)
return;
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{ {
if (e.Key == Key.A) if (e.Key == Key.A)