diff --git a/src/main/kotlin/fr/shikkanime/platforms/DisneyPlusPlatform.kt b/src/main/kotlin/fr/shikkanime/platforms/DisneyPlusPlatform.kt index cb9c1aa2..9982c7b4 100644 --- a/src/main/kotlin/fr/shikkanime/platforms/DisneyPlusPlatform.kt +++ b/src/main/kotlin/fr/shikkanime/platforms/DisneyPlusPlatform.kt @@ -27,7 +27,7 @@ import java.time.format.DateTimeFormatter import java.util.logging.Level class DisneyPlusPlatform : - AbstractPlatform>() { + AbstractPlatform>>() { @Inject private lateinit var configCacheService: ConfigCacheService @@ -45,22 +45,17 @@ class DisneyPlusPlatform : } } - private val seasons = MapCache>(Duration.ofDays(1)) { - val accessToken = identifiers[it.countryCode]!! - return@MapCache runBlocking { - DisneyPlusWrapper.getSeasons( - accessToken, - it.countryCode, - it.disneyPlusSimulcast.name - ) - } - } - override suspend fun fetchApiContent( key: CountryCodeDisneyPlusSimulcastKeyCache, zonedDateTime: ZonedDateTime - ) = this.seasons[key]!!.flatMap { season -> - DisneyPlusWrapper.getEpisodes(identifiers[key.countryCode]!!, key.countryCode, season) + ): Pair> { + val accessToken = identifiers[key.countryCode]!! + val (animeDetails, seasons) = DisneyPlusWrapper.getAnimeDetailsWithSeasons( + accessToken, + key.disneyPlusSimulcast.name + ) + val episodes = seasons.flatMap { DisneyPlusWrapper.getEpisodes(accessToken, it) } + return animeDetails to episodes } override fun fetchEpisodes(zonedDateTime: ZonedDateTime, bypassFileContent: File?): List { @@ -71,11 +66,16 @@ class DisneyPlusPlatform : it.releaseDay == zonedDateTime.dayOfWeek.value && zonedDateTime.toLocalTime() .isEqualOrAfter(LocalTime.parse(it.releaseTime)) }.forEach { simulcast -> - val api = getApiContent(CountryCodeDisneyPlusSimulcastKeyCache(countryCode, simulcast), zonedDateTime) + val (animeDetails, episodes) = getApiContent( + CountryCodeDisneyPlusSimulcastKeyCache( + countryCode, + simulcast + ), zonedDateTime + ) - api.forEach { + episodes.forEach { try { - list.add(convertEpisode(countryCode, simulcast, it, zonedDateTime)) + list.add(convertEpisode(countryCode, simulcast, animeDetails, it, zonedDateTime)) } catch (_: AnimeException) { // Ignore } catch (e: Exception) { @@ -91,49 +91,40 @@ class DisneyPlusPlatform : private fun convertEpisode( countryCode: CountryCode, simulcast: DisneyPlusConfiguration.DisneyPlusSimulcast, + animeDetails: JsonObject, jsonObject: JsonObject, zonedDateTime: ZonedDateTime ): Episode { - val texts = jsonObject.getAsJsonObject("text") - val titles = texts?.getAsJsonObject("title")?.getAsJsonObject("full") - val descriptions = texts.getAsJsonObject("description")?.getAsJsonObject("medium") - val animeName = titles?.getAsJsonObject("series")?.getAsJsonObject("default")?.getAsString("content") - ?: throw Exception("Anime name is null") - - val animeImage = jsonObject.getAsJsonObject("image")?.getAsJsonObject("tile")?.getAsJsonObject("0.71") - ?.getAsJsonObject("series")?.getAsJsonObject("default")?.getAsString("url") - ?: throw Exception("Anime image is null") - val animeBanner = jsonObject.getAsJsonObject("image")?.getAsJsonObject("tile")?.getAsJsonObject("1.33") - ?.getAsJsonObject("series")?.getAsJsonObject("default")?.getAsString("url") - ?: throw Exception("Anime image is null") - val animeDescription = descriptions?.getAsJsonObject("series") - ?.getAsJsonObject("default")?.getAsString("content")?.replace('\n', ' ') ?: "" - - val season = requireNotNull(jsonObject.getAsInt("seasonSequenceNumber")) { "Season is null" } - val number = jsonObject.getAsInt("episodeSequenceNumber") ?: -1 - - val id = jsonObject.getAsString("contentId") - - val title = - titles.getAsJsonObject("program")?.getAsJsonObject("default")?.getAsString("content")?.ifBlank { null } - + val animeName = animeDetails.getAsString("title") ?: throw Exception("Anime name is null") + val tileObject = animeDetails.getAsJsonObject("artwork")?.getAsJsonObject("standard")?.getAsJsonObject("tile") + ?: throw Exception("Tile object is null") + val animeImageId = + tileObject.getAsJsonObject("0.71")?.getAsString("imageId") ?: throw Exception("Anime image is null") + val animeImage = DisneyPlusWrapper.getImageUrl(animeImageId) + val animeBannerId = + tileObject.getAsJsonObject("1.33")?.getAsString("imageId") ?: throw Exception("Anime image is null") + val animeBanner = DisneyPlusWrapper.getImageUrl(animeBannerId) + val animeDescription = + animeDetails.getAsJsonObject("description")?.getAsString("full")?.replace('\n', ' ') ?: "" + + val season = requireNotNull(jsonObject.getAsInt("seasonNumber")) { "Season is null" } + val number = jsonObject.getAsInt("episodeNumber") ?: -1 + val id = jsonObject.getAsString("id") + val title = jsonObject.getAsString("episodeTitle")?.ifBlank { null } val url = "https://www.disneyplus.com/${countryCode.locale.lowercase()}/video/$id" - - val image = jsonObject.getAsJsonObject("image")?.getAsJsonObject("thumbnail")?.getAsJsonObject("1.78") - ?.getAsJsonObject("program")?.getAsJsonObject("default")?.getAsString("url") - ?: throw Exception("Image is null") - - var duration = jsonObject.getAsJsonObject("mediaMetadata")?.getAsLong("runtimeMillis", -1) ?: -1 + val imageId = jsonObject.getAsJsonObject("artwork")?.getAsJsonObject("standard")?.getAsJsonObject("thumbnail") + ?.getAsJsonObject("1.78")?.getAsString("imageId") ?: throw Exception("Image is null") + val image = DisneyPlusWrapper.getImageUrl(imageId) + var duration = jsonObject.getAsLong("durationMs", -1) if (duration != -1L) { duration /= 1000 } - val description = descriptions?.getAsJsonObject("program") - ?.getAsJsonObject("default")?.getAsString("content")?.replace('\n', ' ')?.ifBlank { null } - - val releaseDateTimeUTC = zonedDateTime.withUTC() - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "T${simulcast.releaseTime}Z" + val description = + jsonObject.getAsJsonObject("description")?.getAsString("medium")?.replace('\n', ' ')?.ifBlank { null } + val releaseDateTimeUTC = + zonedDateTime.withUTC().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "T${simulcast.releaseTime}Z" val releaseDateTime = ZonedDateTime.parse(releaseDateTimeUTC) return Episode( diff --git a/src/main/kotlin/fr/shikkanime/wrappers/DisneyPlusWrapper.kt b/src/main/kotlin/fr/shikkanime/wrappers/DisneyPlusWrapper.kt index faab94d7..427a939a 100644 --- a/src/main/kotlin/fr/shikkanime/wrappers/DisneyPlusWrapper.kt +++ b/src/main/kotlin/fr/shikkanime/wrappers/DisneyPlusWrapper.kt @@ -1,7 +1,6 @@ package fr.shikkanime.wrappers import com.google.gson.JsonObject -import fr.shikkanime.entities.enums.CountryCode import fr.shikkanime.utils.HttpRequest import fr.shikkanime.utils.ObjectParser import fr.shikkanime.utils.ObjectParser.getAsBoolean @@ -38,38 +37,49 @@ object DisneyPlusWrapper { .getAsString("accessToken")!! } - suspend fun getSeasons(accessToken: String, countryCode: CountryCode, id: String): List { + suspend fun getAnimeDetailsWithSeasons(accessToken: String, id: String): Pair> { val seasonsResponse = HttpRequest().get( - "https://disney.content.edge.bamgrid.com/svc/content/DmcSeriesBundle/version/5.1/region/${countryCode.name}/audience/k-false,l-true/maturity/1850/language/${countryCode.locale}/encodedSeriesId/$id", + "https://disney.api.edge.bamgrid.com/explore/v1.4/page/entity-$id?disableSmartFocus=true&enhancedContainersLimit=15&limit=15", mapOf("Authorization" to "Bearer $accessToken") ) require(seasonsResponse.status.value == 200) { "Failed to fetch Disney+ content" } - val seasonsJson = ObjectParser.fromJson(seasonsResponse.bodyAsText(), JsonObject::class.java) - return seasonsJson.getAsJsonObject("data") - .getAsJsonObject("DmcSeriesBundle") - .getAsJsonObject("seasons") - .getAsJsonArray("seasons") - .mapNotNull { it.asJsonObject.getAsString("seasonId") } + val jsonObject = ObjectParser.fromJson(seasonsResponse.bodyAsText(), JsonObject::class.java) + val pageObject = jsonObject.getAsJsonObject("data").getAsJsonObject("page") + + val seasons = pageObject.getAsJsonArray("containers") + .filter { it.asJsonObject.getAsString("type") == "episodes" } + .map { it.asJsonObject } + .getOrNull(0) + ?.getAsJsonArray("seasons") + ?.filter { it.asJsonObject.getAsString("type") == "season" } + ?.mapNotNull { it.asJsonObject.getAsString("id") } ?: emptyList() + + return pageObject.getAsJsonObject("visuals") to seasons } - suspend fun getEpisodes(accessToken: String, countryCode: CountryCode, seasonId: String): List { + suspend fun getEpisodes(accessToken: String, seasonId: String): List { val episodes = mutableListOf() var page = 1 var hasMore: Boolean do { val url = - "https://disney.content.edge.bamgrid.com/svc/content/DmcEpisodes/version/5.1/region/${countryCode.name}/audience/k-false,l-true/maturity/1850/language/${countryCode.locale}/seasonId/$seasonId/pageSize/15/page/${page++}" + "https://disney.api.edge.bamgrid.com/explore/v1.4/season/$seasonId?limit=24&offset=${(page++ - 1) * 24}" val response = HttpRequest().get(url, mapOf("Authorization" to "Bearer $accessToken")) require(response.status.value == 200) { "Failed to fetch Disney+ content" } val json = ObjectParser.fromJson(response.bodyAsText(), JsonObject::class.java) - val dmcEpisodesMeta = json.getAsJsonObject("data").getAsJsonObject("DmcEpisodes") - hasMore = dmcEpisodesMeta.getAsJsonObject("meta").getAsBoolean("hasMore") ?: false - dmcEpisodesMeta.getAsJsonArray("videos").forEach { episodes.add(it.asJsonObject) } + val jsonObject = json.getAsJsonObject("data").getAsJsonObject("season") + hasMore = jsonObject.getAsJsonObject("pagination").getAsBoolean("hasMore") ?: false + + jsonObject.getAsJsonArray("items") + .filter { it.asJsonObject.getAsString("type") == "view" } + .forEach { episodes.add(it.asJsonObject.getAsJsonObject("visuals")) } } while (hasMore) return episodes } + + fun getImageUrl(id: String) = "https://disney.images.edge.bamgrid.com/ripcut-delivery/v2/variant/disney/$id/compose" } \ No newline at end of file diff --git a/src/main/resources/db/changelog/2024/06/01-changelog.xml b/src/main/resources/db/changelog/2024/06/01-changelog.xml new file mode 100644 index 00000000..4650c9fc --- /dev/null +++ b/src/main/resources/db/changelog/2024/06/01-changelog.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.xml b/src/main/resources/db/changelog/db.changelog-master.xml index 77e941a2..cc6df9b7 100644 --- a/src/main/resources/db/changelog/db.changelog-master.xml +++ b/src/main/resources/db/changelog/db.changelog-master.xml @@ -52,4 +52,6 @@ + + \ No newline at end of file