fix(all/yomiroll): Fetch anime status from AniList (#2728)
Co-authored-by: Samfun75 <38332931+Samfun75@users.noreply.github.com> Co-authored-by: jmir1 <jhmiramon@gmail.com>
This commit is contained in:
parent
facbb2a526
commit
7ab9dc9a61
@ -8,7 +8,7 @@ ext {
|
||||
extName = 'Yomiroll'
|
||||
pkgNameSuffix = 'all.kamyroll'
|
||||
extClass = '.Yomiroll'
|
||||
extVersionCode = 26
|
||||
extVersionCode = 27
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -59,30 +59,26 @@ data class Anime(
|
||||
val genres: ArrayList<String>? = null,
|
||||
val series_metadata: Metadata? = null,
|
||||
@SerialName("movie_listing_metadata")
|
||||
val movie_metadata: MovieMeta? = null,
|
||||
val movie_metadata: Metadata? = null,
|
||||
val content_provider: String? = null,
|
||||
val audio_locale: String? = null,
|
||||
val audio_locales: ArrayList<String>? = null,
|
||||
val subtitle_locales: ArrayList<String>? = null,
|
||||
val maturity_ratings: ArrayList<String>? = null,
|
||||
val is_dubbed: Boolean? = null,
|
||||
val is_subbed: Boolean? = null,
|
||||
) {
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
val maturity_ratings: ArrayList<String>,
|
||||
val is_simulcast: Boolean,
|
||||
val audio_locales: ArrayList<String>,
|
||||
val is_simulcast: Boolean? = null,
|
||||
val audio_locales: ArrayList<String>? = null,
|
||||
val subtitle_locales: ArrayList<String>,
|
||||
val is_dubbed: Boolean,
|
||||
val is_subbed: Boolean,
|
||||
@SerialName("tenant_categories")
|
||||
val genres: ArrayList<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MovieMeta(
|
||||
val is_dubbed: Boolean,
|
||||
val is_subbed: Boolean,
|
||||
val maturity_ratings: ArrayList<String>,
|
||||
val subtitle_locales: ArrayList<String>,
|
||||
@SerialName("tenant_categories")
|
||||
val genres: ArrayList<String>? = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@ -172,6 +168,22 @@ data class Subtitle(
|
||||
val url: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AnilistResult(
|
||||
val data: AniData,
|
||||
) {
|
||||
@Serializable
|
||||
data class AniData(
|
||||
@SerialName("Media")
|
||||
val media: Media? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Media(
|
||||
val status: String,
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> List<T>.thirdLast(): T? {
|
||||
if (size < 3) return null
|
||||
return this[size - 3]
|
||||
|
@ -15,15 +15,19 @@ import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
@ -53,6 +57,8 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val mainScope by lazy { MainScope() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
@ -65,6 +71,8 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
super.client.newBuilder().addInterceptor(tokenInterceptor).build()
|
||||
}
|
||||
|
||||
private val noTokenClient = super.client
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
@ -73,7 +81,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = json.decodeFromString<AnimeResult>(response.body.string())
|
||||
val parsed = json.decodeFromString<AnimeResult>(response.use { it.body.string() })
|
||||
val animeList = parsed.data.mapNotNull { it.toSAnimeOrNull() }
|
||||
val position = response.request.url.queryParameter("start")?.toIntOrNull() ?: 0
|
||||
return AnimesPage(animeList, position + 36 < parsed.total)
|
||||
@ -103,7 +111,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val bod = response.body.string()
|
||||
val bod = response.use { it.body.string() }
|
||||
val total: Int
|
||||
val items =
|
||||
if (response.request.url.encodedPath.contains("search")) {
|
||||
@ -125,6 +133,43 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
// Function to fetch anime status using AniList GraphQL API ispired by OppaiStream.kt
|
||||
private fun fetchStatusByTitle(title: String): Int {
|
||||
val query = """
|
||||
query {
|
||||
Media(search: "$title", isAdult: false, type: ANIME) {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
native
|
||||
english
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val requestBody = FormBody.Builder()
|
||||
.add("query", query)
|
||||
.build()
|
||||
|
||||
val response = noTokenClient.newCall(
|
||||
POST("https://graphql.anilist.co", body = requestBody),
|
||||
).execute().use { it.body.string() }
|
||||
|
||||
val responseParsed = json.decodeFromString<AnilistResult>(response)
|
||||
|
||||
return when (responseParsed.data.media?.status) {
|
||||
"FINISHED" -> SAnime.COMPLETED
|
||||
"RELEASING" -> SAnime.ONGOING
|
||||
"NOT_YET_RELEASED" -> SAnime.LICENSED
|
||||
"CANCELLED" -> SAnime.CANCELLED
|
||||
"HIATUS" -> SAnime.ON_HIATUS
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
val mediaId = json.decodeFromString<LinkData>(anime.url)
|
||||
val resp = client.newCall(
|
||||
@ -133,17 +178,10 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
} else {
|
||||
GET("$crApiUrl/cms/movie_listings/${mediaId.id}?locale=en-US")
|
||||
},
|
||||
).execute()
|
||||
val info = json.decodeFromString<AnimeResult>(resp.body.string())
|
||||
).execute().use { it.body.string() }
|
||||
val info = json.decodeFromString<AnimeResult>(resp)
|
||||
return Observable.just(
|
||||
anime.apply {
|
||||
author = info.data.first().content_provider
|
||||
status = SAnime.COMPLETED
|
||||
if (genre.isNullOrBlank()) {
|
||||
genre =
|
||||
info.data.first().genres?.joinToString { gen -> gen.replaceFirstChar { it.uppercase() } }
|
||||
}
|
||||
},
|
||||
info.data.first().toSAnimeOrNull(anime) ?: anime,
|
||||
)
|
||||
}
|
||||
|
||||
@ -161,7 +199,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val seasons = json.decodeFromString<SeasonResult>(response.body.string())
|
||||
val seasons = json.decodeFromString<SeasonResult>(response.use { it.body.string() })
|
||||
val series = response.request.url.encodedPath.contains("series/")
|
||||
val chunkSize = Runtime.getRuntime().availableProcessors()
|
||||
return if (series) {
|
||||
@ -176,7 +214,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
seasons.data.mapIndexed { index, movie ->
|
||||
SEpisode.create().apply {
|
||||
url = EpisodeData(listOf(Pair(movie.id, ""))).toJsonString()
|
||||
name = "Movie"
|
||||
name = "Movie ${index + 1}"
|
||||
episode_number = (index + 1).toFloat()
|
||||
date_upload = movie.date?.let(::parseDate) ?: 0L
|
||||
}
|
||||
@ -185,10 +223,9 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun getEpisodes(seasonData: SeasonResult.Season): List<SEpisode> {
|
||||
val episodeResp =
|
||||
val body =
|
||||
client.newCall(GET("$crApiUrl/cms/seasons/${seasonData.id}/episodes"))
|
||||
.execute()
|
||||
val body = episodeResp.body.string()
|
||||
.execute().use { it.body.string() }
|
||||
val episodes = json.decodeFromString<EpisodeResult>(body)
|
||||
|
||||
return episodes.data.sortedBy { it.episode_number }.mapNotNull EpisodeMap@{ ep ->
|
||||
@ -246,8 +283,8 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
private fun extractVideo(media: Pair<String, String>): List<Video> {
|
||||
val (mediaId, aud) = media
|
||||
val response = client.newCall(getVideoRequest(mediaId)).execute()
|
||||
val streams = json.decodeFromString<VideoStreams>(response.body.string())
|
||||
val response = client.newCall(getVideoRequest(mediaId)).execute().use { it.body.string() }
|
||||
val streams = json.decodeFromString<VideoStreams>(response)
|
||||
|
||||
val subLocale = preferences.getString(PREF_SUB_KEY, PREF_SUB_DEFAULT)!!.getLocale()
|
||||
val subsList = runCatching {
|
||||
@ -276,7 +313,7 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
runCatching {
|
||||
val playlist = client.newCall(GET(stream.url)).execute()
|
||||
if (playlist.code != 200) return@parallelMap null
|
||||
playlist.body.string().substringAfter("#EXT-X-STREAM-INF:")
|
||||
playlist.use { it.body.string() }.substringAfter("#EXT-X-STREAM-INF:")
|
||||
.split("#EXT-X-STREAM-INF:").map {
|
||||
val hardsub = stream.hardsub_locale.let { hs ->
|
||||
if (hs.isNotBlank()) " - HardSub: $hs" else ""
|
||||
@ -340,6 +377,14 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Pair("sv-SE", "Swedish"),
|
||||
Pair("zh-CN", "Chinese (PRC)"),
|
||||
Pair("zh-HK", "Chinese (Hong Kong)"),
|
||||
Pair("zh-TW", "Chinese (Taiwan)"),
|
||||
Pair("ca-ES", "Català"),
|
||||
Pair("id-ID", "Bahasa Indonesia"),
|
||||
Pair("ms-MY", "Bahasa Melayu"),
|
||||
Pair("ta-IN", "Tamil"),
|
||||
Pair("te-IN", "Telugu"),
|
||||
Pair("th-TH", "Thai"),
|
||||
Pair("vi-VN", "Vietnamese"),
|
||||
)
|
||||
|
||||
private fun LinkData.toJsonString(): String {
|
||||
@ -355,55 +400,69 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
private fun Anime.toSAnimeOrNull() = runCatching { toSAnime() }.getOrNull()
|
||||
private fun Anime.toSAnimeOrNull(anime: SAnime? = null) =
|
||||
runCatching { toSAnime(anime) }.getOrNull()
|
||||
|
||||
private fun Anime.toSAnime(): SAnime =
|
||||
private fun Anime.toSAnime(anime: SAnime? = null): SAnime =
|
||||
SAnime.create().apply {
|
||||
title = this@toSAnime.title
|
||||
thumbnail_url = images.poster_tall?.getOrNull(0)?.thirdLast()?.source
|
||||
?: images.poster_tall?.getOrNull(0)?.last()?.source
|
||||
url = LinkData(id, type!!).toJsonString()
|
||||
genre = series_metadata?.genres?.joinToString()
|
||||
?: movie_metadata?.genres?.joinToString() ?: ""
|
||||
status = SAnime.COMPLETED
|
||||
var desc = this@toSAnime.description + "\n"
|
||||
desc += "\nLanguage:" +
|
||||
(
|
||||
if (series_metadata?.subtitle_locales?.any() == true ||
|
||||
movie_metadata?.subtitle_locales?.any() == true ||
|
||||
series_metadata?.is_subbed == true
|
||||
) {
|
||||
" Sub"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
) +
|
||||
(
|
||||
if (series_metadata?.audio_locales?.size ?: 0 > 1 ||
|
||||
movie_metadata?.is_dubbed == true
|
||||
) {
|
||||
" Dub"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
desc += "\nMaturity Ratings: " +
|
||||
(
|
||||
series_metadata?.maturity_ratings?.joinToString()
|
||||
?: movie_metadata?.maturity_ratings?.joinToString() ?: ""
|
||||
)
|
||||
desc += if (series_metadata?.is_simulcast == true) "\nSimulcast" else ""
|
||||
desc += "\n\nAudio: " + (
|
||||
series_metadata?.audio_locales?.sortedBy { it.getLocale() }
|
||||
?.joinToString { it.getLocale() } ?: ""
|
||||
url = anime?.url ?: LinkData(id, type!!).toJsonString()
|
||||
genre = anime?.genre ?: (series_metadata?.genres ?: movie_metadata?.genres ?: genres)
|
||||
?.joinToString { gen -> gen.replaceFirstChar { it.uppercase() } }
|
||||
status = if (anime != null) fetchStatusByTitle(this@toSAnime.title) else SAnime.UNKNOWN
|
||||
author = content_provider
|
||||
description = anime?.description ?: StringBuilder().apply {
|
||||
appendLine(this@toSAnime.description)
|
||||
appendLine()
|
||||
|
||||
append("Language:")
|
||||
if ((
|
||||
subtitle_locales ?: (
|
||||
series_metadata
|
||||
?: movie_metadata
|
||||
)?.subtitle_locales
|
||||
)?.any() == true ||
|
||||
(series_metadata ?: movie_metadata)?.is_subbed == true ||
|
||||
is_subbed == true
|
||||
) {
|
||||
append(" Sub")
|
||||
}
|
||||
if (((series_metadata?.audio_locales ?: audio_locales)?.size ?: 0) > 1 ||
|
||||
(series_metadata ?: movie_metadata)?.is_dubbed == true ||
|
||||
is_dubbed == true
|
||||
) {
|
||||
append(" Dub")
|
||||
}
|
||||
appendLine()
|
||||
|
||||
append("Maturity Ratings: ")
|
||||
appendLine(
|
||||
((series_metadata ?: movie_metadata)?.maturity_ratings ?: maturity_ratings)
|
||||
?.joinToString() ?: "-",
|
||||
)
|
||||
desc += "\n\nSubs: " + (
|
||||
series_metadata?.subtitle_locales?.sortedBy { it.getLocale() }
|
||||
?.joinToString { it.getLocale() }
|
||||
?: movie_metadata?.subtitle_locales?.sortedBy { it.getLocale() }
|
||||
?.joinToString { it.getLocale() } ?: ""
|
||||
if (series_metadata?.is_simulcast == true) appendLine("Simulcast")
|
||||
appendLine()
|
||||
|
||||
append("Audio: ")
|
||||
appendLine(
|
||||
(series_metadata?.audio_locales ?: audio_locales ?: listOf(audio_locale ?: "-"))
|
||||
.sortedBy { it.getLocale() }
|
||||
.joinToString { it.getLocale() },
|
||||
)
|
||||
description = desc
|
||||
appendLine()
|
||||
|
||||
append("Subs: ")
|
||||
append(
|
||||
(
|
||||
subtitle_locales ?: series_metadata?.subtitle_locales
|
||||
?: movie_metadata?.subtitle_locales
|
||||
)
|
||||
?.sortedBy { it.getLocale() }
|
||||
?.joinToString { it.getLocale() },
|
||||
)
|
||||
}.toString()
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
@ -506,15 +565,23 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
this.apply {
|
||||
key = PREF_USE_LOCAL_TOKEN_KEY
|
||||
title = PREF_USE_LOCAL_TOKEN_TITLE
|
||||
summary = runBlocking {
|
||||
withContext(Dispatchers.IO) { getTokenDetail() }
|
||||
mainScope.launch(Dispatchers.IO) {
|
||||
getTokenDetail().let {
|
||||
withContext(Dispatchers.Main) {
|
||||
summary = it
|
||||
}
|
||||
}
|
||||
}
|
||||
setDefaultValue(false)
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val new = newValue as Boolean
|
||||
preferences.edit().putBoolean(key, new).commit().also {
|
||||
summary = runBlocking {
|
||||
withContext(Dispatchers.IO) { getTokenDetail(true) }
|
||||
mainScope.launch(Dispatchers.IO) {
|
||||
getTokenDetail(true).let {
|
||||
withContext(Dispatchers.Main) {
|
||||
summary = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -529,12 +596,12 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun getTokenDetail(force: Boolean = false): String {
|
||||
return try {
|
||||
return runCatching {
|
||||
val storedToken = tokenInterceptor.getAccessToken(force)
|
||||
"Token location: " + storedToken.bucket?.substringAfter("/")?.substringBefore("/")
|
||||
} catch (e: Exception) {
|
||||
}.getOrElse {
|
||||
tokenInterceptor.removeToken()
|
||||
"Error: ${e.localizedMessage ?: "Something Went Wrong"}"
|
||||
"Error: ${it.localizedMessage ?: "Something Went Wrong"}"
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user