diff --git a/Wabbajack.CLI/Browser/BrowserExtensions.cs b/Wabbajack.CLI/Browser/BrowserExtensions.cs index c2daf11f..3cf8bdfb 100644 --- a/Wabbajack.CLI/Browser/BrowserExtensions.cs +++ b/Wabbajack.CLI/Browser/BrowserExtensions.cs @@ -11,57 +11,6 @@ namespace Wabbajack.CLI.Browser; public static class BrowserExtensions { - public static async Task WaitForReady(this WebView browser) - { - while (!browser.IsInitialized) - { - await Task.Delay(250); - } - - while (browser.BrowserObject == null) - { - await Task.Delay(250); - } - } - public static async Task NavigateTo(this WebView browser, Uri location) - { - browser.Navigate(location.ToString()); - await browser.WaitForIdle(); - } - - public static async Task WaitForIdle(this WebView browser) - { - while (browser.IsBusy) - { - await Task.Delay(250); - } - } - - public static async Task Cookies(this WebView view, string domainEnding, CancellationToken token) - { - var results = CefCookieManager.GetGlobalManager(null)!; - var cookies = await results.GetCookiesAsync(c => c.Domain.EndsWith(domainEnding), token)!; - return cookies.Select(c => new Cookie - { - Domain = c.Domain, - Name = c.Name, - Path = c.Path, - Value = c.Value - }).ToArray(); - } - - public static async Task EvaluateJavaScript(this WebView view, string js) - { - view.GetMainFrame().ExecuteJavaScript(js, "", 0); - } - - public static async Task GetDom(this WebView view, CancellationToken token) - { - var source = await view.GetMainFrame().GetSourceAsync(token); - var doc = new HtmlDocument(); - doc.LoadHtml(source); - return doc; - } } \ No newline at end of file diff --git a/Wabbajack.CLI/Browser/BrowserHost.cs b/Wabbajack.CLI/Browser/BrowserHost.cs index 8e8dd327..b4c8add9 100644 --- a/Wabbajack.CLI/Browser/BrowserHost.cs +++ b/Wabbajack.CLI/Browser/BrowserHost.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia; @@ -7,7 +8,9 @@ using Avalonia.ReactiveUI; using Avalonia.Threading; using CefNet; using CefNet.Avalonia; +using HtmlAgilityPack; using Microsoft.Extensions.DependencyInjection; +using Wabbajack.DTOs.Logins; namespace Wabbajack.CLI.Browser { @@ -66,5 +69,72 @@ namespace Wabbajack.CLI.Browser _mainWindowViewModel = vm; _browser = browser; } + + public string Instructions + { + set + { + Dispatcher.UIThread.Post(() => + { + _mainWindowViewModel.Instructions = value; + }); + } + } + + public async Task WaitForReady() + { + while (!_browser.IsInitialized) + { + await Task.Delay(250); + } + + while (_browser.BrowserObject == null) + { + await Task.Delay(250); + } + } + + public async Task NavigateTo(Uri location) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + _browser.Navigate(location.ToString()); + }); + await WaitForIdle(); + } + + public async Task WaitForIdle() + { + while (_browser.IsBusy) + { + await Task.Delay(250); + } + } + + public async Task Cookies(string domainEnding, CancellationToken token) + { + var results = CefCookieManager.GetGlobalManager(null)!; + var cookies = await results.GetCookiesAsync(c => c.Domain.EndsWith(domainEnding), token)!; + 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) + { + _browser.GetMainFrame().ExecuteJavaScript(js, "", 0); + } + + public async Task GetDom(CancellationToken token) + { + var source = await _browser.GetMainFrame().GetSourceAsync(token); + var doc = new HtmlDocument(); + doc.LoadHtml(source); + return doc; + } } -} +} \ No newline at end of file diff --git a/Wabbajack.CLI/Program.cs b/Wabbajack.CLI/Program.cs index 55c80ff7..5be8b327 100644 --- a/Wabbajack.CLI/Program.cs +++ b/Wabbajack.CLI/Program.cs @@ -97,6 +97,6 @@ internal class Program reg.Register(VfsIndexFolder.MakeCommand); reg.Register(NexusLogin.MakeCommand); - return await service!.Run(args); + return await service.Run(args); } } \ No newline at end of file diff --git a/Wabbajack.CLI/Verbs/NexusLogin.cs b/Wabbajack.CLI/Verbs/NexusLogin.cs index 30771b3f..131e1023 100644 --- a/Wabbajack.CLI/Verbs/NexusLogin.cs +++ b/Wabbajack.CLI/Verbs/NexusLogin.cs @@ -1,9 +1,14 @@ +using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Fizzler.Systems.HtmlAgilityPack; using Microsoft.Extensions.Logging; using Wabbajack.CLI.Browser; +using Wabbajack.DTOs.Logins; +using Wabbajack.Services.OSIntegrated; namespace Wabbajack.CLI.Verbs; @@ -11,11 +16,13 @@ public class NexusLogin : AVerb { private readonly ILogger _logger; private readonly BrowserHost _host; + private readonly EncryptedJsonTokenProvider _tokenProvider; - public NexusLogin(ILogger logger, BrowserHost host) + public NexusLogin(ILogger logger, BrowserHost host, EncryptedJsonTokenProvider tokenProvider) { _logger = logger; _host = host; + _tokenProvider = tokenProvider; } public static Command MakeCommand() @@ -28,7 +35,80 @@ public class NexusLogin : AVerb public async Task Run(CancellationToken token) { var browser = await _host.CreateBrowser(); + + token.ThrowIfCancellationRequested(); + browser.Instructions = "Please log into the Nexus"; + + await browser.WaitForReady(); + + await browser.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 browser.Cookies("nexusmods.com", token); + if (cookies.Any(c => c.Name == "member_id")) + break; + + token.ThrowIfCancellationRequested(); + await Task.Delay(500, token); + } + + browser.Instructions = "Getting API Key..."; + + await browser.NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api")); + + var key = ""; + + while (true) + { + try + { + key = (await browser.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 browser.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;" + ); + browser.Instructions = "Generating API Key, Please Wait..."; + } + catch (Exception) + { + // ignored + } + + token.ThrowIfCancellationRequested(); + await Task.Delay(500, token); + } + + browser.Instructions = "Success, saving information..."; + await _tokenProvider.SetToken(new NexusApiState + { + Cookies = cookies, + ApiKey = key + }); + return 0; } diff --git a/Wabbajack.CLI/Wabbajack.CLI.csproj b/Wabbajack.CLI/Wabbajack.CLI.csproj index d97ce8f3..a539ab5f 100644 --- a/Wabbajack.CLI/Wabbajack.CLI.csproj +++ b/Wabbajack.CLI/Wabbajack.CLI.csproj @@ -14,6 +14,7 @@ + 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>();