Merge branch 'master' into image-hashing

This commit is contained in:
Timothy Baldridge 2021-06-19 05:17:18 -06:00
commit d94aa06ab8
10 changed files with 704 additions and 56 deletions

View File

@ -148,7 +148,7 @@ Reading all the previous section you might wonder if Wabbajack is able to detect
This basically means `original + patch = final` and we only include `patch` in the `.wabbajack` file which, by itself, is just gibberish and completely useless without the original file. This allows us to distribute arbitrary changes without violating copyrights as we do not copy copyrighted material. Instead, we copy instructions on how to modify the copyrighted material.
You don't even have to tell Wabbajack that a specific file was modified, that you be way too much work. Instead Wabbajack will figure out which file got modified and create a binary patch. The modified file can be anything from some modified settings file to a patched plugin or optimized mesh/texture.
You don't even have to tell Wabbajack that a specific file was modified, that would be way too much work. Instead Wabbajack will figure out which file got modified and create a binary patch. The modified file can be anything from some modified settings file to a patched plugin or optimized mesh/texture.
#### BSA Decomposition

View File

@ -35,7 +35,7 @@ namespace Wabbajack.Lib.Downloaders
typeof(MegaDownloader.State),
typeof(ModDBDownloader.State),
typeof(NexusDownloader.State),
typeof(VectorPlexusDownloader.State),
typeof(VectorPlexusOAuthDownloader.State),
typeof(DeadlyStreamDownloader.State),
typeof(TESAllianceDownloader.State),
typeof(TESAllDownloader.State),

View File

