movies(
63 | @Path("keyword_id") Integer keywordId
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/ChangesService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.ChangeResultsPage;
7 | import com.uwetrottmann.tmdb2.entities.TmdbDate;
8 | import retrofit2.Call;
9 | import retrofit2.http.GET;
10 | import retrofit2.http.Query;
11 |
12 | public interface ChangesService {
13 | /**
14 | * Get a list of all of the movie ids that have been changed in the past 24 hours.
15 | *
16 | * You can query it for up to 14 days worth of changed IDs at a time with the start_date and end_date query parameters.
17 | * 100 items are returned per page.
18 | *
19 | * @param start_date Optional. Filter results with a start date.
20 | * @param end_date Optional. Filter results with an end date.
21 | */
22 | @GET("movie/changes")
23 | Call movie(
24 | @Query("start_date") TmdbDate start_date,
25 | @Query("end_date") TmdbDate end_date
26 | );
27 |
28 | /**
29 | * Get a list of all of the person ids that have been changed in the past 24 hours.
30 | *
31 | * You can query it for up to 14 days worth of changed IDs at a time with the start_date and end_date query parameters.
32 | * 100 items are returned per page.
33 | *
34 | * @param start_date Optional. Filter results with a start date.
35 | * @param end_date Optional. Filter results with an end date.
36 | */
37 | @GET("person/changes")
38 | Call person(
39 | @Query("start_date") TmdbDate start_date,
40 | @Query("end_date") TmdbDate end_date
41 | );
42 |
43 | /**
44 | * Get a list of all of the TV show ids that have been changed in the past 24 hours.
45 | *
46 | * You can query it for up to 14 days worth of changed IDs at a time with the start_date and end_date query parameters.
47 | * 100 items are returned per page.
48 | *
49 | * @param start_date Optional. Filter results with a start date.
50 | * @param end_date Optional. Filter results with an end date.
51 | */
52 | @GET("tv/changes")
53 | Call tv(
54 | @Query("start_date") TmdbDate start_date,
55 | @Query("end_date") TmdbDate end_date
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/services/ChangesServiceTest.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.BaseTestCase;
7 | import com.uwetrottmann.tmdb2.entities.ChangeResultsPage;
8 | import com.uwetrottmann.tmdb2.entities.TmdbDate;
9 | import org.junit.Test;
10 | import retrofit2.Call;
11 |
12 | import java.io.IOException;
13 |
14 | import static com.uwetrottmann.tmdb2.TestData.testMovieChangesEndDate;
15 | import static com.uwetrottmann.tmdb2.TestData.testMovieChangesStartDate;
16 | import static com.uwetrottmann.tmdb2.TestData.testPersonChangesEndDate;
17 | import static com.uwetrottmann.tmdb2.TestData.testPersonChangesStartDate;
18 | import static com.uwetrottmann.tmdb2.TestData.testTvShowChangesEndDate;
19 | import static com.uwetrottmann.tmdb2.TestData.testTvShowChangesStartDate;
20 | import static com.uwetrottmann.tmdb2.assertions.ChangeAssertions.assertChangeResultsPage;
21 |
22 | public class ChangesServiceTest extends BaseTestCase {
23 |
24 | @Test
25 | public void test_movie_changes() throws IOException {
26 | Call call = getUnauthenticatedInstance().changesService().movie(
27 | new TmdbDate(testMovieChangesStartDate),
28 | new TmdbDate(testMovieChangesEndDate)
29 | );
30 |
31 | ChangeResultsPage changeResultsPage = call.execute().body();
32 |
33 | assertChangeResultsPage(changeResultsPage);
34 | }
35 |
36 | @Test
37 | public void test_tv_changes() throws IOException {
38 | Call call = getUnauthenticatedInstance().changesService().tv(
39 | new TmdbDate(testTvShowChangesStartDate),
40 | new TmdbDate(testTvShowChangesEndDate)
41 | );
42 |
43 | ChangeResultsPage changeResultsPage = call.execute().body();
44 |
45 | assertChangeResultsPage(changeResultsPage);
46 | }
47 |
48 | @Test
49 | public void test_person_changes() throws IOException {
50 | Call call = getUnauthenticatedInstance().changesService().person(
51 | new TmdbDate(testPersonChangesStartDate),
52 | new TmdbDate(testPersonChangesEndDate)
53 | );
54 |
55 | ChangeResultsPage changeResultsPage = call.execute().body();
56 |
57 | assertChangeResultsPage(changeResultsPage);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/CompaniesService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.AppendToResponse;
7 | import com.uwetrottmann.tmdb2.entities.Company;
8 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
9 | import java.util.Map;
10 | import retrofit2.Call;
11 | import retrofit2.http.GET;
12 | import retrofit2.http.Path;
13 | import retrofit2.http.Query;
14 | import retrofit2.http.QueryMap;
15 |
16 | public interface CompaniesService {
17 | /**
18 | * Get the basic company information for a specific A Company TMDb id.
19 | *
20 | * @param companyId A Company TMDb id.
21 | */
22 | @GET("company/{company_id}")
23 | Call summary(
24 | @Path("company_id") int companyId
25 | );
26 |
27 | /**
28 | * Get the basic company information for a specific A Company TMDb id.
29 | *
30 | * @param companyId A Company TMDb id.
31 | * @param appendToResponse Optional. extra requests to append to the result. Accepted Value(s): movies
32 | */
33 | @GET("company/{company_id}")
34 | Call summary(
35 | @Path("company_id") int companyId,
36 | @Query("append_to_response") AppendToResponse appendToResponse
37 | );
38 |
39 | /**
40 | * Get the basic company information for a specific A Company TMDb id.
41 | *
42 | * @param companyId A Company TMDb id.
43 | * @param appendToResponse Optional. extra requests to append to the result. Accepted Value(s): movies
44 | * @param options Optional. parameters for the appended extra results.
45 | */
46 | @GET("company/{company_id}")
47 | Call summary(
48 | @Path("company_id") int companyId,
49 | @Query("append_to_response") AppendToResponse appendToResponse,
50 | @QueryMap Map options
51 | );
52 |
53 | /**
54 | * Get the movies for a specific A Company TMDb id.
55 | *
56 | * Is highly recommend using {@link DiscoverService#discoverMovie discoverMovie}
57 | * instead of this method as it is much more flexible.
58 | *
59 | * @param companyId A Company TMDb id.
60 | */
61 | @GET("company/{company_id}/movies")
62 | Call movies(
63 | @Path("company_id") int companyId
64 | );
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **[Pull requests](CONTRIBUTING.md) (e.g. support for more API endpoints, bug fixes) are welcome!**
2 |
3 | # tmdb-java
4 |
5 | tmdb-java is an inofficial wrapper around the [TMDB v3 API](https://developer.themoviedb.org/reference/intro/getting-started)
6 | using [retrofit 2](https://square.github.io/retrofit/).
7 |
8 | ## Usage
9 | [Available on Maven Central](https://central.sonatype.com/search?q=tmdb-java)
10 |
11 | Add the following dependency to your Gradle project:
12 |
13 | ```groovy
14 | implementation("com.uwetrottmann.tmdb2:tmdb-java:2.13.0")
15 | ```
16 |
17 | or your Maven project:
18 |
19 | ```xml
20 |
21 | com.uwetrottmann.tmdb2
22 | tmdb-java
23 | 2.13.0
24 |
25 | ```
26 |
27 | Use like any other retrofit2 based service. For example:
28 |
29 | ```java
30 | // Create an instance of the service you wish to use
31 | // you should re-use these
32 | Tmdb tmdb = new Tmdb(API_KEY);
33 | MoviesService moviesService = tmdb.moviesService();
34 | // Call any of the available endpoints
35 | try {
36 | Response response = moviesService
37 | .summary(550)
38 | .execute();
39 | if (response.isSuccessful()) {
40 | Movie movie = response.body();
41 | System.out.println(movie.title + " is awesome!");
42 | }
43 | } catch (Exception e) {
44 | // see execute() javadoc
45 | }
46 | ```
47 |
48 | See test cases in `src/test/` for more examples and the [retrofit website](https://square.github.io/retrofit/) for configuration options.
49 |
50 | ### Android
51 | This library ships Java 8 bytecode. This requires Android Gradle Plugin 3.2.x or newer.
52 |
53 | ## Proguard / R8
54 | It is likely not every method in this library is used, so it is probably useful to strip unused ones with Proguard.
55 | Apply the [Proguard rules for retrofit](https://square.github.io/retrofit/#download).
56 |
57 | The specific rules for this library are [already bundled](src/main/resources/META-INF/proguard/tmdb-java.pro) into the
58 | release which can be interpreted by R8 automatically, ProGuard users must manually add the rules.
59 |
60 | ## License
61 |
62 | This work by [Uwe Trottmann](https://www.uwetrottmann.com) and [contributors](https://github.com/UweTrottmann/tmdb-java/graphs/contributors)
63 | is licensed under the [Apache License 2.0](LICENSE.txt).
64 | Each relevant file contains an [SPDX short-form identifier](https://spdx.dev/learn/handling-license-info/)
65 | at the first possible line with the `Apache-2.0` value.
66 |
67 | For contributors and changes also see the Git history.
68 |
69 | Don't just copy, make it better.
70 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/CollectionsService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.AppendToResponse;
7 | import com.uwetrottmann.tmdb2.entities.Collection;
8 | import com.uwetrottmann.tmdb2.entities.Images;
9 | import retrofit2.Call;
10 | import retrofit2.http.GET;
11 | import retrofit2.http.Path;
12 | import retrofit2.http.Query;
13 | import retrofit2.http.QueryMap;
14 |
15 | import java.util.Map;
16 |
17 | public interface CollectionsService {
18 |
19 | /**
20 | * Get the basic collection information for a specific collection id.
21 | *
22 | * @param collectionId TMDb id.
23 | * @param language Optional. ISO 639-1 code.
24 | */
25 | @GET("collection/{collection_id}")
26 | Call summary(
27 | @Path("collection_id") int collectionId,
28 | @Query("language") String language
29 | );
30 |
31 | /**
32 | * Get the basic collection information for a specific collection id.
33 | *
34 | * @param collectionId TMDb id.
35 | * @param language Optional. ISO 639-1 code.
36 | * @param appendToResponse Optional. extra requests to append to the result.
37 | */
38 | @GET("collection/{collection_id}")
39 | Call summary(
40 | @Path("collection_id") int collectionId,
41 | @Query("language") String language,
42 | @Query("append_to_response") AppendToResponse appendToResponse
43 | );
44 |
45 | /**
46 | * Get the basic collection information for a specific collection id.
47 | *
48 | * @param collectionId TMDb id.
49 | * @param language Optional. ISO 639-1 code.
50 | * @param appendToResponse Optional. extra requests to append to the result.
51 | * @param options Optional. parameters for the appended extra results.
52 | */
53 | @GET("collection/{collection_id}")
54 | Call summary(
55 | @Path("collection_id") int collectionId,
56 | @Query("language") String language,
57 | @Query("append_to_response") AppendToResponse appendToResponse,
58 | @QueryMap Map options
59 | );
60 |
61 | /**
62 | * Get the images (posters and backdrops) for a specific collection id.
63 | *
64 | * @param collectionId TMDb id.
65 | * @param language Optional. ISO 639-1 code.
66 | */
67 | @GET("collection/{collection_id}/images")
68 | Call images(
69 | @Path("collection_id") int collectionId,
70 | @Query("language") String language
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/services/DiscoverServiceTest.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testMovieGenreRomance;
7 | import static com.uwetrottmann.tmdb2.TestData.testNetwork;
8 | import static com.uwetrottmann.tmdb2.TestData.testPersonCast;
9 | import static com.uwetrottmann.tmdb2.TestData.testPersonCrew;
10 | import static com.uwetrottmann.tmdb2.TestData.testTvGenreDrama;
11 | import static com.uwetrottmann.tmdb2.TestData.testTvGenreSciFi;
12 | import static com.uwetrottmann.tmdb2.assertions.MovieAssertions.assertMovieResultsPage;
13 | import static com.uwetrottmann.tmdb2.assertions.TvAssertions.assertTvShowResultsPage;
14 |
15 | import com.uwetrottmann.tmdb2.BaseTestCase;
16 | import com.uwetrottmann.tmdb2.entities.DiscoverFilter;
17 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
18 | import com.uwetrottmann.tmdb2.entities.TmdbDate;
19 | import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
20 | import com.uwetrottmann.tmdb2.enumerations.SortBy;
21 | import java.io.IOException;
22 | import org.junit.Test;
23 | import retrofit2.Call;
24 |
25 | public class DiscoverServiceTest extends BaseTestCase {
26 |
27 | @Test
28 | public void test_discover_movie() throws IOException {
29 | Call call = getUnauthenticatedInstance().discoverMovie()
30 | .page(1)
31 | .primary_release_date_gte(new TmdbDate("1990-01-01"))
32 | .sort_by(SortBy.RELEASE_DATE_DESC)
33 | .with_cast(new DiscoverFilter(testPersonCast.id))
34 | .with_crew(new DiscoverFilter(testPersonCrew.id))
35 | .without_genres(new DiscoverFilter(testMovieGenreRomance.id))
36 | // 'cult film', 'insomnia' or 'based on short story'
37 | .with_keywords(new DiscoverFilter(DiscoverFilter.Separator.OR, 34117, 4142, 156866))
38 | // filter seems broken in combination with others, temporarily disable
39 | // .with_release_type(new DiscoverFilter(DiscoverFilter.Separator.OR,
40 | // ReleaseType.THEATRICAL, ReleaseType.DIGITAL))
41 | .build();
42 | MovieResultsPage results = call.execute().body();
43 |
44 | assertMovieResultsPage(results);
45 | }
46 |
47 | @Test
48 | public void test_discover_tv() throws IOException {
49 | Call call = getUnauthenticatedInstance().discoverTv()
50 | .sort_by(SortBy.VOTE_AVERAGE_DESC)
51 | .with_genres(new DiscoverFilter(testTvGenreDrama.id, testTvGenreSciFi.id))
52 | .with_networks(new DiscoverFilter(testNetwork.id))
53 | .first_air_date_gte(new TmdbDate("2010-01-01"))
54 | .first_air_date_lte(new TmdbDate("2017-01-01"))
55 | .build();
56 | TvShowResultsPage results = call.execute().body();
57 |
58 | assertTvShowResultsPage(results);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/TmdbInterceptor.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2;
5 |
6 | import java.io.IOException;
7 | import java.util.List;
8 | import javax.annotation.Nonnull;
9 | import okhttp3.HttpUrl;
10 | import okhttp3.Interceptor;
11 | import okhttp3.Request;
12 | import okhttp3.Response;
13 |
14 | /**
15 | * {@link Interceptor} to add the API key query parameter and if available session information. As it modifies the URL
16 | * and may retry requests, ensure this is added as an application interceptor (never a network interceptor), otherwise
17 | * caching will be broken and requests will fail.
18 | */
19 | public class TmdbInterceptor implements Interceptor {
20 |
21 | private final Tmdb tmdb;
22 |
23 |
24 | public TmdbInterceptor(Tmdb tmdb) {
25 | this.tmdb = tmdb;
26 | }
27 |
28 | @Override
29 | public Response intercept(@Nonnull Chain chain) throws IOException {
30 | return handleIntercept(chain, tmdb);
31 | }
32 |
33 | /**
34 | * If the host matches {@link Tmdb#API_HOST} adds a query parameter with the API key.
35 | */
36 | public static Response handleIntercept(Chain chain, Tmdb tmdb) throws IOException {
37 | Request request = chain.request();
38 |
39 | if (!Tmdb.API_HOST.equals(request.url().host())) {
40 | // do not intercept requests for other hosts
41 | // this allows the interceptor to be used on a shared okhttp client
42 | return chain.proceed(request);
43 | }
44 |
45 | // add (or replace) the API key query parameter
46 | HttpUrl.Builder urlBuilder = request.url().newBuilder();
47 | urlBuilder.setEncodedQueryParameter(Tmdb.PARAM_API_KEY, tmdb.apiKey());
48 |
49 | if (tmdb.isLoggedIn()) {
50 | // add auth only for paths that require it
51 | List pathSegments = request.url().pathSegments();
52 | if ((pathSegments.size() >= 2 && pathSegments.get(1).equals("account"))
53 | || pathSegments.get(pathSegments.size() - 1).equals("account_states")
54 | || pathSegments.get(pathSegments.size() - 1).equals("rating")
55 | || !request.method().equals("GET")) {
56 | addSessionToken(tmdb, urlBuilder);
57 | }
58 | }
59 |
60 | Request.Builder builder = request.newBuilder();
61 | builder.url(urlBuilder.build());
62 | Response response = chain.proceed(builder.build());
63 |
64 | if (!response.isSuccessful()) {
65 | // re-try if the server indicates we should
66 | String retryHeader = response.header("Retry-After");
67 | if (retryHeader != null) {
68 | try {
69 | int retry = Integer.parseInt(retryHeader);
70 | Thread.sleep((int) ((retry + 0.5) * 1000));
71 |
72 | // close body of unsuccessful response
73 | if (response.body() != null) {
74 | response.body().close();
75 | }
76 | // is fine because, unlike a network interceptor, an application interceptor can re-try requests
77 | return handleIntercept(chain, tmdb);
78 | } catch (NumberFormatException | InterruptedException ignored) {
79 | }
80 | }
81 | }
82 |
83 | return response;
84 | }
85 |
86 | private static void addSessionToken(Tmdb tmdb, HttpUrl.Builder urlBuilder) {
87 | // prefer account session if both are available
88 | if (tmdb.getSessionId() != null) {
89 | urlBuilder.addEncodedQueryParameter(Tmdb.PARAM_SESSION_ID, tmdb.getSessionId());
90 | } else if (tmdb.getGuestSessionId() != null) {
91 | urlBuilder.addEncodedQueryParameter(Tmdb.PARAM_GUEST_SESSION_ID, tmdb.getGuestSessionId());
92 | }
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/SearchService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.CollectionResultsPage;
7 | import com.uwetrottmann.tmdb2.entities.CompanyResultsPage;
8 | import com.uwetrottmann.tmdb2.entities.KeywordResultsPage;
9 | import com.uwetrottmann.tmdb2.entities.MediaResultsPage;
10 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
11 | import com.uwetrottmann.tmdb2.entities.PersonResultsPage;
12 | import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
13 | import retrofit2.Call;
14 | import retrofit2.http.GET;
15 | import retrofit2.http.Query;
16 |
17 | public interface SearchService {
18 |
19 | /**
20 | * Search for companies.
21 | *
22 | * @see Documentation
23 | */
24 | @GET("search/company")
25 | Call company(
26 | @Query("query") String query,
27 | @Query("page") Integer page
28 | );
29 |
30 | /**
31 | * Search for collections.
32 | *
33 | * @see Documentation
34 | */
35 | @GET("search/collection")
36 | Call collection(
37 | @Query("query") String query,
38 | @Query("page") Integer page,
39 | @Query("language") String language
40 | );
41 |
42 | /**
43 | * Search for keywords.
44 | *
45 | * @see Documentation
46 | */
47 | @GET("search/keyword")
48 | Call keyword(
49 | @Query("query") String query,
50 | @Query("page") Integer page
51 | );
52 |
53 | /**
54 | * Search for movies.
55 | *
56 | * @see Documentation
57 | */
58 | @GET("search/movie")
59 | Call movie(
60 | @Query("query") String query,
61 | @Query("page") Integer page,
62 | @Query("language") String language,
63 | @Query("region") String region,
64 | @Query("include_adult") Boolean includeAdult,
65 | @Query("year") Integer year,
66 | @Query("primary_release_year") Integer primaryReleaseYear
67 | );
68 |
69 | /**
70 | * Search multiple models in a single request.
71 | * Multi search currently supports searching for movies,
72 | * tv shows and people in a single request.
73 | *
74 | * @see Documentation
75 | */
76 | @GET("search/multi")
77 | Call multi(
78 | @Query("query") String query,
79 | @Query("page") Integer page,
80 | @Query("language") String language,
81 | @Query("region") String region,
82 | @Query("include_adult") Boolean includeAdult
83 | );
84 |
85 | /**
86 | * Search for people.
87 | *
88 | * @see Documentation
89 | */
90 | @GET("search/person")
91 | Call person(
92 | @Query("query") String query,
93 | @Query("page") Integer page,
94 | @Query("language") String language,
95 | @Query("region") String region,
96 | @Query("include_adult") Boolean includeAdult
97 | );
98 |
99 | /**
100 | * Search for TV shows.
101 | *
102 | * @see Documentation
103 | */
104 | @GET("search/tv")
105 | Call tv(
106 | @Query("query") String query,
107 | @Query("page") Integer page,
108 | @Query("language") String language,
109 | @Query("first_air_date_year") Integer firstAirDateYear,
110 | @Query("include_adult") Boolean includeAdult
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/TmdbAuthenticator.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2;
5 |
6 | import com.uwetrottmann.tmdb2.entities.GuestSession;
7 | import com.uwetrottmann.tmdb2.entities.RequestToken;
8 | import com.uwetrottmann.tmdb2.entities.Session;
9 | import com.uwetrottmann.tmdb2.exceptions.TmdbAuthenticationFailedException;
10 | import com.uwetrottmann.tmdb2.services.AuthenticationService;
11 | import java.io.IOException;
12 | import javax.annotation.Nullable;
13 | import okhttp3.Authenticator;
14 | import okhttp3.HttpUrl;
15 | import okhttp3.Request;
16 | import okhttp3.Response;
17 | import okhttp3.Route;
18 |
19 | public class TmdbAuthenticator implements Authenticator {
20 |
21 | private final Tmdb tmdb;
22 |
23 | public TmdbAuthenticator(Tmdb tmdb) {
24 | this.tmdb = tmdb;
25 | }
26 |
27 | @Override
28 | public Request authenticate(@Nullable Route route, Response response) throws IOException {
29 | return handleRequest(response, tmdb);
30 | }
31 |
32 | @Nullable
33 | public static Request handleRequest(Response response, Tmdb tmdb) throws IOException {
34 | if (response.request().url().pathSegments().get(0).equals(Tmdb.PATH_AUTHENTICATION)) {
35 | return null;
36 | }
37 |
38 | if (responseCount(response) >= 2) {
39 | throw new TmdbAuthenticationFailedException(30,
40 | "Authentication failed: You do not have permissions to access the service.");
41 | }
42 |
43 | HttpUrl.Builder urlBuilder = response.request().url().newBuilder();
44 |
45 | // prefer account session if both are available
46 | if (tmdb.useAccountSession()) {
47 | if (tmdb.getUsername() == null || tmdb.getPassword() == null) {
48 | throw new TmdbAuthenticationFailedException(26, "You must provide a username and password.");
49 | }
50 | String session = acquireAccountSession(tmdb);
51 | if (session == null) {
52 | return null; // failed to retrieve session, give up
53 | }
54 | urlBuilder.setEncodedQueryParameter(Tmdb.PARAM_SESSION_ID, session);
55 | } else if (tmdb.useGuestSession()) {
56 | String session = acquireGuestSession(tmdb);
57 | if (session == null) {
58 | return null; // failed to retrieve session, give up
59 | }
60 | urlBuilder.setEncodedQueryParameter(Tmdb.PARAM_GUEST_SESSION_ID, tmdb.getGuestSessionId());
61 | } else {
62 | throw new TmdbAuthenticationFailedException(30,
63 | "Authentication failed: You do not have permissions to access the service.");
64 | }
65 |
66 | return response.request().newBuilder().url(urlBuilder.build()).build();
67 | }
68 |
69 | @Nullable
70 | public static String acquireAccountSession(Tmdb tmdb) throws IOException {
71 | AuthenticationService authService = tmdb.getRetrofit().create(AuthenticationService.class);
72 |
73 | RequestToken token = authService.requestToken().execute().body();
74 | if (token == null) {
75 | return null;
76 | }
77 |
78 | token = authService.validateToken(tmdb.getUsername(), tmdb.getPassword(), token.request_token).execute().body();
79 | if (token == null) {
80 | return null;
81 | }
82 |
83 | Session session = authService.createSession(token.request_token).execute().body();
84 | if (session == null) {
85 | return null;
86 | }
87 |
88 | tmdb.setSessionId(session.session_id);
89 | return session.session_id;
90 | }
91 |
92 | @Nullable
93 | public static String acquireGuestSession(Tmdb tmdb) throws IOException {
94 | AuthenticationService authService = tmdb.getRetrofit().create(AuthenticationService.class);
95 | GuestSession session = authService.createGuestSession().execute().body();
96 | if (session == null) {
97 | return null;
98 | }
99 |
100 | tmdb.setGuestSessionId(session.guest_session_id);
101 | return session.guest_session_id;
102 | }
103 |
104 | private static int responseCount(Response response) {
105 | int result = 1;
106 | while ((response = response.priorResponse()) != null) {
107 | result++;
108 | }
109 | return result;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/services/SearchServiceTest.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testCollection;
7 | import static com.uwetrottmann.tmdb2.TestData.testCompany;
8 | import static com.uwetrottmann.tmdb2.TestData.testMovie;
9 | import static com.uwetrottmann.tmdb2.TestData.testPerson;
10 | import static com.uwetrottmann.tmdb2.TestData.testTvShow;
11 | import static com.uwetrottmann.tmdb2.assertions.CollectionAssertions.assertCollectionResultsPage;
12 | import static com.uwetrottmann.tmdb2.assertions.CompanyAssertions.assertCompanyResultsPage;
13 | import static com.uwetrottmann.tmdb2.assertions.KeywordAssertions.assertKeywordResultsPage;
14 | import static com.uwetrottmann.tmdb2.assertions.MediaAssertions.assertMediaResultsPage;
15 | import static com.uwetrottmann.tmdb2.assertions.MovieAssertions.assertMovieResultsPage;
16 | import static com.uwetrottmann.tmdb2.assertions.PersonAssertions.assertPersonResultsPage;
17 | import static com.uwetrottmann.tmdb2.assertions.TvAssertions.assertTvShowResultsPage;
18 |
19 | import com.uwetrottmann.tmdb2.BaseTestCase;
20 | import com.uwetrottmann.tmdb2.entities.CollectionResultsPage;
21 | import com.uwetrottmann.tmdb2.entities.CompanyResultsPage;
22 | import com.uwetrottmann.tmdb2.entities.KeywordResultsPage;
23 | import com.uwetrottmann.tmdb2.entities.MediaResultsPage;
24 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
25 | import com.uwetrottmann.tmdb2.entities.PersonResultsPage;
26 | import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
27 | import java.io.IOException;
28 | import org.junit.Test;
29 | import retrofit2.Call;
30 |
31 | public class SearchServiceTest extends BaseTestCase {
32 |
33 | @Test
34 | public void test_companySearch() throws IOException {
35 | Call call = getUnauthenticatedInstance().searchService().company(
36 | testCompany.name,
37 | null
38 | );
39 |
40 | CompanyResultsPage companyResults = call.execute().body();
41 |
42 | assertCompanyResultsPage(companyResults);
43 | }
44 |
45 | @Test
46 | public void test_collectionSearch() throws IOException {
47 | Call call = getUnauthenticatedInstance().searchService().collection(
48 | testCollection.name,
49 | null,
50 | null
51 | );
52 | CollectionResultsPage collectionResults = call.execute().body();
53 |
54 | assertCollectionResultsPage(collectionResults);
55 | }
56 |
57 | @Test
58 | public void test_keywordSearch() throws IOException {
59 | Call call = getUnauthenticatedInstance().searchService().keyword(
60 | "fight",
61 | null
62 | );
63 | KeywordResultsPage keywordResults = call.execute().body();
64 |
65 | assertKeywordResultsPage(keywordResults);
66 | }
67 |
68 | @Test
69 | public void test_movieSearch() throws IOException {
70 | Call call = getUnauthenticatedInstance().searchService().movie(
71 | testMovie.title,
72 | null,
73 | null,
74 | null,
75 | null,
76 | null,
77 | null
78 | );
79 |
80 | MovieResultsPage movieResults = call.execute().body();
81 |
82 | assertMovieResultsPage(movieResults);
83 | }
84 |
85 | @Test
86 | public void test_personSearch() throws IOException {
87 | Call call = getUnauthenticatedInstance().searchService().person(
88 | testPerson.name,
89 | null,
90 | null,
91 | null,
92 | null
93 | );
94 | PersonResultsPage personResults = call.execute().body();
95 |
96 | assertPersonResultsPage(personResults);
97 | }
98 |
99 | @Test
100 | public void test_tv() throws IOException {
101 | Call call = getUnauthenticatedInstance().searchService().tv(
102 | testTvShow.name,
103 | null,
104 | null,
105 | null,
106 | null
107 | );
108 |
109 | TvShowResultsPage tvResults = call.execute().body();
110 |
111 | assertTvShowResultsPage(tvResults);
112 | }
113 |
114 | @Test
115 | public void test_multiSearch() throws IOException {
116 | Call call = getUnauthenticatedInstance().searchService().multi(
117 | "snowden",
118 | null,
119 | null,
120 | null,
121 | null
122 | );
123 |
124 | MediaResultsPage mediaResultsPage = call.execute().body();
125 |
126 | assertMediaResultsPage(mediaResultsPage);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/DiscoverService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.DiscoverFilter;
7 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
8 | import com.uwetrottmann.tmdb2.entities.TmdbDate;
9 | import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
10 | import com.uwetrottmann.tmdb2.enumerations.SortBy;
11 | import retrofit2.Call;
12 | import retrofit2.http.GET;
13 | import retrofit2.http.Query;
14 |
15 | public interface DiscoverService {
16 |
17 | /**
18 | * Discover movies by different types of data like average rating, number of votes, genres and certifications.
19 | *
20 | * @see Movie Discover
21 | */
22 | @GET("discover/movie")
23 | Call discoverMovie(
24 | @Query("language") String language,
25 | @Query("region") String region,
26 | @Query("sort_by") SortBy sort_by,
27 | @Query("certification_country") String certification_country,
28 | @Query("certification") String certification,
29 | @Query("certification_lte") String certification_lte,
30 | @Query("include_adult") Boolean include_adult,
31 | @Query("include_video") Boolean include_video,
32 | @Query("page") Integer page,
33 | @Query("primary_release_year") Integer primary_release_year,
34 | @Query("primary_release_date.gte") TmdbDate primary_release_date_gte,
35 | @Query("primary_release_date.lte") TmdbDate primary_release_date_lte,
36 | @Query("release_date.gte") TmdbDate release_date_gte,
37 | @Query("release_date.lte") TmdbDate release_date_lte,
38 | @Query("vote_count.gte") Integer vote_count_gte,
39 | @Query("vote_count.lte") Integer vote_count_lte,
40 | @Query("vote_average.gte") Float vote_average_gte,
41 | @Query("vote_average.lte") Float vote_average_lte,
42 | @Query("with_cast") DiscoverFilter with_cast,
43 | @Query("with_crew") DiscoverFilter with_crew,
44 | @Query("with_companies") DiscoverFilter with_companies,
45 | @Query("with_genres") DiscoverFilter with_genres,
46 | @Query("with_keywords") DiscoverFilter with_keywords,
47 | @Query("with_people") DiscoverFilter with_people,
48 | @Query("year") Integer year,
49 | @Query("without_genres") DiscoverFilter without_genres,
50 | @Query("with_runtime.gte") Integer with_runtime_gte,
51 | @Query("with_runtime.lte") Integer with_runtime_lte,
52 | @Query("with_release_type") DiscoverFilter with_release_type,
53 | @Query("with_original_language") String with_original_language,
54 | @Query("without_keywords") DiscoverFilter without_keywords,
55 | @Query("with_watch_providers") DiscoverFilter with_watch_providers,
56 | @Query("watch_region") String watch_region,
57 | @Query("with_watch_monetization_types") String with_watch_monetization_types
58 | );
59 |
60 | /**
61 | * Discover TV shows by different types of data like average rating, number of votes, genres, the network they aired
62 | * on and air dates.
63 | *
64 | * @see TV Discover
65 | */
66 | @GET("discover/tv")
67 | Call discoverTv(
68 | @Query("language") String language,
69 | @Query("sort_by") SortBy sort_by,
70 | @Query("air_date.gte") TmdbDate air_date_gte,
71 | @Query("air_date.lte") TmdbDate air_date_lte,
72 | @Query("first_air_date.gte") TmdbDate first_air_date_gte,
73 | @Query("first_air_date.lte") TmdbDate first_air_date_lte,
74 | @Query("first_air_date_year") Integer first_air_date_year,
75 | @Query("page") Integer page,
76 | @Query("timezone") String timezone,
77 | @Query("vote_average.gte") Float vote_average_gte,
78 | @Query("vote_average.lte") Float vote_average_lte,
79 | @Query("vote_count.gte") Integer vote_count_gte,
80 | @Query("vote_count.lte") Integer vote_count_lte,
81 | @Query("with_genres") DiscoverFilter with_genres,
82 | @Query("with_networks") DiscoverFilter with_networks,
83 | @Query("without_genres") DiscoverFilter without_genres,
84 | @Query("with_runtime.gte") Integer with_runtime_gte,
85 | @Query("with_runtime.lte") Integer with_runtime_lte,
86 | @Query("include_null_first_air_dates") Boolean include_null_first_air_dates,
87 | @Query("with_original_language") String with_original_language,
88 | @Query("without_keywords") DiscoverFilter without_keywords,
89 | @Query("screened_theatrically") Boolean screened_theatrically,
90 | @Query("with_companies") DiscoverFilter with_companies,
91 | @Query("with_keywords") DiscoverFilter with_keywords,
92 | @Query("with_watch_providers") DiscoverFilter with_watch_providers,
93 | @Query("watch_region") String watch_region,
94 | @Query("with_watch_monetization_types") String with_watch_monetization_types
95 | );
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/services/FindServiceTest.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testMovie;
7 | import static com.uwetrottmann.tmdb2.TestData.testPerson;
8 | import static com.uwetrottmann.tmdb2.TestData.testTvEpisode;
9 | import static com.uwetrottmann.tmdb2.TestData.testTvSeason;
10 | import static com.uwetrottmann.tmdb2.TestData.testTvShow;
11 | import static com.uwetrottmann.tmdb2.assertions.MovieAssertions.assertBaseMovie;
12 | import static com.uwetrottmann.tmdb2.assertions.PersonAssertions.assertBasePerson;
13 | import static com.uwetrottmann.tmdb2.assertions.TvAssertions.assertBaseTvSeason;
14 | import static com.uwetrottmann.tmdb2.assertions.TvAssertions.assertBaseTvShow;
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | import com.uwetrottmann.tmdb2.BaseTestCase;
18 | import com.uwetrottmann.tmdb2.entities.BaseMovie;
19 | import com.uwetrottmann.tmdb2.entities.BasePerson;
20 | import com.uwetrottmann.tmdb2.entities.BaseTvEpisode;
21 | import com.uwetrottmann.tmdb2.entities.BaseTvSeason;
22 | import com.uwetrottmann.tmdb2.entities.BaseTvShow;
23 | import com.uwetrottmann.tmdb2.entities.FindResults;
24 | import com.uwetrottmann.tmdb2.enumerations.ExternalSource;
25 | import java.io.IOException;
26 | import org.junit.Test;
27 | import retrofit2.Call;
28 |
29 | public class FindServiceTest extends BaseTestCase {
30 |
31 | @Test
32 | public void test_find_no_results() throws IOException {
33 | FindResults noResults = getUnauthenticatedInstance().findService()
34 | .find(1, ExternalSource.TVDB_ID, null)
35 | .execute()
36 | .body();
37 | assertThat(noResults).isNotNull();
38 | assertThat(noResults.movie_results).isNotNull();
39 | assertThat(noResults.person_results).isNotNull();
40 | assertThat(noResults.tv_results).isNotNull();
41 | assertThat(noResults.tv_episode_results).isNotNull();
42 | assertThat(noResults.tv_season_results).isNotNull();
43 | }
44 |
45 | @Test
46 | public void test_find_movie() throws IOException {
47 | Call call = getUnauthenticatedInstance().findService().find(
48 | testMovie.imdb_id,
49 | ExternalSource.IMDB_ID,
50 | null
51 | );
52 |
53 | FindResults results = call.execute().body();
54 |
55 | assertThat(results).isNotNull();
56 |
57 | assertThat(results.movie_results).isNotNull();
58 | assertThat(results.movie_results).isNotEmpty();
59 | for (BaseMovie movie : results.movie_results) {
60 | assertBaseMovie(movie);
61 | assertThat(movie.title).isEqualTo(testMovie.title);
62 | }
63 | }
64 |
65 | @Test
66 | public void test_find_people() throws IOException {
67 | Call call = getUnauthenticatedInstance().findService().find(
68 | testPerson.imdb_id,
69 | ExternalSource.IMDB_ID,
70 | null
71 | );
72 |
73 | FindResults results = call.execute().body();
74 |
75 | assertThat(results).isNotNull();
76 |
77 | assertThat(results.person_results).isNotNull();
78 | assertThat(results.person_results).isNotEmpty();
79 | for (BasePerson person : results.person_results) {
80 | assertBasePerson(person);
81 | assertThat(person.name).isEqualTo(testPerson.name);
82 | }
83 |
84 | }
85 |
86 | @Test
87 | public void test_find_tv_show() throws IOException {
88 | Call call = getUnauthenticatedInstance().findService().find(
89 | testTvShow.external_ids.imdb_id,
90 | ExternalSource.IMDB_ID,
91 | null
92 | );
93 |
94 | FindResults results = call.execute().body();
95 |
96 | assertThat(results).isNotNull();
97 |
98 | assertThat(results.tv_results).isNotNull();
99 | assertThat(results.tv_results).isNotEmpty();
100 | for (BaseTvShow tvShow : results.tv_results) {
101 | assertBaseTvShow(tvShow);
102 | assertThat(tvShow.name).isEqualTo(testTvShow.name);
103 | }
104 | }
105 |
106 | @Test
107 | public void test_find_tv_season() throws IOException {
108 | Call call = getUnauthenticatedInstance().findService().find(
109 | testTvSeason.external_ids.tvdb_id,
110 | ExternalSource.TVDB_ID,
111 | null
112 | );
113 |
114 | FindResults results = call.execute().body();
115 |
116 | assertThat(results).isNotNull();
117 |
118 | assertThat(results.tv_season_results).isNotNull();
119 | assertThat(results.tv_season_results).isNotEmpty();
120 | for (BaseTvSeason tvSeason : results.tv_season_results) {
121 | assertBaseTvSeason(tvSeason);
122 | assertThat(tvSeason.id).isEqualTo(testTvSeason.id);
123 | }
124 | }
125 |
126 | @Test
127 | public void test_find_tv_episode() throws IOException {
128 | Call call = getUnauthenticatedInstance().findService().find(
129 | testTvEpisode.external_ids.imdb_id,
130 | ExternalSource.IMDB_ID,
131 | null
132 | );
133 |
134 | FindResults results = call.execute().body();
135 | assertThat(results).isNotNull();
136 |
137 | assertThat(results.tv_episode_results).isNotNull();
138 | assertThat(results.tv_episode_results).isNotEmpty();
139 | for (BaseTvEpisode tvEpisode : results.tv_episode_results) {
140 | assertThat(tvEpisode.id).isEqualTo(testTvEpisode.id);
141 | }
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/assertions/MovieAssertions.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.assertions;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testProductionCompany;
7 | import static com.uwetrottmann.tmdb2.assertions.CompanyAssertions.assertBaseCompany;
8 | import static com.uwetrottmann.tmdb2.assertions.GenericAssertions.assertBaseResultsPage;
9 | import static com.uwetrottmann.tmdb2.assertions.GenericAssertions.assertSpokenLanguages;
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | import com.uwetrottmann.tmdb2.TestData;
13 | import com.uwetrottmann.tmdb2.entities.BaseCompany;
14 | import com.uwetrottmann.tmdb2.entities.BaseMovie;
15 | import com.uwetrottmann.tmdb2.entities.Movie;
16 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
17 | import com.uwetrottmann.tmdb2.entities.ReleaseDate;
18 | import com.uwetrottmann.tmdb2.entities.ReleaseDatesResult;
19 | import com.uwetrottmann.tmdb2.entities.ReleaseDatesResults;
20 |
21 | public class MovieAssertions {
22 | public static void assertBaseMovie(BaseMovie movie) {
23 | assertThat(movie).isNotNull();
24 | assertThat(movie.id).isNotNull();
25 | assertThat(movie.title).isNotNull();
26 | assertThat(movie.original_language).isNotEmpty();
27 | assertThat(movie.overview).isNotNull();
28 | assertThat(movie.adult).isNotNull();
29 | assertThat(movie.vote_average).isGreaterThanOrEqualTo(0);
30 | assertThat(movie.vote_count).isGreaterThanOrEqualTo(0);
31 |
32 | try {
33 | Movie _movie = (Movie) movie;
34 | assertThat(_movie.genres.get(0).id).isNotNull();
35 | assertThat(_movie.genres.get(0).name).isNotNull();
36 | } catch (Exception exc) {
37 | assertThat(movie.genre_ids).isNotNull();
38 | }
39 | }
40 |
41 |
42 | public static void assertMovie(Movie movie) {
43 | assertBaseMovie(movie);
44 | assertThat(movie.tagline).isNotEmpty();
45 | assertThat(movie.homepage).isNotNull();
46 | assertThat(movie.budget).isNotNull();
47 | assertThat(movie.imdb_id).isNotNull();
48 | assertThat(movie.belongs_to_collection).isNotNull();
49 | assertThat(movie.belongs_to_collection.id).isNotNull();
50 | assertThat(movie.belongs_to_collection.name).isNotNull();
51 | assertThat(movie.production_countries).isNotNull();
52 |
53 | for (BaseCompany company : movie.production_companies) {
54 | assertBaseCompany(company, false);
55 | }
56 |
57 | assertThat(movie.revenue).isNotNull();
58 | assertThat(movie.spoken_languages).isNotEmpty();
59 | assertSpokenLanguages(movie.spoken_languages);
60 | assertThat(movie.status).isNotNull();
61 | }
62 |
63 | public static void assertMovieDataIntegrity(Movie movie) {
64 | assertMovie(movie);
65 | assertThat(movie.budget).isGreaterThan(0);
66 | assertThat(movie.imdb_id).isEqualTo(TestData.testMovie.imdb_id);
67 | assertThat(movie.external_ids.wikidata_id).isEqualTo(TestData.testMovie.external_ids.wikidata_id);
68 | assertThat(movie.production_companies).isNotEmpty();
69 | assertThat(movie.production_companies.get(0).id).isEqualTo(testProductionCompany.id);
70 | assertThat(movie.production_companies.get(0).name).isEqualTo(testProductionCompany.name);
71 | assertThat(movie.production_countries).isNotEmpty();
72 | assertThat(movie.production_countries.get(0).iso_3166_1).isEqualTo("US");
73 | assertThat(movie.production_countries.get(0).name).isEqualTo("United States of America");
74 | assertThat(movie.revenue).isGreaterThan(0);
75 | assertThat(movie.runtime).isEqualTo(TestData.testMovie.runtime);
76 | assertThat(movie.spoken_languages.get(0).iso_639_1).isEqualTo("en");
77 | assertThat(movie.spoken_languages.get(0).name).isEqualTo("English");
78 | assertThat(movie.status).isEqualTo(TestData.testMovie.status);
79 | assertThat(movie.release_date).isEqualTo(TestData.testMovie.release_date);
80 | assertThat(movie.title).isEqualTo(TestData.testMovie.title);
81 | assertThat(movie.original_title).isEqualTo(TestData.testMovie.original_title);
82 | assertThat(movie.original_language).isEqualTo(TestData.testMovie.original_language);
83 | assertThat(movie.belongs_to_collection.name).isEqualTo(TestData.testCollection.name);
84 | assertThat(movie.belongs_to_collection.id).isEqualTo(TestData.testCollection.id);
85 | assertThat(movie.id).isEqualTo(TestData.testMovie.id);
86 | }
87 |
88 | public static void assertMovieReleaseDates(ReleaseDatesResults releaseDates) {
89 | assertThat(releaseDates).isNotNull();
90 | assertThat(releaseDates.results).isNotNull();
91 | assertThat(releaseDates.results).isNotEmpty();
92 |
93 | for (ReleaseDatesResult result : releaseDates.results) {
94 | assertThat(result.iso_3166_1).isNotNull();
95 | assertThat(result.release_dates).isNotNull();
96 | assertThat(result.release_dates).isNotEmpty();
97 | for (ReleaseDate releaseDate : result.release_dates) {
98 | assertThat(releaseDate.certification).isNotNull();
99 | assertThat(releaseDate.note).isNotNull();
100 | assertThat(releaseDate.release_date).isNotNull();
101 | assertThat(releaseDate.type).isNotNull();
102 | }
103 | }
104 | }
105 |
106 | public static void assertMovieResultsPage(MovieResultsPage movieResultsPage) {
107 | assertBaseResultsPage(movieResultsPage);
108 | for (BaseMovie baseMovie : movieResultsPage.results) {
109 | assertBaseMovie(baseMovie);
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/assertions/PersonAssertions.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.assertions;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testPerson;
7 | import static com.uwetrottmann.tmdb2.assertions.GenericAssertions.assertBaseResultsPage;
8 | import static com.uwetrottmann.tmdb2.assertions.MediaAssertions.assertMedia;
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | import com.uwetrottmann.tmdb2.TestData;
12 | import com.uwetrottmann.tmdb2.entities.BasePerson;
13 | import com.uwetrottmann.tmdb2.entities.BasePersonCredit;
14 | import com.uwetrottmann.tmdb2.entities.Media;
15 | import com.uwetrottmann.tmdb2.entities.Person;
16 | import com.uwetrottmann.tmdb2.entities.PersonCastCredit;
17 | import com.uwetrottmann.tmdb2.entities.PersonCredits;
18 | import com.uwetrottmann.tmdb2.entities.PersonCrewCredit;
19 | import com.uwetrottmann.tmdb2.entities.PersonExternalIds;
20 | import com.uwetrottmann.tmdb2.entities.PersonResultsPage;
21 | import java.util.List;
22 |
23 | public class PersonAssertions {
24 | public static void assertBasePerson(BasePerson person) {
25 | assertBasePerson(person, true);
26 | }
27 |
28 | public static void assertBasePerson(BasePerson person, Boolean extensive) {
29 | assertThat(person).isNotNull();
30 | assertThat(person.id).isPositive();
31 | assertThat(person.name).isNotNull();
32 |
33 | if (!extensive)
34 | return;
35 |
36 | assertThat(person.adult).isNotNull();
37 | if (person.popularity != null) {
38 | assertThat(person.popularity).isGreaterThanOrEqualTo(0.0);
39 | }
40 | try {
41 | Person p = (Person) person;
42 | } catch (Exception exc) {
43 | assertThat(person.known_for).isNotNull();
44 | for (Media media : person.known_for) {
45 | assertMedia(media);
46 | }
47 | }
48 | }
49 |
50 | public static void assertPerson(Person person) {
51 | assertBasePerson(person);
52 | assertThat(person.biography).isNotNull();
53 | assertThat(person.birthday).isNotNull();
54 | assertThat(person.gender).isNotNull();
55 | assertThat(person.imdb_id).isNotNull();
56 | assertThat(person.place_of_birth).isNotNull();
57 | assertThat(person.also_known_as).isNotNull();
58 | }
59 |
60 |
61 | public static void assertPersonDataIntegrity(Person person) {
62 | assertPerson(person);
63 | assertThat(person.name).isEqualTo(TestData.testPerson.name);
64 | assertThat(person.birthday).isEqualTo(TestData.testPerson.birthday);
65 | assertThat(person.id).isEqualTo(TestData.testPerson.id);
66 | assertThat(person.imdb_id).isEqualTo(TestData.testPerson.imdb_id);
67 | assertThat(person.gender).isEqualTo(TestData.testPerson.gender);
68 | assertThat(person.adult).isEqualTo(TestData.testPerson.adult);
69 | assertThat(person.place_of_birth).isEqualTo(TestData.testPerson.place_of_birth);
70 |
71 | }
72 |
73 | public static void assertTestPersonExternalIds(PersonExternalIds ids) {
74 | assertThat(ids.imdb_id).isEqualTo(testPerson.imdb_id);
75 | assertThat(ids.wikidata_id).isEqualTo(testPerson.external_ids.wikidata_id);
76 | }
77 |
78 | public static void assertPersonResultsPage(PersonResultsPage personResultsPage) {
79 | assertBaseResultsPage(personResultsPage);
80 | for (BasePerson basePerson : personResultsPage.results) {
81 | assertBasePerson(basePerson);
82 | }
83 | }
84 |
85 | public static void assertPersonCredits(PersonCredits credits) {
86 | assertThat(credits).isNotNull();
87 | assertPersonCastCredits(credits.cast);
88 | assertPersonCrewCredits(credits.crew);
89 | }
90 |
91 | public static void assertPersonCastCredits(List credits) {
92 | assertThat(credits).isNotNull();
93 | assertThat(credits).isNotEmpty();
94 | for (PersonCastCredit credit : credits) {
95 | assertBasePersonCredit(credit);
96 |
97 | assertThat(credit.character).isNotNull();
98 |
99 | switch (credit.media.media_type) {
100 | case TV:
101 | assertThat(credit.episode_count).isNotNull();
102 | }
103 |
104 | }
105 | }
106 |
107 | public static void assertPersonCrewCredits(List credits) {
108 | assertThat(credits).isNotNull();
109 | assertThat(credits).isNotEmpty();
110 | for (PersonCrewCredit credit : credits) {
111 | assertBasePersonCredit(credit);
112 |
113 | assertThat(credit.job).isNotNull();
114 | assertThat(credit.department).isNotNull();
115 | }
116 | }
117 |
118 |
119 | public static void assertBasePersonCredit(BasePersonCredit credit) {
120 | assertThat(credit.media.media_type).isNotNull();
121 | assertThat(credit.credit_id).isNotNull();
122 | assertThat(credit.media).isNotNull();
123 | switch (credit.media.media_type) {
124 | case MOVIE:
125 | assertThat(credit.media.movie).isNotNull();
126 | assertThat(credit.media.movie.id).isNotNull();
127 | assertThat(credit.media.movie.title).isNotNull();
128 | assertThat(credit.media.movie.original_title).isNotNull();
129 | break;
130 | case TV:
131 | assertThat(credit.media.tvShow).isNotNull();
132 | assertThat(credit.media.tvShow.id).isNotNull();
133 | assertThat(credit.media.tvShow.name).isNotNull();
134 | assertThat(credit.media.tvShow.original_name).isNotNull();
135 | break;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/com/uwetrottmann/tmdb2/services/ListsService.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import com.uwetrottmann.tmdb2.entities.List;
7 | import com.uwetrottmann.tmdb2.entities.ListCreateRequest;
8 | import com.uwetrottmann.tmdb2.entities.ListCreateResponse;
9 | import com.uwetrottmann.tmdb2.entities.ListItemStatus;
10 | import com.uwetrottmann.tmdb2.entities.ListOperation;
11 | import com.uwetrottmann.tmdb2.entities.Status;
12 | import retrofit2.Call;
13 | import retrofit2.http.Body;
14 | import retrofit2.http.DELETE;
15 | import retrofit2.http.GET;
16 | import retrofit2.http.POST;
17 | import retrofit2.http.Path;
18 | import retrofit2.http.Query;
19 |
20 | public interface ListsService {
21 |
22 | /**
23 | * Get the details of a list.
24 | *
25 | * @param listId A BaseList TMDb id.(String/Integer).
26 | */
27 | @GET("list/{list_id}")
28 | Call summary(
29 | @Path("list_id") String listId
30 | );
31 |
32 | /**
33 | * Get the details of a list.
34 | *
35 | * @param listId A BaseList TMDb id.(String/Integer).
36 | */
37 | @GET("list/{list_id}")
38 | Call summary(
39 | @Path("list_id") Integer listId
40 | );
41 |
42 | /**
43 | * Check if a movie is listed in a list.
44 | *
45 | * @param listId A BaseList TMDb id.(String/Integer).
46 | * @param movieId Movie Id.
47 | */
48 | @GET("list/{list_id}/item_status")
49 | Call itemStatus(
50 | @Path("list_id") String listId,
51 | @Query("movie_id") Integer movieId);
52 |
53 | /**
54 | * Check if a movie is listed in a list.
55 | *
56 | * @param listId A BaseList TMDb id.(String/Integer).
57 | * @param movieId Movie Id.
58 | */
59 | @GET("list/{list_id}/item_status")
60 | Call itemStatus(
61 | @Path("list_id") Integer listId,
62 | @Query("movie_id") Integer movieId);
63 |
64 | /**
65 | * Create a list.
66 | *
67 | * Requires an active Session.
68 | */
69 | @POST("list")
70 | Call createList(
71 | @Body ListCreateRequest request
72 | );
73 |
74 | /**
75 | * Add movie to list.
76 | *
77 | * Requires an active Session.
78 | *
79 | * @param listId A BaseList TMDb id.(String/Integer).
80 | * @param item The Body to send. ({@link com.uwetrottmann.tmdb2.entities.ListOperation ListOperation})
81 | */
82 | @POST("list/{list_id}/add_item")
83 | Call addMovie(
84 | @Path("list_id") String listId,
85 | @Body ListOperation item
86 | );
87 |
88 | /**
89 | * Add movie to list.
90 | *
91 | * Requires an active Session.
92 | *
93 | * @param listId A BaseList TMDb id.(String/Integer).
94 | * @param item The Body to send. ({@link com.uwetrottmann.tmdb2.entities.ListOperation ListOperation})
95 | */
96 | @POST("list/{list_id}/add_item")
97 | Call addMovie(
98 | @Path("list_id") Integer listId,
99 | @Body ListOperation item
100 | );
101 |
102 | /**
103 | * Remove a movie from a list.
104 | *
105 | * Requires an active Session.
106 | *
107 | * @param listId A BaseList TMDb id.(String/Integer).
108 | * @param item The Body to send. ({@link com.uwetrottmann.tmdb2.entities.ListOperation ListOperation})
109 | */
110 | @POST("list/{list_id}/remove_item")
111 | Call removeMovie(
112 | @Path("list_id") String listId,
113 | @Body ListOperation item
114 | );
115 |
116 | /**
117 | * Remove a movie from a list.
118 | *
119 | * Requires an active Session.
120 | *
121 | * @param listId A BaseList TMDb id.(String/Integer).
122 | * @param item The Body to send. ({@link com.uwetrottmann.tmdb2.entities.ListOperation ListOperation})
123 | */
124 | @POST("list/{list_id}/remove_item")
125 | Call removeMovie(
126 | @Path("list_id") Integer listId,
127 | @Body ListOperation item
128 | );
129 |
130 | /**
131 | * Clear all of the items from a list.
132 | *
133 | * Requires an active Session.
134 | *
135 | * @param listId A BaseList TMDb id.(String/Integer).
136 | * @param confirm Confirmation (Boolean).
137 | */
138 | @POST("list/{list_id}/clear")
139 | Call clear(
140 | @Path("list_id") String listId,
141 | @Query("confirm") Boolean confirm
142 | );
143 |
144 | /**
145 | * Clear all of the items from a list.
146 | *
147 | * Requires an active Session.
148 | *
149 | * @param listId A BaseList TMDb id.(String/Integer).
150 | * @param confirm Confirmation (Boolean).
151 | */
152 | @POST("list/{list_id}/clear")
153 | Call clear(
154 | @Path("list_id") Integer listId,
155 | @Query("confirm") Boolean confirm
156 | );
157 |
158 | /**
159 | * Delete a list.
160 | *
161 | * Requires an active Session.
162 | *
163 | * @param listId A BaseList TMDb id.(String/Integer).
164 | */
165 | @DELETE("list/{list_id}")
166 | Call delete(
167 | @Path("list_id") String listId
168 | );
169 |
170 |
171 | /**
172 | * Delete a list.
173 | *
174 | * Requires an active Session.
175 | *
176 | * @param listId A BaseList TMDb id.(String/Integer).
177 | */
178 | @DELETE("list/{list_id}")
179 | Call delete(
180 | @Path("list_id") Integer listId
181 | );
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/src/test/java/com/uwetrottmann/tmdb2/services/GuestSessionTest.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | // Copyright 2025 Uwe Trottmann
3 |
4 | package com.uwetrottmann.tmdb2.services;
5 |
6 | import static com.uwetrottmann.tmdb2.TestData.testMovie;
7 | import static com.uwetrottmann.tmdb2.TestData.testTvEpisode;
8 | import static com.uwetrottmann.tmdb2.TestData.testTvSeason;
9 | import static com.uwetrottmann.tmdb2.TestData.testTvShow;
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | import com.uwetrottmann.tmdb2.BaseTestCase;
13 | import com.uwetrottmann.tmdb2.entities.MovieResultsPage;
14 | import com.uwetrottmann.tmdb2.entities.RatingObject;
15 | import com.uwetrottmann.tmdb2.entities.Status;
16 | import com.uwetrottmann.tmdb2.entities.TvEpisodeResultsPage;
17 | import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
18 | import java.io.IOException;
19 | import org.junit.After;
20 | import org.junit.Before;
21 | import org.junit.Ignore;
22 | import org.junit.Test;
23 | import retrofit2.Call;
24 |
25 | @Ignore("Run these tests manually to not risk getting blocked by TMDB servers")
26 | public class GuestSessionTest extends BaseTestCase {
27 |
28 | @Before
29 | public void turnOnGuestSession() {
30 | getAuthenticatedInstance().guestSession();
31 | }
32 |
33 | @After
34 | public void cleanUpGuestSession() {
35 | getAuthenticatedInstance().clearSessions();
36 | }
37 |
38 | @Test
39 | public void test_modify_rating_movie() throws IOException, InterruptedException {
40 | RatingObject obj = new RatingObject();
41 | obj.value = 5D;
42 |
43 | Call call = getAuthenticatedInstance().moviesService().addRating(testMovie.id, obj);
44 | Status status = call.execute().body();
45 | assertThat(status).isNotNull();
46 | assertThat(status.status_code).isIn(1, 12);
47 |
48 | // Wait until the next request to let the servers catch up
49 | Thread.sleep(1000);
50 |
51 | MovieResultsPage movieResultsPage = getAuthenticatedInstance().guestSessionService().ratedMovies(
52 | getAuthenticatedInstance().getGuestSessionId(),
53 | null,
54 | null
55 | ).execute().body();
56 | assertThat(movieResultsPage).isNotNull();
57 | assertThat(movieResultsPage.page).isNotNull();
58 | assertThat(movieResultsPage.total_pages).isNotNull();
59 | assertThat(movieResultsPage.total_results).isNotNull();
60 | assertThat(movieResultsPage.results).isNotNull();
61 |
62 | // Wait until the next request to let the servers catch up
63 | Thread.sleep(1000);
64 |
65 | call = getAuthenticatedInstance().moviesService().deleteRating(testMovie.id);
66 | status = call.execute().body();
67 | assertThat(status).isNotNull();
68 | assertThat(status.status_code).isEqualTo(13);
69 | }
70 |
71 | @Test
72 | public void test_modify_rating_tvEpisode() throws IOException, InterruptedException {
73 | RatingObject obj = new RatingObject();
74 | obj.value = 5D;
75 |
76 | Call call = getAuthenticatedInstance().tvEpisodesService().addRating(testTvShow.id,
77 | testTvSeason.season_number, testTvEpisode.episode_number, obj);
78 | Status status = call.execute().body();
79 | assertThat(status).isNotNull();
80 | assertThat(status.status_code).isIn(1, 12);
81 |
82 | // Wait until the next request to let the servers catch up
83 | Thread.sleep(1000);
84 |
85 | TvEpisodeResultsPage tvEpisodeResultsPage = getAuthenticatedInstance().guestSessionService().ratedTvEpisodes(
86 | getAuthenticatedInstance().getGuestSessionId(),
87 | null,
88 | null
89 | ).execute().body();
90 |
91 | assertThat(tvEpisodeResultsPage).isNotNull();
92 | assertThat(tvEpisodeResultsPage.page).isNotNull();
93 | assertThat(tvEpisodeResultsPage.total_pages).isNotNull();
94 | assertThat(tvEpisodeResultsPage.total_results).isNotNull();
95 | assertThat(tvEpisodeResultsPage.results).isNotNull();
96 |
97 | // Wait until the next request to let the servers catch up
98 | Thread.sleep(1000);
99 |
100 | call = getAuthenticatedInstance().tvEpisodesService().deleteRating(testTvShow.id, testTvSeason.season_number,
101 | testTvEpisode.episode_number);
102 | status = call.execute().body();
103 | assertThat(status).isNotNull();
104 | assertThat(status.status_code).isEqualTo(13);
105 | }
106 |
107 |
108 | @Test
109 | public void test_modify_rating_tvShow() throws IOException, InterruptedException {
110 | RatingObject obj = new RatingObject();
111 | obj.value = 5D;
112 |
113 | Call call = getAuthenticatedInstance().tvService().addRating(testTvShow.id, obj);
114 | Status status = call.execute().body();
115 | assertThat(status).isNotNull();
116 | assertThat(status.status_code).isIn(1, 12);
117 |
118 | // Wait until the next request to let the servers catch up
119 | Thread.sleep(1000);
120 |
121 | TvShowResultsPage tvShowResultsPage = getAuthenticatedInstance().guestSessionService().ratedTvShows(
122 | getAuthenticatedInstance().getGuestSessionId(),
123 | null,
124 | null
125 | ).execute().body();
126 |
127 | assertThat(tvShowResultsPage).isNotNull();
128 | assertThat(tvShowResultsPage.page).isNotNull();
129 | assertThat(tvShowResultsPage.total_pages).isNotNull();
130 | assertThat(tvShowResultsPage.total_results).isNotNull();
131 | assertThat(tvShowResultsPage.results).isNotNull();
132 |
133 | // Wait until the next request to let the servers catch up
134 | Thread.sleep(1000);
135 |
136 | call = getAuthenticatedInstance().tvService().deleteRating(testTvShow.id);
137 | status = call.execute().body();
138 | assertThat(status).isNotNull();
139 | assertThat(status.status_code).isEqualTo(13);
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------