refactor(src/pt): General refactorations + Purge AnimeTV (#2458)

This commit is contained in:
Claudemirovsky
2023-11-02 08:33:39 -03:00
committed by GitHub
parent f248c8b1a8
commit e93c99701b
52 changed files with 1185 additions and 1426 deletions

View File

@ -13,7 +13,6 @@ 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.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
@ -43,6 +42,10 @@ class AniDong : ParsedAnimeHttpSource() {
} }
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "article.top10_animes_item > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.attr("title") title = element.attr("title")
@ -50,20 +53,134 @@ class AniDong : ParsedAnimeHttpSource() {
} }
override fun popularAnimeNextPageSelector() = null override fun popularAnimeNextPageSelector() = null
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "article.top10_animes_item > a"
// ============================== Episodes ============================== // =============================== Latest ===============================
override fun episodeFromElement(element: Element): SEpisode { override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page/")
override fun latestUpdatesSelector() = "article.main_content_article > a"
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = "div.paginacao > a.next"
// =============================== Search ===============================
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.use { it.asJsoup() })
return AnimesPage(listOf(details), false)
}
override fun getFilterList() = AniDongFilters.FILTER_LIST
private val nonce by lazy {
client.newCall(GET("$baseUrl/?js_global=1&ver=6.2.2")).execute()
.use { it.body.string() }
.substringAfter("search_nonce")
.substringAfter("'")
.substringBefore("'")
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AniDongFilters.getSearchParameters(filters)
val body = FormBody.Builder()
.add("letra", "")
.add("action", "show_animes_ajax")
.add("nome", query)
.add("status", params.status)
.add("formato", params.format)
.add("search_nonce", nonce)
.add("paged", page.toString())
.apply {
params.genres.forEach { add("generos[]", it) }
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", headers = apiHeaders, body = body)
}
override fun searchAnimeParse(response: Response): AnimesPage {
val searchData: SearchResultDto = response.use { it.body.string() }
.takeIf { it.trim() != "402" }
?.let(json::decodeFromString)
?: return AnimesPage(emptyList(), false)
val animes = searchData.animes.map {
SAnime.create().apply {
setUrlWithoutDomain(it.url)
title = it.title
thumbnail_url = it.thumbnail_url
}
}
val hasNextPage = searchData.pages > 1 && searchData.animes.size == 10
return AnimesPage(animes, hasNextPage)
}
override fun searchAnimeSelector(): String {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
override fun searchAnimeFromElement(element: Element): SAnime {
throw UnsupportedOperationException("Not used.")
}
override fun searchAnimeNextPageSelector(): String? {
throw UnsupportedOperationException("Not used.")
}
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
val infos = doc.selectFirst("div.anime_infos")!!
setUrlWithoutDomain(doc.location())
title = infos.selectFirst("div > h3")!!.ownText()
thumbnail_url = infos.selectFirst("img")?.attr("src")
genre = infos.select("div[itemprop=genre] a").eachText().joinToString()
artist = infos.selectFirst("div[itemprop=productionCompany]")?.text()
status = doc.selectFirst("div:contains(Status) span")?.text().let {
when {
it == null -> SAnime.UNKNOWN
it == "Completo" -> SAnime.COMPLETED
it.contains("Lançamento") -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
description = buildString {
infos.selectFirst("div.anime_name + div.anime_info")?.text()?.also {
append("Nomes alternativos: $it\n")
}
doc.selectFirst("div[itemprop=description]")?.text()?.also {
append("\n$it")
}
}
}
// ============================== Episodes ==============================
override fun episodeListSelector(): String { override fun episodeListSelector(): String {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
override fun episodeFromElement(element: Element): SEpisode {
throw UnsupportedOperationException("Not used.")
}
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val doc = getRealDoc(response.asJsoup()) val doc = getRealDoc(response.use { it.asJsoup() })
val id = doc.selectFirst("link[rel=shortlink]")!!.attr("href").substringAfter("=") val id = doc.selectFirst("link[rel=shortlink]")!!.attr("href").substringAfter("=")
val body = FormBody.Builder() val body = FormBody.Builder()
@ -71,8 +188,9 @@ class AniDong : ParsedAnimeHttpSource() {
.add("anime_id", id) .add("anime_id", id)
.build() .build()
val res = client.newCall(POST("$baseUrl/api", headers = apiHeaders, body = body)).execute() val res = client.newCall(POST("$baseUrl/api", apiHeaders, body)).execute()
val data = json.decodeFromString<EpisodeListDto>(res.body.string()) .use { it.body.string() }
val data = json.decodeFromString<EpisodeListDto>(res)
return buildList { return buildList {
data.episodes.forEach { add(episodeFromObject(it, "Episódio")) } data.episodes.forEach { add(episodeFromObject(it, "Episódio")) }
@ -88,40 +206,9 @@ class AniDong : ParsedAnimeHttpSource() {
name = "$prefix ${episode.epi_num}" name = "$prefix ${episode.epi_num}"
} }
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
val infos = doc.selectFirst("div.anime_infos")!!
setUrlWithoutDomain(doc.location())
title = infos.selectFirst("div > h3")!!.ownText()
thumbnail_url = infos.selectFirst("img")!!.attr("src")
genre = infos.select("div[itemprop=genre] a").eachText().joinToString()
artist = infos.selectFirst("div[itemprop=productionCompany]")!!.text()
status = doc.selectFirst("div:contains(Status) span")?.text().let {
when {
it == null -> SAnime.UNKNOWN
it == "Completo" -> SAnime.COMPLETED
it.contains("Lançamento") -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
description = buildString {
infos.selectFirst("div.anime_name + div.anime_info")?.text()?.let {
append("Nomes alternativos: $it\n")
}
doc.selectFirst("div[itemprop=description]")?.text()?.let {
append("\n$it")
}
}
}
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup() val doc = response.use { it.asJsoup() }
return doc.select("div.player_option").flatMap { return doc.select("div.player_option").flatMap {
val url = it.attr("data-playerlink") val url = it.attr("data-playerlink")
val playerName = it.text().trim() val playerName = it.text().trim()
@ -158,97 +245,13 @@ class AniDong : ParsedAnimeHttpSource() {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element): SAnime {
throw UnsupportedOperationException("Not used.")
}
override fun searchAnimeNextPageSelector(): String? {
throw UnsupportedOperationException("Not used.")
}
override fun searchAnimeParse(response: Response): AnimesPage {
val searchData: SearchResultDto = response.use { it.body.string() }
.takeIf { it.trim() != "402" }
?.let(json::decodeFromString)
?: return AnimesPage(emptyList<SAnime>(), false)
val animes = searchData.animes.map {
SAnime.create().apply {
setUrlWithoutDomain(it.url)
title = it.title
thumbnail_url = it.thumbnail_url
}
}
val hasNextPage = searchData.pages > 1 && searchData.animes.size == 10
return AnimesPage(animes, hasNextPage)
}
override fun getFilterList() = AniDongFilters.FILTER_LIST
private val nonce by lazy {
client.newCall(GET("$baseUrl/?js_global=1&ver=6.2.2")).execute()
.use { it.body.string() }
.substringAfter("search_nonce")
.substringAfter("'")
.substringBefore("'")
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AniDongFilters.getSearchParameters(filters)
val body = FormBody.Builder()
.add("letra", "")
.add("action", "show_animes_ajax")
.add("nome", query)
.add("status", params.status)
.add("formato", params.format)
.add("search_nonce", nonce)
.add("paged", page.toString())
.apply {
params.genres.forEach { add("generos[]", it) }
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", headers = apiHeaders, body = body)
}
override fun searchAnimeSelector(): String {
throw UnsupportedOperationException("Not used.")
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
// =============================== Latest ===============================
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = "div.paginacao > a.next"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page/")
override fun latestUpdatesSelector() = "article.main_content_article > a"
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun getRealDoc(document: Document): Document { private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/video/")) return document if (!document.location().contains("/video/")) return document
return document.selectFirst(".episodioControleItem:has(i.ri-grid-fill)")?.let { return document.selectFirst(".episodioControleItem:has(i.ri-grid-fill)")?.let {
client.newCall(GET(it.attr("href"), headers)).execute().asJsoup() client.newCall(GET(it.attr("href"), headers)).execute()
.use { req -> req.asJsoup() }
} ?: document } ?: document
} }

View File

@ -31,6 +31,7 @@ object AniDongFilters {
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
): List<String> { ): List<String> {
return (getFirst<R>() as CheckBoxFilterList).state return (getFirst<R>() as CheckBoxFilterList).state
.asSequence()
.filter { it.state } .filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second } .map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank) .filter(String::isNotBlank)

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AFFilters { object AFFilters {
open class QueryPartFilter( open class QueryPartFilter(
displayName: String, displayName: String,
val vals: Array<Pair<String, String>>, val vals: Array<Pair<String, String>>,
@ -16,9 +15,7 @@ object AFFilters {
} }
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return first { it is R }.let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class GenreFilter : QueryPartFilter("Gênero", AFFiltersData.GENRES) class GenreFilter : QueryPartFilter("Gênero", AFFiltersData.GENRES)

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.animefire package eu.kanade.tachiyomi.animeextension.pt.animefire
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 eu.kanade.tachiyomi.animeextension.pt.animefire.extractors.AnimeFireExtractor import eu.kanade.tachiyomi.animeextension.pt.animefire.extractors.AnimeFireExtractor
@ -17,7 +16,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -38,11 +36,11 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
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)
} }
@ -51,42 +49,34 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.add("Accept-Language", ACCEPT_LANGUAGE) .add("Accept-Language", ACCEPT_LANGUAGE)
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/top-animes/$page")
override fun popularAnimeSelector() = latestUpdatesSelector() override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top-animes/$page")
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element) override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector() override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// ============================== Episodes ============================== // =============================== Latest ===============================
override fun episodeListSelector(): String = "div.div_video_list > a" override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/home/$page")
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeFromElement(element: Element) = SEpisode.create().apply { override fun latestUpdatesSelector() = "article.cardUltimosEps > a"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
val url = element.attr("href") val url = element.attr("href")
setUrlWithoutDomain(url) // get anime url from episode url
name = element.text() when (url.substringAfterLast("/").toIntOrNull()) {
episode_number = url.substringAfterLast("/").toFloatOrNull() ?: 0F null -> setUrlWithoutDomain(url)
} else -> {
val substr = url.substringBeforeLast("/")
// ============================ Video Links ============================= setUrlWithoutDomain("$substr-todos-os-episodios")
override fun videoListParse(response: Response): List<Video> { }
val document = response.asJsoup()
val videoElement = document.selectFirst("video#my-video")
return if (videoElement != null) {
AnimeFireExtractor(client, json).videoListFromElement(videoElement)
} else {
IframeExtractor(client).videoListFromDocument(document, headers)
} }
title = element.selectFirst("h3.animeTitle")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("data-src")
} }
override fun videoListSelector() = throw Exception("not used") override fun latestUpdatesNextPageSelector() = "ul.pagination img.seta-right"
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> { override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { return if (query.startsWith(PREFIX_SEARCH)) {
val id = query.removePrefix(PREFIX_SEARCH) val id = query.removePrefix(PREFIX_SEARCH)
@ -115,50 +105,60 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return GET("$baseUrl/pesquisar/$fixedQuery/$page") return GET("$baseUrl/pesquisar/$fixedQuery/$page")
} }
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val content = document.selectFirst("div.divDivAnimeInfo")!! val content = document.selectFirst("div.divDivAnimeInfo")!!
val names = content.selectFirst("div.div_anime_names")!! val names = content.selectFirst("div.div_anime_names")!!
val infos = content.selectFirst("div.divAnimePageInfo")!! val infos = content.selectFirst("div.divAnimePageInfo")!!
setUrlWithoutDomain(document.location()) setUrlWithoutDomain(document.location())
thumbnail_url = content.selectFirst("div.sub_animepage_img > img")!! thumbnail_url = content.selectFirst("div.sub_animepage_img > img")?.attr("data-src")
.attr("data-src")
title = names.selectFirst("h1")!!.text() title = names.selectFirst("h1")!!.text()
genre = infos.select("a.spanGeneros").eachText().joinToString() genre = infos.select("a.spanGeneros").eachText().joinToString()
author = infos.getInfo("Estúdios") author = infos.getInfo("Estúdios")
status = parseStatus(infos.getInfo("Status")) status = parseStatus(infos.getInfo("Status"))
description = buildString { description = buildString {
append(content.selectFirst("div.divSinopse > span")!!.text() + "\n") content.selectFirst("div.divSinopse > span")?.also {
names.selectFirst("h6")?.let { append("\nNome alternativo: ${it.text()}") } append(it.text() + "\n")
infos.getInfo("Dia de")?.let { append("\nDia de lançamento: $it") }
infos.getInfo("Áudio")?.let { append("\nTipo: $it") }
infos.getInfo("Ano")?.let { append("\nAno: $it") }
infos.getInfo("Episódios")?.let { append("\nEpisódios: $it") }
infos.getInfo("Temporada")?.let { append("\nTemporada: $it") }
}
}
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = "ul.pagination img.seta-right"
override fun latestUpdatesSelector(): String = "article.cardUltimosEps > a"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
val url = element.attr("href")
// get anime url from episode url
when (url.substringAfterLast("/").toIntOrNull()) {
null -> setUrlWithoutDomain(url)
else -> {
val substr = url.substringBeforeLast("/")
setUrlWithoutDomain("$substr-todos-os-episodios")
} }
names.selectFirst("h6")?.also { append("\nNome alternativo: ${it.text()}") }
infos.getInfo("Dia de")?.also { append("\nDia de lançamento: $it") }
infos.getInfo("Áudio")?.also { append("\nTipo: $it") }
infos.getInfo("Ano")?.also { append("\nAno: $it") }
infos.getInfo("Episódios")?.also { append("\nEpisódios: $it") }
infos.getInfo("Temporada")?.also { append("\nTemporada: $it") }
} }
title = element.selectFirst("h3.animeTitle")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("data-src")
} }
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/home/$page") // ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListSelector(): String = "div.div_video_list > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
val url = element.attr("href")
setUrlWithoutDomain(url)
name = element.text()
episode_number = url.substringAfterLast("/").toFloatOrNull() ?: 0F
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.use { it.asJsoup() }
val videoElement = document.selectFirst("video#my-video")
return if (videoElement != null) {
AnimeFireExtractor(client, json).videoListFromElement(videoElement)
} else {
IframeExtractor(client).videoListFromDocument(document, headers)
}
}
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -181,7 +181,6 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun getFilterList(): AnimeFilterList = AFFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = AFFilters.FILTER_LIST
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun parseStatus(statusString: String?): Int { private fun parseStatus(statusString: String?): Int {
return when (statusString?.trim()) { return when (statusString?.trim()) {
"Completo" -> SAnime.COMPLETED "Completo" -> SAnime.COMPLETED

View File

@ -3,22 +3,20 @@ package eu.kanade.tachiyomi.animeextension.pt.animefire.extractors
import eu.kanade.tachiyomi.animeextension.pt.animefire.dto.AFResponseDto import eu.kanade.tachiyomi.animeextension.pt.animefire.dto.AFResponseDto
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
class AnimeFireExtractor(private val client: OkHttpClient, private val json: Json) { class AnimeFireExtractor(private val client: OkHttpClient, private val json: Json) {
fun videoListFromElement(videoElement: Element): List<Video> { fun videoListFromElement(videoElement: Element): List<Video> {
val jsonUrl = videoElement.attr("data-video-src") val jsonUrl = videoElement.attr("data-video-src")
val response = client.newCall(GET(jsonUrl)).execute() val response = client.newCall(GET(jsonUrl)).execute()
val responseDto = json.decodeFromString<AFResponseDto>( .use { it.body.string() }
response.body.string(), val responseDto = json.decodeFromString<AFResponseDto>(response)
) return responseDto.videos.map {
val videoList = responseDto.videos.map {
val url = it.url.replace("\\", "") val url = it.url.replace("\\", "")
Video(url, it.quality, url) Video(url, it.quality, url)
} }
return videoList
} }
} }

View File

@ -11,8 +11,8 @@ class IframeExtractor(private val client: OkHttpClient) {
val iframeElement = doc.selectFirst("div#div_video iframe")!! val iframeElement = doc.selectFirst("div#div_video iframe")!!
val iframeUrl = iframeElement.attr("src") val iframeUrl = iframeElement.attr("src")
val response = client.newCall(GET(iframeUrl, headers)).execute() val response = client.newCall(GET(iframeUrl, headers)).execute()
val html = response.body.string() .use { it.body.string() }
val url = html.substringAfter("play_url") val url = response.substringAfter("play_url")
.substringAfter(":\"") .substringAfter(":\"")
.substringBefore("\"") .substringBefore("\"")
val video = Video(url, "Default", url, headers = headers) val video = Video(url, "Default", url, headers = headers)

View File

@ -29,6 +29,10 @@ class AnimesAria : ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/novos/animes?page=$page")
override fun popularAnimeSelector() = "div.item > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.attr("title") title = element.attr("title")
@ -37,23 +41,60 @@ class AnimesAria : ParsedAnimeHttpSource() {
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector() override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/novos/animes?page=$page") // =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/novos/episodios?page=$page")
override fun popularAnimeSelector() = "div.item > a" override fun latestUpdatesSelector() = "div.item > div.pos-rlt"
// ============================== Episodes ============================== override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed() thumbnail_url = element.selectFirst("img")?.attr("src")
val ahref = element.selectFirst("a")!!
override fun episodeFromElement(element: Element) = SEpisode.create().apply { title = ahref.attr("title")
element.parent()!!.selectFirst("a > b")!!.ownText().let { setUrlWithoutDomain(ahref.attr("href").substringBefore("/episodio/"))
name = it
episode_number = it.substringAfter(" ").toFloatOrNull() ?: 0F
}
setUrlWithoutDomain(element.attr("href"))
scanlator = element.text().substringAfter(" ") // sub/dub
} }
override fun episodeListSelector() = "td div.clear > a.btn-xs" override fun latestUpdatesNextPageSelector() = "a:containsOwn(Próximo):not(.disabled)"
// =============================== Search ===============================
override fun getFilterList() = AnimesAriaFilters.FILTER_LIST
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.use { it.asJsoup() })
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimesAriaFilters.getSearchParameters(filters)
val url = "$baseUrl/anime/buscar".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", page.toString())
.addQueryParameter("tipo", params.type)
.addQueryParameter("genero", params.genre)
.addQueryParameter("status", params.status)
.addQueryParameter("letra", params.letter)
.addQueryParameter("audio", params.audio)
.addQueryParameter("ano", params.year)
.addQueryParameter("temporada", params.season)
.build()
return GET(url.toString())
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun searchAnimeSelector() = popularAnimeSelector()
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
@ -61,18 +102,18 @@ class AnimesAria : ParsedAnimeHttpSource() {
val row = document.selectFirst("div.anime_background_w div.row")!! val row = document.selectFirst("div.anime_background_w div.row")!!
title = row.selectFirst("h1 > span")!!.text() title = row.selectFirst("h1 > span")!!.text()
status = row.selectFirst("div.clear span.btn")?.text().toStatus() status = row.selectFirst("div.clear span.btn")?.text().toStatus()
thumbnail_url = document.selectFirst("link[as=image]")!!.attr("href") thumbnail_url = document.selectFirst("link[as=image]")?.attr("href")
genre = row.select("div.clear a.btn").eachText().joinToString() genre = row.select("div.clear a.btn").eachText().joinToString()
description = buildString { description = buildString {
document.selectFirst("li.active > small")!! document.selectFirst("li.active > small")!!
.ownText() .ownText()
.substringAfter(": ") .substringAfter(": ")
.let(::append) .also(::append)
append("\n\n") append("\n\n")
row.selectFirst("h1 > small")?.text()?.let { row.selectFirst("h1 > small")?.text()?.also {
append("Títulos Alternativos: $it\n") append("Títulos Alternativos: $it\n")
} }
@ -85,6 +126,20 @@ class AnimesAria : ParsedAnimeHttpSource() {
} }
} }
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.parent()!!.selectFirst("a > b")!!.ownText().also {
name = it
episode_number = it.substringAfter(" ").toFloatOrNull() ?: 0F
}
setUrlWithoutDomain(element.attr("href"))
scanlator = element.text().substringAfter(" ") // sub/dub
}
override fun episodeListSelector() = "td div.clear > a.btn-xs"
// ============================ 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.asJsoup()
@ -119,61 +174,6 @@ class AnimesAria : ParsedAnimeHttpSource() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun getFilterList(): AnimeFilterList = AnimesAriaFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimesAriaFilters.getSearchParameters(filters)
val url = "$baseUrl/anime/buscar".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", page.toString())
.addQueryParameter("tipo", params.type)
.addQueryParameter("genero", params.genre)
.addQueryParameter("status", params.status)
.addQueryParameter("letra", params.letter)
.addQueryParameter("audio", params.audio)
.addQueryParameter("ano", params.year)
.addQueryParameter("temporada", params.season)
.build()
return GET(url.toString())
}
override fun searchAnimeSelector() = popularAnimeSelector()
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
// =============================== Latest ===============================
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
thumbnail_url = element.selectFirst("img")!!.attr("src")
val ahref = element.selectFirst("a")!!
title = ahref.attr("title")
setUrlWithoutDomain(ahref.attr("href").substringBefore("/episodio/"))
}
override fun latestUpdatesNextPageSelector() = "a:containsOwn(Próximo):not(.disabled)"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/novos/episodios?page=$page")
override fun latestUpdatesSelector() = "div.item > div.pos-rlt"
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun String?.toStatus() = when (this) { private fun String?.toStatus() = when (this) {
"Finalizado" -> SAnime.COMPLETED "Finalizado" -> SAnime.COMPLETED

View File

@ -16,9 +16,7 @@ object AnimesAriaFilters {
} }
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.first { it is R }.let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class TypeFilter : QueryPartFilter("Tipo", AnimesAriaFiltersData.TYPES) class TypeFilter : QueryPartFilter("Tipo", AnimesAriaFiltersData.TYPES)
@ -40,16 +38,18 @@ object AnimesAriaFilters {
) )
data class FilterSearchParams( data class FilterSearchParams(
val type: String, val type: String = "",
val genre: String, val genre: String = "",
val status: String, val status: String = "",
val letter: String, val letter: String = "",
val audio: String, val audio: String = "",
val year: String, val year: String = "",
val season: String, val season: String = "",
) )
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams( return FilterSearchParams(
filters.asQueryPart<TypeFilter>(), filters.asQueryPart<TypeFilter>(),
filters.asQueryPart<GenreFilter>(), filters.asQueryPart<GenreFilter>(),

View File

@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess 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.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -49,50 +48,109 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
override fun popularAnimeRequest(page: Int) = GET(baseUrl) override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = latestUpdatesSelector() override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
// ============================== Episodes ============================== // =============================== Latest ===============================
override fun episodeListParse(response: Response): List<SEpisode> { override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page")
val doc = getRealDoc(response.asJsoup())
val pagination = doc.selectFirst("ul.content-pagination") override fun latestUpdatesSelector() = "div.b_flex:nth-child(2) > div.itemE > a"
return if (pagination != null) {
val episodes = mutableListOf<SEpisode>() override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
episodes += doc.select(episodeListSelector()).map(::episodeFromElement) setUrlWithoutDomain(element.attr("href"))
val lastPage = doc.selectFirst("ul.content-pagination > li:nth-last-child(2) > span")!!.text().toInt() thumbnail_url = element.selectFirst("img")!!.let {
for (i in 2..lastPage) { it.attr("data-lazy-src").ifEmpty { it.attr("src") }
val request = GET(doc.location() + "/page/$i", headers) }
val res = client.newCall(request).execute() title = element.selectFirst("span.title_anime")!!.text()
val pageDoc = res.use { it.asJsoup() }
episodes += pageDoc.select(episodeListSelector()).map(::episodeFromElement)
}
episodes
} else doc.select(episodeListSelector()).map(::episodeFromElement)
} }
override fun episodeFromElement(element: Element) = SEpisode.create().apply { override fun latestUpdatesNextPageSelector() = "ul > li.next"
setUrlWithoutDomain(element.attr("href"))
val epname = element.selectFirst("div.episode")!!.text() // =============================== Search ===============================
episode_number = epname.substringAfterLast(" ").toFloatOrNull() ?: 1F override fun getFilterList() = AnimesDigitalFilters.FILTER_LIST
name = buildString {
append(epname) override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
element.selectFirst("div.sub_title")?.text()?.let { return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
if (!it.contains("Ainda não tem um titulo oficial")) { val id = query.removePrefix(PREFIX_SEARCH)
append(" - $it") client.newCall(GET("$baseUrl/anime/a/$id"))
} .asObservableSuccess()
} .map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
} }
} }
override fun episodeListSelector() = "div.item_ep > a" private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.use { it.asJsoup() })
return AnimesPage(listOf(details), false)
}
private val searchToken by lazy {
client.newCall(GET("$baseUrl/animes-legendado")).execute()
.use {
it.asJsoup().selectFirst("div.menu_filter_box")!!.attr("data-secury")
}
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimesDigitalFilters.getSearchParameters(filters)
val body = FormBody.Builder().apply {
add("type", "lista")
add("limit", "30")
add("token", searchToken)
if (query.isNotEmpty()) {
add("search", query)
}
add("pagina", "$page")
val filterData = baseUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("type_url", params.type)
addQueryParameter("filter_audio", params.audio)
addQueryParameter("filter_letter", params.initialLetter)
addQueryParameter("filter_order", "name")
}.build().encodedQuery.orEmpty()
val genres = params.genres.joinToString { "\"$it\"" }
val delgenres = params.deleted_genres.joinToString { "\"$it\"" }
add("filters", """{"filter_data": "$filterData", "filter_genre_add": [$genres], "filter_genre_del": [$delgenres]}""")
}.build()
return POST("$baseUrl/func/listanime", body = body, headers = headers)
}
override fun searchAnimeSelector() = "div.itemA > a"
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeParse(response: Response): AnimesPage {
return runCatching {
val data = response.parseAs<SearchResponseDto>()
val animes = data.results.map(Jsoup::parse)
.mapNotNull { it.selectFirst(searchAnimeSelector()) }
.map(::searchAnimeFromElement)
val hasNext = data.total_page > data.page
AnimesPage(animes, hasNext)
}.getOrElse { AnimesPage(emptyList(), false) }
}
@Serializable
data class SearchResponseDto(
val results: List<String>,
val page: Int,
val total_page: Int,
)
override fun searchAnimeNextPageSelector(): String? {
throw UnsupportedOperationException("Not used.")
}
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document) val doc = getRealDoc(document)
setUrlWithoutDomain(doc.location()) setUrlWithoutDomain(doc.location())
thumbnail_url = doc.selectFirst("div.poster > img")!!.attr("data-lazy-src") thumbnail_url = doc.selectFirst("div.poster > img")?.attr("data-lazy-src")
status = when (doc.selectFirst("div.clw > div.playon")?.text()) { status = when (doc.selectFirst("div.clw > div.playon")?.text()) {
"Em Lançamento" -> SAnime.ONGOING "Em Lançamento" -> SAnime.ONGOING
"Completo" -> SAnime.COMPLETED "Completo" -> SAnime.COMPLETED
@ -110,11 +168,47 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
description = infos.selectFirst("div.sinopse")?.text() description = infos.selectFirst("div.sinopse")?.text()
} }
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = getRealDoc(response.use { it.asJsoup() })
val pagination = doc.selectFirst("ul.content-pagination")
return if (pagination != null) {
val episodes = mutableListOf<SEpisode>()
episodes += doc.select(episodeListSelector()).map(::episodeFromElement)
val lastPage = doc.selectFirst("ul.content-pagination > li:nth-last-child(2) > span")!!.text().toInt()
for (i in 2..lastPage) {
val request = GET(doc.location() + "/page/$i", headers)
val res = client.newCall(request).execute()
val pageDoc = res.use { it.asJsoup() }
episodes += pageDoc.select(episodeListSelector()).map(::episodeFromElement)
}
episodes
} else {
doc.select(episodeListSelector()).map(::episodeFromElement)
}
}
override fun episodeListSelector() = "div.item_ep > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
val epname = element.selectFirst("div.episode")!!.text()
episode_number = epname.substringAfterLast(" ").toFloatOrNull() ?: 1F
name = buildString {
append(epname)
element.selectFirst("div.sub_title")?.text()?.let {
if (!it.contains("Ainda não tem um titulo oficial")) {
append(" - $it")
}
}
}
}
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val player = response.asJsoup().selectFirst("div#player")!! val player = response.use { it.asJsoup() }.selectFirst("div#player")!!
return player.select("div.tab-video").flatMap { return player.select("div.tab-video").flatMap { div ->
it.select(videoListSelector()).flatMap { element -> div.select(videoListSelector()).flatMap { element ->
runCatching { runCatching {
videosFromElement(element) videosFromElement(element)
}.onFailure { it.printStackTrace() }.getOrElse { emptyList() } }.onFailure { it.printStackTrace() }.getOrElse { emptyList() }
@ -133,18 +227,18 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
client.newCall(GET(url, headers)).execute() client.newCall(GET(url, headers)).execute()
.asJsoup() .use { it.asJsoup() }
.select(videoListSelector()) .select(videoListSelector())
.flatMap(::videosFromElement) .flatMap(::videosFromElement)
} }
"script" -> { "script" -> {
val scriptData = element.data().let { val scriptData = element.data().let {
when { when {
"eval(function" in it -> Unpacker.unpack(it).ifEmpty { null } "eval(function" in it -> Unpacker.unpack(it)
else -> it else -> it
} }
}?.replace("\\", "") }.ifEmpty { null }?.replace("\\", "")
scriptData?.let(::videosFromScript) ?: emptyList() scriptData?.let(::videosFromScript).orEmpty()
} }
else -> emptyList() else -> emptyList()
} }
@ -177,99 +271,6 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeParse(response: Response): AnimesPage {
return runCatching {
val data = response.parseAs<SearchResponseDto>()
val animes = data.results.map(Jsoup::parse)
.mapNotNull { it.selectFirst(searchAnimeSelector()) }
.map(::searchAnimeFromElement)
val hasNext = data.total_page > data.page
AnimesPage(animes, hasNext)
}.getOrElse { AnimesPage(emptyList(), false) }
}
@Serializable
data class SearchResponseDto(
val results: List<String>,
val page: Int,
val total_page: Int,
)
override fun searchAnimeNextPageSelector(): String? {
throw UnsupportedOperationException("Not used.")
}
private val searchToken by lazy {
client.newCall(GET("$baseUrl/animes-legendado")).execute()
.use {
it.asJsoup().selectFirst("div.menu_filter_box")!!.attr("data-secury")
}
}
override fun getFilterList() = AnimesDigitalFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimesDigitalFilters.getSearchParameters(filters)
val body = FormBody.Builder().apply {
add("type", "lista")
add("limit", "30")
add("token", searchToken)
if (query.isNotEmpty()) {
add("search", query)
}
add("pagina", "$page")
val filterData = baseUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("type_url", params.type)
addQueryParameter("filter_audio", params.audio)
addQueryParameter("filter_letter", params.initialLetter)
addQueryParameter("filter_order", "name")
}.build().encodedQuery
val genres = params.genres.joinToString { "\"$it\"" }
val delgenres = params.deleted_genres.joinToString { "\"$it\"" }
add("filters", """{"filter_data": "$filterData", "filter_genre_add": [$genres], "filter_genre_del": [$delgenres]}""")
}.build()
return POST("$baseUrl/func/listanime", body = body, headers = headers)
}
override fun searchAnimeSelector() = "div.itemA > a"
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/a/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
// =============================== Latest ===============================
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.let {
it.attr("data-lazy-src").ifEmpty { it.attr("src") }
}
title = element.selectFirst("span.title_anime")!!.text()
}
override fun latestUpdatesNextPageSelector() = "ul > li.next"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page")
override fun latestUpdatesSelector() = "div.b_flex:nth-child(2) > div.itemE > a"
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply { ListPreference(screen.context).apply {
@ -300,7 +301,7 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return document.selectFirst("div.subitem > a:contains(menu)")?.let { link -> return document.selectFirst("div.subitem > a:contains(menu)")?.let { link ->
client.newCall(GET(link.attr("href"))) client.newCall(GET(link.attr("href")))
.execute() .execute()
.asJsoup() .use { it.asJsoup() }
} ?: document } ?: document
} }
@ -309,15 +310,11 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
private fun Element.getInfo(key: String): String? { private fun Element.getInfo(key: String): String? {
return selectFirst("div.info:has(span:containsOwn($key))") return selectFirst("div.info:has(span:containsOwn($key))")?.run {
?.ownText() ownText()
?.trim() .trim()
?.let { .takeUnless { it.isBlank() || it == "?" }
when (it) { }
"", "?" -> null
else -> it
}
}
} }
private fun String.substringAfterKey() = substringAfter(":") private fun String.substringAfterKey() = substringAfter(":")

