From 2edbbc523af759f9083aac2ec39b5274cf288ab7 Mon Sep 17 00:00:00 2001 From: runetfreedom Date: Wed, 16 Oct 2024 09:11:20 +0300 Subject: [PATCH] External routing rules templates + regional presets support (#5840) * External routing rules templates + auto download geo file if repo changed * Regional presets support --- v2rayN/ServiceLib/Enums/EPresetType.cs | 8 ++ v2rayN/ServiceLib/Global.cs | 5 + v2rayN/ServiceLib/Handler/ConfigHandler.cs | 114 ++++++++++++++++-- v2rayN/ServiceLib/Models/ConfigItems.cs | 1 + v2rayN/ServiceLib/Models/RoutingTemplate.cs | 9 ++ v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 38 ++++++ v2rayN/ServiceLib/Resx/ResUI.resx | 12 ++ .../ViewModels/MainWindowViewModel.cs | 35 +++++- .../ViewModels/OptionSettingViewModel.cs | 3 + .../ViewModels/RoutingSettingViewModel.cs | 4 +- v2rayN/v2rayN.Desktop/Views/MainWindow.axaml | 4 + .../v2rayN.Desktop/Views/MainWindow.axaml.cs | 2 + .../Views/OptionSettingWindow.axaml | 14 +++ .../Views/OptionSettingWindow.axaml.cs | 5 + v2rayN/v2rayN/Views/MainWindow.xaml | 10 ++ v2rayN/v2rayN/Views/MainWindow.xaml.cs | 2 + v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 17 +++ .../v2rayN/Views/OptionSettingWindow.xaml.cs | 5 + 18 files changed, 272 insertions(+), 16 deletions(-) create mode 100644 v2rayN/ServiceLib/Enums/EPresetType.cs create mode 100644 v2rayN/ServiceLib/Models/RoutingTemplate.cs diff --git a/v2rayN/ServiceLib/Enums/EPresetType.cs b/v2rayN/ServiceLib/Enums/EPresetType.cs new file mode 100644 index 00000000..57a1984f --- /dev/null +++ b/v2rayN/ServiceLib/Enums/EPresetType.cs @@ -0,0 +1,8 @@ +namespace ServiceLib.Enums +{ + public enum EPresetType + { + Default = 0, + Russia = 1, + } +} diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 8d1cdc2f..73ec731b 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -126,6 +126,11 @@ @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-{0}/{1}.srs", }; + public static readonly List RoutingRulesSources = new() { + "", //Default + @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/refs/heads/main/template.json", + }; + public static readonly Dictionary UserAgentTexts = new() { {"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" }, diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index a574cbeb..937061d4 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1,4 +1,6 @@ -using System.Data; +using ServiceLib.Common; +using System.Data; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; namespace ServiceLib.Handler @@ -1616,6 +1618,79 @@ namespace ServiceLib.Handler return item; } + public static int InitRouting(Config config, bool blImportAdvancedRules = false) + { + if (!String.IsNullOrEmpty(config.constItem.routeRulesTemplateSourceUrl)) + { + InitExternalRouting(config, blImportAdvancedRules); + } + else + { + InitBuiltinRouting(config, blImportAdvancedRules); + } + + if (GetLockedRoutingItem(config) == null) + { + var item1 = new RoutingItem() + { + remarks = "locked", + url = string.Empty, + locked = true, + }; + AddBatchRoutingRules(ref item1, Utils.GetEmbedText(Global.CustomRoutingFileName + "locked")); + } + + return 0; + } + + public static int InitExternalRouting(Config config, bool blImportAdvancedRules = false) + { + DownloadService downloadHandle = new DownloadService(); + var templateContent = Task.Run(() => downloadHandle.TryDownloadString(config.constItem.routeRulesTemplateSourceUrl, false, "")).Result; + if (String.IsNullOrEmpty(templateContent)) + return InitBuiltinRouting(config, blImportAdvancedRules); // fallback + + var template = JsonUtils.Deserialize(templateContent); + if (template == null) + return InitBuiltinRouting(config, blImportAdvancedRules); // fallback + + var items = AppHandler.Instance.RoutingItems(); + var maxSort = items.Count; + + if (blImportAdvancedRules || items.Where(t => t.remarks.StartsWith(template.version)).ToList().Count <= 0) + { + for (var i = 0; i < template.routingItems.Length; i++) + { + var item = template.routingItems[i]; + + if (String.IsNullOrEmpty(item.url) && String.IsNullOrEmpty(item.ruleSet)) + continue; + + var ruleSetsString = !String.IsNullOrEmpty(item.ruleSet) + ? item.ruleSet + : Task.Run(() => downloadHandle.TryDownloadString(item.url, false, "")).Result; + + if (String.IsNullOrEmpty(ruleSetsString)) + continue; + + item.remarks = $"{template.version}-{item.remarks}"; + item.enabled = true; + item.sort = ++maxSort; + item.url = string.Empty; + + AddBatchRoutingRules(ref item, ruleSetsString); + + //first rule as default at first startup + if (!blImportAdvancedRules && i == 0) + { + SetDefaultRouting(config, item); + } + } + } + + return 0; + } + public static int InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) { var ver = "V3-"; @@ -1655,17 +1730,6 @@ namespace ServiceLib.Handler SetDefaultRouting(config, item2); } } - - if (GetLockedRoutingItem(config) == null) - { - var item1 = new RoutingItem() - { - remarks = "locked", - url = string.Empty, - locked = true, - }; - AddBatchRoutingRules(ref item1, Utils.GetEmbedText(Global.CustomRoutingFileName + "locked")); - } return 0; } @@ -1724,5 +1788,31 @@ namespace ServiceLib.Handler } #endregion DNS + + #region Presets + + public static bool ApplyPreset(Config config, EPresetType type) + { + switch (type) + { + case EPresetType.Default: + config.constItem.geoSourceUrl = ""; + config.constItem.srsSourceUrl = ""; + config.constItem.routeRulesTemplateSourceUrl = ""; + + return true; + + case EPresetType.Russia: + config.constItem.geoSourceUrl = Global.GeoFilesSources[1]; + config.constItem.srsSourceUrl = Global.SingboxRulesetSources[1]; + config.constItem.routeRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; + + return true; + } + + return false; + } + + #endregion } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index ff282ab1..f42aee96 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -142,6 +142,7 @@ public string subConvertUrl { get; set; } = string.Empty; public string? geoSourceUrl { get; set; } public string? srsSourceUrl { get; set; } + public string? routeRulesTemplateSourceUrl { get; set; } } [Serializable] diff --git a/v2rayN/ServiceLib/Models/RoutingTemplate.cs b/v2rayN/ServiceLib/Models/RoutingTemplate.cs new file mode 100644 index 00000000..1eb9c1d0 --- /dev/null +++ b/v2rayN/ServiceLib/Models/RoutingTemplate.cs @@ -0,0 +1,9 @@ +namespace ServiceLib.Models +{ + [Serializable] + public class RoutingTemplate + { + public string version { get; set; } + public RoutingItem[] routingItems { get; set; } + } +} diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 65e6ebda..0c92385c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1113,6 +1113,33 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Regional presets 的本地化字符串。 + /// + public static string menuPresets { + get { + return ResourceManager.GetString("menuPresets", resourceCulture); + } + } + + /// + /// 查找类似 Default 的本地化字符串。 + /// + public static string menuPresetsDefault { + get { + return ResourceManager.GetString("menuPresetsDefault", resourceCulture); + } + } + + /// + /// 查找类似 Russia 的本地化字符串。 + /// + public static string menuPresetsRussia { + get { + return ResourceManager.GetString("menuPresetsRussia", resourceCulture); + } + } + /// /// 查找类似 Auto column width adjustment 的本地化字符串。 /// @@ -3013,6 +3040,17 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Routing rules source (optional) 的本地化字符串。 + /// + public static string TbSettingsRoutingRulesSource + { + get + { + return ResourceManager.GetString("TbSettingsRoutingRulesSource", resourceCulture); + } + } + /// /// 查找类似 HTTP Port 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 5b69fc70..cfc597e6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1336,4 +1336,16 @@ UpgradeApp does not exist + + Routing rules source (optional) + + + Regional presets + + + Default + + + Russia + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index bf00f0d4..efc1f15a 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -44,6 +44,10 @@ namespace ServiceLib.ViewModels public ReactiveCommand ClearServerStatisticsCmd { get; } public ReactiveCommand OpenTheFileLocationCmd { get; } + //Presets + public ReactiveCommand PresetDefaultCmd { get; } + public ReactiveCommand PresetRussiaCmd { get; } + public ReactiveCommand ReloadCmd { get; } [Reactive] @@ -181,6 +185,16 @@ namespace ServiceLib.ViewModels await Reload(); }); + PresetDefaultCmd = ReactiveCommand.CreateFromTask(async () => + { + await ApplyPreset(EPresetType.Default); + }); + + PresetRussiaCmd = ReactiveCommand.CreateFromTask(async () => + { + await ApplyPreset(EPresetType.Russia); + }); + #endregion WhenAnyValue && ReactiveCommand AutoHideStartup(); @@ -188,7 +202,7 @@ namespace ServiceLib.ViewModels private void Init() { - ConfigHandler.InitBuiltinRouting(_config); + ConfigHandler.InitRouting(_config); ConfigHandler.InitBuiltinDNS(_config); CoreHandler.Instance.Init(_config, UpdateHandler); TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); @@ -431,7 +445,7 @@ namespace ServiceLib.ViewModels var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null); if (ret == true) { - ConfigHandler.InitBuiltinRouting(_config); + ConfigHandler.InitRouting(_config); Locator.Current.GetService()?.RefreshRoutingsMenu(); Reload(); } @@ -543,5 +557,22 @@ namespace ServiceLib.ViewModels } #endregion core job + + #region Presets + + public async Task ApplyPreset(EPresetType type) + { + ConfigHandler.ApplyPreset(_config, type); + + await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler); + + ConfigHandler.InitRouting(_config); + Locator.Current.GetService()?.RefreshRoutingsMenu(); + + ConfigHandler.SaveConfig(_config, false); + Reload(); + } + + #endregion } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index d3b79f35..7b0d200d 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -67,6 +67,7 @@ namespace ServiceLib.ViewModels [Reactive] public int MainGirdOrientation { get; set; } [Reactive] public string GeoFileSourceUrl { get; set; } [Reactive] public string SrsFileSourceUrl { get; set; } + [Reactive] public string RoutingRulesSourceUrl { get; set; } #endregion UI @@ -168,6 +169,7 @@ namespace ServiceLib.ViewModels MainGirdOrientation = (int)_config.uiItem.mainGirdOrientation; GeoFileSourceUrl = _config.constItem.geoSourceUrl; SrsFileSourceUrl = _config.constItem.srsSourceUrl; + RoutingRulesSourceUrl = _config.constItem.routeRulesTemplateSourceUrl; #endregion UI @@ -322,6 +324,7 @@ namespace ServiceLib.ViewModels _config.uiItem.mainGirdOrientation = (EGirdOrientation)MainGirdOrientation; _config.constItem.geoSourceUrl = GeoFileSourceUrl; _config.constItem.srsSourceUrl = SrsFileSourceUrl; + _config.constItem.routeRulesTemplateSourceUrl = RoutingRulesSourceUrl; //systemProxy _config.systemProxyItem.systemProxyExceptions = systemProxyExceptions; diff --git a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs index ab75ef51..39289cc5 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -71,7 +71,7 @@ namespace ServiceLib.ViewModels _updateView = updateView; SelectedSource = new(); - ConfigHandler.InitBuiltinRouting(_config); + ConfigHandler.InitRouting(_config); enableRoutingAdvanced = _config.routingBasicItem.enableRoutingAdvanced; domainStrategy = _config.routingBasicItem.domainStrategy; @@ -286,7 +286,7 @@ namespace ServiceLib.ViewModels private async Task RoutingAdvancedImportRules() { - if (ConfigHandler.InitBuiltinRouting(_config, true) == 0) + if (ConfigHandler.InitRouting(_config, true) == 0) { RefreshRoutingItems(); IsModified = true; diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml index e86bd461..159de1d8 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml @@ -74,6 +74,10 @@ + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index f4fd21c5..a417a876 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -76,6 +76,8 @@ namespace v2rayN.Desktop.Views this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PresetDefaultCmd, v => v.menuPresetsDefault).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PresetRussiaCmd, v => v.menuPresetsRussia).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 92e13a93..e073b02e 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -366,6 +366,7 @@ + @@ -638,6 +639,19 @@ Grid.Column="1" Width="300" Classes="Margin8" /> + + + diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index c21c3401..33d6f630 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -91,6 +91,10 @@ namespace v2rayN.Desktop.Views { cmbSrsFilesSourceUrl.Items.Add(it); }); + Global.RoutingRulesSources.ForEach(it => + { + cmbRoutingRulesSourceUrl.Items.Add(it); + }); foreach (EGirdOrientation it in Enum.GetValues(typeof(EGirdOrientation))) { cmbMainGirdOrientation.Items.Add(it.ToString()); @@ -142,6 +146,7 @@ namespace v2rayN.Desktop.Views this.Bind(ViewModel, vm => vm.MainGirdOrientation, v => v.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 7757911b..c2143cf0 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -171,6 +171,16 @@ x:Name="menuGlobalHotkeySetting" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuGlobalHotkeySetting}" /> + + + + vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PresetDefaultCmd, v => v.menuPresetsDefault).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PresetRussiaCmd, v => v.menuPresetsRussia).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 24c351e3..d52bfe29 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -530,6 +530,7 @@ + @@ -879,6 +880,22 @@ Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index 7e364d3c..ba62351c 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -93,6 +93,10 @@ namespace v2rayN.Views { cmbSrsFilesSourceUrl.Items.Add(it); }); + Global.RoutingRulesSources.ForEach(it => + { + cmbRoutingRulesSourceUrl.Items.Add(it); + }); foreach (EGirdOrientation it in Enum.GetValues(typeof(EGirdOrientation))) { cmbMainGirdOrientation.Items.Add(it.ToString()); @@ -155,6 +159,7 @@ namespace v2rayN.Views this.Bind(ViewModel, vm => vm.MainGirdOrientation, v => v.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);