From 339919b0806106040499c00c1b27fe89614804f4 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 27 Feb 2026 15:32:57 +0800 Subject: [PATCH 1/8] Perf Policy Group generate --- v2rayN/ServiceLib/Global.cs | 2 + v2rayN/ServiceLib/Handler/ConfigHandler.cs | 41 +++------- .../ServiceLib/Manager/GroupProfileManager.cs | 2 +- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 78 +++---------------- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.fr.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.hu.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 30 ++----- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 30 ++----- .../ViewModels/ProfilesViewModel.cs | 56 ++----------- .../v2rayN.Desktop/Views/ProfilesView.axaml | 10 +-- .../Views/ProfilesView.axaml.cs | 7 +- v2rayN/v2rayN/Views/ProfilesView.xaml | 27 +------ v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 7 +- 16 files changed, 80 insertions(+), 360 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 90721217..c11ec6bf 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -90,6 +90,8 @@ public class Global public const int Hysteria2DefaultHopInt = 10; + public const string PolicyGroupDefaultAllFilter = "^(?!.*(剩余|过期|到期|重置)).*$"; + public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index b0bde1df..0cc9e4dd 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1165,46 +1165,23 @@ public static class ConfigHandler /// /// Create a group server that combines multiple servers for load balancing - /// Generates a configuration file that references multiple servers + /// Generates a PolicyGroup profile with references to the sub-items /// /// Current configuration - /// Selected servers to combine - /// Core type to use (Xray or sing_box) - /// Load balancing algorithm + /// Sub-item for grouping /// Result object with success state and data - public static async Task AddGroupServer4Multiple(Config config, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId) + public static async Task AddGroupAllServer(Config config, SubItem? subItem) { var result = new RetResult(); var indexId = Utils.GetGuid(false); - var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList()); + var subId = subItem?.Id; - var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} "; - if (coreType == ECoreType.Xray) - { - remark += multipleLoad switch - { - EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing, - EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback, - EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom, - EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin, - EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad, - _ => ResUI.menuGenGroupMultipleServerXrayRoundRobin, - }; - } - else if (coreType == ECoreType.sing_box) - { - remark += multipleLoad switch - { - EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, - EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback, - _ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, - }; - } + var remark = subItem is null ? ResUI.TbConfigTypePolicyGroup : $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}"; var profile = new ProfileItem { IndexId = indexId, - CoreType = coreType, + CoreType = ECoreType.Xray, ConfigType = EConfigType.PolicyGroup, Remarks = remark, IsSub = false @@ -1215,8 +1192,10 @@ public static class ConfigHandler } var extraItem = new ProtocolExtraItem { - ChildItems = childProfileIndexId, - MultipleLoad = multipleLoad, + MultipleLoad = EMultipleLoad.LeastPing, + GroupType = profile.ConfigType.ToString(), + SubChildItems = subId, + Filter = Global.PolicyGroupDefaultAllFilter, }; profile.SetProtocolExtra(extraItem); var ret = await AddServerCommon(config, profile, true); diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 510667dd..027cf193 100644 --- a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -128,7 +128,7 @@ public class GroupProfileManager private static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) { - if (extra == null || extra.SubChildItems.IsNullOrEmpty()) + if (extra == null || extra.Filter.IsNullOrEmpty()) { return []; } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 5e35d3bb..2dd145a1 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -834,6 +834,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 All configurations 的本地化字符串。 + /// + public static string menuAllServers { + get { + return ResourceManager.GetString("menuAllServers", resourceCulture); + } + } + /// /// 查找类似 Backup and Restore 的本地化字符串。 /// @@ -1006,74 +1015,11 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。 + /// 查找类似 Generate Policy Group 的本地化字符串。 /// - public static string menuGenGroupMultipleServer { + public static string menuGenGroupServer { get { - return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture); - } - } - - /// - /// 查找类似 Fallback by sing-box 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerSingBoxFallback { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture); - } - } - - /// - /// 查找类似 LeastPing by sing-box 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerSingBoxLeastPing { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture); - } - } - - /// - /// 查找类似 Fallback by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayFallback { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture); - } - } - - /// - /// 查找类似 LeastLoad by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayLeastLoad { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture); - } - } - - /// - /// 查找类似 LeastPing by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayLeastPing { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture); - } - } - - /// - /// 查找类似 Random by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayRandom { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture); - } - } - - /// - /// 查找类似 RoundRobin by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayRoundRobin { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture); + return ResourceManager.GetString("menuGenGroupServer", resourceCulture); } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 0df75d77..957e5de0 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1371,24 +1371,6 @@ مخفی و پورت می شود، با کاما (،) جدا می شود - - Generate Policy Group from Multiple Profiles - - - چند سرور تصادفی توسط Xray - - - چند سرور RoundRobin توسط Xray - - - چند سرور LeastPing توسط Xray - - - چند سرور LeastLoad توسط Xray - - - LeastPing چند سرور توسط sing-box - صادر کردن سرور @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 6c882112..07f59492 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1368,24 +1368,6 @@ Écrase le port ; pour plusieurs groupes, séparer par virgules (,) - - Générer un groupe de stratégie depuis plusieurs profils - - - Xray aléatoire (multi-sélection) - - - Xray équilibrage (tourniquet) multi-sélection - - - Xray latence minimale (multi-sélection) - - - Xray le plus stable (multi-sélection) - - - sing-box latence minimale (multi-sélection) - Exporter @@ -1530,12 +1512,6 @@ Basculement (failover) - - sing-box basculement (multi-sélection) - - - Xray basculement (multi-sélection) - Le cœur « {0} » ne prend pas en charge le type de réseau « {1} » @@ -1683,4 +1659,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index dd7d22f4..2fb4d62c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1371,24 +1371,6 @@ A portot lefedi, vesszővel (,) elválasztva - - Generate Policy Group from Multiple Profiles - - - Több konfiguráció véletlenszerűen Xray szerint - - - Több konfiguráció RoundRobin Xray szerint - - - Több konfiguráció legkisebb pinggel Xray szerint - - - Több konfiguráció legkisebb terheléssel Xray szerint - - - Több konfiguráció legkisebb pinggel sing-box szerint - Konfiguráció exportálása @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index fa53ccc8..21c65216 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1371,24 +1371,6 @@ Will cover the port, separate with commas (,) - - Generate Policy Group from Multiple Profiles - - - Random by Xray - - - RoundRobin by Xray - - - LeastPing by Xray - - - LeastLoad by Xray - - - LeastPing by sing-box - Export @@ -1533,12 +1515,6 @@ Fallback - - Fallback by sing-box - - - Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index b33a10e3..91095963 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1371,24 +1371,6 @@ Заменит указанный порт, перечисляйте через запятую (,) - - Generate Policy Group from Multiple Profiles - - - Случайный (Xray) - - - Круговой (Xray) - - - Минимальное RTT (минимальное время туда-обратно) (Xray) - - - Минимальная нагрузка (Xray) - - - Минимальное RTT (минимальное время туда-обратно) (sing-box) - Экспортировать конфигурацию @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + \ 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 be76525e..b02e7877 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1368,24 +1368,6 @@ 会覆盖端口,多组时用逗号 (,) 隔开 - - 多选生成策略组 - - - 多选随机 Xray - - - 多选负载均衡 Xray - - - 多选最低延迟 Xray - - - 多选最稳定 Xray - - - 多选最低延迟 sing-box - 导出 @@ -1530,12 +1512,6 @@ 故障转移 - - 多选故障转移 sing-box - - - 多选故障转移 Xray - 核心 '{0}' 不支持网络类型 '{1}' @@ -1683,4 +1659,10 @@ 订阅后置节点 {0} 未找到,已跳过。 + + 一键生成策略组 + + + 全部配置项 + \ 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 4132a27b..7323f2ef 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1368,24 +1368,6 @@ 會覆蓋埠,多組時用逗號 (,) 隔開 - - 多選生成策略組 - - - 多選隨機 Xray - - - 多選負載平衡 Xray - - - 多選最低延遲 Xray - - - 多選最穩定 Xray - - - 多選最低延遲 sing-box - 匯出 @@ -1530,12 +1512,6 @@ 容錯移轉 - - 多選容錯移轉 sing-box - - - 多選容錯移轉 Xray - 核心 '{0}' 不支援網路類型 '{1}' @@ -1683,4 +1659,10 @@ Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 1cc6f46e..42545a08 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -43,13 +43,7 @@ public class ProfilesViewModel : MyReactiveObject public ReactiveCommand CopyServerCmd { get; } public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayRandomCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayRoundRobinCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayLeastPingCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayLeastLoadCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayFallbackCmd { get; } - public ReactiveCommand GenGroupMultipleServerSingBoxLeastPingCmd { get; } - public ReactiveCommand GenGroupMultipleServerSingBoxFallbackCmd { get; } + public ReactiveCommand GenGroupAllServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -134,33 +128,9 @@ public class ProfilesViewModel : MyReactiveObject { await ShareServerAsync(); }, canEditRemove); - GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () => + GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () => { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random); - }, canEditRemove); - GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin); - }, canEditRemove); - GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing); - }, canEditRemove); - GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); - }, canEditRemove); - GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback); - }, canEditRemove); - GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); - }, canEditRemove); - GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback); + await GenGroupAllServer(); }, canEditRemove); //servers move @@ -641,29 +611,15 @@ public class ProfilesViewModel : MyReactiveObject await _updateView?.Invoke(EViewAction.ShareServer, url); } - private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad) + private async Task GenGroupAllServer() { - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } - - var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id); + var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub); if (ret.Success != true) { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } - if (ret?.Data?.ToString() == _config.IndexId) - { - await RefreshServers(); - Reload(); - } - else - { - await SetDefaultServer(ret?.Data?.ToString()); - } + await RefreshServers(); } public async Task SortServer(string colName) diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml index 116f35fa..71569feb 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml @@ -193,14 +193,8 @@ - - - - - - - - + + diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index aaebb518..3ce58944 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -60,12 +60,7 @@ public partial class ProfilesView : ReactiveUserControl 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.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); //servers move //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml index 6cf0d159..3cfd59a7 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -240,32 +240,11 @@ Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" /> - + - - - - - - + Header="{x:Static resx:ResUI.menuAllServers}" /> diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index 58b468a4..aeeaaa64 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -54,12 +54,7 @@ public partial class ProfilesView 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.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); From 94f15b91503554d2c4495f507463d7e22f19f4b3 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 27 Feb 2026 17:00:39 +0800 Subject: [PATCH 2/8] Scroll to new group node --- v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 42545a08..28d21919 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -8,6 +8,7 @@ public class ProfilesViewModel : MyReactiveObject private string _serverFilter = string.Empty; private Dictionary _dicHeaderSort = new(); private SpeedtestService? _speedtestService; + private string? _pendingSelectIndexId; #endregion private prop @@ -362,15 +363,14 @@ public class ProfilesViewModel : MyReactiveObject ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { - var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); - if (selected != null) + ProfileItemModel? selected = null; + if (!_pendingSelectIndexId.IsNullOrEmpty()) { - SelectedProfile = selected; - } - else - { - SelectedProfile = lstModel.First(); + selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId); + _pendingSelectIndexId = null; } + selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); + SelectedProfile = selected ?? lstModel.First(); } await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); @@ -619,6 +619,7 @@ public class ProfilesViewModel : MyReactiveObject NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } + _pendingSelectIndexId = ret.Data?.ToString(); await RefreshServers(); } From d4df6b91f0dc94574777e85f5543a104491e3f2a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 27 Feb 2026 17:43:20 +0800 Subject: [PATCH 3/8] Add region group --- v2rayN/ServiceLib/Global.cs | 2 - v2rayN/ServiceLib/Handler/ConfigHandler.cs | 82 ++++++++++++++++++- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 ++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 3 + v2rayN/ServiceLib/Resx/ResUI.fr.resx | 3 + 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/ProfilesViewModel.cs | 18 ++++ .../v2rayN.Desktop/Views/ProfilesView.axaml | 1 + .../Views/ProfilesView.axaml.cs | 1 + v2rayN/v2rayN/Views/ProfilesView.xaml | 4 + v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 1 + 15 files changed, 136 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index c11ec6bf..90721217 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -90,8 +90,6 @@ public class Global public const int Hysteria2DefaultHopInt = 10; - public const string PolicyGroupDefaultAllFilter = "^(?!.*(剩余|过期|到期|重置)).*$"; - public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 0cc9e4dd..78e74f1c 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1163,6 +1163,16 @@ public static class ConfigHandler return 0; } + private const string PolicyGroupDefaultAllFilter = "^(?!.*(?:剩余|过期|到期|重置)).*$"; + + /// + /// Combines a region pattern with PolicyGroupDefaultAllFilter so results must + /// match the region keyword AND not contain expiry/traffic noise words. + /// Result pattern: ^(?!.*(?:剩余|过期|到期|重置)).*(?:regionPattern).*$ + /// + private static string CombineWithDefaultAllFilter(string regionPattern) + => $"^(?!.*(?:剩余|过期|到期|重置)).*(?:{regionPattern}).*$"; + /// /// Create a group server that combines multiple servers for load balancing /// Generates a PolicyGroup profile with references to the sub-items @@ -1195,7 +1205,7 @@ public static class ConfigHandler MultipleLoad = EMultipleLoad.LeastPing, GroupType = profile.ConfigType.ToString(), SubChildItems = subId, - Filter = Global.PolicyGroupDefaultAllFilter, + Filter = PolicyGroupDefaultAllFilter, }; profile.SetProtocolExtra(extraItem); var ret = await AddServerCommon(config, profile, true); @@ -1204,6 +1214,76 @@ public static class ConfigHandler return result; } + private static readonly Dictionary PolicyGroupRegionFilters = new() + { + { "JP", "日本|[Jj][Pp]|🇯🇵" }, + { "US", "美国|[Uu][Ss]|🇺🇸" }, + { "HK", "香港|[Hh][Kk]|🇭🇰" }, + { "TW", "台湾|[Tt][Ww]|🇹🇼" }, + { "KR", "韩国|[Kk][Rr]|🇰🇷" }, + { "SG", "新加坡|[Ss][Gg]|🇸🇬" }, + { "DE", "德国|[Dd][Ee]|🇩🇪" }, + { "FR", "法国|[Ff][Rr]|🇫🇷" }, + { "GB", "英国|[Gg][Bb]|🇬🇧" }, + { "CA", "加拿大|[Cc][Aa]|🇨🇦" }, + { "AU", "澳大利亚|[Aa][Uu]|🇦🇺" }, + { "RU", "俄罗斯|[Rr][Uu]|🇷🇺" }, + { "BR", "巴西|[Bb][Rr]|🇧🇷" }, + { "IN", "印度|[Ii][Nn]|🇮🇳" }, + { "VN", "越南|[Vv][Nn]|🇻🇳" }, + { "ID", "印度尼西亚|[Ii][Dd]|🇮🇩" }, + { "MX", "墨西哥|[Mm][Xx]|🇲🇽" } + }; + + public static async Task AddGroupRegionServer(Config config, SubItem? subItem) + { + var result = new RetResult(); + List indexIdList = []; + + foreach (var regionFilter in PolicyGroupRegionFilters) + { + var indexId = Utils.GetGuid(false); + var subId = subItem?.Id; + + var remark = subItem is null ? ResUI.TbConfigTypePolicyGroup : $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}"; + var profile = new ProfileItem + { + IndexId = indexId, + CoreType = ECoreType.Xray, + ConfigType = EConfigType.PolicyGroup, + Remarks = remark, + IsSub = false + }; + if (!subId.IsNullOrEmpty()) + { + profile.Subid = subId; + } + var extraItem = new ProtocolExtraItem + { + MultipleLoad = EMultipleLoad.LeastPing, + GroupType = profile.ConfigType.ToString(), + SubChildItems = subId, + Filter = CombineWithDefaultAllFilter(regionFilter.Value), + }; + profile.SetProtocolExtra(extraItem); + + var childProfile = await GroupProfileManager.GetChildProfileItemsByProtocolExtra(extraItem); + if (childProfile.Count == 0) + { + continue; + } + + var ret = await AddServerCommon(config, profile, true); + if (ret == 0) + { + indexIdList.Add(indexId); + } + } + result.Success = indexIdList.Count > 0; + result.Data = indexIdList; + return result; + } + /// /// Get a SOCKS server profile for pre-SOCKS functionality /// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 2dd145a1..b00bec2d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1023,6 +1023,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Group by Region 的本地化字符串。 + /// + public static string menuGenRegionGroup { + get { + return ResourceManager.GetString("menuGenRegionGroup", resourceCulture); + } + } + /// /// 查找类似 Global Hotkey Setting 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 957e5de0..16836861 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if All configurations + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 07f59492..253831ef 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if All configurations + + Group by Region + diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 2fb4d62c..a29d8987 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if All configurations + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 21c65216..17416125 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if All configurations + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 91095963..d8a174d5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if All configurations + + Group by Region + \ 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 b02e7877..3a4e4924 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1665,4 +1665,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 7323f2ef..330a36e2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1665,4 +1665,7 @@ All configurations + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 28d21919..91c745d6 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -45,6 +45,7 @@ public class ProfilesViewModel : MyReactiveObject public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } public ReactiveCommand GenGroupAllServerCmd { get; } + public ReactiveCommand GenGroupRegionServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -133,6 +134,10 @@ public class ProfilesViewModel : MyReactiveObject { await GenGroupAllServer(); }, canEditRemove); + GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await GenGroupRegionServer(); + }, canEditRemove); //servers move MoveTopCmd = ReactiveCommand.CreateFromTask(async () => @@ -623,6 +628,19 @@ public class ProfilesViewModel : MyReactiveObject await RefreshServers(); } + private async Task GenGroupRegionServer() + { + var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub); + if (ret.Success != true) + { + NoticeManager.Instance.Enqueue(ResUI.OperationFailed); + return; + } + var indexIdList = ret.Data as List; + _pendingSelectIndexId = indexIdList?.FirstOrDefault(); + await RefreshServers(); + } + public async Task SortServer(string colName) { if (colName.IsNullOrEmpty()) diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml index 71569feb..e0751ca1 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml @@ -195,6 +195,7 @@ + diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index 3ce58944..4ca8e793 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -61,6 +61,7 @@ public partial class ProfilesView : ReactiveUserControl 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.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables); //servers move //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml index 3cfd59a7..fd984306 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -245,6 +245,10 @@ x:Name="menuGenGroupAllServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuAllServers}" /> + diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index aeeaaa64..46def3e2 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -55,6 +55,7 @@ public partial class ProfilesView 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.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables); //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); From de0fa9d4553f4d409a6cd8968493f928015de72e Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 27 Feb 2026 17:45:06 +0800 Subject: [PATCH 4/8] I18n --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 49 ++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 78e74f1c..a08ef708 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1163,15 +1163,22 @@ public static class ConfigHandler return 0; } - private const string PolicyGroupDefaultAllFilter = "^(?!.*(?:剩余|过期|到期|重置)).*$"; + // Matches subscription-info noise words in both Chinese and English. + // Chinese: 剩余(remaining), 过期/到期(expired/expiry), 重置(reset) + // English: remaining, expir(e/ed/y), reset + private const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset"; + + private const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; /// /// Combines a region pattern with PolicyGroupDefaultAllFilter so results must - /// match the region keyword AND not contain expiry/traffic noise words. - /// Result pattern: ^(?!.*(?:剩余|过期|到期|重置)).*(?:regionPattern).*$ + /// match the region keyword AND not contain expiry/traffic noise words (CN + EN). + /// Result pattern: ^(?!.*(?:...excludeKeywords...)).*(?:regionPattern).*$ /// private static string CombineWithDefaultAllFilter(string regionPattern) - => $"^(?!.*(?:剩余|过期|到期|重置)).*(?:{regionPattern}).*$"; + { + return $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$"; + } /// /// Create a group server that combines multiple servers for load balancing @@ -1216,23 +1223,23 @@ public static class ConfigHandler private static readonly Dictionary PolicyGroupRegionFilters = new() { - { "JP", "日本|[Jj][Pp]|🇯🇵" }, - { "US", "美国|[Uu][Ss]|🇺🇸" }, - { "HK", "香港|[Hh][Kk]|🇭🇰" }, - { "TW", "台湾|[Tt][Ww]|🇹🇼" }, - { "KR", "韩国|[Kk][Rr]|🇰🇷" }, - { "SG", "新加坡|[Ss][Gg]|🇸🇬" }, - { "DE", "德国|[Dd][Ee]|🇩🇪" }, - { "FR", "法国|[Ff][Rr]|🇫🇷" }, - { "GB", "英国|[Gg][Bb]|🇬🇧" }, - { "CA", "加拿大|[Cc][Aa]|🇨🇦" }, - { "AU", "澳大利亚|[Aa][Uu]|🇦🇺" }, - { "RU", "俄罗斯|[Rr][Uu]|🇷🇺" }, - { "BR", "巴西|[Bb][Rr]|🇧🇷" }, - { "IN", "印度|[Ii][Nn]|🇮🇳" }, - { "VN", "越南|[Vv][Nn]|🇻🇳" }, - { "ID", "印度尼西亚|[Ii][Dd]|🇮🇩" }, - { "MX", "墨西哥|[Mm][Xx]|🇲🇽" } + { "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" }, + { "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" }, + { "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" }, + { "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" }, + { "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" }, + { "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" }, + { "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" }, + { "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" }, + { "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" }, + { "CA", "加拿大|🇨🇦|[Cc]anada" }, + { "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" }, + { "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" }, + { "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" }, + { "IN", "印度|🇮🇳|[Ii]ndia" }, + { "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" }, + { "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" }, + { "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" } }; public static async Task AddGroupRegionServer(Config config, SubItem? subItem) From 023f9f1a7ee6485eb4613d265aaa04cfe1be418c Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 28 Feb 2026 19:27:00 +0800 Subject: [PATCH 5/8] Fix --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 19 ++++++++++++++----- .../ServiceLib/Manager/GroupProfileManager.cs | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index a08ef708..42f0042e 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1191,10 +1191,15 @@ public static class ConfigHandler { var result = new RetResult(); - var indexId = Utils.GetGuid(false); var subId = subItem?.Id; + if (subId.IsNullOrEmpty()) + { + result.Success = false; + return result; + } - var remark = subItem is null ? ResUI.TbConfigTypePolicyGroup : $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}"; + var indexId = Utils.GetGuid(false); + var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}"; var profile = new ProfileItem { IndexId = indexId, @@ -1245,14 +1250,18 @@ public static class ConfigHandler public static async Task AddGroupRegionServer(Config config, SubItem? subItem) { var result = new RetResult(); + var subId = subItem?.Id; + if (subId.IsNullOrEmpty()) + { + result.Success = false; + return result; + } List indexIdList = []; foreach (var regionFilter in PolicyGroupRegionFilters) { var indexId = Utils.GetGuid(false); - var subId = subItem?.Id; - - var remark = subItem is null ? ResUI.TbConfigTypePolicyGroup : $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}"; + var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}"; var profile = new ProfileItem { IndexId = indexId, diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 027cf193..510667dd 100644 --- a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -128,7 +128,7 @@ public class GroupProfileManager private static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) { - if (extra == null || extra.Filter.IsNullOrEmpty()) + if (extra == null || extra.SubChildItems.IsNullOrEmpty()) { return []; } From 58e3824d67edc369408757885960fde0fdd3b75a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 28 Feb 2026 19:43:10 +0800 Subject: [PATCH 6/8] Fix --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 42f0042e..091d31eb 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1256,6 +1256,7 @@ public static class ConfigHandler result.Success = false; return result; } + var childProfiles = await AppManager.Instance.ProfileItems(subId); List indexIdList = []; foreach (var regionFilter in PolicyGroupRegionFilters) @@ -1283,8 +1284,14 @@ public static class ConfigHandler }; profile.SetProtocolExtra(extraItem); - var childProfile = await GroupProfileManager.GetChildProfileItemsByProtocolExtra(extraItem); - if (childProfile.Count == 0) + var matchedChildProfiles = childProfiles?.Where(p => + p != null && + p.IsValid() && + !p.ConfigType.IsComplexType() && + (extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter)) + ) + .ToList() ?? []; + if (matchedChildProfiles.Count == 0) { continue; } From 9828b07935b800424bd15cbac358802222a1858b Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 28 Feb 2026 19:51:30 +0800 Subject: [PATCH 7/8] Move default filter to Global --- v2rayN/ServiceLib/Global.cs | 4 ++++ v2rayN/ServiceLib/Handler/ConfigHandler.cs | 24 ++++++---------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 90721217..7d44bb0c 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -90,6 +90,10 @@ public class Global public const int Hysteria2DefaultHopInt = 10; + public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset"; + + public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; + public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 091d31eb..e5b6fec3 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1163,23 +1163,6 @@ public static class ConfigHandler return 0; } - // Matches subscription-info noise words in both Chinese and English. - // Chinese: 剩余(remaining), 过期/到期(expired/expiry), 重置(reset) - // English: remaining, expir(e/ed/y), reset - private const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset"; - - private const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; - - /// - /// Combines a region pattern with PolicyGroupDefaultAllFilter so results must - /// match the region keyword AND not contain expiry/traffic noise words (CN + EN). - /// Result pattern: ^(?!.*(?:...excludeKeywords...)).*(?:regionPattern).*$ - /// - private static string CombineWithDefaultAllFilter(string regionPattern) - { - return $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$"; - } - /// /// Create a group server that combines multiple servers for load balancing /// Generates a PolicyGroup profile with references to the sub-items @@ -1217,7 +1200,7 @@ public static class ConfigHandler MultipleLoad = EMultipleLoad.LeastPing, GroupType = profile.ConfigType.ToString(), SubChildItems = subId, - Filter = PolicyGroupDefaultAllFilter, + Filter = Global.PolicyGroupDefaultAllFilter, }; profile.SetProtocolExtra(extraItem); var ret = await AddServerCommon(config, profile, true); @@ -1226,6 +1209,11 @@ public static class ConfigHandler return result; } + private static string CombineWithDefaultAllFilter(string regionPattern) + { + return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$"; + } + private static readonly Dictionary PolicyGroupRegionFilters = new() { { "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" }, From 689bbba0ab0d615167637113306c518c04ee4ddd Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 28 Feb 2026 20:13:37 +0800 Subject: [PATCH 8/8] Default Filter List --- v2rayN/ServiceLib/Global.cs | 12 ++++++++++++ .../v2rayN.Desktop/Views/AddGroupServerWindow.axaml | 11 +++++++---- .../Views/AddGroupServerWindow.axaml.cs | 3 ++- v2rayN/v2rayN/Views/AddGroupServerWindow.xaml | 12 +++++++----- v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs | 3 ++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 7d44bb0c..685b74c7 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -94,6 +94,18 @@ public class Global public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; + public static readonly List PolicyGroupDefaultFilterList = + [ + // All nodes (exclude traffic/expiry info) + PolicyGroupDefaultAllFilter, + // Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍 + @"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$", + // Dedicated line nodes, e.g. IPLC, IEPL + $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$", + // Japan nodes + $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$", + ]; + public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml index c036bb03..c042868c 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml @@ -117,7 +117,8 @@ Grid.Column="2" Margin="{StaticResource Margin4}" VerticalAlignment="Center" - Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> + Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" + TextWrapping="Wrap" /> - + VerticalAlignment="Center" + IsEditable="True" /> diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs index 142f9d6b..06784ac0 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs @@ -29,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase ResUI.TbRoundRobin, ResUI.TbLeastLoad, }; + cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList; switch (profileItem.ConfigType) { @@ -53,7 +54,7 @@ public partial class AddGroupServerWindow : WindowBase 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.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).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 baaa0114..bbbf29c6 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml @@ -173,7 +173,8 @@ Margin="{StaticResource Margin4}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> + Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" + TextWrapping="Wrap" /> - + IsEditable="True" + Style="{StaticResource DefComboBox}" /> diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs index 4b1642d2..965b19d9 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs @@ -24,6 +24,7 @@ public partial class AddGroupServerWindow ResUI.TbRoundRobin, ResUI.TbLeastLoad, }; + cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList; switch (profileItem.ConfigType) { @@ -48,7 +49,7 @@ public partial class AddGroupServerWindow 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.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.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);