diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd8cf26..64fc3d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog + +## Next release + +* Add variants of watched methods that have an option to include special episodes. Deprecated the original variants. ## 6.19.0 - 2026-05-08 diff --git a/src/main/java/com/uwetrottmann/trakt5/TraktV2.java b/src/main/java/com/uwetrottmann/trakt5/TraktV2.java index 113b923b..57495e4e 100644 --- a/src/main/java/com/uwetrottmann/trakt5/TraktV2.java +++ b/src/main/java/com/uwetrottmann/trakt5/TraktV2.java @@ -81,6 +81,8 @@ public class TraktV2 { public static final String HEADER_TRAKT_API_VERSION = "trakt-api-version"; public static final String HEADER_TRAKT_API_KEY = "trakt-api-key"; + public static final String QUERY_PARAM_SPECIALS = "specials"; + @Nullable private OkHttpClient okHttpClient; @Nullable private Retrofit retrofit; diff --git a/src/main/java/com/uwetrottmann/trakt5/enums/Specials.java b/src/main/java/com/uwetrottmann/trakt5/enums/Specials.java new file mode 100644 index 00000000..8e931cfd --- /dev/null +++ b/src/main/java/com/uwetrottmann/trakt5/enums/Specials.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Option to include special episodes for the watched shows and watched episodes endpoint. + */ +public class Specials implements TraktEnum { + + /** + * Return special episodes ("season 0"). + *