View File

@ -19,26 +19,20 @@ object AnimesDigitalFilters {
open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values) open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values)
class TriFilterVal(name: String) : TriState(name) class TriFilterVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
private inline fun <reified R> AnimeFilterList.parseTriFilter( private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
): List<List<String>> { ): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() } .filterNot { it.isIgnored() }
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second } .map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
.groupBy { it.first } // group by state .groupBy { it.first } // group by state
.let { .let { dict ->
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>() val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>() val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded) listOf(included, excluded)
} }
} }

View File

@ -61,7 +61,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.selectFirst("div.tituloEP")!!.text() title = element.selectFirst("div.tituloEP")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("data-lazy-src") thumbnail_url = element.selectFirst("img")?.attr("data-lazy-src")
} }
override fun latestUpdatesNextPageSelector() = "ol.pagination > a:contains(>)" override fun latestUpdatesNextPageSelector() = "ol.pagination > a:contains(>)"
@ -113,7 +113,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
addQueryParameter("filter_letter", params.letter) addQueryParameter("filter_letter", params.letter)
addQueryParameter("filter_order", params.orderBy) addQueryParameter("filter_order", params.orderBy)
addQueryParameter("filter_sort", "abc") addQueryParameter("filter_sort", "abc")
}.build().encodedQuery }.build().encodedQuery.orEmpty()
val genres = params.genres.joinToString { "\"$it\"" } val genres = params.genres.joinToString { "\"$it\"" }
val delgenres = params.deleted_genres.joinToString { "\"$it\"" } val delgenres = params.deleted_genres.joinToString { "\"$it\"" }
@ -155,7 +155,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
title = content.selectFirst("section > h1")!!.text() title = content.selectFirst("section > h1")!!.text()
.removePrefix("Assistir ") .removePrefix("Assistir ")
.removeSuffix("Temporada Online") .removeSuffix("Temporada Online")
thumbnail_url = content.selectFirst("img")!!.attr("data-lazy-src") thumbnail_url = content.selectFirst("img")?.attr("data-lazy-src")
description = content.select("section.sinopseEp p").eachText().joinToString("\n") description = content.select("section.sinopseEp p").eachText().joinToString("\n")
val infos = content.selectFirst("div.info > ol")!! val infos = content.selectFirst("div.info > ol")!!
@ -170,8 +170,8 @@ class AnimesGames : ParsedAnimeHttpSource() {
} }
private fun Element.getInfo(info: String) = private fun Element.getInfo(info: String) =
selectFirst("li:has(span:contains($info))")?.let { selectFirst("li:has(span:contains($info))")?.run {
it.selectFirst("span[data]")?.text() ?: it.ownText() selectFirst("span[data]")?.text() ?: ownText()
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================

View File

@ -19,24 +19,20 @@ object AnimesGamesFilters {
open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values) open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values)
class TriFilterVal(name: String) : TriState(name) class TriFilterVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart() return (first { it is R } as QueryPartFilter).toQueryPart()
} }
private inline fun <reified R> AnimeFilterList.parseTriFilter( private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
): List<List<String>> { ): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() } .filterNot { it.isIgnored() }
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second } .map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
.groupBy { it.first } // group by state .groupBy { it.first } // group by state
.let { .let { dict ->
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>() val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>() val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded) listOf(included, excluded)
} }
} }

