feat(src/es): VerPelisTop added & HLA fixed (Kohi-den/extensions-source#1073)

Closes Kohi-den/extensions-source#1066
Closes Kohi-den/extensions-source#1056

Co-authored-by: imper1aldev <23511335+imper1aldev@users.noreply.github.com>

(cherry picked from commit 178e85bcd50940355d0d7032ad44779f8f8dcc50)
This commit is contained in:
Cuong-Tran
2026-01-22 22:48:46 +07:00
parent c49861c1ba
commit 9bced48de9
2 changed files with 182 additions and 19 deletions

View File

@@ -196,16 +196,15 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val animeId = Regex("""media/([^/?#]+)""")
.find(response.request.url.toString())?.groupValues?.get(1)?.lowercase() ?: ""
val jsoup = response.asJsoup()
jsoup.select("article.group\\/item").forEach {
val href = it.select("a").attr("abs:href")
val epNum = it.select("div.bg-line.text-subs span").text()
val episode = SEpisode.create().apply {
episode_number = epNum.toFloatOrNull() ?: return@forEach
epNum.toFloatOrNull()?.let { num -> episode_number = num }
name = "Episodio $epNum"
url = "/media/$animeId/$epNum"
setUrlWithoutDomain(href)
}
episodes.add(episode)
}
@@ -268,30 +267,44 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
}
}
val allVideos = serverList.parallelCatchingFlatMapBlocking { each ->
when (each.name.lowercase()) {
"streamwish" -> streamWishExtractor.videosFromUrl(each.url, videoNameGen = { "StreamWish:$it" })
"mp4upload" -> mp4uploadExtractor.videosFromUrl(each.url, headers = headers, prefix = "Mp4Upload")
"voe" -> voeExtractor.videosFromUrl(each.url)
"arc" -> listOf(Video(each.url.substringAfter("#"), "Arc", each.url.substringAfter("#")))
"yupi", "yourupload" -> yourUploadExtractor.videoFromUrl(each.url, headers = headers)
"burst" -> burstCloudExtractor.videoFromUrl(each.url, headers = headers)
"sendvid" -> sendvidExtractor.videosFromUrl(each.url)
"mediafire" -> mediaFireExtractor.getVideoFromUrl(each.url)
"fireload" -> fireLoadExtractor.getVideoFromUrl(each.url)
"vidhide" -> vidhideExtractor.videosFromUrl(each.url)
"mega" -> megacloudExtractor.getVideosFromUrl(each.url, "Megacloud", "Megacloud")
val allVideos = serverList.parallelCatchingFlatMapBlocking { server ->
val serverName = serverDomainCatalog.firstOrNull { (_, names) -> names.any { it.lowercase() in server.url.lowercase() } }?.first
?: server.name.lowercase()
when (serverName) {
"streamwish" -> streamWishExtractor.videosFromUrl(server.url, videoNameGen = { "StreamWish:$it" })
"mp4upload" -> mp4uploadExtractor.videosFromUrl(server.url, headers = headers, prefix = "Mp4Upload")
"voe" -> voeExtractor.videosFromUrl(server.url)
"arc" -> listOf(Video(server.url.substringAfter("#"), "Arc", server.url.substringAfter("#")))
"yupi", "yourupload" -> yourUploadExtractor.videoFromUrl(server.url, headers = headers)
"burst", "burstcloud" -> burstCloudExtractor.videoFromUrl(server.url, headers = headers)
"sendvid" -> sendvidExtractor.videosFromUrl(server.url)
"mediafire" -> mediaFireExtractor.getVideoFromUrl(server.url)
"fireload" -> fireLoadExtractor.getVideoFromUrl(server.url)
"vidhide" -> vidhideExtractor.videosFromUrl(server.url) // streamHideVidExtractor
"mega" -> megacloudExtractor.getVideosFromUrl(server.url, "Megacloud", "Megacloud")
"vip" -> universalExtractor.videosFromUrl(
each.url.replace("/play/", "/m3u8/"),
server.url.replace("/play/", "/m3u8/"),
origRequestHeader = headers,
prefix = "VIP",
)
else -> emptyList()
else -> universalExtractor.videosFromUrl(server.url, headers)
}
}
return allVideos
}
private val serverDomainCatalog = listOf(
"streamwish" to listOf("wishembed", "streamwish", "strwish", "wish", "Kswplayer", "Swhoi", "Multimovies", "Uqloads", "neko-stream", "swdyu", "iplayerhls", "streamgg"),
"voe" to listOf("voe", "tubelessceliolymph", "simpulumlamerop", "urochsunloath", "nathanfromsubject", "yip.", "metagnathtuggers", "donaldlineelse"),
"arc" to listOf("arc"),
"mp4upload" to listOf("mp4upload"),
"yourupload" to listOf("yourupload", "yupi"),
"burstcloud" to listOf("burstcloud", "burst"),
"vidhide" to listOf("ahvsh", "streamhide", "guccihide", "streamvid", "vidhide", "kinoger", "smoothpre", "dhtpre", "peytonepre", "earnvids", "ryderjet"),
"sendvid" to listOf("sendvid"),
"mediafire" to listOf("mediafire"),
)
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT).orEmpty()
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT).orEmpty()

