From 52369e84dca9c6469cbbab9ab6ead7ce6d2f2d4b Mon Sep 17 00:00:00 2001 From: Elwador <75888166+Elwador@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:10:08 +0200 Subject: [PATCH] Chg - Adjusted HTTP Client to allow the usage of VPN split tunneling https://github.com/Crunchy-DL/Crunchy-Downloader/issues/57 Chg - Adjusted some debug messages --- CRD/Downloader/Crunchyroll/CRAuth.cs | 6 +- .../Crunchyroll/CrunchyrollManager.cs | 4 +- CRD/Downloader/QueueManager.cs | 19 ++-- CRD/Utils/Http/HttpClientReq.cs | 97 +++++++++++++------ CRD/ViewModels/AddDownloadPageViewModel.cs | 7 ++ 5 files changed, 97 insertions(+), 36 deletions(-) diff --git a/CRD/Downloader/Crunchyroll/CRAuth.cs b/CRD/Downloader/Crunchyroll/CRAuth.cs index 4fada42..b239501 100644 --- a/CRD/Downloader/Crunchyroll/CRAuth.cs +++ b/CRD/Downloader/Crunchyroll/CRAuth.cs @@ -79,7 +79,11 @@ public class CrAuth{ if (response.IsOk){ JsonTokenToFileAndVariable(response.ResponseContent); } else{ - MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - {response.ResponseContent.Substring(0,response.ResponseContent.Length < 200 ? response.ResponseContent.Length : 200)}", ToastType.Error, 10)); + if (response.ResponseContent.Contains("invalid_credentials")){ + MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - because of invalid login credentials", ToastType.Error, 10)); + } else{ + MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - {response.ResponseContent.Substring(0,response.ResponseContent.Length < 200 ? response.ResponseContent.Length : 200)}", ToastType.Error, 10)); + } } if (crunInstance.Token?.refresh_token != null){ diff --git a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs index d3268fe..7d6f973 100644 --- a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs +++ b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs @@ -899,7 +899,7 @@ public class CrunchyrollManager{ tsFile = videoDownloadResult.tsFile; if (!videoDownloadResult.Ok){ - Console.Error.WriteLine($"DL Stats: {JsonConvert.SerializeObject(videoDownloadResult.Parts)}"); + Console.Error.WriteLine($"Faild to download video - DL Stats: {JsonConvert.SerializeObject(videoDownloadResult.Parts)}"); dlFailed = true; } @@ -918,7 +918,7 @@ public class CrunchyrollManager{ tsFile = audioDownloadResult.tsFile; if (!audioDownloadResult.Ok){ - Console.Error.WriteLine($"DL Stats: {JsonConvert.SerializeObject(audioDownloadResult.Parts)}"); + Console.Error.WriteLine($"Faild to download audio - DL Stats: {JsonConvert.SerializeObject(audioDownloadResult.Parts)}"); dlFailed = true; } diff --git a/CRD/Downloader/QueueManager.cs b/CRD/Downloader/QueueManager.cs index 1b130d2..a6f13e8 100644 --- a/CRD/Downloader/QueueManager.cs +++ b/CRD/Downloader/QueueManager.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using CRD.Downloader.Crunchyroll; +using CRD.Utils; using CRD.Utils.CustomList; using CRD.Utils.Structs; using CRD.Utils.Structs.History; @@ -142,12 +144,17 @@ public class QueueManager{ } selected.DownloadSubs = historyEpisode.sublist.Count > 0 ? historyEpisode.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs; - - Queue.Add(selected); + Queue.Add(selected); if (selected.Data.Count < dubLang.Count){ Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs"); + Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: "); + + var languages = sList.EpisodeAndLanguages.Items.Select((a, index) => + $"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).CrLocale ?? "Unknown"}").ToArray(); + + Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ?? [])}]"); MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue but couldn't find all selected dubs", ToastType.Warning, 2)); } else{ Console.WriteLine("Added Episode to Queue"); @@ -211,9 +218,9 @@ public class QueueManager{ var selected = CrunchyrollManager.Instance.CrSeries.ItemSelectMultiDub(list.Data, data.DubLang, data.But, data.AllEpisodes, data.E); bool failed = false; - + foreach (var crunchyEpMeta in selected.Values.ToList()){ - if (crunchyEpMeta.Data?.First().Playback != null){ + if (crunchyEpMeta.Data?.First() != null){ if (CrunchyrollManager.Instance.CrunOptions.History){ var historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId); if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){ @@ -243,8 +250,8 @@ public class QueueManager{ var subLangList = CrunchyrollManager.Instance.History.GetSubList(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId); crunchyEpMeta.DownloadSubs = subLangList.Count > 0 ? subLangList : CrunchyrollManager.Instance.CrunOptions.DlSubs; - - + + Queue.Add(crunchyEpMeta); } else{ failed = true; diff --git a/CRD/Utils/Http/HttpClientReq.cs b/CRD/Utils/Http/HttpClientReq.cs index e55c308..7c4aad2 100644 --- a/CRD/Utils/Http/HttpClientReq.cs +++ b/CRD/Utils/Http/HttpClientReq.cs @@ -4,6 +4,8 @@ using System.Net.Http; using System.Collections.Generic; using System.Collections.Specialized; using System.Net.Http.Headers; +using System.Net.Sockets; +using System.Text; using System.Threading.Tasks; using CRD.Downloader; using CRD.Downloader.Crunchyroll; @@ -34,49 +36,75 @@ public class HttpClientReq{ private HttpClient client; - private HttpClientHandler handler; + private Dictionary cookieStore; public HttpClientReq(){ - // Initialize the HttpClientHandler - handler = new HttpClientHandler(); - handler.CookieContainer = new CookieContainer(); - handler.UseCookies = true; - handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli; + cookieStore = new Dictionary(); + + client = new HttpClient(CreateHttpClientHandler()); - // Initialize the HttpClient with the handler - client = new HttpClient(handler); - // client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"); client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/1.9.0 Nintendo Switch/18.1.0.0 UE4/4.27"); // client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/3.60.0 Android/9 okhttp/4.12.0"); - } - public void SetETPCookie(string refresh_token){ - var cookie = new Cookie("etp_rt", refresh_token){ - Domain = "crunchyroll.com", - Path = "/", - }; - - var cookie2 = new Cookie("c_locale", "en-US"){ - Domain = "crunchyroll.com", - Path = "/", - }; + private HttpMessageHandler CreateHttpClientHandler(){ + return new SocketsHttpHandler(){ + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli, + ConnectCallback = async (context, cancellationToken) => { + // Resolve IPv4 addresses only + var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, AddressFamily.InterNetwork, cancellationToken).ConfigureAwait(false); - handler.CookieContainer.Add(cookie); - handler.CookieContainer.Add(cookie2); + // Create an IPv4 socket + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.NoDelay = true; + + try{ + await socket.ConnectAsync(entry.AddressList, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false); + return new NetworkStream(socket, ownsSocket: true); + } catch{ + socket.Dispose(); + throw; + } + } + }; + } + + + public void SetETPCookie(string refresh_token){ + // var cookie = new Cookie("etp_rt", refresh_token){ + // Domain = "crunchyroll.com", + // Path = "/", + // }; + // + // var cookie2 = new Cookie("c_locale", "en-US"){ + // Domain = "crunchyroll.com", + // Path = "/", + // }; + + AddCookie("crunchyroll.com", new Cookie("etp_rt", refresh_token)); + AddCookie("crunchyroll.com", new Cookie("c_locale", "en-US")); + } + + private void AddCookie(string domain, Cookie cookie){ + if (!cookieStore.ContainsKey(domain)){ + cookieStore[domain] = new CookieCollection(); + } + + cookieStore[domain].Add(cookie); } public async Task<(bool IsOk, string ResponseContent)> SendHttpRequest(HttpRequestMessage request){ - string content = string.Empty; try{ + AttachCookies(request); + HttpResponseMessage response = await client.SendAsync(request); content = await response.Content.ReadAsStringAsync(); - + response.EnsureSuccessStatusCode(); - + return (IsOk: true, ResponseContent: content); } catch (Exception e){ // Console.Error.WriteLine($"Error: {e} \n Response: {(content.Length < 500 ? content : "error to long")}"); @@ -85,6 +113,23 @@ public class HttpClientReq{ } } + private void AttachCookies(HttpRequestMessage request){ + if (cookieStore.TryGetValue(request.RequestUri.Host, out CookieCollection cookies)){ + var cookieHeader = new StringBuilder(); + foreach (Cookie cookie in cookies){ + if (cookieHeader.Length > 0){ + cookieHeader.Append("; "); + } + + cookieHeader.Append($"{cookie.Name}={cookie.Value}"); + } + + if (cookieHeader.Length > 0){ + request.Headers.Add("Cookie", cookieHeader.ToString()); + } + } + } + public static HttpRequestMessage CreateRequestMessage(string uri, HttpMethod requestMethod, bool authHeader, bool disableDrmHeader, NameValueCollection? query){ UriBuilder uriBuilder = new UriBuilder(uri); @@ -99,7 +144,6 @@ public class HttpClientReq{ } if (disableDrmHeader){ - } @@ -114,7 +158,6 @@ public class HttpClientReq{ public HttpClient GetHttpClient(){ return client; } - } public static class Api{ diff --git a/CRD/ViewModels/AddDownloadPageViewModel.cs b/CRD/ViewModels/AddDownloadPageViewModel.cs index 2397904..8fa36d9 100644 --- a/CRD/ViewModels/AddDownloadPageViewModel.cs +++ b/CRD/ViewModels/AddDownloadPageViewModel.cs @@ -88,6 +88,13 @@ public partial class AddDownloadPageViewModel : ViewModelBase{ } private async Task UpdateSearch(string value){ + if (string.IsNullOrEmpty(value)){ + SearchPopupVisible = false; + RaisePropertyChanged(nameof(SearchVisible)); + SearchItems.Clear(); + return; + } + var searchResults = await CrunchyrollManager.Instance.CrSeries.Search(value, CrunchyrollManager.Instance.CrunOptions.HistoryLang); var searchItems = searchResults?.Data?.First().Items;