View File

@ -36,7 +36,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.selectFirst("h2.ttl")!!.text() title = element.selectFirst("h2.ttl")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
} }
override fun popularAnimeNextPageSelector() = null override fun popularAnimeNextPageSelector() = null
@ -49,7 +49,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href")) setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
title = element.selectFirst("h2")!!.text() title = element.selectFirst("h2")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
} }
override fun latestUpdatesNextPageSelector() = "nav.pagination > a.next" override fun latestUpdatesNextPageSelector() = "nav.pagination > a.next"
@ -100,7 +100,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document) val doc = getRealDoc(document)
setUrlWithoutDomain(doc.location()) setUrlWithoutDomain(doc.location())
thumbnail_url = doc.selectFirst("img.lnk-blk")!!.attr("src") thumbnail_url = doc.selectFirst("img.lnk-blk")?.attr("src")
val infos = doc.selectFirst("header.hd > div.rght")!! val infos = doc.selectFirst("header.hd > div.rght")!!
title = infos.selectFirst("h2.title")!!.text() title = infos.selectFirst("h2.title")!!.text()
@ -110,7 +110,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
description = buildString { description = buildString {
infos.selectFirst("h2.ttl")?.text() infos.selectFirst("h2.ttl")?.text()
?.takeIf(String::isNotBlank) ?.takeIf(String::isNotBlank)
?.let { append("Títulos alternativos: $it\n\n") } ?.also { append("Títulos alternativos: $it\n\n") }
doc.select("div.entry > p").eachText().forEach { doc.select("div.entry > p").eachText().forEach {
append("$it\n") append("$it\n")
@ -159,7 +159,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
} }
private fun extractVideoFromResponse(response: Response): Video { private fun extractVideoFromResponse(response: Response): Video {
val decodedBody = LinkfunBypasser.decodeAtob(response.body.string()) val decodedBody = LinkfunBypasser.decodeAtob(response.use { it.body.string() })
val url = decodedBody val url = decodedBody
.substringAfter("sources") .substringAfter("sources")
.substringAfter("file: \"") .substringAfter("file: \"")
@ -185,7 +185,8 @@ class AnimesOrion : ParsedAnimeHttpSource() {
if (!document.location().contains("/episodio/")) return document if (!document.location().contains("/episodio/")) return document
return document.selectFirst("div.epsdsnv > a:has(i.fa-indent)")?.let { return document.selectFirst("div.epsdsnv > a:has(i.fa-indent)")?.let {
client.newCall(GET(baseUrl + it.attr("href"), headers)).execute().asJsoup() client.newCall(GET(it.attr("href"), headers)).execute()
.use { req -> req.asJsoup() }
} ?: document } ?: document
} }

View File

@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -45,18 +44,75 @@ class AnimesROLL : AnimeHttpSource() {
override fun popularAnimeRequest(page: Int) = GET(baseUrl) override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeParse(response: Response) = latestUpdatesParse(response) override fun popularAnimeParse(response: Response) = latestUpdatesParse(response)
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos")
override fun latestUpdatesParse(response: Response): AnimesPage {
val parsed = response.use { it.asJsoup() }.parseAs<LatestAnimeDto>()
val animes = parsed.episodes.map { it.episode.anime!!.toSAnime() }
return AnimesPage(animes, false)
}
// =============================== Search ===============================
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response)
return AnimesPage(listOf(details), false)
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.asObservableSuccess()
.map(::searchAnimeByPathParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
override fun searchAnimeParse(response: Response): AnimesPage {
val results = response.parseAs<SearchResultsDto>()
val animes = (results.animes + results.movies).map { it.toSAnime() }
return AnimesPage(animes, false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$OLD_API_URL/search?q=$query")
}
// =========================== Anime Details ============================
override fun animeDetailsParse(response: Response): SAnime {
val doc = response.use { it.asJsoup() }
val anime = when {
doc.location().contains("/f/") -> doc.parseAs<MovieInfoDto>().movieData
else -> doc.parseAs<AnimeDataDto>()
}
return anime.toSAnime().apply {
setUrlWithoutDomain(doc.location())
author = anime.director.takeIf { it != "0" }
description = buildString {
append(anime.description.ifNotEmpty { it + "\n" })
append(anime.duration.ifNotEmpty { "\nDuração: $it" })
append(anime.animeCalendar?.ifNotEmpty { "\nLança toda(o) $it" }.orEmpty())
}
genre = doc.select("div#generos > a").eachText().joinToString()
status = if (anime.animeCalendar == null) SAnime.COMPLETED else SAnime.ONGOING
}
}
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val originalUrl = response.request.url.toString() val doc = response.use { it.asJsoup() }
val originalUrl = doc.location()
return if ("/f/" in originalUrl) { return if ("/f/" in originalUrl) {
val od = response.asJsoup().parseAs<MovieInfoDto>().movieData.od val od = doc.parseAs<MovieInfoDto>().movieData.od
SEpisode.create().apply { SEpisode.create().apply {
url = "$OLD_API_URL/od/$od/filme.mp4" url = "$OLD_API_URL/od/$od/filme.mp4"
name = "Filme" name = "Filme"
episode_number = 0F episode_number = 0F
}.let(::listOf) }.let(::listOf)
} else { } else {
val anime = response.asJsoup().parseAs<AnimeDataDto>() val anime = doc.parseAs<AnimeDataDto>()
val urlStart = "https://cdn-01.gamabunta.xyz/hls/animes/${anime.slug}" val urlStart = "https://cdn-01.gamabunta.xyz/hls/animes/${anime.slug}"
return fetchEpisodesRecursively(anime.id).map { episode -> return fetchEpisodesRecursively(anime.id).map { episode ->
@ -101,68 +157,7 @@ class AnimesROLL : AnimeHttpSource() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
// =========================== Anime Details ============================
override fun animeDetailsParse(response: Response): SAnime {
val doc = response.asJsoup()
val anime = when {
response.request.url.toString().contains("/f/") ->
doc.parseAs<MovieInfoDto>().movieData
else -> doc.parseAs<AnimeDataDto>()
}
return anime.toSAnime().apply {
author = if (anime.director != "0") anime.director else null
var desc = anime.description.ifNotEmpty { it + "\n" }
desc += anime.duration.ifNotEmpty { "\nDuração: $it" }
desc += anime.animeCalendar?.let {
it.ifNotEmpty { "\nLança toda(o) $it" }
} ?: ""
description = desc
genre = doc.select("div#generos > a").joinToString(", ") { it.text() }
status = if (anime.animeCalendar == null) SAnime.COMPLETED else SAnime.ONGOING
}
}
// =============================== Search ===============================
private fun searchAnimeByPathParse(response: Response, path: String): AnimesPage {
val details = animeDetailsParse(response)
details.url = "/$path"
return AnimesPage(listOf(details), false)
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.asObservableSuccess()
.map { response ->
searchAnimeByPathParse(response, path)
}
} else {
super.fetchSearchAnime(page, query, filters)
}
}
override fun searchAnimeParse(response: Response): AnimesPage {
val results = response.parseAs<SearchResultsDto>()
val animes = (results.animes + results.movies).map { it.toSAnime() }
return AnimesPage(animes, false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$OLD_API_URL/search?q=$query")
}
// =============================== Latest ===============================
override fun latestUpdatesParse(response: Response): AnimesPage {
val parsed = response.asJsoup().parseAs<LatestAnimeDto>()
val animes = parsed.episodes.map { it.episode.anime!!.toSAnime() }
return AnimesPage(animes, false)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos")
// ============================= Utilities ============================== // ============================= Utilities ==============================
private inline fun <reified T> Document.parseAs(): T { private inline fun <reified T> Document.parseAs(): T {
val nextData = this.selectFirst("script#__NEXT_DATA__")!! val nextData = this.selectFirst("script#__NEXT_DATA__")!!
.data() .data()
@ -172,27 +167,24 @@ class AnimesROLL : AnimeHttpSource() {
} }
private inline fun <reified T> Response.parseAs(): T { private inline fun <reified T> Response.parseAs(): T {
val responseBody = body.string() return use { it.body.string() }.let(json::decodeFromString)
return json.decodeFromString(responseBody)
} }
private fun String.ifNotEmpty(block: (String) -> String): String { private fun String.ifNotEmpty(block: (String) -> String): String {
return if (isNotEmpty() && this != "0") block(this) else "" return if (isNotEmpty() && this != "0") block(this) else ""
} }
fun AnimeDataDto.toSAnime(): SAnime { fun AnimeDataDto.toSAnime() = SAnime.create().apply {
return SAnime.create().apply { val ismovie = slug == ""
val ismovie = slug == "" url = if (ismovie) "/f/$id" else "/anime/$slug"
url = if (ismovie) "/f/$id" else "/anime/$slug" thumbnail_url = "https://static.anroll.net/images/".let {
thumbnail_url = "https://static.anroll.net/images/".let { if (ismovie) {
if (ismovie) { it + "filmes/capas/$slug_movie.jpg"
it + "filmes/capas/$slug_movie.jpg" } else {
} else { it + "animes/capas/$slug.jpg"
it + "animes/capas/$slug.jpg"
}
} }
title = anititle
} }
title = anititle
} }
companion object { companion object {

View File

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.animeextension.pt.animestc
import eu.kanade.tachiyomi.animeextension.pt.animestc.dto.AnimeDto import eu.kanade.tachiyomi.animeextension.pt.animestc.dto.AnimeDto
import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilter.TriState
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
object ATCFilters { object ATCFilters {
open class QueryPartFilter( open class QueryPartFilter(
displayName: String, displayName: String,
val vals: Array<Pair<String, String>>, val vals: Array<Pair<String, String>>,
@ -17,17 +17,23 @@ object ATCFilters {
fun toQueryPart() = vals[state].second fun toQueryPart() = vals[state].second
} }
open class TriStateFilterList(name: String, values: List<TriState>) : AnimeFilter.Group<AnimeFilter.TriState>(name, values) open class TriStateFilterList(name: String, values: List<TriState>) : AnimeFilter.Group<TriState>(name, values)
private class TriStateVal(name: String) : AnimeFilter.TriState(name) private class TriStateVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart() }
}
private inline fun <reified R> AnimeFilterList.parseTriFilter(): List<List<String>> {
return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() }
.map { filter -> filter.state to filter.name }
.groupBy { it.first } // group by state
.let { dict ->
val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded)
}
} }
class InitialLetterFilter : QueryPartFilter("Primeira letra", ATCFiltersData.INITIAL_LETTER) class InitialLetterFilter : QueryPartFilter("Primeira letra", ATCFiltersData.INITIAL_LETTER)
@ -56,36 +62,32 @@ object ATCFilters {
data class FilterSearchParams( data class FilterSearchParams(
val initialLetter: String = "", val initialLetter: String = "",
val status: String = "", val status: String = "",
var orderAscending: Boolean = true, val orderAscending: Boolean = true,
var sortBy: String = "", val sortBy: String = "",
val blackListedGenres: ArrayList<String> = ArrayList(), val blackListedGenres: List<String> = emptyList(),
val includedGenres: ArrayList<String> = ArrayList(), val includedGenres: List<String> = emptyList(),
var animeName: String = "", var animeName: String = "",
) )
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams() if (filters.isEmpty()) return FilterSearchParams()
val searchParams = FilterSearchParams( val (includedGenres, excludedGenres) = filters.parseTriFilter<GenresFilter>()
val sortFilter = filters.firstOrNull { it is SortFilter } as? SortFilter
val (orderBy, ascending) = sortFilter?.state?.run {
val order = ATCFiltersData.ORDERS[index].second
val orderAscending = ascending
Pair(order, orderAscending)
} ?: Pair("", true)
return FilterSearchParams(
filters.asQueryPart<InitialLetterFilter>(), filters.asQueryPart<InitialLetterFilter>(),
filters.asQueryPart<StatusFilter>(), filters.asQueryPart<StatusFilter>(),
ascending,
orderBy,
includedGenres,
excludedGenres,
) )
filters.getFirst<SortFilter>().state?.let {
val order = ATCFiltersData.ORDERS[it.index].second
searchParams.orderAscending = it.ascending
searchParams.sortBy = order
}
filters.getFirst<GenresFilter>()
.state.forEach { genre ->
if (genre.isIncluded()) {
searchParams.includedGenres.add(genre.name)
} else if (genre.isExcluded()) {
searchParams.blackListedGenres.add(genre.name)
}
}
return searchParams
} }
private fun mustRemove(anime: AnimeDto, params: FilterSearchParams): Boolean { private fun mustRemove(anime: AnimeDto, params: FilterSearchParams): Boolean {

View File

@ -196,12 +196,14 @@ class AnimesTC : ConfigurableAnimeSource, AnimeHttpSource() {
val links = videoDto.links val links = videoDto.links
val allLinks = listOf(links.low, links.medium, links.high).flatten() val allLinks = listOf(links.low, links.medium, links.high).flatten()
val supportedPlayers = listOf("anonfiles", "send") val supportedPlayers = listOf("anonfiles", "send")
val online = links.online?.filterNot { "mega" in it }?.map { val online = links.online?.run {
Video(it, "Player ATC", it, headers) filterNot { "mega" in it }.map {
} ?: emptyList<Video>() Video(it, "Player ATC", it, headers)
}
}.orEmpty()
return online + allLinks.filter { it.name in supportedPlayers }.parallelMap { return online + allLinks.filter { it.name in supportedPlayers }.parallelMap {
val playerUrl = LinkBypasser(client, json).bypass(it, videoDto.id) val playerUrl = LinkBypasser(client, json).bypass(it, videoDto.id)
if (playerUrl == null) return@parallelMap null ?: return@parallelMap null
val quality = when (it.quality) { val quality = when (it.quality) {
"low" -> "SD" "low" -> "SD"
"medium" -> "HD" "medium" -> "HD"
@ -258,7 +260,7 @@ class AnimesTC : ConfigurableAnimeSource, AnimeHttpSource() {
} }
private fun Response.getAnimeDto(): AnimeDto { private fun Response.getAnimeDto(): AnimeDto {
val responseBody = body.string() val responseBody = use { it.body.string() }
return try { return try {
parseAs<AnimeDto>(responseBody) parseAs<AnimeDto>(responseBody)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.animeextension.pt.animestc.dto.VideoDto.VideoLink
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient

View File

@ -26,6 +26,7 @@ object AVFilters {
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
): String { ): String {
return (getFirst<R>() as CheckBoxFilterList).state return (getFirst<R>() as CheckBoxFilterList).state
.asSequence()
.filter { it.state } .filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second } .map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank) .filter(String::isNotBlank)

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.animesvision package eu.kanade.tachiyomi.animeextension.pt.animesvision
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 eu.kanade.tachiyomi.animeextension.pt.animesvision.dto.AVResponseDto import eu.kanade.tachiyomi.animeextension.pt.animesvision.dto.AVResponseDto
@ -26,10 +25,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@ -59,7 +57,7 @@ class AnimesVision : 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)
} }
@ -69,8 +67,8 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
private fun nextPageSelector() = "ul.pagination li.page-item:contains():not(.disabled)" private fun nextPageSelector() = "ul.pagination li.page-item:contains():not(.disabled)"
override fun popularAnimeSelector() = "div#anime-trending div.item > a.film-poster"
override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers) override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers)
override fun popularAnimeSelector() = "div#anime-trending div.item > a.film-poster"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
val img = element.selectFirst("img")!! val img = element.selectFirst("img")!!
@ -81,6 +79,93 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeNextPageSelector() = null override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos?page=$page")
override fun latestUpdatesSelector() = episodeListSelector()
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
title = element.selectFirst("h3")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun latestUpdatesNextPageSelector() = nextPageSelector()
// =============================== Search ===============================
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.asObservableSuccess()
.map(::searchAnimeByPathParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response)
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AVFilters.getSearchParameters(filters)
val url = "$baseUrl/search?".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("nome", query)
.addQueryParameter("tipo", params.type)
.addQueryParameter("idioma", params.language)
.addQueryParameter("ordenar", params.sort)
.addQueryParameter("ano_inicial", params.initial_year)
.addQueryParameter("ano_final", params.last_year)
.addQueryParameter("fansub", params.fansub)
.addQueryParameter("status", params.status)
.addQueryParameter("temporada", params.season)
.addQueryParameter("estudios", params.studio)
.addQueryParameter("produtores", params.producer)
.addQueryParameter("generos", params.genres)
.build()
return GET(url.toString(), headers)
}
override fun searchAnimeSelector() = "div.film_list-wrap div.film-poster"
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
val elementA = element.selectFirst("a")!!
title = elementA.attr("title")
setUrlWithoutDomain(elementA.attr("href"))
thumbnail_url = element.selectFirst("img")?.attr("data-src")
}
override fun searchAnimeNextPageSelector() = nextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
val content = doc.selectFirst("div#ani_detail div.anis-content")!!
val detail = content.selectFirst("div.anisc-detail")!!
val infos = content.selectFirst("div.anisc-info")!!
thumbnail_url = content.selectFirst("img")?.attr("src")
title = detail.selectFirst("h2.film-name")!!.text()
genre = infos.getInfo("Gêneros")
author = infos.getInfo("Produtores")
artist = infos.getInfo("Estúdios")
status = parseStatus(infos.getInfo("Status"))
description = buildString {
append(infos.getInfo("Sinopse") + "\n")
infos.getInfo("Inglês")?.also { append("\nTítulo em inglês: $it") }
infos.getInfo("Japonês")?.also { append("\nTítulo em japonês: $it") }
infos.getInfo("Foi ao ar em")?.also { append("\nFoi ao ar em: $it") }
infos.getInfo("Temporada")?.also { append("\nTemporada: $it") }
infos.getInfo("Duração")?.also { append("\nDuração: $it") }
infos.getInfo("Fansub")?.also { append("\nFansub: $it") }
}
}
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListSelector() = "div.container div.screen-items > div.item" override fun episodeListSelector() = "div.container div.screen-items > div.item"
@ -112,7 +197,7 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val body = response.body.string() val body = response.use { it.body.string() }
val internalVideos = GlobalVisionExtractor() val internalVideos = GlobalVisionExtractor()
.videoListFromHtml(body) .videoListFromHtml(body)
.toMutableList() .toMutableList()
@ -148,116 +233,30 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val reqBody = body.toRequestBody() val reqBody = body.toRequestBody()
val url = "$baseUrl/livewire/message/components.episodio.player-episodio-component" val url = "$baseUrl/livewire/message/components.episodio.player-episodio-component"
val response = client.newCall(POST(url, headers, reqBody)).execute() val response = client.newCall(POST(url, headers, reqBody)).execute()
val responseBody = response.body.string() val responseBody = response.use { it.body.string() }
val resJson = json.decodeFromString<AVResponseDto>(responseBody) val resJson = json.decodeFromString<AVResponseDto>(responseBody)
(resJson.serverMemo?.data?.framePlay ?: resJson.effects?.html) (resJson.serverMemo?.data?.framePlay ?: resJson.effects?.html)
?.let(::parsePlayerData) ?.let(::parsePlayerData).orEmpty()
?: emptyList<Video>()
}.flatten() }.flatten()
} }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val voeExtractor by lazy { VoeExtractor(client) }
private fun parsePlayerData(data: String) = runCatching { private fun parsePlayerData(data: String) = runCatching {
when { when {
"streamtape" in data -> "streamtape" in data -> voeExtractor.videosFromUrl(data)
StreamTapeExtractor(client).videoFromUrl(data)?.let(::listOf) "dood" in data -> doodExtractor.videosFromUrl(data)
"dood" in data -> "voe.sx" in data -> voeExtractor.videosFromUrl(data)
DoodExtractor(client).videoFromUrl(data)?.let(::listOf) else -> emptyList()
"voe.sx" in data ->
VoeExtractor(client).videoFromUrl(data)?.let(::listOf)
else -> null
} }
}.getOrNull() ?: emptyList<Video>() }.getOrElse { emptyList() }
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
val elementA = element.selectFirst("a")!!
title = elementA.attr("title")
setUrlWithoutDomain(elementA.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("data-src")
}
override fun searchAnimeNextPageSelector() = nextPageSelector()
override fun searchAnimeSelector() = "div.film_list-wrap div.film-poster"
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.asObservableSuccess()
.map(::searchAnimeByPathParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response)
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AVFilters.getSearchParameters(filters)
val url = "$baseUrl/search?".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("nome", query)
.addQueryParameter("tipo", params.type)
.addQueryParameter("idioma", params.language)
.addQueryParameter("ordenar", params.sort)
.addQueryParameter("ano_inicial", params.initial_year)
.addQueryParameter("ano_final", params.last_year)
.addQueryParameter("fansub", params.fansub)
.addQueryParameter("status", params.status)
.addQueryParameter("temporada", params.season)
.addQueryParameter("estudios", params.studio)
.addQueryParameter("produtores", params.producer)
.addQueryParameter("generos", params.genres)
return GET(url.build().toString(), headers)
}
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
val content = doc.selectFirst("div#ani_detail div.anis-content")!!
val detail = content.selectFirst("div.anisc-detail")!!
val infos = content.selectFirst("div.anisc-info")!!
thumbnail_url = content.selectFirst("img")!!.attr("src")
title = detail.selectFirst("h2.film-name")!!.text()
genre = infos.getInfo("Gêneros")
author = infos.getInfo("Produtores")
artist = infos.getInfo("Estúdios")
status = parseStatus(infos.getInfo("Status"))
description = buildString {
append(infos.getInfo("Sinopse") + "\n")
infos.getInfo("Inglês")?.let { append("\nTítulo em inglês: $it") }
infos.getInfo("Japonês")?.let { append("\nTítulo em japonês: $it") }
infos.getInfo("Foi ao ar em")?.let { append("\nFoi ao ar em: $it") }
infos.getInfo("Temporada")?.let { append("\nTemporada: $it") }
infos.getInfo("Duração")?.let { append("\nDuração: $it") }
infos.getInfo("Fansub")?.let { append("\nFansub: $it") }
}
}
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector() = nextPageSelector()
override fun latestUpdatesSelector() = episodeListSelector()
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
title = element.selectFirst("h3")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("src")
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos?page=$page")
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply { ListPreference(screen.context).apply {

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.animeextension.pt.animesvision.extractors package eu.kanade.tachiyomi.animeextension.pt.animesvision.extractors
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
class GlobalVisionExtractor { class GlobalVisionExtractor {
companion object { companion object {
private val REGEX_URL = Regex(""""file":"(\S+?)",.*?"label":"(.*?)"""") private val REGEX_URL = Regex(""""file":"(\S+?)",.*?"label":"(.*?)"""")

View File

@ -20,7 +20,6 @@ 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.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
@ -41,7 +40,7 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/") .add("Referer", "$baseUrl/")
@ -62,9 +61,9 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href")) setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href"))
thumbnail_url = element.selectFirst("div.cover-image")?.let { thumbnail_url = element.selectFirst("div.cover-image")?.run {
it.attr("style").substringAfter("url('").substringBefore("'") attr("style").substringAfter("url('").substringBefore("'")
} ?: "" }
title = element.selectFirst("span.series-title")!!.text() title = element.selectFirst("span.series-title")!!.text()
} }
@ -176,8 +175,8 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeNextPageSelector(): String? = null override fun searchAnimeNextPageSelector(): String? = null
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply { override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href")) setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src") ?: "" thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src")
title = element.selectFirst("div.aniInfos")?.text() ?: "Anime" title = element.selectFirst("div.aniInfos")?.text() ?: "Anime"
} }
@ -249,8 +248,8 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return SEpisode.create().apply { return SEpisode.create().apply {
name = "Ep. ${epNumber?.text()?.trim() ?: counter} - ${epTitle.replace(EPISODE_REGEX, "")}" name = "Ep. ${epNumber?.text()?.trim() ?: counter} - ${epTitle.replace(EPISODE_REGEX, "")}"
.replace(" - - ", " - ") .replace(" - - ", " - ")
episode_number = epNumber?.let { episode_number = epNumber?.run {
it.text().trim().toFloatOrNull() text().trim().toFloatOrNull()
} ?: counter.toFloat() } ?: counter.toFloat()
scanlator = info scanlator = info
setUrlWithoutDomain(element.selectFirst("article > a")!!.attr("href")) setUrlWithoutDomain(element.selectFirst("article > a")!!.attr("href"))
@ -287,25 +286,9 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.selectFirst("script:containsData(sources:)") .selectFirst("script:containsData(sources:)")
?.data() ?.data()
?.let(BloggerJWPlayerExtractor::videosFromScript) ?.let(BloggerJWPlayerExtractor::videosFromScript)
?: emptyList() .orEmpty()
}
url.startsWith(baseUrl) -> {
val videoDocument = client.newCall(GET(url, headers)).execute()
.use { it.asJsoup() }
val script = videoDocument.selectFirst("script:containsData(decodeURIComponent)")?.data()
?.let(::getDecrypted)
?: videoDocument.selectFirst("script:containsData(sources:)")?.data()
?: return@flatMap emptyList()
when {
"/bloggerjwplayer" in url || "jwplayer-2" in url || "/ctaplayer" in url -> {
BloggerJWPlayerExtractor.videosFromScript(script)
}
"/m3u8" in url -> PlaylistExtractor.videosFromScript(script)
else -> emptyList()
}
} }
url.startsWith(baseUrl) -> videosFromInternalUrl(url)
"blogger.com" in url -> extractBloggerVideos(url, vid.text().trim()) "blogger.com" in url -> extractBloggerVideos(url, vid.text().trim())
else -> emptyList() else -> emptyList()
} }
@ -314,6 +297,24 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return videoList return videoList
} }
private fun videosFromInternalUrl(url: String): List<Video> {
val videoDocument = client.newCall(GET(url, headers)).execute()
.use { it.asJsoup() }
val script = videoDocument.selectFirst("script:containsData(decodeURIComponent)")?.data()
?.let(::getDecrypted)
?: videoDocument.selectFirst("script:containsData(sources:)")?.data()
?: return emptyList()
return when {
"/bloggerjwplayer" in url || "jwplayer-2" in url || "/ctaplayer" in url -> {
BloggerJWPlayerExtractor.videosFromScript(script)
}
"/m3u8" in url -> PlaylistExtractor.videosFromScript(script)
else -> emptyList()
}
}
private fun extractBloggerVideos(url: String, name: String): List<Video> { private fun extractBloggerVideos(url: String, name: String): List<Video> {
return client.newCall(GET(url, headers)).execute() return client.newCall(GET(url, headers)).execute()
.use { it.body.string() } .use { it.body.string() }
@ -360,14 +361,14 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun getDecrypted(script: String): String { private fun getDecrypted(script: String): String? {
val patchedScript = script.trim().split("\n").first() val patchedScript = script.trim().split("\n").first()
.replace("eval(function", "function a") .replace("eval(function", "function a")
.replace("decodeURIComponent(escape(r))}(", "r};a(") .replace("decodeURIComponent(escape(r))}(", "r};a(")
.substringBeforeLast(")") .substringBeforeLast(")")
return QuickJs.create().use { return QuickJs.create().use {
it.evaluate(patchedScript).toString() it.evaluate(patchedScript)?.toString()
} }
} }

View File

@ -18,38 +18,22 @@ object AnimesZoneFilters {
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String { private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
val value = this.filterIsInstance<R>().joinToString("") { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart() .takeUnless(String::isEmpty)
} ?.let { "&$name=$it" }
return if (value.isEmpty()) { .orEmpty()
""
} else {
"&$name=$value"
}
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return this.filterIsInstance<R>().first()
} }
private inline fun <reified R> AnimeFilterList.parseCheckbox( private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
name: String, name: String,
): String { ): String {
return (this.getFirst<R>() as CheckBoxFilterList).state return (first { it is R } as CheckBoxFilterList).state
.mapNotNull { checkbox -> .asSequence()
if (checkbox.state) { .filter { it.state }
options.find { it.first == checkbox.name }!!.second .map { checkbox -> options.find { it.first == checkbox.name }!!.second }
} else { .filter(String::isNotBlank)
null .joinToString("%2C") { "&$name=$it" }
}
}.joinToString("%2C").let {
if (it.isBlank()) {
""
} else {
"&$name=$it"
}
}
} }
class GenreFilter : CheckBoxFilterList( class GenreFilter : CheckBoxFilterList(

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,13 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'AnimeTV'
pkgNameSuffix = 'pt.animetv'
extClass = '.AnimeTV'
extVersionCode = 1
libVersion = '12'
containsNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,139 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.animetv
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
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.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import okhttp3.Response
class AnimeTV : AnimeHttpSource() {
override val name = "AnimeTV"
override val baseUrl = "https://appanimeplus.tk/play-api.php"
override val lang = "pt-BR"
override val supportsLatest = true
private val cdnUrl = "https://cdn.appanimeplus.tk/img/"
// == POPULAR ANIME ==
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl?populares")
override fun popularAnimeParse(response: Response): AnimesPage {
val animes = Json.decodeFromString<JsonArray>(response.body.string())
if (animes.isEmpty()) return AnimesPage(emptyList(), false)
val animeList = mutableListOf<SAnime>()
for (anime in animes) {
val newAnime = SAnime.create()
newAnime.title = anime.jsonObject["category_name"]!!.jsonPrimitive.content
newAnime.thumbnail_url = "$cdnUrl" + anime.jsonObject["category_image"]!!.jsonPrimitive.content
newAnime.url = "$baseUrl?info=" + anime.jsonObject["id"]!!.jsonPrimitive.content
animeList.add(newAnime)
}
return AnimesPage(animeList, false)
}
// == LATEST ANIME == / DEPRECATED
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl?latest")
override fun latestUpdatesParse(response: Response): AnimesPage {
val animes = Json.decodeFromString<JsonArray>(response.body.string())
if (animes.isEmpty()) return AnimesPage(emptyList(), false)
val animeList = mutableListOf<SAnime>()
for (anime in animes) {
val newAnime = SAnime.create()
newAnime.title = anime.jsonObject["title"]!!.jsonPrimitive.content
newAnime.thumbnail_url = "$cdnUrl" + anime.jsonObject["category_image"]!!.jsonPrimitive.content
newAnime.url = "$baseUrl?info=" + anime.jsonObject["category_id"]!!.jsonPrimitive.content
animeList.add(newAnime)
}
return AnimesPage(animeList, false)
}
// == SEARCH ANIME ==
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl?search=$query")
override fun searchAnimeParse(response: Response): AnimesPage {
val animes = Json.decodeFromString<JsonArray>(response.body.string())
if (animes.isEmpty()) return AnimesPage(emptyList(), false)
val animeList = mutableListOf<SAnime>()
for (anime in animes) {
val newAnime = SAnime.create()
newAnime.title = anime.jsonObject["category_name"]!!.jsonPrimitive.content
newAnime.thumbnail_url = "$cdnUrl" + anime.jsonObject["category_image"]!!.jsonPrimitive.content
newAnime.url = "$baseUrl?info=" + anime.jsonObject["id"]!!.jsonPrimitive.content
animeList.add(newAnime)
}
return AnimesPage(animeList, false)
}
// == PARSE ANIME ==
override fun animeDetailsRequest(anime: SAnime): Request = GET(anime.url)
override fun animeDetailsParse(response: Response): SAnime {
val animes = Json.decodeFromString<JsonArray>(response.body.string())
if (animes.isEmpty()) return SAnime.create()
val animeData = animes.first()
val anime = SAnime.create()
anime.url = "$baseUrl?info=" + animeData.jsonObject["id"]!!.jsonPrimitive.content
anime.title = animeData.jsonObject["category_name"]!!.jsonPrimitive.content
anime.description = animeData.jsonObject["category_description"]!!.jsonPrimitive.content
anime.genre = animeData.jsonObject["category_genres"]!!.jsonPrimitive.content
anime.status = 0
anime.thumbnail_url = "$cdnUrl" + animeData.jsonObject["category_image"]!!.jsonPrimitive.content
anime.initialized = true
return anime
}
// == PARSE EPISODES ==
override fun episodeListRequest(anime: SAnime): Request = GET("$baseUrl?cat_id=" + anime.url.substring(42))
override fun episodeListParse(response: Response): List<SEpisode> {
val episodesData = Json.decodeFromString<JsonArray>(response.body.string())
if (episodesData.isEmpty()) return emptyList()
val episodes = mutableListOf<SEpisode>()
val ovas = mutableListOf<SEpisode>()
for (episode in episodesData) {
val newEpisode = SEpisode.create()
newEpisode.url = "$baseUrl?episodios=" + episode.jsonObject["video_id"]!!.jsonPrimitive.content
newEpisode.name = episode.jsonObject["title"]!!.jsonPrimitive.content
newEpisode.date_upload = System.currentTimeMillis()
if (newEpisode.name.contains("ova", ignoreCase = true)) {
ovas.add(newEpisode)
} else {
episodes.add(newEpisode)
}
}
if (episodes.first().name.last() === '1') episodes.reverse()
for (ova in ovas) { episodes.add(0, ova) }
return episodes
}
// == PARSE VIDEOS ==
override fun videoListRequest(episode: SEpisode): Request {
return GET("$baseUrl?episodios=" + episode.url.substring(47))
}
override fun videoListParse(response: Response): List<Video> {
val videosData = Json.decodeFromString<JsonArray>(response.body.string())
val videoData = videosData.first()
val videos = mutableListOf<Video>()
val hasSD = videoData.jsonObject["locationsd"]!!.jsonPrimitive.content.length > 0
if (hasSD) {
val url = videoData.jsonObject["locationsd"]!!.jsonPrimitive.content
videos.add(Video(url, "HD", url))
}
val url = videoData.jsonObject["location"]!!.jsonPrimitive.content
videos.add(Video(url, "SD", url))
return videos
}
// == FILTERS ==
override fun getFilterList(): AnimeFilterList = AnimeFilterList(emptyList())
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.anitube package eu.kanade.tachiyomi.animeextension.pt.anitube
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 eu.kanade.tachiyomi.animeextension.pt.anitube.extractors.AnitubeExtractor import eu.kanade.tachiyomi.animeextension.pt.anitube.extractors.AnitubeExtractor
@ -13,7 +12,6 @@ import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -34,9 +32,9 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
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)
} }
@ -114,7 +112,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val infos = content.selectFirst("div.anime_infos")!! val infos = content.selectFirst("div.anime_infos")!!
title = doc.selectFirst("div.anime_container_titulo")!!.text() title = doc.selectFirst("div.anime_container_titulo")!!.text()
thumbnail_url = content.selectFirst("img")!!.attr("src") thumbnail_url = content.selectFirst("img")?.attr("src")
genre = infos.getInfo("Gêneros") genre = infos.getInfo("Gêneros")
author = infos.getInfo("Autor") author = infos.getInfo("Autor")
artist = infos.getInfo("Estúdio") artist = infos.getInfo("Estúdio")
@ -125,7 +123,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
description = buildString { description = buildString {
append(doc.selectFirst("div.sinopse_container_content")!!.text() + "\n") append(doc.selectFirst("div.sinopse_container_content")!!.text() + "\n")
infoItems.forEach { item -> infoItems.forEach { item ->
infos.getInfo(item)?.let { append("\n$item: $it") } infos.getInfo(item)?.also { append("\n$item: $it") }
} }
} }
} }
@ -134,15 +132,15 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeListSelector() = "div.animepag_episodios_item > a" override fun episodeListSelector() = "div.animepag_episodios_item > a"
override fun episodeListParse(response: Response) = buildList { override fun episodeListParse(response: Response) = buildList {
var doc = getRealDoc(response.asJsoup()) var doc = getRealDoc(response.use { it.asJsoup() })
do { do {
if (isNotEmpty()) { if (isNotEmpty()) {
val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href") val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href")
doc = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup() doc = client.newCall(GET(baseUrl + path, headers)).execute().use { it.asJsoup() }
} }
doc.select(episodeListSelector()) doc.select(episodeListSelector())
.map(::episodeFromElement) .map(::episodeFromElement)
.let(::addAll) .also(::addAll)
} while (doc.selectFirst(popularAnimeNextPageSelector()) != null) } while (doc.selectFirst(popularAnimeNextPageSelector()) != null)
reverse() reverse()
} }
@ -192,7 +190,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)") return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)")
?.let { ?.let {
val path = it.attr("href") val path = it.attr("href")
client.newCall(GET(baseUrl + path, headers)).execute().asJsoup() client.newCall(GET(baseUrl + path, headers)).execute().use { it.asJsoup() }
} ?: document } ?: document
} }

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object BAFilters { object BAFilters {
open class QueryPartFilter( open class QueryPartFilter(
displayName: String, displayName: String,
val vals: Array<Pair<String, String>>, val vals: Array<Pair<String, String>>,
@ -19,14 +18,18 @@ object BAFilters {
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values) open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.getFirst(): R { private inline fun <reified R> AnimeFilterList.parseCheckbox(
return first { it is R } as R options: Array<Pair<String, String>>,
): List<String> {
return (first { it is R } as CheckBoxFilterList).state
.asSequence()
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.toList()
} }
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class LanguageFilter : QueryPartFilter("Idioma", BAFiltersData.LANGUAGES) class LanguageFilter : QueryPartFilter("Idioma", BAFiltersData.LANGUAGES)
@ -52,12 +55,7 @@ object BAFilters {
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams() if (filters.isEmpty()) return FilterSearchParams()
val genres = listOf(" ") + filters.getFirst<GenresFilter>().state val genres = listOf(" ") + filters.parseCheckbox<GenresFilter>(BAFiltersData.GENRES)
.mapNotNull { genre ->
if (genre.state) {
BAFiltersData.GENRES.find { it.first == genre.name }!!.second
} else { null }
}.toList()
return FilterSearchParams( return FilterSearchParams(
filters.asQueryPart<LanguageFilter>(), filters.asQueryPart<LanguageFilter>(),

View File

@ -106,7 +106,7 @@ class BetterAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
val body = response.body.string() val body = response.body.string()
val data = json.decodeFromString<LivewireResponseDto>(body) val data = json.decodeFromString<LivewireResponseDto>(body)
val html = data.effects.html?.unescape() ?: "" val html = data.effects.html?.unescape().orEmpty()
val document = Jsoup.parse(html) val document = Jsoup.parse(html)
val animes = document.select(searchAnimeSelector()).map { element -> val animes = document.select(searchAnimeSelector()).map { element ->
searchAnimeFromElement(element) searchAnimeFromElement(element)

View File

@ -9,7 +9,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -36,24 +35,24 @@ class BetterAnimeExtractor(
}.filterNotNull() }.filterNotNull()
} }
private fun videoUrlFromToken(qtoken: String, _token: String): String? { private fun videoUrlFromToken(qtoken: String, token: String): String? {
val body = """ val body = """
{ {
"_token": "$_token", "_token": "$token",
"info": "$qtoken" "info": "$qtoken"
} }
""".trimIndent() """.trimIndent()
val reqBody = body.toRequestBody("application/json".toMediaType()) val reqBody = body.toRequestBody("application/json".toMediaType())
val request = POST("$baseUrl/changePlayer", headers, reqBody) val request = POST("$baseUrl/changePlayer", headers, reqBody)
return runCatching { return runCatching {
val response = client.newCall(request).execute() val response = client.newCall(request).execute().use { it.body.string() }
val resJson = json.decodeFromString<ChangePlayerDto>(response.body.string()) val resJson = json.decodeFromString<ChangePlayerDto>(response)
resJson.frameLink?.let(::videoUrlFromPlayer) resJson.frameLink?.let(::videoUrlFromPlayer)
}.getOrNull() }.getOrNull()
} }
private fun videoUrlFromPlayer(url: String): String { private fun videoUrlFromPlayer(url: String): String {
val html = client.newCall(GET(url, headers)).execute().body.string() val html = client.newCall(GET(url, headers)).execute().use { it.body.string() }
val videoUrl = html.substringAfter("file\":") val videoUrl = html.substringAfter("file\":")
.substringAfter("\"") .substringAfter("\"")
@ -63,9 +62,9 @@ class BetterAnimeExtractor(
return videoUrl return videoUrl
} }
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(Dispatchers.Default) { runBlocking {
map { async { f(it) } }.awaitAll() map { async(Dispatchers.Default) { f(it) } }.awaitAll()
} }
companion object { companion object {

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.donghuanosekai package eu.kanade.tachiyomi.animeextension.pt.donghuanosekai
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 eu.kanade.tachiyomi.animeextension.pt.donghuanosekai.extractors.DonghuaNoSekaiExtractor import eu.kanade.tachiyomi.animeextension.pt.donghuanosekai.extractors.DonghuaNoSekaiExtractor
@ -21,7 +20,6 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -53,7 +51,7 @@ class DonghuaNoSekai : 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)
} }
@ -65,7 +63,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.attr("title") title = element.attr("title")
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
} }
override fun popularAnimeNextPageSelector() = null override fun popularAnimeNextPageSelector() = null
@ -78,7 +76,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.selectFirst("div.title h3")!!.text() title = element.selectFirst("div.title h3")!!.text()
thumbnail_url = element.selectFirst("div.thumb img")!!.attr("src") thumbnail_url = element.selectFirst("div.thumb img")?.attr("src")
} }
override fun latestUpdatesNextPageSelector() = "ul.content-pagination > li.next" override fun latestUpdatesNextPageSelector() = "ul.content-pagination > li.next"
@ -132,7 +130,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
addQueryParameter("filter_order", params.orderBy) addQueryParameter("filter_order", params.orderBy)
addQueryParameter("filter_status", params.status) addQueryParameter("filter_status", params.status)
addQueryParameter("type_url", "ONA") addQueryParameter("type_url", "ONA")
}.build().encodedQuery }.build().encodedQuery.orEmpty()
val genres = params.genres.joinToString { "\"$it\"" } val genres = params.genres.joinToString { "\"$it\"" }
val delgenres = params.deleted_genres.joinToString { "\"$it\"" } val delgenres = params.deleted_genres.joinToString { "\"$it\"" }
@ -166,7 +164,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document) val doc = getRealDoc(document)
setUrlWithoutDomain(doc.location()) setUrlWithoutDomain(doc.location())
thumbnail_url = doc.selectFirst("div.poster > img")!!.attr("src") thumbnail_url = doc.selectFirst("div.poster > img")?.attr("src")
val infos = doc.selectFirst("div.dados")!! val infos = doc.selectFirst("div.dados")!!
title = infos.selectFirst("h1")!!.text() title = infos.selectFirst("h1")!!.text()
@ -179,7 +177,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
doc.select("div.articleContent:has(div:contains(Sinopse)) > div.context > p") doc.select("div.articleContent:has(div:contains(Sinopse)) > div.context > p")
.eachText() .eachText()
.joinToString("\n\n") .joinToString("\n\n")
.let(::append) .also(::append)
append("\n") append("\n")
@ -199,7 +197,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply { override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
element.selectFirst("span.episode")!!.text().let { element.selectFirst("span.episode")!!.text().also {
name = it name = it
episode_number = it.substringAfterLast(" ").toFloatOrNull() ?: 0F episode_number = it.substringAfterLast(" ").toFloatOrNull() ?: 0F
} }
@ -207,9 +205,9 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
private val extractor by lazy { DonghuaNoSekaiExtractor(client, headers) }
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val doc = response.use { it.asJsoup() } val doc = response.use { it.asJsoup() }
val extractor = DonghuaNoSekaiExtractor(client, headers)
return doc.select("div.slideItem[data-video-url]").parallelMap { return doc.select("div.slideItem[data-video-url]").parallelMap {
runCatching { runCatching {
@ -263,7 +261,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} ?: document } ?: document
} }
private fun String?.parseStatus() = when (this?.trim()?.lowercase()) { private fun String?.parseStatus() = when (this?.run { trim().lowercase() }) {
"completo" -> SAnime.COMPLETED "completo" -> SAnime.COMPLETED
"em lançamento" -> SAnime.ONGOING "em lançamento" -> SAnime.ONGOING
"em pausa" -> SAnime.ON_HIATUS "em pausa" -> SAnime.ON_HIATUS

View File

@ -19,26 +19,20 @@ object DonghuaNoSekaiFilters {
open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values) open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values)
class TriFilterVal(name: String) : TriState(name) class TriFilterVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
private inline fun <reified R> AnimeFilterList.parseTriFilter( private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>, options: Array<Pair<String, String>>,
): List<List<String>> { ): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() } .filterNot { it.isIgnored() }
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second } .map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
.groupBy { it.first } // group by state .groupBy { it.first } // group by state
.let { .let { dict ->
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>() val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>() val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded) listOf(included, excluded)
} }
} }

