From 554e15d438512e50e7fc22bd68d45d7b4610fb7f Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sat, 22 Feb 2025 15:20:39 +0200 Subject: [PATCH] New: Watch list sorting and rate limit for Trakt Import Lists --- .../ImportLists/Trakt/User/TraktUserImport.cs | 5 +-- .../Trakt/User/TraktUserListType.cs | 8 ++-- .../Trakt/User/TraktUserRequestGenerator.cs | 37 +++++++++++++------ .../Trakt/User/TraktUserSettings.cs | 16 +++++++- src/NzbDrone.Core/Localization/Core/en.json | 6 +++ .../Notifications/Trakt/TraktProxy.cs | 25 +++++++++---- 6 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs index 6f8198cd7..dd100f331 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserImport.cs @@ -25,10 +25,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User public override IImportListRequestGenerator GetRequestGenerator() { - return new TraktUserRequestGenerator(_traktProxy) - { - Settings = Settings - }; + return new TraktUserRequestGenerator(_traktProxy, Settings); } } } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserListType.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserListType.cs index b90508d14..a42eaf01a 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserListType.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserListType.cs @@ -1,14 +1,14 @@ -using System.Runtime.Serialization; +using NzbDrone.Core.Annotations; namespace NzbDrone.Core.ImportLists.Trakt.User { public enum TraktUserListType { - [EnumMember(Value = "User Watch List")] + [FieldOption(Label = "ImportListsTraktSettingsUserListTypeWatch")] UserWatchList = 0, - [EnumMember(Value = "User Watched List")] + [FieldOption(Label = "ImportListsTraktSettingsUserListTypeWatched")] UserWatchedList = 1, - [EnumMember(Value = "User Collection List")] + [FieldOption(Label = "ImportListsTraktSettingsUserListTypeCollection")] UserCollectionList = 2 } } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs index f68ef6813..eb545870c 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserRequestGenerator.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -using System.Net.Http; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Notifications.Trakt; namespace NzbDrone.Core.ImportLists.Trakt.User @@ -8,11 +8,12 @@ namespace NzbDrone.Core.ImportLists.Trakt.User public class TraktUserRequestGenerator : IImportListRequestGenerator { private readonly ITraktProxy _traktProxy; - public TraktUserSettings Settings { get; set; } + private readonly TraktUserSettings _settings; - public TraktUserRequestGenerator(ITraktProxy traktProxy) + public TraktUserRequestGenerator(ITraktProxy traktProxy, TraktUserSettings settings) { _traktProxy = traktProxy; + _settings = settings; } public virtual ImportListPageableRequestChain GetMovies() @@ -26,25 +27,39 @@ namespace NzbDrone.Core.ImportLists.Trakt.User private IEnumerable GetMoviesRequest() { - var link = string.Empty; - var userName = Settings.Username.IsNotNullOrWhiteSpace() ? Settings.Username.Trim() : Settings.AuthUser.Trim(); + var requestBuilder = new HttpRequestBuilder(_settings.Link.Trim()); - switch (Settings.TraktListType) + switch (_settings.TraktListType) { case (int)TraktUserListType.UserWatchList: - link += $"users/{userName}/watchlist/movies?limit={Settings.Limit}"; + var watchSorting = _settings.TraktWatchSorting switch + { + (int)TraktUserWatchSorting.Added => "added", + (int)TraktUserWatchSorting.Title => "title", + (int)TraktUserWatchSorting.Released => "released", + _ => "rank" + }; + + requestBuilder + .Resource("/users/{userName}/watchlist/movies/{sorting}") + .SetSegment("sorting", watchSorting); break; case (int)TraktUserListType.UserWatchedList: - link += $"users/{userName}/watched/movies?limit={Settings.Limit}"; + requestBuilder.Resource("/users/{userName}/watched/movies"); break; case (int)TraktUserListType.UserCollectionList: - link += $"users/{userName}/collection/movies?limit={Settings.Limit}"; + requestBuilder.Resource("/users/{userName}/collection/movies"); break; } - var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken)); + var userName = _settings.Username.IsNotNullOrWhiteSpace() ? _settings.Username.Trim() : _settings.AuthUser.Trim(); + + requestBuilder + .SetSegment("userName", userName) + .WithRateLimit(4) + .AddQueryParam("limit", _settings.Limit.ToString()); - yield return request; + yield return new ImportListRequest(_traktProxy.BuildRequest(requestBuilder.Build(), _settings.AccessToken)); } } } diff --git a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs index c5575b602..6b7869266 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/User/TraktUserSettings.cs @@ -20,12 +20,16 @@ namespace NzbDrone.Core.ImportLists.Trakt.User public TraktUserSettings() { TraktListType = (int)TraktUserListType.UserWatchList; + TraktWatchSorting = (int)TraktUserWatchSorting.Rank; } - [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "Type of list you're seeking to import from")] + [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "ImportListsTraktSettingsListTypeHelpText")] public int TraktListType { get; set; } - [FieldDefinition(2, Label = "Username", HelpText = "Username for the List to import from (empty to use Auth User)")] + [FieldDefinition(2, Label = "ImportListsTraktSettingsWatchListSorting", Type = FieldType.Select, SelectOptions = typeof(TraktUserWatchSorting), HelpText = "ImportListsTraktSettingsWatchListSortingHelpText")] + public int TraktWatchSorting { get; set; } + + [FieldDefinition(3, Label = "Username", HelpText = "ImportListsTraktSettingsUserListUsernameHelpText")] public string Username { get; set; } public override NzbDroneValidationResult Validate() @@ -33,4 +37,12 @@ namespace NzbDrone.Core.ImportLists.Trakt.User return new NzbDroneValidationResult(Validator.Validate(this)); } } + + public enum TraktUserWatchSorting + { + Rank = 0, + Added = 1, + Title = 2, + Released = 3 + } } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 2f6c3c290..feaa288e9 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -792,6 +792,12 @@ "ImportListsTraktSettingsPopularListTypeTrendingMovies": "Trending Movies", "ImportListsTraktSettingsRating": "Rating", "ImportListsTraktSettingsRatingMovieHelpText": "Filter movies by rating range (0-100)", + "ImportListsTraktSettingsUserListTypeCollection": "User Collection List", + "ImportListsTraktSettingsUserListTypeWatch": "User Watch List", + "ImportListsTraktSettingsUserListTypeWatched": "User Watched List", + "ImportListsTraktSettingsUserListUsernameHelpText": "Username for the List to import from (leave empty to use Auth User)", + "ImportListsTraktSettingsWatchListSorting": "Watch List Sorting", + "ImportListsTraktSettingsWatchListSortingHelpText": "If List Type is Watch, select the order to sort the list", "ImportListsTraktSettingsYears": "Years", "ImportListsTraktSettingsYearsMovieHelpText": "Filter movies by year or year range", "ImportMechanismHealthCheckMessage": "Enable Completed Download Handling", diff --git a/src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs b/src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs index c45ff1607..2e2c0b30f 100644 --- a/src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs +++ b/src/NzbDrone.Core/Notifications/Trakt/TraktProxy.cs @@ -1,6 +1,5 @@ using System; using System.Net.Http; -using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -16,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Trakt void AddToCollection(TraktCollectMoviesResource payload, string accessToken); void RemoveFromCollection(TraktCollectMoviesResource payload, string accessToken); HttpRequest BuildRequest(string resource, HttpMethod method, string accessToken); + HttpRequest BuildRequest(HttpRequest request, string accessToken); } public class TraktProxy : ITraktProxy @@ -27,12 +27,12 @@ namespace NzbDrone.Core.Notifications.Trakt private const string ClientId = "64508a8bf370cee550dde4806469922fd7cd70afb2d5690e3ee7f75ae784b70e"; private readonly IHttpClient _httpClient; - private readonly Logger _logger; - public TraktProxy(IHttpClient httpClient, Logger logger) + private static TimeSpan DefaultRateLimit => TimeSpan.FromSeconds(2); + + public TraktProxy(IHttpClient httpClient) { _httpClient = httpClient; - _logger = logger; } public void AddToCollection(TraktCollectMoviesResource payload, string accessToken) @@ -87,16 +87,27 @@ namespace NzbDrone.Core.Notifications.Trakt { var request = new HttpRequestBuilder(URL).Resource(resource).Build(); - request.RateLimit = TimeSpan.FromSeconds(2); - request.Headers.Accept = HttpAccept.Json.Value; + request.RateLimit = DefaultRateLimit; request.Method = method; + return BuildRequest(request, accessToken); + } + + public HttpRequest BuildRequest(HttpRequest request, string accessToken) + { + if (request.RateLimit < DefaultRateLimit) + { + request.RateLimit = DefaultRateLimit; + } + + request.Headers.Accept = HttpAccept.Json.Value; + request.Headers.Add("trakt-api-version", "2"); request.Headers.Add("trakt-api-key", ClientId); if (accessToken.IsNotNullOrWhiteSpace()) { - request.Headers.Add("Authorization", "Bearer " + accessToken); + request.Headers.Add("Authorization", $"Bearer {accessToken}"); } return request;