diff --git a/CHANGELOG.md b/CHANGELOG.md index 181ceaa8..ac00a597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ ## Next release +* Address [Trakt requiring pagination](https://github.com/trakt/trakt-api/discussions/681) for lists and library + (formerly collection) endpoints: + * Add paginated variants of `Sync.collectionMovies()`, `Sync.collectionShows()`, `Users.collectionMovies()` + and `Users.collectionShows()` with required `page` and `limit` parameters. The original methods are deprecated. + * Add `Users.listItems()` variants that require pagination and support type and sort order parameters. The original + method is deprecated. +* Add history endpoints to `Sync`. Thanks @defhead! [#125](https://github.com/UweTrottmann/trakt-java/pull/125) +* Add `Sync.playback()` that also accepts start and end times and supports pagination. The existing `getPlayback()` + methods are deprecated. +* Add warnings to `watched_at` fields + that [Trakt will only store and return minute-precision timestamps](https://github.com/trakt/trakt-api/discussions/694). +* Add `TraktV2.isAccountLimitExceeded()`, `TraktV2.isRateLimitExceeded()` and `TraktV2.isServerError()` methods to help + inspect `Response` objects for more common Trakt HTTP status codes. +* Add variants of the watchlist methods that support pagination and sort order parameters. + ## 6.16.0 - 2024-11-07 * `TraktV2`: add `isUnauthorized(response)`, `isAccountLocked(response)` and `isNotVip(response)` helper methods. diff --git a/RELEASING.md b/RELEASING.md index 344b7aaa..f4412080 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,8 +2,14 @@ ## Local testing +Note: on Windows, avoid Git Bash. It uses its own `gpg`. + ```bash -./mwnw clean install -P release,heyuwe-sign +./mvnw clean install -P release,heyuwe-sign -DskipTests +``` + +```powershell +.\mvnw.cmd clean install -P release,heyuwe-sign -DskipTests ``` ## Preparing a release and deploying it to Maven Central diff --git a/pom.xml b/pom.xml index 11a4a2f2..9e1dcee1 100644 --- a/pom.xml +++ b/pom.xml @@ -132,8 +132,7 @@ maven-compiler-plugin 3.14.1 - 1.8 - 1.8 + 8 @@ -205,6 +204,8 @@ 3.12.0 8 + + all,-missing diff --git a/src/main/java/com/uwetrottmann/trakt5/TraktV2.java b/src/main/java/com/uwetrottmann/trakt5/TraktV2.java index 0ab10660..113b923b 100644 --- a/src/main/java/com/uwetrottmann/trakt5/TraktV2.java +++ b/src/main/java/com/uwetrottmann/trakt5/TraktV2.java @@ -361,6 +361,14 @@ public static boolean isUnauthorized(Response response) { return response.code() == 401; } + /** + * Checks if the response code is 420, which indicates an account limit would be exceeded. These limits can be higher for VIP users. + */ + public static boolean isAccountLimitExceeded(Response response) { + return response.code() == 420; + } + /** * Checks if the response code is 423, which indicates the * Trakt account is locked. @@ -377,6 +385,21 @@ public static boolean isNotVip(Response response) { return response.code() == 426; } + /** + * Checks if the response code is 429, which indicates the rate limit was exceeded. + * Trakt rate limiting info + */ + public static boolean isRateLimitExceeded(Response response) { + return response.code() == 429; + } + + /** + * Checks if the response code is 500 or greater, which indicates a server error. + */ + public static boolean isServerError(Response response) { + return response.code() >= 500; + } + /** * If it exists, returns the value of the {@code X-Pagination-Page-Count} header from the response. */ diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/BaseCheckinResponse.java b/src/main/java/com/uwetrottmann/trakt5/entities/BaseCheckinResponse.java index ce105819..75bad584 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/BaseCheckinResponse.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/BaseCheckinResponse.java @@ -21,6 +21,10 @@ public abstract class BaseCheckinResponse { + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. + */ public OffsetDateTime watched_at; public ShareSettings sharing; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/BaseEpisode.java b/src/main/java/com/uwetrottmann/trakt5/entities/BaseEpisode.java index 68c03915..c7b2eb7e 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/BaseEpisode.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/BaseEpisode.java @@ -26,6 +26,10 @@ public class BaseEpisode { public OffsetDateTime collected_at; /** watched */ public Integer plays; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. + */ public OffsetDateTime last_watched_at; /** progress */ public Boolean completed; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/BaseMovie.java b/src/main/java/com/uwetrottmann/trakt5/entities/BaseMovie.java index beee97c5..121c74be 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/BaseMovie.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/BaseMovie.java @@ -23,6 +23,10 @@ public class BaseMovie { public Movie movie; public OffsetDateTime collected_at; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. + */ public OffsetDateTime last_watched_at; public OffsetDateTime last_updated_at; public OffsetDateTime listed_at; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/BaseShow.java b/src/main/java/com/uwetrottmann/trakt5/entities/BaseShow.java index ae344f88..3aa9fee1 100755 --- a/src/main/java/com/uwetrottmann/trakt5/entities/BaseShow.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/BaseShow.java @@ -33,6 +33,10 @@ public class BaseShow { public OffsetDateTime listed_at; /** watched */ public Integer plays; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. + */ public OffsetDateTime last_watched_at; public OffsetDateTime last_updated_at; public OffsetDateTime reset_at; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/HistoryEntry.java b/src/main/java/com/uwetrottmann/trakt5/entities/HistoryEntry.java index b4483046..fe50a5d9 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/HistoryEntry.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/HistoryEntry.java @@ -21,6 +21,10 @@ public class HistoryEntry { public Long id; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. + */ public OffsetDateTime watched_at; public String action; public String type; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/SyncEpisode.java b/src/main/java/com/uwetrottmann/trakt5/entities/SyncEpisode.java index 1716b6d9..a9e42bf2 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/SyncEpisode.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/SyncEpisode.java @@ -34,6 +34,10 @@ public class SyncEpisode { public EpisodeIds ids; public OffsetDateTime collected_at; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. Using {@code .truncatedTo(ChronoUnit.MINUTES)} can be helpful. + */ public OffsetDateTime watched_at; public OffsetDateTime rated_at; public Rating rating; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/SyncMovie.java b/src/main/java/com/uwetrottmann/trakt5/entities/SyncMovie.java index 5df2eff2..1937f4bd 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/SyncMovie.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/SyncMovie.java @@ -32,6 +32,10 @@ public class SyncMovie { public MovieIds ids; public OffsetDateTime collected_at; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. Using {@code .truncatedTo(ChronoUnit.MINUTES)} can be helpful. + */ public OffsetDateTime watched_at; public OffsetDateTime rated_at; public Rating rating; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/SyncSeason.java b/src/main/java/com/uwetrottmann/trakt5/entities/SyncSeason.java index f947d033..03fa69e7 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/SyncSeason.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/SyncSeason.java @@ -29,6 +29,10 @@ public class SyncSeason { public List episodes; public OffsetDateTime collected_at; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. Using {@code .truncatedTo(ChronoUnit.MINUTES)} can be helpful. + */ public OffsetDateTime watched_at; public OffsetDateTime rated_at; public Rating rating; diff --git a/src/main/java/com/uwetrottmann/trakt5/entities/SyncShow.java b/src/main/java/com/uwetrottmann/trakt5/entities/SyncShow.java index 1ee82f09..a880d0fa 100644 --- a/src/main/java/com/uwetrottmann/trakt5/entities/SyncShow.java +++ b/src/main/java/com/uwetrottmann/trakt5/entities/SyncShow.java @@ -29,6 +29,10 @@ public class SyncShow { public List seasons; public OffsetDateTime collected_at; + /** + * Warning: Trakt is planning to only store and return minute-precision timestamps for watched_at. So seconds and + * nanoseconds will always be zero. Using {@code .truncatedTo(ChronoUnit.MINUTES)} can be helpful. + */ public OffsetDateTime watched_at; public OffsetDateTime rated_at; public Rating rating; diff --git a/src/main/java/com/uwetrottmann/trakt5/enums/PlaybackType.java b/src/main/java/com/uwetrottmann/trakt5/enums/PlaybackType.java new file mode 100644 index 00000000..bea16af4 --- /dev/null +++ b/src/main/java/com/uwetrottmann/trakt5/enums/PlaybackType.java @@ -0,0 +1,35 @@ +/* + * Copyright © 2026 Uwe Trottmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.uwetrottmann.trakt5.enums; + +public enum PlaybackType implements TraktEnum { + + MOVIES("movies"), + EPISODES("episodes"); + + private final String value; + + PlaybackType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + +} diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Calendars.java b/src/main/java/com/uwetrottmann/trakt5/services/Calendars.java index 4caa17d3..f3892ef8 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Calendars.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Calendars.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.CalendarMovieEntry; import com.uwetrottmann.trakt5.entities.CalendarShowEntry; import retrofit2.Call; @@ -27,7 +28,7 @@ public interface Calendars { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required * * @see #shows(String, int) */ @@ -38,7 +39,7 @@ Call> myShows( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required * * @see #newShows(String, int) */ @@ -49,7 +50,7 @@ Call> myNewShows( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required * * @see #seasonPremieres(String, int) */ @@ -60,7 +61,7 @@ Call> mySeasonPremieres( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required * * @see #movies(String, int) */ diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Checkin.java b/src/main/java/com/uwetrottmann/trakt5/services/Checkin.java index bdec1cb5..05ef01d9 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Checkin.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Checkin.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.EpisodeCheckin; import com.uwetrottmann.trakt5.entities.EpisodeCheckinResponse; import com.uwetrottmann.trakt5.entities.MovieCheckin; @@ -28,7 +29,7 @@ public interface Checkin { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Check into an episode. This should be tied to a user action to manually indicate they are watching something. The * item will display as watching on the site, then automatically switch to watched status once the duration has @@ -40,7 +41,7 @@ Call checkin( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Check into a movie. This should be tied to a user action to manually indicate they are watching something. The * item will display as watching on the site, then automatically switch to watched status once the duration has @@ -52,7 +53,7 @@ Call checkin( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Removes any active checkins, no need to provide a specific item. */ diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Comments.java b/src/main/java/com/uwetrottmann/trakt5/services/Comments.java index 2d7681f0..e7d43090 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Comments.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Comments.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.Comment; import retrofit2.Call; import retrofit2.http.Body; @@ -30,7 +31,7 @@ public interface Comments { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add a new comment to a movie, show, episode, or list. If you add a review, it needs to be at least 200 words. * Also make sure to allow and encourage spoilers to be indicated in your app. @@ -44,7 +45,7 @@ Call post( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns a single comment and indicates how many replies it has. Use GET /comments/:id/replies to get the actual * replies. @@ -57,7 +58,7 @@ Call get( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Update a single comment created within the last hour. The OAuth user must match the author of the comment in * order to update it. @@ -72,7 +73,7 @@ Call update( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Delete a single comment created within the last hour. This also effectively removes any replies this comment has. * The OAuth user must match the author of the comment in order to delete it. @@ -85,7 +86,7 @@ Call delete( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all replies for a comment. It is possible these replies could have replies themselves, so in that case * you would just call GET /comment/:id/replies again with the new comment_id. @@ -98,7 +99,7 @@ Call> replies( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add a new reply to an existing comment. Also make sure to allow and encourage spoilers to be indicated in your * app. diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Notes.java b/src/main/java/com/uwetrottmann/trakt5/services/Notes.java index fc9135e9..5e544107 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Notes.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Notes.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.AddNoteRequest; import com.uwetrottmann.trakt5.entities.Note; import com.uwetrottmann.trakt5.entities.UpdateNoteRequest; @@ -30,7 +31,7 @@ public interface Notes { /** - * VIP Only, OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add a note (maximum 500 characters). Note: this also replaces an existing note. *

