diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 73108a58..e62f88ee 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -124,6 +124,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..bed7bd7a 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; } 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..75a4af32 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3013,6 +3013,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..afd1a699 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1336,4 +1336,7 @@ UpgradeApp does not exist + + Routing rules source (optional) + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/UpdateService.cs b/v2rayN/ServiceLib/Services/UpdateService.cs index f25548ce..006a96f0 100644 --- a/v2rayN/ServiceLib/Services/UpdateService.cs +++ b/v2rayN/ServiceLib/Services/UpdateService.cs @@ -253,6 +253,23 @@ namespace ServiceLib.Services }); } + public async Task VerifyGeoFilesRepo(Config config, Action updateFunc) + { + var repoPath = Utils.GetBinPath("geo.repo"); + var repo = File.Exists(repoPath) ? File.ReadAllText(repoPath) : ""; + + if (repo != (config.constItem.geoSourceUrl ?? "")) + { + await UpdateGeoFileAll(config, updateFunc); + + File.WriteAllText(repoPath, repo); + + return false; + } + + return true; + } + public async Task UpdateGeoFileAll(Config config, Action updateFunc) { await UpdateGeoFile("geosite", config, updateFunc); diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 084e0f86..c122c53f 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -188,9 +188,10 @@ namespace ServiceLib.ViewModels private void Init() { - ConfigHandler.InitBuiltinRouting(_config); + ConfigHandler.InitRouting(_config); ConfigHandler.InitBuiltinDNS(_config); CoreHandler.Instance.Init(_config, UpdateHandler); + Task.Run(() => VerifyGeoFiles(true)); TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); if (_config.guiItem.enableStatistics) @@ -421,6 +422,7 @@ namespace ServiceLib.ViewModels var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); if (ret == true) { + await VerifyGeoFiles(); Locator.Current.GetService()?.InboundDisplayStatus(); Reload(); } @@ -431,7 +433,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(); } @@ -541,6 +543,14 @@ namespace ServiceLib.ViewModels } } + public async Task VerifyGeoFiles(bool needReload = false) + { + var result = await new UpdateService().VerifyGeoFilesRepo(_config, UpdateHandler); + + if (needReload && !result) + Reload(); + } + #endregion core job } } \ 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/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 2154a27e..b07b3b71 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/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);