TioAnime improvements and VidGuardExtractor added

This commit is contained in:
imper1aldev 2024-09-14 00:06:34 -06:00
parent 336fc9d891
commit addec95219
17 changed files with 377 additions and 259 deletions

View file

@ -0,0 +1,8 @@
plugins {
id("lib-android")
}
dependencies {
implementation(project(":lib:playlist-utils"))
implementation("org.mozilla:rhino:1.7.14")
}

View file

@ -0,0 +1,104 @@
package eu.kanade.tachiyomi.lib.vidguardextractor
import android.util.Base64
import android.util.Log
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeJSON
import org.mozilla.javascript.NativeObject
import org.mozilla.javascript.Scriptable
import uy.kohesive.injekt.injectLazy
class VidGuardExtractor(private val client: OkHttpClient) {
private val playlistUtils by lazy { PlaylistUtils(client) }
private val json: Json by injectLazy()
fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix $it" }
fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "VidGuard:$quality" }): List<Video> {
val res = client.newCall(GET(url)).execute().asJsoup()
val scriptData = res.selectFirst("script:containsData(eval)")?.data() ?: return emptyList()
val jsonStr2 = json.decodeFromString<SvgObject>(runJS2(scriptData))
val playlistUrl = sigDecode(jsonStr2.stream)
return playlistUtils.extractFromHls(playlistUrl, videoNameGen = videoNameGen)
}
private fun sigDecode(url: String): String {
val sig = url.split("sig=")[1].split("&")[0]
val t = sig.chunked(2)
.joinToString("") { (Integer.parseInt(it, 16) xor 2).toChar().toString() }
.let {
val padding = when (it.length % 4) {
2 -> "=="
3 -> "="
else -> ""
}
String(Base64.decode((it + padding).toByteArray(Charsets.UTF_8), Base64.DEFAULT))
}
.dropLast(5)
.reversed()
.toCharArray()
.apply {
for (i in indices step 2) {
if (i + 1 < size) {
this[i] = this[i + 1].also { this[i + 1] = this[i] }
}
}
}
.concatToString()
.dropLast(5)
return url.replace(sig, t)
}
private fun runJS2(hideMyHtmlContent: String): String {
var result = ""
val r = Runnable {
val rhino = Context.enter()
rhino.initSafeStandardObjects()
rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initSafeStandardObjects()
scope.put("window", scope, scope)
try {
rhino.evaluateString(
scope,
hideMyHtmlContent,
"JavaScript",
1,
null,
)
val svgObject = scope.get("svg", scope)
result = if (svgObject is NativeObject) {
NativeJSON.stringify(Context.getCurrentContext(), scope, svgObject, null, null)
.toString()
} else {
Context.toString(svgObject)
}
} catch (e: Exception) {
Log.i("Error", e.toString())
} finally {
Context.exit()
}
}
val t = Thread(ThreadGroup("A"), r, "thread_rhino", 2000000) // StackSize 2Mb: Run in a thread because rhino requires more stack size for large scripts.
t.start()
t.join()
t.interrupt()
return result
}
}
@Serializable
data class SvgObject(
val stream: String,
val hash: String,
)

View file

