Consumyroll: list dubs versions in scanlator (#1252)
This commit is contained in:
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'Consumyroll'
|
||||
pkgNameSuffix = 'all.kamyroll'
|
||||
extClass = '.Consumyroll'
|
||||
extVersionCode = 9
|
||||
extVersionCode = 10
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -18,12 +18,14 @@ class AccessTokenInterceptor(
|
||||
private val preferences: SharedPreferences
|
||||
) : Interceptor {
|
||||
private var accessToken = preferences.getString("access_token", null) ?: ""
|
||||
private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0"
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (accessToken.isBlank()) accessToken = refreshAccessToken()
|
||||
|
||||
val request = chain.request().newBuilder()
|
||||
.header("authorization", accessToken)
|
||||
.header("User-Agent", userAgent)
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(request)
|
||||
@ -57,6 +59,7 @@ class AccessTokenInterceptor(
|
||||
private fun newRequestWithAccessToken(request: Request, accessToken: String): Request {
|
||||
return request.newBuilder()
|
||||
.header("authorization", accessToken)
|
||||
.header("User-Agent", userAgent)
|
||||
.build()
|
||||
}
|
||||
|
||||
@ -64,7 +67,7 @@ class AccessTokenInterceptor(
|
||||
val client = OkHttpClient().newBuilder().build()
|
||||
val headers = Headers.headersOf(
|
||||
"Content-Type", "application/x-www-form-urlencoded",
|
||||
"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0",
|
||||
"User-Agent", userAgent,
|
||||
"Authorization", "Basic a3ZvcGlzdXZ6Yy0teG96Y21kMXk6R21JSTExenVPVnRnTjdlSWZrSlpibzVuLTRHTlZ0cU8="
|
||||
)
|
||||
val postBody = "grant_type=client_id".toRequestBody("application/x-www-form-urlencoded".toMediaType())
|
||||
|
@ -21,6 +21,8 @@ import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@ -90,16 +92,12 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val cleanQuery = query.replace(" ", "+").lowercase()
|
||||
return GET("$crUrl/content/v2/discover/search?q=$cleanQuery&n=6&type=&locale=en-US")
|
||||
return GET("$baseUrl/search/$cleanQuery")
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val parsed = json.decodeFromString<SearchAnimeResult>(response.body!!.string())
|
||||
val animeList = parsed.data.filter { it.type == "top_results" }.map { result ->
|
||||
result.items.filter { it.type == "series" }.map { ani ->
|
||||
ani.toSAnime()
|
||||
}
|
||||
}.flatten()
|
||||
val animeList = parsed.results.map { it.toSAnime() }
|
||||
return AnimesPage(animeList, false)
|
||||
}
|
||||
|
||||
@ -108,8 +106,23 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
val mediaId = json.decodeFromString<LinkData>(anime.url)
|
||||
val resp = client.newCall(GET("$crUrl/content/v2/cms/series/${mediaId.id}?locale=en-US")).execute()
|
||||
val info = json.decodeFromString<AnimeResult>(resp.body!!.string())
|
||||
return Observable.just(anime.apply { author = info.data.first().content_provider })
|
||||
val info = json.decodeFromString<FetchResult>(resp.body!!.string())
|
||||
val ani = info.data.first()
|
||||
val aniDetail = SAnime.create()
|
||||
aniDetail.apply {
|
||||
author = ani.content_provider
|
||||
status = SAnime.COMPLETED
|
||||
if (anime.description.isNullOrBlank()) {
|
||||
var desc = ani.description + "\n"
|
||||
desc += "\nLanguage: Sub" + (if (ani.audio_locales.size > 1) " Dub" else "")
|
||||
desc += "\nMaturity Ratings: ${ani.maturity_ratings.joinToString()}"
|
||||
desc += if (ani.is_simulcast) "\nSimulcast" else ""
|
||||
desc += "\n\nAudio: " + ani.audio_locales.joinToString { it.getLocale() }
|
||||
desc += "\n\nSubs: " + ani.subtitle_locales.joinToString { it.getLocale() }
|
||||
description = desc
|
||||
}
|
||||
}
|
||||
return Observable.just(aniDetail)
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime = throw Exception("not used")
|
||||
@ -123,31 +136,75 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val seasons = json.decodeFromString<SeasonResult>(response.body!!.string())
|
||||
return seasons.data.parallelMap { seasonData ->
|
||||
runCatching {
|
||||
val episodeResp = client.newCall(GET("$crUrl/content/v2/cms/seasons/${seasonData.id}/episodes")).execute()
|
||||
val episodes = json.decodeFromString<EpisodeResult>(episodeResp.body!!.string())
|
||||
episodes.data.sortedBy { it.episode_number }.map { ep ->
|
||||
return if (seasons.data.isNotEmpty()) {
|
||||
seasons.data.parallelMap { seasonData ->
|
||||
runCatching {
|
||||
val episodeResp = client.newCall(GET("$crUrl/content/v2/cms/seasons/${seasonData.id}/episodes")).execute()
|
||||
val episodes = json.decodeFromString<EpisodeResult>(episodeResp.body!!.string())
|
||||
episodes.data.sortedBy { it.episode_number }.map { ep ->
|
||||
SEpisode.create().apply {
|
||||
url = EpisodeData(
|
||||
ep.versions.map { Pair(it.id, it.audio_locale) }
|
||||
).toJsonString()
|
||||
name = if (ep.episode_number > 0 || ep.episode.isNumeric()) {
|
||||
"Season ${seasonData.season_number} Ep ${df.format(ep.episode_number)}: " + ep.title
|
||||
} else { ep.title }
|
||||
episode_number = ep.episode_number
|
||||
date_upload = parseDate(ep.airDate)
|
||||
scanlator = ep.versions.sortedBy { it.audio_locale }
|
||||
.joinToString { it.audio_locale.substringBefore("-") }
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
}.filterNotNull().flatten().reversed()
|
||||
} else {
|
||||
val aniId = response.request.url.toString().substringAfter("series/").substringBefore("/seasons")
|
||||
val resp = client.newCall(GET("$baseUrl/info/$aniId?type=series&fetchAllSeasons=true")).execute()
|
||||
val medias = json.decodeFromString<ConsumetEpsiodes>(resp.body!!.string())
|
||||
|
||||
medias.episodes.entries.map { (key, value) ->
|
||||
val audLang = key.replace("[^A-Za-z ]".toRegex(), "")
|
||||
.replace("Dub", "", true)
|
||||
.replace("subbed", "Japanese", true)
|
||||
val episodes = value.jsonArray.map {
|
||||
json.decodeFromString<EpisodeDto>(it.jsonObject.toString())
|
||||
}
|
||||
episodes.map { ep ->
|
||||
RawEpisode(
|
||||
ep.id,
|
||||
ep.title,
|
||||
ep.season_number,
|
||||
ep.episode_number,
|
||||
ep.releaseDate,
|
||||
audLang
|
||||
)
|
||||
}
|
||||
}.flatten().groupBy { "${it.season}_${it.episode}" }
|
||||
.mapNotNull { group ->
|
||||
val (season, episode) = group.key.split("_")
|
||||
val ep = episode.toFloatOrNull() ?: 0F
|
||||
SEpisode.create().apply {
|
||||
url = EpisodeData(
|
||||
ep.versions.map { Pair(it.id, it.audio_locale) }
|
||||
group.value.map { Pair(it.id, it.audLang) }
|
||||
).toJsonString()
|
||||
name = if (ep.episode_number > 0 || ep.episode.isNumeric()) {
|
||||
"Season ${seasonData.season_number} Ep ${df.format(ep.episode_number)}: " + ep.title
|
||||
} else { ep.title }
|
||||
episode_number = ep.episode_number
|
||||
date_upload = parseDate(ep.airDate)
|
||||
name = if (ep > 0) "Season $season Ep ${df.format(ep)}: " +
|
||||
group.value.first().title else group.value.first().title
|
||||
episode_number = ep
|
||||
date_upload = parseDate(group.value.first().releaseDate)
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
}.filterNotNull().flatten().reversed()
|
||||
}.reversed()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||
val urlJson = json.decodeFromString<EpisodeData>(episode.url)
|
||||
val videoList = urlJson.ids.parallelMap { media ->
|
||||
val dubLang = preferences.getString("preferred_audio", "en-US")!!.getLocale()
|
||||
val videoList = urlJson.ids.filter {
|
||||
val secLang = if (it.second.contains("-")) it.second.getLocale() else it.second
|
||||
secLang == "Japanese" || dubLang.contains(secLang)
|
||||
}.parallelMap { media ->
|
||||
runCatching {
|
||||
extractVideo(media)
|
||||
}.getOrNull()
|
||||
@ -162,7 +219,8 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
private fun extractVideo(media: Pair<String, String>): List<Video> {
|
||||
val (vidId, audLang) = media
|
||||
val response = client.newCall(GET("$baseUrl/episode/$vidId")).execute()
|
||||
val streams = json.decodeFromString<VideoStreams>(response.body!!.string())
|
||||
val body = response.body!!.string()
|
||||
val streams = json.decodeFromString<VideoStreams>(body)
|
||||
|
||||
var subsList = emptyList<Track>()
|
||||
val subLocale = preferences.getString("preferred_sub", "en-US")!!.getLocale()
|
||||
@ -187,9 +245,10 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
.let { hs ->
|
||||
if (hs.isNotBlank()) " - HardSub: $hs" else ""
|
||||
}
|
||||
val audio = if (audLang.contains("-")) audLang.getLocale() else audLang
|
||||
val quality = it.substringAfter("RESOLUTION=")
|
||||
.split(",")[0].split("\n")[0].substringAfter("x") +
|
||||
"p - Aud: ${audLang.getLocale()}$hardsub"
|
||||
"p - Aud: $audio$hardsub"
|
||||
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
|
||||
@ -212,14 +271,14 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
private val df = DecimalFormat("0.#")
|
||||
|
||||
private fun String.getLocale(): String {
|
||||
return locale.firstOrNull { it.first == this }?.second ?: ""
|
||||
}
|
||||
|
||||
private fun String.isNumeric(): Boolean {
|
||||
return this@isNumeric.toDoubleOrNull() != null
|
||||
}
|
||||
|
||||
private fun String.getLocale(): String {
|
||||
return locale.firstOrNull { it.first == this }?.second ?: ""
|
||||
}
|
||||
|
||||
private val locale = arrayOf(
|
||||
Pair("ar-ME", "Arabic"),
|
||||
Pair("ar-SA", "Arabic (Saudi Arabia)"),
|
||||
@ -234,8 +293,8 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
Pair("hi-IN", "Hindi"),
|
||||
Pair("it-IT", "Italian"),
|
||||
Pair("ko-KR", "Korean"),
|
||||
Pair("pt-BR", "Português (Brasil)"),
|
||||
Pair("pt-PT", "Português (Portugal)"),
|
||||
Pair("pt-BR", "Portuguese (Brasil)"),
|
||||
Pair("pt-PT", "Portuguese (Portugal)"),
|
||||
Pair("pl-PL", "Polish"),
|
||||
Pair("ru-RU", "Russian"),
|
||||
Pair("tr-TR", "Turkish"),
|
||||
@ -273,7 +332,16 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
desc += if (this@toSAnime.series_metadata.is_simulcast) "\nSimulcast" else ""
|
||||
desc += "\n\nAudio: " + this@toSAnime.series_metadata.audio_locales.joinToString { it.getLocale() }
|
||||
desc += "\n\nSubs: " + this@toSAnime.series_metadata.subtitle_locales.joinToString { it.getLocale() }
|
||||
description += desc
|
||||
description = desc
|
||||
}
|
||||
|
||||
private fun SearchAnimeResult.SearchAnime.toSAnime(): SAnime =
|
||||
SAnime.create().apply {
|
||||
title = this@toSAnime.title
|
||||
thumbnail_url = this@toSAnime.image
|
||||
url = LinkData(this@toSAnime.id, this@toSAnime.type).toJsonString()
|
||||
genre = this@toSAnime.genres?.joinToString() ?: "Anime"
|
||||
status = SAnime.COMPLETED
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
@ -316,7 +384,7 @@ class Consumyroll : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
entries = locale.map { it.second }.toTypedArray()
|
||||
entryValues = locale.map { it.first }.toTypedArray()
|
||||
setDefaultValue("en-US")
|
||||
summary = "%s"
|
||||
summary = "Only the selected language and subbed versions will be loaded"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.all.kamyroll
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Serializable
|
||||
data class AccessToken(
|
||||
@ -59,17 +60,39 @@ data class AnimeResult(
|
||||
|
||||
@Serializable
|
||||
data class SearchAnimeResult(
|
||||
val total: Int,
|
||||
val data: ArrayList<Result>
|
||||
val results: ArrayList<SearchAnime>,
|
||||
) {
|
||||
@Serializable
|
||||
data class Result(
|
||||
data class SearchAnime(
|
||||
val id: String,
|
||||
val type: String,
|
||||
val count: Int,
|
||||
val items: ArrayList<Anime>
|
||||
val title: String,
|
||||
val description: String,
|
||||
val image: String,
|
||||
val hasDub: Boolean,
|
||||
val genres: ArrayList<String>?
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FetchAnime(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val images: Images,
|
||||
val content_provider: String,
|
||||
val audio_locales: ArrayList<String>,
|
||||
val subtitle_locales: ArrayList<String>,
|
||||
val maturity_ratings: ArrayList<String>,
|
||||
val is_simulcast: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FetchResult(
|
||||
val total: Int,
|
||||
val data: ArrayList<FetchAnime>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SeasonResult(
|
||||
val total: Int,
|
||||
@ -106,6 +129,30 @@ data class EpisodeResult(
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ConsumetEpsiodes(
|
||||
val episodes: JsonObject
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeDto(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val season_number: Int,
|
||||
val episode_number: Float,
|
||||
val releaseDate: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RawEpisode(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val season: Int,
|
||||
val episode: Float,
|
||||
val releaseDate: String,
|
||||
val audLang: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeData(
|
||||
val ids: List<Pair<String, String>>
|
||||
|
Reference in New Issue
Block a user