View File

@ -29,8 +29,8 @@ class DonghuaNoSekaiExtractor(
val iframeUrl = iframe.attr("src") val iframeUrl = iframe.attr("src")
when { when {
iframeUrl.contains("nativov2.php") || iframeUrl.contains("/embed2/") -> { iframeUrl.contains("nativov2.php") || iframeUrl.contains("/embed2/") -> {
val url = iframeUrl.toHttpUrl().let { val url = iframeUrl.toHttpUrl().run {
it.queryParameter("id") ?: it.queryParameter("v") queryParameter("id") ?: queryParameter("v")
} ?: return emptyList() } ?: return emptyList()
val quality = url.substringAfter("_").substringBefore("_") val quality = url.substringAfter("_").substringBefore("_")
@ -51,16 +51,16 @@ class DonghuaNoSekaiExtractor(
.substringBefore("]") .substringBefore("]")
.split("{") .split("{")
.drop(1) .drop(1)
.map { .map { line ->
val url = it.substringAfter("file: \"").substringBefore('"') val url = line.substringAfter("file: \"").substringBefore('"')
val quality = it.substringAfter("label: \"") val quality = line.substringAfter("label: \"")
.substringBefore('"') .substringBefore('"')
.let { label -> .run {
when (label) { when (this) {
"SD" -> "480p" "SD" -> "480p"
"HD" -> "720p" "HD" -> "720p"
"FHD", "FULLHD" -> "1080p" "FHD", "FULLHD" -> "1080p"
else -> label else -> this
} }
} }
Video(url, "$playerName - $quality", url, headers) Video(url, "$playerName - $quality", url, headers)

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.flixei package eu.kanade.tachiyomi.animeextension.pt.flixei
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 eu.kanade.tachiyomi.animeextension.pt.flixei.dto.AnimeDto import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.AnimeDto
@ -25,7 +24,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
@ -52,7 +50,7 @@ class Flixei : 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)
} }
@ -68,12 +66,10 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(animes, false) return AnimesPage(animes, false)
} }
private fun parseAnimeFromObject(anime: AnimeDto): SAnime { private fun parseAnimeFromObject(anime: AnimeDto) = SAnime.create().apply {
return SAnime.create().apply { title = anime.title
title = anime.title setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis")
setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis") thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp"
thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp"
}
} }
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
@ -88,6 +84,66 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filmes/estreia/$page")
override fun latestUpdatesSelector() = "div.generalMoviesList > a.gPoster"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
title = element.selectFirst("div.i span")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("src")
setUrlWithoutDomain(element.attr("abs:href"))
}
override fun latestUpdatesNextPageSelector() = "div.paginationSystem a.next"
// =============================== Search ===============================
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/assistir/$path/online/gratis"))
.asObservableSuccess()
.map(::searchAnimeByPathParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.use { it.asJsoup() })
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$baseUrl/pesquisar/$query")
}
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = null
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
thumbnail_url = document.selectFirst("meta[property=og:image]")?.attr("content")
val container = document.selectFirst("div.moviePresent")!!
with(container) {
title = selectFirst("h2.tit")!!.text()
genre = select("div.genres > span").eachText().joinToString()
author = getInfo("Diretor")
artist = getInfo("Produtoras")
description = buildString {
selectFirst("p")?.text()?.also { append(it + "\n\n") }
getInfo("Título")?.also { append("Título original: $it\n") }
getInfo("Serie de")?.also { append("ano: $it\n") }
getInfo("Elenco")?.also { append("Elenco: $it\n") }
getInfo("Qualidade")?.also { append("Qualidade: $it\n") }
}
}
}
// ============================== Episodes ============================== // ============================== Episodes ==============================
private fun getSeasonEps(seasonElement: Element): List<SEpisode> { private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
val id = seasonElement.attr("data-load-episodes") val id = seasonElement.attr("data-load-episodes")
@ -105,13 +161,13 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val docUrl = response.asJsoup().selectFirst("div#playButton")!! val docUrl = response.use { it.asJsoup() }.selectFirst("div#playButton")!!
.attr("onclick") .attr("onclick")
.substringAfter("'") .substringAfter("'")
.substringBefore("'") .substringBefore("'")
return if (response.request.url.toString().contains("/serie/")) { return if (response.request.url.toString().contains("/serie/")) {
client.newCall(GET(docUrl)).execute() client.newCall(GET(docUrl)).execute()
.asJsoup() .use { it.asJsoup() }
.select("div#seasons div.item[data-load-episodes]") .select("div#seasons div.item[data-load-episodes]")
.flatMap(::getSeasonEps) .flatMap(::getSeasonEps)
.reversed() .reversed()
@ -133,28 +189,6 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(document.location())
thumbnail_url = document.selectFirst("meta[property=og:image]")!!.attr("content")
val container = document.selectFirst("div.moviePresent")!!
with(container) {
title = selectFirst("h2.tit")!!.text()
genre = select("div.genres > span").eachText().joinToString()
author = getInfo("Diretor")
artist = getInfo("Produtoras")
description = buildString {
selectFirst("p")?.text()?.let { append(it + "\n\n") }
getInfo("Título")?.let { append("Título original: $it\n") }
getInfo("Serie de")?.let { append("ano: $it\n") }
getInfo("Elenco")?.let { append("Elenco: $it\n") }
getInfo("Qualidade")?.let { append("Qualidade: $it\n") }
}
}
}
}
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request { override fun videoListRequest(episode: SEpisode): Request {
val url = episode.url val url = episode.url
@ -167,7 +201,7 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val body = response.body.string() val body = response.use { it.body.string() }
// Pair<Language, Query> // Pair<Language, Query>
val items = if (body.startsWith("{")) { val items = if (body.startsWith("{")) {
val data = json.decodeFromString<ApiResultsDto<PlayersDto>>(body) val data = json.decodeFromString<ApiResultsDto<PlayersDto>>(body)
@ -203,6 +237,9 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return items.parallelMap(::getVideosFromItem).flatten() return items.parallelMap(::getVideosFromItem).flatten()
} }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private fun getVideosFromItem(item: Pair<String, String>): List<Video> { private fun getVideosFromItem(item: Pair<String, String>): List<Video> {
val (lang, query) = item val (lang, query) = item
val headers = headersBuilder().set("referer", WAREZ_URL).build() val headers = headersBuilder().set("referer", WAREZ_URL).build()
@ -211,19 +248,16 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} else { } else {
client.newCall(GET("$WAREZ_URL/embed/getPlay.php$query", headers)) client.newCall(GET("$WAREZ_URL/embed/getPlay.php$query", headers))
.execute() .execute()
.body.string() .use { it.body.string() }
.substringAfter("location.href=\"") .substringAfter("location.href=\"")
.substringBefore("\";") .substringBefore("\";")
} }
return when (query.substringAfter("sv=")) { return when (query.substringAfter("sv=")) {
"streamtape" -> "streamtape" -> streamtapeExtractor.videosFromUrl(hostUrl, "Streamtape($lang)")
StreamTapeExtractor(client).videoFromUrl(hostUrl, "Streamtape($lang)") "mixdrop" -> mixdropExtractor.videoFromUrl(hostUrl, lang)
?.let(::listOf)
"mixdrop" ->
MixDropExtractor(client).videoFromUrl(hostUrl, lang)
else -> null // TODO: Add warezcdn extractor else -> null // TODO: Add warezcdn extractor
} ?: emptyList() }.orEmpty()
} }
override fun videoFromElement(element: Element): Video { override fun videoFromElement(element: Element): Video {
@ -238,86 +272,44 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
throw UnsupportedOperationException("Not used.") throw UnsupportedOperationException("Not used.")
} }
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeNextPageSelector() = null
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$baseUrl/pesquisar/$query")
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/assistir/$path/online/gratis"))
.asObservableSuccess()
.map(::searchAnimeByPathParse)
} else {
super.fetchSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup())
return AnimesPage(listOf(details), false)
}
// =============================== Latest ===============================
override fun latestUpdatesFromElement(element: Element): SAnime {
return SAnime.create().apply {
title = element.selectFirst("div.i span")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("src")
setUrlWithoutDomain(element.attr("abs:href"))
}
}
override fun latestUpdatesNextPageSelector() = "div.paginationSystem a.next"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filmes/estreia/$page")
override fun latestUpdatesSelector() = "div.generalMoviesList > a.gPoster"
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val preferredPlayer = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_PLAYER_KEY key = PREF_PLAYER_KEY
title = PREF_PLAYER_TITLE title = PREF_PLAYER_TITLE
entries = PREF_PLAYER_ARRAY entries = PREF_PLAYER_ARRAY
entryValues = PREF_PLAYER_ARRAY entryValues = PREF_PLAYER_ARRAY
setDefaultValue(PREF_PLAYER_DEFAULT) setDefaultValue(PREF_PLAYER_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
val preferredLanguage = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY key = PREF_LANGUAGE_KEY
title = PREF_LANGUAGE_TITLE title = PREF_LANGUAGE_TITLE
entries = PREF_LANGUAGE_ENTRIES entries = PREF_LANGUAGE_ENTRIES
entryValues = PREF_LANGUAGE_VALUES entryValues = PREF_LANGUAGE_VALUES
setDefaultValue(PREF_LANGUAGE_DEFAULT) setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(preferredPlayer)
screen.addPreference(preferredLanguage)
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
private inline fun <reified T> Response.parseAs(): T { private inline fun <reified T> Response.parseAs(): T {
return body.string().let(json::decodeFromString) return use { it.body.string() }.let(json::decodeFromString)
} }
private inline fun <A, B> Iterable<A>.parallelMap(crossinline f: suspend (A) -> B): List<B> = private inline fun <A, B> Iterable<A>.parallelMap(crossinline f: suspend (A) -> B): List<B> =

View File

@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -58,10 +57,10 @@ class HentaisTube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeSelector() = "ul.ul_sidebar > li" override fun popularAnimeSelector() = "ul.ul_sidebar > li"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
element.selectFirst("div.rt a.series")!!.also { element.selectFirst("div.rt a.series")!!.run {
setUrlWithoutDomain(it.attr("href")) setUrlWithoutDomain(attr("href"))
title = it.text().substringBefore(" - Episódios") title = text().substringBefore(" - Episódios")
} }
} }
@ -75,7 +74,7 @@ class HentaisTube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href").substringBeforeLast("-") + "s") setUrlWithoutDomain(element.attr("href").substringBeforeLast("-") + "s")
title = element.attr("title") title = element.attr("title")
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
} }
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
@ -145,7 +144,7 @@ class HentaisTube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location()) setUrlWithoutDomain(document.location())
val infos = document.selectFirst("div#anime")!! val infos = document.selectFirst("div#anime")!!
thumbnail_url = infos.selectFirst("img")!!.attr("src") thumbnail_url = infos.selectFirst("img")?.attr("src")
title = infos.getInfo("Hentai:") title = infos.getInfo("Hentai:")
genre = infos.getInfo("Tags") genre = infos.getInfo("Tags")
artist = infos.getInfo("Estúdio") artist = infos.getInfo("Estúdio")
@ -165,15 +164,17 @@ class HentaisTube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
return response.asJsoup().select(videoListSelector()).parallelMap { return response.use { it.asJsoup() }.select(videoListSelector()).parallelMap {
runCatching { runCatching {
client.newCall(GET(it.attr("src"), headers)).execute().use { res -> client.newCall(GET(it.attr("src"), headers)).execute().use { res ->
extractVideosFromIframe(res.asJsoup()) extractVideosFromIframe(res.use { it.asJsoup() })
} }
}.getOrElse { emptyList() } }.getOrElse { emptyList() }
}.flatten() }.flatten()
} }
private val bloggerExtractor by lazy { BloggerExtractor(client) }
private fun extractVideosFromIframe(iframe: Document): List<Video> { private fun extractVideosFromIframe(iframe: Document): List<Video> {
val url = iframe.location() val url = iframe.location()
return when { return when {
@ -185,11 +186,11 @@ class HentaisTube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
url.contains("/index.php") -> { url.contains("/index.php") -> {
val bloggerUrl = iframe.selectFirst("iframe")!!.attr("src") val bloggerUrl = iframe.selectFirst("iframe")!!.attr("src")
BloggerExtractor(client).videosFromUrl(bloggerUrl, headers) bloggerExtractor.videosFromUrl(bloggerUrl, headers)
} }
url.contains("/player.php") -> { url.contains("/player.php") -> {
val ahref = iframe.selectFirst("a")!!.attr("href") val ahref = iframe.selectFirst("a")!!.attr("href")
val internal = client.newCall(GET(ahref, headers)).execute().asJsoup() val internal = client.newCall(GET(ahref, headers)).execute().use { it.asJsoup() }
val videoUrl = internal.selectFirst("video > source")!!.attr("src") val videoUrl = internal.selectFirst("video > source")!!.attr("src")
listOf(Video(videoUrl, "Alternativo", videoUrl, headers)) listOf(Video(videoUrl, "Alternativo", videoUrl, headers))
} }

View File

@ -35,9 +35,9 @@ object HentaisTubeFilters {
.filterNot { it.isIgnored() } .filterNot { it.isIgnored() }
.map { filter -> filter.state to filter.name } .map { filter -> filter.state to filter.name }
.groupBy { it.first } // group by state .groupBy { it.first } // group by state
.let { .let { dict ->
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>() val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>() val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded) listOf(included, excluded)
} }
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.hinatasoul package eu.kanade.tachiyomi.animeextension.pt.hinatasoul
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 eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor
@ -36,7 +35,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client = network.cloudflareClient override val client = network.cloudflareClient
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)
} }
@ -95,7 +94,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply { override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src") thumbnail_url = element.selectFirst("img")?.attr("src")
title = element.selectFirst("div.ultimosAnimesHomeItemInfosNome")!!.text() title = element.selectFirst("div.ultimosAnimesHomeItemInfosNome")!!.text()
} }
@ -144,7 +143,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
doc.select(episodeListSelector()) doc.select(episodeListSelector())
.map(::episodeFromElement) .map(::episodeFromElement)
.let(::addAll) .also(::addAll)
} while (hasNextPage(doc)) } while (hasNextPage(doc))
reverse() reverse()
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.listadeanimes package eu.kanade.tachiyomi.animeextension.pt.listadeanimes
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
@ -16,35 +15,65 @@ import rx.Observable
class ListaDeAnimes : ParsedAnimeHttpSource() { class ListaDeAnimes : ParsedAnimeHttpSource() {
override val name = "Lista de Animes" override val name = "Lista de Animes"
override val baseUrl = "https://www.listadeanimes.com" override val baseUrl = "https://www.listadeanimes.com"
override val lang = "pt-BR" override val lang = "pt-BR"
override val supportsLatest = false override val supportsLatest = false
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.add("Referer", baseUrl) .add("Referer", baseUrl)
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeSelector(): String = "article.post.excerpt > div.capa:not(:has(a[href=https://www.listadeanimes.com/anime-lista-online]))" override fun popularAnimeRequest(page: Int) = GET("$baseUrl/page/$page")
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeSelector() = "article.post.excerpt > div.capa:not(:has(a[href=$baseUrl/anime-lista-online]))"
return SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
val img = element.selectFirst("img")!! setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
title = titleCase(img.attr("title").substringBefore(" todos os episódios")) val img = element.selectFirst("img")!!
thumbnail_url = img.attr("data-src") title = titleCase(img.attr("title").substringBefore(" todos os episódios"))
initialized = false thumbnail_url = img.attr("data-src")
}
} }
override fun popularAnimeNextPageSelector() = "a.next.page-numbers" override fun popularAnimeNextPageSelector() = "a.next.page-numbers"
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() // =============================== Latest ===============================
val animes = document.select(popularAnimeSelector()).map(::popularAnimeFromElement) override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.")
return AnimesPage(animes, document.selectFirst(searchAnimeNextPageSelector()) != null) override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used.")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = GET("$baseUrl/page/$page?s=$query")
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val titleText = document.selectFirst("h1.title.single-title")!!.text()
title = titleCase(titleText.substringBefore(" todos os episódios"))
thumbnail_url = document.selectFirst("img.aligncenter.size-full")?.attr("src")
val infos = document.selectFirst("div#content.post-single-content > center")
val infosText = infos?.run {
html()
.replace("<br>", "\n")
.replace("<b>", "")
.replace("</b>", "")
}?.let { "\n\n$it" }.orEmpty()
val sinopse = document.selectFirst("div#content > *:contains(Sinopse)")?.nextElementSibling()
description = (sinopse?.text() ?: "Sem sinopse.") + infosText
genre = document.select("a[rel=tag]").joinToString { it.text() }
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListSelector(): String = "div.videos > ul" override fun episodeListSelector() = "div.videos > ul"
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup() val doc = response.asJsoup()
return doc.select("div.videos > ul > li:gt(0)") return doc.select("div.videos > ul > li:gt(0)")
@ -75,53 +104,10 @@ class ListaDeAnimes : ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw UnsupportedOperationException("Not used.") override fun videoFromElement(element: Element) = throw UnsupportedOperationException("Not used.")
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") override fun videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
// =============================== Search =============================== // ============================= Utilities ==============================
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/page/$page?s=$query")
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
override fun searchAnimeParse(response: Response): AnimesPage = popularAnimeParse(response)
override fun getFilterList(): AnimeFilterList = AnimeFilterList(emptyList())
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(document.location())
val titleText = document.selectFirst("h1.title.single-title")!!.text()
title = titleCase(titleText.substringBefore(" todos os episódios"))
thumbnail_url = document.selectFirst("img.aligncenter.size-full")!!.attr("src")
val infos = document.selectFirst("div#content.post-single-content > center")
var infosText: String? = null
if (infos != null) {
infosText = infos.html()
.replace("<br>", "\n")
.replace("<b>", "")
.replace("</b>", "")
}
val sinopse = document.selectFirst("div#content > *:contains(Sinopse)")?.nextElementSibling()
description = (if (sinopse != null) sinopse?.text() else "Sem sinopse.") + if (infosText != null) "\n\n$infosText" else ""
genre = document.select("a[rel=tag]").joinToString(", ") { it.text() }
}
}
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesParse(response: Response): AnimesPage = throw UnsupportedOperationException("Not used.")
private fun titleCase(str: String): String { private fun titleCase(str: String): String {
val builder = StringBuilder(str) return str.split(' ')
var whitespace = true .map { it.replaceFirstChar(Char::uppercase) }
str.forEachIndexed { index, c -> .joinToString(" ")
if (c.isWhitespace()) {
whitespace = true
} else if (whitespace) {
builder.setCharAt(index, c.uppercaseChar())
whitespace = false
}
}
return builder.toString()
} }
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix package eu.kanade.tachiyomi.animeextension.pt.megaflix
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
@ -42,7 +41,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/") override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
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)
} }
@ -54,7 +53,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
title = element.selectFirst("h2.entry-title")!!.text() title = element.selectFirst("h2.entry-title")!!.text()
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href")) setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("abs:src") thumbnail_url = element.selectFirst("img")?.absUrl("src")
} }
override fun popularAnimeNextPageSelector() = null override fun popularAnimeNextPageSelector() = null
@ -110,7 +109,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
setUrlWithoutDomain(document.location()) setUrlWithoutDomain(document.location())
val infos = document.selectFirst("div.bd > article.post.single")!! val infos = document.selectFirst("div.bd > article.post.single")!!
title = infos.selectFirst("h1.entry-title")!!.text() title = infos.selectFirst("h1.entry-title")!!.text()
thumbnail_url = "https:" + infos.selectFirst("img")!!.attr("src") thumbnail_url = infos.selectFirst("img")?.absUrl("src")
genre = infos.select("span.genres > a").eachText().joinToString() genre = infos.select("span.genres > a").eachText().joinToString()
description = infos.selectFirst("div.description")?.text() description = infos.selectFirst("div.description")?.text()
} }
@ -146,33 +145,31 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply { override fun episodeFromElement(element: Element) = SEpisode.create().apply {
name = element.selectFirst("h2.entry-title")!!.text() name = element.selectFirst("h2.entry-title")!!.text()
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href")) setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
episode_number = element.selectFirst("span.num-epi") episode_number = element.selectFirst("span.num-epi")?.run {
?.text() text().split("x").let {
?.split("x")
?.let {
val season = it.first().toFloatOrNull() ?: 0F val season = it.first().toFloatOrNull() ?: 0F
val episode = it.last().toFloatOrNull() ?: 0F val episode = it.last().toFloatOrNull() ?: 0F
season * 100F + episode season * 100F + episode
} }
?: 0F } ?: 0F
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val items = response.asJsoup().select(videoListSelector()) val items = response.use { it.asJsoup() }.select(videoListSelector())
return items return items
.parallelMap { element -> .parallelMap { element ->
val language = element.text().substringAfter("-") val language = element.text().substringAfter("-")
val id = element.attr("href") val id = element.attr("href")
val url = element.parents().get(5) val url = element.parents().get(5)?.selectFirst("div$id a")
?.selectFirst("div$id a") ?.run {
?.attr("href") attr("href")
?.substringAfter("token=") .substringAfter("token=")
?.let { Base64.decode(it, Base64.DEFAULT).let(::String) } .let { String(Base64.decode(it, Base64.DEFAULT)) }
?.substringAfter("||") .substringAfter("||")
?: return@parallelMap emptyList() } ?: return@parallelMap emptyList()
runCatching { getVideoList(url, language) }.getOrNull() ?: emptyList() runCatching { getVideoList(url, language) }.getOrNull().orEmpty()
}.flatten() }.flatten()
} }
@ -183,7 +180,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private fun getVideoList(url: String, language: String): List<Video>? { private fun getVideoList(url: String, language: String): List<Video>? {
return when { return when {
"mixdrop.co" in url -> mixdropExtractor.videoFromUrl(url, language) "mixdrop.co" in url -> mixdropExtractor.videoFromUrl(url, language)
"streamtape.com" in url -> streamtapeExtractor.videoFromUrl(url, "StreamTape - $language")?.let(::listOf) "streamtape.com" in url -> streamtapeExtractor.videosFromUrl(url, "StreamTape - $language")
"mflix.vip" in url -> megaflixExtractor.videosFromUrl(url, language) "mflix.vip" in url -> megaflixExtractor.videosFromUrl(url, language)
else -> null else -> null
} }

View File

@ -16,9 +16,7 @@ object MegaflixFilters {
} }
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.first { it is R }.let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class GenreFilter : QueryPartFilter("Gênero", MegaflixFiltersData.GENRES) class GenreFilter : QueryPartFilter("Gênero", MegaflixFiltersData.GENRES)

View File

@ -12,7 +12,7 @@ class MegaflixExtractor(private val client: OkHttpClient, private val headers: H
fun videosFromUrl(url: String, lang: String = ""): List<Video> { fun videosFromUrl(url: String, lang: String = ""): List<Video> {
val unpacked = client.newCall(GET(url, headers)).execute() val unpacked = client.newCall(GET(url, headers)).execute()
.body.string() .use { it.body.string() }
.let(JsUnpacker::unpackAndCombine) .let(JsUnpacker::unpackAndCombine)
?.replace("\\", "") ?.replace("\\", "")
?: return emptyList() ?: return emptyList()

View File

@ -16,9 +16,7 @@ object MAFilters {
} }
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return first { it is R }.let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class LetterFilter : QueryPartFilter("Letra inicial", MAFiltersData.LETTERS) class LetterFilter : QueryPartFilter("Letra inicial", MAFiltersData.LETTERS)
@ -45,6 +43,8 @@ object MAFilters {
) )
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams( return FilterSearchParams(
filters.asQueryPart<LetterFilter>(), filters.asQueryPart<LetterFilter>(),
filters.asQueryPart<YearFilter>(), filters.asQueryPart<YearFilter>(),

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes package eu.kanade.tachiyomi.animeextension.pt.meusanimes
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 eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors.IframeExtractor import eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors.IframeExtractor
@ -34,76 +33,39 @@ class MeusAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
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 popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element) override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeNextPageSelector(): String? = null
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
override fun popularAnimeSelector(): String = "div.ultAnisContainerItem > a" override fun popularAnimeSelector(): String = "div.ultAnisContainerItem > a"
// ============================== Episodes ============================== override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.attr("href")!!.also { override fun popularAnimeNextPageSelector() = null
setUrlWithoutDomain(it)
episode_number = it.substringAfterLast("/").toFloatOrNull() ?: 0F // =============================== Latest ===============================
} override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
name = element.text()
override fun latestUpdatesSelector() = "div.ultEpsContainerItem > a"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
title = element.attr("title")
thumbnail_url = element.selectFirst("img")?.attr("data-lazy-src")
val epUrl = element.attr("href")
if (epUrl.substringAfterLast("/").toIntOrNull() != null) {
setUrlWithoutDomain(epUrl.substringBeforeLast("/") + "-todos-os-episodios")
} else { setUrlWithoutDomain(epUrl) }
} }
override fun episodeListSelector(): String = "div#aba_epi > a" override fun latestUpdatesNextPageSelector() = null
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val infos = document.selectFirst("div.animeInfos")!!
val right = document.selectFirst("div.right")!!
setUrlWithoutDomain(document.location())
title = right.selectFirst("h1")!!.text()
genre = right.select("ul.animeGen a").eachText().joinToString()
thumbnail_url = infos.selectFirst("img")!!.attr("data-lazy-src")
description = right.selectFirst("div.animeSecondContainer > p:gt(0)")!!.text()
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoElement = document.selectFirst("div.playerBox > *")!!
return if (videoElement.tagName() == "video") {
MeusAnimesExtractor(client).videoListFromElement(videoElement)
} else {
IframeExtractor(client, headers).videoListFromIframe(videoElement)
}
}
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeNextPageSelector() = "div.paginacao > a.next"
override fun getFilterList(): AnimeFilterList = MAFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = MAFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val defaultUrl = "$baseUrl/lista-de-animes/$page"
val params = MAFilters.getSearchParameters(filters)
return when {
params.letter.isNotBlank() -> GET("$defaultUrl?letra=${params.letter}")
params.year.isNotBlank() -> GET("$defaultUrl?ano=${params.year}")
params.audio.isNotBlank() -> GET("$defaultUrl?audio=${params.audio}")
params.genre.isNotBlank() -> GET("$defaultUrl?genero=${params.genre}")
query.isNotBlank() -> GET("$defaultUrl?s=$query")
else -> GET(defaultUrl)
}
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> { override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH) val id = query.removePrefix(PREFIX_SEARCH)
@ -120,38 +82,85 @@ class MeusAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(listOf(details), false) return AnimesPage(listOf(details), false)
} }
// =============================== Latest =============================== override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { val defaultUrl = "$baseUrl/lista-de-animes/$page"
title = element.attr("title") val params = MAFilters.getSearchParameters(filters)
thumbnail_url = element.selectFirst("img")?.attr("data-lazy-src") return when {
val epUrl = element.attr("href") params.letter.isNotBlank() -> GET("$defaultUrl?letra=${params.letter}")
params.year.isNotBlank() -> GET("$defaultUrl?ano=${params.year}")
if (epUrl.substringAfterLast("/").toIntOrNull() != null) { params.audio.isNotBlank() -> GET("$defaultUrl?audio=${params.audio}")
setUrlWithoutDomain(epUrl.substringBeforeLast("/") + "-todos-os-episodios") params.genre.isNotBlank() -> GET("$defaultUrl?genero=${params.genre}")
} else { setUrlWithoutDomain(epUrl) } query.isNotBlank() -> GET("$defaultUrl?s=$query")
else -> GET(defaultUrl)
}
} }
override fun latestUpdatesNextPageSelector(): String? = null override fun searchAnimeSelector() = popularAnimeSelector()
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
override fun latestUpdatesSelector(): String = "div.ultEpsContainerItem > a" override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = "div.paginacao > a.next"
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val infos = document.selectFirst("div.animeInfos")!!
val right = document.selectFirst("div.right")!!
setUrlWithoutDomain(document.location())
title = right.selectFirst("h1")!!.text()
genre = right.select("ul.animeGen a").eachText().joinToString()
thumbnail_url = infos.selectFirst("img")?.attr("data-lazy-src")
description = right.selectFirst("div.animeSecondContainer > p:gt(0)")!!.text()
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListSelector() = "div#aba_epi > a"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.attr("href").also {
setUrlWithoutDomain(it)
episode_number = it.substringAfterLast("/").toFloatOrNull() ?: 0F
}
name = element.text()
}
// ============================ Video Links =============================
private val meusanimesExtractor by lazy { MeusAnimesExtractor(client) }
private val iframeExtractor by lazy { IframeExtractor(client, headers) }
override fun videoListParse(response: Response): List<Video> {
val document = response.use { it.asJsoup() }
val videoElement = document.selectFirst("div.playerBox > *")!!
return when (videoElement.tagName()) {
"video" -> meusanimesExtractor.videoListFromElement(videoElement)
else -> iframeExtractor.videoListFromIframe(videoElement)
}
}
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_ENTRIES entryValues = PREF_QUALITY_ENTRIES
setDefaultValue(PREF_QUALITY_DEFAULT) setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref)
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================

