From 1ce640ba2b0e1c3fefb6966f8101331ecf230cd8 Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 4 Mar 2020 13:10:49 +0100 Subject: [PATCH] Add non-Nexus mods to the Slideshow (#574) * Created AbstractMetaState * Added IAbstractMetaState to NexusDownloader.State Slideshow is fully working with this setup and nothing changed functionally. * Renamed IAbstractMetaState to IMetaState * Changed modVMs in SlideShow from type NexusDownloader.State to IMetaState * Simplified IMetaState and ModVM * Removed Setter from IMetaState and added to LoversLabDownloader * Throw exception when the modlist could not be loaded * Created AbstractMetaState AbstractMetaState implements AbstractDownloadState and indicates that a State from a specific Download contains meta information. This is used for the Slideshow and can also be used for the Manifest. * Created GatherMetaData function * Implemented new AbstractMetaState for LoversLab * Implemented new AbstractMetaState for NexusMods * Replaced Utils.Log with Utils.Error * Slideshow fixes * Replaced AbstractMetaState with IMetaState * Updated CHANGELOG Co-authored-by: Timothy Baldridge --- CHANGELOG.md | 4 ++ Wabbajack.Common/Utils.cs | 2 +- Wabbajack.Lib/ACompiler.cs | 21 +++++++ Wabbajack.Lib/CerasConfig.cs | 2 +- .../Downloaders/AbstractDownloadState.cs | 15 ++++- .../Downloaders/AbstractIPS4Downloader.cs | 35 ++++++++---- .../Downloaders/LoversLabDownloader.cs | 41 ++++++++------ Wabbajack.Lib/Downloaders/NexusDownloader.cs | 55 +++++++++++-------- Wabbajack.Lib/MO2Compiler.cs | 5 +- Wabbajack.Lib/NexusApi/NexusApi.cs | 2 +- Wabbajack.Lib/Validation/ValidateModlist.cs | 4 +- .../View Models/Installers/InstallerVM.cs | 6 +- Wabbajack/View Models/ModListVM.cs | 1 + Wabbajack/View Models/ModVM.cs | 32 ++--------- Wabbajack/View Models/SlideShow.cs | 28 ++++++---- .../View Models/UserInterventionHandlers.cs | 2 +- .../Views/Installers/InstallationView.xaml.cs | 2 +- 17 files changed, 158 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546e8ab2..b972d878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ### Changelog +#### Version - 1.0.1.0 - 3/xx/2020 +* Added download support for YouTube +* Slideshow can now display mods from non-Nexus sites + #### Verison - 1.0.0.0 - 2/29/2020 * 1.0, first non-beta release diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index b7fe6a2c..65abad2b 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -223,7 +223,7 @@ namespace Wabbajack.Common return sha.Hash.ToBase64(); } - + public static string StringSHA256Hex(this string s) { var sha = new SHA256Managed(); diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index 3d5a1936..a41985d7 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -78,6 +78,27 @@ namespace Wabbajack.Lib return id; } + public async Task GatherMetaData() + { + Utils.Log($"Getting meta data for {SelectedArchives.Count} archives"); + await SelectedArchives.PMap(Queue, async a => + { + if (a.State is IMetaState metaState) + { + var b = await metaState.LoadMetaData(); + Utils.Log(b + ? $"Getting meta data for {a.Name} was successful!" + : $"Getting meta data for {a.Name} failed!"); + } + else + { + Utils.Log($"Archive {a.Name} is not an AbstractMetaState!"); + } + }); + + return true; + } + public void ExportModList() { Utils.Log($"Exporting ModList to {ModListOutputFile}"); diff --git a/Wabbajack.Lib/CerasConfig.cs b/Wabbajack.Lib/CerasConfig.cs index aa3b1927..8d1d7450 100644 --- a/Wabbajack.Lib/CerasConfig.cs +++ b/Wabbajack.Lib/CerasConfig.cs @@ -30,7 +30,7 @@ namespace Wabbajack.Lib typeof(PropertyFile), typeof(SteamMeta), typeof(SteamWorkshopDownloader), typeof(SteamWorkshopDownloader.State), typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State), typeof(VectorPlexusDownloader.State), typeof(DeadlyStreamDownloader.State), typeof(AFKModsDownloader.State), typeof(TESAllianceDownloader.State), - typeof(TES3ArchiveState), typeof(TES3FileState), typeof(BethesdaNetDownloader.State), typeof(YouTubeDownloader) + typeof(TES3ArchiveState), typeof(TES3FileState), typeof(BethesdaNetDownloader.State), typeof(YouTubeDownloader), typeof(IMetaState) }, }; Config.VersionTolerance.Mode = VersionToleranceMode.Standard; diff --git a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs index ed182e84..91d2534c 100644 --- a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs +++ b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs @@ -7,10 +7,23 @@ using Wabbajack.Lib.Validation; namespace Wabbajack.Lib.Downloaders { + public interface IMetaState + { + string URL { get; } + string Name { get; set; } + string Author { get; set; } + string Version { get; set; } + string ImageURL { get; set; } + bool IsNSFW { get; set; } + string Description { get; set; } + + Task LoadMetaData(); + } + public abstract class AbstractDownloadState { - public static List KnownSubTypes = new List() + public static List KnownSubTypes = new List { typeof(HTTPDownloader.State), typeof(GameFileSourceDownloader.State), diff --git a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs index 921de292..4df8f011 100644 --- a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs +++ b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs @@ -5,6 +5,7 @@ using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; +using HtmlAgilityPack; using Newtonsoft.Json; using Wabbajack.Common; using Wabbajack.Lib.Validation; @@ -15,8 +16,8 @@ namespace Wabbajack.Lib.Downloaders // IPS4 is the site used by LoversLab, VectorPlexus, etc. the general mechanics of each site are the // same, so we can fairly easily abstract the state. // Pass in the state type via TState - public abstract class AbstractIPS4Downloader : AbstractNeedsLoginDownloader, IDownloader - where TState : AbstractIPS4Downloader.State, new() + public abstract class AbstractIPS4Downloader : AbstractNeedsLoginDownloader, IDownloader + where TState : AbstractIPS4Downloader.State, new() where TDownloader : IDownloader { public override string SiteName { get; } @@ -61,7 +62,7 @@ namespace Wabbajack.Lib.Downloaders } - public class State : AbstractDownloadState where TDownloader : IDownloader + public class State : AbstractDownloadState, IMetaState where TDownloader : IDownloader { public string FileID { get; set; } public string FileName { get; set; } @@ -69,10 +70,12 @@ namespace Wabbajack.Lib.Downloaders private static bool IsHTTPS => Downloader.SiteURL.AbsolutePath.StartsWith("https://"); private static string URLPrefix => IsHTTPS ? "https://" : "http://"; - private static string Site => string.IsNullOrWhiteSpace(Downloader.SiteURL.Query) + public static string Site => string.IsNullOrWhiteSpace(Downloader.SiteURL.Query) ? $"{URLPrefix}{Downloader.SiteURL.Host}" : Downloader.SiteURL.ToString(); + public static AbstractNeedsLoginDownloader Downloader => (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance(); + public override object[] PrimaryKey { get @@ -100,13 +103,13 @@ namespace Wabbajack.Lib.Downloaders private async Task ResolveDownloadStream() { - var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance(); + //var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance(); TOP: var csrfurl = FileID == null ? $"{Site}/files/file/{FileName}/?do=download" : $"{Site}/files/file/{FileName}/?do=download&r={FileID}"; - var html = await downloader.AuthedClient.GetStringAsync(csrfurl); + var html = await Downloader.AuthedClient.GetStringAsync(csrfurl); var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])|(?<=csrfKey: \").*(?=[&\"\'])"); var matches = pattern.Matches(html).Cast(); @@ -122,10 +125,10 @@ namespace Wabbajack.Lib.Downloaders : $"{Site}/files/file/{FileName}/{sep}do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}"; - var streamResult = await downloader.AuthedClient.GetAsync(url); + var streamResult = await Downloader.AuthedClient.GetAsync(url); if (streamResult.StatusCode != HttpStatusCode.OK) { - Utils.ErrorThrow(new InvalidOperationException(), $"{downloader.SiteName} servers reported an error for file: {FileID}"); + Utils.ErrorThrow(new InvalidOperationException(), $"{Downloader.SiteName} servers reported an error for file: {FileID}"); } var contentType = streamResult.Content.Headers.ContentType; @@ -138,7 +141,7 @@ namespace Wabbajack.Lib.Downloaders var secs = times.Download - times.CurrentTime; for (int x = 0; x < secs; x++) { - Utils.Status($"Waiting for {secs} at the request of {downloader.SiteName}", Percent.FactoryPutInRange(x, secs)); + Utils.Status($"Waiting for {secs} at the request of {Downloader.SiteName}", Percent.FactoryPutInRange(x, secs)); await Task.Delay(1000); } streamResult.Dispose(); @@ -200,7 +203,19 @@ namespace Wabbajack.Lib.Downloaders }; } - private static AbstractNeedsLoginDownloader Downloader => (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance(); + // from IMetaState + public string URL => $"{Site}/files/file/{FileName}"; + public string Name { get; set; } + public string Author { get; set; } + public string Version { get; set; } + public string ImageURL { get; set; } + public virtual bool IsNSFW { get; set; } + public string Description { get; set; } + + public virtual async Task LoadMetaData() + { + return false; + } } protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) : diff --git a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs index 1e639860..e3bd5740 100644 --- a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs +++ b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs @@ -1,24 +1,9 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; -using System.Reactive; -using System.Reactive.Linq; -using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; -using System.Web; -using System.Windows.Input; -using CefSharp; -using ReactiveUI; +using HtmlAgilityPack; using Wabbajack.Common; -using Wabbajack.Lib.LibCefHelpers; -using Wabbajack.Lib.NexusApi; -using Wabbajack.Lib.Validation; using Wabbajack.Lib.WebAutomation; -using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.Lib.Downloaders { @@ -29,7 +14,6 @@ namespace Wabbajack.Lib.Downloaders public override Uri SiteURL => new Uri("https://www.loverslab.com"); public override Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico"); #endregion - public LoversLabDownloader() : base(new Uri("https://www.loverslab.com/login"), "loverslabcookies", "loverslab.com") { @@ -46,8 +30,31 @@ namespace Wabbajack.Lib.Downloaders Utils.Error(ex); } } + public class State : State { + public override bool IsNSFW => true; + + public override async Task LoadMetaData() + { + var html = await Downloader.AuthedClient.GetStringAsync(URL); + var doc = new HtmlDocument(); + doc.LoadHtml(html); + var node = doc.DocumentNode; + Name = node.SelectNodes("//h1[@class='ipsType_pageTitle ipsContained_container']/span")?.First().InnerHtml; + Author = 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 = node.SelectNodes("//section/h2[@class='ipsType_sectionHead']/span[@data-role='versionTitle']") + ? + .First().InnerHtml; + ImageURL = 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"); + return true; + } } } } diff --git a/Wabbajack.Lib/Downloaders/NexusDownloader.cs b/Wabbajack.Lib/Downloaders/NexusDownloader.cs index 0a5e443e..c0d5e60a 100644 --- a/Wabbajack.Lib/Downloaders/NexusDownloader.cs +++ b/Wabbajack.Lib/Downloaders/NexusDownloader.cs @@ -6,11 +6,13 @@ using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; +using Ceras; using ReactiveUI; using Wabbajack.Common; using Wabbajack.Common.StatusFeed.Errors; using Wabbajack.Lib.NexusApi; using Wabbajack.Lib.Validation; +using Game = Wabbajack.Common.Game; namespace Wabbajack.Lib.Downloaders { @@ -69,21 +71,18 @@ namespace Wabbajack.Lib.Downloaders Utils.Error($"Error getting mod info for Nexus mod with {general.modID}"); throw; } + return new State { - GameName = general.gameName, - FileID = general.fileID, - ModID = general.modID, + Name = NexusApiUtils.FixupSummary(info.name), + Author = NexusApiUtils.FixupSummary(info.author), Version = general.version ?? "0.0.0.0", - Author = info.author, - UploadedBy = info.uploaded_by, - UploaderProfile = info.uploaded_users_profile_url, - ModName = info.name, - SlideShowPic = info.picture_url, - NexusURL = NexusApiUtils.GetModURL(game, info.mod_id), - Summary = info.summary, - Adult = info.contains_adult_content - + ImageURL = info.picture_url, + IsNSFW = info.contains_adult_content, + Description = NexusApiUtils.FixupSummary(info.summary), + GameName = general.gameName, + ModID = general.modID, + FileID = general.fileID }; } @@ -123,20 +122,30 @@ namespace Wabbajack.Lib.Downloaders } } - public class State : AbstractDownloadState + public class State : AbstractDownloadState, IMetaState { + public string URL => $"http://nexusmods.com/{NexusApiUtils.ConvertGameName(GameName)}/mods/{ModID}"; + + public string Name { get; set; } + public string Author { get; set; } - public string FileID { get; set; } + + public string Version { get; set; } + + public string ImageURL { get; set; } + + public bool IsNSFW { get; set; } + + public string Description { get; set; } + + public async Task LoadMetaData() + { + return true; + } + public string GameName { get; set; } public string ModID { get; set; } - public string UploadedBy { get; set; } - public string UploaderProfile { get; set; } - public string Version { get; set; } - public string SlideShowPic { get; set; } - public string ModName { get; set; } - public string NexusURL { get; set; } - public string Summary { get; set; } - public bool Adult { get; set; } + public string FileID { get; set; } public override object[] PrimaryKey { get => new object[]{GameName, ModID, FileID};} @@ -192,7 +201,7 @@ namespace Wabbajack.Lib.Downloaders } catch (Exception ex) { - Utils.Log($"{ModName} - {GameName} - {ModID} - {FileID} - Error getting Nexus download URL - {ex}"); + Utils.Log($"{Name} - {GameName} - {ModID} - {FileID} - Error getting Nexus download URL - {ex}"); return false; } diff --git a/Wabbajack.Lib/MO2Compiler.cs b/Wabbajack.Lib/MO2Compiler.cs index da7a0047..6a99f918 100644 --- a/Wabbajack.Lib/MO2Compiler.cs +++ b/Wabbajack.Lib/MO2Compiler.cs @@ -87,7 +87,7 @@ namespace Wabbajack.Lib protected override async Task _Begin(CancellationToken cancel) { if (cancel.IsCancellationRequested) return false; - ConfigureProcessor(19, ConstructDynamicNumThreads(await RecommendQueueSize())); + ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize())); UpdateTracker.Reset(); UpdateTracker.NextStep("Gathering information"); Info("Looking for other profiles"); @@ -279,6 +279,9 @@ namespace Wabbajack.Lib UpdateTracker.NextStep("Building Patches"); await BuildPatches(); + UpdateTracker.NextStep("Gathering Metadata"); + await GatherMetaData(); + ModList = new ModList { GameType = CompilingGame.Game, diff --git a/Wabbajack.Lib/NexusApi/NexusApi.cs b/Wabbajack.Lib/NexusApi/NexusApi.cs index 2dc59bae..54d75838 100644 --- a/Wabbajack.Lib/NexusApi/NexusApi.cs +++ b/Wabbajack.Lib/NexusApi/NexusApi.cs @@ -284,7 +284,7 @@ namespace Wabbajack.Lib.NexusApi try { - Utils.Log($"Requesting manual download for {archive.ModName}"); + Utils.Log($"Requesting manual download for {archive.Name}"); return (await Utils.Log(await ManuallyDownloadNexusFile.Create(archive)).Task).ToString(); } catch (TaskCanceledException ex) diff --git a/Wabbajack.Lib/Validation/ValidateModlist.cs b/Wabbajack.Lib/Validation/ValidateModlist.cs index d4ef2b85..88cce7ad 100644 --- a/Wabbajack.Lib/Validation/ValidateModlist.cs +++ b/Wabbajack.Lib/Validation/ValidateModlist.cs @@ -113,7 +113,7 @@ namespace Wabbajack.Lib.Validation if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive)) { var ext = Path.GetExtension(p.ArchiveHashPath.Last()); - var url = (archive.archive.State as NexusDownloader.State).NexusURL; + var url = (archive.archive.State as NexusDownloader.State).URL; if (Consts.AssetFileExtensions.Contains(ext) && !(archive.permissions.CanModifyAssets ?? true)) { ValidationErrors.Push($"{p.To} from {url} is set to disallow asset modification"); @@ -131,7 +131,7 @@ namespace Wabbajack.Lib.Validation { if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive)) { - var url = (archive.archive.State as NexusDownloader.State).NexusURL; + var url = (archive.archive.State as NexusDownloader.State).URL; if (!(archive.permissions.CanExtractBSAs ?? true) && p.ArchiveHashPath.Skip(1).ButLast().Any(a => Consts.SupportedBSAs.Contains(Path.GetExtension(a).ToLower()))) { diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index c4600081..9c8dceef 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -274,7 +274,7 @@ namespace Wabbajack _titleText = Observable.CombineLatest( this.WhenAny(x => x.ModList) .Select(modList => modList?.Name ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.ModName) + this.WhenAny(x => x.Slideshow.TargetMod.State.Name) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) @@ -282,7 +282,7 @@ namespace Wabbajack _authorText = Observable.CombineLatest( this.WhenAny(x => x.ModList) .Select(modList => modList?.Author ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor) + this.WhenAny(x => x.Slideshow.TargetMod.State.Author) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) @@ -290,7 +290,7 @@ namespace Wabbajack _description = Observable.CombineLatest( this.WhenAny(x => x.ModList) .Select(modList => modList?.Description ?? string.Empty), - this.WhenAny(x => x.Slideshow.TargetMod.ModDescription) + this.WhenAny(x => x.Slideshow.TargetMod.State.Description) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs index 783b42c0..14370c33 100644 --- a/Wabbajack/View Models/ModListVM.cs +++ b/Wabbajack/View Models/ModListVM.cs @@ -38,6 +38,7 @@ namespace Wabbajack catch (Exception ex) { Error = ex; + Utils.Error(ex, "Exception while loading the modlist!"); } ImageObservable = Observable.Return(Unit.Default) diff --git a/Wabbajack/View Models/ModVM.cs b/Wabbajack/View Models/ModVM.cs index 80b0fc76..4b16f864 100644 --- a/Wabbajack/View Models/ModVM.cs +++ b/Wabbajack/View Models/ModVM.cs @@ -1,49 +1,29 @@ using ReactiveUI; using System; -using System.IO; -using System.Net.Http; using System.Reactive.Linq; using System.Windows.Media.Imaging; using Wabbajack.Common; using Wabbajack.Lib; using Wabbajack.Lib.Downloaders; -using Wabbajack.Lib.NexusApi; namespace Wabbajack { public class ModVM : ViewModel { - public string ModName { get; } - - public string ModID { get; } - - public string ModDescription { get; } - - public string ModAuthor { get; } - - public bool IsNSFW { get; } - - public string ModURL { get; } - - public string ImageURL { get; } + public IMetaState State { get; } // Image isn't exposed as a direct property, but as an observable. // This acts as a caching mechanism, as interested parties will trigger it to be created, // and the cached image will automatically be released when the last interested party is gone. public IObservable ImageObservable { get; } - public ModVM(NexusDownloader.State m) + public ModVM(IMetaState state) { - ModName = NexusApiUtils.FixupSummary(m.ModName); - ModID = m.ModID; - ModDescription = NexusApiUtils.FixupSummary(m.Summary); - ModAuthor = NexusApiUtils.FixupSummary(m.Author); - IsNSFW = m.Adult; - ModURL = m.NexusURL; - ImageURL = m.SlideShowPic; - ImageObservable = Observable.Return(ImageURL) + State = state; + + ImageObservable = Observable.Return(State.ImageURL) .ObserveOn(RxApp.TaskpoolScheduler) - .DownloadBitmapImage((ex) => Utils.Log($"Skipping slide for mod {ModName} ({ModID})")) + .DownloadBitmapImage((ex) => Utils.Log($"Skipping slide for mod {State.Name}")) .Replay(1) .RefCount(TimeSpan.FromMilliseconds(5000)); } diff --git a/Wabbajack/View Models/SlideShow.cs b/Wabbajack/View Models/SlideShow.cs index 9ff1fb22..7f60f259 100644 --- a/Wabbajack/View Models/SlideShow.cs +++ b/Wabbajack/View Models/SlideShow.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Text.RegularExpressions; using System.Windows.Media.Imaging; using Wabbajack.Common; using Wabbajack.Lib; @@ -33,7 +34,7 @@ namespace Wabbajack public ModVM TargetMod => _targetMod.Value; public ReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { }); - public ReactiveCommand VisitNexusSiteCommand { get; } + public ReactiveCommand VisitURLCommand { get; } public const int PreloadAmount = 4; @@ -82,23 +83,24 @@ namespace Wabbajack { if (modList?.SourceModList?.Archives == null) { - return Observable.Empty() - .ToObservableChangeSet(x => x.ModID); + return Observable.Empty() + .ToObservableChangeSet(x => x.URL); } return modList.SourceModList.Archives .Select(m => m.State) - .OfType() + .OfType() + .DistinctBy(x => x.URL) // Shuffle it .Shuffle(_random) - .AsObservableChangeSet(x => x.ModID); + .AsObservableChangeSet(x => x.URL); }) // Switch to the new list after every ModList change .Switch() - .Transform(nexus => new ModVM(nexus)) + .Transform(mod => new ModVM(mod)) .DisposeMany() // Filter out any NSFW slides if we don't want them .AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW)) - .Filter(slide => !slide.IsNSFW || ShowNSFW) + .Filter(slide => !slide.State.IsNSFW || ShowNSFW) .RefCount(); // Find target mod to display by combining dynamic list with currently desired index @@ -120,14 +122,18 @@ namespace Wabbajack .Switch() .ToGuiProperty(this, nameof(Image)); - VisitNexusSiteCommand = ReactiveCommand.Create( + VisitURLCommand = ReactiveCommand.Create( execute: () => { - Utils.OpenWebsite(TargetMod.ModURL); + Utils.OpenWebsite(TargetMod.State.URL); return Unit.Default; }, - canExecute: this.WhenAny(x => x.TargetMod.ModURL) - .Select(x => x?.StartsWith("https://") ?? false) + canExecute: this.WhenAny(x => x.TargetMod.State.URL) + .Select(x => + { + var regex = new Regex("^(http|https):\\/\\/"); + return x != null && regex.Match(x).Success; + }) .ObserveOnGuiThread()); // Preload upcoming images diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs index aa2061ef..b373f868 100644 --- a/Wabbajack/View Models/UserInterventionHandlers.cs +++ b/Wabbajack/View Models/UserInterventionHandlers.cs @@ -157,7 +157,7 @@ namespace Wabbajack }; await vm.Driver.WaitForInitialized(); IWebDriver browser = new CefSharpWrapper(vm.Browser); - vm.Instructions = $"Please Download {state.ModName} - {state.ModID} - {state.FileID}"; + vm.Instructions = $"Please Download {state.Name} - {state.ModID} - {state.FileID}"; browser.DownloadHandler = uri => { manuallyDownloadNexusFile.Resume(uri); diff --git a/Wabbajack/Views/Installers/InstallationView.xaml.cs b/Wabbajack/Views/Installers/InstallationView.xaml.cs index 240e08f5..dfb668c2 100644 --- a/Wabbajack/Views/Installers/InstallationView.xaml.cs +++ b/Wabbajack/Views/Installers/InstallationView.xaml.cs @@ -73,7 +73,7 @@ namespace Wabbajack }) .BindToStrict(this, x => x.PlayPauseButton.ToolTip) .DisposeWith(dispose); - this.WhenAny(x => x.ViewModel.Slideshow.VisitNexusSiteCommand) + this.WhenAny(x => x.ViewModel.Slideshow.VisitURLCommand) .BindToStrict(this, x => x.OpenWebsite.Command) .DisposeWith(dispose); this.BindStrict(this.ViewModel, x => x.Slideshow.ShowNSFW, x => x.ShowNSFWButton.IsChecked,