@ -1,7 +1,7 @@
ext {
extName = 'TioanimeH'
extClass = '.TioanimeHFactory'
extVersionCode = 18
extVersionCode = 19
}
apply from: "$rootDir/common.gradle"
@ -10,4 +10,6 @@ dependencies {
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:vidguard-extractor'))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,208 @@
package eu.kanade.tachiyomi.animeextension.es.tioanimeh
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilter.CheckBox
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Calendar
object TioAnimeHFilters {
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)
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.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() }
private fun Array<Pair<String, String>>.toCheckBoxVal(): List<CheckBox> = map { CheckBoxVal(it.first, false) }
private fun String.addSuffix(): String = takeIf { it.isNotBlank() }?.let { "$it,${Calendar.getInstance().get(Calendar.YEAR)}" } ?: this
internal fun getSearchParameters(filters: AnimeFilterList, origen: TioAnimeHFiltersData.ORIGEN): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
val strFilter = buildString {
when (origen) {
TioAnimeHFiltersData.ORIGEN.ANIME -> {
append(filters.parseCheckbox<TypesFilter>(TioAnimeHFiltersData.TYPES, "type"))
append(filters.parseCheckbox<AnimeGenresFilter>(TioAnimeHFiltersData.ANIME_GENRES, "genero"))
append(filters.asQueryPart<YearsFilter>("year").addSuffix())
append(filters.asQueryPart<StatesFilter>("status"))
append(filters.asQueryPart<SortFilter>("sort"))
}
else -> {
append(filters.asQueryPart<HentaiGenresFilter>("genero"))
}
}
}
return FilterSearchParams(strFilter)
}
private val ANIME_FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
TypesFilter(),
AnimeGenresFilter(),
YearsFilter(),
StatesFilter(),
SortFilter(),
)
private val HENTAI_FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
HentaiGenresFilter(),
)
fun getFilterList(origen: TioAnimeHFiltersData.ORIGEN) = when (origen) {
TioAnimeHFiltersData.ORIGEN.ANIME -> ANIME_FILTER_LIST
else -> HENTAI_FILTER_LIST
}
class TypesFilter : CheckBoxFilterList("Tipo", TioAnimeHFiltersData.TYPES.toCheckBoxVal())
class AnimeGenresFilter : CheckBoxFilterList("Género", TioAnimeHFiltersData.ANIME_GENRES.toCheckBoxVal())
class HentaiGenresFilter : QueryPartFilter("Género", TioAnimeHFiltersData.HENTAI_GENRES)
class YearsFilter : QueryPartFilter("Año", TioAnimeHFiltersData.YEARS)
class StatesFilter : QueryPartFilter("Estado", TioAnimeHFiltersData.STATES)
class SortFilter : QueryPartFilter("Orden", TioAnimeHFiltersData.SORT)
object TioAnimeHFiltersData {
val YEARS = arrayOf(Pair("Todos", "")) + (1950..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
val TYPES = arrayOf(
Pair("TV", "0"),
Pair("Película", "1"),
Pair("OVA", "2"),
Pair("Especial", "3"),
)
val ANIME_GENRES = arrayOf(
Pair("Acción", "accion"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventuras", "aventura"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Demencia", "demencia"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Espacial", "espacial"),
Pair("Fantasía", "fantasia"),
Pair("Harem", "harem"),
Pair("Historico", "historico"),
Pair("Infantil", "infantil"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos de la vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shounen", "shounen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superpoderes", "superpoderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
val HENTAI_GENRES = arrayOf(
Pair("Ahegao", "ahegao"),
Pair("Anal", "anal"),
Pair("Bestialidad", "bestialidad"),
Pair("Bondage", "bondage"),
Pair("Chikan", "chikan"),
Pair("Comedia", "comedia"),
Pair("Enfermeras", "enfermeras"),
Pair("Escolar", "escolar"),
Pair("Fantasia", "fantasia"),
Pair("Futanari", "futanari"),
Pair("Gangbang", "gangbang"),
Pair("Harem", "harem"),
Pair("Incesto", "incesto"),
Pair("Vamp", "vamp"),
Pair("Maids", "maids"),
Pair("Milf", "milf"),
Pair("Netorare", "netorare"),
Pair("Masturbacion", "masturbacion"),
Pair("Romance", "romance"),
Pair("Shota", "shota"),
Pair("Tentaculos", "tentaculos"),
Pair("Tetonas", "tetonas"),
Pair("Violacion", "violacion"),
Pair("Virgenes", "virgenes"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Demonios", "demonios"),
Pair("Felacion", "felacion"),
)
val STATES = arrayOf(
Pair("Finalizado", "2"),
Pair("En emisión", "1"),
Pair("Próximamente", "3"),
)
val SORT = arrayOf(
Pair("Mas Reciente", "recent"),
Pair("Menos Reciente", "-recent"),
)
enum class ORIGEN {
ANIME,
HENTAI,
}
}
}

View file

@ -4,19 +4,20 @@ import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.tioanimeh.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
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.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.vidguardextractor.VidGuardExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -54,21 +55,21 @@ open class TioanimeH(override val name: String, override val baseUrl: String) :
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/directorio?p=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.url = element.select("article a").attr("href")
anime.title = element.select("article a h3").text()
anime.thumbnail_url = baseUrl + element.select("article a div figure img").attr("src")
return anime
return SAnime.create().apply {
url = element.select("article a").attr("href")
title = element.select("article a h3").text()
thumbnail_url = baseUrl + element.select("article a div figure img").attr("src")
}
}
override fun popularAnimeNextPageSelector(): String = "nav ul.pagination.d-inline-flex li.page-item a.page-link"
override fun popularAnimeNextPageSelector(): String = ".pagination .active ~ li:not(.disabled)"
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val epInfoScript = document.selectFirst("script:containsData(var episodes = )")!!.data()
if (epInfoScript.substringAfter("episodes = [").substringBefore("];").isEmpty()) {
return listOf<SEpisode>()
return emptyList()
}
val epNumList = epInfoScript.substringAfter("episodes = [").substringBefore("];").split(",")
@ -84,27 +85,36 @@ open class TioanimeH(override val name: String, override val baseUrl: String) :
}
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
/*--------------------------------Video extractors------------------------------------*/
private val voeExtractor by lazy { VoeExtractor(client) }
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
private val mixDropExtractor by lazy { MixDropExtractor(client) }
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val serverList = document.selectFirst("script:containsData(var videos =)")!!.data().substringAfter("var videos = [[").substringBefore("]];")
.replace("\"", "").split("],[")
serverList.forEach {
val serverList = document.selectFirst("script:containsData(var videos =)")
?.data()?.substringAfter("var videos = [[")?.substringBefore("]];")
?.replace("\"", "")?.split("],[") ?: return emptyList()
return serverList.parallelCatchingFlatMapBlocking {
val servers = it.split(",")
val serverName = servers[0]
val serverUrl = servers[1].replace("\\/", "/")
when (serverName.lowercase()) {
"voe" -> VoeExtractor(client).videosFromUrl(serverUrl).let(videoList::addAll)
"vidguard" -> VidGuardExtractor(client).videosFromUrl(serverUrl).let(videoList::addAll)
"okru" -> OkruExtractor(client).videosFromUrl(serverUrl).let(videoList::addAll)
"yourupload" -> YourUploadExtractor(client).videoFromUrl(serverUrl, headers = headers).let(videoList::addAll)
"voe" -> voeExtractor.videosFromUrl(serverUrl)
"vidguard" -> vidGuardExtractor.videosFromUrl(serverUrl)
"okru" -> okruExtractor.videosFromUrl(serverUrl)
"yourupload" -> yourUploadExtractor.videoFromUrl(serverUrl, headers = headers)
"mixdrop" -> mixDropExtractor.videosFromUrl(serverUrl)
else -> emptyList()
}
}
return videoList
}
override fun videoListSelector() = throw UnsupportedOperationException()
@ -126,32 +136,29 @@ open class TioanimeH(override val name: String, override val baseUrl: String) :
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = if (filterList.isNotEmpty())filterList.find { it is GenreFilter } as GenreFilter else { GenreFilter().apply { state = 0 } }
val params = TioAnimeHFilters.getSearchParameters(filters, TioAnimeHFilters.TioAnimeHFiltersData.ORIGEN.HENTAI)
return when {
query.isNotBlank() -> GET("$baseUrl/directorio?q=$query&p=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/directorio?genero=${genreFilter.toUriPart()}&p=$page")
else -> GET("$baseUrl/directorio?p=$page ")
params.filter.isNotBlank() -> GET("$baseUrl/directorio${params.getQuery()}&p=$page")
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.select("h1.title").text()
anime.description = document.selectFirst("p.sinopsis")!!.ownText()
anime.genre = document.select("p.genres span.btn.btn-sm.btn-primary.rounded-pill a").joinToString { it.text() }
anime.thumbnail_url = document.select(".thumb img").attr("abs:src")
anime.status = parseStatus(document.select("a.btn.btn-success.btn-block.status").text())
return anime
return SAnime.create().apply {
title = document.select("h1.title").text()
description = document.selectFirst("p.sinopsis")!!.ownText()
genre = document.select("p.genres span.btn.btn-sm.btn-primary.rounded-pill a").joinToString { it.text() }
thumbnail_url = document.select(".thumb img").attr("abs:src")
status = parseStatus(document.select("a.btn.btn-success.btn-block.status").text())
}
}
private fun parseStatus(statusString: String): Int {
@ -165,59 +172,21 @@ open class TioanimeH(override val name: String, override val baseUrl: String) :
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.title = element.select("article a h3").text()
anime.thumbnail_url = baseUrl + element.select("article a div figure img").attr("src")
return SAnime.create().apply {
title = element.select("article a h3").text()
thumbnail_url = baseUrl + element.select("article a div figure img").attr("src")
val slug = if (baseUrl.contains("hentai")) "/hentai/" else "/anime/"
val fixUrl = element.select("article a").attr("href").split("-").toTypedArray()
val realUrl = fixUrl.copyOf(fixUrl.size - 1).joinToString("-").replace("/ver/", slug)
anime.setUrlWithoutDomain(realUrl)
return anime
setUrlWithoutDomain(realUrl)
}
}
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
override fun latestUpdatesSelector() = ".episodes li"
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
class GenreFilter : UriPartFilter(
"Generos",
arrayOf(
Pair("<selecionar>", ""),
Pair("Ahegao", "ahegao"),
Pair("Anal", "anal"),
Pair("Casadas", "casadas"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Enfermeras", "enfermeras"),
Pair("Futanari", "futanari"),
Pair("Harem", "Harem"),
Pair("Gore", "gore"),
Pair("Hardcore", "hardcore"),
Pair("Incesto", "incesto"),
Pair("Juegos Sexuales", "juegos-sexuales"),
Pair("Milfs", "milf"),
Pair("Orgia", "orgia"),
Pair("Romance", "romance"),
Pair("Shota", "shota"),
Pair("Succubus", "succubus"),
Pair("Tetonas", "tetonas"),
Pair("Violacion", "violacion"),
Pair("Virgenes(como tu)", "virgenes"),
Pair("Yaoi", "Yaoi"),
Pair("Yuri", "yuri"),
),
)
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun getFilterList(): AnimeFilterList = TioAnimeHFilters.getFilterList(TioAnimeHFilters.TioAnimeHFiltersData.ORIGEN.HENTAI)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {

View file

@ -15,65 +15,16 @@ class TioanimeHFactory : AnimeSourceFactory {
class TioAnime : TioanimeH("TioAnime", "https://tioanime.com") {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = if (filterList.isNotEmpty())filterList.find { it is GenreFilter } as GenreFilter else { GenreFilter().apply { state = 0 } }
val params = TioAnimeHFilters.getSearchParameters(filters, TioAnimeHFilters.TioAnimeHFiltersData.ORIGEN.ANIME)
return when {
query.isNotBlank() -> GET("$baseUrl/directorio?q=$query&p=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/directorio?genero=${genreFilter.toUriPart()}&p=$page")
else -> GET("$baseUrl/directorio?p=$page ")
params.filter.isNotBlank() -> GET("$baseUrl/directorio${params.getQuery()}&p=$page")
else -> popularAnimeRequest(page)
}
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", "all"),
Pair("Acción", "accion"),
Pair("Artes Marciales", "artes_marciales"),
Pair("Aventuras", "aventura"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia_ficcion"),
Pair("Comedia", "comedia"),
Pair("Demencia", "demencia"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Espacial", "espacial"),
Pair("Fantasía", "fantasia"),
Pair("Harem", "harem"),
Pair("Historico", "historico"),
Pair("Infantil", "infantil"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos de la vida", "recuentos_de_la_vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shounen", "shounen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superpoderes", "superpoderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
override fun getFilterList(): AnimeFilterList = TioAnimeHFilters.getFilterList(TioAnimeHFilters.TioAnimeHFiltersData.ORIGEN.ANIME)
}
class TioHentai : TioanimeH("TioHentai", "https://tiohentai.com")

View file

@ -1,124 +0,0 @@
package eu.kanade.tachiyomi.animeextension.es.tioanimeh.extractors
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class VidGuardExtractor(private val client: OkHttpClient) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch) {
var payload: String = ""
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().asJsoup()
val scriptUrl = doc.selectFirst("script[src*=ad/plugin]")
?.absUrl("src")
?: return emptyList()
val headers = Headers.headersOf("Referer", url)
val script = client.newCall(GET(scriptUrl, headers)).execute()
.body.string()
val sources = getSourcesFromScript(script, url)
.takeIf { it.isNotBlank() && it != "undefined" }
?: return emptyList()
return sources.substringAfter("stream:[").substringBefore("}]")
.split('{')
.drop(1)
.mapNotNull { line ->
val resolution = line.substringAfter("Label\":\"").substringBefore('"')
val videoUrl = line.substringAfter("URL\":\"").substringBefore('"')
.takeIf(String::isNotBlank)
?.let(::fixUrl)
?: return@mapNotNull null
Video(videoUrl, "VidGuard:$resolution", videoUrl, headers)
}
}
private fun getSourcesFromScript(script: String, url: String): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
val jsinterface = JsObject(latch)
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.clearCache(true)
view?.clearFormData()
view?.evaluateJavascript(script) {}
view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {}
}
}
webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null)
}
latch.await(5, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return jsinterface.payload
}
private fun fixUrl(url: String): String {
val httpUrl = url.toHttpUrl()
val originalSign = httpUrl.queryParameter("sig")!!
val newSign = originalSign.chunked(2).joinToString("") {
Char(it.toInt(16) xor 2).toString()
}
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substring(5)
.chunked(2)
.reversed()
.joinToString("")
.substring(5)
return httpUrl.newBuilder()
.removeAllQueryParameters("sig")
.addQueryParameter("sig", newSign)
.build()
.toString()
}
}