+ * This option is not documented, but according to the GitHub issue "sync/watched/shows no returning "specials" + * episodes ?" required. + */ + public static final Specials TRUE = new Specials("true"); + + private final String value; + + private Specials(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java index 1075c4a5..ed29addd 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java @@ -38,6 +38,7 @@ import com.uwetrottmann.trakt5.enums.HistoryType; import com.uwetrottmann.trakt5.enums.PlaybackType; import com.uwetrottmann.trakt5.enums.RatingsFilter; +import com.uwetrottmann.trakt5.enums.Specials; import org.threeten.bp.OffsetDateTime; import retrofit2.Call; import retrofit2.http.Body; @@ -286,7 +287,10 @@ Call> watchedShows( * OAuth {@link TraktV2#accessToken(String) access token} required *

* Like {@link Users#watchedShows(UserSlug, int, int, ExtendedShowsWatched)}. + * + * @deprecated Use {@link #watchedShows(int, int, ExtendedShowsWatched, Specials)} instead. */ + @Deprecated @GET("sync/watched/shows") Call> watchedShows( @Query("page") int page, @@ -294,17 +298,45 @@ Call> watchedShows( @Query(value = "extended", encoded = true) ExtendedShowsWatched extended ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link Users#watchedShows(UserSlug, int, int, ExtendedShowsWatched, Specials)}. + */ + @GET("sync/watched/shows") + Call> watchedShows( + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) ExtendedShowsWatched extended, + @Query(value = TraktV2.QUERY_PARAM_SPECIALS, encoded = true) Specials specials + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} required *

* Like {@link Users#watchedShowsMin(UserSlug, int, int)}. + * + * @deprecated Use {@link #watchedShowsMin(int, int, Specials)} instead. */ + @Deprecated @GET("sync/watched/shows?extended=min") Call>>>> watchedShowsMin( @Query("page") int page, @Query("limit") int limit ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Like {@link Users#watchedShowsMin(UserSlug, int, int, Specials)}. + */ + @GET("sync/watched/shows?extended=min") + Call>>>> watchedShowsMin( + @Query("page") int page, + @Query("limit") int limit, + @Query(value = TraktV2.QUERY_PARAM_SPECIALS, encoded = true) Specials specials + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} required *

@@ -312,13 +344,31 @@ Call>>>> watchedShowsMi * * @param page Number of page of results to be returned. * @param limit Number of results to return per page. + * + * @deprecated Use {@link #watchedEpisodes(int, int, Specials)} instead. */ + @Deprecated @GET("sync/watched/episodes") Call> watchedEpisodes( @Query("page") int page, @Query("limit") int limit ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Returns all episodes a user has watched. + * + * @param page Number of page of results to be returned. + * @param limit Number of results to return per page. + */ + @GET("sync/watched/episodes") + Call> watchedEpisodes( + @Query("page") int page, + @Query("limit") int limit, + @Query(value = TraktV2.QUERY_PARAM_SPECIALS, encoded = true) Specials specials + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} required *

@@ -334,13 +384,39 @@ Call> watchedEpisodes( * ] * } * + * + * @deprecated Use {@link #watchedEpisodesMin(int, int, Specials)} instead. */ + @Deprecated @GET("sync/watched/episodes?extended=min") Call>> watchedEpisodesMin( @Query("page") int page, @Query("limit") int limit ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

+ * Returns episode Trakt IDs mapped to a list of watched at timestamps. + *

+ * An example for an equivalent JSON for a single episode with three watched at timestamps: + *

+     * {
+     *   "73482": [
+     *     "2015-02-10T09:04:00.000Z",
+     *     "2015-02-10T15:42:00.000Z",
+     *     "2015-02-24T05:51:00.000Z"
+     *   ]
+     * }
+     * 
+ */ + @GET("sync/watched/episodes?extended=min") + Call>> watchedEpisodesMin( + @Query("page") int page, + @Query("limit") int limit, + @Query(value = TraktV2.QUERY_PARAM_SPECIALS, encoded = true) Specials specials + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} required *

diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Users.java b/src/main/java/com/uwetrottmann/trakt5/services/Users.java index 92ab2a0e..3bd55251 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Users.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Users.java @@ -44,6 +44,7 @@ import com.uwetrottmann.trakt5.enums.ExtendedShowsWatched; import com.uwetrottmann.trakt5.enums.HistoryType; import com.uwetrottmann.trakt5.enums.RatingsFilter; +import com.uwetrottmann.trakt5.enums.Specials; import org.threeten.bp.OffsetDateTime; import retrofit2.Call; import retrofit2.http.Body; @@ -850,7 +851,10 @@ Call> watchedShows( * @param userSlug Example: "sean". * @param page Number of page of results to be returned. * @param limit Number of results to return per page. + * + * @deprecated Use {@link #watchedShows(UserSlug, int, int, ExtendedShowsWatched, Specials)} instead. */ + @Deprecated @GET("users/{username}/watched/shows") Call> watchedShows( @Path("username") UserSlug userSlug, @@ -859,6 +863,24 @@ Call> watchedShows( @Query(value = "extended", encoded = true) ExtendedShowsWatched extended ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Returns all shows a user has watched sorted by most plays. + * + * @param userSlug Example: "sean". + * @param page Number of page of results to be returned. + * @param limit Number of results to return per page. + */ + @GET("users/{username}/watched/shows") + Call> watchedShows( + @Path("username") UserSlug userSlug, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = "extended", encoded = true) ExtendedShowsWatched extended, + @Query(value = "specials", encoded = true) Specials specials + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} optional *

@@ -879,7 +901,10 @@ Call> watchedShows( * } * } * + * + * @deprecated Use {@link #watchedShowsMin(UserSlug, int, int, Specials)} instead. */ + @Deprecated @GET("users/{username}/watched/shows?extended=min") Call>>>> watchedShowsMin( @Path("username") UserSlug userSlug, @@ -887,4 +912,33 @@ Call>>>> watchedShowsMi @Query("limit") int limit ); + /** + * OAuth {@link TraktV2#accessToken(String) access token} optional + *

+ * Returns show Trakt IDs mapped to season Trakt IDs, mapped to episode Trakt IDs mapped to a list of watched at + * timestamps. + *

+ * An example for an equivalent JSON for a single show, season and episode with three watched at timestamps: + *

+     * {
+     *   "77712": {
+     *     "95726": {
+     *       "1498291": [
+     *         "2026-04-29T17:54:00.000Z",
+     *         "2026-04-29T17:57:00.000Z",
+     *         "2026-05-08T02:38:00.000Z"
+     *       ]
+     *     }
+     *   }
+     * }
+     * 
+ */ + @GET("users/{username}/watched/shows?extended=min") + Call>>>> watchedShowsMin( + @Path("username") UserSlug userSlug, + @Query("page") int page, + @Query("limit") int limit, + @Query(value = TraktV2.QUERY_PARAM_SPECIALS, encoded = true) Specials specials + ); + } diff --git a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java index 3c1a4aec..19187bba 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java @@ -52,6 +52,7 @@ import com.uwetrottmann.trakt5.enums.HistoryType; import com.uwetrottmann.trakt5.enums.Rating; import com.uwetrottmann.trakt5.enums.RatingsFilter; +import com.uwetrottmann.trakt5.enums.Specials; import org.junit.Test; import org.threeten.bp.Instant; import org.threeten.bp.OffsetDateTime; @@ -357,7 +358,7 @@ public void test_watchedMoviesMin() throws IOException { @Test public void test_watchedShows() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchedShows(PAGE_ONE, LIMIT_MAX, ExtendedShowsWatched.PROGRESS)); + getTrakt().sync().watchedShows(PAGE_ONE, LIMIT_MAX, ExtendedShowsWatched.PROGRESS, Specials.TRUE)); assertListPaginationHeaders(response); assertSyncShows(response.body(), "watched"); @@ -368,7 +369,8 @@ public void test_watchedShows_extended() throws IOException { // Note: Starting 2026-05-30 these extended values will be the default List shows = executeCall( getTrakt().sync().watchedShows(PAGE_ONE, LIMIT_MAX, - ExtendedShowsWatched.of(ExtendedShowsWatched.FULL, ExtendedShowsWatched.NOSEASONS))); + ExtendedShowsWatched.of(ExtendedShowsWatched.FULL, ExtendedShowsWatched.NOSEASONS), + Specials.TRUE)); assertWatchedShowsFullNoSeasons(shows); } @@ -376,7 +378,7 @@ public void test_watchedShows_extended() throws IOException { @Test public void test_watchedShowsMin() throws IOException { Response>>>> response = executeCallWithoutReadingBody( - getTrakt().sync().watchedShowsMin(PAGE_ONE, LIMIT_MAX)); + getTrakt().sync().watchedShowsMin(PAGE_ONE, LIMIT_MAX, Specials.TRUE)); // As of 2026-05-08, pagination is not supported, yet // assertListPaginationHeaders(response); @@ -387,7 +389,7 @@ public void test_watchedShowsMin() throws IOException { @Test public void test_watchedEpisodes() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchedEpisodes(PAGE_ONE, LIMIT_MAX)); + getTrakt().sync().watchedEpisodes(PAGE_ONE, LIMIT_MAX, Specials.TRUE)); assertListPaginationHeaders(response); assertThat(response.body()) @@ -401,7 +403,7 @@ public void test_watchedEpisodes() throws IOException { @Test public void test_watchedEpisodesMin() throws IOException { Response>> response = executeCallWithoutReadingBody( - getTrakt().sync().watchedEpisodesMin(PAGE_ONE, LIMIT_MAX)); + getTrakt().sync().watchedEpisodesMin(PAGE_ONE, LIMIT_MAX, Specials.TRUE)); // As of 2026-05-08, pagination is not supported, yet // assertListPaginationHeaders(response); diff --git a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java index d5db2811..031c9a18 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java @@ -57,6 +57,7 @@ import com.uwetrottmann.trakt5.enums.RatingsFilter; import com.uwetrottmann.trakt5.enums.SortBy; import com.uwetrottmann.trakt5.enums.SortHow; +import com.uwetrottmann.trakt5.enums.Specials; import org.junit.Ignore; import org.junit.Test; import org.threeten.bp.LocalTime; @@ -575,7 +576,7 @@ public void test_watchedMoviesMin() throws IOException { public void test_watchedShows() throws IOException { Response> response = executeCallWithoutReadingBody( getTrakt().users().watchedShows(TestData.USER_SLUG, PAGE_ONE, LIMIT_MAX, - ExtendedShowsWatched.PROGRESS)); + ExtendedShowsWatched.PROGRESS, Specials.TRUE)); assertListPaginationHeaders(response); assertSyncShows(response.body(), "watched"); @@ -586,7 +587,8 @@ public void test_watchedShows_extended() throws IOException { // Note: Starting 2026-05-30 these extended values will be the default List shows = executeCall( getTrakt().users().watchedShows(TestData.USER_SLUG, PAGE_ONE, LIMIT_MAX, - ExtendedShowsWatched.of(ExtendedShowsWatched.FULL, ExtendedShowsWatched.NOSEASONS))); + ExtendedShowsWatched.of(ExtendedShowsWatched.FULL, ExtendedShowsWatched.NOSEASONS), + Specials.TRUE)); assertWatchedShowsFullNoSeasons(shows); } @@ -594,7 +596,7 @@ public void test_watchedShows_extended() throws IOException { @Test public void test_watchedShowsMin() throws IOException { Response>>>> response = executeCallWithoutReadingBody( - getTrakt().users().watchedShowsMin(TestData.USER_SLUG, PAGE_ONE, LIMIT_MAX)); + getTrakt().users().watchedShowsMin(TestData.USER_SLUG, PAGE_ONE, LIMIT_MAX, Specials.TRUE)); // As of 2026-05-08, pagination is not supported, yet // assertListPaginationHeaders(response);