From eebe365719399723d0eec004f8dba189743f2690 Mon Sep 17 00:00:00 2001 From: josefarinna Date: Sat, 10 Oct 2026 12:26:43 +0200 Subject: [PATCH] add smart list sort, type, streaming region and parental filters --- .../Trakt.NET/Enums/TraktSmartListItemType.cs | 20 +++ .../Internal/Modules/TraktSmartListsModule.cs | 11 +- .../Internal/Parameters/TraktFilter.cs | 35 +++++ .../Internal/Requests/SmartListRequests.cs | 12 ++ .../Json/SmartLists/TraktSmartListFilters.cs | 21 +++ .../Modules/TraktSmartListsModule.cs | 36 +++-- src/libs/Trakt.NET/Parameters/TraktFilter.cs | 18 +++ .../libs/JsonData/SmartLists/smartlist.json | 22 +++ .../Enums/TraktSmartListItemTypeTests.cs | 79 +++++++++++ .../Parameters/TraktFilterTests.cs | 126 +++++++++++++++++- .../SmartLists/TraktSmartListTests.cs | 29 ++++ .../GetSmartListItemsTests.cs | 71 +++++++++- .../SmartListItemsGetRequestTests.cs | 54 ++++++-- 13 files changed, 510 insertions(+), 24 deletions(-) create mode 100644 src/libs/Trakt.NET/Enums/TraktSmartListItemType.cs create mode 100644 src/tests/libs/Trakt.NET.Core.Tests/Enums/TraktSmartListItemTypeTests.cs diff --git a/src/libs/Trakt.NET/Enums/TraktSmartListItemType.cs b/src/libs/Trakt.NET/Enums/TraktSmartListItemType.cs new file mode 100644 index 000000000..e7ef37e9d --- /dev/null +++ b/src/libs/Trakt.NET/Enums/TraktSmartListItemType.cs @@ -0,0 +1,20 @@ +namespace TraktNET +{ + /// Determines the item type to narrow a smart list holding both media types. + [TraktEnum(HasPathSupport = true)] + public enum TraktSmartListItemType + { + /// An invalid smart list item type. + Unspecified, + + /// All items (movies and shows). + All, + + /// Movies only. + Movies, + + /// Shows only. + Shows + } +} + diff --git a/src/libs/Trakt.NET/Internal/Modules/TraktSmartListsModule.cs b/src/libs/Trakt.NET/Internal/Modules/TraktSmartListsModule.cs index 6c7ea3930..f53e81518 100644 --- a/src/libs/Trakt.NET/Internal/Modules/TraktSmartListsModule.cs +++ b/src/libs/Trakt.NET/Internal/Modules/TraktSmartListsModule.cs @@ -19,14 +19,19 @@ private Task> GetSmartListImplAsync(string listIdO } private Task> GetSmartListItemsImplAsync( - string listIdOrSlug, TraktFilter? filter = null, string? watchnow = null, + string listIdOrSlug, TraktSmartListItemType? type = null, TraktSortBy? sortBy = null, TraktSortHow? sortHow = null, + TraktFilter? filter = null, string? watchnow = null, string? watchnowCountry = null, TraktExtendedInfo? extendedInfo = null, uint? page = null, uint? limit = null, CancellationToken cancellationToken = default) { var request = new SmartListItemsGetRequest { ListId = listIdOrSlug, + Type = type, + SortBy = sortBy, + SortHow = sortHow, Filter = filter, Watchnow = watchnow, + WatchnowCountry = watchnowCountry, ExtendedInfo = extendedInfo, Page = page, Limit = limit @@ -36,8 +41,12 @@ private Task> GetSmartListItemsImplAsync( => new SmartListItemsGetRequest { ListId = listIdOrSlug, + Type = type, + SortBy = sortBy, + SortHow = sortHow, Filter = filter, Watchnow = watchnow, + WatchnowCountry = watchnowCountry, ExtendedInfo = extendedInfo, Page = page, Limit = limit diff --git a/src/libs/Trakt.NET/Internal/Parameters/TraktFilter.cs b/src/libs/Trakt.NET/Internal/Parameters/TraktFilter.cs index bfd071cd7..f27a80386 100644 --- a/src/libs/Trakt.NET/Internal/Parameters/TraktFilter.cs +++ b/src/libs/Trakt.NET/Internal/Parameters/TraktFilter.cs @@ -31,6 +31,12 @@ public sealed partial class TraktFilter private const string IgnoreWatchlistedName = "ignore_watchlisted"; private const string StartDateName = "start_date"; private const string EndDateName = "end_date"; + private const string ParentalNudityName = "parental_nudity"; + private const string ParentalViolenceName = "parental_violence"; + private const string ParentalProfanityName = "parental_profanity"; + private const string ParentalAlcoholName = "parental_alcohol"; + private const string ParentalFrighteningName = "parental_frightening"; + private const string ParentalIncludeUnratedName = "parental_include_unrated"; public override string ToString() { @@ -162,9 +168,38 @@ public override string ToString() values.Add($"{EndDateName}={EndDate.Value.ToTraktSortDateTimeString()}"); } + AddParentalRange(values, ParentalNudity, ParentalNudityName); + AddParentalRange(values, ParentalViolence, ParentalViolenceName); + AddParentalRange(values, ParentalProfanity, ParentalProfanityName); + AddParentalRange(values, ParentalAlcohol, ParentalAlcoholName); + AddParentalRange(values, ParentalFrightening, ParentalFrighteningName); + + if (ParentalIncludeUnrated.HasValue) + { + values.Add($"{ParentalIncludeUnratedName}={ParentalIncludeUnrated.Value.ToString().ToLower(CultureInfo.CurrentCulture)}"); + } + return string.Join("&", values); } + private static void AddParentalRange(List values, Range? range, string name) + { + if (range.HasValue) + { + uint firstValue = range.Value.From; + uint secondValue = range.Value.To; + + if (firstValue <= secondValue) + { + values.Add($"{name}={firstValue}-{secondValue}"); + } + else + { + values.Add($"{name}={secondValue}-{firstValue}"); + } + } + } + private static void AddRange(List values, Range? range, string name) { if (range.HasValue && range.Value.From > 0 && range.Value.To > 0) diff --git a/src/libs/Trakt.NET/Internal/Requests/SmartListRequests.cs b/src/libs/Trakt.NET/Internal/Requests/SmartListRequests.cs index 87623dc32..a323144af 100644 --- a/src/libs/Trakt.NET/Internal/Requests/SmartListRequests.cs +++ b/src/libs/Trakt.NET/Internal/Requests/SmartListRequests.cs @@ -14,12 +14,24 @@ internal sealed partial class SmartListGetRequest SupportsExtendedInfo = true, SupportsPagination = true, OAuthRequirement = TraktOAuthRequirement.OptionalButMightBeRequired)] internal sealed partial class SmartListItemsGetRequest { + [TraktRequestParameter] + internal TraktSmartListItemType? Type { get; set; } + + [TraktRequestParameter] + internal TraktSortBy? SortBy { get; set; } + + [TraktRequestParameter] + internal TraktSortHow? SortHow { get; set; } + [TraktRequestQuery("filter")] internal TraktFilter? Filter { get; set; } [TraktRequestQuery("watchnow")] internal string? Watchnow { get; set; } + [TraktRequestQuery("watchnow_country")] + internal string? WatchnowCountry { get; set; } + internal override TraktRequestObjectType RequestObjectType => TraktRequestObjectType.List; } } diff --git a/src/libs/Trakt.NET/Json/SmartLists/TraktSmartListFilters.cs b/src/libs/Trakt.NET/Json/SmartLists/TraktSmartListFilters.cs index 67aa41c3c..f92c0beb8 100644 --- a/src/libs/Trakt.NET/Json/SmartLists/TraktSmartListFilters.cs +++ b/src/libs/Trakt.NET/Json/SmartLists/TraktSmartListFilters.cs @@ -42,6 +42,9 @@ public record class TraktSmartListFilters /// Collection of watchnow streaming service options. public string[]? Watchnow { get; set; } + /// 2-character country code for watchnow. + public string? WatchnowCountry { get; set; } + /// Year range [min, max]. Max 2 items. public uint[]? Years { get; set; } @@ -66,6 +69,24 @@ public record class TraktSmartListFilters /// MyAnimeList ratings range [min, max]. Max 2 items. public float[]? MalRatings { get; set; } + /// Parental guide nudity severity range [min, max] from 0 (none) to 3 (severe). Max 2 items. + public uint[]? ParentalNudity { get; set; } + + /// Parental guide violence severity range [min, max] from 0 (none) to 3 (severe). Max 2 items. + public uint[]? ParentalViolence { get; set; } + + /// Parental guide profanity severity range [min, max] from 0 (none) to 3 (severe). Max 2 items. + public uint[]? ParentalProfanity { get; set; } + + /// Parental guide alcohol severity range [min, max] from 0 (none) to 3 (severe). Max 2 items. + public uint[]? ParentalAlcohol { get; set; } + + /// Parental guide frightening severity range [min, max] from 0 (none) to 3 (severe). Max 2 items. + public uint[]? ParentalFrightening { get; set; } + + /// Gets or sets whether to keep titles without a parental guide when a parental range is set. + public bool? ParentalIncludeUnrated { get; set; } + /// Gets or sets whether watched items should be ignored. public bool? IgnoreWatched { get; set; } diff --git a/src/libs/Trakt.NET/Modules/TraktSmartListsModule.cs b/src/libs/Trakt.NET/Modules/TraktSmartListsModule.cs index f4af8e26d..e46713b4c 100644 --- a/src/libs/Trakt.NET/Modules/TraktSmartListsModule.cs +++ b/src/libs/Trakt.NET/Modules/TraktSmartListsModule.cs @@ -119,8 +119,12 @@ public Task> GetSmartListAsync(TraktSmartList list /// Gets items of a smart list. /// The id or slug of the smart list, for which items should be retrieved. + /// Determines, which type of smart list items should be queried. See also . + /// Determines, how to sort the smart list items. See also . + /// Determines, the sort direction for the smart list items. See also . /// Optional filters. See also . /// Optional watchnow streaming service options. + /// Optional 2-letter country code for watchnow streaming services. /// The extended information options. See also . /// The page number of items to retrieve. /// The page limit of items to retrieve. @@ -141,14 +145,19 @@ public Task> GetSmartListAsync(TraktSmartList list /// Thrown, if the request fails. /// Thrown, if validation of request data fails. public Task> GetSmartListItemsAsync( - string listIdOrSlug, TraktFilter? filter = null, string? watchnow = null, + string listIdOrSlug, TraktSmartListItemType? type = null, TraktSortBy? sortBy = null, TraktSortHow? sortHow = null, + TraktFilter? filter = null, string? watchnow = null, string? watchnowCountry = null, TraktExtendedInfo? extendedInfo = null, uint? page = null, uint? limit = null, CancellationToken cancellationToken = default) - => GetSmartListItemsImplAsync(listIdOrSlug, filter, watchnow, extendedInfo, page, limit, cancellationToken); + => GetSmartListItemsImplAsync(listIdOrSlug, type, sortBy, sortHow, filter, watchnow, watchnowCountry, extendedInfo, page, limit, cancellationToken); /// Gets items of a smart list. /// The Trakt-ID of the smart list, for which items should be retrieved. + /// Determines, which type of smart list items should be queried. See also . + /// Determines, how to sort the smart list items. See also . + /// Determines, the sort direction for the smart list items. See also . /// Optional filters. See also . /// Optional watchnow streaming service options. + /// Optional 2-letter country code for watchnow streaming services. /// The extended information options. See also . /// The page number of items to retrieve. /// The page limit of items to retrieve. @@ -170,19 +179,24 @@ public Task> GetSmartListItemsAsync( /// Thrown, if validation of request data fails. /// Thrown, if the given is 0. public Task> GetSmartListItemsAsync( - uint traktListId, TraktFilter? filter = null, string? watchnow = null, + uint traktListId, TraktSmartListItemType? type = null, TraktSortBy? sortBy = null, TraktSortHow? sortHow = null, + TraktFilter? filter = null, string? watchnow = null, string? watchnowCountry = null, TraktExtendedInfo? extendedInfo = null, uint? page = null, uint? limit = null, CancellationToken cancellationToken = default) { if (traktListId == 0) throw new ArgumentException("list id must not be 0", nameof(traktListId)); - return GetSmartListItemsAsync(traktListId.ToInvariantCultureString(), filter, watchnow, extendedInfo, page, limit, cancellationToken); + return GetSmartListItemsAsync(traktListId.ToInvariantCultureString(), type, sortBy, sortHow, filter, watchnow, watchnowCountry, extendedInfo, page, limit, cancellationToken); } /// Gets items of a smart list. /// The ids of the smart list, for which items should be retrieved. + /// Determines, which type of smart list items should be queried. See also . + /// Determines, how to sort the smart list items. See also . + /// Determines, the sort direction for the smart list items. See also . /// Optional filters. See also . /// Optional watchnow streaming service options. + /// Optional 2-letter country code for watchnow streaming services. /// The extended information options. See also . /// The page number of items to retrieve. /// The page limit of items to retrieve. @@ -205,7 +219,8 @@ public Task> GetSmartListItemsAsync( /// Thrown, if the given is null. /// Thrown, if the given has not any ids set. public Task> GetSmartListItemsAsync( - TraktListIDs listIds, TraktFilter? filter = null, string? watchnow = null, + TraktListIDs listIds, TraktSmartListItemType? type = null, TraktSortBy? sortBy = null, TraktSortHow? sortHow = null, + TraktFilter? filter = null, string? watchnow = null, string? watchnowCountry = null, TraktExtendedInfo? extendedInfo = null, uint? page = null, uint? limit = null, CancellationToken cancellationToken = default) { ArgumentValidator.ThrowIfNull(listIds); @@ -213,13 +228,17 @@ public Task> GetSmartListItemsAsync( if (!listIds.HasAnyID) throw new ArgumentException($"{nameof(listIds)} has not any ids set", nameof(listIds)); - return GetSmartListItemsAsync(listIds.BestID, filter, watchnow, extendedInfo, page, limit, cancellationToken); + return GetSmartListItemsAsync(listIds.BestID, type, sortBy, sortHow, filter, watchnow, watchnowCountry, extendedInfo, page, limit, cancellationToken); } /// Gets items of a smart list. /// The smart list, for which items should be retrieved. + /// Determines, which type of smart list items should be queried. See also . + /// Determines, how to sort the smart list items. See also . + /// Determines, the sort direction for the smart list items. See also . /// Optional filters. See also . /// Optional watchnow streaming service options. + /// Optional 2-letter country code for watchnow streaming services. /// The extended information options. See also . /// The page number of items to retrieve. /// The page limit of items to retrieve. @@ -241,11 +260,12 @@ public Task> GetSmartListItemsAsync( /// Thrown, if validation of request data fails. /// Thrown, if the given is null. public Task> GetSmartListItemsAsync( - TraktSmartList list, TraktFilter? filter = null, string? watchnow = null, + TraktSmartList list, TraktSmartListItemType? type = null, TraktSortBy? sortBy = null, TraktSortHow? sortHow = null, + TraktFilter? filter = null, string? watchnow = null, string? watchnowCountry = null, TraktExtendedInfo? extendedInfo = null, uint? page = null, uint? limit = null, CancellationToken cancellationToken = default) { ArgumentValidator.ThrowIfNull(list); - return GetSmartListItemsAsync(list.IDs!, filter, watchnow, extendedInfo, page, limit, cancellationToken); + return GetSmartListItemsAsync(list.IDs!, type, sortBy, sortHow, filter, watchnow, watchnowCountry, extendedInfo, page, limit, cancellationToken); } } } diff --git a/src/libs/Trakt.NET/Parameters/TraktFilter.cs b/src/libs/Trakt.NET/Parameters/TraktFilter.cs index 9140fa9cf..39927e3cb 100644 --- a/src/libs/Trakt.NET/Parameters/TraktFilter.cs +++ b/src/libs/Trakt.NET/Parameters/TraktFilter.cs @@ -86,5 +86,23 @@ public sealed partial class TraktFilter /// Gets or sets the end date constraint. public DateTime? EndDate { get; set; } + + /// Parental guide nudity severity range between 0 (none) and 3 (severe). + public Range? ParentalNudity { get; set; } + + /// Parental guide violence severity range between 0 (none) and 3 (severe). + public Range? ParentalViolence { get; set; } + + /// Parental guide profanity severity range between 0 (none) and 3 (severe). + public Range? ParentalProfanity { get; set; } + + /// Parental guide alcohol severity range between 0 (none) and 3 (severe). + public Range? ParentalAlcohol { get; set; } + + /// Parental guide frightening severity range between 0 (none) and 3 (severe). + public Range? ParentalFrightening { get; set; } + + /// Gets or sets whether to keep titles without a parental guide when a parental range is set. + public bool? ParentalIncludeUnrated { get; set; } } } diff --git a/src/tests/libs/JsonData/SmartLists/smartlist.json b/src/tests/libs/JsonData/SmartLists/smartlist.json index c8004b644..eb3e8e90d 100644 --- a/src/tests/libs/JsonData/SmartLists/smartlist.json +++ b/src/tests/libs/JsonData/SmartLists/smartlist.json @@ -45,6 +45,7 @@ "watchnow": [ "netflix" ], + "watchnow_country": "us", "years": [ 2010, 2020 @@ -77,6 +78,27 @@ 7.0, 9.5 ], + "parental_nudity": [ + 0, + 1 + ], + "parental_violence": [ + 0, + 2 + ], + "parental_profanity": [ + 1, + 3 + ], + "parental_alcohol": [ + 0, + 0 + ], + "parental_frightening": [ + 2, + 3 + ], + "parental_include_unrated": true, "ignore_watched": true, "ignore_watchlisted": false, "ignore_watching": true, diff --git a/src/tests/libs/Trakt.NET.Core.Tests/Enums/TraktSmartListItemTypeTests.cs b/src/tests/libs/Trakt.NET.Core.Tests/Enums/TraktSmartListItemTypeTests.cs new file mode 100644 index 000000000..ca9690bf3 --- /dev/null +++ b/src/tests/libs/Trakt.NET.Core.Tests/Enums/TraktSmartListItemTypeTests.cs @@ -0,0 +1,79 @@ +using System.Text.Json; + +namespace TraktNET.Enums +{ + public sealed class TraktSmartListItemTypeTests + { + [Fact] + public void TestTraktSmartListItemTypeToJson() + { + TraktSmartListItemType.Unspecified.ToJson().ShouldBeNull(); + TraktSmartListItemType.All.ToJson().ShouldBe("all"); + TraktSmartListItemType.Movies.ToJson().ShouldBe("movies"); + TraktSmartListItemType.Shows.ToJson().ShouldBe("shows"); + ((TraktSmartListItemType)99).ToJson().ShouldBeNull(); + } + + [Fact] + public void TestTraktSmartListItemTypeFromJson() + { + "unspecified".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Unspecified); + "all".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.All); + "movies".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Movies); + "shows".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Shows); + + string? nullValue = null; + nullValue.ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Unspecified); + "invalid".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Unspecified); + "".ToTraktSmartListItemType().ShouldBe(TraktSmartListItemType.Unspecified); + } + + [Fact] + public void TestTraktSmartListItemTypeToURI() + { + TraktSmartListItemType.Unspecified.ToURI().ShouldBe(string.Empty); + TraktSmartListItemType.All.ToURI().ShouldBe("all"); + TraktSmartListItemType.Movies.ToURI().ShouldBe("movies"); + TraktSmartListItemType.Shows.ToURI().ShouldBe("shows"); + ((TraktSmartListItemType)99).ToURI().ShouldBe(string.Empty); + } + + [Fact] + public void TestTraktSmartListItemTypeAsPathParameter() + { + TraktSmartListItemType.Unspecified.AsPathParameter().ShouldBe(string.Empty); + TraktSmartListItemType.All.AsPathParameter().ShouldBe("all"); + TraktSmartListItemType.Movies.AsPathParameter().ShouldBe("movies"); + TraktSmartListItemType.Shows.AsPathParameter().ShouldBe("shows"); + ((TraktSmartListItemType)99).AsPathParameter().ShouldBe(string.Empty); + } + + [Fact] + public void TestTraktSmartListItemTypeDisplayName() + { + TraktSmartListItemType.Unspecified.DisplayName().ShouldBe("Unspecified"); + TraktSmartListItemType.All.DisplayName().ShouldBe("All"); + TraktSmartListItemType.Movies.DisplayName().ShouldBe("Movies"); + TraktSmartListItemType.Shows.DisplayName().ShouldBe("Shows"); + ((TraktSmartListItemType)99).DisplayName().ShouldBe("99"); + } + + [Fact] + public void TestTraktSmartListItemTypeJsonConverter() + { + var converter = new TraktSmartListItemTypeJsonConverter(); + converter.CanConvert(typeof(TraktSmartListItemType)).ShouldBeTrue(); + converter.CanConvert(typeof(int)).ShouldBeFalse(); + + var options = new JsonSerializerOptions + { + Converters = { converter } + }; + + JsonSerializer.Serialize(TraktSmartListItemType.Movies, options).ShouldBe("\"movies\""); + JsonSerializer.Deserialize("\"movies\"", options).ShouldBe(TraktSmartListItemType.Movies); + JsonSerializer.Deserialize("\"\"", options).ShouldBe(TraktSmartListItemType.Unspecified); + } + } +} + diff --git a/src/tests/libs/Trakt.NET.Core.Tests/Parameters/TraktFilterTests.cs b/src/tests/libs/Trakt.NET.Core.Tests/Parameters/TraktFilterTests.cs index 239f93e3c..230f2b0f5 100644 --- a/src/tests/libs/Trakt.NET.Core.Tests/Parameters/TraktFilterTests.cs +++ b/src/tests/libs/Trakt.NET.Core.Tests/Parameters/TraktFilterTests.cs @@ -34,6 +34,12 @@ public void TestTraktFilterConstructor() filter.IgnoreWatchlisted.ShouldBeNull(); filter.StartDate.ShouldBeNull(); filter.EndDate.ShouldBeNull(); + filter.ParentalNudity.ShouldBeNull(); + filter.ParentalViolence.ShouldBeNull(); + filter.ParentalProfanity.ShouldBeNull(); + filter.ParentalAlcohol.ShouldBeNull(); + filter.ParentalFrightening.ShouldBeNull(); + filter.ParentalIncludeUnrated.ShouldBeNull(); } [Fact] @@ -506,6 +512,114 @@ public void TestTraktFilterToStringDates() filter.ToString().ShouldBe("start_date=2024-01-01&end_date=2024-12-31"); } + [Fact] + public void TestTraktFilterToStringParentalNudity() + { + var filter = new TraktFilter + { + ParentalNudity = new Range(0, 1) + }; + + filter.ToString().ShouldBe("parental_nudity=0-1"); + + filter = new TraktFilter + { + ParentalNudity = new Range(1, 0) + }; + + filter.ToString().ShouldBe("parental_nudity=0-1"); + } + + [Fact] + public void TestTraktFilterToStringParentalViolence() + { + var filter = new TraktFilter + { + ParentalViolence = new Range(1, 2) + }; + + filter.ToString().ShouldBe("parental_violence=1-2"); + + filter = new TraktFilter + { + ParentalViolence = new Range(2, 1) + }; + + filter.ToString().ShouldBe("parental_violence=1-2"); + } + + [Fact] + public void TestTraktFilterToStringParentalProfanity() + { + var filter = new TraktFilter + { + ParentalProfanity = new Range(0, 3) + }; + + filter.ToString().ShouldBe("parental_profanity=0-3"); + + filter = new TraktFilter + { + ParentalProfanity = new Range(3, 0) + }; + + filter.ToString().ShouldBe("parental_profanity=0-3"); + } + + [Fact] + public void TestTraktFilterToStringParentalAlcohol() + { + var filter = new TraktFilter + { + ParentalAlcohol = new Range(2, 3) + }; + + filter.ToString().ShouldBe("parental_alcohol=2-3"); + + filter = new TraktFilter + { + ParentalAlcohol = new Range(3, 2) + }; + + filter.ToString().ShouldBe("parental_alcohol=2-3"); + } + + [Fact] + public void TestTraktFilterToStringParentalFrightening() + { + var filter = new TraktFilter + { + ParentalFrightening = new Range(1, 3) + }; + + filter.ToString().ShouldBe("parental_frightening=1-3"); + + filter = new TraktFilter + { + ParentalFrightening = new Range(3, 1) + }; + + filter.ToString().ShouldBe("parental_frightening=1-3"); + } + + [Fact] + public void TestTraktFilterToStringParentalIncludeUnrated() + { + var filter = new TraktFilter + { + ParentalIncludeUnrated = true + }; + + filter.ToString().ShouldBe("parental_include_unrated=true"); + + filter = new TraktFilter + { + ParentalIncludeUnrated = false + }; + + filter.ToString().ShouldBe("parental_include_unrated=false"); + } + [Fact] public void TestTraktFilterToStringAllValues() { @@ -536,7 +650,13 @@ public void TestTraktFilterToStringAllValues() IgnoreCollected = true, IgnoreWatchlisted = true, StartDate = new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc), - EndDate = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc) + EndDate = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc), + ParentalNudity = new Range(0, 1), + ParentalViolence = new Range(1, 2), + ParentalProfanity = new Range(0, 3), + ParentalAlcohol = new Range(2, 3), + ParentalFrightening = new Range(1, 3), + ParentalIncludeUnrated = true }; filter.ToString().ShouldBe("query=testquery&years=2020-2024&genres=action,drama&languages=en,de" @@ -544,7 +664,9 @@ public void TestTraktFilterToStringAllValues() + "&tmdb_votes=2000-5000&imdb_ratings=5.5-10&imdb_votes=2000-5000&rt_meters=70-90&rt_user_meters=70-90" + "&metascores=5.5-10&certifications=R,tv-pg&network_ids=7,8,9&status=ended,planned" + "&episode_types=series_premiere,season_premiere&hide=unwatched&ignore_watched=true&ignore_collected=true" - + "&ignore_watchlisted=true&start_date=2024-01-01&end_date=2024-12-31"); + + "&ignore_watchlisted=true&start_date=2024-01-01&end_date=2024-12-31" + + "&parental_nudity=0-1&parental_violence=1-2&parental_profanity=0-3&parental_alcohol=2-3&parental_frightening=1-3" + + "&parental_include_unrated=true"); } } } diff --git a/src/tests/libs/Trakt.NET.Json.Tests/SmartLists/TraktSmartListTests.cs b/src/tests/libs/Trakt.NET.Json.Tests/SmartLists/TraktSmartListTests.cs index 13550f3e8..c3fb41cf7 100644 --- a/src/tests/libs/Trakt.NET.Json.Tests/SmartLists/TraktSmartListTests.cs +++ b/src/tests/libs/Trakt.NET.Json.Tests/SmartLists/TraktSmartListTests.cs @@ -84,6 +84,8 @@ public async Task TestTraktSmartListFromJson() smartList.Filters.Watchnow.Length.ShouldBe(1); smartList.Filters.Watchnow[0].ShouldBe("netflix"); + smartList.Filters.WatchnowCountry.ShouldBe("us"); + smartList.Filters.Years.ShouldNotBeNull(); smartList.Filters.Years.Length.ShouldBe(2); smartList.Filters.Years[0].ShouldBe(2010U); @@ -124,6 +126,33 @@ public async Task TestTraktSmartListFromJson() smartList.Filters.MalRatings[0].ShouldBe(7.0f); smartList.Filters.MalRatings[1].ShouldBe(9.5f); + smartList.Filters.ParentalNudity.ShouldNotBeNull(); + smartList.Filters.ParentalNudity.Length.ShouldBe(2); + smartList.Filters.ParentalNudity[0].ShouldBe(0U); + smartList.Filters.ParentalNudity[1].ShouldBe(1U); + + smartList.Filters.ParentalViolence.ShouldNotBeNull(); + smartList.Filters.ParentalViolence.Length.ShouldBe(2); + smartList.Filters.ParentalViolence[0].ShouldBe(0U); + smartList.Filters.ParentalViolence[1].ShouldBe(2U); + + smartList.Filters.ParentalProfanity.ShouldNotBeNull(); + smartList.Filters.ParentalProfanity.Length.ShouldBe(2); + smartList.Filters.ParentalProfanity[0].ShouldBe(1U); + smartList.Filters.ParentalProfanity[1].ShouldBe(3U); + + smartList.Filters.ParentalAlcohol.ShouldNotBeNull(); + smartList.Filters.ParentalAlcohol.Length.ShouldBe(2); + smartList.Filters.ParentalAlcohol[0].ShouldBe(0U); + smartList.Filters.ParentalAlcohol[1].ShouldBe(0U); + + smartList.Filters.ParentalFrightening.ShouldNotBeNull(); + smartList.Filters.ParentalFrightening.Length.ShouldBe(2); + smartList.Filters.ParentalFrightening[0].ShouldBe(2U); + smartList.Filters.ParentalFrightening[1].ShouldBe(3U); + + smartList.Filters.ParentalIncludeUnrated.ShouldBe(true); + smartList.Filters.IgnoreWatched.ShouldBe(true); smartList.Filters.IgnoreWatchlisted.ShouldBe(false); smartList.Filters.IgnoreWatching.ShouldBe(true); diff --git a/src/tests/libs/Trakt.NET.Modules.Tests/SmartListsModule/GetSmartListItemsTests.cs b/src/tests/libs/Trakt.NET.Modules.Tests/SmartListsModule/GetSmartListItemsTests.cs index 563973233..3802190e0 100644 --- a/src/tests/libs/Trakt.NET.Modules.Tests/SmartListsModule/GetSmartListItemsTests.cs +++ b/src/tests/libs/Trakt.NET.Modules.Tests/SmartListsModule/GetSmartListItemsTests.cs @@ -45,7 +45,7 @@ public async Task TestGetSmartListItemsWithFilterAndQuery() TraktClient client = ModuleTestUtility.GetClient(expectedUri, responseContent); TraktPagedResponse response = await client.SmartLists.GetSmartListItemsAsync( - ListSlug, filter, watchnow: "netflix", cancellationToken: TestContext.Current.CancellationToken); + ListSlug, filter: filter, watchnow: "netflix", cancellationToken: TestContext.Current.CancellationToken); response.ShouldNotBeNull(); response.IsSuccess.ShouldBeTrue(); @@ -239,6 +239,67 @@ public async Task TestGetSmartListItemsWithExtendedInfoPageAndLimit() response.PageCount.ShouldBe(1U); } + [Fact] + public async Task TestGetSmartListItemsWithTypeSortByAndSortHow() + { + string responseContent = await TestUtility.GetJsonFileContentAsync("Lists\\listitems.json"); + + string expectedUri = $"smart-lists/{ListSlug}/items/movies/rank/desc"; + + TraktClient client = ModuleTestUtility.GetClient(expectedUri, responseContent); + + TraktPagedResponse response = await client.SmartLists.GetSmartListItemsAsync( + ListSlug, TraktSmartListItemType.Movies, TraktSortBy.Rank, TraktSortHow.Descending, + cancellationToken: TestContext.Current.CancellationToken); + + response.ShouldNotBeNull(); + response.IsSuccess.ShouldBeTrue(); + response.HasValue.ShouldBeTrue(); + response.Content.ShouldNotBeNull(); + } + + [Fact] + public async Task TestGetSmartListItemsWithWatchnowCountry() + { + string responseContent = await TestUtility.GetJsonFileContentAsync("Lists\\listitems.json"); + + string expectedUri = $"{GetSmartListItemsUri}?watchnow=netflix&watchnow_country=us"; + + TraktClient client = ModuleTestUtility.GetClient(expectedUri, responseContent); + + TraktPagedResponse response = await client.SmartLists.GetSmartListItemsAsync( + ListSlug, watchnow: "netflix", watchnowCountry: "us", cancellationToken: TestContext.Current.CancellationToken); + + response.ShouldNotBeNull(); + response.IsSuccess.ShouldBeTrue(); + response.HasValue.ShouldBeTrue(); + response.Content.ShouldNotBeNull(); + } + + [Fact] + public async Task TestGetSmartListItemsWithParentalFilter() + { + string responseContent = await TestUtility.GetJsonFileContentAsync("Lists\\listitems.json"); + + var filter = new TraktFilter + { + ParentalNudity = new Range(0, 1), + ParentalIncludeUnrated = true + }; + + string expectedUri = $"{GetSmartListItemsUri}?parental_nudity=0-1&parental_include_unrated=true"; + + TraktClient client = ModuleTestUtility.GetClient(expectedUri, responseContent); + + TraktPagedResponse response = await client.SmartLists.GetSmartListItemsAsync( + ListSlug, filter: filter, cancellationToken: TestContext.Current.CancellationToken); + + response.ShouldNotBeNull(); + response.IsSuccess.ShouldBeTrue(); + response.HasValue.ShouldBeTrue(); + response.Content.ShouldNotBeNull(); + } + [Fact] public async Task TestGetSmartListItemsComplete() { @@ -247,15 +308,17 @@ public async Task TestGetSmartListItemsComplete() var filter = new TraktFilter { Genres = ["science-fiction"], - IgnoreWatched = true + IgnoreWatched = true, + ParentalViolence = new Range(1, 2) }; - string expectedUri = $"{GetSmartListItemsUri}?genres=science-fiction&ignore_watched=true&watchnow=netflix&extended={ExtendedInfo.ToURI()}&page={Page}&limit={Limit}"; + string expectedUri = $"smart-lists/{ListSlug}/items/movies/rank/desc?genres=science-fiction&ignore_watched=true&parental_violence=1-2&watchnow=netflix&watchnow_country=us&extended={ExtendedInfo.ToURI()}&page={Page}&limit={Limit}"; TraktClient client = ModuleTestUtility.GetClient(expectedUri, responseContent, Page, 1, Limit, ListItemCount); TraktPagedResponse response = await client.SmartLists.GetSmartListItemsAsync( - ListSlug, filter, watchnow: "netflix", extendedInfo: ExtendedInfo, page: Page, limit: Limit, cancellationToken: TestContext.Current.CancellationToken); + ListSlug, TraktSmartListItemType.Movies, TraktSortBy.Rank, TraktSortHow.Descending, + filter, "netflix", "us", ExtendedInfo, Page, Limit, cancellationToken: TestContext.Current.CancellationToken); response.ShouldNotBeNull(); response.IsSuccess.ShouldBeTrue(); diff --git a/src/tests/libs/Trakt.NET.Requests.Tests/Get/SmartLists/SmartListItemsGetRequestTests.cs b/src/tests/libs/Trakt.NET.Requests.Tests/Get/SmartLists/SmartListItemsGetRequestTests.cs index 4ae0dab60..ed4ecabbf 100644 --- a/src/tests/libs/Trakt.NET.Requests.Tests/Get/SmartLists/SmartListItemsGetRequestTests.cs +++ b/src/tests/libs/Trakt.NET.Requests.Tests/Get/SmartLists/SmartListItemsGetRequestTests.cs @@ -9,21 +9,39 @@ public sealed class SmartListItemsGetRequestTests private const string URIPath = "smart-lists/123/items"; [Theory] - [InlineData(null, null, null, null, $"{URIPath}")] - [InlineData("us", null, null, null, $"{URIPath}?watchnow=us")] - [InlineData(null, TraktExtendedInfo.None, null, null, $"{URIPath}")] - [InlineData(null, TraktExtendedInfo.Full, null, null, $"{URIPath}?extended=full")] - [InlineData(null, null, 10, null, $"{URIPath}?page=10")] - [InlineData(null, null, null, 20, $"{URIPath}?limit=20")] - [InlineData(null, null, 10, 20, $"{URIPath}?page=10&limit=20")] - [InlineData("us", TraktExtendedInfo.Full, 10, 20, $"{URIPath}?watchnow=us&extended=full&page=10&limit=20")] - public void TestSmartListItemsGetRequestHasValidURIPath(string? watchnow, + [InlineData(null, null, null, null, null, null, null, null, URIPath)] + [InlineData(null, null, null, "us", null, null, null, null, $"{URIPath}?watchnow=us")] + [InlineData(null, null, null, null, "us", null, null, null, $"{URIPath}?watchnow_country=us")] + [InlineData(null, null, null, "us", "us", null, null, null, $"{URIPath}?watchnow=us&watchnow_country=us")] + [InlineData(null, null, null, null, null, TraktExtendedInfo.None, null, null, URIPath)] + [InlineData(null, null, null, null, null, TraktExtendedInfo.Full, null, null, $"{URIPath}?extended=full")] + [InlineData(null, null, null, null, null, null, 10, null, $"{URIPath}?page=10")] + [InlineData(null, null, null, null, null, null, null, 20, $"{URIPath}?limit=20")] + [InlineData(null, null, null, null, null, null, 10, 20, $"{URIPath}?page=10&limit=20")] + [InlineData(null, null, null, "us", "us", TraktExtendedInfo.Full, 10, 20, $"{URIPath}?watchnow=us&watchnow_country=us&extended=full&page=10&limit=20")] + [InlineData(null, null, TraktSortHow.Ascending, null, null, null, null, null, $"{URIPath}/asc")] + [InlineData(null, null, TraktSortHow.Descending, null, null, null, null, null, $"{URIPath}/desc")] + [InlineData(null, TraktSortBy.Rank, null, null, null, null, null, null, $"{URIPath}/rank")] + [InlineData(null, TraktSortBy.Rank, TraktSortHow.Ascending, null, null, null, null, null, $"{URIPath}/rank/asc")] + [InlineData(null, TraktSortBy.Rank, TraktSortHow.Descending, null, null, null, null, null, $"{URIPath}/rank/desc")] + [InlineData(TraktSmartListItemType.All, null, null, null, null, null, null, null, $"{URIPath}/all")] + [InlineData(TraktSmartListItemType.Movies, null, null, null, null, null, null, null, $"{URIPath}/movies")] + [InlineData(TraktSmartListItemType.Shows, null, null, null, null, null, null, null, $"{URIPath}/shows")] + [InlineData(TraktSmartListItemType.Movies, TraktSortBy.Rank, null, null, null, null, null, null, $"{URIPath}/movies/rank")] + [InlineData(TraktSmartListItemType.Movies, TraktSortBy.Rank, TraktSortHow.Descending, null, null, null, null, null, $"{URIPath}/movies/rank/desc")] + [InlineData(TraktSmartListItemType.Movies, TraktSortBy.Rank, TraktSortHow.Descending, "us", "us", TraktExtendedInfo.Full, 10, 20, $"{URIPath}/movies/rank/desc?watchnow=us&watchnow_country=us&extended=full&page=10&limit=20")] + public void TestSmartListItemsGetRequestHasValidURIPath(TraktSmartListItemType? type, TraktSortBy? sortBy, + TraktSortHow? sortHow, string? watchnow, string? watchnowCountry, TraktExtendedInfo? extendedInfo, int? page, int? limit, string expectedURIPath) { var request = new SmartListItemsGetRequest { ListId = "123", + Type = type, + SortBy = sortBy, + SortHow = sortHow, Watchnow = watchnow, + WatchnowCountry = watchnowCountry, ExtendedInfo = extendedInfo, Page = (uint?)page, Limit = (uint?)limit @@ -47,6 +65,24 @@ public void TestSmartListItemsGetRequestHasValidURIPathWithFilter() request.RequestUri.ShouldBe(new Uri($"{URIPath}?query=game of thrones", UriKind.Relative)); } + [Fact] + public void TestSmartListItemsGetRequestHasValidURIPathWithParentalFilter() + { + var filter = new TraktFilter + { + ParentalNudity = new Range(0, 1), + ParentalIncludeUnrated = true + }; + var request = new SmartListItemsGetRequest + { + ListId = "123", + Filter = filter + }; + + request.BuildUri(); + request.RequestUri.ShouldBe(new Uri($"{URIPath}?parental_nudity=0-1&parental_include_unrated=true", UriKind.Relative)); + } + [Fact] public void TestSmartListItemsGetRequestHasValidOAuthRequirement() {