From 3c152e04d1a8fa60cfcca8dd1d81f0ded5d1c54b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 9 Dec 2023 10:54:29 -0700 Subject: [PATCH] Support showing content ratings for TmdbProvider (#705) * Support showing content ratings for TmdbProvider --- .../com/lagradost/cloudstream3/MainAPI.kt | 164 +++++++++++++++++- .../metaproviders/TmdbProvider.kt | 24 +++ .../ui/result/ResultFragmentPhone.kt | 6 + .../ui/result/ResultFragmentTv.kt | 6 + .../ui/result/ResultViewModel2.kt | 2 + app/src/main/res/layout/fragment_result.xml | 8 +- .../main/res/layout/fragment_result_tv.xml | 8 +- 7 files changed, 213 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 35a628a3..cf2e1cf0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -1193,6 +1193,7 @@ interface LoadResponse { var syncData: MutableMap var posterHeaders: Map? var backgroundPosterUrl: String? + var contentRating: String? companion object { private val malIdPrefix = malApi.idPrefix @@ -1511,7 +1512,37 @@ data class TorrentLoadResponse( override var syncData: MutableMap = mutableMapOf(), override var posterHeaders: Map? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse + override var contentRating: String? = null, +) : LoadResponse { + /** + * Secondary constructor for backwards compatibility without contentRating. + * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. + */ + constructor( + name: String, + url: String, + apiName: String, + magnet: String?, + torrent: String?, + plot: String?, + type: TvType = TvType.Torrent, + posterUrl: String? = null, + year: Int? = null, + rating: Int? = null, + tags: List? = null, + duration: Int? = null, + trailers: MutableList = mutableListOf(), + recommendations: List? = null, + actors: List? = null, + comingSoon: Boolean = false, + syncData: MutableMap = mutableMapOf(), + posterHeaders: Map? = null, + backgroundPosterUrl: String? = null, + ) : this( + name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers, + recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null + ) +} data class AnimeLoadResponse( var engName: String? = null, @@ -1542,6 +1573,7 @@ data class AnimeLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, + override var contentRating: String? = null, ) : LoadResponse, EpisodeResponse { override fun getLatestEpisodes(): Map { return episodes.map { (status, episodes) -> @@ -1559,6 +1591,41 @@ data class AnimeLoadResponse( episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 } } + episode } + + /** + * Secondary constructor for backwards compatibility without contentRating. + * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. + */ + constructor( + engName: String? = null, + japName: String? = null, + name: String, + url: String, + apiName: String, + type: TvType, + posterUrl: String? = null, + year: Int? = null, + episodes: MutableMap> = mutableMapOf(), + showStatus: ShowStatus? = null, + plot: String? = null, + tags: List? = null, + synonyms: List? = null, + rating: Int? = null, + duration: Int? = null, + trailers: MutableList = mutableListOf(), + recommendations: List? = null, + actors: List? = null, + comingSoon: Boolean = false, + syncData: MutableMap = mutableMapOf(), + posterHeaders: Map? = null, + nextAiring: NextAiring? = null, + seasonNames: List? = null, + backgroundPosterUrl: String? = null, + ) : this( + engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags, + synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders, + nextAiring, seasonNames, backgroundPosterUrl, null + ) } /** @@ -1610,7 +1677,36 @@ data class LiveStreamLoadResponse( override var syncData: MutableMap = mutableMapOf(), override var posterHeaders: Map? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse + override var contentRating: String? = null, +) : LoadResponse { + /** + * Secondary constructor for backwards compatibility without contentRating. + * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. + */ + constructor( + name: String, + url: String, + apiName: String, + dataUrl: String, + posterUrl: String? = null, + year: Int? = null, + plot: String? = null, + type: TvType = TvType.Live, + rating: Int? = null, + tags: List? = null, + duration: Int? = null, + trailers: MutableList = mutableListOf(), + recommendations: List? = null, + actors: List? = null, + comingSoon: Boolean = false, + syncData: MutableMap = mutableMapOf(), + posterHeaders: Map? = null, + backgroundPosterUrl: String? = null, + ) : this( + name, url, apiName, dataUrl, posterUrl, year, plot, type, rating, tags, duration, trailers, + recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null + ) +} data class MovieLoadResponse( override var name: String, @@ -1633,7 +1729,36 @@ data class MovieLoadResponse( override var syncData: MutableMap = mutableMapOf(), override var posterHeaders: Map? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse + override var contentRating: String? = null, +) : LoadResponse { + /** + * Secondary constructor for backwards compatibility without contentRating. + * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. + */ + constructor( + name: String, + url: String, + apiName: String, + type: TvType, + dataUrl: String, + posterUrl: String? = null, + year: Int? = null, + plot: String? = null, + rating: Int? = null, + tags: List? = null, + duration: Int? = null, + trailers: MutableList = mutableListOf(), + recommendations: List? = null, + actors: List? = null, + comingSoon: Boolean = false, + syncData: MutableMap = mutableMapOf(), + posterHeaders: Map? = null, + backgroundPosterUrl: String? = null, + ) : this( + name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers, + recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl,null + ) +} suspend fun MainAPI.newMovieLoadResponse( name: String, @@ -1757,6 +1882,7 @@ data class TvSeriesLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, + override var contentRating: String? = null, ) : LoadResponse, EpisodeResponse { override fun getLatestEpisodes(): Map { val maxSeason = @@ -1773,6 +1899,38 @@ data class TvSeriesLoadResponse( (it.season ?: Int.MIN_VALUE) < season && it.season != 0 } + episode } + + /** + * Secondary constructor for backwards compatibility without contentRating. + * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. + */ + constructor( + name: String, + url: String, + apiName: String, + type: TvType, + episodes: List, + posterUrl: String? = null, + year: Int? = null, + plot: String? = null, + showStatus: ShowStatus? = null, + rating: Int? = null, + tags: List? = null, + duration: Int? = null, + trailers: MutableList = mutableListOf(), + recommendations: List? = null, + actors: List? = null, + comingSoon: Boolean = false, + syncData: MutableMap = mutableMapOf(), + posterHeaders: Map? = null, + nextAiring: NextAiring? = null, + seasonNames: List? = null, + backgroundPosterUrl: String? = null, + ) : this( + name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration, + trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames, + backgroundPosterUrl, null + ) } suspend fun MainAPI.newTvSeriesLoadResponse( diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 314177af..50301e22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -151,6 +151,8 @@ open class TmdbProvider : MainAPI() { recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) + + contentRating = fetchContentRating(id, "US") } } @@ -193,6 +195,8 @@ open class TmdbProvider : MainAPI() { recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) + + contentRating = fetchContentRating(id, "US") } } @@ -264,6 +268,26 @@ open class TmdbProvider : MainAPI() { return null } + open suspend fun fetchContentRating(id: Int?, country: String): String? { + id ?: return null + + val contentRatings = tmdb.tvService().content_ratings(id).awaitResponse().body()?.results + return if (!contentRatings.isNullOrEmpty()) { + contentRatings.firstOrNull { it: ContentRating -> + it.iso_3166_1 == country + }?.rating + } else { + val releaseDates = tmdb.moviesService().releaseDates(id).awaitResponse().body()?.results + val certification = releaseDates?.firstOrNull { it: ReleaseDatesResult -> + it.iso_3166_1 == country + }?.release_dates?.firstOrNull { it: ReleaseDate -> + !it.certification.isNullOrBlank() + }?.certification + + certification + } + } + // Possible to add recommendations and such here. override suspend fun load(url: String): LoadResponse? { // https://www.themoviedb.org/movie/7445-brothers diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 7bcce764..0541a40f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -677,6 +677,7 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultMetaYear.setText(d.yearText) resultMetaDuration.setText(d.durationText) resultMetaRating.setText(d.ratingText) + resultMetaContentRating.setText(d.contentRatingText) resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) @@ -701,6 +702,11 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultCastItems.isGone = d.actors.isNullOrEmpty() (resultCastItems.adapter as? ActorAdaptor)?.updateList(d.actors ?: emptyList()) + if (d.contentRatingText == null) { + // If there is no rating to display, we don't want an empty gap + resultMetaContentRating.width = 0 + } + if (syncModel.addSyncs(d.syncData)) { syncModel.updateMetaAndUser() syncModel.updateSynced() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 396ec863..427e9cb3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -826,6 +826,7 @@ class ResultFragmentTv : Fragment() { resultMetaYear.setText(d.yearText) resultMetaDuration.setText(d.durationText) resultMetaRating.setText(d.ratingText) + resultMetaContentRating.setText(d.contentRatingText) resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) @@ -865,6 +866,11 @@ class ResultFragmentTv : Fragment() { (resultCastItems.adapter as? ActorAdaptor)?.updateList( d.actors ?: emptyList() ) + + if (d.contentRatingText == null) { + // If there is no rating to display, we don't want an empty gap + resultMetaContentRating.width = 0 + } } is Resource.Loading -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index d744fac5..0cad17c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -118,6 +118,7 @@ data class ResultData( val plotText: UiText, val apiName: UiText, val ratingText: UiText?, + val contentRatingText: UiText?, val vpnText: UiText?, val metaText: UiText?, val durationText: UiText?, @@ -249,6 +250,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { apiName = txt(apiName), ratingText = rating?.div(1000f) ?.let { if (it <= 0.1f) null else txt(R.string.rating_format, it) }, + contentRatingText = txt(contentRating), vpnText = txt( when (repo.vpnStatus) { VPNStatus.None -> null diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 9d748c5a..70461518 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -360,10 +360,16 @@ + + + +