refactor(multisrc/animestream): Make multisrc a bit more generic and customizable (#1775)
This commit is contained in:
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.StreamWishExt
|
|||||||
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.VidMolyExtractor
|
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.VidMolyExtractor
|
||||||
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.VtubeExtractor
|
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.VtubeExtractor
|
||||||
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.WolfstreamExtractor
|
import eu.kanade.tachiyomi.animeextension.en.animenosub.extractors.WolfstreamExtractor
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||||
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
||||||
@ -18,23 +17,11 @@ class Animenosub : AnimeStream(
|
|||||||
"Animenosub",
|
"Animenosub",
|
||||||
"https://animenosub.com",
|
"https://animenosub.com",
|
||||||
) {
|
) {
|
||||||
// ============================== Popular ===============================
|
|
||||||
|
|
||||||
override fun popularAnimeSelector() = "div.serieslist.wpop-weekly li"
|
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
|
override fun getEpisodeName(element: Element, epNum: String): String {
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
|
||||||
val episodeTitle = element.selectFirst("div.epl-title")?.text() ?: ""
|
val episodeTitle = element.selectFirst("div.epl-title")?.text() ?: ""
|
||||||
return SEpisode.create().apply {
|
val complement = if (episodeTitle.contains("Episode $epNum", true)) "" else episodeTitle
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
return "Ep. $epNum $complement"
|
||||||
element.selectFirst("div.epl-num")!!.text().let {
|
|
||||||
name = "Ep. $it ${if (episodeTitle.contains("Episode $it", true)) "" else episodeTitle}"
|
|
||||||
episode_number = it.substringBefore(" ").toFloatOrNull() ?: 0F
|
|
||||||
}
|
|
||||||
element.selectFirst("div.epl-sub")?.text()?.let { scanlator = it }
|
|
||||||
date_upload = element.selectFirst("div.epl-date")?.text().toDate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================ Video Links =============================
|
// ============================ Video Links =============================
|
||||||
|
@ -85,21 +85,13 @@ abstract class AnimeStream(
|
|||||||
return super.fetchPopularAnime(page)
|
return super.fetchPopularAnime(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
override fun popularAnimeRequest(page: Int) = GET("$animeListUrl/?page=$page&order=popular")
|
||||||
return SAnime.create().apply {
|
|
||||||
val ahref = element.selectFirst("h4 > a.series")!!
|
|
||||||
setUrlWithoutDomain(ahref.attr("href"))
|
|
||||||
title = ahref.text()
|
|
||||||
thumbnail_url = element.selectFirst("img")!!.getImageUrl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularAnimeNextPageSelector() = null
|
override fun popularAnimeSelector() = searchAnimeSelector()
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
|
override fun popularAnimeNextPageSelector() = searchAnimeNextPageSelector()
|
||||||
|
|
||||||
/* Possible classes: wpop-weekly, wpop-monthly, wpop-alltime */
|
override fun popularAnimeFromElement(element: Element) = searchAnimeFromElement(element)
|
||||||
override fun popularAnimeSelector() = "div.serieslist.wpop-alltime li"
|
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
@ -112,40 +104,67 @@ abstract class AnimeStream(
|
|||||||
else -> "Episode"
|
else -> "Episode"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused_parameter")
|
||||||
|
protected open fun getEpisodeName(element: Element, epNum: String) = "$episodePrefix $epNum"
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
override fun episodeFromElement(element: Element): SEpisode {
|
||||||
return SEpisode.create().apply {
|
return SEpisode.create().apply {
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
element.selectFirst("div.epl-num")!!.text().let {
|
element.selectFirst(".epl-num")!!.text().let {
|
||||||
name = "$episodePrefix $it"
|
name = getEpisodeName(element, it)
|
||||||
episode_number = it.substringBefore(" ").toFloatOrNull() ?: 0F
|
episode_number = it.substringBefore(" ").toFloatOrNull() ?: 0F
|
||||||
}
|
}
|
||||||
element.selectFirst("div.epl-sub")?.text()?.let { scanlator = it }
|
element.selectFirst(".epl-sub")?.text()?.let { scanlator = it }
|
||||||
date_upload = element.selectFirst("div.epl-date")?.text().toDate()
|
date_upload = element.selectFirst(".epl-date")?.text().toDate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun episodeListSelector() = "div.eplister > ul > li > a"
|
override fun episodeListSelector() = "div.eplister > ul > li > a"
|
||||||
|
|
||||||
// =========================== Anime Details ============================
|
// =========================== Anime Details ============================
|
||||||
|
protected open val animeDetailsSelector = "div.info-content, div.right ul.data"
|
||||||
|
protected open val animeAltNameSelector = ".alter"
|
||||||
|
protected open val animeTitleSelector = "h1.entry-title"
|
||||||
|
protected open val animeThumbnailSelector = "div.thumb > img, div.limage > img"
|
||||||
|
protected open val animeGenresSelector = "div.genxed > a, li:contains(Genre:) a"
|
||||||
|
protected open val animeDescriptionSelector = ".entry-content[itemprop=description], .desc"
|
||||||
|
protected open val animeAdditionalInfoSelector = "div.spe > span, li:has(b)"
|
||||||
|
|
||||||
|
protected open val animeStatusText = "Status"
|
||||||
|
protected open val animeArtistText = "tudio"
|
||||||
|
protected open val animeAuthorText = "Fansub"
|
||||||
|
|
||||||
|
protected open val animeAltNamePrefix = when (lang) {
|
||||||
|
"pt-BR" -> "Nome(s) alternativo(s): "
|
||||||
|
else -> "Alternative name(s): "
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getAnimeDescription(document: Document) =
|
||||||
|
document.selectFirst(animeDescriptionSelector)?.text()
|
||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
override fun animeDetailsParse(document: Document): SAnime {
|
||||||
return SAnime.create().apply {
|
return SAnime.create().apply {
|
||||||
setUrlWithoutDomain(document.location())
|
setUrlWithoutDomain(document.location())
|
||||||
title = document.selectFirst("h1.entry-title")!!.text()
|
title = document.selectFirst(animeTitleSelector)!!.text()
|
||||||
thumbnail_url = document.selectFirst("div.thumb > img")!!.getImageUrl()
|
thumbnail_url = document.selectFirst(animeThumbnailSelector)!!.getImageUrl()
|
||||||
|
|
||||||
val infos = document.selectFirst("div.info-content")!!
|
val infos = document.selectFirst(animeDetailsSelector)!!
|
||||||
genre = infos.select("div.genxed > a").eachText().joinToString()
|
genre = infos.select(animeGenresSelector).eachText().joinToString()
|
||||||
|
|
||||||
status = parseStatus(infos.getInfo("Status"))
|
status = parseStatus(infos.getInfo(animeStatusText))
|
||||||
artist = infos.getInfo("tudio")
|
artist = infos.getInfo(animeArtistText)
|
||||||
author = infos.getInfo("Fansub")
|
author = infos.getInfo(animeAuthorText)
|
||||||
|
|
||||||
description = buildString {
|
description = buildString {
|
||||||
document.selectFirst("div.entry-content")?.text()?.let {
|
getAnimeDescription(document)?.let {
|
||||||
append("$it\n\n")
|
append("$it\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
infos.select("div.spe > span").eachText().forEach {
|
document.selectFirst(animeAltNameSelector)?.text()
|
||||||
|
?.takeIf(String::isNotBlank)
|
||||||
|
?.let { append("$animeAltNamePrefix$it\n") }
|
||||||
|
|
||||||
|
infos.select(animeAdditionalInfoSelector).eachText().forEach {
|
||||||
append("$it\n")
|
append("$it\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +172,7 @@ abstract class AnimeStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================ Video Links =============================
|
// ============================ Video Links =============================
|
||||||
override fun videoListSelector() = "select.mirror > option[data-index]"
|
override fun videoListSelector() = "select.mirror > option[data-index], ul.mirror a[data-em]"
|
||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
val items = response.asJsoup().select(videoListSelector())
|
val items = response.asJsoup().select(videoListSelector())
|
||||||
@ -167,7 +186,17 @@ abstract class AnimeStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getHosterUrl(element: Element): String {
|
protected open fun getHosterUrl(element: Element): String {
|
||||||
return Base64.decode(element.attr("value"), Base64.DEFAULT)
|
val encodedData = when (element.tagName()) {
|
||||||
|
"option" -> element.attr("value")
|
||||||
|
"a" -> element.attr("data-em")
|
||||||
|
else -> throw Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getHosterUrl(encodedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getHosterUrl(encodedData: String): String {
|
||||||
|
return Base64.decode(encodedData, Base64.DEFAULT)
|
||||||
.let(::String) // bytearray -> string
|
.let(::String) // bytearray -> string
|
||||||
.let(Jsoup::parse) // string -> document
|
.let(Jsoup::parse) // string -> document
|
||||||
.selectFirst("iframe[src~=.]")!!
|
.selectFirst("iframe[src~=.]")!!
|
||||||
@ -193,7 +222,7 @@ abstract class AnimeStream(
|
|||||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||||
return SAnime.create().apply {
|
return SAnime.create().apply {
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
title = element.selectFirst("div.tt")!!.ownText()
|
title = element.selectFirst("div.tt, div.ttl")!!.ownText()
|
||||||
thumbnail_url = element.selectFirst("img")!!.getImageUrl()
|
thumbnail_url = element.selectFirst("img")!!.getImageUrl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,7 +240,7 @@ abstract class AnimeStream(
|
|||||||
if (params.studios.isNotEmpty()) append(params.studios + "&")
|
if (params.studios.isNotEmpty()) append(params.studios + "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
GET("$baseUrl/anime/?page=$page&$multiString&status=${params.status}&type=${params.type}&sub=${params.sub}&order=${params.order}")
|
GET("$animeListUrl/?page=$page&$multiString&status=${params.status}&type=${params.type}&sub=${params.sub}&order=${params.order}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,13 +303,15 @@ abstract class AnimeStream(
|
|||||||
*/
|
*/
|
||||||
protected open val fetchFilters = true
|
protected open val fetchFilters = true
|
||||||
|
|
||||||
|
protected open val filtersSelector = "span.sec1 > div.filter > ul"
|
||||||
|
|
||||||
private fun fetchFilterList() {
|
private fun fetchFilterList() {
|
||||||
if (fetchFilters && !AnimeStreamFilters.filterInitialized()) {
|
if (fetchFilters && !AnimeStreamFilters.filterInitialized()) {
|
||||||
AnimeStreamFilters.filterElements = runBlocking {
|
AnimeStreamFilters.filterElements = runBlocking {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
client.newCall(GET(animeListUrl)).execute()
|
client.newCall(GET(animeListUrl)).execute()
|
||||||
.asJsoup()
|
.asJsoup()
|
||||||
.select("span.sec1 > div.filter > ul")
|
.select(filtersSelector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,19 +33,10 @@ object AnimeStreamFilters {
|
|||||||
name: String,
|
name: String,
|
||||||
): String {
|
): String {
|
||||||
return (getFirst<R>() as CheckBoxFilterList).state
|
return (getFirst<R>() as CheckBoxFilterList).state
|
||||||
.mapNotNull { checkbox ->
|
.filter { it.state }
|
||||||
when {
|
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
|
||||||
checkbox.state -> {
|
.filter(String::isNotBlank)
|
||||||
options.find { it.first == checkbox.name }!!.second
|
.joinToString("&") { "$name[]=$it" }
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.joinToString("&$name[]=").let {
|
|
||||||
when {
|
|
||||||
it.isBlank() -> ""
|
|
||||||
else -> "$name[]=$it"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
||||||
|
@ -8,7 +8,7 @@ class AnimeStreamGenerator : ThemeSourceGenerator {
|
|||||||
|
|
||||||
override val themeClass = "AnimeStream"
|
override val themeClass = "AnimeStream"
|
||||||
|
|
||||||
override val baseVersionCode = 1
|
override val baseVersionCode = 2
|
||||||
|
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
SingleLang("AnimeXin", "https://animexin.vip", "all", isNsfw = false, overrideVersionCode = 4),
|
SingleLang("AnimeXin", "https://animexin.vip", "all", isNsfw = false, overrideVersionCode = 4),
|
||||||
|
Reference in New Issue
Block a user