diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index a87e4ba1..8e33e840 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -10,6 +10,7 @@ using Wabbajack.App.Blazor.Utility; using Wabbajack.Services.OSIntegrated; using Blazored.Modal; using Blazored.Toast; +using Wabbajack.App.Blazor.Browser.ViewModels; namespace Wabbajack.App.Blazor; @@ -72,6 +73,7 @@ public partial class App services.AddBlazoredModal(); services.AddBlazoredToast(); services.AddTransient(); + services.AddTransient(); services.AddSingleton(); services.AddSingleton(); return services; diff --git a/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml b/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml index fad4bd13..0cbc4305 100644 --- a/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml +++ b/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Wabbajack.App.Blazor.Browser" + xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> @@ -11,8 +12,7 @@ - _ - + _ diff --git a/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml.cs b/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml.cs index c2e78f80..c88f1719 100644 --- a/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml.cs +++ b/Wabbajack.App.Blazor/Browser/BrowserTabView.xaml.cs @@ -1,8 +1,11 @@ using System; using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using ReactiveUI; +using Wabbajack.Common; namespace Wabbajack.App.Blazor.Browser; @@ -15,19 +18,30 @@ public partial class BrowserTabView : IDisposable _compositeDisposable = new CompositeDisposable(); InitializeComponent(); Browser.Browser.Source = new Uri("http://www.google.com"); + vm.Browser = Browser; DataContext = vm; vm.WhenAnyValue(vm => vm.HeaderText) .BindTo(this, view => view.HeaderText.Text) .DisposeWith(_compositeDisposable); + + Start().FireAndForget(); + } + + private async Task Start() + { + await ((BrowserTabViewModel) DataContext).RunWrapper(CancellationToken.None); + ClickClose(this, new RoutedEventArgs()); } public void Dispose() { _compositeDisposable.Dispose(); + var vm = (BrowserTabViewModel) DataContext; + vm.Browser = null; } - private void ButtonBase_OnClick(object sender, RoutedEventArgs e) + private void ClickClose(object sender, RoutedEventArgs e) { var tc = (TabControl) this.Parent; tc.Items.Remove(this); diff --git a/Wabbajack.App.Blazor/Browser/BrowserTabViewModel.cs b/Wabbajack.App.Blazor/Browser/BrowserTabViewModel.cs index 6dbab4b8..c9f4aa69 100644 --- a/Wabbajack.App.Blazor/Browser/BrowserTabViewModel.cs +++ b/Wabbajack.App.Blazor/Browser/BrowserTabViewModel.cs @@ -1,10 +1,84 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using HtmlAgilityPack; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.Wpf; using ReactiveUI.Fody.Helpers; +using Wabbajack.DTOs.Logins; namespace Wabbajack.App.Blazor.Browser; -public class BrowserTabViewModel : ViewModel +public abstract class BrowserTabViewModel : ViewModel { [Reactive] public string HeaderText { get; set; } + [Reactive] + public string Instructions { get; set; } + + public BrowserView? Browser { get; set; } + + private WebView2 _browser => Browser!.Browser; + + public async Task RunWrapper(CancellationToken token) + { + await Run(token); + } + + protected abstract Task Run(CancellationToken token); + + public async Task NavigateTo(Uri uri) + { + var tcs = new TaskCompletionSource(); + + void Completed(object? o, CoreWebView2NavigationCompletedEventArgs a) + { + if (a.IsSuccess) + { + tcs.TrySetResult(); + } + else + { + tcs.TrySetException(new Exception($"Navigation error to {uri}")); + } + } + + _browser.NavigationCompleted += Completed; + _browser.Source = uri; + await tcs.Task; + _browser.NavigationCompleted -= Completed; + } + + public async Task GetCookies(string domainEnding, CancellationToken token) + { + var cookies = (await _browser.CoreWebView2.CookieManager.GetCookiesAsync("")) + .Where(c => c.Domain.EndsWith(domainEnding)); + return cookies.Select(c => new Cookie + { + Domain = c.Domain, + Name = c.Name, + Path = c.Path, + Value = c.Value + }).ToArray(); + } + + public async Task EvaluateJavaScript(string js) + { + return await _browser.ExecuteScriptAsync(js); + } + + public async Task GetDom(CancellationToken token) + { + var v = HttpUtility.UrlDecode("\u003D"); + var source = await EvaluateJavaScript("document.body.outerHTML"); + var decoded = JsonSerializer.Deserialize(source); + var doc = new HtmlDocument(); + doc.LoadHtml(decoded); + return doc; + } + } diff --git a/Wabbajack.App.Blazor/Browser/BrowserView.xaml b/Wabbajack.App.Blazor/Browser/BrowserView.xaml index 07528384..9b38a902 100644 --- a/Wabbajack.App.Blazor/Browser/BrowserView.xaml +++ b/Wabbajack.App.Blazor/Browser/BrowserView.xaml @@ -7,10 +7,28 @@ xmlns:local="clr-namespace:Wabbajack.App.Blazor.Browser" xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" xmlns:reactiveUi="http://reactiveui.net" + xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + + + + + + + + + + + + + + diff --git a/Wabbajack.App.Blazor/Browser/ViewModels/NexusLogin.cs b/Wabbajack.App.Blazor/Browser/ViewModels/NexusLogin.cs index ceca7080..19b786a6 100644 --- a/Wabbajack.App.Blazor/Browser/ViewModels/NexusLogin.cs +++ b/Wabbajack.App.Blazor/Browser/ViewModels/NexusLogin.cs @@ -1,11 +1,94 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Fizzler.Systems.HtmlAgilityPack; using Wabbajack.DTOs.Logins; +using Wabbajack.Services.OSIntegrated; namespace Wabbajack.App.Blazor.Browser.ViewModels; public class NexusLogin : BrowserTabViewModel { - public NexusLogin() + private readonly EncryptedJsonTokenProvider _tokenProvider; + + public NexusLogin(EncryptedJsonTokenProvider tokenProvider) { HeaderText = "Nexus Login"; + _tokenProvider = tokenProvider; + } + + protected override async Task Run(CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + Instructions = "Please log into the Nexus"; + + await NavigateTo(new Uri( + "https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com")); + + + Cookie[] cookies = { }; + while (true) + { + cookies = await GetCookies("nexusmods.com", token); + if (cookies.Any(c => c.Name == "member_id")) + break; + + token.ThrowIfCancellationRequested(); + await Task.Delay(500, token); + } + + Instructions = "Getting API Key..."; + + await NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api")); + + var key = ""; + + while (true) + { + try + { + key = (await GetDom(token)) + .DocumentNode + .QuerySelectorAll("input[value=wabbajack]") + .SelectMany(p => p.ParentNode.ParentNode.QuerySelectorAll("textarea.application-key")) + .Select(node => node.InnerHtml) + .FirstOrDefault() ?? ""; + } + catch (Exception) + { + // ignored + } + + if (!string.IsNullOrEmpty(key)) + break; + + try + { + await EvaluateJavaScript( + "var found = document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"form button[type=submit]\");" + + "found.onclick= function() {return true;};" + + "found.class = \" \"; " + + "found.click();" + + "found.remove(); found = undefined;" + ); + Instructions = "Generating API Key, Please Wait..."; + } + catch (Exception) + { + // ignored + } + + token.ThrowIfCancellationRequested(); + await Task.Delay(500, token); + } + + Instructions = "Success, saving information..."; + await _tokenProvider.SetToken(new NexusApiState + { + Cookies = cookies, + ApiKey = key + }); } } diff --git a/Wabbajack.App.Blazor/MainWindow.xaml b/Wabbajack.App.Blazor/MainWindow.xaml index a2172ade..fb7d6b09 100644 --- a/Wabbajack.App.Blazor/MainWindow.xaml +++ b/Wabbajack.App.Blazor/MainWindow.xaml @@ -21,11 +21,6 @@ - - - Manual Download - - diff --git a/Wabbajack.App.Blazor/Pages/Settings.razor b/Wabbajack.App.Blazor/Pages/Settings.razor index b1597c82..1ee55e52 100644 --- a/Wabbajack.App.Blazor/Pages/Settings.razor +++ b/Wabbajack.App.Blazor/Pages/Settings.razor @@ -2,8 +2,11 @@ @using ReactiveUI @using Wabbajack.App.Blazor.Browser.ViewModels @using Wabbajack.App.Blazor.Messages +@using Microsoft.Extensions.DependencyInjection @namespace Wabbajack.App.Blazor.Pages +@inject IServiceProvider _serviceProvider; +
@@ -16,6 +19,6 @@ public void LoginToNexus() { - MessageBus.Current.SendMessage(new OpenBrowserTab(new NexusLogin())); + MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService())); } } diff --git a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj index 9dff5ada..386406b0 100644 --- a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj +++ b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj @@ -17,8 +17,10 @@ + + diff --git a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs index 57247400..3f344507 100644 --- a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs +++ b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs @@ -113,7 +113,7 @@ public static class ServiceExtensions service.AddSingleton(); // Token Providers - service.AddAllSingleton, NexusApiTokenProvider>(); + service.AddAllSingleton, EncryptedJsonTokenProvider, NexusApiTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, LoversLabTokenProvider>();