@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using Wabbajack.Lib.Downloaders.DTOs;
using Wabbajack.Lib.Http;
using Wabbajack.Lib.Validation;
namespace Wabbajack.Lib.Downloaders
{
public abstract class AbstractIPS4OAuthDownloader<TDownloader, TState> : INeedsLogin, IDownloader, IWaitForWindowDownloader
where TState : AbstractIPS4OAuthDownloader<TDownloader, TState>.State, new()
where TDownloader : IDownloader
{
public AbstractIPS4OAuthDownloader(string clientID, Uri authEndpoint, Uri tokenEndpoint, IEnumerable<string> scopes, string encryptedKeyName)
{
ClientID = clientID;
AuthorizationEndpoint = authEndpoint;
TokenEndpoint = tokenEndpoint;
EncryptedKeyName = encryptedKeyName;
TriggerLogin = ReactiveCommand.CreateFromTask(
execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestOAuthLogin(ClientID, authEndpoint, tokenEndpoint, SiteName, scopes, EncryptedKeyName)).Task),
canExecute: IsLoggedIn.Select(b => !b).ObserveOnGuiThread());
ClearLogin = ReactiveCommand.CreateFromTask(
execute: () => Utils.CatchAndLog(async () => await Utils.DeleteEncryptedJson(EncryptedKeyName)),
canExecute: IsLoggedIn.ObserveOnGuiThread());
}
public string EncryptedKeyName { get; }
public Uri TokenEndpoint { get; }
public Uri AuthorizationEndpoint { get; }
public string ClientID { get; }
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(EncryptedKeyName);
public abstract string SiteName { get; }
public IObservable<string>? MetaInfo { get; }
public abstract Uri SiteURL { get; }
public virtual Uri? IconUri { get; }
public Client? AuthedClient { get; set; }
private bool _isPrepared = false;
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false)
{
if (archiveINI.General.ips4Site == SiteName && archiveINI.General.ips4Mod != null && archiveINI.General.ips4File != null)
{
if (!long.TryParse(archiveINI.General.ips4Mod, out long parsedMod))
return null;
var state = new TState {IPS4Mod = parsedMod, IPS4File = archiveINI.General.ips4File};
if (!quickMode)
{
var downloads = await GetDownloads(state.IPS4Mod);
state.IPS4Url = downloads.Url ?? "";
}
return state;
}
return null;
}
public async Task<IPS4OAuthFilesResponse.Root> GetDownloads(long modID)
{
var responseString = await (await GetAuthedClient())!.GetStringAsync(SiteURL+ $"api/downloads/files/{modID}") ;
return responseString.FromJsonString<IPS4OAuthFilesResponse.Root>();
}
public async Task Prepare()
{
if (_isPrepared) return;
AuthedClient = (await GetAuthedClient()) ?? throw new Exception($"Not logged into {SiteName}");
_isPrepared = true;
}
private async Task<Http.Client?> GetAuthedClient()
{
if (!Utils.HaveEncryptedJson(EncryptedKeyName))
return null;
var data = await Utils.FromEncryptedJson<OAuthResultState>(EncryptedKeyName);
await data.Refresh();
var client = new Http.Client();
client.Headers.Add(("Authorization", $"Bearer {data.AccessToken}"));
return client;
}
public async Task WaitForNextRequestWindow()
{
}
public abstract class State : AbstractDownloadState, IMetaState
{
public long IPS4Mod { get; set; }
public string IPS4File { get; set; } = "";
public string IPS4Url { get; set; } = "";
public override object[] PrimaryKey => new object[] {IPS4Mod, IPS4File};
public override bool IsWhitelisted(ServerWhitelist whitelist)
{
return true;
}
public override async Task<bool> Download(Archive a, AbsolutePath destination)
{
var downloads = await TypedDownloader.GetDownloads(IPS4Mod);
var fileEntry = downloads.Files.First(f => f.Name == IPS4File);
if (a.Size != 0 && fileEntry.Size != a.Size)
throw new Exception(
$"File {IPS4File} on mod {IPS4Mod} on {TypedDownloader.SiteName} appears to be re-uploaded with the same name");
var state = new HTTPDownloader.State(fileEntry.Url!) {Client = TypedDownloader.AuthedClient!};
if (a.Size == 0) a.Size = fileEntry.Size!.Value;
return await state.Download(a, destination);
}
private static AbstractIPS4OAuthDownloader<TDownloader, TState> TypedDownloader => (AbstractIPS4OAuthDownloader<TDownloader, TState>)(object)DownloadDispatcher.GetInstance<TDownloader>();
public override async Task<bool> Verify(Archive archive, CancellationToken? token = null)
{
var downloads = await DownloadDispatcher.GetInstance<VectorPlexusOAuthDownloader>().GetDownloads(IPS4Mod);
var fileEntry = downloads.Files.FirstOrDefault(f => f.Name == IPS4File);
if (fileEntry == null) return false;
return archive.Size == 0 || fileEntry.Size == archive.Size;
}
public override string? GetManifestURL(Archive a)
{
return IPS4Url;
}
public override string[] GetMetaIni()
{
return new[]
{
"[General]",
$"ips4Site={TypedDownloader.SiteName}",
$"ips4Mod={IPS4Mod}",
$"ips4File={IPS4File}"
};
}
public Uri URL => new(IPS4Url);
public string? Name { get; set; }
public string? Author { get; set; }
public string? Version { get; set; }
public Uri? ImageURL { get; set; }
public bool IsNSFW { get; set; }
public string? Description { get; set; }
public async Task<bool> LoadMetaData()
{
var data = await TypedDownloader.GetDownloads(IPS4Mod);
Name = data.Title;
Author = data.Author?.Name;
Version = data.Version;
ImageURL = data.PrimaryScreenshot.Url != null ? new Uri(data.PrimaryScreenshot.Url) : null;
IsNSFW = true;
Description = "";
IPS4Url = data.Url!;
return true;
}
}
}
[JsonName("OAuthResultState")]
public class OAuthResultState
{
[JsonProperty("access_token")]
public string AccessToken { get; set; } = "";
[JsonProperty("token_type")]
public string TokenType { get; set; } = "";
[JsonProperty("expires_in")]
public long ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; } = "";
[JsonProperty("scope")]
public string Scope { get; set; } = "";
[JsonProperty("authorization_code")]
public string AuthorizationCode { get; set; } = "";
[JsonProperty("token_endpoint")]
public Uri? TokenEndpoint { get; set; }
[JsonProperty("expires_at")]
public DateTime ExpiresAt { get; set; }
[JsonProperty("client_id")]
public string ClientID { get; set; } = "";
internal void FillInData(string authCode, RequestOAuthLogin oa)
{
AuthorizationCode = authCode;
TokenEndpoint = oa.TokenEndpoint;
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(ExpiresIn);
ClientID = oa.ClientID;
}
public async Task<bool> Refresh()
{
if (ExpiresAt > DateTime.UtcNow + TimeSpan.FromHours(6))
return true;
var client = new Http.Client();
var formData = new KeyValuePair<string?, string?>[]
{
new ("grant_type", "refresh_token"),
new ("refresh_token", RefreshToken),
new ("client_id", ClientID)
};
using var response = await client.PostAsync(TokenEndpoint!.ToString(), new FormUrlEncodedContent(formData.ToList()));
var responseData = (await response.Content.ReadAsStringAsync()).FromJsonString<OAuthResultState>();
AccessToken = responseData.AccessToken;
ExpiresIn = responseData.ExpiresIn;
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(ExpiresIn);
return true;
}
}
public class RequestOAuthLogin : AUserIntervention
{
public string ClientID { get; }
public Uri AuthorizationEndpoint { get; }
public Uri TokenEndpoint { get; }
public string SiteName { get; }
public string[] Scopes { get; }
public RequestOAuthLogin(string clientID, Uri authEndpoint, Uri tokenEndpoint, string siteName, IEnumerable<string> scopes, string key)
{
ClientID = clientID;
AuthorizationEndpoint = authEndpoint;
TokenEndpoint = tokenEndpoint;
SiteName = siteName;
Scopes = scopes.ToArray();
EncryptedDataKey = key;
}
public string EncryptedDataKey { get; set; }
public override string ShortDescription => $"Getting {SiteName} Login";
public override string ExtendedDescription { get; } = string.Empty;
private readonly TaskCompletionSource<OAuthResultState> _source = new ();
public Task<OAuthResultState> Task => _source.Task;
public async Task Resume(string authCode)
{
Handled = true;
var client = new Http.Client();
var formData = new KeyValuePair<string?, string?>[]
{
new ("grant_type", "authorization_code"),
new ("code", authCode),
new ("client_id", ClientID)
};
using var response = await client.PostAsync(TokenEndpoint.ToString(), new FormUrlEncodedContent(formData.ToList()));
var responseData = (await response.Content.ReadAsStringAsync()).FromJsonString<OAuthResultState>();
responseData.FillInData(authCode, this);
await responseData.ToEcryptedJson(EncryptedDataKey);
_source.SetResult(new OAuthResultState());
}
public override void Cancel()
{
Handled = true;
_source.TrySetCanceled();
}
}
}

