diff --git a/Wabbajack.sln b/Wabbajack.sln index 6b83520a..edc10232 100644 --- a/Wabbajack.sln +++ b/Wabbajack.sln @@ -151,8 +151,8 @@ Global {A47FFF32-782B-4D9F-8704-C98FB32FA8CC}.Release|x64.Build.0 = Release|Any CPU {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|Any CPU.ActiveCfg = Debug|Any CPU {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|Any CPU.Build.0 = Debug|Any CPU - {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|x64.ActiveCfg = Debug|x64 - {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|x64.Build.0 = Debug|x64 + {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|x64.ActiveCfg = Debug|Any CPU + {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug (no commandargs)|x64.Build.0 = Debug|Any CPU {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB003D18-DFFB-49C7-B054-FB375345AE26}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/Wabbajack/AppState.cs b/Wabbajack/AppState.cs index 208a5ba1..f3b7a80f 100644 --- a/Wabbajack/AppState.cs +++ b/Wabbajack/AppState.cs @@ -12,7 +12,7 @@ using System.Windows.Input; using System.Windows.Media.Imaging; using System.Windows.Threading; using Wabbajack.Common; -using Wabbajack.Properties; +using Wabbajack.NexusApi; namespace Wabbajack { @@ -92,7 +92,7 @@ namespace Wabbajack private void SetupSlideshow() { - var files = NexusAPI.CachedSlideShow; + var files = NexusApiClient.CachedSlideShow; if (files.Any()) { SlideShowElements = files.ToList(); @@ -100,7 +100,7 @@ namespace Wabbajack } public Random _random = new Random(); - public List SlideShowElements = new List(); + public List SlideShowElements = new List(); private DateTime _lastSlideShowUpdate = new DateTime(); public ObservableCollection Log { get; } @@ -387,11 +387,11 @@ namespace Wabbajack HTMLReport = _modList.ReportHTML; Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - SlideShowElements = modlist.Archives.OfType().Select(m => new NexusAPI.SlideShowItem + SlideShowElements = modlist.Archives.OfType().Select(m => new SlideShowItem { - ModName = NexusAPI.FixupSummary(m.ModName), - AuthorName = NexusAPI.FixupSummary(m.Author), - ModSummary = NexusAPI.FixupSummary(m.Summary), + ModName = NexusApiUtils.FixupSummary(m.ModName), + AuthorName = NexusApiUtils.FixupSummary(m.Author), + ModSummary = NexusApiUtils.FixupSummary(m.Summary), ImageURL = m.SlideShowPic, ModURL = m.NexusURL, }).ToList(); diff --git a/Wabbajack/Compiler.cs b/Wabbajack/Compiler.cs index 3c62ef23..382e4721 100644 --- a/Wabbajack/Compiler.cs +++ b/Wabbajack/Compiler.cs @@ -16,7 +16,7 @@ using K4os.Compression.LZ4.Streams; using Newtonsoft.Json; using VFS; using Wabbajack.Common; -using static Wabbajack.NexusAPI; +using Wabbajack.NexusApi; using Directory = Alphaleonis.Win32.Filesystem.Directory; using File = Alphaleonis.Win32.Filesystem.File; using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo; @@ -67,7 +67,6 @@ namespace Wabbajack public string MO2ProfileDir => Path.Combine(MO2Folder, "profiles", MO2Profile); public List InstallDirectives { get; private set; } - public string NexusKey { get; private set; } internal UserStatus User { get; private set; } public List SelectedArchives { get; private set; } public List AllFiles { get; private set; } @@ -75,6 +74,17 @@ namespace Wabbajack public ConcurrentBag ExtraFiles { get; private set; } public Dictionary ModInis { get; private set; } + private NexusApiClient _nexusApiClient; + private NexusApiClient NexusApiClient { + get + { + if (_nexusApiClient == null) + _nexusApiClient = new NexusApiClient(); + + return _nexusApiClient; + } + } + public VirtualFileSystem VFS => VirtualFileSystem.VFS; public List IndexedArchives { get; private set; } @@ -227,9 +237,8 @@ namespace Wabbajack if (IndexedArchives.Any(a => a.IniData?.General?.gameName != null)) { - NexusKey = GetNexusAPIKey(); - User = GetUserStatus(NexusKey); - if (!User.is_premium) Info($"User {User.name} is not a premium Nexus user, cannot continue"); + var nexusClient = new NexusApiClient(); + if (!nexusClient.IsPremium) Info($"User {nexusClient.Username} is not a premium Nexus user, cannot continue"); } @@ -484,13 +493,13 @@ namespace Wabbajack ModID = general.modID, Version = general.version ?? "0.0.0.0" }; - var info = GetModInfo(nm, NexusKey); + var info = _nexusApiClient.GetModInfo(nm); nm.Author = info.author; nm.UploadedBy = info.uploaded_by; nm.UploaderProfile = info.uploaded_users_profile_url; nm.ModName = info.name; nm.SlideShowPic = info.picture_url; - nm.NexusURL = NexusAPI.GetModURL(info.game_name, info.mod_id); + nm.NexusURL = NexusApiUtils.GetModURL(info.game_name, info.mod_id); nm.Summary = info.summary; result = nm; diff --git a/Wabbajack/Installer.cs b/Wabbajack/Installer.cs index 41a362a4..fb154533 100644 --- a/Wabbajack/Installer.cs +++ b/Wabbajack/Installer.cs @@ -14,6 +14,7 @@ using Compression.BSA; using K4os.Compression.LZ4.Streams; using VFS; using Wabbajack.Common; +using Wabbajack.NexusApi; using Directory = Alphaleonis.Win32.Filesystem.Directory; using File = Alphaleonis.Win32.Filesystem.File; using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo; @@ -25,6 +26,8 @@ namespace Wabbajack { private string _downloadsFolder; + private NexusApiClient _nexusApiClient; + public Installer(ModList mod_list, string output_folder) { Outputfolder = output_folder; @@ -44,7 +47,6 @@ namespace Wabbajack public ModList ModList { get; } public Dictionary HashedArchives { get; private set; } - public string NexusAPIKey { get; set; } public bool IgnoreMissingFiles { get; internal set; } public string GameFolder { get; set; } @@ -159,7 +161,7 @@ namespace Wabbajack mods.PMap(mod => { - var er = NexusAPI.EndorseMod(mod, NexusAPIKey); + var er = _nexusApiClient.EndorseMod(mod); Utils.Log($"Endorsed {mod.GameName} - {mod.ModID} - Result: {er.message}"); }); Info("Done! You may now exit the application!"); @@ -402,14 +404,19 @@ namespace Wabbajack Info("Getting Nexus API Key, if a browser appears, please accept"); if (ModList.Archives.OfType().Any()) { - NexusAPIKey = NexusAPI.GetNexusAPIKey(); + _nexusApiClient = new NexusApiClient(); - var user_status = NexusAPI.GetUserStatus(NexusAPIKey); - - if (!user_status.is_premium) + if (!_nexusApiClient.IsAuthenticated) { - Info( - $"Automated installs with Wabbajack requires a premium nexus account. {user_status.name} is not a premium account"); + Error( + $"Authenticating for the Nexus failed. A nexus account is required to automatically download mods."); + return; + } + + if (!_nexusApiClient.IsPremium) + { + Error( + $"Automated installs with Wabbajack requires a premium nexus account. {_nexusApiClient.Username} is not a premium account."); return; } } @@ -442,7 +449,7 @@ namespace Wabbajack string url; try { - url = NexusAPI.GetNexusDownloadLink(a, NexusAPIKey, !download); + url = _nexusApiClient.GetNexusDownloadLink(a, !download); if (!download) return true; } catch (Exception ex) diff --git a/Wabbajack/NexusAPI.cs b/Wabbajack/NexusAPI.cs deleted file mode 100644 index 5832cc67..00000000 --- a/Wabbajack/NexusAPI.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Reflection; -using System.Reflection.Emit; -using System.Security.Authentication; -using System.Threading.Tasks; -using System.Windows.Media.TextFormatting; -using Wabbajack.Common; -using WebSocketSharp; - -namespace Wabbajack -{ - internal class NexusAPI - { - public static string GetNexusAPIKey() - { - var fi = new FileInfo("nexus.key_cache"); - if (fi.Exists && fi.LastWriteTime > DateTime.Now.AddHours(-72)) return File.ReadAllText("nexus.key_cache"); - - var guid = Guid.NewGuid(); - var _websocket = new WebSocket("wss://sso.nexusmods.com") - { - SslConfiguration = - { - EnabledSslProtocols = SslProtocols.Tls12 - } - }; - - var api_key = new TaskCompletionSource(); - _websocket.OnMessage += (sender, msg) => { api_key.SetResult(msg.Data); }; - - _websocket.Connect(); - _websocket.Send("{\"id\": \"" + guid + "\", \"appid\": \"" + Consts.AppName + "\"}"); - - Process.Start($"https://www.nexusmods.com/sso?id={guid}&application=" + Consts.AppName); - - api_key.Task.Wait(); - var result = api_key.Task.Result; - File.WriteAllText("nexus.key_cache", result); - return result; - } - - private static HttpClient BaseNexusClient(string apikey) - { - var _baseHttpClient = new HttpClient(); - - _baseHttpClient.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent); - _baseHttpClient.DefaultRequestHeaders.Add("apikey", apikey); - _baseHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - _baseHttpClient.DefaultRequestHeaders.Add("Application-Name", Consts.AppName); - _baseHttpClient.DefaultRequestHeaders.Add("Application-Version", - $"{Assembly.GetEntryAssembly().GetName().Version}"); - return _baseHttpClient; - } - - public static string GetNexusDownloadLink(NexusMod archive, string apikey, bool cache = false) - { - if (cache && TryGetCachedLink(archive, apikey, out var result)) return result; - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - var client = BaseNexusClient(apikey); - string url; - var get_url_link = string.Format( - "https://api.nexusmods.com/v1/games/{0}/mods/{1}/files/{2}/download_link.json", - ConvertGameName(archive.GameName), archive.ModID, archive.FileID); - using (var s = client.GetStreamSync(get_url_link)) - { - url = s.FromJSON>().First().URI; - return url; - } - } - - private static bool TryGetCachedLink(NexusMod archive, string apikey, out string result) - { - if (!Directory.Exists(Consts.NexusCacheDirectory)) - Directory.CreateDirectory(Consts.NexusCacheDirectory); - - - var path = Path.Combine(Consts.NexusCacheDirectory, - $"link-{archive.GameName}-{archive.ModID}-{archive.FileID}.txt"); - if (!File.Exists(path) || DateTime.Now - new FileInfo(path).LastWriteTime > new TimeSpan(24, 0, 0)) - { - File.Delete(path); - result = GetNexusDownloadLink(archive, apikey); - File.WriteAllText(path, result); - return true; - } - - result = File.ReadAllText(path); - return true; - } - - public static string ConvertGameName(string gameName) - { - if (gameName == "SkyrimSE") return "skyrimspecialedition"; - if (gameName == "FalloutNV") return "newvegas"; - return gameName.ToLower(); - } - - - public static UserStatus GetUserStatus(string apikey) - { - var url = "https://api.nexusmods.com/v1/users/validate.json"; - var client = BaseNexusClient(apikey); - - using (var s = client.GetStreamSync(url)) - { - return s.FromJSON(); - } - } - - - public static NexusFileInfo GetFileInfo(NexusMod mod, string apikey) - { - var url = - $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/files/{mod.FileID}.json"; - var client = BaseNexusClient(apikey); - - using (var s = client.GetStreamSync(url)) - { - return s.FromJSON(); - } - } - - private const int CACHED_VERSION_NUMBER = 1; - public static ModInfo GetModInfo(NexusMod archive, string apikey) - { - if (!Directory.Exists(Consts.NexusCacheDirectory)) - Directory.CreateDirectory(Consts.NexusCacheDirectory); - - TOP: - var path = Path.Combine(Consts.NexusCacheDirectory, $"mod-info-{archive.GameName}-{archive.ModID}.json"); - try - { - if (File.Exists(path)) - { - var result = path.FromJSON(); - if (result._internal_version != CACHED_VERSION_NUMBER) - { - File.Delete(path); - goto TOP; - } - - return result; - } - } - catch (Exception) - { - File.Delete(path); - } - - - var url = - $"https://api.nexusmods.com/v1/games/{ConvertGameName(archive.GameName)}/mods/{archive.ModID}.json"; - var client = BaseNexusClient(apikey); - - using (var s = client.GetStreamSync(url)) - { - var result = s.FromJSON(); - result.game_name = archive.GameName; - result.mod_id = archive.ModID; - result._internal_version = CACHED_VERSION_NUMBER; - result.ToJSON(path); - return result; - } - } - - - public static EndorsementResponse EndorseMod(NexusMod mod, string apikey) - { - Utils.Status($"Endorsing ${mod.GameName} - ${mod.ModID}"); - var url = - $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/endorse.json"; - var client = BaseNexusClient(apikey); - - var content = new FormUrlEncodedContent(new Dictionary {{"version", mod.Version}}); - - using (var s = client.PostStreamSync(url, content)) - { - return s.FromJSON(); - } - } - - private class DownloadLink - { - public string URI { get; set; } - } - - - public class UserStatus - { - public string email; - public bool is_premium; - public bool is_supporter; - public string key; - public string name; - public string profile_url; - public string user_id; - } - - public class NexusFileInfo - { - public ulong category_id; - public string category_name; - public string changelog_html; - public string description; - public string external_virus_scan_url; - public ulong file_id; - public string file_name; - public bool is_primary; - public string mod_version; - public string name; - public ulong size; - public ulong size_kb; - public DateTime uploaded_time; - public ulong uploaded_timestamp; - public string version; - } - - public class ModInfo - { - public uint _internal_version; - public string game_name; - public string mod_id; - public string name; - public string summary; - public string author; - public string uploaded_by; - public string uploaded_users_profile_url; - public string picture_url; - } - - public class SlideShowItem - { - public string ImageURL; - public string ModName; - public string ModSummary; - public string AuthorName; - public string ModURL; - } - - public static IEnumerable CachedSlideShow - { - get - { - if (!Directory.Exists(Consts.NexusCacheDirectory)) return new SlideShowItem[]{}; - - return Directory.EnumerateFiles(Consts.NexusCacheDirectory) - .Where(f => f.EndsWith(".json")) - .Select(f => f.FromJSON()) - .Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null) - .Select(m => new SlideShowItem - { - ImageURL = m.picture_url, - ModName = FixupSummary(m.name), - AuthorName = FixupSummary(m.author), - ModURL = GetModURL(m.game_name, m.mod_id), - ModSummary = FixupSummary(m.summary) - }); - } - } - - public static string GetModURL(string argGameName, string argModId) - { - return $"https://nexusmods.com/{ConvertGameName(argGameName)}/mods/{argModId}"; - } - - public static string FixupSummary(string argSummary) - { - if (argSummary != null) - { - return argSummary.Replace("'", "'") - .Replace("
", "\n\n") - .Replace("
", "\n\n") - .Replace("!", "!"); - } - - return argSummary; - } - } - - public class EndorsementResponse - { - public string message; - public string status; - } -} \ No newline at end of file diff --git a/Wabbajack/NexusApi/Dtos.cs b/Wabbajack/NexusApi/Dtos.cs new file mode 100644 index 00000000..1eda50cb --- /dev/null +++ b/Wabbajack/NexusApi/Dtos.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wabbajack.NexusApi +{ + public class UserStatus + { + public string email; + public bool is_premium; + public bool is_supporter; + public string key; + public string name; + public string profile_url; + public string user_id; + } + + public class NexusFileInfo + { + public ulong category_id; + public string category_name; + public string changelog_html; + public string description; + public string external_virus_scan_url; + public ulong file_id; + public string file_name; + public bool is_primary; + public string mod_version; + public string name; + public ulong size; + public ulong size_kb; + public DateTime uploaded_time; + public ulong uploaded_timestamp; + public string version; + } + + public class ModInfo + { + public uint _internal_version; + public string game_name; + public string mod_id; + public string name; + public string summary; + public string author; + public string uploaded_by; + public string uploaded_users_profile_url; + public string picture_url; + } + + public class SlideShowItem + { + public string ImageURL; + public string ModName; + public string ModSummary; + public string AuthorName; + public string ModURL; + } + + public class EndorsementResponse + { + public string message; + public string status; + } +} diff --git a/Wabbajack/NexusApi/NexusApi.cs b/Wabbajack/NexusApi/NexusApi.cs new file mode 100644 index 00000000..ae8bef29 --- /dev/null +++ b/Wabbajack/NexusApi/NexusApi.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Security.Authentication; +using System.Threading.Tasks; +using Wabbajack.Common; +using static Wabbajack.NexusApi.NexusApiUtils; +using WebSocketSharp; + +namespace Wabbajack.NexusApi +{ + internal class NexusApiClient : INotifyPropertyChanged + { + private static readonly string API_KEY_CACHE_FILE = "nexus.key_cache"; + + private static readonly uint CACHED_VERSION_NUMBER = 1; + + + private readonly HttpClient _httpClient; + + + #region Authentication + + private readonly string _apiKey; + + public bool IsAuthenticated => _apiKey != null; + + private UserStatus _userStatus; + + public bool IsPremium => IsAuthenticated && _userStatus.is_premium; + + public string Username => _userStatus?.name; + + + private static string GetApiKey() + { + // check if there exists a cached api key + var fi = new FileInfo(API_KEY_CACHE_FILE); + if (fi.Exists && fi.LastWriteTime > DateTime.Now.AddHours(-72)) + { + return File.ReadAllText(API_KEY_CACHE_FILE); + } + + // open a web socket to receive the api key + var guid = Guid.NewGuid(); + var _websocket = new WebSocket("wss://sso.nexusmods.com") + { + SslConfiguration = + { + EnabledSslProtocols = SslProtocols.Tls12 + } + }; + + var api_key = new TaskCompletionSource(); + _websocket.OnMessage += (sender, msg) => { api_key.SetResult(msg.Data); }; + + _websocket.Connect(); + _websocket.Send("{\"id\": \"" + guid + "\", \"appid\": \"" + Consts.AppName + "\"}"); + + // open a web browser to get user permission + Process.Start($"https://www.nexusmods.com/sso?id={guid}&application=" + Consts.AppName); + + // get the api key from the socket and cache it + api_key.Task.Wait(); + var result = api_key.Task.Result; + File.WriteAllText(API_KEY_CACHE_FILE, result); + + return result; + } + + private UserStatus GetUserStatus() + { + var url = "https://api.nexusmods.com/v1/users/validate.json"; + return Get(url); + } + + #endregion + + #region Rate Tracking + + private readonly object RemainingLock = new object(); + + private int _dailyRemaining; + public int DailyRemaining + { + get + { + lock (RemainingLock) + { + return _dailyRemaining; + } + } + } + + private int _hourlyRemaining; + public int HourlyRemaining + { + get + { + lock (RemainingLock) + { + return _hourlyRemaining; + } + } + } + + + private void UpdateRemaining(HttpResponseMessage response) + { + int dailyRemaining = int.Parse(response.Headers.GetValues("x-rl-daily-remaining").First()); + int hourlyRemaining = int.Parse(response.Headers.GetValues("x-rl-hourly-remaining").First()); + + lock (RemainingLock) + { + _dailyRemaining = Math.Min(dailyRemaining, hourlyRemaining); + _hourlyRemaining = Math.Min(dailyRemaining, hourlyRemaining); + + OnPropertyChanged(nameof(DailyRemaining)); + OnPropertyChanged(nameof(HourlyRemaining)); + } + } + + #endregion + + + public NexusApiClient() + { + _apiKey = GetApiKey(); + _httpClient = new HttpClient(); + + // set default headers for all requests to the Nexus API + var headers = _httpClient.DefaultRequestHeaders; + headers.Add("User-Agent", Consts.UserAgent); + headers.Add("apikey", _apiKey); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + headers.Add("Application-Name", Consts.AppName); + headers.Add("Application-Version", $"{Assembly.GetEntryAssembly().GetName().Version}"); + + _userStatus = GetUserStatus(); + } + + private T Get(string url) + { + Task responseTask = _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + responseTask.Wait(); + + var response = responseTask.Result; + UpdateRemaining(response); + + using (var stream = _httpClient.GetStreamSync(url)) + { + return stream.FromJSON(); + } + } + + + public string GetNexusDownloadLink(NexusMod archive, bool cache = false) + { + if (cache && TryGetCachedLink(archive, out var result)) + return result; + + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + + var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(archive.GameName)}/mods/{archive.ModID}/files/{archive.FileID}/download_link.json"; + return Get>(url).First().URI; + } + + private bool TryGetCachedLink(NexusMod archive, out string result) + { + if (!Directory.Exists(Consts.NexusCacheDirectory)) + Directory.CreateDirectory(Consts.NexusCacheDirectory); + + var path = Path.Combine(Consts.NexusCacheDirectory, $"link-{archive.GameName}-{archive.ModID}-{archive.FileID}.txt"); + if (!File.Exists(path) || (DateTime.Now - new FileInfo(path).LastWriteTime).TotalHours > 24) + { + File.Delete(path); + result = GetNexusDownloadLink(archive); + File.WriteAllText(path, result); + return true; + } + + result = File.ReadAllText(path); + return true; + } + + public NexusFileInfo GetFileInfo(NexusMod mod) + { + var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/files/{mod.FileID}.json"; + return Get(url); + } + + public ModInfo GetModInfo(NexusMod archive) + { + if (!Directory.Exists(Consts.NexusCacheDirectory)) + Directory.CreateDirectory(Consts.NexusCacheDirectory); + + ModInfo result = null; + TOP: + var path = Path.Combine(Consts.NexusCacheDirectory, $"mod-info-{archive.GameName}-{archive.ModID}.json"); + try + { + if (File.Exists(path)) + { + result = path.FromJSON(); + if (result._internal_version != CACHED_VERSION_NUMBER) + { + File.Delete(path); + goto TOP; + } + + return result; + } + } + catch (Exception) + { + File.Delete(path); + } + + var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(archive.GameName)}/mods/{archive.ModID}.json"; + result = Get(url); + + result.game_name = archive.GameName; + result.mod_id = archive.ModID; + result._internal_version = CACHED_VERSION_NUMBER; + result.ToJSON(path); + return result; + } + + public EndorsementResponse EndorseMod(NexusMod mod) + { + Utils.Status($"Endorsing ${mod.GameName} - ${mod.ModID}"); + var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/endorse.json"; + + var content = new FormUrlEncodedContent(new Dictionary {{"version", mod.Version}}); + + using (var stream = _httpClient.PostStreamSync(url, content)) + { + return stream.FromJSON(); + } + } + + + public static IEnumerable CachedSlideShow + { + get + { + if (!Directory.Exists(Consts.NexusCacheDirectory)) return new SlideShowItem[]{}; + + return Directory.EnumerateFiles(Consts.NexusCacheDirectory) + .Where(f => f.EndsWith(".json")) + .Select(f => f.FromJSON()) + .Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null) + .Select(m => new SlideShowItem + { + ImageURL = m.picture_url, + ModName = FixupSummary(m.name), + AuthorName = FixupSummary(m.author), + ModURL = GetModURL(m.game_name, m.mod_id), + ModSummary = FixupSummary(m.summary) + }); + } + } + + + private class DownloadLink + { + public string URI { get; set; } + } + + + #region INotifyPropertyChanged + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + #endregion + } + +} \ No newline at end of file diff --git a/Wabbajack/NexusApi/NexusApiUtils.cs b/Wabbajack/NexusApi/NexusApiUtils.cs new file mode 100644 index 00000000..79188636 --- /dev/null +++ b/Wabbajack/NexusApi/NexusApiUtils.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wabbajack.NexusApi +{ + public sealed class NexusApiUtils + { + public static string ConvertGameName(string gameName) + { + if (gameName == "SkyrimSE") return "skyrimspecialedition"; + if (gameName == "FalloutNV") return "newvegas"; + return gameName.ToLower(); + } + + public static string GetModURL(string argGameName, string argModId) + { + return $"https://nexusmods.com/{ConvertGameName(argGameName)}/mods/{argModId}"; + } + + public static string FixupSummary(string argSummary) + { + if (argSummary != null) + { + return argSummary.Replace("'", "'") + .Replace("
", "\n\n") + .Replace("
", "\n\n") + .Replace("!", "!"); + } + + return argSummary; + } + } +} diff --git a/Wabbajack/ReportBuilder.cs b/Wabbajack/ReportBuilder.cs index 148445e0..03ac6889 100644 --- a/Wabbajack/ReportBuilder.cs +++ b/Wabbajack/ReportBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Wabbajack.Common; +using Wabbajack.NexusApi; namespace Wabbajack { @@ -51,9 +52,9 @@ namespace Wabbajack { case NexusMod m: var profile = m.UploaderProfile.Replace("/games/", - "/" + NexusAPI.ConvertGameName(m.GameName).ToLower() + "/"); + "/" + NexusApiUtils.ConvertGameName(m.GameName).ToLower() + "/"); NoWrapText( - $"* [{m.Name}](http://nexusmods.com/{NexusAPI.ConvertGameName(m.GameName)}/mods/{m.ModID})"); + $"* [{m.Name}](http://nexusmods.com/{NexusApiUtils.ConvertGameName(m.GameName)}/mods/{m.ModID})"); NoWrapText($" * Author : [{m.UploadedBy}]({profile})"); NoWrapText($" * Version : {m.Version}"); break; diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index df7b5b77..1f1eef2e 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -182,6 +182,8 @@ + + ModeSelectionWindow.xaml @@ -216,7 +218,7 @@ - + Code