View File

@ -1,13 +1,16 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext { ext {
extName = 'Muito Hentai' extName = 'Muito Hentai'
pkgNameSuffix = 'pt.muitohentai' pkgNameSuffix = 'pt.muitohentai'
extClass = '.MuitoHentai' extClass = '.MuitoHentai'
extVersionCode = 1 extVersionCode = 2
libVersion = '12' libVersion = '13'
containsNsfw = true containsNsfw = true
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.animeextension.pt.muitohentai package eu.kanade.tachiyomi.animeextension.pt.muitohentai
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -16,123 +14,106 @@ import java.text.SimpleDateFormat
class MuitoHentai : ParsedAnimeHttpSource() { class MuitoHentai : ParsedAnimeHttpSource() {
override val name = "Muito Hentai" override val name = "Muito Hentai"
override val baseUrl = "https://www.muitohentai.com" override val baseUrl = "https://www.muitohentai.com"
override val lang = "pt-BR" override val lang = "pt-BR"
override val supportsLatest = true override val supportsLatest = true
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeSelector(): String = "ul.ul_sidebar > li" override fun popularAnimeRequest(page: Int) = GET("$baseUrl/ranking-hentais/?paginacao=$page")
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ranking-hentais/?paginacao=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeSelector() = "ul.ul_sidebar > li"
return SAnime.create().apply {
thumbnail_url = element.selectFirst("div.zeroleft > a > img")!!.attr("src") override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
val a = element.selectFirst("div.lefthentais > div > b:gt(0) > a.series")!! thumbnail_url = element.selectFirst("div.zeroleft > a > img")?.attr("src")
url = a.attr("href") element.selectFirst("div.lefthentais > div > b:gt(0) > a.series")!!.run {
title = a.text() setUrlWithoutDomain(attr("href"))
title = text()
} }
} }
override fun popularAnimeNextPageSelector() = "div.paginacao > a:contains(»)" override fun popularAnimeNextPageSelector() = "div.paginacao > a:contains(»)"
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() // =============================== Latest ===============================
val animes = document.select(popularAnimeSelector()).map(::popularAnimeFromElement) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/")
return AnimesPage(animes, document.selectFirst(popularAnimeNextPageSelector()) != null)
override fun latestUpdatesSelector() = "div.animation-2 > article:contains(Episódio)"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
val slug = element.selectFirst("a")!!.attr("href")
.substringAfter("/episodios/")
.substringBefore("-episodio")
url = "/info/$slug"
val img = element.selectFirst("img")!!
title = img.attr("alt")
thumbnail_url = img.attr("src")
}
override fun latestUpdatesNextPageSelector() = null
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = GET("$baseUrl/buscar/$query")
override fun searchAnimeSelector() = "div#archive-content > article > div.poster"
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
val img = element.selectFirst("img")!!
title = img.attr("alt")
thumbnail_url = img.attr("src")
}
override fun searchAnimeNextPageSelector() = null
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val data = document.selectFirst("div.sheader > div.data")!!
title = data.selectFirst("h1")!!.text()
genre = data.selectFirst("div.sgeneros")!!.children()
.filterNot { it.text().contains(title) }
.joinToString { it.text() }
description = data.selectFirst("div#info1 > div.wp-content > p")!!.text()
thumbnail_url = document.selectFirst("div.sheader > div.poster > img")?.attr("src")
} }
// ============================== Episodes ============================== // ============================== Episodes ==============================
override fun episodeListSelector(): String = "article.item" override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup() override fun episodeListSelector() = "article.item"
return doc
.select(episodeListSelector()) override fun episodeFromElement(element: Element) = SEpisode.create().apply {
.map(::episodeFromElement) val data = element.selectFirst("div.data")!!
.reversed() setUrlWithoutDomain(element.selectFirst("div.poster > div.season_m > a")!!.attr("href"))
} name = data.selectFirst("h3")!!.text().trim()
override fun episodeFromElement(element: Element): SEpisode { date_upload = data.selectFirst("span")?.text()?.parseDate() ?: 0L
return SEpisode.create().apply {
val data = element.selectFirst("div.data")!!
url = element.selectFirst("div.poster > div.season_m > a")!!.attr("href")
name = data.selectFirst("h3")!!.text().trim()
date_upload = parseDate(data.selectFirst("span")!!.text())
}
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
override fun videoListSelector() = "div.playex > div#option-0 > iframe" override fun videoListSelector() = "div.playex > div#option-0 > iframe"
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup() val doc = response.use { it.asJsoup() }
val idplay = doc.selectFirst(videoListSelector())!!.attr("src").substringAfter("?idplay=") val idplay = doc.selectFirst(videoListSelector())!!.attr("src").substringAfter("?idplay=")
val res = client.newCall(GET("https://www.hentaitube.online/players_sites/mt/index.php?idplay=$idplay")).execute() val res = client.newCall(GET("https://www.hentaitube.online/players_sites/mt/index.php?idplay=$idplay")).execute()
val pdoc = res.asJsoup() val pdoc = res.use { it.asJsoup() }
return pdoc return pdoc
.select("source") .select("source")
.map(::videoFromElement) .map(::videoFromElement)
.reversed()
} }
override fun videoFromElement(element: Element): Video { override fun videoFromElement(element: Element): Video {
val url = element.attr("src") val url = element.attr("src")
return Video(url, element.attr("label"), url) return Video(url, element.attr("label"), url)
} }
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") override fun videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
// =============================== Search =============================== // ============================= Utilities ==============================
override fun searchAnimeSelector() = "div#archive-content > article > div.poster" private fun String.parseDate(): Long {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/buscar/$query") return runCatching { DATE_FORMATTER.parse(this)?.time }
override fun searchAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
url = element.selectFirst("a")!!.attr("href")
val img = element.selectFirst("img")!!
title = img.attr("alt")
thumbnail_url = img.attr("src")
}
}
override fun searchAnimeNextPageSelector() = throw UnsupportedOperationException("Not used.")
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document
.select(searchAnimeSelector())
.map(::searchAnimeFromElement)
return AnimesPage(animes, false)
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(emptyList())
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(document.location())
val data = document.selectFirst("div.sheader > div.data")!!
title = data.selectFirst("h1")!!.text()
genre = data.selectFirst("div.sgeneros")!!.children().filter { it -> !it.text().contains(title) }.joinToString(", ") { it.text() }
description = data.selectFirst("div#info1 > div.wp-content > p")!!.text()
thumbnail_url = document.selectFirst("div.sheader > div.poster > img")!!.attr("src")
}
}
// =============================== Latest ===============================
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesSelector(): String = "div.animation-2 > article:contains(Episódio)"
override fun latestUpdatesFromElement(element: Element): SAnime {
return SAnime.create().apply {
val slug = element.selectFirst("a")!!.attr("href")
.substringAfter("/episodios/")
.substringBefore("-episodio")
url = "/info/$slug"
val img = element.selectFirst("img")!!
title = img.attr("alt")
thumbnail_url = img.attr("src")
}
}
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/")
override fun latestUpdatesParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document
.select(latestUpdatesSelector())
.map(::latestUpdatesFromElement)
return AnimesPage(animes, false)
}
private fun parseDate(dateStr: String): Long {
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L .getOrNull() ?: 0L
} }
companion object { companion object {

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer package eu.kanade.tachiyomi.animeextension.pt.vizer
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 eu.kanade.tachiyomi.animeextension.pt.vizer.VizerFilters.FilterSearchParams import eu.kanade.tachiyomi.animeextension.pt.vizer.VizerFilters.FilterSearchParams
@ -27,7 +26,6 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
@ -48,11 +46,11 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client = network.cloudflareClient
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)
} }
@ -88,83 +86,13 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
thumbnail_url = "$baseUrl/content/$imgslug/posterPt/342/${item.id}.webp" thumbnail_url = "$baseUrl/content/$imgslug/posterPt/342/${item.id}.webp"
} }
// ============================== Episodes ============================== // =============================== Latest ===============================
private fun getSeasonEps(seasonElement: Element): List<SEpisode> { override fun latestUpdatesRequest(page: Int) = apiRequest("getHomeSliderSeries=1")
val id = seasonElement.attr("data-season-id")
val sname = seasonElement.text()
val response = client.newCall(apiRequest("getEpisodes=$id")).execute()
val episodes = response.parseAs<EpisodeListDto>().episodes
.filter { it.released }
.map {
SEpisode.create().apply {
name = "Temp $sname: Ep ${it.name} - ${it.title}"
episode_number = it.name.toFloatOrNull() ?: 0F
url = it.id
}
}
return episodes
}
override fun episodeListParse(response: Response): List<SEpisode> { override fun latestUpdatesParse(response: Response): AnimesPage {
val doc = response.asJsoup() val parsedData = response.parseAs<SearchResultDto>()
val seasons = doc.select("div#seasonsList div.item[data-season-id]") val animes = parsedData.list.map(::animeFromObject)
return if (seasons.size > 0) { return AnimesPage(animes, false)
seasons.flatMap(::getSeasonEps).reversed()
} else {
listOf(
SEpisode.create().apply {
name = "Filme"
episode_number = 1F
url = response.request.url.toString()
},
)
}
}
// ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request {
val url = episode.url
return if (url.startsWith("https")) {
// Its an real url, maybe from a movie
GET(url, headers)
} else {
// Fake url, its an ID that will be used to get episode languages
// (sub/dub) and then return the video link
apiRequest("getEpisodeLanguages=$url")
}
}
override fun videoListParse(response: Response): List<Video> {
val body = response.body.string()
val videoObjectList = if (body.startsWith("{")) {
json.decodeFromString<VideoLanguagesDto>(body).videos
} else {
val videoJson = body.substringAfterLast("videoPlayerBox(").substringBefore(");")
json.decodeFromString<VideoLanguagesDto>(videoJson).videos
}
return videoObjectList.flatMap(::getVideosFromObject)
}
private fun getVideosFromObject(videoObj: VideoDto): List<Video> {
val players = client.newCall(apiRequest("getVideoPlayers=${videoObj.id}"))
.execute()
.parseAs<PlayersDto>()
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
val videoList = players.iterator().mapNotNull { (name, status) ->
if (status == "0") return@mapNotNull null
val url = getPlayerUrl(videoObj.id, name)
when (name) {
"mixdrop" ->
MixDropExtractor(client).videoFromUrl(url, langPrefix)
"streamtape" ->
StreamTapeExtractor(client)
.videoFromUrl(url, "StreamTape($langPrefix)")
?.let(::listOf)
else -> null
}
}.flatten()
return videoList
} }
// =============================== Search =============================== // =============================== Search ===============================
@ -227,77 +155,145 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
description = buildString { description = buildString {
append(doc.selectFirst("span.desc")!!.text() + "\n") append(doc.selectFirst("span.desc")!!.text() + "\n")
doc.selectFirst("div.year")?.let { append("\nAno: ${it.text()}") } doc.selectFirst("div.year")?.also { append("\nAno: ${it.text()}") }
doc.selectFirst("div.tm")?.let { append("\nDuração: ${it.text()}") } doc.selectFirst("div.tm")?.also { append("\nDuração: ${it.text()}") }
doc.selectFirst("a.rating")?.let { append("\nNota: ${it.text()}") } doc.selectFirst("a.rating")?.also { append("\nNota: ${it.text()}") }
} }
} }
// =============================== Latest =============================== // ============================== Episodes ==============================
override fun latestUpdatesRequest(page: Int) = apiRequest("getHomeSliderSeries=1") private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
val id = seasonElement.attr("data-season-id")
val sname = seasonElement.text()
val response = client.newCall(apiRequest("getEpisodes=$id")).execute()
val episodes = response.parseAs<EpisodeListDto>().episodes
.filter { it.released }
.map {
SEpisode.create().apply {
name = "Temp $sname: Ep ${it.name} - ${it.title}"
episode_number = it.name.toFloatOrNull() ?: 0F
url = it.id
}
}
return episodes
}
override fun latestUpdatesParse(response: Response): AnimesPage { override fun episodeListParse(response: Response): List<SEpisode> {
val parsedData = response.parseAs<SearchResultDto>() val doc = response.use { it.asJsoup() }
val animes = parsedData.list.map(::animeFromObject).toList() val seasons = doc.select("div#seasonsList div.item[data-season-id]")
return AnimesPage(animes, false) return if (seasons.size > 0) {
seasons.flatMap(::getSeasonEps).reversed()
} else {
listOf(
SEpisode.create().apply {
name = "Filme"
episode_number = 1F
url = response.request.url.toString()
},
)
}
}
// ============================ Video Links =============================
override fun videoListRequest(episode: SEpisode): Request {
val url = episode.url
return if (url.startsWith("https")) {
// Its an real url, maybe from a movie
GET(url, headers)
} else {
// Fake url, its an ID that will be used to get episode languages
// (sub/dub) and then return the video link
apiRequest("getEpisodeLanguages=$url")
}
}
override fun videoListParse(response: Response): List<Video> {
val body = response.use { it.body.string() }
val videoObjectList = if (body.startsWith("{")) {
json.decodeFromString<VideoLanguagesDto>(body).videos
} else {
val videoJson = body.substringAfterLast("videoPlayerBox(").substringBefore(");")
json.decodeFromString<VideoLanguagesDto>(videoJson).videos
}
return videoObjectList.flatMap(::getVideosFromObject)
}
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private fun getVideosFromObject(videoObj: VideoDto): List<Video> {
val players = client.newCall(apiRequest("getVideoPlayers=${videoObj.id}"))
.execute()
.parseAs<PlayersDto>()
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
val videoList = players.iterator().mapNotNull { (name, status) ->
if (status == "0") return@mapNotNull null
val url = getPlayerUrl(videoObj.id, name)
when (name) {
"mixdrop" -> mixdropExtractor.videoFromUrl(url, langPrefix)
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
else -> null
}
}.flatten()
return videoList
} }
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val popularPage = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_POPULAR_PAGE_KEY key = PREF_POPULAR_PAGE_KEY
title = PREF_POPULAR_PAGE_TITLE title = PREF_POPULAR_PAGE_TITLE
entries = PREF_POPULAR_PAGE_ENTRIES entries = PREF_POPULAR_PAGE_ENTRIES
entryValues = PREF_POPULAR_PAGE_VALUES entryValues = PREF_POPULAR_PAGE_VALUES
setDefaultValue(PREF_POPULAR_PAGE_DEFAULT) setDefaultValue(PREF_POPULAR_PAGE_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
val preferredPlayer = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_PLAYER_KEY key = PREF_PLAYER_KEY
title = PREF_PLAYER_TITLE title = PREF_PLAYER_TITLE
entries = PREF_PLAYER_ARRAY entries = PREF_PLAYER_ARRAY
entryValues = PREF_PLAYER_ARRAY entryValues = PREF_PLAYER_ARRAY
setDefaultValue(PREF_PLAYER_DEFAULT) setDefaultValue(PREF_PLAYER_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
val preferredLanguage = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY key = PREF_LANGUAGE_KEY
title = PREF_LANGUAGE_TITLE title = PREF_LANGUAGE_TITLE
entries = PREF_LANGUAGE_ENTRIES entries = PREF_LANGUAGE_ENTRIES
entryValues = PREF_LANGUAGE_VALUES entryValues = PREF_LANGUAGE_VALUES
setDefaultValue(PREF_LANGUAGE_DEFAULT) setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = findIndexOfValue(selected) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(popularPage)
screen.addPreference(preferredPlayer)
screen.addPreference(preferredLanguage)
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
private fun getPlayerUrl(id: String, name: String): String { private fun getPlayerUrl(id: String, name: String): String {
val req = GET("$baseUrl/embed/getPlay.php?id=$id&sv=$name") val req = GET("$baseUrl/embed/getPlay.php?id=$id&sv=$name")
val body = client.newCall(req).execute().body.string() val body = client.newCall(req).execute().use { it.body.string() }
return body.substringAfter("location.href=\"").substringBefore("\";") return body.substringAfter("location.href=\"").substringBefore("\";")
} }
@ -319,8 +315,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
} }
private inline fun <reified T> Response.parseAs(): T { private inline fun <reified T> Response.parseAs(): T {
val responseBody = body.string() return use { it.body.string() }.let(json::decodeFromString)
return json.decodeFromString(responseBody)
} }
companion object { companion object {

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object VizerFilters { object VizerFilters {
open class QueryPartFilter( open class QueryPartFilter(
displayName: String, displayName: String,
val vals: Array<Pair<String, String>>, val vals: Array<Pair<String, String>>,
@ -16,14 +15,8 @@ object VizerFilters {
fun toQueryPart() = vals[state].second fun toQueryPart() = vals[state].second
} }
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String { private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let { return (first { it is R } as QueryPartFilter).toQueryPart()
(it as QueryPartFilter).toQueryPart()
}
} }
class TypeFilter : QueryPartFilter("Tipo", VizerFiltersData.TYPES) class TypeFilter : QueryPartFilter("Tipo", VizerFiltersData.TYPES)
@ -50,27 +43,32 @@ object VizerFilters {
val minYear: String = "1890", val minYear: String = "1890",
val maxYear: String = "2022", val maxYear: String = "2022",
val genre: String = "all", val genre: String = "all",
var orderBy: String = "rating", val orderBy: String = "rating",
var orderWay: String = "desc", val orderWay: String = "desc",
) )
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
val searchParams = FilterSearchParams( if (filters.isEmpty()) return FilterSearchParams()
val sortFilter = filters.firstOrNull { it is SortFilter } as? SortFilter
val (orderBy, ascending) = sortFilter?.state?.run {
val order = VizerFiltersData.ORDERS[index].second
val orderWay = if (ascending) "asc" else "desc"
Pair(order, orderWay)
} ?: Pair("rating", "desc")
return FilterSearchParams(
filters.asQueryPart<TypeFilter>(), filters.asQueryPart<TypeFilter>(),
filters.asQueryPart<MinYearFilter>(), filters.asQueryPart<MinYearFilter>(),
filters.asQueryPart<MaxYearFilter>(), filters.asQueryPart<MaxYearFilter>(),
filters.asQueryPart<GenreFilter>(), filters.asQueryPart<GenreFilter>(),
orderBy,
ascending,
) )
filters.getFirst<SortFilter>().state?.let {
val order = VizerFiltersData.ORDERS[it.index].second
searchParams.orderBy = order
searchParams.orderWay = if (it.ascending) "asc" else "desc"
}
return searchParams
} }
private object VizerFiltersData { private object VizerFiltersData {
val TYPES = arrayOf( val TYPES = arrayOf(
Pair("Animes", "anime"), Pair("Animes", "anime"),
Pair("Filmes", "Movies"), Pair("Filmes", "Movies"),