From 2f5e83598473313474d383da7f79e454b1a23fe7 Mon Sep 17 00:00:00 2001 From: Dasmius007 <176837655+Dasmius007@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:52:30 -0700 Subject: [PATCH 1/2] Added many new options, updated settings UI, changed internal data types for a couple settings, and misc. bug fixes --- HumbleKeysLibrary.cs | 515 ++++++++++++++++++-------- HumbleKeysLibrarySettings.cs | 56 ++- HumbleKeysLibrarySettingsView.xaml | 145 +++++--- HumbleKeysLibrarySettingsView.xaml.cs | 10 +- Localization/en-US.xaml | 4 + README.md | 6 +- changelog.md | 11 + 7 files changed, 523 insertions(+), 224 deletions(-) diff --git a/HumbleKeysLibrary.cs b/HumbleKeysLibrary.cs index f08f47fe..53e045fe 100644 --- a/HumbleKeysLibrary.cs +++ b/HumbleKeysLibrary.cs @@ -18,21 +18,29 @@ public class HumbleKeysLibrary : LibraryPlugin private static readonly ILogger logger = LogManager.GetLogger(); private const string dbImportMessageId = "humblekeyslibImportError"; private const string humblePurchaseUrlMask = @"https://www.humblebundle.com/downloads?key={0}"; - const string steamGameUrlMask = @"https://store.steampowered.com/app/{0}"; - const string steamSearchUrlMask = @"https://store.steampowered.com/search/?term={0}"; + private const string steamGameUrlMask = @"https://store.steampowered.com/app/{0}"; + private const string steamSearchUrlMask = @"https://store.steampowered.com/search/?term={0}"; private const string REDEEMED_STR = "Key: Redeemed"; private const string UNREDEEMED_STR = "Key: Unredeemed"; private const string UNREDEEMABLE_STR = "Key: Unredeemable"; private static readonly string[] PAST_TAGS = { REDEEMED_STR, UNREDEEMED_STR, UNREDEEMABLE_STR, "Redeemed", "Unredeemed", "Unredeemable"}; private const string HUMBLE_KEYS_SRC_NAME = "Humble Keys"; private const string HUMBLE_KEYS_PLATFORM_NAME = "Humble Key: "; + private const string NINTENDO_SWITCH = "nintendo_switch"; + private const string PC_WINDOWS = "pc_windows"; + #endregion + + #region === Variables ================ + private Platform winPlatform; + private Platform switchPlatform; + private readonly KeyInfo humbleKeysSource = new KeyInfo { Name = "Unknown" }; + public override string Name => "Humble Keys"; #endregion #region === Accessors ================ private HumbleKeysLibrarySettings Settings { get; set; } public override Guid Id { get; } = Guid.Parse("62ac4052-e08a-4a1a-b70a-c2c0c3673bb9"); - public override string Name => "Humble Keys"; // Implementing Client adds ability to open it via special menu in Playnite. public override LibraryClient Client { get; } = new HumbleKeysLibraryClient(); @@ -116,7 +124,7 @@ public Dictionary ScrapeOrders() return orders.Select(kv => kv.Value) .SelectMany(a => a.tpkd_dict?.all_tpks) .Where(t => t != null - && Settings.keyTypeWhitelist.Contains(t.key_type) + && Settings.keyTypeWhitelist.ContainsKey(t.key_type) && !string.IsNullOrWhiteSpace(t.gamekey) ).GroupBy(tpk => tpk.gamekey); } @@ -134,156 +142,208 @@ protected void ProcessOrders(Dictionary orders, IEnumerable platform.SpecificationId == PC_WINDOWS); + if (switchPlatform == null) switchPlatform = PlayniteApi.Database.Platforms.FirstOrDefault(platform => platform.SpecificationId == NINTENDO_SWITCH); + PlayniteApi.Database.BeginBufferUpdate(); - foreach (var tpkdGroup in tpkds) + try { - var tpkdGroupEntries = tpkdGroup.AsEnumerable(); - Tag humbleChoiceTag = null; - var groupEntries = tpkdGroupEntries.ToList(); - if (Settings.ImportChoiceKeys && Settings.CurrentTagMethodology != "none" && groupEntries.Count() > 1) + foreach (var tpkdGroup in tpkds) { - var isHumbleMonthly = orders[tpkdGroup.Key].product.human_name.Contains("Humble Monthly"); - if (Settings.CurrentTagMethodology == "all" || Settings.CurrentTagMethodology == "monthly" && isHumbleMonthly) + var tpkdGroupEntries = tpkdGroup.AsEnumerable(); + Tag humbleChoiceTag = null; + var groupEntries = tpkdGroupEntries.ToList(); + if (Settings.ImportChoiceKeys && tagMethod != TagMethodology.None && groupEntries.Count() > 1) { - humbleChoiceTag = PlayniteApi.Database.Tags.Add($"Bundle: {orders[tpkdGroup.Key].product.human_name}"); + var isHumbleMonthly = orders[tpkdGroup.Key].product.human_name.Contains("Humble Monthly"); + if (tagMethod == TagMethodology.All || tagMethod == TagMethodology.Monthly && isHumbleMonthly) + { + humbleChoiceTag = PlayniteApi.Database.Tags.Add($"Bundle: {orders[tpkdGroup.Key].product.human_name}"); + } } - } - var bundleContainsUnredeemableKeys = false; - var sourceOrder = orders[tpkdGroup.Key]; - if (sourceOrder != null && sourceOrder.product.category != "storefront" && sourceOrder.total_choices > 0 && sourceOrder.product.is_subs_v2_product) - { - bundleContainsUnredeemableKeys = sourceOrder.choices_remaining == 0; - } + var bundleContainsUnredeemableKeys = false; + var sourceOrder = orders[tpkdGroup.Key]; + if (sourceOrder != null && sourceOrder.product.category != "storefront" && sourceOrder.total_choices > 0 && sourceOrder.product.is_subs_v2_product) + { + bundleContainsUnredeemableKeys = sourceOrder.choices_remaining == 0; + } - // Monthly bundle has all choices made - if (bundleContainsUnredeemableKeys && humbleChoiceTag != null) - { - // search Playnite db for all games that are not included in groupEntries, these can be removed - var virtualOrders = groupEntries.Where(tpk => tpk.is_virtual).Select(GetGameId) ?? - new List(); - var gameKeys = virtualOrders.ToList(); - // for this bundle, get all games from the database that are not in the keys collection for this order - var libraryKeysNotInOrder = PlayniteApi.Database.Games - .Where(game => - game.TagIds != null && game.TagIds.Contains(humbleChoiceTag.Id) && gameKeys.Contains(game.GameId)) - .ToList(); - foreach (var game in libraryKeysNotInOrder) + // Monthly bundle has all choices made + if (bundleContainsUnredeemableKeys && humbleChoiceTag != null) { - switch (Settings.CurrentUnredeemableMethodology) + // search Playnite db for all games that are not included in groupEntries, these can be removed + var virtualOrders = groupEntries.Where(tpk => tpk.is_virtual).Select(GetGameId) ?? + new List(); + var gameKeys = virtualOrders.ToList(); + // for this bundle, get all games from the database that are not in the keys collection for this order + var libraryKeysNotInOrder = PlayniteApi.Database.Games + .Where(game => + game.TagIds != null && game.TagIds.Contains(humbleChoiceTag.Id) && gameKeys.Contains(game.GameId)) + .ToList(); + foreach (var game in libraryKeysNotInOrder) { - case "tag": + switch (unredeemableMethod) { - game.TagIds.Remove(unredeemedTag.Id); - if (game.TagIds.Contains(unredeemableTag.Id)) continue; - - game.TagIds.Add(unredeemableTag.Id); - PlayniteApi.Notifications.Add( - new NotificationMessage("HumbleKeysLibraryUpdate_"+game.Id, - $"{game.Name} is no longer redeemable", NotificationType.Info, - () => + case UnredeemableMethodology.Tag: + { + game.TagIds.Remove(unredeemedTag.Id); + if (game.TagIds.Contains(unredeemableTag.Id)) continue; + + game.TagIds.Add(unredeemableTag.Id); + PlayniteApi.Notifications.Add( + new NotificationMessage("HumbleKeysLibraryUpdate_" + game.Id, + $"{game.Name} is no longer redeemable", NotificationType.Info, + () => + { + if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) + return; + PlayniteApi.MainView.SelectGame(game.Id); + }) + ); + break; + } + case UnredeemableMethodology.Delete: + { + if (PlayniteApi.Database.Games.Remove(game)) { - if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) - return; - PlayniteApi.MainView.SelectGame(game.Id); - }) - ); - break; - } - case "delete": - { - if (PlayniteApi.Database.Games.Remove(game)) - { - removedGames.Add(game); - } + removedGames.Add(game); + } - break; + break; + } } } } - } - foreach (var tpkd in groupEntries) - { - var gameId = GetGameId(tpkd); + foreach (var tpkd in groupEntries) + { + var gameId = GetGameId(tpkd); - var alreadyImported = PlayniteApi.Database.Games.FirstOrDefault(game => game.GameId == gameId && game.PluginId == Id); + var alreadyImported = PlayniteApi.Database.Games.FirstOrDefault(game => game.GameId == gameId && game.PluginId == Id); - if (alreadyImported == null) - { - if (!Settings.IgnoreRedeemedKeys || (Settings.IgnoreRedeemedKeys && !IsKeyPresent(tpkd))) + if (alreadyImported == null) { - importedGames.Add(ImportNewGame(tpkd, humbleChoiceTag)); + if (!Settings.IgnoreRedeemedKeys || (Settings.IgnoreRedeemedKeys && !IsKeyPresent(tpkd))) + { + importedGames.Add(ImportNewGame(tpkd, humbleChoiceTag)); + } } - } - else - { - if (!Settings.IgnoreRedeemedKeys || (Settings.IgnoreRedeemedKeys && !IsKeyPresent(tpkd))) + else { - var tagsUpdated = UpdateRedemptionStatus(alreadyImported, tpkd, humbleChoiceTag); - var linksUpdated = UpdateStoreLinks(alreadyImported.Links, tpkd); - if (!tagsUpdated && !linksUpdated) continue; - - if (alreadyImported.TagIds.Contains(unredeemableTag.Id)) + if (!Settings.IgnoreRedeemedKeys || (Settings.IgnoreRedeemedKeys && !IsKeyPresent(tpkd))) { - switch (Settings.CurrentUnredeemableMethodology) + var tagsUpdated = UpdateRedemptionStatus(alreadyImported, tpkd, humbleChoiceTag); + var otherUpdated = UpdatePlatform(alreadyImported, tpkd); + if (UpdateRedemptionStore(alreadyImported, tpkd)) otherUpdated = true; + + if (Settings.AddLinks) { - case "tag": + if (alreadyImported.Links == null) + { + alreadyImported.Links = new ObservableCollection(); + } + + if (UpdateStoreLinks(alreadyImported.Links, tpkd, true)) otherUpdated = true; + } + + if (!tagsUpdated && !otherUpdated) continue; + + if (alreadyImported.TagIds != null && alreadyImported.TagIds.Contains(unredeemableTag.Id)) + { + switch (unredeemableMethod) + { + case UnredeemableMethodology.Tag: + { + PlayniteApi.Database.Games.Update(alreadyImported); + PlayniteApi.Notifications.Add( + new NotificationMessage("HumbleKeysLibraryUpdate_" + alreadyImported.Id, + $"{alreadyImported.Name} is no longer redeemable", NotificationType.Info, + () => + { + if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) + return; + PlayniteApi.MainView.SelectGame(alreadyImported.Id); + }) + ); + break; + } + case UnredeemableMethodology.Delete: + { + if (PlayniteApi.Database.Games.Remove(alreadyImported)) + { + removedGames.Add(alreadyImported); + } + break; + } + } + } + else + { + PlayniteApi.Database.Games.Update(alreadyImported); + if (tagsUpdated) { - PlayniteApi.Database.Games.Update(alreadyImported); PlayniteApi.Notifications.Add( - new NotificationMessage("HumbleKeysLibraryUpdate_"+alreadyImported.Id, - $"{alreadyImported.Name} is no longer redeemable", NotificationType.Info, + new NotificationMessage("HumbleKeysLibraryUpdate_" + alreadyImported.Id, + $"Tags Updated for {alreadyImported.Name}: " + GetOrderRedemptionTagState(tpkd), NotificationType.Info, () => { - if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) - return; + if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) return; PlayniteApi.MainView.SelectGame(alreadyImported.Id); }) ); - break; - } - case "delete": - { - if (PlayniteApi.Database.Games.Remove(alreadyImported)) - { - removedGames.Add(alreadyImported); - } - break; } } } else { - PlayniteApi.Database.Games.Update(alreadyImported); - PlayniteApi.Notifications.Add( - new NotificationMessage("HumbleKeysLibraryUpdate_"+alreadyImported.Id, - $"Tags Updated for {alreadyImported.Name}: "+GetOrderRedemptionTagState(tpkd), NotificationType.Info, - () => - { - if (PlayniteApi.ApplicationInfo.Mode == ApplicationMode.Fullscreen) return; - PlayniteApi.MainView.SelectGame(alreadyImported.Id); - }) - ); + // Remove Existing Game? + PlayniteApi.Database.Games.Remove(alreadyImported); + logger.Trace( + $"Removing game {alreadyImported.Name} since Settings.IgnoreRedeemedKeys is: [{Settings.IgnoreRedeemedKeys}] and IsKeyPresent() is [{IsKeyPresent(tpkd)}]"); } } - else - { - // Remove Existing Game? - PlayniteApi.Database.Games.Remove(alreadyImported); - logger.Trace( - $"Removing game {alreadyImported.Name} since Settings.IgnoreRedeemedKeys is: [{Settings.IgnoreRedeemedKeys}] and IsKeyPresent() is [{IsKeyPresent(tpkd)}]"); - } } } } - PlayniteApi.Database.EndBufferUpdate(); + finally + { + PlayniteApi.Database.EndBufferUpdate(); + } } - bool UpdateStoreLinks(ObservableCollection links, Order.TpkdDict.Tpk tpkd) + bool UpdateStoreLinks(ObservableCollection links, Order.TpkdDict.Tpk tpkd, bool useDispatcher) { - if (tpkd.key_type != "steam") return false; + var recordChanged = false; + + // add link to Humble purchase + if (!string.IsNullOrWhiteSpace(tpkd?.gamekey)) + { + var humbleLink = MakeLink(tpkd?.gamekey); + + if (!links.Contains(humbleLink)) + { + if (useDispatcher) + { + API.Instance.MainView.UIDispatcher.Invoke(delegate + { + links.Add(humbleLink); + }); + } + else + { + links.Add(humbleLink); + } + + recordChanged = true; + } + } + + if (tpkd.key_type != "steam") return recordChanged; Link steamGameLink; string humanName = string.Empty; @@ -305,16 +365,28 @@ bool UpdateStoreLinks(ObservableCollection links, Order.TpkdDict.Tpk tpkd) { humanName = humanName.Remove(humanName.LastIndexOf(" DLC", StringComparison.Ordinal)); } + var steamLinks = links.Where((link1, i) => link1.Name == "Steam"); var steamLinksList = steamLinks.ToList(); var existingSteamLink = steamLinksList.FirstOrDefault(); if (existingSteamLink == null) { - links.Add(steamGameLink); + if (useDispatcher) + { + API.Instance.MainView.UIDispatcher.Invoke(delegate + { + links.Add(steamGameLink); + }); + } + else + { + links.Add(steamGameLink); + } + return true; } - if (!string.IsNullOrEmpty(tpkd.steam_app_id) && existingSteamLink.Url == steamGameLink.Url) return false; + if (!string.IsNullOrEmpty(tpkd.steam_app_id) && existingSteamLink.Url == steamGameLink.Url) return recordChanged; // steam link url doesn't match expected value if (existingSteamLink.Url != steamGameLink.Url) @@ -323,7 +395,7 @@ bool UpdateStoreLinks(ObservableCollection links, Order.TpkdDict.Tpk tpkd) } else { - return false; + return recordChanged; } return true; @@ -335,73 +407,215 @@ Game ImportNewGame(Order.TpkdDict.Tpk tpkd, Tag groupTag = null) { Name = tpkd.human_name, GameId = GetGameId(tpkd), - Source = new MetadataNameProperty(HUMBLE_KEYS_SRC_NAME), - Platforms = new HashSet { new MetadataNameProperty( - HUMBLE_KEYS_PLATFORM_NAME + tpkd.key_type) }, - Tags = new HashSet(), - Links = new List(), }; - - // add tag reflecting redemption status - gameInfo.Tags.Add(new MetadataNameProperty(GetOrderRedemptionTagState(tpkd))); - - if (!string.IsNullOrWhiteSpace(tpkd?.gamekey)) + + if (Settings.RedemptionStore != (int)RedemptionStoreType.Source) { - gameInfo.Links.Add(MakeLink(tpkd?.gamekey)); + gameInfo.Source = new MetadataNameProperty(HUMBLE_KEYS_SRC_NAME); } - // adds link to humble purchase - var links = new ObservableCollection(); - if (UpdateStoreLinks(links, tpkd)) + + if (Settings.AddKeyStatus) { - gameInfo.Links.AddRange(links.ToList()); + // add tag reflecting redemption status + gameInfo.Tags = new HashSet { new MetadataNameProperty(GetOrderRedemptionTagState(tpkd)) }; } - PlayniteApi.Database.BeginBufferUpdate(); + + if (Settings.AddLinks) + { + var links = new ObservableCollection(); + if (UpdateStoreLinks(links, tpkd, false)) + { + gameInfo.Links = new List(); + gameInfo.Links.AddRange(links.ToList()); + } + } + + // no need to call BeginBufferUpdate() here because the only place this method is called already did that var game = PlayniteApi.Database.ImportGame(gameInfo, this); + var gameChanged = false; + if (groupTag != null) { + EnsureTagList(game); game.TagIds.Add(groupTag.Id); + gameChanged = true; + } + + if (UpdatePlatform(game, tpkd)) gameChanged = true; + if (UpdateRedemptionStore(game, tpkd)) gameChanged = true; + + if (gameChanged) + { PlayniteApi.Database.Games.Update(game); } - PlayniteApi.Database.EndBufferUpdate(); + return game; } // If a game is expired, add tag 'Key: Unredeemable' // If a game had been redeemed since last added to Playnite, remove the tag 'Key: Unredeemed' and add the tag 'Key: Redeemed' - // returns whether tags were updated or not + // returns whether tags were updated or not bool UpdateRedemptionStatus(Game existingGame, Order.TpkdDict.Tpk tpkd, Tag groupTag = null) { var recordChanged = false; if (existingGame == null) { return false; } - if (!Settings.keyTypeWhitelist.Contains(tpkd.key_type)) { return false; } - - // process tags on existingGame only if there was a change in tag status - - var existingRedemptionTagIds = existingGame.Tags?.Where(t => PAST_TAGS.Contains(t.Name)).ToList().Select(tag => tag.Id)??Enumerable.Empty(); - - // This creates a new Tag in the Tag Database if it doesn't already exist for 'Tag: Redeemed' - var tagIds = existingRedemptionTagIds.ToList(); - var currentTagState = PlayniteApi.Database.Tags.Add(GetOrderRedemptionTagState(tpkd)); + if (!Settings.keyTypeWhitelist.ContainsKey(tpkd.key_type)) { return false; } if (groupTag != null) { - if (existingGame.Tags != null && existingGame.Tags.All(tag => tag.Id != groupTag.Id)) + if (existingGame.Tags == null || existingGame.Tags.All(tag => tag.Id != groupTag.Id)) { + EnsureTagList(existingGame); existingGame.TagIds.Add(groupTag.Id); recordChanged = true; } } + + if (!Settings.AddKeyStatus) return recordChanged; + + // process tags on existingGame only if there was a change in tag status + var existingRedemptionTagIds = existingGame.Tags?.Where(t => PAST_TAGS.Contains(t.Name)).ToList().Select(tag => tag.Id)??Enumerable.Empty(); + + // This creates a new Tag in the Tag Database if it doesn't already exist for 'Tag: Redeemed' + var tagIds = existingRedemptionTagIds.ToList(); + // no need to call BeginBufferUpdate() here because the only place this method is called already did that + var currentTagState = PlayniteApi.Database.Tags.Add(GetOrderRedemptionTagState(tpkd)); + // existingGame already tagged with correct tag state if (tagIds.Contains(currentTagState.Id)) return recordChanged; - // remove all tags related to key state - existingGame.TagIds.RemoveAll(tagId => tagIds.Contains(tagId)); + if (existingGame.TagIds == null) + { + existingGame.TagIds = new List(); + } + else + { + // remove all tags related to key state + existingGame.TagIds.RemoveAll(tagId => tagIds.Contains(tagId)); + } existingGame.TagIds.Add(currentTagState.Id); return true; } + // Add Platform if needed + // returns whether it was updated or not + bool UpdatePlatform(Game game, Order.TpkdDict.Tpk tpkd) + { + var recordChanged = false; + + if (tpkd.key_type == "nintendo_direct") + { + // Add "Nintendo Switch" for all Nintendo keys + if (Settings.AddPlatformNintendo) + { + if (game.Platforms?.FirstOrDefault(platform => platform.SpecificationId == NINTENDO_SWITCH) == null) + { + EnsurePlatformList(game); + game.PlatformIds.Add(switchPlatform.Id); + recordChanged = true; + } + } + } + else + { + // Add default "PC (Windows)" for all other keys + if (Settings.AddPlatformWindows) + { + if (game.Platforms?.FirstOrDefault(platform => platform.SpecificationId == PC_WINDOWS) == null) + { + EnsurePlatformList(game); + game.PlatformIds.Add(winPlatform.Id); + recordChanged = true; + } + } + } + + return recordChanged; + } + + // Add Redemption Store if needed + // returns whether it was updated or not + bool UpdateRedemptionStore(Game game, Order.TpkdDict.Tpk tpkd) + { + if (Settings.RedemptionStore == (int)RedemptionStoreType.None) return false; + var recordChanged = false; + var newSource = GetKeyInfo(tpkd.key_type, Settings.RedemptionStore == (int)RedemptionStoreType.Source); + string newName = HUMBLE_KEYS_PLATFORM_NAME + newSource.Name; + + switch (Settings.RedemptionStore) + { + case (int)RedemptionStoreType.Source: + if (game.SourceId != newSource.SourceId) + { + game.SourceId = newSource.SourceId; + recordChanged = true; + } + + break; + case (int)RedemptionStoreType.Tag: + var newTag = PlayniteApi.Database.Tags.FirstOrDefault(tag => tag.Name == newName) ?? PlayniteApi.Database.Tags.Add(newName); + EnsureTagList(game); + + if (!game.TagIds.Contains(newTag.Id)) + { + game.TagIds.Add(newTag.Id); + recordChanged = true; + } + + break; + case (int)RedemptionStoreType.Category: + var newCat = PlayniteApi.Database.Categories.FirstOrDefault(category => category.Name == newName) ?? PlayniteApi.Database.Categories.Add(newName); + EnsureCategoryList(game); + + if (!game.CategoryIds.Contains(newCat.Id)) + { + game.CategoryIds.Add(newCat.Id); + recordChanged = true; + } + + break; + case (int)RedemptionStoreType.Platform: + var newPlat = PlayniteApi.Database.Platforms.FirstOrDefault(platform => platform.Name == newName) ?? PlayniteApi.Database.Platforms.Add(newName); + + EnsurePlatformList(game); + if (!game.PlatformIds.Contains(newPlat.Id)) + { + game.PlatformIds.Add(newPlat.Id); + recordChanged = true; + } + + break; + } + + return recordChanged; + } + + KeyInfo GetKeyInfo(string key_type, bool needSourceId) + { + if (Settings.keyTypeWhitelist.TryGetValue(key_type, out KeyInfo keyInfo)) + { + if (needSourceId && keyInfo.SourceId == Guid.Empty) + { + var source = PlayniteApi.Database.Sources.FirstOrDefault(src => src.Name == keyInfo.SourceName) ?? PlayniteApi.Database.Sources.Add(new MetadataNameProperty(keyInfo.SourceName)); + keyInfo.SourceId = source.Id; + } + + return keyInfo; + } + else + { + if (needSourceId && humbleKeysSource.SourceId == Guid.Empty) + { + var source = PlayniteApi.Database.Sources.FirstOrDefault(src => src.Name == HUMBLE_KEYS_SRC_NAME) ?? PlayniteApi.Database.Sources.Add(new MetadataNameProperty(HUMBLE_KEYS_SRC_NAME)); + humbleKeysSource.SourceId = source.Id; + } + + return humbleKeysSource; + } + } + #region === Helper Methods ============ private static string GetGameId(Order.TpkdDict.Tpk tpk) => $"{tpk.machine_name}_{tpk.gamekey}"; private static Link MakeLink(string gameKey) => new Link("Humble Purchase URL", string.Format(humblePurchaseUrlMask, gameKey) ); @@ -413,7 +627,18 @@ private static string GetOrderRedemptionTagState(Order.TpkdDict.Tpk t) if (t.is_expired) return UNREDEEMABLE_STR; return IsKeyPresent(t) ? REDEEMED_STR : UNREDEEMED_STR; } - + private static void EnsureTagList(Game game) + { + if (game.TagIds == null) game.TagIds = new List(); + } + private static void EnsurePlatformList(Game game) + { + if (game.PlatformIds == null) game.PlatformIds = new List(); + } + private static void EnsureCategoryList(Game game) + { + if (game.CategoryIds == null) game.CategoryIds = new List(); + } #endregion } } \ No newline at end of file diff --git a/HumbleKeysLibrarySettings.cs b/HumbleKeysLibrarySettings.cs index e34eda42..156a04b7 100644 --- a/HumbleKeysLibrarySettings.cs +++ b/HumbleKeysLibrarySettings.cs @@ -8,27 +8,63 @@ namespace HumbleKeys { + public enum RedemptionStoreType + { + None, // 0 + Source, // 1 + Tag, // 2 + Category, // 3 + Platform, // 4 + } + + public enum TagMethodology + { + None, // 0 + Monthly, // 1 + All, // 2 + } + + public enum UnredeemableMethodology + { + Tag, // 0 + Delete, // 1 + } + + public class KeyInfo + { + public string Name { get; set; } // Description used anywhere else needed, other than for Playnite's "Source" field + public string SourceName { get; set; } // Exact match for Playnite's "Source" field, only used when Redemption Store is saved to Source + public Guid SourceId { get; set; } // "Source" field GUID found in Playnite + } + public class HumbleKeysLibrarySettings : ObservableObject, ISettings { private readonly HumbleKeysLibrary plugin; - private static ILogger logger = LogManager.GetLogger(); + private static readonly ILogger logger = LogManager.GetLogger(); private HumbleKeysLibrarySettings editingClone; public bool ConnectAccount { get; set; } = false; public bool IgnoreRedeemedKeys { get; set; } = false; + public bool AddKeyStatus { get; set; } = true; + public int RedemptionStore { get; set; } = (int)RedemptionStoreType.Source; + public bool AddLinks { get; set; } = true; public bool ImportChoiceKeys { get; set; } = false; + public int TagWithBundleName { get; set; } = (int)TagMethodology.None; + public int UnredeemableKeyHandling { get; set; } = (int)UnredeemableMethodology.Tag; public bool CacheEnabled { get; set; } = false; - public string CurrentTagMethodology { get; set; } = "none"; + public bool AddPlatformNintendo { get; set; } = true; + public bool AddPlatformWindows { get; set; } = true; - public string CurrentUnredeemableMethodology { get; set; } = "tag"; - [DontSerialize] - public List keyTypeWhitelist = new List() { - "gog", - "nintendo_direct", - "origin", - "origin_keyless", - "steam", + public Dictionary keyTypeWhitelist = new Dictionary + { + //["epic"] = new KeyInfo { Name = "Epic", SourceName = "Epic" }, // This key type is valid so do we want to add it? I only have game dev asset keys at Epic myself right now but I assume this could be real games too + //["epic_keyless"] = new KeyInfo { Name = "Epic keyless", SourceName = "Epic" }, // Is this even a valid key type? I just guessed it might be because Humble mentions it has keyless Epic keys + ["gog"] = new KeyInfo { Name = "GOG", SourceName = "GOG" }, + ["nintendo_direct"] = new KeyInfo { Name = "Nintendo", SourceName = "Nintendo" }, + ["origin"] = new KeyInfo { Name = "EA", SourceName = "EA app" }, + ["origin_keyless"] = new KeyInfo { Name = "EA keyless", SourceName = "EA app" }, + ["steam"] = new KeyInfo { Name = "Steam", SourceName = "Steam" }, }; [DontSerialize] diff --git a/HumbleKeysLibrarySettingsView.xaml b/HumbleKeysLibrarySettingsView.xaml index ab39e486..ee9b66d9 100644 --- a/HumbleKeysLibrarySettingsView.xaml +++ b/HumbleKeysLibrarySettingsView.xaml @@ -29,69 +29,92 @@ IsChecked="{Binding ConnectAccount}" Content="{DynamicResource LOCHumbleSettingsConnectAccount}"/> - - - - - - - - - - - - - None - - - - - Monthly Only - - All - - - - - - - - - - + + + + + + + + + None + Source + Tag + Category + Platform + + + + + + + + + + - - Tag - Delete - - - + + None + + + + + Monthly Only + + All + + + + + + + + + + Tag + Delete + + - + IsChecked="{Binding CacheEnabled}" + Content="Enable Cache" + Name="EnableCache" + ToolTip="When checked, Humble Keys Library will create a persistent Cache in ExtensionsData directory, if a Cache entry exists, it will no longer be retrieved from Humble"/> + +