From 42263c6c2fb1512f3faa1bab91c3d77426444468 Mon Sep 17 00:00:00 2001 From: shatinz Date: Thu, 29 Jan 2026 15:57:31 +0400 Subject: [PATCH] sending connection data to 3x-ui --- 3x-ui | 1 + v2rayN/ServiceLib/Global.cs | 2 +- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 24 +++ v2rayN/ServiceLib/Manager/CoreManager.cs | 10 ++ v2rayN/ServiceLib/Models/Config.cs | 7 + v2rayN/ServiceLib/Models/ConfigItems.cs | 1 + .../Services/ConnectionProbeService.cs | 141 ++++++++++++++++++ .../ViewModels/OptionSettingViewModel.cs | 5 + v2rayN/v2rayN/App.xaml.cs | 14 +- v2rayN/v2rayN/Views/MainWindow.xaml | 2 +- v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 27 ++++ .../v2rayN/Views/OptionSettingWindow.xaml.cs | 2 + v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml | 25 ++++ .../v2rayN/Views/PrivacyNoticeWindow.xaml.cs | 18 +++ v2rayN/v2rayN/v2rayN.csproj | 3 + 15 files changed, 279 insertions(+), 3 deletions(-) create mode 160000 3x-ui create mode 100644 v2rayN/ServiceLib/Services/ConnectionProbeService.cs create mode 100644 v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml create mode 100644 v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml.cs diff --git a/3x-ui b/3x-ui new file mode 160000 index 00000000..70b36517 --- /dev/null +++ b/3x-ui @@ -0,0 +1 @@ +Subproject commit 70b365171f9b40b540be5bff1a9c8b7227a37ac6 diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 94a66fa5..3feb753b 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -4,7 +4,7 @@ public class Global { #region const - public const string AppName = "v2rayN"; + public const string AppName = "shi2ray"; public const string GithubUrl = "https://github.com"; public const string GithubApiUrl = "https://api.github.com/repos"; public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 5877d012..1109ee51 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1334,6 +1334,30 @@ public static class ConfigHandler return -1; } + // Check for reportUrl and auto-set + if (strData.Contains("reportUrl=")) + { + try + { + // Simple regex or string parsing to find reportUrl value + // It might be URL encoded, but usually it's just a query param. + // Regex pattern to capture reportUrl value until next & or end of line/string + var match = System.Text.RegularExpressions.Regex.Match(strData, @"reportUrl=([^&\s]+)"); + if (match.Success) + { + var url = match.Groups[1].Value; + if (!string.IsNullOrEmpty(url)) + { + // Decode if necessary (it might be double encoded if inside a URI) + // But for now take simpler approach + config.ReportItem.ReportUrl = System.Web.HttpUtility.UrlDecode(url); + } + } + } + catch {} + } + + var subFilter = string.Empty; //remove sub items if (isSub && subid.IsNotEmpty()) diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 79dbdeb0..b372ee2c 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -1,5 +1,7 @@ namespace ServiceLib.Manager; +using ServiceLib.Services; + /// /// Core process processing class /// @@ -14,11 +16,13 @@ public class CoreManager private bool _linuxSudo = false; private Func? _updateFunc; private const string _tag = "CoreHandler"; + private ConnectionProbeService _connectionProbeService; public async Task Init(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; + _connectionProbeService = new ConnectionProbeService(config); //Copy the bin folder to the storage location (for init) if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") @@ -90,6 +94,12 @@ public class CoreManager if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); + + // Auto Report + _ = Task.Run(async () => { + await Task.Delay(2000); // Wait for core to stabilize + await _connectionProbeService.ProbeAndReportAsync(node, _updateFunc); + }); } } diff --git a/v2rayN/ServiceLib/Models/Config.cs b/v2rayN/ServiceLib/Models/Config.cs index 738ee286..6459e2fb 100644 --- a/v2rayN/ServiceLib/Models/Config.cs +++ b/v2rayN/ServiceLib/Models/Config.cs @@ -34,6 +34,13 @@ public class Config public List GlobalHotkeys { get; set; } public List CoreTypeItem { get; set; } public SimpleDNSItem SimpleDNSItem { get; set; } + public ReportItem ReportItem { get; set; } #endregion other entities } + +[Serializable] +public class ReportItem +{ + public string ReportUrl { get; set; } = ""; +} diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index eeb88deb..252bcd34 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -74,6 +74,7 @@ public class GUIItem public int TrayMenuServersLimit { get; set; } = 20; public bool EnableHWA { get; set; } = false; public bool EnableLog { get; set; } = true; + public bool PrivacyNoticeAccepted { get; set; } } [Serializable] diff --git a/v2rayN/ServiceLib/Services/ConnectionProbeService.cs b/v2rayN/ServiceLib/Services/ConnectionProbeService.cs new file mode 100644 index 00000000..7f15a75a --- /dev/null +++ b/v2rayN/ServiceLib/Services/ConnectionProbeService.cs @@ -0,0 +1,141 @@ +using System.Net.NetworkInformation; +using System.Net.Http; +using System.Text.Json; +using System.Text; + +namespace ServiceLib.Services; + +public class ConnectionProbeService +{ + private static readonly string _tag = "ConnectionProbeService"; + private readonly Config _config; + + public ConnectionProbeService(Config config) + { + _config = config; + } + + public async Task ProbeAndReportAsync(ProfileItem profile, Func updateFunc = null) + { + var result = new ReportResult(); + + if (updateFunc != null) await updateFunc(false, "Starting Connection Probe & Report..."); + + // 1. Gather System Info + result.SystemInfo = GetSystemInfo(); + + // 2. Test Connection (Ping) + // Using existing ConnectionHandler logic but simplified for single target + try + { + var webProxy = new WebProxy($"socks5://{Global.Loopback}:{profile.Port}"); + var url = _config.SpeedTestItem.SpeedPingTestUrl; + var responseTime = await ConnectionHandler.GetRealPingTime(url, webProxy, 10); + + result.ConnectionQuality = new ConnectionQuality + { + Latency = responseTime, + Success = responseTime > 0 + }; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + result.ConnectionQuality = new ConnectionQuality { Success = false, Message = ex.Message }; + } + + // 3. Populate Protocol Info + result.ProtocolInfo = new ProtocolInfo + { + Protocol = profile.ConfigType.ToString(), + Remarks = profile.Remarks, + Address = profile.Address + }; + + // 4. Send Report + if (!string.IsNullOrEmpty(_config.ReportItem.ReportUrl)) + { + if (updateFunc != null) await updateFunc(false, "Sending Connection Report..."); + await SendReportAsync(result); + if (updateFunc != null) await updateFunc(false, "Connection Report Sent Successfully."); + } + + return result; + } + + private SystemInfo GetSystemInfo() + { + var info = new SystemInfo(); + try + { + // Simple heuristic to guess active interface + var interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up) + .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .ToList(); + + if (interfaces.Count > 0) + { + // Prioritize Ethernet/Wifi + var bestMatch = interfaces.FirstOrDefault(n => n.NetworkInterfaceType == NetworkInterfaceType.Ethernet || n.NetworkInterfaceType == NetworkInterfaceType.Wireless80211) + ?? interfaces.First(); + + info.InterfaceName = bestMatch.Name; + info.InterfaceDescription = bestMatch.Description; + info.InterfaceType = bestMatch.NetworkInterfaceType.ToString(); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + info.Message = "Failed to gather system info"; + } + return info; + } + + private async Task SendReportAsync(ReportResult report) + { + try + { + using var client = new HttpClient(); + var json = JsonSerializer.Serialize(report); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await client.PostAsync(_config.ReportItem.ReportUrl, content); + response.EnsureSuccessStatusCode(); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + throw; // Re-throw to show error in UI + } + } +} + +public class ReportResult +{ + public SystemInfo SystemInfo { get; set; } + public ConnectionQuality ConnectionQuality { get; set; } + public ProtocolInfo ProtocolInfo { get; set; } +} + +public class SystemInfo +{ + public string InterfaceName { get; set; } + public string InterfaceDescription { get; set; } // Often contains SIM/ISP info (e.g., "Intel Wi-Fi", "Quectel Mobile Broadband") + public string InterfaceType { get; set; } + public string Message { get; set; } +} + +public class ConnectionQuality +{ + public int Latency { get; set; } + public bool Success { get; set; } + public string Message { get; set; } +} + +public class ProtocolInfo +{ + public string Protocol { get; set; } + public string Remarks { get; set; } + public string Address { get; set; } +} diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 86251873..a407c1d0 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -67,6 +67,7 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public string SrsFileSourceUrl { get; set; } [Reactive] public string RoutingRulesSourceUrl { get; set; } [Reactive] public string IPAPIUrl { get; set; } + [Reactive] public string ReportUrl { get; set; } #endregion UI @@ -201,6 +202,9 @@ public class OptionSettingViewModel : MyReactiveObject SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl; RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl; IPAPIUrl = _config.SpeedTestItem.IPAPIUrl; + + if (_config.ReportItem == null) _config.ReportItem = new ReportItem(); + ReportUrl = _config.ReportItem.ReportUrl; #endregion UI @@ -367,6 +371,7 @@ public class OptionSettingViewModel : MyReactiveObject _config.ConstItem.SrsSourceUrl = SrsFileSourceUrl; _config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl; _config.SpeedTestItem.IPAPIUrl = IPAPIUrl; + _config.ReportItem.ReportUrl = ReportUrl; //systemProxy _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; diff --git a/v2rayN/v2rayN/App.xaml.cs b/v2rayN/v2rayN/App.xaml.cs index ca56311b..56976a4a 100644 --- a/v2rayN/v2rayN/App.xaml.cs +++ b/v2rayN/v2rayN/App.xaml.cs @@ -18,7 +18,7 @@ public partial class App : Application /// Open only one process /// /// - protected override void OnStartup(StartupEventArgs e) + protected override async void OnStartup(StartupEventArgs e) { var exePathKey = Utils.GetMd5(Utils.GetExePath()); @@ -38,6 +38,18 @@ public partial class App : Application return; } + if (AppManager.Instance.Config.GuiItem.PrivacyNoticeAccepted == false) + { + var window = new Views.PrivacyNoticeWindow(); + if (window.ShowDialog() != true) + { + Environment.Exit(0); + return; + } + AppManager.Instance.Config.GuiItem.PrivacyNoticeAccepted = true; + await ConfigHandler.SaveConfig(AppManager.Instance.Config); + } + AppManager.Instance.InitComponents(); base.OnStartup(e); } diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 1c49ca24..9ebf36b9 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:view="clr-namespace:v2rayN.Views" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" - Title="v2rayN" + Title="shi2ray" Width="1200" Height="800" MinWidth="800" diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 00b2dc9e..59f3efdb 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -522,6 +522,33 @@ --> + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index 8aa0cd02..daec556b 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -110,6 +110,8 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.ReportUrl, v => v.txtReportUrl.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); this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml b/v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml new file mode 100644 index 00000000..185f767c --- /dev/null +++ b/v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml @@ -0,0 +1,25 @@ + + + + + + + + + Note that we use connection data only for improving the services and we do not take the private information. + + +