@@ -42,7 +43,7 @@ Call addNote( ); /** - * VIP Only, OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get a note. *

@@ -54,7 +55,7 @@ Call getNote( ); /** - * VIP Only, OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Update a note (maximum 500 characters). *

@@ -67,7 +68,7 @@ Call updateNote( ); /** - * VIP Only, OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Delete a note. *

diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Recommendations.java b/src/main/java/com/uwetrottmann/trakt5/services/Recommendations.java index d548b2e9..bdda1c41 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Recommendations.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Recommendations.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.Movie; import com.uwetrottmann.trakt5.entities.Show; import com.uwetrottmann.trakt5.enums.Extended; @@ -30,7 +31,7 @@ public interface Recommendations { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Personalized movie recommendations for a user. Results returned with the top recommendation first. * @@ -45,7 +46,7 @@ Call> movies( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Dismiss a movie from getting recommended anymore. * @@ -57,7 +58,7 @@ Call dismissMovie( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Personalized show recommendations for a user. Results returned with the top recommendation first. * @@ -72,7 +73,7 @@ Call> shows( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Dismiss a show from getting recommended anymore. * diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Scrobble.java b/src/main/java/com/uwetrottmann/trakt5/services/Scrobble.java index 440b09ca..e1afb0cd 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Scrobble.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Scrobble.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.PlaybackResponse; import com.uwetrottmann.trakt5.entities.ScrobbleProgress; import retrofit2.Call; @@ -24,7 +25,7 @@ public interface Scrobble { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* User starts a video */ @@ -34,7 +35,7 @@ Call startWatching( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* User pauses a video */ @@ -44,7 +45,7 @@ Call pauseWatching( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* User stops a video */ diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Shows.java b/src/main/java/com/uwetrottmann/trakt5/services/Shows.java index a3eb79d6..29d1f1a9 100755 --- a/src/main/java/com/uwetrottmann/trakt5/services/Shows.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Shows.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.BaseShow; import com.uwetrottmann.trakt5.entities.Comment; import com.uwetrottmann.trakt5.entities.Credits; @@ -123,7 +124,7 @@ Call> comments( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns collection progress for show including details on all seasons and episodes. The {@code next_episode} will * be the next episode the user should collect, if there are no upcoming episodes it will be set to {@code null}. @@ -162,7 +163,7 @@ Call collectedProgress( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns watched progress for show including details on all seasons and episodes. The {@code next_episode} will be * the next episode the user should watch, if there are no upcoming episodes it will be set to {@code null}. If not diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java index c4088ac3..f5c902f0 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java @@ -16,8 +16,10 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.BaseMovie; import com.uwetrottmann.trakt5.entities.BaseShow; +import com.uwetrottmann.trakt5.entities.HistoryEntry; import com.uwetrottmann.trakt5.entities.LastActivities; import com.uwetrottmann.trakt5.entities.PlaybackResponse; import com.uwetrottmann.trakt5.entities.RatedEpisode; @@ -26,10 +28,14 @@ import com.uwetrottmann.trakt5.entities.RatedShow; import com.uwetrottmann.trakt5.entities.SyncItems; import com.uwetrottmann.trakt5.entities.SyncResponse; +import com.uwetrottmann.trakt5.entities.UserSlug; import com.uwetrottmann.trakt5.entities.WatchlistedEpisode; import com.uwetrottmann.trakt5.entities.WatchlistedSeason; import com.uwetrottmann.trakt5.enums.Extended; +import com.uwetrottmann.trakt5.enums.HistoryType; +import com.uwetrottmann.trakt5.enums.PlaybackType; import com.uwetrottmann.trakt5.enums.RatingsFilter; +import org.threeten.bp.OffsetDateTime; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; @@ -38,12 +44,13 @@ import retrofit2.http.Path; import retrofit2.http.Query; +import javax.annotation.Nonnull; import java.util.List; public interface Sync { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* This method is a useful first step in the syncing process. We recommended caching these dates locally, then you * can compare to know exactly what data has changed recently. This can greatly optimize your syncs so you don't @@ -53,31 +60,61 @@ public interface Sync { Call lastActivities(); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Get all collected movies in a user's collection. A collected item indicates availability to watch digitally or on - * physical media. + * Get all movies in a user's library (formerly collection). A collected item indicates availability to watch + * digitally or on physical media. + * + * @deprecated Use {@link #collectionMovies(int, int, Extended)} instead. + */ + @Deprecated + @GET("sync/collection/movies") + Call> collectionMovies( + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link Users#collectionMovies(UserSlug, int, int, Extended)}. */ @GET("sync/collection/movies") Call> collectionMovies( + @Query("page") int page, + @Query("limit") int limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Get all collected shows in a user's collection. A collected item indicates availability to watch digitally or on - * physical media. + * Get all shows in a user's library (formerly collection). A collected item indicates availability to watch + * digitally or on physical media. + * + * @deprecated Use {@link #collectionShows(int, int, Extended)} instead. */ + @Deprecated @GET("sync/collection/shows") Call> collectionShows( @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Add one or more items to a user's collection including the format of the item. + * Like {@link Users#collectionShows(UserSlug, int, int, Extended)}. + */ + @GET("sync/collection/shows") + Call> collectionShows( + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Add one or more items to a user's library (formerly collection) including the format of the item. * * @param items A list of movies, shows, seasons or episodes. */ @@ -87,9 +124,9 @@ Call addItemsToCollection( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Remove one or more items from a user's collection. + * Remove one or more items from a user's library (formerly collection). * * @param items A list of movies, shows, seasons or episodes. */ @@ -99,7 +136,7 @@ Call deleteItemsFromCollection( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all movies a user has watched. */ @@ -109,37 +146,88 @@ Call> watchedMovies( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Whenever a scrobble is paused, the playback progress is saved. Use this progress to sync up playback across + * different media centers or apps. For example, you can start watching a movie in a media center, stop it, then + * resume on your tablet from the same spot. Each item will have the progress percentage between 0 and 100. + *

+ * Use {@link #playback(PlaybackType, OffsetDateTime, OffsetDateTime, Integer, Integer)} to specify a type to only + * get movies or episodes. + *

+ * By default, all results will be returned. Pagination is optional and can be used for something like an "on deck" + * feature, or if you only need a limited data set. + *

+ * Note: Trakt only saves playback progress for the last 6 months. + * + * @see #playback(PlaybackType, OffsetDateTime, OffsetDateTime, Integer, Integer) + */ + @GET("sync/playback") + Call> playback( + @Query("start_at") OffsetDateTime startAt, + @Query("end_at") OffsetDateTime endAt, + @Query("page") Integer page, + @Query("limit") Integer limit + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link #playback(OffsetDateTime, OffsetDateTime, Integer, Integer)}, but allows to specify a type to only + * get movies or episodes. + * + * @see #playback(OffsetDateTime, OffsetDateTime, Integer, Integer) + */ + @GET("sync/playback/{type}") + Call> playback( + @Path("type") PlaybackType type, + @Query("start_at") OffsetDateTime startAt, + @Query("end_at") OffsetDateTime endAt, + @Query("page") Integer page, + @Query("limit") Integer limit + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all playbacks; + * + * @deprecated Use {@link #playback(OffsetDateTime, OffsetDateTime, Integer, Integer)} instead. */ + @Deprecated @GET("sync/playback") Call> getPlayback( @Query("limit") Integer limit ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all playbacks; + * + * @deprecated Use {@link #playback(PlaybackType, OffsetDateTime, OffsetDateTime, Integer, Integer)} instead. */ + @Deprecated @GET("sync/playback/episodes") Call> getPlaybackEpisodes( @Query("limit") Integer limit ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all playbacks; + * + * @deprecated Use {@link #playback(PlaybackType, OffsetDateTime, OffsetDateTime, Integer, Integer)} instead. */ + @Deprecated @GET("sync/playback/movies") Call> getPlaybackMovies( @Query("limit") Integer limit ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Remove a playback item from a user's playback progress list. A 404 will be returned if the id is invalid. *

@@ -152,7 +240,7 @@ Call removePlayback( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Returns all shows a user has watched. */ @@ -162,7 +250,59 @@ Call> watchedShows( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link Users#history(UserSlug, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime)}. + * + * @see Sync#history(HistoryType, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) + */ + @GET("sync/history") + Call> history( + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended, + @Query("start_at") OffsetDateTime startAt, + @Query("end_at") OffsetDateTime endAt + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link #history(Integer, Integer, Extended, OffsetDateTime, OffsetDateTime)}, but allows to set a type to + * only return movies or episodes. + * + * @see Users#history(UserSlug, HistoryType, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) + * @see Sync#history(Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) + */ + @GET("sync/history/{type}") + Call> history( + @Path("type") HistoryType type, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended, + @Query("start_at") OffsetDateTime startAt, + @Query("end_at") OffsetDateTime endAt + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like + * {@link Users#history(UserSlug, HistoryType, int, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime)}. + */ + @GET("sync/history/{type}/{id}") + Call> history( + @Path("type") HistoryType type, + @Path("id") int id, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended, + @Query("start_at") OffsetDateTime startAt, + @Query("end_at") OffsetDateTime endAt + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add items to a user's watch history. Accepts shows, seasons, episodes and movies. If only a show is passed, * assumes all seasons are to be marked watched. Same for seasons. Send a watched_at UTC datetime to @@ -176,7 +316,7 @@ Call addItemsToWatchedHistory( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Remove items from a user's watch history including all watches, scrobbles, and checkins. Accepts shows, seasons, * episodes and movies. If only a show is passed, assumes all seasons are to be removed from history. Same for @@ -190,7 +330,7 @@ Call deleteItemsFromWatchedHistory( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get a user's ratings filtered by movies. You can filter for a specific rating between 1 and 10. * @@ -209,7 +349,7 @@ Call> ratingsMovies( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get a user's ratings filtered by shows. You can filter for a specific rating between 1 and 10. * @@ -228,7 +368,7 @@ Call> ratingsShows( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get a user's ratings filtered by seasons. You can filter for a specific rating between 1 and 10. * @@ -247,7 +387,7 @@ Call> ratingsSeasons( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get a user's ratings filtered by episodes. You can filter for a specific rating between 1 and 10. * @@ -266,7 +406,7 @@ Call> ratingsEpisodes( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Rate one or more items. * @@ -278,7 +418,7 @@ Call addRatings( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Delete ratings for one or more items. * @@ -291,43 +431,105 @@ Call deleteRatings( /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Returns all items in a user's watchlist filtered by movies. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Like {@link Users#watchlistMovies(UserSlug, Extended)}. + */ + @GET("sync/watchlist/movies") + Call> watchlistMovies( + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistMovies(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/movies") Call> watchlistMovies( + @Query("page") Integer page, + @Query("limit") Integer limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Required + * Like {@link Users#watchlistMovies(UserSlug, String, String, Integer, Integer, Extended)}. + */ + @GET("sync/watchlist/movies/{sort_by}/{sort_how}") + Call> watchlistMovies( + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Returns all items in a user's watchlist filtered by shows. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Like {@link Users#watchlistShows(UserSlug, Extended)}. + */ + @GET("sync/watchlist/shows") + Call> watchlistShows( + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistShows(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/shows") Call> watchlistShows( + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistShows(UserSlug, String, String, Integer, Integer, Extended)}. + */ + @GET("sync/watchlist/shows/{sort_by}/{sort_how}") + Call> watchlistShows( + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Returns all items in a user's watchlist filtered by seasons. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Like {@link Users#watchlistSeasons(UserSlug, Extended)}. + */ + @GET("sync/watchlist/seasons") + Call> watchlistSeasons( + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistSeasons(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/seasons") Call> watchlistSeasons( + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistSeasons(UserSlug, String, String, Integer, Integer, Extended)}. + */ + @GET("sync/watchlist/seasons/{sort_by}/{sort_how}") + Call> watchlistSeasons( + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

- * Returns all items in a user's watchlist filtered by episodes. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Like {@link Users#watchlistEpisodes(UserSlug, Extended)}. */ @GET("sync/watchlist/episodes") Call> watchlistEpisodes( @@ -335,7 +537,29 @@ Call> watchlistEpisodes( ); /** - * OAuth Required + * Like {@link Users#watchlistEpisodes(UserSlug, Integer, Integer, Extended)}. + */ + @GET("sync/watchlist/episodes") + Call> watchlistEpisodes( + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link Users#watchlistEpisodes(UserSlug, String, String, Integer, Integer, Extended)}. + */ + @GET("sync/watchlist/episodes/{sort_by}/{sort_how}") + Call> watchlistEpisodes( + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add one of more items to a user's watchlist. * @@ -347,7 +571,7 @@ Call addItemsToWatchlist( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Delete one or more items from a user's watchlist. * diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Users.java b/src/main/java/com/uwetrottmann/trakt5/services/Users.java index 93ba1153..0edb0c1f 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Users.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Users.java @@ -16,6 +16,7 @@ package com.uwetrottmann.trakt5.services; +import com.uwetrottmann.trakt5.TraktV2; import com.uwetrottmann.trakt5.entities.BaseMovie; import com.uwetrottmann.trakt5.entities.BaseShow; import com.uwetrottmann.trakt5.entities.Followed; @@ -51,12 +52,13 @@ import retrofit2.http.Path; import retrofit2.http.Query; +import javax.annotation.Nonnull; import java.util.List; public interface Users { /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Get the user's settings so you can align your app's experience with what they're used to on the Trakt website. */ @@ -64,7 +66,7 @@ public interface Users { Call settings(); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get a user's profile information. If the user is private, info will only be returned if you send OAuth and are * either that user or an approved follower. @@ -78,13 +80,15 @@ Call profile( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Get all collected movies in a user's collection. A collected item indicates availability to watch digitally or on - * physical media. + * Get all movies in a user's library (formerly collection). A collected item indicates availability to watch + * digitally or on physical media. * * @param userSlug Example: "sean". + * @deprecated Use {@link #collectionMovies(UserSlug, int, int, Extended)} instead. */ + @Deprecated @GET("users/{username}/collection/movies") Call> collectionMovies( @Path("username") UserSlug userSlug, @@ -92,21 +96,59 @@ Call> collectionMovies( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Get all collected shows in a user's collection. A collected item indicates availability to watch digitally or on - * physical media. + * Get all movies in a user's library (formerly collection). * * @param userSlug Example: "sean". + * @param page Number of page of results to be returned. + * @param limit Number of results to return per page. + * @see Sync#collectionMovies(int, int, Extended) + */ + @GET("users/{username}/collection/movies") + Call> collectionMovies( + @Path("username") UserSlug userSlug, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Get all shows in a user's library (formerly collection). A collected item indicates availability to watch + * digitally or on physical media. + * + * @param userSlug Example: "sean". + * @deprecated Use {@link #collectionShows(UserSlug, int, int, Extended)} instead. + */ + @Deprecated + @GET("users/{username}/collection/shows") + Call> collectionShows( + @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Get all shows in a user's library (formerly collection). + * + * @param userSlug Example: "sean". + * @param page Number of page of results to be returned. + * @param limit Number of results to return per page. + * @see Sync#collectionShows(int, int, Extended) */ @GET("users/{username}/collection/shows") Call> collectionShows( @Path("username") UserSlug userSlug, + @Query("page") int page, + @Query("limit") int limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * VIP Only, OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns the most recently added notes for the user. *

@@ -122,7 +164,7 @@ Call> notes( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all custom lists for a user. */ @@ -132,7 +174,7 @@ Call> lists( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Create a new custom list. The name is the only required field, but the other info is recommended to ask for. */ @@ -143,7 +185,7 @@ Call createList( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Update a custom list by sending 1 or more parameters. If you update the list name, the original slug will still * be retained so existing references to this list won't break. @@ -156,7 +198,7 @@ Call updateList( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Remove a custom list and all items it contains. */ @@ -167,7 +209,7 @@ Call deleteList( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Reorder all custom lists by sending the updated rank of list ids. */ @@ -178,10 +220,13 @@ Call reorderLists( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get all items on a custom list. Items can be movies, shows, seasons, episodes, or people. + * + * @deprecated Use {@link #listItems(UserSlug, String, int, int, Extended)} instead. */ + @Deprecated @GET("users/{username}/lists/{id}/items") Call> listItems( @Path("username") UserSlug userSlug, @@ -190,7 +235,96 @@ Call> listItems( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Get all items on a personal list. Items can be a movie, show, season, episode, or person. Use + * {@link #listItems(UserSlug, String, String, int, int, Extended)} to specify the type parameter with a single + * value or comma-delimited string for multiple item types. + *

+ * Notes: Each list item contains a notes field with text entered by the user. + *

+ * Sorting: Default sorting is based on the list defaults and sent in the X-Sort-By and X-Sort-How headers. Use + * {@link #listItems(UserSlug, String, String, String, String, int, int, Extended)} to specify a custom sort order. + */ + @GET("users/{username}/lists/{id}/items") + Call> listItems( + @Path("username") UserSlug userSlug, + @Path("id") String id, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #listItems(UserSlug, String, int, int, Extended)}, but you can specify a sort order. + *

+ * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. + *

+ * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released , runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/lists/{id}/items/{sort_by}/{sort_how}") + Call> listItems( + @Path("username") UserSlug userSlug, + @Path("id") String id, + @Path("sort_by") String sortBy, + @Path("sort_how") String sortHow, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #listItems(UserSlug, String, int, int, Extended)}, but you can specify the type parameter with a + * single value or comma-delimited string for multiple item types. + * + * @param type Filter for a specific item type. Example: {@code movie,show}. Possible values: movie, show, season, + * episode, person. + */ + @GET("users/{username}/lists/{id}/items/{type}") + Call> listItems( + @Path("username") UserSlug userSlug, + @Path("id") String id, + @Path("type") String type, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #listItems(UserSlug, String, String, int, int, Extended)}, but you can specify a sort order. + *

+ * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. + *

+ * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param type Filter for a specific item type. Example: {@code movie,show}. Possible values: movie, show, + * season, episode, person. + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released, runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/lists/{id}/items/{type}/{sort_by}/{sort_how}") + Call> listItems( + @Path("username") UserSlug userSlug, + @Path("id") String id, + @Path("type") String type, + @Path("sort_by") String sortBy, + @Path("sort_how") String sortHow, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Add one or more items to a custom list. Items can be movies, shows, seasons, episodes, or people. */ @@ -202,7 +336,7 @@ Call addListItems( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Remove one or more items from a custom list. */ @@ -214,7 +348,7 @@ Call deleteListItems( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Reorder all items on a list by sending the updated rank of list item ids. */ @@ -226,7 +360,7 @@ Call reorderListItems( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* If the user has a private profile, the follow request will require approval (approved_at will be null). If a user * is public, they will be followed immediately (approved_at will have a date). @@ -239,7 +373,7 @@ Call follow( ); /** - * OAuth Required + * OAuth {@link TraktV2#accessToken(String) access token} required *

* Unfollow someone you already follow. */ @@ -249,7 +383,7 @@ Call unfollow( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all followers including when the relationship began. */ @@ -260,7 +394,7 @@ Call> followers( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all user's they follow including when the relationship began. */ @@ -271,7 +405,7 @@ Call> following( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all friends for a user including when the relationship began. Friendship is a 2 way relationship where * each user follows the other. @@ -283,7 +417,7 @@ Call> friends( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns movies and episodes that a user has watched, sorted by most recent. *

@@ -292,6 +426,7 @@ Call> friends( * {@code watch}. * * @param userSlug Example: "sean". + * @see Sync#history(Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) */ @GET("users/{username}/history") Call> history( @@ -304,15 +439,13 @@ Call> history( ); /** - * OAuth Optional - *

- * Returns movies or episodes that a user has watched, sorted by most recent. + * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * The {@code id} uniquely identifies each history event and can be used to remove events individually using the - * {@code POST /sync/history/remove method}. The action will be set to {@code scrobble}, {@code checkin}, or - * {@code watch}. + * Like {@link #history(UserSlug, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime)}, but allows to set a + * type to only return movies or episodes. * * @param userSlug Example: "sean". + * @see Sync#history(HistoryType, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) */ @GET("users/{username}/history/{type}") Call> history( @@ -326,7 +459,7 @@ Call> history( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns the history for just the specified item. For example, {@code /history/movies/12601} would return all * watches for TRON: Legacy and {@code /history/shows/1388} would return all watched episodes for Breaking Bad. If @@ -338,6 +471,7 @@ Call> history( * {@code watch}. * * @param userSlug Example: "sean". + * @see Sync#history(HistoryType, int, Integer, Integer, Extended, OffsetDateTime, OffsetDateTime) */ @GET("users/{username}/history/{type}/{id}") Call> history( @@ -352,7 +486,7 @@ Call> history( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get a user's ratings filtered by movies. You can filter for a specific rating between 1 and 10. * @@ -367,7 +501,7 @@ Call> ratingsMovies( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get a user's ratings filtered by shows. You can filter for a specific rating between 1 and 10. * @@ -382,7 +516,7 @@ Call> ratingsShows( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get a user's ratings filtered by seasons. You can filter for a specific rating between 1 and 10. * @@ -397,7 +531,7 @@ Call> ratingsSeasons( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Get a user's ratings filtered by episodes. You can filter for a specific rating between 1 and 10. * @@ -412,55 +546,227 @@ Call> ratingsEpisodes( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Returns all items in a user's watchlist filtered by movies. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Returns all items in a user's watchlist filtered by movies. + *

+ * The watchlist should not be used as a list of what the user is actively watching. Use a combination of the + * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. + *

+ * Auto Removal + *

+ * When an item is watched, it will be automatically removed from the watchlist. For shows and seasons, watching 1 + * episode will remove the entire show or season. + * + * @see #watchlistMovies(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/movies") Call> watchlistMovies( - @Path("username") UserSlug userSlug, + @Nonnull @Path("username") UserSlug userSlug, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Optional + * Like {@link #watchlistMovies(UserSlug, Extended)}, but you can specify pagination parameters. + */ + @GET("users/{username}/watchlist/movies") + Call> watchlistMovies( + @Nonnull @Path("username") UserSlug userSlug, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #watchlistMovies(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + *

+ * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

- * Returns all items in a user's watchlist filtered by shows. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released , runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/watchlist/movies/{sort_by}/{sort_how}") + Call> watchlistMovies( + @Nonnull @Path("username") UserSlug userSlug, + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Returns all items in a user's watchlist filtered by shows. + *

+ * The watchlist should not be used as a list of what the user is actively watching. Use a combination of the + * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. + *

+ * Auto Removal + *

+ * When an item is watched, it will be automatically removed from the watchlist. For shows and seasons, watching 1 + * episode will remove the entire show or season. + * + * @see #watchlistShows(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/shows") Call> watchlistShows( - @Path("username") UserSlug userSlug, + @Nonnull @Path("username") UserSlug userSlug, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Optional + * Like {@link #watchlistShows(UserSlug, Extended)}, but you can specify pagination parameters. + */ + @GET("users/{username}/watchlist/shows") + Call> watchlistShows( + @Nonnull @Path("username") UserSlug userSlug, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #watchlistShows(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + *

+ * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

- * Returns all items in a user's watchlist filtered by seasons. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released , runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/watchlist/shows/{sort_by}/{sort_how}") + Call> watchlistShows( + @Nonnull @Path("username") UserSlug userSlug, + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Returns all items in a user's watchlist filtered by seasons. + *

+ * The watchlist should not be used as a list of what the user is actively watching. Use a combination of the + * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. + *

+ * Auto Removal + *

+ * When an item is watched, it will be automatically removed from the watchlist. For shows and seasons, watching 1 + * episode will remove the entire show or season. + * + * @see #watchlistSeasons(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/seasons") Call> watchlistSeasons( - @Path("username") UserSlug userSlug, + @Nonnull @Path("username") UserSlug userSlug, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Optional + * Like {@link #watchlistSeasons(UserSlug, Extended)}, but you can specify pagination parameters. + */ + @GET("users/{username}/watchlist/seasons") + Call> watchlistSeasons( + @Nonnull @Path("username") UserSlug userSlug, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #watchlistSeasons(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. *

- * Returns all items in a user's watchlist filtered by episodes. When an item is watched, it will be automatically - * removed from the watchlist. To track what the user is actively watching, use the progress APIs. + * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. + *

+ * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released , runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/watchlist/seasons/{sort_by}/{sort_how}") + Call> watchlistSeasons( + @Nonnull @Path("username") UserSlug userSlug, + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Returns all items in a user's watchlist filtered by episodes. + *

+ * The watchlist should not be used as a list of what the user is actively watching. Use a combination of the + * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. + *

+ * Auto Removal + *

+ * When an item is watched, it will be automatically removed from the watchlist. For shows and seasons, watching 1 + * episode will remove the entire show or season. + * + * @see #watchlistEpisodes(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/episodes") Call> watchlistEpisodes( - @Path("username") UserSlug userSlug, + @Nonnull @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #watchlistEpisodes(UserSlug, Extended)}, but you can specify pagination parameters. + */ + @GET("users/{username}/watchlist/episodes") + Call> watchlistEpisodes( + @Nonnull @Path("username") UserSlug userSlug, + @Query("page") Integer page, + @Query("limit") Integer limit, + @Query(value = "extended", encoded = true) Extended extended + ); + + /** + * Like {@link #watchlistEpisodes(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + *

+ * The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. + *

+ * Some sort_by options are VIP Only including imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, + * votes, imdb_votes, and tmdb_votes. If sent for a non VIP, the items will fall back to rank. + * + * @param sortBy Sort by a specific property. Possible values: rank, added, title, released , runtime, popularity, + * random, percentage, imdb_rating, tmdb_rating, rt_tomatometer, rt_audience, metascore, votes, + * imdb_votes, tmdb_votes, my_rating, watched, collected. + * @param sortHow Sort direction. Possible values: asc, desc. + */ + @GET("users/{username}/watchlist/episodes/{sort_by}/{sort_how}") + Call> watchlistEpisodes( + @Nonnull @Path("username") UserSlug userSlug, + @Nonnull @Path("sort_by") String sortBy, + @Nonnull @Path("sort_how") String sortHow, + @Query("page") Integer page, + @Query("limit") Integer limit, @Query(value = "extended", encoded = true) Extended extended ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all movies or shows a user has watched sorted by most plays. * @@ -473,7 +779,7 @@ Call> watchedMovies( ); /** - * OAuth Optional + * OAuth {@link TraktV2#accessToken(String) access token} optional *

* Returns all movies or shows a user has watched sorted by most plays. * diff --git a/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java b/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java index 3fcf6dcc..b1f48e8b 100644 --- a/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java +++ b/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java @@ -32,6 +32,8 @@ import com.uwetrottmann.trakt5.entities.Ratings; import com.uwetrottmann.trakt5.entities.Stats; import com.uwetrottmann.trakt5.entities.TraktError; +import com.uwetrottmann.trakt5.entities.WatchlistedEpisode; +import com.uwetrottmann.trakt5.entities.WatchlistedSeason; import com.uwetrottmann.trakt5.enums.Type; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; @@ -210,6 +212,7 @@ public void assertShowStats(Stats stats) { } protected static void assertSyncMovies(List movies, String type) { + assertThat(movies).isNotEmpty(); for (BaseMovie movie : movies) { assertThat(movie.movie).isNotNull(); switch (type) { @@ -255,6 +258,32 @@ protected static void assertSyncShows(List shows, String type) { } } + public static void assertWatchlistShows(List shows) { + assertThat(shows).isNotEmpty(); + for (BaseShow show : shows) { + assertThat(show.show).isNotNull(); + assertThat(show.listed_at).isNotNull(); + } + } + + public static void assertWatchlistSeasons(List seasons) { + assertThat(seasons).isNotEmpty(); + for (WatchlistedSeason season : seasons) { + assertThat(season.season).isNotNull(); + assertThat(season.show).isNotNull(); + assertThat(season.listed_at).isNotNull(); + } + } + + public static void assertWatchlistEpisodes(List episodes) { + assertThat(episodes).isNotEmpty(); + for (WatchlistedEpisode episode : episodes) { + assertThat(episode.episode).isNotNull(); + assertThat(episode.show).isNotNull(); + assertThat(episode.listed_at).isNotNull(); + } + } + public void assertCast(Credits credits, Type type) { for (CastMember castMember : credits.cast) { assertThat(castMember.character).isNotNull(); @@ -308,6 +337,22 @@ public void assertCrewMembers(@Nullable List crew, Type type) { } } + /** + * Checks that the promised pagination headers are returned and that the supplied page and limit parameters were + * used. + */ + public static void assertPaginationHeaders(Response response, int expectedPage, int expectedLimit) { + assertThat(TraktV2.getPageCount(response)).isNotNull(); + assertThat(TraktV2.getItemCount(response)).isNotNull(); + assertThat(response.headers().get("X-Pagination-Page")).isEqualTo(String.valueOf(expectedPage)); + assertThat(response.headers().get("X-Pagination-Limit")).isEqualTo(String.valueOf(expectedLimit)); + } + + public static void assertSortOrderHeaders(Response response, String expectedSortBy, String expectedSortHow) { + assertThat(response.headers().get("x-applied-sort-by")).isEqualTo(expectedSortBy); + assertThat(response.headers().get("x-applied-sort-how")).isEqualTo(expectedSortHow); + } + protected void assertAccessTokenResponse(Response response) { assertSuccessfulResponse(response); assertThat(response.body().access_token).isNotEmpty(); diff --git a/src/test/java/com/uwetrottmann/trakt5/services/CommentsTest.java b/src/test/java/com/uwetrottmann/trakt5/services/CommentsTest.java index 8ae87c28..c54ae0d5 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/CommentsTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/CommentsTest.java @@ -49,7 +49,7 @@ public void test_postAndUpdate() throws InterruptedException, IOException { Thread.sleep(3000); // delete the comment again - Response response = getTrakt().comments().delete(commentResponse.id).execute(); + Response response = getTrakt().comments().delete(commentResponse.id).execute(); assertSuccessfulResponse(response); assertThat(response.code()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); } @@ -64,7 +64,7 @@ public void test_delete() throws InterruptedException, IOException { Thread.sleep(3000); // delete the comment again - Response response = getTrakt().comments().delete(commentResponse.id).execute(); + Response response = getTrakt().comments().delete(commentResponse.id).execute(); assertSuccessfulResponse(response); assertThat(response.code()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); } @@ -95,7 +95,7 @@ public void test_replies() throws InterruptedException, IOException { } // delete the comment and replies (does this work?) - Response deleteResponse = getTrakt().comments().delete(response.id).execute(); + Response deleteResponse = getTrakt().comments().delete(response.id).execute(); assertSuccessfulResponse(deleteResponse); assertThat(deleteResponse.code()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); } diff --git a/src/test/java/com/uwetrottmann/trakt5/services/HistoryAssertions.java b/src/test/java/com/uwetrottmann/trakt5/services/HistoryAssertions.java new file mode 100644 index 00000000..98414343 --- /dev/null +++ b/src/test/java/com/uwetrottmann/trakt5/services/HistoryAssertions.java @@ -0,0 +1,65 @@ +/* + * Copyright © 2026 Uwe Trottmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.uwetrottmann.trakt5.services; + + +import com.uwetrottmann.trakt5.entities.HistoryEntry; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HistoryAssertions { + + public static void assertHistory(List history) { + for (HistoryEntry entry : history) { + assertThat(entry.id).isGreaterThan(0); + assertThat(entry.watched_at).isNotNull(); + assertThat(entry.action).isNotEmpty(); + assertThat(entry.type).isNotEmpty(); + if ("episode".equals(entry.type)) { + assertThat(entry.episode).isNotNull(); + assertThat(entry.show).isNotNull(); + } else if ("movie".equals(entry.type)) { + assertThat(entry.movie).isNotNull(); + } + } + } + + public static void assertEpisodeHistory(List history) { + for (HistoryEntry entry : history) { + assertThat(entry.id).isGreaterThan(0); + assertThat(entry.watched_at).isNotNull(); + assertThat(entry.action).isNotEmpty(); + assertThat(entry.type).isEqualTo("episode"); + assertThat(entry.episode).isNotNull(); + assertThat(entry.show).isNotNull(); +// System.out.println( +// "Episode watched at date: " + entry.watched_at + entry.watched_at.toInstant().toEpochMilli()); + } + } + + public static void assertMovieHistory(List history) { + for (HistoryEntry entry : history) { + assertThat(entry.watched_at).isNotNull(); + assertThat(entry.action).isNotEmpty(); + assertThat(entry.type).isEqualTo("movie"); + assertThat(entry.movie).isNotNull(); + } + } + +} diff --git a/src/test/java/com/uwetrottmann/trakt5/services/MoviesTest.java b/src/test/java/com/uwetrottmann/trakt5/services/MoviesTest.java index 85416462..049f1eaf 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/MoviesTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/MoviesTest.java @@ -104,7 +104,7 @@ public void test_translation() throws IOException { "de")); assertThat(translations).isNotNull(); // we know that Batman Begins has a German translation, otherwise this test would fail - assertThat(translations).hasSize(1); + assertThat(translations).isNotEmpty(); assertThat(translations.get(0).language).isEqualTo("de"); } diff --git a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java index d1c657eb..85a06f8d 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java @@ -21,6 +21,7 @@ import com.uwetrottmann.trakt5.entities.BaseMovie; import com.uwetrottmann.trakt5.entities.BaseShow; import com.uwetrottmann.trakt5.entities.EpisodeIds; +import com.uwetrottmann.trakt5.entities.HistoryEntry; import com.uwetrottmann.trakt5.entities.LastActivities; import com.uwetrottmann.trakt5.entities.LastActivity; import com.uwetrottmann.trakt5.entities.LastActivityAccount; @@ -44,12 +45,14 @@ import com.uwetrottmann.trakt5.entities.SyncShow; import com.uwetrottmann.trakt5.entities.WatchlistedEpisode; import com.uwetrottmann.trakt5.entities.WatchlistedSeason; +import com.uwetrottmann.trakt5.enums.HistoryType; import com.uwetrottmann.trakt5.enums.Rating; import com.uwetrottmann.trakt5.enums.RatingsFilter; import org.junit.Test; import org.threeten.bp.Instant; import org.threeten.bp.OffsetDateTime; import org.threeten.bp.ZoneOffset; +import org.threeten.bp.temporal.ChronoUnit; import retrofit2.Call; import retrofit2.Response; @@ -57,6 +60,9 @@ import java.util.ArrayList; import java.util.List; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertEpisodeHistory; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertHistory; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertMovieHistory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -101,7 +107,7 @@ public void test_getPlayback() throws IOException, InterruptedException { // Give the server some time to process the request. Thread.sleep(1500); - List playbacks = executeCall(getTrakt().sync().getPlayback(10)); + List playbacks = executeCall(getTrakt().sync().playback(null, null, null, 10)); assertThat(playbacks).isNotNull(); boolean foundEpisode = false; boolean foundMovie = false; @@ -180,13 +186,13 @@ private void assertLastActivityUpdated(LastActivityUpdated activity) { @Test public void test_collectionMovies() throws IOException { - List movies = executeCall(getTrakt().sync().collectionMovies(null)); + List movies = executeCall(getTrakt().sync().collectionMovies(1, 1000, null)); assertSyncMovies(movies, "collection"); } @Test public void test_collectionShows() throws IOException { - List shows = executeCall(getTrakt().sync().collectionShows(null)); + List shows = executeCall(getTrakt().sync().collectionShows(1, 1000, null)); assertSyncShows(shows, "collection"); } @@ -319,16 +325,17 @@ public void test_watchedShows() throws IOException { public void test_addItemsToWatchedHistory() throws IOException { // movie SyncMovie movie = new SyncMovie(); - movie.watched_at = OffsetDateTime.now().minusHours(1); + // Trakt only stores minute precision for watched_at + movie.watched_at = OffsetDateTime.now().truncatedTo(ChronoUnit.MINUTES).minusHours(1); movie.ids = buildMovieIds(); // episode SyncEpisode episode = new SyncEpisode(); episode.number = TestData.EPISODE_NUMBER; - episode.watched_at = OffsetDateTime.now().minusHours(1); + episode.watched_at = OffsetDateTime.now().truncatedTo(ChronoUnit.MINUTES).minusHours(1); SyncEpisode episode2 = new SyncEpisode(); episode2.number = 2; - episode2.watched_at = OffsetDateTime.now().minusMinutes(30); + episode2.watched_at = OffsetDateTime.now().truncatedTo(ChronoUnit.MINUTES).minusMinutes(30); // season SyncSeason season = new SyncSeason(); season.number = TestData.EPISODE_SEASON; @@ -384,10 +391,12 @@ public void test_ratingsMovies_filtered() throws IOException { @Test public void test_ratingsMovies_with_pagination() throws IOException { - Call> call = getTrakt().sync().ratingsMovies(RatingsFilter.ALL, null, 1, 2); + int page = 1; + int limit = 2; + Call> call = getTrakt().sync().ratingsMovies(RatingsFilter.ALL, null, page, limit); Response> response = executeCallWithoutReadingBody(call); - assertThat(response.headers().get("X-Pagination-Page-Count")).isNotEmpty(); - assertThat(response.headers().get("X-Pagination-Item-Count")).isNotEmpty(); + + assertPaginationHeaders(response, page, limit); } @Test @@ -398,10 +407,12 @@ public void test_ratingsShows() throws IOException { @Test public void test_ratingsShows_with_pagination() throws IOException { - Call> call = getTrakt().sync().ratingsShows(RatingsFilter.ALL, null, 1, 2); + int page = 1; + int limit = 2; + Call> call = getTrakt().sync().ratingsShows(RatingsFilter.ALL, null, page, limit); Response> response = executeCallWithoutReadingBody(call); - assertThat(response.headers().get("X-Pagination-Page-Count")).isNotEmpty(); - assertThat(response.headers().get("X-Pagination-Item-Count")).isNotEmpty(); + + assertPaginationHeaders(response, page, limit); } @Test @@ -413,10 +424,12 @@ public void test_ratingsSeasons() throws IOException { @Test public void test_ratingsSeasons_with_pagination() throws IOException { - Call> call = getTrakt().sync().ratingsSeasons(RatingsFilter.ALL, null, 1, 5); + int page = 1; + int limit = 5; + Call> call = getTrakt().sync().ratingsSeasons(RatingsFilter.ALL, null, page, limit); Response> response = executeCallWithoutReadingBody(call); - assertThat(response.headers().get("X-Pagination-Page-Count")).isNotEmpty(); - assertThat(response.headers().get("X-Pagination-Item-Count")).isNotEmpty(); + + assertPaginationHeaders(response, page, limit); } @Test @@ -428,10 +441,12 @@ public void test_ratingsEpisodes() throws IOException { @Test public void test_ratingsEpisodes_with_pagination() throws IOException { - Call> call = getTrakt().sync().ratingsEpisodes(RatingsFilter.ALL, null, 1, 2); + int page = 1; + int limit = 2; + Call> call = getTrakt().sync().ratingsEpisodes(RatingsFilter.ALL, null, page, limit); Response> response = executeCallWithoutReadingBody(call); - assertThat(response.headers().get("X-Pagination-Page-Count")).isNotEmpty(); - assertThat(response.headers().get("X-Pagination-Item-Count")).isNotEmpty(); + + assertPaginationHeaders(response, page, limit); } @Test @@ -514,36 +529,78 @@ public void test_watchlistMovies() throws IOException { assertSyncMovies(movies, "watchlist"); } + @Test + public void test_watchlistMovies_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistMovies(1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistMovies_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistMovies("title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); + } + @Test public void test_watchlistShows() throws IOException { List shows = executeCall(getTrakt().sync().watchlistShows(null)); - assertThat(shows).isNotNull(); - for (BaseShow show : shows) { - assertThat(show.show).isNotNull(); - assertThat(show.listed_at).isNotNull(); - } + assertWatchlistShows(shows); + } + + @Test + public void test_watchlistShows_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistShows(1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistShows_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistShows("title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); } @Test public void test_watchlistSeasons() throws IOException { List seasons = executeCall(getTrakt().sync().watchlistSeasons(null)); - assertThat(seasons).isNotNull(); - for (WatchlistedSeason season : seasons) { - assertThat(season.season).isNotNull(); - assertThat(season.show).isNotNull(); - assertThat(season.listed_at).isNotNull(); - } + assertWatchlistSeasons(seasons); + } + + @Test + public void test_watchlistSeasons_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistSeasons(1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistSeasons_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistSeasons("title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); } @Test public void test_watchlistEpisodes() throws IOException { List episodes = executeCall(getTrakt().sync().watchlistEpisodes(null)); - assertThat(episodes).isNotNull(); - for (WatchlistedEpisode episode : episodes) { - assertThat(episode.episode).isNotNull(); - assertThat(episode.show).isNotNull(); - assertThat(episode.listed_at).isNotNull(); - } + assertWatchlistEpisodes(episodes); + } + + @Test + public void test_watchlistEpisodes_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistEpisodes(1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistEpisodes_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().sync().watchlistEpisodes("title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); } @Test @@ -615,6 +672,60 @@ public void test_deleteItemsFromWatchlist() throws IOException { assertSyncResponseDelete(requestResponse); } + @Test + public void test_history() throws IOException { + List history = executeCall( + getTrakt().sync().history(null, null, null, null, null) + ); + assertHistory(history); + } + + @Test + public void test_history_withPagination() throws IOException { + int page = 1; + int limit = 5; + Call> call = getTrakt().sync().history(page, limit, null, null, null); + Response> response = executeCallWithoutReadingBody(call); + + assertPaginationHeaders(response, page, limit); + + List body = response.body(); + if (body == null) { + throw new IllegalStateException("Body should not be null for successful response"); + } + assertHistory(body); + } + + @Test + public void test_history_episodes() throws IOException { + List history = executeCall( + getTrakt().sync().history(HistoryType.EPISODES, null, null, null, null, null) + ); + + assertEpisodeHistory(history); + } + + @Test + public void test_history_movies() throws IOException { + List history = executeCall( + getTrakt().sync().history(HistoryType.MOVIES, null, null, null, null, null)); + + assertMovieHistory(history); + } + + @Test + public void test_history_item() throws IOException { + List history = executeCall( + getTrakt().sync().history(HistoryType.MOVIES, + TestData.MOVIE_WATCHED_TRAKT_ID, null, null, null, + OffsetDateTime.of(2016, 8, 3, 9, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2016, 8, 3, 10, 0, 0, 0, ZoneOffset.UTC)) + ); + + assertThat(history.size()).isGreaterThan(0); + assertMovieHistory(history); + } + private MovieIds buildMovieIds() { return MovieIds.tmdb(TestData.MOVIE_TMDB_ID); diff --git a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java index e63a4d1d..97deb739 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java @@ -67,6 +67,9 @@ import java.util.ArrayList; import java.util.List; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertEpisodeHistory; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertHistory; +import static com.uwetrottmann.trakt5.services.HistoryAssertions.assertMovieHistory; import static org.assertj.core.api.Assertions.assertThat; public class UsersTest extends BaseTestCase { @@ -102,13 +105,13 @@ public void test_profile() throws IOException { @Test public void test_collectionMovies() throws IOException { List movies = executeCall( - getTrakt().users().collectionMovies(TestData.USER_SLUG, null)); + getTrakt().users().collectionMovies(TestData.USER_SLUG, 1, 1000, null)); assertSyncMovies(movies, "collection"); } @Test public void test_collectionShows() throws IOException { - List shows = executeCall(getTrakt().users().collectionShows(TestData.USER_SLUG, null)); + List shows = executeCall(getTrakt().users().collectionShows(TestData.USER_SLUG, 1, 1000, null)); assertSyncShows(shows, "collection"); } @@ -187,7 +190,7 @@ public void test_createList() throws IOException { assertThat(createdList.sort_how).isEqualTo(SortHow.DESC); // Note: created list is always desc, even on web. // ...and delete it again - Response deleteResponse = getTrakt().users().deleteList(UserSlug.ME, + Response deleteResponse = getTrakt().users().deleteList(UserSlug.ME, String.valueOf(createdList.ids.trakt)).execute(); assertSuccessfulResponse(deleteResponse); assertThat(deleteResponse.code()).isEqualTo(204); @@ -210,9 +213,45 @@ public void test_updateList() throws IOException { @Test public void test_listItems() throws IOException { - List entries = executeCall(getTrakt().users().listItems(UserSlug.ME, - String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), - null)); + List entries = executeCall( + getTrakt().users().listItems(UserSlug.ME, + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), 1, 1000, null) + ); + assertListEntries(entries); + } + + @Test + public void test_listItems_sortOrder() throws IOException { + List entries = executeCall( + getTrakt().users().listItems(UserSlug.ME, + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), + "title", "asc", 1, 1000, null) + ); + assertListEntries(entries); + } + + @Test + public void test_listItems_type() throws IOException { + List entries = executeCall( + getTrakt().users().listItems(UserSlug.ME, + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), + "movie,show", 1, 1000, null) + ); + assertListEntries(entries); + } + + @Test + public void test_listItems_typeAndsortOrder() throws IOException { + List entries = executeCall( + getTrakt().users().listItems(UserSlug.ME, + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), + "movie,show", "title", "asc", 1, 1000, null) + ); + assertListEntries(entries); + } + + private static void assertListEntries(List entries) { + assertThat(entries).isNotEmpty(); for (ListEntry entry : entries) { assertThat(entry.listed_at).isNotNull(); assertThat(entry.id).isNotNull(); @@ -254,7 +293,7 @@ public void test_addListItems() throws IOException { @Test public void test_reorderListItems() throws IOException { List entries = executeCall(getTrakt().users().listItems(UserSlug.ME, - String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), 1, 1000, null)); // reverse order @@ -293,7 +332,7 @@ public void test_reorderLists() throws IOException { public void test_unfollowAndFollow() throws InterruptedException, IOException { // unfollow first UserSlug userToFollow = new UserSlug(TestData.USER_TO_FOLLOW); - Response response = getTrakt().users().unfollow(userToFollow).execute(); + Response response = getTrakt().users().unfollow(userToFollow).execute(); assertSuccessfulResponse(response); assertThat(response.code()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); @@ -333,72 +372,48 @@ public void test_friends() throws IOException { } @Test - public void test_historyEpisodesAndMovies() throws IOException { + public void test_history() throws IOException { List history = executeCall( getTrakt().users().history(TestData.USER_SLUG, 1, DEFAULT_PAGE_SIZE, null, null, null)); - for (HistoryEntry entry : history) { - assertThat(entry.id).isGreaterThan(0); - assertThat(entry.watched_at).isNotNull(); - assertThat(entry.action).isNotEmpty(); - assertThat(entry.type).isNotEmpty(); - if ("episode".equals(entry.type)) { - assertThat(entry.episode).isNotNull(); - assertThat(entry.show).isNotNull(); - } else if ("movie".equals(entry.type)) { - assertThat(entry.movie).isNotNull(); - } - } + assertHistory(history); } @Test - public void test_historyEpisodes() throws IOException { + public void test_history_episodes() throws IOException { List history = executeCall( getTrakt().users().history(TestData.USER_SLUG, HistoryType.EPISODES, 1, DEFAULT_PAGE_SIZE, null, null, null)); - for (HistoryEntry entry : history) { - assertThat(entry.id).isGreaterThan(0); - assertThat(entry.watched_at).isNotNull(); - assertThat(entry.action).isNotEmpty(); - assertThat(entry.type).isEqualTo("episode"); - assertThat(entry.episode).isNotNull(); - assertThat(entry.show).isNotNull(); - System.out.println( - "Episode watched at date: " + entry.watched_at + entry.watched_at.toInstant().toEpochMilli()); - } + + assertEpisodeHistory(history); } @Test - public void test_historyMovies() throws IOException { + public void test_history_movies() throws IOException { List history = executeCall( getTrakt().users().history(UserSlug.ME, HistoryType.MOVIES, 1, DEFAULT_PAGE_SIZE, null, null, null)); + assertMovieHistory(history); } @Test - public void test_historyItem() throws IOException { - List history = executeCall(getTrakt().users().history(UserSlug.ME, HistoryType.MOVIES, - TestData.MOVIE_WATCHED_TRAKT_ID, 1, - DEFAULT_PAGE_SIZE, null, - OffsetDateTime.of(2016, 8, 3, 9, 0, 0, 0, ZoneOffset.UTC), - OffsetDateTime.of(2016, 8, 3, 10, 0, 0, 0, ZoneOffset.UTC))); + public void test_history_item() throws IOException { + List history = executeCall( + getTrakt().users().history(UserSlug.ME, HistoryType.MOVIES, + TestData.MOVIE_WATCHED_TRAKT_ID, 1, + DEFAULT_PAGE_SIZE, null, + OffsetDateTime.of(2016, 8, 3, 9, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2016, 8, 3, 10, 0, 0, 0, ZoneOffset.UTC)) + ); + assertThat(history.size()).isGreaterThan(0); assertMovieHistory(history); } - private void assertMovieHistory(List history) { - for (HistoryEntry entry : history) { - assertThat(entry.watched_at).isNotNull(); - assertThat(entry.action).isNotEmpty(); - assertThat(entry.type).isEqualTo("movie"); - assertThat(entry.movie).isNotNull(); - } - } - @Test public void test_ratingsMovies() throws IOException { List ratedMovies = executeCall( @@ -449,38 +464,79 @@ public void test_watchlistMovies() throws IOException { assertSyncMovies(movies, "watchlist"); } + @Test + public void test_watchlistMovies_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistMovies(UserSlug.ME, 1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistMovies_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistMovies(UserSlug.ME, "title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); + } + @Test public void test_watchlistShows() throws IOException { - List shows = executeCall(getTrakt().users().watchlistShows(UserSlug.ME, - null)); - for (BaseShow show : shows) { - assertThat(show.show).isNotNull(); - assertThat(show.listed_at).isNotNull(); - } + List shows = executeCall(getTrakt().users().watchlistShows(UserSlug.ME, null)); + assertWatchlistShows(shows); + } + + @Test + public void test_watchlistShows_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistShows(UserSlug.ME, 1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistShows_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistShows(UserSlug.ME, "title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); } @Test public void test_watchlistSeasons() throws IOException { - List seasons = executeCall(getTrakt().users().watchlistSeasons(UserSlug.ME, - null)); - for (WatchlistedSeason season : seasons) { - assertThat(season.season).isNotNull(); - assertThat(season.show).isNotNull(); - assertThat(season.listed_at).isNotNull(); - } + List seasons = executeCall(getTrakt().users().watchlistSeasons(UserSlug.ME, null)); + assertWatchlistSeasons(seasons); + } + + @Test + public void test_watchlistSeasons_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistSeasons(UserSlug.ME, 1, 10, null)); + assertPaginationHeaders(response, 1, 10); + } + + @Test + public void test_watchlistSeasons_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistSeasons(UserSlug.ME, "title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); } @Test public void test_watchlistEpisodes() throws IOException { - List episodes = executeCall(getTrakt().users().watchlistEpisodes(UserSlug.ME, - null)); - for (WatchlistedEpisode episode : episodes) { - assertThat(episode.episode).isNotNull(); - assertThat(episode.show).isNotNull(); - assertThat(episode.listed_at).isNotNull(); - } + List episodes = executeCall(getTrakt().users().watchlistEpisodes(UserSlug.ME, null)); + assertWatchlistEpisodes(episodes); + } + + @Test + public void test_watchlistEpisodes_pagination() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistEpisodes(UserSlug.ME, 1, 10, null)); + assertPaginationHeaders(response, 1, 10); } + @Test + public void test_watchlistEpisodes_sortOrder() throws IOException { + Response> response = executeCallWithoutReadingBody( + getTrakt().users().watchlistEpisodes(UserSlug.ME, "title", "asc", null, null, null)); + assertSortOrderHeaders(response, "title", "asc"); + } @Test public void test_watchedMovies() throws IOException {