feat(pt/animeszone): Add newer extractor endpoints + refactor (#2135)

This commit is contained in:
Claudemirovsky
2023-09-03 08:20:39 -03:00
committed by GitHub
parent 5c90af8709
commit 52c5d3ba49
3 changed files with 176 additions and 228 deletions

View File

@ -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 = 'AnimesZone' extName = 'AnimesZone'
pkgNameSuffix = 'pt.animeszone' pkgNameSuffix = 'pt.animeszone'
extClass = '.AnimesZone' extClass = '.AnimesZone'
extVersionCode = 5 extVersionCode = 6
libVersion = '13' libVersion = '13'
containsNsfw = true containsNsfw = true
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.animeszone package eu.kanade.tachiyomi.animeextension.pt.animeszone
import android.app.Application import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import app.cash.quickjs.QuickJs import app.cash.quickjs.QuickJs
@ -17,11 +16,9 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -30,7 +27,6 @@ import okhttp3.Response
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -47,38 +43,32 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
.add("Origin", baseUrl)
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)
} }
companion object {
private val EPISODE_REGEX = Regex("""Episódio ?\d+\.?\d* ?""")
}
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/tendencia/") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/tendencia/")
override fun popularAnimeSelector(): String = "div.items > div.seriesList" override fun popularAnimeSelector(): String = "div.items > div.seriesList"
override fun popularAnimeNextPageSelector(): String? = null override fun popularAnimeNextPageSelector(): String? = null
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
return SAnime.create().apply { setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href"))
setUrlWithoutDomain( thumbnail_url = element.selectFirst("div.cover-image")?.let {
element.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath, it.attr("style").substringAfter("url('").substringBefore("'")
) } ?: ""
thumbnail_url = element.selectFirst("div.cover-image")?.let { title = element.selectFirst("span.series-title")!!.text()
it.attr("style").substringAfter("url('").substringBefore("'")
} ?: ""
title = element.selectFirst("span.series-title")!!.text()
}
} }
// =============================== Latest =============================== // =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return if (page == 1) { return if (page == 1) {
GET("$baseUrl/animes-legendados/") GET("$baseUrl/animes-legendados/")
@ -91,41 +81,27 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesNextPageSelector(): String = "div.paginadorplay > a.active + a" override fun latestUpdatesNextPageSelector(): String = "div.paginadorplay > a.active + a"
override fun latestUpdatesFromElement(element: Element): SAnime { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
return SAnime.create().apply { setUrlWithoutDomain(element.selectFirst("div.aniItem > a[href]")!!.attr("abs:href"))
setUrlWithoutDomain( thumbnail_url = element.selectFirst("div.aniItemImg img[src]")?.attr("abs:src")
element.selectFirst("div.aniItem > a[href]")!!.attr("href").toHttpUrl().encodedPath, title = element.selectFirst("h2.aniTitulo")!!.text()
)
thumbnail_url = element.selectFirst("div.aniItemImg img[src]")?.attr("src") ?: ""
title = element.selectFirst("h2.aniTitulo")!!.text()
}
} }
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used")
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
val params = AnimesZoneFilters.getSearchParameters(filters) val params = AnimesZoneFilters.getSearchParameters(filters)
return client.newCall(searchAnimeRequest(page, query, params))
.asObservableSuccess()
.map { response ->
searchAnimeParse(response)
}
}
private fun searchAnimeRequest(page: Int, query: String, filters: AnimesZoneFilters.FilterSearchParams): Request {
val cleanQuery = query.replace(" ", "%20") val cleanQuery = query.replace(" ", "%20")
val queryQuery = if (query.isBlank()) { val queryQuery = if (query.isBlank()) {
"" ""
} else { } else {
"&_pesquisa=$cleanQuery" "&_pesquisa=$cleanQuery"
} }
val url = "$baseUrl/?s=${filters.genre}${filters.year}${filters.version}${filters.studio}${filters.type}${filters.adult}$queryQuery" val url = "$baseUrl/?s=${params.genre}${params.year}${params.version}${params.studio}${params.type}${params.adult}$queryQuery"
val httpParams = url.substringAfter("$baseUrl/?").split("&").joinToString(",") { val httpParams = url.substringAfter("$baseUrl/?").split("&").joinToString(",") {
val data = it.split("=") val (key, value) = it.split("=", limit = 2)
"\"${data[0]}\":\"${data[1]}\"" "\"$key\":\"$value\""
} }
val softRefresh = if (page == 1) 0 else 1 val softRefresh = if (page == 1) 0 else 1
val jsonBody = """ val jsonBody = """
@ -134,22 +110,22 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
"data":{ "data":{
"facets":{ "facets":{
"generos":[ "generos":[
${queryToJsonList(filters.genre)} ${queryToJsonList(params.genre)}
], ],
"versao":[ "versao":[
${queryToJsonList(filters.year)} ${queryToJsonList(params.year)}
], ],
"tipo":[ "tipo":[
${queryToJsonList(filters.version)} ${queryToJsonList(params.version)}
], ],
"estudio":[ "estudio":[
${queryToJsonList(filters.studio)} ${queryToJsonList(params.studio)}
], ],
"tipototal":[ "tipototal":[
${queryToJsonList(filters.type)} ${queryToJsonList(params.type)}
], ],
"adulto":[ "adulto":[
${queryToJsonList(filters.adult)} ${queryToJsonList(params.adult)}
], ],
"pesquisa":"$query", "pesquisa":"$query",
"paginar":[ "paginar":[
@ -180,112 +156,90 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
""".trimIndent().toRequestBody("application/json".toMediaType()) """.trimIndent().toRequestBody("application/json".toMediaType())
val postHeaders = headers.newBuilder() return POST(url, headers, jsonBody)
.add("Accept", "*/*")
.add("Content-Type", "application/json")
.add("Host", baseUrl.toHttpUrl().host)
.add("Origin", baseUrl)
.add("Referer", url)
.build()
return POST(url, body = jsonBody, headers = postHeaders)
} }
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
val parsed = json.decodeFromString<PostResponse>(response.body.string()) val parsed = response.parseAs<PostResponse>()
val document = Jsoup.parse(parsed.template) val document = Jsoup.parse(parsed.template)
val animes = document.select(searchAnimeSelector()).map { element -> val animes = document.select(searchAnimeSelector()).map(::searchAnimeFromElement)
searchAnimeFromElement(element)
}
return AnimesPage(animes, parsed.settings.pager.page < parsed.settings.pager.total_pages) val hasNextPage = parsed.settings.pager.run { page < total_pages }
return AnimesPage(animes, hasNextPage)
} }
override fun searchAnimeSelector(): String = "div.aniItem" override fun searchAnimeSelector(): String = "div.aniItem"
override fun searchAnimeNextPageSelector(): String? = null override fun searchAnimeNextPageSelector(): String? = null
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
return SAnime.create().apply { setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href"))
setUrlWithoutDomain( thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src") ?: ""
element.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath, title = element.selectFirst("div.aniInfos")?.text() ?: "Anime"
)
thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: ""
title = element.selectFirst("div.aniInfos")?.text() ?: "Anime"
}
} }
// ============================== FILTERS =============================== // ============================== FILTERS ===============================
override fun getFilterList(): AnimeFilterList = AnimesZoneFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = AnimesZoneFilters.FILTER_LIST
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
override fun animeDetailsParse(document: Document): SAnime { title = document.selectFirst("div.container > div div.bottom-div > h4")?.ownText()
return SAnime.create().apply { ?: document.selectFirst("div#info > h1")?.text()
title = document.selectFirst("div.container > div div.bottom-div > h4")?.ownText() ?: "Anime" ?: "Anime"
thumbnail_url = document.selectFirst("div.container > div > img[src]")?.attr("src") thumbnail_url = document.selectFirst("div.container > div > img[src]")?.attr("abs:src")
description = document.selectFirst("section#sinopse p:matches(.)")?.text() description = document.selectFirst("section#sinopse p:matches(.)")?.text()
genre = document.select("div.card-body table > tbody > tr:has(>td:contains(Genres)) td > a").joinToString(", ") { it.text() } ?: document.selectFirst("div.content.right > dialog > p:matches(.)")?.text()
} genre = document.select("div.card-body table > tbody > tr:has(>td:contains(Genres)) td > a").joinToString { it.text() }
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val episodeList = mutableListOf<SEpisode>() val document = response.use { it.asJsoup() }
val document = response.asJsoup()
// Used only for fallback
var counter = 1
val singleVideo = document.selectFirst("div.anime__video__player") val singleVideo = document.selectFirst("div.anime__video__player")
// First check if single episode // First check if single episode
if (singleVideo != null) { return if (singleVideo != null) {
val episode = SEpisode.create() SEpisode.create().apply {
episode.name = document.selectFirst("div#info h1")?.text() ?: "Episódio" name = document.selectFirst("div#info h1")?.text() ?: "Episódio"
episode.episode_number = 1F episode_number = 1F
episode.setUrlWithoutDomain(response.request.url.encodedPath) setUrlWithoutDomain(document.location())
episodeList.add(episode) }.let(::listOf)
} else { } else {
document.select(episodeListSelector()).forEach { ep -> buildList {
val name = ep.selectFirst("h2.aniTitulo")?.text()?.trim() document.select(episodeListSelector()).forEach { ep ->
// Check if it's multi-season val name = ep.selectFirst("h2.aniTitulo")?.text()?.trim()
if (name != null && name.startsWith("temporada ", true)) { // Check if it's multi-season
var nextPageUrl: String? = ep.selectFirst("a[href]")!!.attr("href") var nextPageUrl = when {
name != null && name.startsWith("temporada ", true) -> ep.selectFirst("a[href]")!!.attr("href")
else -> {
add(episodeFromElement(ep, size + 1))
document.nextPageUrl()
}
}
while (nextPageUrl != null) { while (nextPageUrl != null) {
val seasonDocument = client.newCall(GET(nextPageUrl)).execute().asJsoup() val seasonDocument = client.newCall(GET(nextPageUrl)).execute()
.use { it.asJsoup() }
seasonDocument.select(episodeListSelector()).forEach { seasonEp -> seasonDocument.select(episodeListSelector()).forEach { seasonEp ->
episodeList.add(episodeFromElement(seasonEp, counter, name)) add(episodeFromElement(seasonEp, size + 1, name))
counter++
} }
nextPageUrl = seasonDocument.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href") nextPageUrl = seasonDocument.nextPageUrl()
}
} else {
episodeList.add(episodeFromElement(ep, counter))
counter++
var nextPageUrl: String? = document.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href")
while (nextPageUrl != null) {
val document = client.newCall(GET(nextPageUrl)).execute().asJsoup()
document.select(episodeListSelector()).forEach { ep ->
episodeList.add(episodeFromElement(ep, counter))
counter++
}
nextPageUrl = document.selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href")
} }
} }
reverse()
} }
} }
return episodeList.reversed()
} }
private fun Document.nextPageUrl() = selectFirst("div.paginadorplay > a:contains(Próxima Pagina)")?.absUrl("href")
override fun episodeListSelector(): String = "main.site-main ul.post-lst > li" override fun episodeListSelector(): String = "main.site-main ul.post-lst > li"
private fun episodeFromElement(element: Element, counter: Int, info: String? = null): SEpisode { private fun episodeFromElement(element: Element, counter: Int, info: String? = null): SEpisode {
@ -299,122 +253,124 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
it.text().trim().toFloatOrNull() it.text().trim().toFloatOrNull()
} ?: counter.toFloat() } ?: counter.toFloat()
scanlator = info scanlator = info
setUrlWithoutDomain( setUrlWithoutDomain(element.selectFirst("article > a")!!.attr("href"))
element.selectFirst("article > a")!!.attr("href").toHttpUrl().encodedPath,
)
} }
} }
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 videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.use { it.asJsoup() }
val videoList = mutableListOf<Video>()
document.select("div#playeroptions li[data-post]").forEach { vid -> val videoList = document.select("div#playeroptions li[data-post]").flatMap { vid ->
val jsonHeaders = headers.newBuilder() val jsonHeaders = headersBuilder()
.add("Accept", "application/json, text/javascript, */*; q=0.01") .add("Accept", "application/json, text/javascript, */*; q=0.01")
.add("Host", baseUrl.toHttpUrl().host)
.add("Referer", response.request.url.toString())
.add("X-Requested-With", "XMLHttpRequest") .add("X-Requested-With", "XMLHttpRequest")
.build() .build()
val response = client.newCall(
GET("$baseUrl/wp-json/dooplayer/v2/${vid.attr("data-post")}/${vid.attr("data-type")}/${vid.attr("data-nume")}", headers = jsonHeaders), val post = vid.attr("data-post")
val type = vid.attr("data-type")
val nume = vid.attr("data-nume")
val apires = client.newCall(
GET("$baseUrl/wp-json/dooplayer/v2/$post/$type/$nume", jsonHeaders),
).execute() ).execute()
val url = json.decodeFromString<VideoResponse>(response.body.string()).embed_url
val url = apires.parseAs<VideoResponse>().embed_url
when { when {
url.startsWith("https://dood") -> { url.startsWith("https://dood") -> DoodExtractor(client).videosFromUrl(url, vid.text().trim())
videoList.addAll(DoodExtractor(client).videosFromUrl(url, vid.text().trim())) "https://gojopoolt" in url -> {
client.newCall(GET(url, headers)).execute()
.use { it.asJsoup() }
.selectFirst("script:containsData(sources:)")
?.data()
?.let(BloggerJWPlayerExtractor::videosFromScript)
?: emptyList()
} }
url.startsWith(baseUrl) -> {
val videoDocument = client.newCall(GET(url, headers)).execute()
.use { it.asJsoup() }
url.toHttpUrl().host == baseUrl.toHttpUrl().host -> { val script = videoDocument.selectFirst("script:containsData(decodeURIComponent)")?.data()
val videoDocument = client.newCall( ?.let(::getDecrypted)
GET(url), ?: videoDocument.selectFirst("script:containsData(sources:)")?.data()
).execute().asJsoup() ?: return@flatMap emptyList()
val decrypted = getDecrypted(videoDocument) ?: return@forEach
when { when {
"/bloggerjwplayer" in url -> "/bloggerjwplayer" in url || "jwplayer-2" in url || "/ctaplayer" in url -> {
videoList.addAll(BloggerJWPlayerExtractor.videosFromScript(decrypted)) BloggerJWPlayerExtractor.videosFromScript(script)
"jwplayer-2" in url -> }
videoList.addAll(BloggerJWPlayerExtractor.videosFromScript(decrypted)) "/m3u8" in url -> PlaylistExtractor.videosFromScript(script)
"/m3u8" in url -> else -> emptyList()
videoList.addAll(PlaylistExtractor.videosFromScript(decrypted))
} }
} }
// url.toHttpUrl().host == "gojoanimes.biz" -> { "blogger.com" in url -> extractBloggerVideos(url, vid.text().trim())
// val videoDocument = client.newCall( else -> emptyList()
// GET(url),
// ).execute().asJsoup()
//
// val decrypted = getDecrypted(videoDocument) ?: return@forEach
//
// Regex("""file: ?["']([^"']+?)["']""").findAll(decrypted).forEach { videoFile ->
// val videoHeaders = headers.newBuilder()
// .add("Accept", "*/*")
// .add("Origin", "https://gojoanimes.biz")
// .add("Referer", "https://gojoanimes.biz/")
// .build()
// videoList.add(
// Video(videoFile.groupValues[1], vid.text().trim(), videoFile.groupValues[1], headers = videoHeaders),
// )
// }
// }
url.toHttpUrl().host == "www.blogger.com" -> {
videoList.addAll(extractBloggerVideos(url, vid.text().trim()))
}
} }
} }
return videoList return videoList
} }
private fun extractBloggerVideos(url: String, name: String): List<Video> {
return client.newCall(GET(url, headers)).execute()
.use { it.body.string() }
.takeIf { !it.contains("errorContainer") }
.let { it ?: return emptyList() }
.substringAfter("\"streams\":[")
.substringBefore("]")
.split("},")
.map {
val videoUrl = it.substringAfter("{\"play_url\":\"").substringBefore('"')
val format = it.substringAfter("\"format_id\":").substringBefore("}")
val quality = when (format) {
"18" -> "360p"
"22" -> "720p"
else -> "Unknown"
}
Video(videoUrl, "$quality - $name", videoUrl, headers = headers)
}
}
override fun videoFromElement(element: Element): Video = throw Exception("Not Used") override fun videoFromElement(element: Element): Video = throw Exception("Not Used")
override fun videoListSelector(): String = throw Exception("Not Used") override fun videoListSelector(): String = throw Exception("Not Used")
override fun videoUrlParse(document: Document): String = throw Exception("Not Used") override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_VALUES
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun getDecrypted(script: String): String {
val patchedScript = script.trim().split("\n").first()
.replace("eval(function", "function a")
.replace("decodeURIComponent(escape(r))}(", "r};a(")
.substringBeforeLast(")")
private fun extractBloggerVideos(url: String, name: String): List<Video> { return QuickJs.create().use {
val document = client.newCall(GET(url)).execute().asJsoup() it.evaluate(patchedScript).toString()
val jsElement = document.selectFirst("script:containsData(VIDEO_CONFIG)") ?: return emptyList()
val js = jsElement.data()
val json = json.decodeFromString<VideoConfig>(js.substringAfter("var VIDEO_CONFIG = "))
return json.streams.map {
Video(it.play_url, "${it.format_id} - $name", it.play_url)
} }
} }
@Serializable
data class VideoConfig(
val streams: List<Stream>,
) {
@Serializable
data class Stream(
val play_url: String,
val format_id: Int,
)
}
private fun getDecrypted(document: Document): String? {
val script = document.selectFirst("script:containsData(decodeURIComponent)") ?: return null
val quickJs = QuickJs.create()
val decrypted = quickJs.evaluate(
script.data().trim().split("\n")[0].replace("eval(function", "function a").replace("decodeURIComponent(escape(r))}(", "r};a(").substringBeforeLast(")"),
).toString()
quickJs.close()
return decrypted
}
private fun queryToJsonList(input: String): String { private fun queryToJsonList(input: String): String {
if (input.isEmpty()) return "" if (input.isEmpty()) return ""
return input.substringAfter("=").split("%2C").joinToString(",") { return input.substringAfter("=").split("%2C").joinToString(",") {
@ -422,10 +378,12 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
private inline fun <reified T> Response.parseAs(): T {
return use { it.body.string() }.let(json::decodeFromString)
}
@Serializable @Serializable
data class VideoResponse( data class VideoResponse(val embed_url: String)
val embed_url: String,
)
@Serializable @Serializable
data class PostResponse( data class PostResponse(
@ -433,9 +391,7 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val settings: Settings, val settings: Settings,
) { ) {
@Serializable @Serializable
data class Settings( data class Settings(val pager: Pager) {
val pager: Pager,
) {
@Serializable @Serializable
data class Pager( data class Pager(
val page: Int, val page: Int,
@ -445,30 +401,20 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "1080")!! 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()
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { companion object {
val videoQualityPref = ListPreference(screen.context).apply { private val EPISODE_REGEX by lazy { Regex("""Episódio ?\d+\.?\d* ?""") }
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf("1080p", "720p", "480p", "360p")
entryValues = arrayOf("1080", "720", "480", "360")
setDefaultValue("720")
summary = "%s"
setOnPreferenceChangeListener { _, newValue -> private const val PREF_QUALITY_KEY = "preferred_quality"
val selected = newValue as String private const val PREF_QUALITY_TITLE = "Qualidade preferida"
val index = findIndexOfValue(selected) private const val PREF_QUALITY_DEFAULT = "720"
val entry = entryValues[index] as String private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
preferences.edit().putString(key, entry).commit() private val PREF_QUALITY_VALUES = arrayOf("1080", "720", "480", "360")
}
}
screen.addPreference(videoQualityPref)
} }
} }

View File

@ -8,13 +8,13 @@ object BloggerJWPlayerExtractor {
return sources.split("{").drop(1).mapNotNull { return sources.split("{").drop(1).mapNotNull {
val label = it.substringAfter("label") val label = it.substringAfter("label")
.substringAfter(":") .substringAfter(':')
.substringAfter("\"") .substringAfter('"')
.substringBefore('"') .substringBefore('"')
val videoUrl = it.substringAfter("file") val videoUrl = it.substringAfter("file")
.substringAfter(":") .substringAfter(':')
.substringAfter("\"") .substringAfter('"')
.substringBefore('"') .substringBefore('"')
.replace("\\", "") .replace("\\", "")
if (videoUrl.isEmpty()) { if (videoUrl.isEmpty()) {