View File

@ -0,0 +1,295 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Wabbajack.Lib.Downloaders.DTOs
{
public class IPS4OAuthFilesResponse
{
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Category
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("class")]
public string? Class { get; set; }
}
public class PrimaryGroup
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("formattedName")]
public string? FormattedName { get; set; }
}
public class Author
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("title")]
public string? Title { get; set; }
[JsonProperty("formattedName")]
public string? FormattedName { get; set; }
[JsonProperty("primaryGroup")]
public PrimaryGroup? PrimaryGroup { get; set; }
[JsonProperty("joined")]
public DateTime Joined { get; set; }
[JsonProperty("reputationPoints")]
public int ReputationPoints { get; set; }
[JsonProperty("photoUrl")]
public string? PhotoUrl { get; set; }
[JsonProperty("photoUrlIsDefault")]
public bool PhotoUrlIsDefault { get; set; }
[JsonProperty("coverPhotoUrl")]
public string? CoverPhotoUrl { get; set; }
[JsonProperty("profileUrl")]
public string? ProfileUrl { get; set; }
[JsonProperty("posts")]
public int Posts { get; set; }
[JsonProperty("lastActivity")]
public DateTime LastActivity { get; set; }
[JsonProperty("lastVisit")]
public DateTime LastVisit { get; set; }
[JsonProperty("lastPost")]
public DateTime LastPost { get; set; }
[JsonProperty("profileViews")]
public int ProfileViews { get; set; }
}
public class File
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("size")]
public long? Size { get; set; }
}
public class Screenshot
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("size")]
public int Size { get; set; }
}
public class PrimaryScreenshot
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("size")]
public string? Size { get; set; }
}
public class Forum
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("topics")]
public int Topics { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
}
public class FirstPost
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("item_id")]
public int ItemId { get; set; }
[JsonProperty("author")]
public Author? Author { get; set; }
[JsonProperty("date")]
public DateTime Date { get; set; }
[JsonProperty("content")]
public string? Content { get; set; }
[JsonProperty("hidden")]
public bool Hidden { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
}
public class LastPost
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("item_id")]
public int ItemId { get; set; }
[JsonProperty("author")]
public Author? Author { get; set; }
[JsonProperty("date")]
public DateTime Date { get; set; }
[JsonProperty("content")]
public string? Content { get; set; }
[JsonProperty("hidden")]
public bool Hidden { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
}
public class Topic
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("title")]
public string? Title { get; set; }
[JsonProperty("forum")]
public Forum? Forum { get; set; }
[JsonProperty("posts")]
public int Posts { get; set; }
[JsonProperty("views")]
public int Views { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("hidden")]
public bool Hidden { get; set; }
[JsonProperty("pinned")]
public bool Pinned { get; set; }
[JsonProperty("featured")]
public bool Featured { get; set; }
[JsonProperty("archived")]
public bool Archived { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("rating")]
public double Rating { get; set; }
}
public class Root
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("title")]
public string? Title { get; set; }
[JsonProperty("category")]
public Category? Category { get; set; }
[JsonProperty("author")]
public Author? Author { get; set; }
[JsonProperty("date")]
public DateTime Date { get; set; }
[JsonProperty("description")]
public string? Description { get; set; }
[JsonProperty("version")]
public string? Version { get; set; }
[JsonProperty("changelog")]
public string? Changelog { get; set; }
[JsonProperty("files")] public List<File> Files { get; set; } = new();
[JsonProperty("screenshots")] public List<Screenshot> Screenshots { get; set; } = new();
[JsonProperty("primaryScreenshot")]
public PrimaryScreenshot PrimaryScreenshot { get; set; } = new();
[JsonProperty("downloads")]
public int Downloads { get; set; }
[JsonProperty("comments")]
public int Comments { get; set; }
[JsonProperty("reviews")]
public int Reviews { get; set; }
[JsonProperty("views")]
public int Views { get; set; }
[JsonProperty("tags")] public List<string?> Tags { get; set; } = new ();
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("hidden")]
public bool Hidden { get; set; }
[JsonProperty("pinned")]
public bool Pinned { get; set; }
[JsonProperty("featured")]
public bool Featured { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("topic")] public Topic Topic { get; set; } = new();
}
}
}

