diff --git a/src/all/torrentioanime/build.gradle b/src/all/torrentioanime/build.gradle index 52ec219c..258789ef 100644 --- a/src/all/torrentioanime/build.gradle +++ b/src/all/torrentioanime/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Torrentio Anime (Torrent / Debrid)' extClass = '.Torrentio' - extVersionCode = 14 + extVersionCode = 15 containsNsfw = false } diff --git a/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/AniListQueries.kt b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/AniListQueries.kt index 9eb474e0..f2925ab2 100644 --- a/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/AniListQueries.kt +++ b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/AniListQueries.kt @@ -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() diff --git a/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/Torrentio.kt b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/Torrentio.kt index 007f9cc6..021cf57f 100644 --- a/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/Torrentio.kt +++ b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/Torrentio.kt @@ -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 { val responseString = response.body.string() - val episodeList = json.decodeFromString(responseString) + val aniZipResponse = json.decodeFromString(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) + } } } diff --git a/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/dto/AniZipDto.kt b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/dto/AniZipDto.kt new file mode 100644 index 00000000..b555f3f2 --- /dev/null +++ b/src/all/torrentioanime/src/eu/kanade/tachiyomi/animeextension/all/torrentioanime/dto/AniZipDto.kt @@ -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? = null, + val episodes: Map? = null, + val episodeCount: Int? = null, + val specialCount: Int? = null, + val images: List? = 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? = 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, +) diff --git a/src/en/allmovies/build.gradle b/src/en/allmovies/build.gradle deleted file mode 100644 index c13c2047..00000000 --- a/src/en/allmovies/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -ext { - extName = 'AllMovies' - extClass = '.AllMovies' - extVersionCode = 12 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/allmovies/res/mipmap-hdpi/ic_launcher.png b/src/en/allmovies/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 977ed7b6..00000000 Binary files a/src/en/allmovies/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/allmovies/res/mipmap-mdpi/ic_launcher.png b/src/en/allmovies/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4b61dcd8..00000000 Binary files a/src/en/allmovies/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/allmovies/res/mipmap-xhdpi/ic_launcher.png b/src/en/allmovies/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ff41c03e..00000000 Binary files a/src/en/allmovies/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/allmovies/res/mipmap-xxhdpi/ic_launcher.png b/src/en/allmovies/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9a4107d1..00000000 Binary files a/src/en/allmovies/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/allmovies/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/allmovies/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c2db7ba3..00000000 Binary files a/src/en/allmovies/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/allmovies/res/web_hi_res_512.png b/src/en/allmovies/res/web_hi_res_512.png deleted file mode 100644 index 93044256..00000000 Binary files a/src/en/allmovies/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/allmovies/src/eu/kanade/tachiyomi/animeextension/en/allmovies/AllMovies.kt b/src/en/allmovies/src/eu/kanade/tachiyomi/animeextension/en/allmovies/AllMovies.kt deleted file mode 100644 index ed496de7..00000000 --- a/src/en/allmovies/src/eu/kanade/tachiyomi/animeextension/en/allmovies/AllMovies.kt +++ /dev/null @@ -1,337 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.allmovies - -import android.app.Application -import android.content.SharedPreferences -import androidx.preference.ListPreference -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource -import eu.kanade.tachiyomi.animesource.model.AnimeFilter -import eu.kanade.tachiyomi.animesource.model.AnimeFilterList -import eu.kanade.tachiyomi.animesource.model.SAnime -import eu.kanade.tachiyomi.animesource.model.SEpisode -import eu.kanade.tachiyomi.animesource.model.Video -import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class AllMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { - - override val name = "AllMoviesForYou" - - override val baseUrl = "https://allmoviesforyou.net" - - override val lang = "en" - - override val supportsLatest = false - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - // Popular Anime - - override fun popularAnimeSelector(): String = "article.TPost > a" - - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series/page/$page") - - override fun popularAnimeFromElement(element: Element): SAnime { - val anime = SAnime.create() - anime.setUrlWithoutDomain(element.attr("href")) - anime.title = element.select("h2.Title").text() - anime.thumbnail_url = "https:" + element.select("div.Image figure img").attr("data-src") - - return anime - } - - override fun popularAnimeNextPageSelector(): String = "div.nav-links a:last-child" - - // Episodes - - override fun episodeListSelector() = throw UnsupportedOperationException() - - override fun episodeListParse(response: Response): List { - val document = response.asJsoup() - val episodeList = mutableListOf() - val seriesLink = document.select("link[rel=canonical]").attr("abs:href") - if (seriesLink.contains("/series/")) { - val seasonsHtml = client.newCall( - GET( - seriesLink, - headers = Headers.headersOf("Referer", document.location()), - ), - ).execute().asJsoup() - val seasonsElements = seasonsHtml.select("section.SeasonBx.AACrdn a") - seasonsElements.forEach { - val seasonEpList = parseEpisodesFromSeries(it) - episodeList.addAll(seasonEpList) - } - } else { - val episode = SEpisode.create() - episode.name = document.select("div.TPMvCn h1.Title").text() - episode.episode_number = 1F - episode.setUrlWithoutDomain(seriesLink) - episodeList.add(episode) - } - return episodeList.reversed() - } - - override fun episodeFromElement(element: Element): SEpisode { - val episode = SEpisode.create() - episode.episode_number = element.select("td > span.Num").text().toFloat() - val seasonNum = element.ownerDocument()!!.select("div.Title span").text() - episode.name = "Season $seasonNum" + "x" + element.select("td span.Num").text() + " : " + element.select("td.MvTbTtl > a").text() - episode.setUrlWithoutDomain(element.select("td.MvTbPly > a.ClA").attr("abs:href")) - return episode - } - - private fun parseEpisodesFromSeries(element: Element): List { - val seasonId = element.attr("abs:href") - val episodesHtml = client.newCall(GET(seasonId)).execute().asJsoup() - val episodeElements = episodesHtml.select("tr.Viewed") - return episodeElements.map { episodeFromElement(it) } - } - - // Video urls - - override fun videoListRequest(episode: SEpisode): Request { - val document = client.newCall(GET(baseUrl + episode.url)).execute().asJsoup() - val iframe = document.select("iframe[src*=/?trembed]").attr("abs:src") - return GET(iframe) - } - - override fun videoListParse(response: Response): List