View File

@@ -0,0 +1,150 @@
package eu.kanade.tachiyomi.animeextension.es.hentaila
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Calendar
object HentailaFilters {
open class QueryPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toQueryPart(name: String) = vals[state].second.takeIf { it.isNotEmpty() }?.let { "&$name=${vals[state].second}" } ?: run { "" }
}
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
open class CheckBoxFilter(name: String) : AnimeFilter.CheckBox(name) {
fun toQueryPart(name: String): String {
return this.state.takeIf { it }?.let { "&$name=true" } ?: run { "" }
}
}
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
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("&$name=").let {
if (it.isBlank()) {
""
} else {
"&$name=$it"
}
}
}
private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name)
}
private inline fun <reified R> AnimeFilterList.checkAsQueryPart(name: String): String {
return (this.getFirst<R>() as CheckBoxFilter).toQueryPart(name)
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return this.filterIsInstance<R>().first()
}
private fun String.changePrefix() = this.takeIf { it.startsWith("&") }?.let { this.replaceFirst("&", "?") } ?: run { this }
data class FilterSearchParams(val filter: String = "") { fun getQuery() = filter.changePrefix() }
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.parseCheckbox<GenresFilter>(HentailaFiltersData.GENRES, "genre") +
filters.asQueryPart<YearMinFilter>("minYear") +
filters.asQueryPart<YearMaxFilter>("maxYear") +
filters.asQueryPart<StatusFilter>("status") +
filters.asQueryPart<SortFilter>("order") +
filters.checkAsQueryPart<CheckBoxFilter>("uncensored"),
)
}
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenresFilter(),
YearMinFilter(),
YearMaxFilter(),
StatusFilter(),
AnimeFilter.Separator(),
CensoredFilter(),
AnimeFilter.Separator(),
SortFilter(),
)
class GenresFilter : CheckBoxFilterList("Género", HentailaFiltersData.GENRES.map { CheckBoxVal(it.first, false) })
class YearMinFilter : QueryPartFilter("Min. Año", arrayOf(Pair("Todos", "")) + HentailaFiltersData.YEARS)
class YearMaxFilter : QueryPartFilter("Max. Año", arrayOf(Pair("Todos", "")) + HentailaFiltersData.YEARS.reversed())
class StatusFilter : QueryPartFilter("Estado", HentailaFiltersData.STATUS)
class SortFilter : QueryPartFilter("Ordenar por", HentailaFiltersData.SORT)
class CensoredFilter : CheckBoxFilter("Sin censura")
private object HentailaFiltersData {
val GENRES = arrayOf(
Pair("3D", "3d"),
Pair("Ahegao", "ahegao"),
Pair("Anal", "anal"),
Pair("Casadas", "casadas"),
Pair("Chikan", "chikan"),
Pair("Ecchi", "ecchi"),
Pair("Enfermeras", "enfermeras"),
Pair("Escolares", "escolares"),
Pair("Futanari", "futanari"),
Pair("Gore", "gore"),
Pair("Hardcore", "hardcore"),
Pair("Harem", "harem"),
Pair("Incesto", "incesto"),
Pair("Juegos Sexuales", "juegos-sexuales"),
Pair("Suspenso", "suspenso"),
Pair("Milfs", "milfs"),
Pair("Maids", "maids"),
Pair("Netorare", "netorare"),
Pair("Ninfomania", "ninfomania"),
Pair("Ninjas", "ninjas"),
Pair("Orgias", "orgias"),
Pair("Romance", "romance"),
Pair("Shota", "shota"),
Pair("Softcore", "softcore"),
Pair("Succubus", "succubus"),
Pair("Teacher", "teacher"),
Pair("Tentaculos", "tentaculos"),
Pair("Tetonas", "tetonas"),
Pair("Vanilla", "vanilla"),
Pair("Violacion", "violacion"),
Pair("Virgenes", "virgenes"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Bondage", "bondage"),
Pair("Elfas", "elfas"),
Pair("Petit", "petit"),
Pair("Threesome", "threesome"),
Pair("Paizuri", "paizuri"),
Pair("Gal", "gal"),
Pair("Oyakodon", "oyakodon"),
)
val YEARS = (1991..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.toTypedArray()
val STATUS = arrayOf(
Pair("Todos", ""),
Pair("Finalizado", "finalizado"),
Pair("Próximamente", "proximamente"),
Pair("En emisión", "emision"),
)
val SORT = arrayOf(
Pair("Predeterminado", ""),
Pair("Puntuación", "score"),
Pair("Populares", "popular"),
Pair("Título", "title"),
Pair("Últimos agregados", "latest_added"),
Pair("Últimos estrenos", "latest_released"),
)
}
}