diff --git a/v2rayN/ServiceLib/Handler/SubscriptionHandler.cs b/v2rayN/ServiceLib/Handler/SubscriptionHandler.cs index b06700f8..f93eb2fa 100644 --- a/v2rayN/ServiceLib/Handler/SubscriptionHandler.cs +++ b/v2rayN/ServiceLib/Handler/SubscriptionHandler.cs @@ -15,119 +15,200 @@ public class SubscriptionHandler foreach (var item in subItem) { - var id = item.Id.TrimEx(); - var url = item.Url.TrimEx(); - var userAgent = item.UserAgent.TrimEx(); - var hashCode = $"{item.Remarks}->"; - if (id.IsNullOrEmpty() || url.IsNullOrEmpty() || (subId.IsNotEmpty() && item.Id != subId)) + try { - //_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}"); - continue; - } - if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol)) - { - continue; - } - if (item.Enabled == false) - { - updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); - continue; - } - - var downloadHandle = new DownloadService(); - downloadHandle.Error += (sender2, args) => - { - updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); - }; - - updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); - - //one url - url = Utils.GetPunycode(url); - //convert - if (item.ConvertTarget.IsNotEmpty()) - { - var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl; - url = string.Format(subConvertUrl!, Utils.UrlEncode(url)); - if (!url.Contains("target=")) + if (!IsValidSubscription(item, subId)) { - url += string.Format("&target={0}", item.ConvertTarget); - } - if (!url.Contains("config=")) - { - url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); - } - } - var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); - if (blProxy && result.IsNullOrEmpty()) - { - result = await downloadHandle.TryDownloadString(url, false, userAgent); - } - - //more url - if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) - { - if (result.IsNotEmpty() && Utils.IsBase64String(result)) - { - result = Utils.Base64Decode(result); + continue; } - var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; - foreach (var it in lstUrl) + var hashCode = $"{item.Remarks}->"; + if (item.Enabled == false) { - var url2 = Utils.GetPunycode(it); - if (url2.IsNullOrEmpty()) - { - continue; - } - - var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent); - if (blProxy && result2.IsNullOrEmpty()) - { - result2 = await downloadHandle.TryDownloadString(url2, false, userAgent); - } - if (result2.IsNotEmpty()) - { - if (Utils.IsBase64String(result2)) - { - result += Environment.NewLine + Utils.Base64Decode(result2); - } - else - { - result += Environment.NewLine + result2; - } - } + updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); + continue; } - } - if (result.IsNullOrEmpty()) + // Create download handler + var downloadHandle = CreateDownloadHandler(hashCode, updateFunc); + updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); + + // Get all subscription content (main subscription + additional subscriptions) + var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle); + + // Process download result + await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc); + + updateFunc?.Invoke(false, "-------------------------------------------------------"); + } + catch (Exception ex) { - updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); + var hashCode = $"{item.Remarks}->"; + Logging.SaveLog("UpdateSubscription", ex); + updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}"); + updateFunc?.Invoke(false, "-------------------------------------------------------"); } - else - { - updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); - if (result?.Length < 99) - { - updateFunc?.Invoke(false, $"{hashCode}{result}"); - } - - var ret = await ConfigHandler.AddBatchServers(config, result, id, true); - if (ret <= 0) - { - Logging.SaveLog("FailedImportSubscription"); - Logging.SaveLog(result); - } - updateFunc?.Invoke(false, - ret > 0 - ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" - : $"{hashCode}{ResUI.MsgFailedImportSubscription}"); - } - updateFunc?.Invoke(false, "-------------------------------------------------------"); - - //await ConfigHandler.DedupServerList(config, id); } updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}"); } + + private static bool IsValidSubscription(SubItem item, string subId) + { + var id = item.Id.TrimEx(); + var url = item.Url.TrimEx(); + + if (id.IsNullOrEmpty() || url.IsNullOrEmpty()) + { + return false; + } + + if (subId.IsNotEmpty() && item.Id != subId) + { + return false; + } + + if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol)) + { + return false; + } + + return true; + } + + private static DownloadService CreateDownloadHandler(string hashCode, Action updateFunc) + { + var downloadHandle = new DownloadService(); + downloadHandle.Error += (sender2, args) => + { + updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); + }; + return downloadHandle; + } + + private static async Task DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent) + { + var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); + + // If download with proxy fails, try direct connection + if (blProxy && result.IsNullOrEmpty()) + { + result = await downloadHandle.TryDownloadString(url, false, userAgent); + } + + return result ?? string.Empty; + } + + private static async Task DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle) + { + // Download main subscription content + var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle); + + // Process additional subscription links (if any) + if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) + { + result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle); + } + + return result; + } + + private static async Task DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle) + { + // Prepare subscription URL and download directly + var url = Utils.GetPunycode(item.Url.TrimEx()); + + // If conversion is needed + if (item.ConvertTarget.IsNotEmpty()) + { + var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() + ? Global.SubConvertUrls.FirstOrDefault() + : config.ConstItem.SubConvertUrl; + + url = string.Format(subConvertUrl!, Utils.UrlEncode(url)); + + if (!url.Contains("target=")) + { + url += string.Format("&target={0}", item.ConvertTarget); + } + + if (!url.Contains("config=")) + { + url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); + } + } + + // Download and return result directly + return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent); + } + + private static async Task DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle) + { + var result = mainResult; + + // If main subscription result is Base64 encoded, decode it first + if (result.IsNotEmpty() && Utils.IsBase64String(result)) + { + result = Utils.Base64Decode(result); + } + + // Process additional URL list + var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; + foreach (var it in lstUrl) + { + var url2 = Utils.GetPunycode(it); + if (url2.IsNullOrEmpty()) + { + continue; + } + + var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent); + + if (additionalResult.IsNotEmpty()) + { + // Process additional subscription results, add to main result + if (Utils.IsBase64String(additionalResult)) + { + result += Environment.NewLine + Utils.Base64Decode(additionalResult); + } + else + { + result += Environment.NewLine + additionalResult; + } + } + } + + return result; + } + + private static async Task ProcessDownloadResult(Config config, string id, string result, string hashCode, Action updateFunc) + { + if (result.IsNullOrEmpty()) + { + updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); + return; + } + + updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); + + // If result is too short, display content directly + if (result.Length < 99) + { + updateFunc?.Invoke(false, $"{hashCode}{result}"); + } + + // Add servers to configuration + var ret = await ConfigHandler.AddBatchServers(config, result, id, true); + if (ret <= 0) + { + Logging.SaveLog("FailedImportSubscription"); + Logging.SaveLog(result); + } + + // Update completion message + updateFunc?.Invoke(false, + ret > 0 + ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" + : $"{hashCode}{ResUI.MsgFailedImportSubscription}"); + } }