fix(all/torrentioanime): use api.ani.zip for episode lists and fix nsfw (#676)

* feat(all/torrentio): use api.ani.zip again for episode list

* feat(all/torrentio): align queries with nsfw tag

* feat(all/torrentio): fix latest query
This commit is contained in:
Victor Borges 2025-02-16 19:30:38 -03:00 committed by GitHub
parent 4933411035
commit 954c401f8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 121 additions and 46 deletions

View file

@ -1,7 +1,7 @@
ext {
extName = 'Torrentio Anime (Torrent / Debrid)'
extClass = '.Torrentio'
extVersionCode = 14
extVersionCode = 15
containsNsfw = false
}

View file

@ -29,7 +29,8 @@ fun anilistQuery() = """
startDate_like: %year,
seasonYear: %seasonYear,
season: %season,
format_in: %format
format_in: %format,
isAdult: false
) {
id
title {
@ -103,7 +104,7 @@ fun anilistLatestQuery() = """
fun getDetailsQuery() = """
query media(%id: Int) {
Media(id: %id) {
Media(id: %id, isAdult: false) {
id
title {
romaji
@ -137,23 +138,3 @@ query media(%id: Int) {
}
}
""".toQuery()
fun getEpisodeQuery() = """
query media(%id: Int, %type: MediaType) {
Media(id: %id, type: %type) {
episodes
nextAiringEpisode {
episode
}
}
}
""".toQuery()
fun getMalIdQuery() = """
query media(%id: Int, %type: MediaType) {
Media(id: %id, type: %type) {
idMal
id
}
}
""".toQuery()

View file

@ -10,10 +10,10 @@ import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.AniZipResponse
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.AnilistMeta
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.AnilistMetaLatest
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.DetailsById
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.EpisodeList
import eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto.StreamDataTorrent
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@ -66,6 +66,7 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
.add("query", query)
.add("variables", variables)
.build()
return POST("https://graphql.anilist.co", body = requestBody)
}
@ -148,7 +149,8 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
override fun popularAnimeParse(response: Response): AnimesPage {
val jsonData = response.body.string()
return parseSearchJson(jsonData) }
return parseSearchJson(jsonData)
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request {
@ -300,41 +302,55 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime): Request {
return GET("https://anime-kitsu.strem.fun/meta/series/anilist%3A${anime.url}.json")
return GET("https://api.ani.zip/mappings?anilist_id=${anime.url}")
}
override fun episodeListParse(response: Response): List<SEpisode> {
val responseString = response.body.string()
val episodeList = json.decodeFromString<EpisodeList>(responseString)
val aniZipResponse = json.decodeFromString<AniZipResponse>(responseString)
return when (episodeList.meta?.type) {
"series" -> {
episodeList.meta.videos
?.let { videos ->
if (preferences.getBoolean(UPCOMING_EP_KEY, UPCOMING_EP_DEFAULT)) { videos } else { videos.filter { video -> (video.released?.let { parseDate(it) } ?: 0L) <= System.currentTimeMillis() } }
return when (aniZipResponse.mappings?.type) {
"TV" -> {
aniZipResponse.episodes
?.let { episodes ->
if (preferences.getBoolean(UPCOMING_EP_KEY, UPCOMING_EP_DEFAULT)) {
episodes
} else {
episodes.filter { (_, episode) -> (episode?.airDate?.let { parseDate(it) } ?: 0L) <= System.currentTimeMillis() }
}
}
?.map { video ->
?.mapNotNull { (_, episode) ->
val episodeNumber = runCatching { episode?.episode?.toFloat() }.getOrNull()
if (episodeNumber == null) {
return@mapNotNull null
}
val title = episode?.title?.get("en")
SEpisode.create().apply {
episode_number = video.episode?.toFloat() ?: 0.0F
url = "/stream/series/${video.videoId}.json"
date_upload = video.released?.let { parseDate(it) } ?: 0L
name = "Episode ${video.episode} : ${
video.title?.removePrefix("Episode ")
?.replaceFirst("\\d+\\s*".toRegex(), "")
?.trim()
}"
scanlator = (video.released?.let { parseDate(it) } ?: 0L).takeIf { it > System.currentTimeMillis() }?.let { "Upcoming" } ?: ""
episode_number = episodeNumber
url = "/stream/series/kitsu:${aniZipResponse.mappings.kitsuId}:${String.format(Locale.ENGLISH, "%.0f", episodeNumber)}.json"
date_upload = episode?.airDate?.let { parseDate(it) } ?: 0L
name = if (title == null) "Episode ${episode?.episode}" else "Episode ${episode.episode}: $title"
scanlator = (episode?.airDate?.let { parseDate(it) } ?: 0L).takeIf { it > System.currentTimeMillis() }?.let { "Upcoming" } ?: ""
}
}.orEmpty().reversed()
}
"movie" -> {
// Handle movie response
"MOVIE" -> {
val dateUpload = if (!aniZipResponse.episodes.isNullOrEmpty()) {
aniZipResponse.episodes["1"]?.airDate?.let { parseDate(it) } ?: 0L
} else {
0L
}
listOf(
SEpisode.create().apply {
episode_number = 1.0F
url = "/stream/movie/${episodeList.meta.kitsuId}.json"
url = "/stream/movie/kitsu:${aniZipResponse.mappings.kitsuId}.json"
name = "Movie"
date_upload = dateUpload
},
).reversed()
}
@ -342,6 +358,12 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
else -> emptyList()
}
}
private fun parseDateTime(dateStr: String): Long {
return runCatching { DATE_TIME_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L
}
private fun parseDate(dateStr: String): Long {
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L
@ -421,6 +443,7 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
udp://www.torrent.eu.org:451/announce,
${fetchTrackers().split("\n").joinToString(",")}
""".trimIndent()
return streamList.streams?.map { stream ->
val urlOrHash =
if (debridProvider == "none") {
@ -875,8 +898,12 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
private const val IS_EFFICIENT_KEY = "efficient"
private const val IS_EFFICIENT_DEFAULT = false
private val DATE_FORMATTER by lazy {
private val DATE_TIME_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
}
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
}
}
}

View file

@ -0,0 +1,67 @@
package eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class AniZipResponse(
val titles: Map<String, String?>? = null,
val episodes: Map<String, AniZipEpisode?>? = null,
val episodeCount: Int? = null,
val specialCount: Int? = null,
val images: List<AniZipImage?>? = null,
val mappings: AniZipMappings? = null,
)
@Serializable
data class AniZipEpisode(
val episode: String? = null,
val episodeNumber: Int? = null,
val absoluteEpisodeNumber: Int? = null,
val seasonNumber: Int? = null,
val title: Map<String, String?>? = null,
val length: Int? = null,
val runtime: Int? = null,
@SerialName("airdate")
val airDate: String? = null,
val rating: String? = null,
@SerialName("anidbEid")
val aniDbEpisodeId: Long? = null,
val tvdbShowId: Long? = null,
val tvdbId: Long? = null,
val overview: String? = null,
val image: String? = null,
)
@Serializable
data class AniZipImage(
val coverType: String? = null,
val url: String? = null,
)
@Serializable
data class AniZipMappings(
@SerialName("animeplanet_id")
val animePlanetId: String? = null,
@SerialName("kitsu_id")
val kitsuId: Long? = null,
@SerialName("mal_id")
val myAnimeListId: Long? = null,
val type: String? = null,
@SerialName("anilist_id")
val aniListId: Long? = null,
@SerialName("anisearch_id")
val aniSearchId: Long? = null,
@SerialName("anidb_id")
val aniDbId: Long? = null,
@SerialName("notifymoe_id")
val notifyMoeId: String? = null,
@SerialName("livechart_id")
val liveChartId: Long? = null,
@SerialName("thetvdb_id")
val theTvDbId: Long? = null,
@SerialName("imdb_id")
val imdbId: String? = null,
@SerialName("themoviedb_id")
val theMovieDbId: String? = null,
)