View File

@ -22,7 +22,7 @@ namespace Wabbajack.Lib.Downloaders
new NexusDownloader(),
new MediaFireDownloader(),
new LoversLabDownloader(),
new VectorPlexusDownloader(),
new VectorPlexusOAuthDownloader(),
new DeadlyStreamDownloader(),
new TESAllianceDownloader(),
new TESAllDownloader(),

View File

@ -1,13 +1,16 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using HtmlAgilityPack;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using Wabbajack.Lib.Validation;
namespace Wabbajack.Lib.Downloaders
{
public class VectorPlexusDownloader : AbstractIPS4Downloader<VectorPlexusDownloader, VectorPlexusDownloader.State>
public class VectorPlexusOAuthDownloader : AbstractIPS4OAuthDownloader<VectorPlexusOAuthDownloader, VectorPlexusOAuthDownloader.State>
{
#region INeedsDownload
public override string SiteName => "Vector Plexus";
@ -15,57 +18,21 @@ namespace Wabbajack.Lib.Downloaders
public override Uri IconUri => new Uri("https://www.vectorplexus.com/favicon.ico");
#endregion
public VectorPlexusDownloader() : base(new Uri("https://vectorplexus.com/login"),
"vectorplexus", "vectorplexus.com")
public VectorPlexusOAuthDownloader() : base("45c6d3c9867903a7daa6ded0a38cedf8",
new Uri("https://vectorplexus.com/oauth/authorize/"),
new Uri("https://vectorplexus.com/oauth/token/"),
new []{"profile", "get_downloads"},
"vector-plexus-oauth2")
{
}
[JsonName("VectorPlexusDownloader")]
public class State : State<VectorPlexusDownloader>
[JsonName("VectorPlexusOAuthDownloader+State")]
public class State : AbstractIPS4OAuthDownloader<VectorPlexusOAuthDownloader, VectorPlexusOAuthDownloader.State>.State
{
public override async Task<bool> LoadMetaData()
public override IDownloader GetDownloader()
{
var html = await Downloader.AuthedClient.GetStringAsync(URL);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var node = doc.DocumentNode;
Name = HttpUtility.HtmlDecode(node
.SelectNodes(
"//h1[@class='ipsType_pageTitle ipsContained_container']/span[@class='ipsType_break ipsContained']")
?.First().InnerHtml);
Author = HttpUtility.HtmlDecode(node
.SelectNodes(
"//div[@class='ipsBox_alt']/div[@class='ipsPhotoPanel ipsPhotoPanel_tiny ipsClearfix ipsSpacer_bottom']/div/p[@class='ipsType_reset ipsType_large ipsType_blendLinks']/a")
?.First().InnerHtml);
Version = HttpUtility.HtmlDecode(node
.SelectNodes("//section/h2[@class='ipsType_sectionHead']/span[@data-role='versionTitle']")
?
.First().InnerHtml);
var url = HttpUtility.HtmlDecode(node
.SelectNodes(
"//div[@class='ipsBox ipsSpacer_top ipsSpacer_double']/section/div[@class='ipsPad ipsAreaBackground']/div[@class='ipsCarousel ipsClearfix']/div[@class='ipsCarousel_inner']/ul[@class='cDownloadsCarousel ipsClearfix']/li[@class='ipsCarousel_item ipsAreaBackground_reset ipsPad_half']/span[@class='ipsThumb ipsThumb_medium ipsThumb_bg ipsCursor_pointer']")
?.First().GetAttributeValue("data-fullurl", "none"));
if (!string.IsNullOrWhiteSpace(url))
{
ImageURL = new Uri(url);
return true;
}
url = HttpUtility.HtmlDecode(node
.SelectNodes(
"//article[@class='ipsColumn ipsColumn_fluid']/div[@class='ipsPad']/section/div[@class='ipsType_richText ipsContained ipsType_break']/p/a/img[@class='ipsImage ipsImage_thumbnailed']")
?.First().GetAttributeValue("src", ""));
if (!string.IsNullOrWhiteSpace(url))
{
ImageURL = new Uri(url);
}
return true;
return DownloadDispatcher.GetInstance<VectorPlexusOAuthDownloader>();
}
}
}

View File

@ -85,6 +85,8 @@ namespace Wabbajack.Lib.LibCefHelpers
public string Path { get; set; } = string.Empty;
}
public static Func<IBrowser, IFrame, string, IRequest, IResourceHandler>? SchemeHandler { get; set; }
public static void Init()
{
if (Inited || Cef.IsInitialized) return;
@ -92,9 +94,28 @@ namespace Wabbajack.Lib.LibCefHelpers
CefSettings settings = new CefSettings();
settings.CachePath = Consts.CefCacheLocation.ToString();
settings.JavascriptFlags = "--noexpose_wasm";
settings.RegisterScheme(new CefCustomScheme()
{
SchemeName = "wabbajack",
SchemeHandlerFactory = new SchemeHandlerFactor()
});
Cef.Initialize(settings);
}
private class SchemeHandlerFactor : ISchemeHandlerFactory
{
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
if (SchemeHandler != null && schemeName == "wabbajack")
{
return SchemeHandler!(browser, frame, schemeName, request);
}
return new ResourceHandler();
}
}
public static bool Inited { get; set; }
public static void ClearCookies()

