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 6c3e48e5..d1dd3b75 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1077,6 +1077,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 20f48b38..31d8d7f7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1650,4 +1650,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 4c5459b8..87fbb37c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1647,4 +1647,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 f90611ab..2c5c44f7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1650,4 +1650,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 98132ca6..16cef1b7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1650,4 +1650,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 04a2168d..4a29c8c9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1650,4 +1650,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 5c30f020..f74d83ef 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1647,4 +1647,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 6cbccf45..ddb087f0 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1647,4 +1647,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 f62573c0..ad21883e 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);