mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Allow manual Nexus downloads
This commit is contained in:
parent
d8500fd618
commit
6255ec224f
@ -60,6 +60,8 @@ namespace Wabbajack.Common
|
|||||||
public string MO2ArchiveName { get; internal set; }
|
public string MO2ArchiveName { get; internal set; }
|
||||||
public Game Game { get; internal set; }
|
public Game Game { get; internal set; }
|
||||||
public string NexusName { get; internal set; }
|
public string NexusName { get; internal set; }
|
||||||
|
// Nexus DB id for the game, used in some specific situations
|
||||||
|
public long NexusGameId { get; internal set; }
|
||||||
public string MO2Name { get; internal set; }
|
public string MO2Name { get; internal set; }
|
||||||
public string GameLocationRegistryKey { get; internal set; }
|
public string GameLocationRegistryKey { get; internal set; }
|
||||||
// to get steam ids: https://steamdb.info
|
// to get steam ids: https://steamdb.info
|
||||||
@ -211,6 +213,7 @@ namespace Wabbajack.Common
|
|||||||
SupportedModManager = ModManager.MO2,
|
SupportedModManager = ModManager.MO2,
|
||||||
Game = Game.SkyrimSpecialEdition,
|
Game = Game.SkyrimSpecialEdition,
|
||||||
NexusName = "skyrimspecialedition",
|
NexusName = "skyrimspecialedition",
|
||||||
|
NexusGameId = 1704,
|
||||||
MO2Name = "Skyrim Special Edition",
|
MO2Name = "Skyrim Special Edition",
|
||||||
MO2ArchiveName = "skyrimse",
|
MO2ArchiveName = "skyrimse",
|
||||||
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim Special Edition",
|
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim Special Edition",
|
||||||
|
@ -236,7 +236,7 @@ namespace Wabbajack.Lib
|
|||||||
var dispatchers = missing.Select(m => m.State.GetDownloader()).Distinct();
|
var dispatchers = missing.Select(m => m.State.GetDownloader()).Distinct();
|
||||||
|
|
||||||
await Task.WhenAll(dispatchers.Select(d => d.Prepare()));
|
await Task.WhenAll(dispatchers.Select(d => d.Prepare()));
|
||||||
|
|
||||||
await DownloadMissingArchives(missing);
|
await DownloadMissingArchives(missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace Wabbajack.Lib.Downloaders
|
|||||||
public class NexusDownloader : IDownloader, INeedsLogin
|
public class NexusDownloader : IDownloader, INeedsLogin
|
||||||
{
|
{
|
||||||
private bool _prepared;
|
private bool _prepared;
|
||||||
private SemaphoreSlim _lock = new SemaphoreSlim(1);
|
private AsyncLock _lock = new AsyncLock();
|
||||||
private UserStatus _status;
|
private UserStatus _status;
|
||||||
private NexusApiClient _client;
|
private NexusApiClient _client;
|
||||||
|
|
||||||
@ -94,32 +94,33 @@ namespace Wabbajack.Lib.Downloaders
|
|||||||
{
|
{
|
||||||
if (!_prepared)
|
if (!_prepared)
|
||||||
{
|
{
|
||||||
await _lock.WaitAsync();
|
using var _ = await _lock.Wait();
|
||||||
try
|
// Could have become prepared while we waited for the lock
|
||||||
|
if (!_prepared)
|
||||||
{
|
{
|
||||||
// Could have become prepared while we waited for the lock
|
_client = await NexusApiClient.Get();
|
||||||
if (!_prepared)
|
_status = await _client.GetUserStatus();
|
||||||
|
if (!_client.IsAuthenticated)
|
||||||
{
|
{
|
||||||
_client = await NexusApiClient.Get();
|
Utils.ErrorThrow(new UnconvertedError(
|
||||||
_status = await _client.GetUserStatus();
|
$"Authenticating for the Nexus failed. A nexus account is required to automatically download mods."));
|
||||||
if (!_client.IsAuthenticated)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!await _client.IsPremium())
|
||||||
|
{
|
||||||
|
var result = await Utils.Log(new YesNoIntervention(
|
||||||
|
"Wabbajack can operate without a premium account, but downloads will be slower and the install process will require more user interactions (you will have to start each download by hand). Are you sure you wish to continue?",
|
||||||
|
"Continue without Premium?")).Task;
|
||||||
|
if (result == ConfirmationIntervention.Choice.Abort)
|
||||||
{
|
{
|
||||||
Utils.ErrorThrow(new UnconvertedError(
|
Utils.ErrorThrow(new UnconvertedError($"Aborting at the request of the user"));
|
||||||
$"Authenticating for the Nexus failed. A nexus account is required to automatically download mods."));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
_prepared = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_lock.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_prepared = true;
|
|
||||||
|
|
||||||
if (_status.is_premium) return;
|
|
||||||
Utils.ErrorThrow(new UnconvertedError($"Automated installs with Wabbajack requires a premium nexus account. {await _client.Username()} is not a premium account."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class State : AbstractDownloadState
|
public class State : AbstractDownloadState
|
||||||
|
@ -37,7 +37,7 @@ namespace Wabbajack.Lib.LibCefHelpers
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Cookie[]> GetCookies(string domainEnding)
|
public static async Task<Cookie[]> GetCookies(string domainEnding = "")
|
||||||
{
|
{
|
||||||
var manager = Cef.GetGlobalCookieManager();
|
var manager = Cef.GetGlobalCookieManager();
|
||||||
var visitor = new CookieVisitor();
|
var visitor = new CookieVisitor();
|
||||||
@ -85,8 +85,14 @@ namespace Wabbajack.Lib.LibCefHelpers
|
|||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
// does nothing, but kicks off the static constructor
|
if (Inited) return;
|
||||||
|
Inited = true;
|
||||||
|
CefSettings settings = new CefSettings();
|
||||||
|
settings.CachePath = Path.Combine(Directory.GetCurrentDirectory() + @"\CEF");
|
||||||
|
Cef.Initialize(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool Inited { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ModuleInitializer
|
public static class ModuleInitializer
|
||||||
|
@ -14,6 +14,7 @@ using Wabbajack.Lib.Downloaders;
|
|||||||
using WebSocketSharp;
|
using WebSocketSharp;
|
||||||
using static Wabbajack.Lib.NexusApi.NexusApiUtils;
|
using static Wabbajack.Lib.NexusApi.NexusApiUtils;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Wabbajack.Lib.Exceptions;
|
||||||
using Wabbajack.Lib.WebAutomation;
|
using Wabbajack.Lib.WebAutomation;
|
||||||
|
|
||||||
namespace Wabbajack.Lib.NexusApi
|
namespace Wabbajack.Lib.NexusApi
|
||||||
@ -261,7 +262,24 @@ namespace Wabbajack.Lib.NexusApi
|
|||||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||||
|
|
||||||
var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(archive.GameName)}/mods/{archive.ModID}/files/{archive.FileID}/download_link.json";
|
var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(archive.GameName)}/mods/{archive.ModID}/files/{archive.FileID}/download_link.json";
|
||||||
return (await Get<List<DownloadLink>>(url)).First().URI;
|
try
|
||||||
|
{
|
||||||
|
return (await Get<List<DownloadLink>>(url)).First().URI;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Utils.Log($"Requesting manual download for {archive.ModName}");
|
||||||
|
return (await Utils.Log(await ManuallyDownloadNexusFile.Create(archive)).Task).ToString();
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
Utils.Error(ex, "Manual cancellation of download");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<NexusFileInfo> GetFileInfo(NexusDownloader.State mod)
|
public async Task<NexusFileInfo> GetFileInfo(NexusDownloader.State mod)
|
||||||
@ -328,5 +346,10 @@ namespace Wabbajack.Lib.NexusApi
|
|||||||
}
|
}
|
||||||
set => _localCacheDir = value;
|
set => _localCacheDir = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri ManualDownloadUrl(NexusDownloader.State state)
|
||||||
|
{
|
||||||
|
return new Uri($"https://www.nexusmods.com/{GameRegistry.GetByMO2ArchiveName(state.GameName).NexusName}/mods/{state.ModID}?tab=files");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs
Normal file
38
Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Lib.Downloaders;
|
||||||
|
|
||||||
|
namespace Wabbajack.Lib
|
||||||
|
{
|
||||||
|
public class ManuallyDownloadNexusFile : AUserIntervention
|
||||||
|
{
|
||||||
|
public NexusDownloader.State State { get; }
|
||||||
|
public override string ShortDescription { get; }
|
||||||
|
public override string ExtendedDescription { get; }
|
||||||
|
|
||||||
|
private TaskCompletionSource<Uri> _tcs = new TaskCompletionSource<Uri>();
|
||||||
|
public Task<Uri> Task => _tcs.Task;
|
||||||
|
|
||||||
|
private ManuallyDownloadNexusFile(NexusDownloader.State state)
|
||||||
|
{
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<ManuallyDownloadNexusFile> Create(NexusDownloader.State state)
|
||||||
|
{
|
||||||
|
var result = new ManuallyDownloadNexusFile(state);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public override void Cancel()
|
||||||
|
{
|
||||||
|
_tcs.SetCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume(Uri s)
|
||||||
|
{
|
||||||
|
_tcs.SetResult(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Lib.LibCefHelpers;
|
using Wabbajack.Lib.LibCefHelpers;
|
||||||
|
|
||||||
namespace Wabbajack.Lib.WebAutomation
|
namespace Wabbajack.Lib.WebAutomation
|
||||||
@ -11,7 +13,7 @@ namespace Wabbajack.Lib.WebAutomation
|
|||||||
public class CefSharpWrapper : IWebDriver
|
public class CefSharpWrapper : IWebDriver
|
||||||
{
|
{
|
||||||
private IWebBrowser _browser;
|
private IWebBrowser _browser;
|
||||||
|
public Action<Uri> DownloadHandler { get; set; }
|
||||||
public CefSharpWrapper(IWebBrowser browser)
|
public CefSharpWrapper(IWebBrowser browser)
|
||||||
{
|
{
|
||||||
_browser = browser;
|
_browser = browser;
|
||||||
@ -33,6 +35,7 @@ namespace Wabbajack.Lib.WebAutomation
|
|||||||
|
|
||||||
_browser.LoadingStateChanged += handler;
|
_browser.LoadingStateChanged += handler;
|
||||||
_browser.Load(uri.ToString());
|
_browser.Load(uri.ToString());
|
||||||
|
_browser.DownloadHandler = new DownloadHandler(this);
|
||||||
return tcs.Task;
|
return tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +53,34 @@ namespace Wabbajack.Lib.WebAutomation
|
|||||||
return Helpers.GetCookies(domainPrefix);
|
return Helpers.GetCookies(domainPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string CefStateName = "cef-state";
|
||||||
|
|
||||||
public async Task WaitForInitialized()
|
public async Task WaitForInitialized()
|
||||||
{
|
{
|
||||||
while (!_browser.IsBrowserInitialized)
|
while (!_browser.IsBrowserInitialized)
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DownloadHandler : IDownloadHandler
|
||||||
|
{
|
||||||
|
private CefSharpWrapper _wrapper;
|
||||||
|
|
||||||
|
public DownloadHandler(CefSharpWrapper wrapper)
|
||||||
|
{
|
||||||
|
_wrapper = wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
|
||||||
|
IBeforeDownloadCallback callback)
|
||||||
|
{
|
||||||
|
_wrapper.DownloadHandler(new Uri(downloadItem.Url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
|
||||||
|
IDownloadItemCallback callback)
|
||||||
|
{
|
||||||
|
callback.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,5 +12,6 @@ namespace Wabbajack.Lib.WebAutomation
|
|||||||
Task NavigateTo(Uri uri);
|
Task NavigateTo(Uri uri);
|
||||||
Task<string> EvaluateJavaScript(string text);
|
Task<string> EvaluateJavaScript(string text);
|
||||||
Task<Helpers.Cookie[]> GetCookies(string domainPrefix);
|
Task<Helpers.Cookie[]> GetCookies(string domainPrefix);
|
||||||
|
public Action<Uri> DownloadHandler { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,18 +74,21 @@ namespace Wabbajack
|
|||||||
.Bind(Log)
|
.Bind(Log)
|
||||||
.Subscribe()
|
.Subscribe()
|
||||||
.DisposeWith(CompositeDisposable);
|
.DisposeWith(CompositeDisposable);
|
||||||
|
|
||||||
|
var singleton_lock = new AsyncLock();
|
||||||
|
|
||||||
Utils.LogMessages
|
Utils.LogMessages
|
||||||
.OfType<IUserIntervention>()
|
.OfType<IUserIntervention>()
|
||||||
.ObserveOnGuiThread()
|
.ObserveOnGuiThread()
|
||||||
.SelectTask(async msg =>
|
.SelectTask(async msg =>
|
||||||
{
|
{
|
||||||
|
using var _ = await singleton_lock.Wait();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserInterventionHandlers.Handle(msg);
|
await UserInterventionHandlers.Handle(msg);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
when (ex.GetType() != typeof(TaskCanceledException))
|
when (ex.GetType() != typeof(TaskCanceledException))
|
||||||
{
|
{
|
||||||
Utils.Error(ex, $"Error while handling user intervention of type {msg?.GetType()}");
|
Utils.Error(ex, $"Error while handling user intervention of type {msg?.GetType()}");
|
||||||
try
|
try
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using CefSharp;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Lib;
|
using Wabbajack.Lib;
|
||||||
@ -66,6 +67,9 @@ namespace Wabbajack
|
|||||||
c.Resume(key);
|
c.Resume(key);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case ManuallyDownloadNexusFile c:
|
||||||
|
await WrapBrowserJob(msg, (vm, cancel) => HandleManualNexusDownload(vm, cancel, c));
|
||||||
|
break;
|
||||||
case RequestBethesdaNetLogin c:
|
case RequestBethesdaNetLogin c:
|
||||||
var data = await BethesdaNetDownloader.Login();
|
var data = await BethesdaNetDownloader.Login();
|
||||||
c.Resume(data);
|
c.Resume(data);
|
||||||
@ -78,14 +82,6 @@ namespace Wabbajack
|
|||||||
c.Resume(data);
|
c.Resume(data);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case YesNoIntervention c:
|
|
||||||
var result = MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.YesNo,
|
|
||||||
MessageBoxImage.Question);
|
|
||||||
if (result == MessageBoxResult.Yes)
|
|
||||||
c.Confirm();
|
|
||||||
else
|
|
||||||
c.Cancel();
|
|
||||||
break;
|
|
||||||
case CriticalFailureIntervention c:
|
case CriticalFailureIntervention c:
|
||||||
MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
|
MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
|
||||||
MessageBoxImage.Error);
|
MessageBoxImage.Error);
|
||||||
@ -98,6 +94,38 @@ namespace Wabbajack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleManualNexusDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadNexusFile manuallyDownloadNexusFile)
|
||||||
|
{
|
||||||
|
var state = manuallyDownloadNexusFile.State;
|
||||||
|
var game = GameRegistry.GetByMO2ArchiveName(state.GameName);
|
||||||
|
var hrefs = new[]
|
||||||
|
{
|
||||||
|
$"/Core/Libs/Common/Widgets/DownloadPopUp?id={state.FileID}&game_id={game.NexusGameId}",
|
||||||
|
$"https://www.nexusmods.com/{game.NexusName}/mods/{state.ModID}?tab=files&file_id={state.FileID}",
|
||||||
|
$"/Core/Libs/Common/Widgets/ModRequirementsPopUp?id={state.FileID}&game_id={game.NexusGameId}"
|
||||||
|
};
|
||||||
|
await vm.Driver.WaitForInitialized();
|
||||||
|
IWebDriver browser = new CefSharpWrapper(vm.Browser);
|
||||||
|
vm.Instructions = $"Please Download {state.ModName} - {state.ModID} - {state.FileID}";
|
||||||
|
browser.DownloadHandler = uri =>
|
||||||
|
{
|
||||||
|
manuallyDownloadNexusFile.Resume(uri);
|
||||||
|
};
|
||||||
|
await browser.NavigateTo(NexusApiClient.ManualDownloadUrl(manuallyDownloadNexusFile.State));
|
||||||
|
|
||||||
|
var buttin_href = $"/Core/Libs/Common/Widgets/DownloadPopUp?id={manuallyDownloadNexusFile.State.FileID}&game_id={Game.SkyrimSpecialEdition}";
|
||||||
|
|
||||||
|
while (!cancel.IsCancellationRequested && !manuallyDownloadNexusFile.Task.IsCompleted) {
|
||||||
|
await browser.EvaluateJavaScript(
|
||||||
|
@"Array.from(document.getElementsByClassName('accordion')).forEach(e => Array.from(e.children).forEach(c => c.style=''))");
|
||||||
|
foreach (var href in hrefs)
|
||||||
|
{
|
||||||
|
const string style = "border-thickness: thick; border-color: #ff0000;border-width: medium;border-style: dashed;background-color: teal;padding: 7px";
|
||||||
|
await browser.EvaluateJavaScript($"Array.from(document.querySelectorAll('.accordion a[href=\"{href}\"]')).forEach(e => {{e.scrollIntoView({{behavior: 'smooth', block: 'center', inline: 'nearest'}}); e.setAttribute('style', '{style}');}});");
|
||||||
|
}
|
||||||
|
await Task.Delay(250);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user