diff --git a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs new file mode 100644 index 00000000..61a83a81 --- /dev/null +++ b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using ReactiveUI; +using Wabbajack.Common; +using Wabbajack.Common.StatusFeed; +using Wabbajack.Lib.LibCefHelpers; +using Wabbajack.Lib.WebAutomation; + +namespace Wabbajack.Lib.Downloaders +{ + public abstract class AbstractNeedsLoginDownloader : INeedsLogin + { + private readonly Uri _loginUri; + private readonly string _encryptedKeyName; + private readonly string _cookieDomain; + private readonly string _cookieName; + protected HttpClient AuthedClient; + + /// + /// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log + /// in via a browser + /// + /// The URI to preset for logging in + /// The name of the encrypted JSON key in which to store cookies + /// The cookie domain to scan + /// The cookie name to wait for + public AbstractNeedsLoginDownloader(Uri loginUri, + string encryptedKeyName, + string cookieDomain, + string cookieName) + { + _loginUri = loginUri; + _encryptedKeyName = encryptedKeyName; + _cookieDomain = cookieDomain; + _cookieName = cookieName; + + TriggerLogin = ReactiveCommand.CreateFromTask( + execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestSiteLogin(this)).Task), + canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); + ClearLogin = ReactiveCommand.Create( + execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)), + canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); + } + + public ICommand TriggerLogin { get; } + public ICommand ClearLogin { get; } + public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName); + public abstract string SiteName { get; } + public virtual string MetaInfo { get; } + public abstract Uri SiteURL { get; } + public virtual Uri IconUri { get; } + + protected virtual async Task WhileWaiting(IWebDriver browser) + { + } + + public async Task GetAndCacheCookies(IWebDriver browser, Action updateStatus, CancellationToken cancel) + { + updateStatus($"Please Log Into {SiteName}"); + await browser.NavigateTo(_loginUri); + var cookies = new Helpers.Cookie[0]; + while (true) + { + cancel.ThrowIfCancellationRequested(); + await WhileWaiting(browser); + cookies = (await browser.GetCookies(_cookieDomain)); + if (cookies.FirstOrDefault(c => c.Name == _cookieName) != null) + break; + await Task.Delay(500, cancel); + } + + cookies.ToEcryptedJson(_encryptedKeyName); + + return cookies; + } + + public async Task GetAuthedClient() + { + Helpers.Cookie[] cookies; + try + { + cookies = Utils.FromEncryptedJson(_encryptedKeyName); + if (cookies != null) + return Helpers.GetClient(cookies, SiteURL.ToString()); + } + catch (FileNotFoundException) { } + + cookies = await Utils.Log(new RequestSiteLogin(this)).Task; + return Helpers.GetClient(cookies, SiteURL.ToString()); + } + + public async Task Prepare() + { + AuthedClient = (await GetAuthedClient()) ?? throw new NotLoggedInError(this); + } + + public class NotLoggedInError : Exception + { + public AbstractNeedsLoginDownloader Downloader { get; } + public NotLoggedInError(AbstractNeedsLoginDownloader downloader) : base( + $"Not logged into {downloader.SiteName}, can't continue") + { + Downloader = downloader; + } + } + + + public class RequestSiteLogin : AUserIntervention + { + public AbstractNeedsLoginDownloader Downloader { get; } + public RequestSiteLogin(AbstractNeedsLoginDownloader downloader) + { + Downloader = downloader; + } + public override string ShortDescription => $"Getting {Downloader.SiteName} Login"; + public override string ExtendedDescription { get; } + + private readonly TaskCompletionSource _source = new TaskCompletionSource(); + public Task Task => _source.Task; + + public void Resume(Helpers.Cookie[] cookies) + { + Handled = true; + _source.SetResult(cookies); + } + + public override void Cancel() + { + Handled = true; + _source.TrySetCanceled(); + } + } + } + + +} diff --git a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs index 1802b605..87d4edf4 100644 --- a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs +++ b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using System.Web; using System.Windows.Input; +using CefSharp; using ReactiveUI; using Wabbajack.Common; using Wabbajack.Lib.LibCefHelpers; @@ -20,32 +21,17 @@ using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.Lib.Downloaders { - public class LoversLabDownloader : IDownloader, INeedsLogin + public class LoversLabDownloader : AbstractNeedsLoginDownloader, IDownloader { - internal HttpClient _authedClient; - - #region INeedsDownload - - public ICommand TriggerLogin { get; } - public ICommand ClearLogin { get; } - public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable("loverslabcookies"); - public string SiteName => "Lovers Lab"; - public string MetaInfo => ""; - public Uri SiteURL => new Uri("https://loverslab.com"); - public Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico"); - - + public override string SiteName => "Lovers Lab"; + public override Uri SiteURL => new Uri("https://loverslab.com"); + public override Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico"); #endregion - public LoversLabDownloader() + public LoversLabDownloader() : base(new Uri("https://www.loverslab.com/login"), + "loverslabcookies", "loverslab.com", "ips4_member_id") { - TriggerLogin = ReactiveCommand.CreateFromTask( - execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestLoversLabLogin()).Task), - canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); - ClearLogin = ReactiveCommand.Create( - execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson("loverslabcookies")), - canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); } @@ -62,58 +48,17 @@ namespace Wabbajack.Lib.Downloaders FileName = file }; } - - public async Task Prepare() + protected override async Task WhileWaiting(IWebDriver browser) { - _authedClient = (await GetAuthedClient()) ?? throw new Exception("not logged into LL, TODO"); - } - - public static async Task GetAndCacheLoversLabCookies(IWebDriver browser, Action updateStatus, CancellationToken cancel) - { - updateStatus("Please Log Into Lovers Lab"); - await browser.NavigateTo(new Uri("https://www.loverslab.com/login")); - async Task CleanAds() - { - try - { - await browser.EvaluateJavaScript( - "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});"); - } - catch (Exception ex) - { - Utils.Error(ex); - } - return false; - } - var cookies = new Helpers.Cookie[0]; - while (true) - { - cancel.ThrowIfCancellationRequested(); - await CleanAds(); - cookies = (await browser.GetCookies("loverslab.com")); - if (cookies.FirstOrDefault(c => c.Name == "ips4_member_id") != null) - break; - await Task.Delay(500, cancel); - } - - cookies.ToEcryptedJson("loverslabcookies"); - - return cookies; - } - - public async Task GetAuthedClient() - { - Helpers.Cookie[] cookies; try { - cookies = Utils.FromEncryptedJson("loverslabcookies"); - if (cookies != null) - return Helpers.GetClient(cookies, "https://www.loverslab.com"); + await browser.EvaluateJavaScript( + "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});"); + } + catch (Exception ex) + { + Utils.Error(ex); } - catch (FileNotFoundException) { } - - cookies = await Utils.Log(new RequestLoversLabLogin()).Task; - return Helpers.GetClient(cookies, "https://www.loverslab.com"); } public class State : AbstractDownloadState @@ -141,7 +86,7 @@ namespace Wabbajack.Lib.Downloaders { var result = DownloadDispatcher.GetInstance(); TOP: - var html = await result._authedClient.GetStringAsync( + var html = await result.AuthedClient.GetStringAsync( $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}"); var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])"); @@ -153,7 +98,7 @@ namespace Wabbajack.Lib.Downloaders var url = $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}"; - var streamResult = await result._authedClient.GetAsync(url); + var streamResult = await result.AuthedClient.GetAsync(url); if (streamResult.StatusCode != HttpStatusCode.OK) { Utils.Error(new InvalidOperationException(), $"LoversLab servers reported an error for file: {FileID}"); @@ -208,25 +153,4 @@ namespace Wabbajack.Lib.Downloaders } } - - public class RequestLoversLabLogin : AUserIntervention - { - public override string ShortDescription => "Getting LoversLab information"; - public override string ExtendedDescription { get; } - - private readonly TaskCompletionSource _source = new TaskCompletionSource(); - public Task Task => _source.Task; - - public void Resume(Helpers.Cookie[] cookies) - { - Handled = true; - _source.SetResult(cookies); - } - - public override void Cancel() - { - Handled = true; - _source.TrySetCanceled(); - } - } } diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index a78d73b3..93863e14 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -123,6 +123,7 @@ + diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs index 7a2ef7cd..c88fa4e4 100644 --- a/Wabbajack/View Models/UserInterventionHandlers.cs +++ b/Wabbajack/View Models/UserInterventionHandlers.cs @@ -66,11 +66,11 @@ namespace Wabbajack c.Resume(key); }); break; - case RequestLoversLabLogin c: + case AbstractNeedsLoginDownloader.RequestSiteLogin c: await WrapBrowserJob(msg, async (vm, cancel) => { await vm.Driver.WaitForInitialized(); - var data = await LoversLabDownloader.GetAndCacheLoversLabCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); + var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); c.Resume(data); }); break;