fix(en/animeflix): Fix popular & latest animes page (#2094)
This commit is contained in:
@ -1,12 +1,14 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
alias(libs.plugins.android.application)
|
||||||
apply plugin: 'kotlinx-serialization'
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
extName = 'AnimeFlix'
|
extName = 'AnimeFlix'
|
||||||
pkgNameSuffix = 'en.animeflix'
|
pkgNameSuffix = 'en.animeflix'
|
||||||
extClass = '.AnimeFlix'
|
extClass = '.AnimeFlix'
|
||||||
extVersionCode = 5
|
extVersionCode = 6
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.animeflix
|
package eu.kanade.tachiyomi.animeextension.en.animeflix
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -45,26 +44,27 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
|
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/page/$page/")
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/page/$page/")
|
override fun popularAnimeSelector() = "div#content_box > div.post-cards > article"
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "div#page > div#content_box > article"
|
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
|
|
||||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
|
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
|
||||||
thumbnail_url = element.selectFirst("img")!!.attr("src")
|
// prevent base64 images
|
||||||
|
thumbnail_url = element.selectFirst("img")!!.run {
|
||||||
|
attr("data-pagespeed-high-res-src").ifEmpty { attr("src") }
|
||||||
|
}
|
||||||
title = element.selectFirst("header")!!.text()
|
title = element.selectFirst("header")!!.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector(): String = "div.nav-links > span.current ~ a"
|
override fun popularAnimeNextPageSelector() = "div.nav-links > a.next"
|
||||||
|
|
||||||
// =============================== Latest ===============================
|
// =============================== Latest ===============================
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-release/page/$page/")
|
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-release/page/$page/")
|
||||||
|
|
||||||
override fun latestUpdatesSelector(): String = popularAnimeSelector()
|
override fun latestUpdatesSelector(): String = popularAnimeSelector()
|
||||||
@ -74,7 +74,6 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
|
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||||
|
|
||||||
// =============================== Search ===============================
|
// =============================== Search ===============================
|
||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
val cleanQuery = query.replace(" ", "+").lowercase()
|
val cleanQuery = query.replace(" ", "+").lowercase()
|
||||||
|
|
||||||
@ -97,7 +96,6 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||||
|
|
||||||
// ============================== Filters ===============================
|
// ============================== Filters ===============================
|
||||||
|
|
||||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||||
AnimeFilter.Header("Text search ignores filters"),
|
AnimeFilter.Header("Text search ignores filters"),
|
||||||
GenreFilter(),
|
GenreFilter(),
|
||||||
@ -141,133 +139,122 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================== Anime Details ============================
|
// =========================== Anime Details ============================
|
||||||
|
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
|
||||||
|
title = document.selectFirst("div.single_post > header > h1")!!.text()
|
||||||
|
thumbnail_url = document.selectFirst("img.imdbwp__img")?.attr("src")
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
val infosDiv = document.selectFirst("div.thecontent h3:contains(Anime Info) ~ ul")!!
|
||||||
val animeInfo = document.select("div.thecontent h3:contains(Anime Info) ~ ul li").joinToString("\n") { it.text() }
|
status = when (infosDiv.getInfo("Status").toString()) {
|
||||||
|
"Completed" -> SAnime.COMPLETED
|
||||||
return SAnime.create().apply {
|
"Currently Airing" -> SAnime.ONGOING
|
||||||
title = document.selectFirst("div.single_post > header > h1")!!.text()
|
else -> SAnime.UNKNOWN
|
||||||
description = document.select("div.thecontent h3:contains(Summary) ~ p:not(:has(*)):not(:empty)").joinToString("\n\n") { it.ownText() } + "\n\n$animeInfo"
|
|
||||||
}
|
}
|
||||||
|
artist = infosDiv.getInfo("Studios")
|
||||||
|
author = infosDiv.getInfo("Producers")
|
||||||
|
genre = infosDiv.getInfo("Genres")
|
||||||
|
val animeInfo = infosDiv.select("li").joinToString("\n") { it.text() }
|
||||||
|
description = document.select("div.thecontent h3:contains(Summary) ~ p:not(:has(*)):not(:empty)")
|
||||||
|
.joinToString("\n\n") { it.ownText() } + "\n\n$animeInfo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Element.getInfo(info: String) =
|
||||||
|
selectFirst("li:contains($info)")?.ownText()?.trim()
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
|
val seasonRegex by lazy { Regex("""season (\d+)""", RegexOption.IGNORE_CASE) }
|
||||||
|
val qualityRegex by lazy { """(\d+)p""".toRegex() }
|
||||||
|
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
val document = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup()
|
val document = client.newCall(GET(baseUrl + anime.url)).execute()
|
||||||
val episodeList = mutableListOf<SEpisode>()
|
.use { it.asJsoup() }
|
||||||
val serversList = mutableListOf<List<EpUrl>>()
|
|
||||||
val seasonRegex = Regex("""season (\d+)""", RegexOption.IGNORE_CASE)
|
|
||||||
val qualityRegex = """(\d+)p""".toRegex()
|
|
||||||
val driveList = mutableListOf<Pair<String, String>>()
|
|
||||||
|
|
||||||
val seasonList = document.select("div.inline > h3:contains(Season),div.thecontent > h3:contains(Season)")
|
val seasonList = document.select("div.inline > h3:contains(Season),div.thecontent > h3:contains(Season)")
|
||||||
|
|
||||||
if (seasonList.distinctBy { seasonRegex.find(it.text())!!.groupValues[1] }.size > 1) {
|
val episodeList = if (seasonList.distinctBy { seasonRegex.find(it.text())!!.groupValues[1] }.size > 1) {
|
||||||
val seasonsLinks = document.select("div.thecontent p:has(span:contains(Gdrive))").groupBy {
|
val seasonsLinks = document.select("div.thecontent p:has(span:contains(Gdrive))").groupBy {
|
||||||
seasonRegex.find(it.previousElementSibling()!!.text())!!.groupValues[1]
|
seasonRegex.find(it.previousElementSibling()!!.text())!!.groupValues[1]
|
||||||
}.values.toList()
|
}
|
||||||
seasonsLinks.forEach { season ->
|
|
||||||
|
|
||||||
val serverListSeason = mutableListOf<List<EpUrl>>()
|
seasonsLinks.flatMap { (seasonNumber, season) ->
|
||||||
|
val serverListSeason = season.map {
|
||||||
season.forEach {
|
val previousText = it.previousElementSibling()!!.text()
|
||||||
val quality = qualityRegex.find(it.previousElementSibling()!!.text())?.groupValues?.get(1) ?: "Unknown quality"
|
val quality = qualityRegex.find(previousText)?.groupValues?.get(1) ?: "Unknown quality"
|
||||||
val seasonNumber = seasonRegex.find(it.previousElementSibling()!!.text())!!.groupValues[1]
|
|
||||||
|
|
||||||
val url = it.selectFirst("a")!!.attr("href")
|
val url = it.selectFirst("a")!!.attr("href")
|
||||||
val episodesDocument = client.newCall(GET(url)).execute().asJsoup()
|
val episodesDocument = client.newCall(GET(url)).execute()
|
||||||
serverListSeason.add(
|
.use { it.asJsoup() }
|
||||||
episodesDocument.select("div.entry-content > h3 > a").map {
|
episodesDocument.select("div.entry-content > h3 > a").map {
|
||||||
EpUrl(quality, it.attr("href"), "Season $seasonNumber ${it.text()}")
|
EpUrl(quality, it.attr("href"), "Season $seasonNumber ${it.text()}")
|
||||||
},
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transpose(serverListSeason).forEachIndexed { index, serverList ->
|
transposeEpisodes(serverListSeason)
|
||||||
episodeList.add(
|
|
||||||
SEpisode.create().apply {
|
|
||||||
name = serverList.first().name
|
|
||||||
episode_number = (index + 1).toFloat()
|
|
||||||
setUrlWithoutDomain(
|
|
||||||
json.encodeToString(serverList),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.select("div.thecontent p:has(span:contains(Gdrive))").forEach {
|
val driveList = document.select("div.thecontent p:has(span:contains(Gdrive))").map {
|
||||||
val quality = qualityRegex.find(it.previousElementSibling()!!.text())?.groupValues?.get(1) ?: "Unknown quality"
|
val quality = qualityRegex.find(it.previousElementSibling()!!.text())?.groupValues?.get(1) ?: "Unknown quality"
|
||||||
driveList.add(Pair(it.selectFirst("a")!!.attr("href"), quality))
|
Pair(it.selectFirst("a")!!.attr("href"), quality)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load episodes
|
// Load episodes
|
||||||
driveList.forEach { drive ->
|
val serversList = driveList.map { drive ->
|
||||||
val episodesDocument = client.newCall(GET(drive.first)).execute().asJsoup()
|
val episodesDocument = client.newCall(GET(drive.first)).execute()
|
||||||
serversList.add(
|
.use { it.asJsoup() }
|
||||||
episodesDocument.select("div.entry-content > h3 > a").map {
|
episodesDocument.select("div.entry-content > h3 > a").map {
|
||||||
EpUrl(drive.second, it.attr("href"), it.text())
|
EpUrl(drive.second, it.attr("href"), it.text())
|
||||||
},
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transpose(serversList).forEachIndexed { index, serverList ->
|
transposeEpisodes(serversList)
|
||||||
episodeList.add(
|
|
||||||
SEpisode.create().apply {
|
|
||||||
name = serverList.first().name
|
|
||||||
episode_number = (index + 1).toFloat()
|
|
||||||
setUrlWithoutDomain(
|
|
||||||
json.encodeToString(serverList),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observable.just(episodeList.reversed())
|
return Observable.just(episodeList.reversed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun transposeEpisodes(serversList: List<List<EpUrl>>) =
|
||||||
|
transpose(serversList).mapIndexed { index, serverList ->
|
||||||
|
SEpisode.create().apply {
|
||||||
|
name = serverList.first().name
|
||||||
|
episode_number = (index + 1).toFloat()
|
||||||
|
setUrlWithoutDomain(json.encodeToString(serverList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun episodeListSelector(): String = throw Exception("Not Used")
|
override fun episodeListSelector(): String = throw Exception("Not Used")
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not Used")
|
override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not Used")
|
||||||
|
|
||||||
// ============================ Video Links =============================
|
// ============================ Video Links =============================
|
||||||
|
|
||||||
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
val failedMediaUrl = mutableListOf<Pair<String, String>>()
|
|
||||||
val urls = json.decodeFromString<List<EpUrl>>(episode.url)
|
val urls = json.decodeFromString<List<EpUrl>>(episode.url)
|
||||||
|
|
||||||
val leechUrls = urls.map {
|
val leechUrls = urls.map {
|
||||||
val firstLeech = client.newCall(GET(it.url)).execute().asJsoup().selectFirst(
|
val firstLeech = client.newCall(GET(it.url)).execute()
|
||||||
"script:containsData(downlaod_button)",
|
.use { it.asJsoup() }
|
||||||
)!!.data().substringAfter("<a href=\"").substringBefore("\">")
|
.selectFirst("script:containsData(downlaod_button)")!!
|
||||||
val link = "https://" + firstLeech.toHttpUrl().host + client.newCall(GET(firstLeech)).execute().body.string()
|
.data()
|
||||||
.substringAfter("replace(\"").substringBefore("\"")
|
.substringAfter("<a href=\"")
|
||||||
|
.substringBefore("\">")
|
||||||
|
|
||||||
|
val path = client.newCall(GET(firstLeech)).execute()
|
||||||
|
.use { it.body.string() }
|
||||||
|
.substringAfter("replace(\"")
|
||||||
|
.substringBefore("\"")
|
||||||
|
|
||||||
|
val link = "https://" + firstLeech.toHttpUrl().host + path
|
||||||
EpUrl(it.quality, link, it.name)
|
EpUrl(it.quality, link, it.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
videoList.addAll(
|
val videoList = leechUrls.parallelMap { url ->
|
||||||
leechUrls.parallelMap { url ->
|
runCatching {
|
||||||
runCatching {
|
if (url.url.toHttpUrl().encodedPath == "/404") return@runCatching null
|
||||||
if (url.url.toHttpUrl().encodedPath == "/404") return@runCatching null
|
val (videos, mediaUrl) = extractVideo(url)
|
||||||
val (videos, mediaUrl) = extractVideo(url)
|
when {
|
||||||
if (videos.isEmpty()) failedMediaUrl.add(Pair(mediaUrl, url.quality))
|
videos.isEmpty() -> extractGDriveLink(mediaUrl, url.quality)
|
||||||
return@runCatching videos
|
else -> videos
|
||||||
}.getOrNull()
|
}
|
||||||
}
|
}.getOrNull()
|
||||||
.filterNotNull()
|
}.filterNotNull().flatten()
|
||||||
.flatten(),
|
|
||||||
)
|
|
||||||
|
|
||||||
videoList.addAll(
|
|
||||||
failedMediaUrl.mapNotNull { (url, quality) ->
|
|
||||||
runCatching {
|
|
||||||
extractGDriveLink(url, quality)
|
|
||||||
}.getOrNull()
|
|
||||||
}.flatten(),
|
|
||||||
)
|
|
||||||
|
|
||||||
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
|
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
|
||||||
|
|
||||||
@ -281,26 +268,19 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
|
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
|
||||||
|
|
||||||
// ============================= Utilities ==============================
|
// ============================= Utilities ==============================
|
||||||
|
|
||||||
// https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/uhdmovies/src/eu/kanade/tachiyomi/animeextension/en/uhdmovies/UHDMovies.kt
|
// https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/uhdmovies/src/eu/kanade/tachiyomi/animeextension/en/uhdmovies/UHDMovies.kt
|
||||||
private fun extractVideo(epUrl: EpUrl): Pair<List<Video>, String> {
|
private fun extractVideo(epUrl: EpUrl): Pair<List<Video>, String> {
|
||||||
val videoList = mutableListOf<Video>()
|
|
||||||
|
|
||||||
val qualityRegex = """(\d+)p""".toRegex()
|
|
||||||
val matchResult = qualityRegex.find(epUrl.name)
|
val matchResult = qualityRegex.find(epUrl.name)
|
||||||
val quality = matchResult?.groupValues?.get(1) ?: epUrl.quality
|
val quality = matchResult?.groupValues?.get(1) ?: epUrl.quality
|
||||||
|
|
||||||
for (type in 1..3) {
|
return (1..3).toList().flatMap { type ->
|
||||||
videoList.addAll(
|
extractWorkerLinks(epUrl.url, quality, type)
|
||||||
extractWorkerLinks(epUrl.url, quality, type),
|
}.let { Pair(it, epUrl.url) }
|
||||||
)
|
|
||||||
}
|
|
||||||
return Pair(videoList, epUrl.url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractWorkerLinks(mediaUrl: String, quality: String, type: Int): List<Video> {
|
private fun extractWorkerLinks(mediaUrl: String, quality: String, type: Int): List<Video> {
|
||||||
val reqLink = mediaUrl.replace("/file/", "/wfile/") + "?type=$type"
|
val reqLink = mediaUrl.replace("/file/", "/wfile/") + "?type=$type"
|
||||||
val resp = client.newCall(GET(reqLink)).execute().asJsoup()
|
val resp = client.newCall(GET(reqLink)).execute().use { it.asJsoup() }
|
||||||
val sizeMatch = SIZE_REGEX.find(resp.select("div.card-header").text().trim())
|
val sizeMatch = SIZE_REGEX.find(resp.select("div.card-header").text().trim())
|
||||||
val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
|
val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
|
||||||
return resp.select("div.card-body div.mb-4 > a").mapIndexed { index, linkElement ->
|
return resp.select("div.card-body div.mb-4 > a").mapIndexed { index, linkElement ->
|
||||||
@ -321,12 +301,12 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
private fun extractGDriveLink(mediaUrl: String, quality: String): List<Video> {
|
private fun extractGDriveLink(mediaUrl: String, quality: String): List<Video> {
|
||||||
val tokenClient = client.newBuilder().addInterceptor(TokenInterceptor()).build()
|
val tokenClient = client.newBuilder().addInterceptor(TokenInterceptor()).build()
|
||||||
val response = tokenClient.newCall(GET(mediaUrl)).execute().asJsoup()
|
val response = tokenClient.newCall(GET(mediaUrl)).execute().use { it.asJsoup() }
|
||||||
val gdBtn = response.selectFirst("div.card-body a.btn")!!
|
val gdBtn = response.selectFirst("div.card-body a.btn")!!
|
||||||
val gdLink = gdBtn.attr("href")
|
val gdLink = gdBtn.attr("href")
|
||||||
val sizeMatch = SIZE_REGEX.find(gdBtn.text())
|
val sizeMatch = SIZE_REGEX.find(gdBtn.text())
|
||||||
val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
|
val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
|
||||||
val gdResponse = client.newCall(GET(gdLink)).execute().asJsoup()
|
val gdResponse = client.newCall(GET(gdLink)).execute().use { it.asJsoup() }
|
||||||
val link = gdResponse.select("form#download-form")
|
val link = gdResponse.select("form#download-form")
|
||||||
return if (link.isNullOrEmpty()) {
|
return if (link.isNullOrEmpty()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
@ -339,7 +319,7 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||||
|
|
||||||
return this.sortedWith(
|
return sortedWith(
|
||||||
compareBy { it.quality.contains(quality) },
|
compareBy { it.quality.contains(quality) },
|
||||||
).reversed()
|
).reversed()
|
||||||
}
|
}
|
||||||
@ -366,7 +346,7 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// From Dopebox
|
// From Dopebox
|
||||||
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
|
private inline fun <A, B> Iterable<A>.parallelMap(crossinline f: suspend (A) -> B): List<B> =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
|
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
|
||||||
}
|
}
|
||||||
@ -374,18 +354,20 @@ class AnimeFlix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
companion object {
|
companion object {
|
||||||
private val SIZE_REGEX = "\\[((?:.(?!\\[))+)][ ]*\$".toRegex(RegexOption.IGNORE_CASE)
|
private val SIZE_REGEX = "\\[((?:.(?!\\[))+)][ ]*\$".toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
private const val PREF_QUALITY_KEY = "pref_quality"
|
||||||
|
private const val PREF_QUALITY_TITLE = "Preferred quality"
|
||||||
private const val PREF_QUALITY_DEFAULT = "1080"
|
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||||
|
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
|
||||||
|
private val PREF_QUALITY_VALUES = arrayOf("1080", "720", "480", "360")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Settings ==============================
|
// ============================== Settings ==============================
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
ListPreference(screen.context).apply {
|
ListPreference(screen.context).apply {
|
||||||
key = PREF_QUALITY_KEY
|
key = PREF_QUALITY_KEY
|
||||||
title = "Preferred quality"
|
title = PREF_QUALITY_TITLE
|
||||||
entries = arrayOf("1080p", "720p", "480p", "360p")
|
entries = PREF_QUALITY_ENTRIES
|
||||||
entryValues = arrayOf("1080", "720", "480", "360")
|
entryValues = PREF_QUALITY_VALUES
|
||||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user