diff --git a/v2rayN/ServiceLib/Enums/EPresetType.cs b/v2rayN/ServiceLib/Enums/EPresetType.cs new file mode 100644 index 0000000000..57a1984f2e --- /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 8d1cdc2f4d..73ec731b47 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -126,6 +126,11 @@ public class Global @"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 a574cbeb20..937061d4a0 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 @@ public static RoutingItem GetDefaultRouting(Config config) 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 @@ public static int InitBuiltinRouting(Config config, bool blImportAdvancedRules = 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 @@ public static int SaveDNSItems(Config config, DNSItem item) } #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 ff282ab1f5..f42aee9643 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -142,6 +142,7 @@ public class ConstItem 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 0000000000..1eb9c1d08e --- /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 65e6ebda25..0c92385c59 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1113,6 +1113,33 @@ public static string menuOptionSetting { } } + /// + /// 查找类似 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 @@ public static string TbSettingsGeoFilesSource { } } + /// + /// 查找类似 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 5b69fc70a6..cfc597e6c3 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 bf00f0d414..efc1f15a6b 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -44,6 +44,10 @@ public class MainWindowViewModel : MyReactiveObject 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 @@ public MainWindowViewModel(Func>? updateView) 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 @@ public MainWindowViewModel(Func>? updateView) 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 @@ private async Task RoutingSettingAsync() 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 @@ private void AutoHideStartup() } #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 d3b79f3515..7b0d200da2 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -67,6 +67,7 @@ public class OptionSettingViewModel : MyReactiveObject [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 @@ public OptionSettingViewModel(Func>? updateView MainGirdOrientation = (int)_config.uiItem.mainGirdOrientation; GeoFileSourceUrl = _config.constItem.geoSourceUrl; SrsFileSourceUrl = _config.constItem.srsSourceUrl; + RoutingRulesSourceUrl = _config.constItem.routeRulesTemplateSourceUrl; #endregion UI @@ -322,6 +324,7 @@ private async Task SaveSettingAsync() _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 ab75ef51b7..39289cc54f 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -71,7 +71,7 @@ public RoutingSettingViewModel(Func>? updateVie _updateView = updateView; SelectedSource = new(); - ConfigHandler.InitBuiltinRouting(_config); + ConfigHandler.InitRouting(_config); enableRoutingAdvanced = _config.routingBasicItem.enableRoutingAdvanced; domainStrategy = _config.routingBasicItem.domainStrategy; @@ -286,7 +286,7 @@ public async Task RoutingAdvancedSetDefault() 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 e86bd4616b..159de1d86a 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 f4fd21c560..a417a876c9 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -76,6 +76,8 @@ public MainWindow() 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 92e13a9312..e073b02e6a 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 c21c34012e..33d6f6308b 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -91,6 +91,10 @@ public OptionSettingWindow() { 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 @@ public OptionSettingWindow() 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 7757911b3b..c2143cf04e 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 24c351e3c2..d52bfe29ae 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 7e364d3c87..ba62351cfb 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -93,6 +93,10 @@ public OptionSettingWindow() { 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 @@ public OptionSettingWindow() 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);