diff --git a/DragonFruit.Six.Api/DragonFruit.Six.Api.csproj b/DragonFruit.Six.Api/DragonFruit.Six.Api.csproj index bd82da3c..dcf259fa 100644 --- a/DragonFruit.Six.Api/DragonFruit.Six.Api.csproj +++ b/DragonFruit.Six.Api/DragonFruit.Six.Api.csproj @@ -26,8 +26,8 @@ - - + + diff --git a/DragonFruit.Six.Api/Legacy/Entities/LegacyPlaylistStats.cs b/DragonFruit.Six.Api/Legacy/Entities/LegacyPlaylistStats.cs index e37f8b27..ef64b7dd 100644 --- a/DragonFruit.Six.Api/Legacy/Entities/LegacyPlaylistStats.cs +++ b/DragonFruit.Six.Api/Legacy/Entities/LegacyPlaylistStats.cs @@ -30,6 +30,8 @@ public void Include(SeasonalStats stats, bool includeAbandonsAsLosses = true) Wins += stats.Wins; Losses += stats.Losses; + MatchesPlayed += stats.Wins + stats.Losses + stats.Abandons; + if (includeAbandonsAsLosses) { Losses += stats.Abandons; diff --git a/DragonFruit.Six.Api/Modern/ModernStatsDeserializer.cs b/DragonFruit.Six.Api/Modern/ModernStatsDeserializer.cs index d718d568..6e7c8a0a 100644 --- a/DragonFruit.Six.Api/Modern/ModernStatsDeserializer.cs +++ b/DragonFruit.Six.Api/Modern/ModernStatsDeserializer.cs @@ -1,8 +1,8 @@ // Dragon6 API Copyright DragonFruit Network // Licensed under Apache-2. Refer to the LICENSE file for more info +using DragonFruit.Six.Api.Accounts.Entities; using DragonFruit.Six.Api.Modern.Containers; -using DragonFruit.Six.Api.Modern.Requests; using DragonFruit.Six.Api.Modern.Utils; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -14,10 +14,20 @@ public static class ModernStatsDeserializer /// /// Abstracts a ubisoft stats response to present the data required /// - public static ModernModeStatsContainer ProcessData(this JObject source, ModernStatsRequest request) + public static ModernModeStatsContainer ProcessData(this JObject source, UbisoftAccount account) { - var platformKey = request.PlatformGroup?.ToString().ToUpperInvariant() ?? request.Account.Platform.ModernName(); - return source?["platforms"]?[platformKey]?.ToObject>(new JsonSerializer + var profileContainer = source?["profileData"].ToObject(); + + if (profileContainer == null) + { + return null; + } + + var platformContainer = profileContainer.Count == 1 + ? profileContainer.First.Value().Value.ToObject() + : profileContainer[account.ProfileId]; + + return platformContainer?["platforms"]?.First.Value().Value.ToObject>(new JsonSerializer { Converters = { new JsonPathConverter() } }); diff --git a/DragonFruit.Six.Api/Modern/ModernStatsExtensions.cs b/DragonFruit.Six.Api/Modern/ModernStatsExtensions.cs index a18da794..31f809b4 100644 --- a/DragonFruit.Six.Api/Modern/ModernStatsExtensions.cs +++ b/DragonFruit.Six.Api/Modern/ModernStatsExtensions.cs @@ -29,7 +29,7 @@ public static class ModernStatsExtensions public static async Task> GetModernStatsAsync(this Dragon6Client client, ModernStatsRequest request, CancellationToken token = default) { var response = await client.PerformAsync(request, token).ConfigureAwait(false); - return response.ProcessData(request); + return response.ProcessData(request.Account); } /// @@ -97,13 +97,15 @@ public static Task GetModernOperatorInfoAsync(this ApiClie /// The to use /// The to get stats for /// The to get stats for + /// Optional to get modern cross-platform stats for. /// Optional /// A container with all seasons tracked. Will return null if no stats found - public static Task>> GetModernSeasonStatsAsync(this Dragon6Client client, UbisoftAccount account, PlaylistType playlistType = PlaylistType.All, CancellationToken token = default) + public static Task>> GetModernSeasonStatsAsync(this Dragon6Client client, UbisoftAccount account, PlaylistType playlistType = PlaylistType.All, PlatformGroup? crossPlatformGroup = null, CancellationToken token = default) { var request = new ModernSeasonalStatsRequest(account) { - Playlist = playlistType + Playlist = playlistType, + PlatformGroup = crossPlatformGroup }; return client.GetModernStatsAsync>(request, token); diff --git a/DragonFruit.Six.Api/Modern/Requests/ModernSeasonalStatsRequest.cs b/DragonFruit.Six.Api/Modern/Requests/ModernSeasonalStatsRequest.cs index 094462cf..9b61fcc8 100644 --- a/DragonFruit.Six.Api/Modern/Requests/ModernSeasonalStatsRequest.cs +++ b/DragonFruit.Six.Api/Modern/Requests/ModernSeasonalStatsRequest.cs @@ -2,12 +2,16 @@ // Licensed under Apache-2. Refer to the LICENSE file for more info using System; +using System.Collections.Generic; +using DragonFruit.Data; +using DragonFruit.Data.Requests; using DragonFruit.Six.Api.Accounts.Entities; using DragonFruit.Six.Api.Enums; +using DragonFruit.Six.Api.Modern.Utils; namespace DragonFruit.Six.Api.Modern.Requests { - public class ModernSeasonalStatsRequest : ModernStatsRequest + public class ModernSeasonalStatsRequest : ModernStatsRequest, IRequestExecutingCallback { protected override string RequestCategory => "seasonal"; protected override string RequestType => "summary"; @@ -38,5 +42,15 @@ public override OperatorType OperatorType protected override string FormattedStartDate => null; protected override string FormattedEndDate => null; protected override string OperatorTypeNames => null; + + void IRequestExecutingCallback.OnRequestExecuting(ApiClient client) + { + var platformQuery = !PlatformGroup.HasValue + ? new KeyValuePair("platform", Account.Platform.ModernName()) + : new KeyValuePair("platformGroup", PlatformGroup.ToString()); + + Queries.Clear(); + Queries.Add(platformQuery); + } } } diff --git a/DragonFruit.Six.Api/Modern/Requests/ModernStatsRequest.cs b/DragonFruit.Six.Api/Modern/Requests/ModernStatsRequest.cs index 250cbca9..b7a61ec8 100644 --- a/DragonFruit.Six.Api/Modern/Requests/ModernStatsRequest.cs +++ b/DragonFruit.Six.Api/Modern/Requests/ModernStatsRequest.cs @@ -3,17 +3,21 @@ using System; using System.Collections.Generic; +using System.Linq; using DragonFruit.Data; using DragonFruit.Data.Parameters; +using DragonFruit.Data.Requests; using DragonFruit.Six.Api.Accounts.Entities; +using DragonFruit.Six.Api.Accounts.Enums; using DragonFruit.Six.Api.Enums; using DragonFruit.Six.Api.Modern.Enums; using DragonFruit.Six.Api.Modern.Utils; +using DragonFruit.Six.Api.Seasonal.Requests; using JetBrains.Annotations; namespace DragonFruit.Six.Api.Modern.Requests { - public abstract class ModernStatsRequest : UbiApiRequest + public abstract class ModernStatsRequest : UbiApiRequest, IRequestExecutingCallback { private const string DateTimeFormat = "yyyyMMdd"; private const int DefaultStartWindow = 14; @@ -22,11 +26,19 @@ public abstract class ModernStatsRequest : UbiApiRequest private OperatorType? _operatorType; private DateTimeOffset? _startDate, _endDate; - public override string Path => $"https://prod.datadev.ubisoft.com/v1/profiles/{Account.ProfileId}/playerstats"; + protected readonly IList> Queries; + + private static readonly DateTimeOffset CrossPlatformStartDate = new(2022, 12, 6, 0, 0, 0, TimeSpan.Zero); + + public override string Path => $"https://prod.datadev.ubisoft.com/v1/users/{Account.UbisoftId}/playerstats"; + + protected override IEnumerable> AdditionalQueries => Queries; protected ModernStatsRequest(UbisoftAccount account) { Account = account ?? throw new NullReferenceException(); + + Queries = new List>(1); } /// @@ -92,12 +104,11 @@ public virtual DateTimeOffset EndDate /// List of seasons to return stats for. Provides an alternative timespan compared to start-end dates. /// [QueryParameter("seasons", CollectionConversionMode.Concatenated)] - public virtual IEnumerable Seasons { get; set; } + public virtual IEnumerable Seasons { get; set; } /// /// Optional to override when getting cross-progression metrics /// - [QueryParameter("platformGroup", EnumHandlingMode.StringUpper)] public PlatformGroup? PlatformGroup { get; set; } /// @@ -117,11 +128,6 @@ public virtual DateTimeOffset EndDate [QueryParameter("view")] protected virtual string RequestCategory => "current"; - [CanBeNull] - [UsedImplicitly] - [QueryParameter("platform")] - protected string PlatformName => PlatformGroup.HasValue ? null : Account.Platform.ModernName(); - [UsedImplicitly] [QueryParameter("spaceId")] protected string GameSpaceId => Account.Platform.GameSpaceId(); @@ -143,5 +149,38 @@ public virtual DateTimeOffset EndDate [UsedImplicitly] [QueryParameter("teamRole")] protected virtual string OperatorTypeNames => OperatorType.Expand(); + + void IRequestExecutingCallback.OnRequestExecuting(ApiClient client) + { + bool useCrossPlayQueries; + + if (Seasons?.Any() != true) + { + useCrossPlayQueries = EndDate > CrossPlatformStartDate; + } + else + { + // check seasons to make sure old and new seasons aren't mixed in + if (Seasons.All(x => x.SeasonNumber < SeasonalStatsRecordRequest.CrossPlatformProgressionId)) + { + useCrossPlayQueries = false; + } + else if (Seasons.All(x => x.SeasonNumber >= SeasonalStatsRecordRequest.CrossPlatformProgressionId)) + { + useCrossPlayQueries = true; + } + else + { + throw new ArgumentException($"{nameof(Seasons)} combines both cross-platform and individual platform seasons.", nameof(Seasons)); + } + } + + var platformQuery = useCrossPlayQueries + ? new KeyValuePair("platform", Account.Platform.ModernName()) + : new KeyValuePair("platformGroup", (PlatformGroup ?? (Account.Platform == Platform.PC ? Api.Enums.PlatformGroup.PC : Api.Enums.PlatformGroup.Console)).ToString()); + + Queries.Clear(); + Queries.Add(platformQuery); + } } } diff --git a/DragonFruit.Six.Api/Modern/Utils/ModernPlatformUtils.cs b/DragonFruit.Six.Api/Modern/Utils/ModernPlatformUtils.cs index 55492caf..4a13e8c8 100644 --- a/DragonFruit.Six.Api/Modern/Utils/ModernPlatformUtils.cs +++ b/DragonFruit.Six.Api/Modern/Utils/ModernPlatformUtils.cs @@ -14,8 +14,8 @@ public static class ModernPlatformUtils public static string ModernName(this Platform platform) => platform switch { Platform.PC => "PC", - Platform.PSN => "PSN", - Platform.XB1 => "XONE", + Platform.PSN => "PLAYSTATION", + Platform.XB1 => "XBOX", _ => throw new ArgumentOutOfRangeException() }; diff --git a/DragonFruit.Six.Api/Modern/Utils/ModernSeason.cs b/DragonFruit.Six.Api/Modern/Utils/ModernSeason.cs new file mode 100644 index 00000000..256ba24f --- /dev/null +++ b/DragonFruit.Six.Api/Modern/Utils/ModernSeason.cs @@ -0,0 +1,23 @@ +// Dragon6 API Copyright DragonFruit Network +// Licensed under Apache-2. Refer to the LICENSE file for more info + +using System; + +namespace DragonFruit.Six.Api.Modern.Utils +{ + public readonly struct ModernSeason + { + public ModernSeason(int year, int season) + { + Year = year; + Season = Math.Max(season, 4); + } + + public int Year { get; } + public int Season { get; } + + public int SeasonNumber => (Year - 1) * 4 + Season; + + public override string ToString() => $"Y{Year}S{Season}"; + } +} diff --git a/DragonFruit.Six.Api/Services/Verification/AccountExtensions.cs b/DragonFruit.Six.Api/Services/Verification/AccountExtensions.cs deleted file mode 100644 index 77dd25ef..00000000 --- a/DragonFruit.Six.Api/Services/Verification/AccountExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Dragon6 API Copyright DragonFruit Network -// Licensed under Apache-2. Refer to the LICENSE file for more info - -namespace DragonFruit.Six.Api.Services.Verification -{ - public static class AccountExtensions - { - public static string GetName(this Dragon6User dragon6User) => string.IsNullOrEmpty(dragon6User.Title) ? GetName(dragon6User.AccountRole) : dragon6User.Title; - public static string GetIcon(this Dragon6User dragon6User) => string.IsNullOrEmpty(dragon6User.TitleIcon) ? GetIcon(dragon6User.AccountRole) : dragon6User.TitleIcon; - public static string GetColor(this Dragon6User dragon6User) => string.IsNullOrEmpty(dragon6User.TitleColour) ? GetColor(dragon6User.AccountRole) : dragon6User.TitleColour; - - /// - /// Retrieves the default title for the provided - /// - public static string GetName(this AccountRole role) => role switch - { - AccountRole.BlockedByAdmin => "Account blocked by admin", - AccountRole.BlockedBySelf => "Account blocked by owner", - - AccountRole.Verified => "Verified User", - AccountRole.Beta => "Beta Tester", - AccountRole.Translator => "Dragon6 Translator", - AccountRole.Supporter => "Dragon6 Supporter", - AccountRole.Contributor => "Recognised Contributor", - AccountRole.Developer => "Dragon6 Admin", - - _ => "Standard User" - }; - - /// - /// Retrieves the default icon for the provided - /// - public static string GetIcon(this AccountRole role) => role switch - { - AccountRole.BlockedByAdmin => "highlight_off", - AccountRole.BlockedBySelf => "explore_off", - - AccountRole.Verified => "check", - AccountRole.Beta => "bug_report", - AccountRole.Translator => "translate", - AccountRole.Supporter => "favorite", - AccountRole.Contributor => "code", - AccountRole.Developer => "verified_user", - - _ => "face", - }; - - /// - /// Retrieves the default title color for the provided - /// - public static string GetColor(this AccountRole role) => role switch - { - AccountRole.BlockedByAdmin => "#BD1818", - AccountRole.BlockedBySelf => "#BD1818", - - AccountRole.Verified => "#20B2AA", - AccountRole.Beta => "#FFA500", - AccountRole.Translator => "#1f8b4c", - AccountRole.Supporter => "#FFB6C1", - AccountRole.Contributor => "#3498DB", - AccountRole.Developer => "#90EE90", - - _ => "#FFFFFF", - }; - } -} diff --git a/DragonFruit.Six.Api/Services/Verification/AccountRole.cs b/DragonFruit.Six.Api/Services/Verification/AccountRole.cs deleted file mode 100644 index 3404e476..00000000 --- a/DragonFruit.Six.Api/Services/Verification/AccountRole.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Dragon6 API Copyright DragonFruit Network -// Licensed under Apache-2. Refer to the LICENSE file for more info - -namespace DragonFruit.Six.Api.Services.Verification -{ - public enum AccountRole - { - BlockedByAdmin = -2, - BlockedBySelf = -1, - - Normal = 0, - - Beta = 1, - Translator = 2, - Verified = 3, - Supporter = 4, - Contributor = 5, - Developer = 6 - } -} diff --git a/DragonFruit.Six.Api/Services/Verification/Dragon6User.cs b/DragonFruit.Six.Api/Services/Verification/Dragon6User.cs deleted file mode 100644 index 890b10be..00000000 --- a/DragonFruit.Six.Api/Services/Verification/Dragon6User.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Dragon6 API Copyright DragonFruit Network -// Licensed under Apache-2. Refer to the LICENSE file for more info - -using Newtonsoft.Json; - -namespace DragonFruit.Six.Api.Services.Verification -{ - /// - /// Publicly-exposed data about a user's verified account (on the dragon6 site) - /// - public class Dragon6User - { - [JsonProperty("profile_id")] - public string ProfileId { get; set; } - - [JsonProperty("role")] - public AccountRole AccountRole { get; set; } - - [JsonProperty("cover_img")] - public string Image { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("title_icon")] - public string TitleIcon { get; set; } - - [JsonProperty("title_colour")] - public string TitleColour { get; set; } - } -}