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.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Request
@ -43,6 +42,10 @@ class AniDong : ParsedAnimeHttpSource() {
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "article.top10_animes_item > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.attr("title")
@ -50,20 +53,134 @@ class AniDong : ParsedAnimeHttpSource() {
}
override fun popularAnimeNextPageSelector() = null
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "article.top10_animes_item > a"
// ============================== Episodes ==============================
override fun episodeFromElement(element: Element): SEpisode {
// =============================== Latest ===============================
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.")
}
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 {
throw UnsupportedOperationException("Not used.")
}
override fun episodeFromElement(element: Element): SEpisode {
throw UnsupportedOperationException("Not used.")
}
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 body = FormBody.Builder()
@ -71,8 +188,9 @@ class AniDong : ParsedAnimeHttpSource() {
.add("anime_id", id)
.build()
val res = client.newCall(POST("$baseUrl/api", headers = apiHeaders, body = body)).execute()
val data = json.decodeFromString<EpisodeListDto>(res.body.string())
val res = client.newCall(POST("$baseUrl/api", apiHeaders, body)).execute()
.use { it.body.string() }
val data = json.decodeFromString<EpisodeListDto>(res)
return buildList {
data.episodes.forEach { add(episodeFromObject(it, "Episódio")) }
@ -88,40 +206,9 @@ class AniDong : ParsedAnimeHttpSource() {
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 =============================
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
val doc = response.use { it.asJsoup() }
return doc.select("div.player_option").flatMap {
val url = it.attr("data-playerlink")
val playerName = it.text().trim()
@ -158,97 +245,13 @@ class AniDong : ParsedAnimeHttpSource() {
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 ==============================
private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/video/")) return document
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
}

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.animefire
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -38,11 +36,11 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override val client = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -51,42 +49,34 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.add("Accept-Language", ACCEPT_LANGUAGE)
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/top-animes/$page")
override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top-animes/$page")
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// ============================== Episodes ==============================
override fun episodeListSelector(): String = "div.div_video_list > a"
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/home/$page")
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")
setUrlWithoutDomain(url)
name = element.text()
episode_number = url.substringAfterLast("/").toFloatOrNull() ?: 0F
}
// ============================ Video Links =============================
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)
// get anime url from episode url
when (url.substringAfterLast("/").toIntOrNull()) {
null -> setUrlWithoutDomain(url)
else -> {
val substr = url.substringBeforeLast("/")
setUrlWithoutDomain("$substr-todos-os-episodios")
}
}
title = element.selectFirst("h3.animeTitle")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("data-src")
}
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")
override fun latestUpdatesNextPageSelector() = "ul.pagination img.seta-right"
// =============================== 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> {
return if (query.startsWith(PREFIX_SEARCH)) {
val id = query.removePrefix(PREFIX_SEARCH)
@ -115,50 +105,60 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return GET("$baseUrl/pesquisar/$fixedQuery/$page")
}
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val content = document.selectFirst("div.divDivAnimeInfo")!!
val names = content.selectFirst("div.div_anime_names")!!
val infos = content.selectFirst("div.divAnimePageInfo")!!
setUrlWithoutDomain(document.location())
thumbnail_url = content.selectFirst("div.sub_animepage_img > img")!!
.attr("data-src")
thumbnail_url = content.selectFirst("div.sub_animepage_img > img")?.attr("data-src")
title = names.selectFirst("h1")!!.text()
genre = infos.select("a.spanGeneros").eachText().joinToString()
author = infos.getInfo("Estúdios")
status = parseStatus(infos.getInfo("Status"))
description = buildString {
append(content.selectFirst("div.divSinopse > span")!!.text() + "\n")
names.selectFirst("h6")?.let { append("\nNome alternativo: ${it.text()}") }
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")
content.selectFirst("div.divSinopse > span")?.also {
append(it.text() + "\n")
}
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -181,7 +181,6 @@ class AnimeFire : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun getFilterList(): AnimeFilterList = AFFilters.FILTER_LIST
// ============================= Utilities ==============================
private fun parseStatus(statusString: String?): Int {
return when (statusString?.trim()) {
"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.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import org.jsoup.nodes.Element
class AnimeFireExtractor(private val client: OkHttpClient, private val json: Json) {
fun videoListFromElement(videoElement: Element): List<Video> {
val jsonUrl = videoElement.attr("data-video-src")
val response = client.newCall(GET(jsonUrl)).execute()
val responseDto = json.decodeFromString<AFResponseDto>(
response.body.string(),
)
val videoList = responseDto.videos.map {
.use { it.body.string() }
val responseDto = json.decodeFromString<AFResponseDto>(response)
return responseDto.videos.map {
val url = it.url.replace("\\", "")
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 iframeUrl = iframeElement.attr("src")
val response = client.newCall(GET(iframeUrl, headers)).execute()
val html = response.body.string()
val url = html.substringAfter("play_url")
.use { it.body.string() }
val url = response.substringAfter("play_url")
.substringAfter(":\"")
.substringBefore("\"")
val video = Video(url, "Default", url, headers = headers)

View File

@ -29,6 +29,10 @@ class AnimesAria : ParsedAnimeHttpSource() {
override val supportsLatest = true
// ============================== 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 {
setUrlWithoutDomain(element.attr("href"))
title = element.attr("title")
@ -37,23 +41,60 @@ class AnimesAria : ParsedAnimeHttpSource() {
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 episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.parent()!!.selectFirst("a > b")!!.ownText().let {
name = it
episode_number = it.substringAfter(" ").toFloatOrNull() ?: 0F
}
setUrlWithoutDomain(element.attr("href"))
scanlator = element.text().substringAfter(" ") // sub/dub
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 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 ============================
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")!!
title = row.selectFirst("h1 > span")!!.text()
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()
description = buildString {
document.selectFirst("li.active > small")!!
.ownText()
.substringAfter(": ")
.let(::append)
.also(::append)
append("\n\n")
row.selectFirst("h1 > small")?.text()?.let {
row.selectFirst("h1 > small")?.text()?.also {
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 =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
@ -119,61 +174,6 @@ class AnimesAria : ParsedAnimeHttpSource() {
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 ==============================
private fun String?.toStatus() = when (this) {
"Finalizado" -> SAnime.COMPLETED

View File

@ -16,9 +16,7 @@ object AnimesAriaFilters {
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.first { it is R }.let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
class TypeFilter : QueryPartFilter("Tipo", AnimesAriaFiltersData.TYPES)
@ -40,16 +38,18 @@ object AnimesAriaFilters {
)
data class FilterSearchParams(
val type: String,
val genre: String,
val status: String,
val letter: String,
val audio: String,
val year: String,
val season: String,
val type: String = "",
val genre: String = "",
val status: String = "",
val letter: String = "",
val audio: String = "",
val year: String = "",
val season: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<TypeFilter>(),
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.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
@ -49,50 +48,109 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
// ============================== Popular ===============================
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = getRealDoc(response.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)
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page")
override fun latestUpdatesSelector() = "div.b_flex:nth-child(2) > div.itemE > a"
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 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")
}
}
override fun latestUpdatesNextPageSelector() = "ul > li.next"
// =============================== Search ===============================
override fun getFilterList() = AnimesDigitalFilters.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/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 ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
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()) {
"Em Lançamento" -> SAnime.ONGOING
"Completo" -> SAnime.COMPLETED
@ -110,11 +168,47 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 =============================
override fun videoListParse(response: Response): List<Video> {
val player = response.asJsoup().selectFirst("div#player")!!
return player.select("div.tab-video").flatMap {
it.select(videoListSelector()).flatMap { element ->
val player = response.use { it.asJsoup() }.selectFirst("div#player")!!
return player.select("div.tab-video").flatMap { div ->
div.select(videoListSelector()).flatMap { element ->
runCatching {
videosFromElement(element)
}.onFailure { it.printStackTrace() }.getOrElse { emptyList() }
@ -133,18 +227,18 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}
client.newCall(GET(url, headers)).execute()
.asJsoup()
.use { it.asJsoup() }
.select(videoListSelector())
.flatMap(::videosFromElement)
}
"script" -> {
val scriptData = element.data().let {
when {
"eval(function" in it -> Unpacker.unpack(it).ifEmpty { null }
"eval(function" in it -> Unpacker.unpack(it)
else -> it
}
}?.replace("\\", "")
scriptData?.let(::videosFromScript) ?: emptyList()
}.ifEmpty { null }?.replace("\\", "")
scriptData?.let(::videosFromScript).orEmpty()
}
else -> emptyList()
}
@ -177,99 +271,6 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
@ -300,7 +301,7 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return document.selectFirst("div.subitem > a:contains(menu)")?.let { link ->
client.newCall(GET(link.attr("href")))
.execute()
.asJsoup()
.use { it.asJsoup() }
} ?: document
}
@ -309,15 +310,11 @@ class AnimesDigital : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
private fun Element.getInfo(key: String): String? {
return selectFirst("div.info:has(span:containsOwn($key))")
?.ownText()
?.trim()
?.let {
when (it) {
"", "?" -> null
else -> it
}
}
return selectFirst("div.info:has(span:containsOwn($key))")?.run {
ownText()
.trim()
.takeUnless { it.isBlank() || it == "?" }
}
}
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)
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 {
return getFirst<R>().let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>,
): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state
return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() }
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
.groupBy { it.first } // group by state
.let {
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>()
.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)
}
}

View File

@ -61,7 +61,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
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(>)"
@ -113,7 +113,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
addQueryParameter("filter_letter", params.letter)
addQueryParameter("filter_order", params.orderBy)
addQueryParameter("filter_sort", "abc")
}.build().encodedQuery
}.build().encodedQuery.orEmpty()
val genres = params.genres.joinToString { "\"$it\"" }
val delgenres = params.deleted_genres.joinToString { "\"$it\"" }
@ -155,7 +155,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
title = content.selectFirst("section > h1")!!.text()
.removePrefix("Assistir ")
.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")
val infos = content.selectFirst("div.info > ol")!!
@ -170,8 +170,8 @@ class AnimesGames : ParsedAnimeHttpSource() {
}
private fun Element.getInfo(info: String) =
selectFirst("li:has(span:contains($info))")?.let {
it.selectFirst("span[data]")?.text() ?: it.ownText()
selectFirst("li:has(span:contains($info))")?.run {
selectFirst("span[data]")?.text() ?: ownText()
}
// ============================== Episodes ==============================

View File

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

View File

@ -36,7 +36,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.selectFirst("h2.ttl")!!.text()
thumbnail_url = element.selectFirst("img")!!.attr("src")
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun popularAnimeNextPageSelector() = null
@ -49,7 +49,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
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"
@ -100,7 +100,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
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")!!
title = infos.selectFirst("h2.title")!!.text()
@ -110,7 +110,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
description = buildString {
infos.selectFirst("h2.ttl")?.text()
?.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 {
append("$it\n")
@ -159,7 +159,7 @@ class AnimesOrion : ParsedAnimeHttpSource() {
}
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
.substringAfter("sources")
.substringAfter("file: \"")
@ -185,7 +185,8 @@ class AnimesOrion : ParsedAnimeHttpSource() {
if (!document.location().contains("/episodio/")) return document
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
}

View File

@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.Response
@ -45,18 +44,75 @@ class AnimesROLL : AnimeHttpSource() {
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
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 ==============================
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) {
val od = response.asJsoup().parseAs<MovieInfoDto>().movieData.od
val od = doc.parseAs<MovieInfoDto>().movieData.od
SEpisode.create().apply {
url = "$OLD_API_URL/od/$od/filme.mp4"
name = "Filme"
episode_number = 0F
}.let(::listOf)
} else {
val anime = response.asJsoup().parseAs<AnimeDataDto>()
val anime = doc.parseAs<AnimeDataDto>()
val urlStart = "https://cdn-01.gamabunta.xyz/hls/animes/${anime.slug}"
return fetchEpisodesRecursively(anime.id).map { episode ->
@ -101,68 +157,7 @@ class AnimesROLL : AnimeHttpSource() {
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 ==============================
private inline fun <reified T> Document.parseAs(): T {
val nextData = this.selectFirst("script#__NEXT_DATA__")!!
.data()
@ -172,27 +167,24 @@ class AnimesROLL : AnimeHttpSource() {
}
private inline fun <reified T> Response.parseAs(): T {
val responseBody = body.string()
return json.decodeFromString(responseBody)
return use { it.body.string() }.let(json::decodeFromString)
}
private fun String.ifNotEmpty(block: (String) -> String): String {
return if (isNotEmpty() && this != "0") block(this) else ""
}
fun AnimeDataDto.toSAnime(): SAnime {
return SAnime.create().apply {
val ismovie = slug == ""
url = if (ismovie) "/f/$id" else "/anime/$slug"
thumbnail_url = "https://static.anroll.net/images/".let {
if (ismovie) {
it + "filmes/capas/$slug_movie.jpg"
} else {
it + "animes/capas/$slug.jpg"
}
fun AnimeDataDto.toSAnime() = SAnime.create().apply {
val ismovie = slug == ""
url = if (ismovie) "/f/$id" else "/anime/$slug"
thumbnail_url = "https://static.anroll.net/images/".let {
if (ismovie) {
it + "filmes/capas/$slug_movie.jpg"
} else {
it + "animes/capas/$slug.jpg"
}
title = anititle
}
title = anititle
}
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.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilter.TriState
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
object ATCFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
@ -17,17 +17,23 @@ object ATCFilters {
fun toQueryPart() = vals[state].second
}
open class TriStateFilterList(name: String, values: List<TriState>) : AnimeFilter.Group<AnimeFilter.TriState>(name, values)
private class TriStateVal(name: String) : AnimeFilter.TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
open class TriStateFilterList(name: String, values: List<TriState>) : AnimeFilter.Group<TriState>(name, values)
private class TriStateVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return getFirst<R>().let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } 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)
@ -56,36 +62,32 @@ object ATCFilters {
data class FilterSearchParams(
val initialLetter: String = "",
val status: String = "",
var orderAscending: Boolean = true,
var sortBy: String = "",
val blackListedGenres: ArrayList<String> = ArrayList(),
val includedGenres: ArrayList<String> = ArrayList(),
val orderAscending: Boolean = true,
val sortBy: String = "",
val blackListedGenres: List<String> = emptyList(),
val includedGenres: List<String> = emptyList(),
var animeName: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): 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<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 {

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.animesvision
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.animesvision.dto.AVResponseDto
@ -26,10 +25,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
@ -59,7 +57,7 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -69,8 +67,8 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular ===============================
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 popularAnimeSelector() = "div#anime-trending div.item > a.film-poster"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
val img = element.selectFirst("img")!!
@ -81,6 +79,93 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 ==============================
override fun episodeListSelector() = "div.container div.screen-items > div.item"
@ -112,7 +197,7 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val body = response.body.string()
val body = response.use { it.body.string() }
val internalVideos = GlobalVisionExtractor()
.videoListFromHtml(body)
.toMutableList()
@ -148,116 +233,30 @@ class AnimesVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val reqBody = body.toRequestBody()
val url = "$baseUrl/livewire/message/components.episodio.player-episodio-component"
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)
(resJson.serverMemo?.data?.framePlay ?: resJson.effects?.html)
?.let(::parsePlayerData)
?: emptyList<Video>()
?.let(::parsePlayerData).orEmpty()
}.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 {
when {
"streamtape" in data ->
StreamTapeExtractor(client).videoFromUrl(data)?.let(::listOf)
"dood" in data ->
DoodExtractor(client).videoFromUrl(data)?.let(::listOf)
"voe.sx" in data ->
VoeExtractor(client).videoFromUrl(data)?.let(::listOf)
else -> null
"streamtape" in data -> voeExtractor.videosFromUrl(data)
"dood" in data -> doodExtractor.videosFromUrl(data)
"voe.sx" in data -> voeExtractor.videosFromUrl(data)
else -> emptyList()
}
}.getOrNull() ?: emptyList<Video>()
}.getOrElse { emptyList() }
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 ===============================
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.animeextension.pt.animesvision.extractors
import eu.kanade.tachiyomi.animesource.model.Video
class GlobalVisionExtractor {
companion object {
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.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
@ -41,7 +40,7 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override val client = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
@ -62,9 +61,9 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href"))
thumbnail_url = element.selectFirst("div.cover-image")?.let {
it.attr("style").substringAfter("url('").substringBefore("'")
} ?: ""
thumbnail_url = element.selectFirst("div.cover-image")?.run {
attr("style").substringAfter("url('").substringBefore("'")
}
title = element.selectFirst("span.series-title")!!.text()
}
@ -176,8 +175,8 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeNextPageSelector(): String? = null
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("abs:href"))
thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src") ?: ""
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
thumbnail_url = element.selectFirst("img[src]")?.attr("abs:src")
title = element.selectFirst("div.aniInfos")?.text() ?: "Anime"
}
@ -249,8 +248,8 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return SEpisode.create().apply {
name = "Ep. ${epNumber?.text()?.trim() ?: counter} - ${epTitle.replace(EPISODE_REGEX, "")}"
.replace(" - - ", " - ")
episode_number = epNumber?.let {
it.text().trim().toFloatOrNull()
episode_number = epNumber?.run {
text().trim().toFloatOrNull()
} ?: counter.toFloat()
scanlator = info
setUrlWithoutDomain(element.selectFirst("article > a")!!.attr("href"))
@ -287,25 +286,9 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.selectFirst("script:containsData(sources:)")
?.data()
?.let(BloggerJWPlayerExtractor::videosFromScript)
?: emptyList()
}
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()
}
.orEmpty()
}
url.startsWith(baseUrl) -> videosFromInternalUrl(url)
"blogger.com" in url -> extractBloggerVideos(url, vid.text().trim())
else -> emptyList()
}
@ -314,6 +297,24 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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> {
return client.newCall(GET(url, headers)).execute()
.use { it.body.string() }
@ -360,14 +361,14 @@ class AnimesZone : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
// ============================= Utilities ==============================
private fun getDecrypted(script: String): String {
private fun getDecrypted(script: String): String? {
val patchedScript = script.trim().split("\n").first()
.replace("eval(function", "function a")
.replace("decodeURIComponent(escape(r))}(", "r};a(")
.substringBeforeLast(")")
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 inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
val value = this.filterIsInstance<R>().joinToString("") {
(it as QueryPartFilter).toQueryPart()
}
return if (value.isEmpty()) {
""
} else {
"&$name=$value"
}
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return this.filterIsInstance<R>().first()
return (first { it is R } as QueryPartFilter).toQueryPart()
.takeUnless(String::isEmpty)
?.let { "&$name=$it" }
.orEmpty()
}
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
name: String,
): String {
return (this.getFirst<R>() as CheckBoxFilterList).state
.mapNotNull { checkbox ->
if (checkbox.state) {
options.find { it.first == checkbox.name }!!.second
} else {
null
}
}.joinToString("%2C").let {
if (it.isBlank()) {
""
} else {
"&$name=$it"
}
}
return (first { it is R } as CheckBoxFilterList).state
.asSequence()
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank)
.joinToString("%2C") { "&$name=$it" }
}
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
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -34,9 +32,9 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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)
}
@ -114,7 +112,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val infos = content.selectFirst("div.anime_infos")!!
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")
author = infos.getInfo("Autor")
artist = infos.getInfo("Estúdio")
@ -125,7 +123,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
description = buildString {
append(doc.selectFirst("div.sinopse_container_content")!!.text() + "\n")
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 episodeListParse(response: Response) = buildList {
var doc = getRealDoc(response.asJsoup())
var doc = getRealDoc(response.use { it.asJsoup() })
do {
if (isNotEmpty()) {
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())
.map(::episodeFromElement)
.let(::addAll)
.also(::addAll)
} while (doc.selectFirst(popularAnimeNextPageSelector()) != null)
reverse()
}
@ -192,7 +190,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)")
?.let {
val path = it.attr("href")
client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
client.newCall(GET(baseUrl + path, headers)).execute().use { it.asJsoup() }
} ?: document
}

View File

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

View File

@ -106,7 +106,7 @@ class BetterAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeParse(response: Response): AnimesPage {
val body = response.body.string()
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 animes = document.select(searchAnimeSelector()).map { element ->
searchAnimeFromElement(element)

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.donghuanosekai
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.donghuanosekai.extractors.DonghuaNoSekaiExtractor
@ -21,7 +20,6 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
@ -53,7 +51,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -65,7 +63,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.attr("title")
thumbnail_url = element.selectFirst("img")!!.attr("src")
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun popularAnimeNextPageSelector() = null
@ -78,7 +76,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
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"
@ -132,7 +130,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
addQueryParameter("filter_order", params.orderBy)
addQueryParameter("filter_status", params.status)
addQueryParameter("type_url", "ONA")
}.build().encodedQuery
}.build().encodedQuery.orEmpty()
val genres = params.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 {
val doc = getRealDoc(document)
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")!!
title = infos.selectFirst("h1")!!.text()
@ -179,7 +177,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
doc.select("div.articleContent:has(div:contains(Sinopse)) > div.context > p")
.eachText()
.joinToString("\n\n")
.let(::append)
.also(::append)
append("\n")
@ -199,7 +197,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
element.selectFirst("span.episode")!!.text().let {
element.selectFirst("span.episode")!!.text().also {
name = it
episode_number = it.substringAfterLast(" ").toFloatOrNull() ?: 0F
}
@ -207,9 +205,9 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
// ============================ Video Links =============================
private val extractor by lazy { DonghuaNoSekaiExtractor(client, headers) }
override fun videoListParse(response: Response): List<Video> {
val doc = response.use { it.asJsoup() }
val extractor = DonghuaNoSekaiExtractor(client, headers)
return doc.select("div.slideItem[data-video-url]").parallelMap {
runCatching {
@ -263,7 +261,7 @@ class DonghuaNoSekai : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} ?: document
}
private fun String?.parseStatus() = when (this?.trim()?.lowercase()) {
private fun String?.parseStatus() = when (this?.run { trim().lowercase() }) {
"completo" -> SAnime.COMPLETED
"em lançamento" -> SAnime.ONGOING
"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)
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 {
return getFirst<R>().let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>,
): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state
return (first { it is R } as TriStateFilterList).state
.filterNot { it.isIgnored() }
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
.groupBy { it.first } // group by state
.let {
val included = it.get(TriState.STATE_INCLUDE)?.map { it.second } ?: emptyList<String>()
val excluded = it.get(TriState.STATE_EXCLUDE)?.map { it.second } ?: emptyList<String>()
.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)
}
}

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.flixei
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.AnimeDto
@ -25,7 +24,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
@ -52,7 +50,7 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -68,12 +66,10 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(animes, false)
}
private fun parseAnimeFromObject(anime: AnimeDto): SAnime {
return SAnime.create().apply {
title = anime.title
setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis")
thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp"
}
private fun parseAnimeFromObject(anime: AnimeDto) = SAnime.create().apply {
title = anime.title
setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis")
thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp"
}
override fun popularAnimeFromElement(element: Element): SAnime {
@ -88,6 +84,66 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 ==============================
private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
val id = seasonElement.attr("data-load-episodes")
@ -105,13 +161,13 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
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")
.substringAfter("'")
.substringBefore("'")
return if (response.request.url.toString().contains("/serie/")) {
client.newCall(GET(docUrl)).execute()
.asJsoup()
.use { it.asJsoup() }
.select("div#seasons div.item[data-load-episodes]")
.flatMap(::getSeasonEps)
.reversed()
@ -133,28 +189,6 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 =============================
override fun videoListRequest(episode: SEpisode): Request {
val url = episode.url
@ -167,7 +201,7 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
override fun videoListParse(response: Response): List<Video> {
val body = response.body.string()
val body = response.use { it.body.string() }
// Pair<Language, Query>
val items = if (body.startsWith("{")) {
val data = json.decodeFromString<ApiResultsDto<PlayersDto>>(body)
@ -203,6 +237,9 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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> {
val (lang, query) = item
val headers = headersBuilder().set("referer", WAREZ_URL).build()
@ -211,19 +248,16 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} else {
client.newCall(GET("$WAREZ_URL/embed/getPlay.php$query", headers))
.execute()
.body.string()
.use { it.body.string() }
.substringAfter("location.href=\"")
.substringBefore("\";")
}
return when (query.substringAfter("sv=")) {
"streamtape" ->
StreamTapeExtractor(client).videoFromUrl(hostUrl, "Streamtape($lang)")
?.let(::listOf)
"mixdrop" ->
MixDropExtractor(client).videoFromUrl(hostUrl, lang)
"streamtape" -> streamtapeExtractor.videosFromUrl(hostUrl, "Streamtape($lang)")
"mixdrop" -> mixdropExtractor.videoFromUrl(hostUrl, lang)
else -> null // TODO: Add warezcdn extractor
} ?: emptyList()
}.orEmpty()
}
override fun videoFromElement(element: Element): Video {
@ -238,86 +272,44 @@ class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val preferredPlayer = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_PLAYER_KEY
title = PREF_PLAYER_TITLE
entries = PREF_PLAYER_ARRAY
entryValues = PREF_PLAYER_ARRAY
setDefaultValue(PREF_PLAYER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
}.also(screen::addPreference)
val preferredLanguage = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = PREF_LANGUAGE_TITLE
entries = PREF_LANGUAGE_ENTRIES
entryValues = PREF_LANGUAGE_VALUES
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(preferredPlayer)
screen.addPreference(preferredLanguage)
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
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> =

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.hinatasoul
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor
@ -36,7 +35,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -95,7 +94,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src")
thumbnail_url = element.selectFirst("img")?.attr("src")
title = element.selectFirst("div.ultimosAnimesHomeItemInfosNome")!!.text()
}
@ -144,7 +143,7 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
doc.select(episodeListSelector())
.map(::episodeFromElement)
.let(::addAll)
.also(::addAll)
} while (hasNextPage(doc))
reverse()
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.listadeanimes
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
@ -16,35 +15,65 @@ import rx.Observable
class ListaDeAnimes : ParsedAnimeHttpSource() {
override val name = "Lista de Animes"
override val baseUrl = "https://www.listadeanimes.com"
override val lang = "pt-BR"
override val supportsLatest = false
override fun headersBuilder() = super.headersBuilder()
.add("Referer", baseUrl)
// ============================== 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): Request = GET("$baseUrl/page/$page")
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
val img = element.selectFirst("img")!!
title = titleCase(img.attr("title").substringBefore(" todos os episódios"))
thumbnail_url = img.attr("data-src")
initialized = false
}
override fun popularAnimeSelector() = "article.post.excerpt > div.capa:not(:has(a[href=$baseUrl/anime-lista-online]))"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
val img = element.selectFirst("img")!!
title = titleCase(img.attr("title").substringBefore(" todos os episódios"))
thumbnail_url = img.attr("data-src")
}
override fun popularAnimeNextPageSelector() = "a.next.page-numbers"
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(popularAnimeSelector()).map(::popularAnimeFromElement)
return AnimesPage(animes, document.selectFirst(searchAnimeNextPageSelector()) != null)
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.")
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 ==============================
override fun episodeListSelector(): String = "div.videos > ul"
override fun episodeListSelector() = "div.videos > ul"
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
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 videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
// =============================== Search ===============================
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.")
// ============================= Utilities ==============================
private fun titleCase(str: String): String {
val builder = StringBuilder(str)
var whitespace = true
str.forEachIndexed { index, c ->
if (c.isWhitespace()) {
whitespace = true
} else if (whitespace) {
builder.setCharAt(index, c.uppercaseChar())
whitespace = false
}
}
return builder.toString()
return str.split(' ')
.map { it.replaceFirstChar(Char::uppercase) }
.joinToString(" ")
}
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
@ -42,7 +41,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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)
}
@ -54,7 +53,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
title = element.selectFirst("h2.entry-title")!!.text()
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
@ -110,7 +109,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
setUrlWithoutDomain(document.location())
val infos = document.selectFirst("div.bd > article.post.single")!!
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()
description = infos.selectFirst("div.description")?.text()
}
@ -146,33 +145,31 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
name = element.selectFirst("h2.entry-title")!!.text()
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
episode_number = element.selectFirst("span.num-epi")
?.text()
?.split("x")
?.let {
episode_number = element.selectFirst("span.num-epi")?.run {
text().split("x").let {
val season = it.first().toFloatOrNull() ?: 0F
val episode = it.last().toFloatOrNull() ?: 0F
season * 100F + episode
}
?: 0F
} ?: 0F
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val items = response.asJsoup().select(videoListSelector())
val items = response.use { it.asJsoup() }.select(videoListSelector())
return items
.parallelMap { element ->
val language = element.text().substringAfter("-")
val id = element.attr("href")
val url = element.parents().get(5)
?.selectFirst("div$id a")
?.attr("href")
?.substringAfter("token=")
?.let { Base64.decode(it, Base64.DEFAULT).let(::String) }
?.substringAfter("||")
?: return@parallelMap emptyList()
val url = element.parents().get(5)?.selectFirst("div$id a")
?.run {
attr("href")
.substringAfter("token=")
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substringAfter("||")
} ?: return@parallelMap emptyList()
runCatching { getVideoList(url, language) }.getOrNull() ?: emptyList()
runCatching { getVideoList(url, language) }.getOrNull().orEmpty()
}.flatten()
}
@ -183,7 +180,7 @@ class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private fun getVideoList(url: String, language: String): List<Video>? {
return when {
"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)
else -> null
}

View File

@ -16,9 +16,7 @@ object MegaflixFilters {
}
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return this.first { it is R }.let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
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> {
val unpacked = client.newCall(GET(url, headers)).execute()
.body.string()
.use { it.body.string() }
.let(JsUnpacker::unpackAndCombine)
?.replace("\\", "")
?: return emptyList()

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.meusanimes.extractors.IframeExtractor
@ -34,76 +33,39 @@ class MeusAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector(): String? = null
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector(): String = "div.ultAnisContainerItem > a"
// ============================== Episodes ==============================
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
element.attr("href")!!.also {
setUrlWithoutDomain(it)
episode_number = it.substringAfterLast("/").toFloatOrNull() ?: 0F
}
name = element.text()
override fun popularAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
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 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")
override fun latestUpdatesNextPageSelector() = null
// =============================== 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 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> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
@ -120,38 +82,85 @@ class MeusAnimes : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(listOf(details), false)
}
// =============================== Latest ===============================
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 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 latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
override fun latestUpdatesSelector(): String = "div.ultEpsContainerItem > a"
override fun searchAnimeSelector() = popularAnimeSelector()
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_ENTRIES
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(videoQualityPref)
}.also(screen::addPreference)
}
// ============================= Utilities ==============================

View File

@ -1,13 +1,16 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext {
extName = 'Muito Hentai'
pkgNameSuffix = 'pt.muitohentai'
extClass = '.MuitoHentai'
extVersionCode = 1
libVersion = '12'
extVersionCode = 2
libVersion = '13'
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
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@ -16,123 +14,106 @@ import java.text.SimpleDateFormat
class MuitoHentai : ParsedAnimeHttpSource() {
override val name = "Muito Hentai"
override val baseUrl = "https://www.muitohentai.com"
override val lang = "pt-BR"
override val supportsLatest = true
// ============================== Popular ===============================
override fun popularAnimeSelector(): String = "ul.ul_sidebar > li"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ranking-hentais/?paginacao=$page")
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/ranking-hentais/?paginacao=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
thumbnail_url = element.selectFirst("div.zeroleft > a > img")!!.attr("src")
val a = element.selectFirst("div.lefthentais > div > b:gt(0) > a.series")!!
url = a.attr("href")
title = a.text()
override fun popularAnimeSelector() = "ul.ul_sidebar > li"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
thumbnail_url = element.selectFirst("div.zeroleft > a > img")?.attr("src")
element.selectFirst("div.lefthentais > div > b:gt(0) > a.series")!!.run {
setUrlWithoutDomain(attr("href"))
title = text()
}
}
override fun popularAnimeNextPageSelector() = "div.paginacao > a:contains(»)"
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(popularAnimeSelector()).map(::popularAnimeFromElement)
return AnimesPage(animes, document.selectFirst(popularAnimeNextPageSelector()) != null)
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/")
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 ==============================
override fun episodeListSelector(): String = "article.item"
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
return doc
.select(episodeListSelector())
.map(::episodeFromElement)
.reversed()
}
override fun episodeFromElement(element: Element): SEpisode {
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())
}
override fun episodeListParse(response: Response) = super.episodeListParse(response).reversed()
override fun episodeListSelector() = "article.item"
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
val data = element.selectFirst("div.data")!!
setUrlWithoutDomain(element.selectFirst("div.poster > div.season_m > a")!!.attr("href"))
name = data.selectFirst("h3")!!.text().trim()
date_upload = data.selectFirst("span")?.text()?.parseDate() ?: 0L
}
// ============================ Video Links =============================
override fun videoListSelector() = "div.playex > div#option-0 > iframe"
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 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
.select("source")
.map(::videoFromElement)
.reversed()
}
override fun videoFromElement(element: Element): Video {
val url = element.attr("src")
return Video(url, element.attr("label"), url)
}
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
// =============================== Search ===============================
override fun searchAnimeSelector() = "div#archive-content > article > div.poster"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/buscar/$query")
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 }
// ============================= Utilities ==============================
private fun String.parseDate(): Long {
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
companion object {

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
@ -48,11 +46,11 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override val client = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
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"
}
// ============================== Episodes ==============================
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
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = apiRequest("getHomeSliderSeries=1")
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
val seasons = doc.select("div#seasonsList div.item[data-season-id]")
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.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
override fun latestUpdatesParse(response: Response): AnimesPage {
val parsedData = response.parseAs<SearchResultDto>()
val animes = parsedData.list.map(::animeFromObject)
return AnimesPage(animes, false)
}
// =============================== Search ===============================
@ -227,77 +155,145 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
description = buildString {
append(doc.selectFirst("span.desc")!!.text() + "\n")
doc.selectFirst("div.year")?.let { append("\nAno: ${it.text()}") }
doc.selectFirst("div.tm")?.let { append("\nDuração: ${it.text()}") }
doc.selectFirst("a.rating")?.let { append("\nNota: ${it.text()}") }
doc.selectFirst("div.year")?.also { append("\nAno: ${it.text()}") }
doc.selectFirst("div.tm")?.also { append("\nDuração: ${it.text()}") }
doc.selectFirst("a.rating")?.also { append("\nNota: ${it.text()}") }
}
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = apiRequest("getHomeSliderSeries=1")
// ============================== Episodes ==============================
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 {
val parsedData = response.parseAs<SearchResultDto>()
val animes = parsedData.list.map(::animeFromObject).toList()
return AnimesPage(animes, false)
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.use { it.asJsoup() }
val seasons = doc.select("div#seasonsList div.item[data-season-id]")
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 ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val popularPage = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_POPULAR_PAGE_KEY
title = PREF_POPULAR_PAGE_TITLE
entries = PREF_POPULAR_PAGE_ENTRIES
entryValues = PREF_POPULAR_PAGE_VALUES
setDefaultValue(PREF_POPULAR_PAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
}.also(screen::addPreference)
val preferredPlayer = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_PLAYER_KEY
title = PREF_PLAYER_TITLE
entries = PREF_PLAYER_ARRAY
entryValues = PREF_PLAYER_ARRAY
setDefaultValue(PREF_PLAYER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
}.also(screen::addPreference)
val preferredLanguage = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = PREF_LANGUAGE_TITLE
entries = PREF_LANGUAGE_ENTRIES
entryValues = PREF_LANGUAGE_VALUES
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(popularPage)
screen.addPreference(preferredPlayer)
screen.addPreference(preferredLanguage)
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
private fun getPlayerUrl(id: String, name: String): String {
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("\";")
}
@ -319,8 +315,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
}
private inline fun <reified T> Response.parseAs(): T {
val responseBody = body.string()
return json.decodeFromString(responseBody)
return use { it.body.string() }.let(json::decodeFromString)
}
companion object {

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object VizerFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
@ -16,14 +15,8 @@ object VizerFilters {
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 {
return getFirst<R>().let {
(it as QueryPartFilter).toQueryPart()
}
return (first { it is R } as QueryPartFilter).toQueryPart()
}
class TypeFilter : QueryPartFilter("Tipo", VizerFiltersData.TYPES)
@ -50,27 +43,32 @@ object VizerFilters {
val minYear: String = "1890",
val maxYear: String = "2022",
val genre: String = "all",
var orderBy: String = "rating",
var orderWay: String = "desc",
val orderBy: String = "rating",
val orderWay: String = "desc",
)
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<MinYearFilter>(),
filters.asQueryPart<MaxYearFilter>(),
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 {
val TYPES = arrayOf(
Pair("Animes", "anime"),
Pair("Filmes", "Movies"),