From 7b5686cd8f56f07e96250fdec03bfa5f35acbc51 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sat, 1 Nov 2025 21:13:39 +0800 Subject: [PATCH] In the policy group, automatically add filtered configurations from the subscription group. https://github.com/2dust/v2rayN/issues/8214 --- .../Manager/ActionPrecheckManager.cs | 8 +++- .../Manager/ProfileGroupItemManager.cs | 34 +++++++++++++-- v2rayN/ServiceLib/Models/ProfileGroupItem.cs | 9 ++++ v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 11 ++++- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.fr.resx | 5 ++- v2rayN/ServiceLib/Resx/ResUI.hu.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 3 ++ .../ViewModels/AddGroupServerViewModel.cs | 32 +++++++++++---- .../Views/AddGroupServerWindow.axaml | 36 +++++++++++++++- .../Views/AddGroupServerWindow.axaml.cs | 3 ++ v2rayN/v2rayN/Views/AddGroupServerWindow.xaml | 41 +++++++++++++++++++ .../v2rayN/Views/AddGroupServerWindow.xaml.cs | 3 ++ 16 files changed, 184 insertions(+), 16 deletions(-) diff --git a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs index a487401c..f4f998b3 100644 --- a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs +++ b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs @@ -115,7 +115,7 @@ public class ActionPrecheckManager(Config config) if (item.ConfigType.IsGroupType()) { ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); - if (group is null || group.ChildItems.IsNullOrEmpty()) + if (group is null || group.NotHasChild()) { errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); return errors; @@ -128,7 +128,11 @@ public class ActionPrecheckManager(Config config) return errors; } - foreach (var child in Utils.String2List(group.ChildItems)) + var childIds = Utils.String2List(group.ChildItems) ?? []; + var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group); + childIds.AddRange(subItems.Select(p => p.IndexId)); + + foreach (var child in childIds) { var childErrors = new List(); if (child.IsNullOrEmpty()) diff --git a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs index bf52dcb2..6cd260d3 100644 --- a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs +++ b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs @@ -220,11 +220,14 @@ public class ProfileGroupItemManager public static async Task<(List Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId) { Instance.TryGet(indexId, out var profileGroupItem); - if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty()) + if (profileGroupItem == null || profileGroupItem.NotHasChild()) { return (new List(), profileGroupItem); } var items = await GetChildProfileItems(profileGroupItem); + var subItems = await GetSubChildProfileItems(profileGroupItem); + items.AddRange(subItems); + return (items, profileGroupItem); } @@ -248,14 +251,39 @@ public class ProfileGroupItemManager return childProfiles; } + public static async Task> GetSubChildProfileItems(ProfileGroupItem? group) + { + if (group == null || group.SubChildItems.IsNullOrEmpty()) + { + return new(); + } + var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems); + + return childProfiles.Where(p => + p != null && + p.IsValid() && + !p.ConfigType.IsComplexType() && + (group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter)) + ) + .ToList(); + } + public static async Task> GetAllChildDomainAddresses(string indexId) { // include grand children var childAddresses = new HashSet(); - if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty()) + if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null) + { return childAddresses; + } - var childIds = Utils.String2List(groupItem.ChildItems); + if (groupItem.SubChildItems.IsNotEmpty()) + { + var subItems = await GetSubChildProfileItems(groupItem); + subItems.ForEach(p => childAddresses.Add(p.Address)); + } + + var childIds = Utils.String2List(groupItem.ChildItems) ?? []; foreach (var childId in childIds) { diff --git a/v2rayN/ServiceLib/Models/ProfileGroupItem.cs b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs index c6131275..12c0f899 100644 --- a/v2rayN/ServiceLib/Models/ProfileGroupItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs @@ -8,5 +8,14 @@ public class ProfileGroupItem public string ChildItems { get; set; } + public string? SubChildItems { get; set; } + + public string? Filter { get; set; } + public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing; + + public bool NotHasChild() + { + return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems); + } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 90b25530..90a44ae5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -19,7 +19,7 @@ namespace ServiceLib.Resx { // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class ResUI { @@ -2958,6 +2958,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。 + /// + public static string TbPolicyGroupSubChildTip { + get { + return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture); + } + } + /// /// 查找类似 Policy Group Type 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 08d4d111..edbd21da 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1599,4 +1599,7 @@ Test real delay + + Auto add filtered configuration from subscription groups + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index a56802e3..542d9ef9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1596,4 +1596,7 @@ Test 1-clic de latence réelle - + + Auto add filtered configuration from subscription groups + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index abb43c0a..e61f279e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1599,4 +1599,7 @@ Test real delay + + Auto add filtered configuration from subscription groups + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 9a1639ce..f1123407 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1599,4 +1599,7 @@ Test real delay + + Auto add filtered configuration from subscription groups + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 67067b9a..14edb9b8 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1599,4 +1599,7 @@ Test real delay + + Auto add filtered configuration from subscription groups + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 96c470c6..cfb89284 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1596,4 +1596,7 @@ 一键测试真连接延迟 + + 自动从订阅分组添加过滤后的配置 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index d03b0d20..4a3822b4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1596,4 +1596,7 @@ 一鍵測試真連線延遲 + + 自動從訂閱分組新增過濾後的配置 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index b5bfe80d..5b0778a5 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -17,6 +17,14 @@ public class AddGroupServerViewModel : MyReactiveObject [Reactive] public string? PolicyGroupType { get; set; } + [Reactive] + public SubItem? SelectedSubItem { get; set; } + + [Reactive] + public string? Filter { get; set; } + + public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); + public IObservableCollection ChildItemsObs { get; } = new ObservableCollectionExtended(); //public ReactiveCommand AddCmd { get; } @@ -64,10 +72,14 @@ public class AddGroupServerViewModel : MyReactiveObject }); SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); - CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString(); - ProfileGroupItemManager.Instance.TryGet(profileItem.IndexId, out var profileGroup); + _ = Init(); + } + + public async Task Init() + { + ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup); PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch { EMultipleLoad.LeastPing => ResUI.TbLeastPing, @@ -78,15 +90,16 @@ public class AddGroupServerViewModel : MyReactiveObject _ => ResUI.TbLeastPing, }; - _ = Init(); - } + var subs = await AppManager.Instance.SubItems(); + subs.Add(new SubItem()); + SubItems.AddRange(subs); + SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault(); + Filter = profileGroup?.Filter; - public async Task Init() - { var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId); if (childItemMulti != null) { - var childIndexIds = childItemMulti.ChildItems.IsNullOrEmpty() ? new List() : Utils.String2List(childItemMulti.ChildItems); + var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? []; foreach (var item in childIndexIds) { var child = await AppManager.Instance.GetProfileItem(item); @@ -181,7 +194,7 @@ public class AddGroupServerViewModel : MyReactiveObject NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } - if (ChildItemsObs.Count == 0) + if (ChildItemsObs.Count == 0 && SelectedSubItem?.Id.IsNullOrEmpty() == true) { NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer); return; @@ -213,6 +226,9 @@ public class AddGroupServerViewModel : MyReactiveObject _ => EMultipleLoad.LeastPing, }; + profileGroup.SubChildItems = SelectedSubItem?.Id; + profileGroup.Filter = Filter; + var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId); if (hasCycle) { diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml index e24fa67c..4279dc4a 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml @@ -38,7 +38,7 @@ + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs index a75f0e43..a5692ffc 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs @@ -46,6 +46,9 @@ public partial class AddGroupServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables); + //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml index b1867be8..b46d3dcd 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml @@ -59,6 +59,8 @@ + + @@ -130,6 +132,45 @@ materialDesign:HintAssist.Hint="{x:Static resx:ResUI.TbPolicyGroupType}" Style="{StaticResource DefComboBox}" /> + + + + + + + diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs index ad3c7ac6..e542c922 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs @@ -41,6 +41,9 @@ public partial class AddGroupServerWindow this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);