View File

@ -362,6 +362,8 @@ namespace Wabbajack.Server.Services
return (archive, ArchiveStatus.Valid);
case MediaFireDownloader.State _:
return (archive, ArchiveStatus.Valid);
case LoversLabDownloader.State _ :
return (archive, ArchiveStatus.Valid);
default:
{
if (data.ArchiveStatus.TryGetValue((archive.State.PrimaryKeyString, archive.Hash),

View File

@ -396,13 +396,15 @@ namespace Wabbajack.Test
}
/*
[Fact]
public async Task VectorPlexusDownload()
{
await DownloadDispatcher.GetInstance<VectorPlexusDownloader>().Prepare();
await DownloadDispatcher.GetInstance<VectorPlexusOAuthDownloader>().Prepare();
var ini = @"[General]
directURL=https://vectorplexus.com/files/file/290-wabbajack-test-file";
ips4Site=Vector Plexus
ips4Mod=290
ips4File=WABBAJACK_TEST_FILE.zip";
var state = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(ini.LoadIniString());
@ -414,13 +416,25 @@ namespace Wabbajack.Test
Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List<string>() }));
await converted.Download(new Archive(state: null!) { Name = "Vector Plexus Test.zip" }, filename.Path);
var archive = new Archive(state: null!) {Name = "Vector Plexus Test.zip"};
await converted.Download(archive, filename.Path);
Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync());
Assert.Equal("Cheese for Everyone!", await filename.Path.ReadAllTextAsync());
Assert.True(converted is VectorPlexusOAuthDownloader.State);
}*/
var st = (VectorPlexusOAuthDownloader.State)converted;
Assert.True(await st.LoadMetaData());
Assert.Equal("halgari", st.Author);
Assert.Equal("Wabbajack Test File", st.Name);
Assert.True(st.IsNSFW);
Assert.Equal("1.0.0", st.Version);
Assert.Equal("https://vectorplexus.com/files/file/290-wabbajack-test-file/", st.GetManifestURL(archive));
Assert.True(st.ImageURL != null);
}
[Fact]
public async Task YandexDownloader()

View File

@ -5,6 +5,7 @@ using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
using System.Windows.Threading;
using CefSharp;
@ -16,6 +17,7 @@ using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.WebAutomation;
using WebSocketSharp;
namespace Wabbajack
{
@ -83,6 +85,14 @@ namespace Wabbajack
var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token);
c.Resume(data);
});
break;
case RequestOAuthLogin oa:
await WrapBrowserJob(oa, async (vm, cancel) =>
{
await OAuthLogin(oa, vm, cancel);
});
break;
case CriticalFailureIntervention c:
MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
@ -97,6 +107,44 @@ namespace Wabbajack
}
}
private async Task OAuthLogin(RequestOAuthLogin oa, WebBrowserVM vm, CancellationTokenSource cancel)
{
await vm.Driver.WaitForInitialized();
vm.Instructions = "Please log in and allow Wabbajack to access your account";
var wrapper = new CefSharpWrapper(vm.Browser);
var scopes = string.Join(" ", oa.Scopes);
var state = Guid.NewGuid().ToString();
await wrapper.NavigateTo(new Uri(oa.AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}&state={state}&scope={scopes}"));
Helpers.SchemeHandler = (browser, frame, _, request) =>
{
var req = new Uri(request.Url);
var parsed = HttpUtility.ParseQueryString(req.Query);
if (parsed.Contains("state"))
{
if (parsed.Get("state") != state)
{
Utils.Log("Bad OAuth state, state, this shouldn't happen");
oa.Cancel();
return new ResourceHandler();
}
}
if (parsed.Contains("code"))
{
oa.Resume(parsed.Get("code"));
}
else
{
oa.Cancel();
}
return new ResourceHandler();
};
while (!oa.Task.IsCanceled && !oa.Task.IsCompleted && !cancel.IsCancellationRequested)
await Task.Delay(250);
}
private async Task HandleManualDownload(WebBrowserVM vm, CancellationTokenSource cancel, ManuallyDownloadFile manuallyDownloadFile)
{
var browser = new CefSharpWrapper(vm.Browser);