Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,26 @@
ext {
extName = 'Pelisplushd'
extClass = '.PelisplushdFactory'
extVersionCode = 53
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:vudeo-extractor'))
implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamlare-extractor'))
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:burstcloud-extractor'))
implementation(project(':lib:fastream-extractor'))
implementation(project(':lib:upstream-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,352 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamHideExtractor
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.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
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.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
open class Pelisplushd(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es"
override val supportsLatest = false
val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
const val PREF_QUALITY_DEFAULT = "1080"
val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
)
}
override fun popularAnimeSelector(): String = "div.Posters a.Posters-link"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href"))
title = element.select("a div.listing-content p").text()
thumbnail_url = element.select("a img").attr("src").replace("/w154/", "/w200/")
}
}
override fun popularAnimeNextPageSelector(): String = "a.page-link"
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val jsoup = response.asJsoup()
if (response.request.url.toString().contains("/pelicula/")) {
val episode = SEpisode.create().apply {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episodes.add(episode)
} else {
jsoup.select("div.tab-content div a").forEachIndexed { index, element ->
val episode = SEpisode.create().apply {
episode_number = (index + 1).toFloat()
name = element.text()
setUrlWithoutDomain(element.attr("abs:href"))
}
episodes.add(episode)
}
}
return episodes.reversed()
}
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val data = document.selectFirst("script:containsData(video[1] = )")?.data()
val apiUrl = data?.substringAfter("video[1] = '", "")?.substringBefore("';", "")
val alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)")
if (!apiUrl.isNullOrEmpty()) {
val apiResponse = client.newCall(GET(apiUrl)).execute().asJsoup()
val regIsUrl = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex()
val encryptedList = apiResponse.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li")
encryptedList.parallelCatchingFlatMapBlocking {
val url = it.attr("onclick")
.substringAfter("go_to_player('")
.substringAfter("go_to_playerVast('")
.substringBefore("?cover_url=")
.substringBefore("')")
.substringBefore("',")
.substringBefore("?poster")
.substringBefore("?c_poster=")
.substringBefore("?thumb=")
.substringBefore("#poster=")
val realUrl = if (!regIsUrl.containsMatchIn(url)) {
String(Base64.decode(url, Base64.DEFAULT))
} else if (url.contains("?data=")) {
val apiPageSoup = client.newCall(GET(url)).execute().asJsoup()
apiPageSoup.selectFirst("iframe")?.attr("src") ?: ""
} else {
url
}
serverVideoResolver(realUrl)
}.also(videoList::addAll)
}
// verifier for old series
if (!apiUrl.isNullOrEmpty() && !apiUrl.contains("/video/") || alternativeServers.any()) {
document.select("ul.TbVideoNv.nav.nav-tabs li").parallelCatchingFlatMapBlocking { id ->
val serverName = id.select("a").text().lowercase()
val serverId = id.attr("data-id")
var serverUrl = data?.substringAfter("video[$serverId] = '", "")?.substringBefore("';", "")
if (serverUrl != null && serverUrl.contains("api.mycdn.moe")) {
val urlId = serverUrl.substringAfter("id=")
serverUrl = when (serverName) {
"sbfast" -> { "https://sbfull.com/e/$urlId" }
"plusto" -> { "https://owodeuwu.xyz/v/$urlId" }
"doodstream" -> { "https://dood.to/e/$urlId" }
"upload", "uqload" -> { "https://uqload.com/embed-$urlId.html" }
else -> ""
}
}
serverVideoResolver(serverUrl ?: "")
}.also(videoList::addAll)
}
return videoList
}
private fun serverVideoResolver(url: String): List<Video> {
val embedUrl = url.lowercase()
return runCatching {
when {
embedUrl.contains("voe") -> VoeExtractor(client).videosFromUrl(url)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> OkruExtractor(client).videosFromUrl(url)
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders)
}
!embedUrl.contains("disable") && (embedUrl.contains("amazon") || embedUrl.contains("amz")) -> {
val body = client.newCall(GET(url)).execute().asJsoup()
return if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
listOf(Video(videoUrl, "Amazon", videoUrl))
} else {
emptyList()
}
}
embedUrl.contains("uqload") -> UqloadExtractor(client).videosFromUrl(url)
embedUrl.contains("mp4upload") -> Mp4uploadExtractor(client).videosFromUrl(url, headers)
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
}
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)!!)
}
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url)
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers)
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> BurstCloudExtractor(client).videoFromUrl(url, headers = headers)
embedUrl.contains("fastream") -> FastreamExtractor(client, headers).videosFromUrl(url, prefix = "Fastream:")
embedUrl.contains("upstream") -> UpstreamExtractor(client).videosFromUrl(url)
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> listOf(StreamTapeExtractor(client).videoFromUrl(url, quality = "StreamTape")!!)
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("guccihide") || embedUrl.contains("streamvid") -> StreamHideExtractor(client).videosFromUrl(url, "StreamHide")
else -> emptyList()
}
}.getOrNull() ?: emptyList()
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
fun getNumberFromString(epsStr: String) = epsStr.filter { it.isDigit() }.ifEmpty { "0" }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val tagFilter = filters.find { it is Tags } as Tags
return when {
query.isNotBlank() -> GET("$baseUrl/search?s=$query&page=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}?page=$page")
tagFilter.state.isNotBlank() -> GET("$baseUrl/year/${tagFilter.state}?page=$page")
else -> GET("$baseUrl/peliculas?page=$page")
}
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
return SAnime.create().apply {
title = document.selectFirst("h1.m-b-5")!!.text()
thumbnail_url = document.selectFirst("div.card-body div.row div.col-sm-3 img.img-fluid")!!
.attr("src").replace("/w154/", "/w500/")
description = document.selectFirst("div.col-sm-4 div.text-large")!!.ownText()
genre = document.select("div.p-v-20.p-h-15.text-center a span").joinToString { it.text() }
status = SAnime.COMPLETED
}
}
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro de año"),
GenreFilter(),
AnimeFilter.Header("Busqueda por año"),
Tags("Año"),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<selecionar>", ""),
Pair("Peliculas", "peliculas"),
Pair("Series", "series"),
Pair("Doramas", "generos/dorama"),
Pair("Animes", "animes"),
Pair("Acción", "generos/accion"),
Pair("Animación", "generos/animacion"),
Pair("Aventura", "generos/aventura"),
Pair("Ciencia Ficción", "generos/ciencia-ficcion"),
Pair("Comedia", "generos/comedia"),
Pair("Crimen", "generos/crimen"),
Pair("Documental", "generos/documental"),
Pair("Drama", "generos/drama"),
Pair("Fantasía", "generos/fantasia"),
Pair("Foreign", "generos/foreign"),
Pair("Guerra", "generos/guerra"),
Pair("Historia", "generos/historia"),
Pair("Misterio", "generos/misterio"),
Pair("Pelicula de Televisión", "generos/pelicula-de-la-television"),
Pair("Romance", "generos/romance"),
Pair("Suspense", "generos/suspense"),
Pair("Terror", "generos/terror"),
Pair("Western", "generos/western"),
),
)
private class Tags(name: String) : AnimeFilter.Text(name)
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 setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_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)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
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()
}
}.also(screen::addPreference)
}
}

View file

@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
class PelisplushdFactory : AnimeSourceFactory {
override fun createSources(): List<AnimeSource> = listOf(
Pelisplushd("PelisPlusHD", "https://ww1.pelisplushd.nu"),
Pelisplusto("PelisPlusTo", "https://ww3.pelisplus.to"),
Pelisplusph("PelisPlusPh", "https://www.pelisplushd.ph"),
)
}

View file

@ -0,0 +1,363 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamHideExtractor
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.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
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.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class Pelisplusph(override val name: String, override val baseUrl: String) : Pelisplushd(name, baseUrl) {
override val supportsLatest = false
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"StreamHide", "Tomatomatela",
)
}
override fun popularAnimeSelector(): String = ".items-peliculas .item-pelicula"
override fun popularAnimeNextPageSelector(): String = ".items-peliculas > a"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(baseUrl + element.selectFirst("a")?.attr("href"))
anime.title = element.select("a .item-detail > p").text()
anime.thumbnail_url = baseUrl + element.select("a .item-picture img").attr("src")
return anime
}
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.selectFirst(".info-content h1")!!.text()
document.select(".info-content p").map { p ->
if (p.select(".content-type").text().contains("Sinópsis:")) {
anime.description = p.select(".sinopsis")!!.text()
}
if (p.select(".content-type").text().contains("Géneros:")) {
anime.genre = p.select(".content-type-a a").joinToString { it.text() }
}
if (p.select(".content-type").text().contains("Reparto:")) {
anime.artist = p.select(".content-type ~ span").text().substringBefore(",")
}
}
anime.status =
if (document.location().contains("/serie/")) SAnime.UNKNOWN else SAnime.COMPLETED
return anime
}
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val jsoup = response.asJsoup()
if (response.request.url.toString().contains("/pelicula/")) {
val episode = SEpisode.create().apply {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episodes.add(episode)
} else {
var index = 0
jsoup.select(".item-season").reversed().mapIndexed { idxSeas, season ->
val seasonNumber = runCatching {
getNumberFromString(season.selectFirst(".item-season-title")!!.ownText())
}.getOrElse { idxSeas + 1 }
season.select(".item-season-episodes a").reversed().mapIndexed { idx, ep ->
index += 1
val noEp = try {
getNumberFromString(ep.ownText())
} catch (_: Exception) {
idx + 1
}
val episode = SEpisode.create().apply {
episode_number = index.toFloat()
name = "T$seasonNumber - E$noEp - ${ep.ownText()}"
setUrlWithoutDomain(ep.attr("abs:href"))
}
episodes.add(episode)
}
}
}
return episodes.reversed()
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/search/$query", headers)
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}?page=$page")
else -> popularAnimeRequest(page)
}
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return document.select("[class*=server-item-]").parallelCatchingFlatMapBlocking { serverItem ->
val langIdx = getNumberFromString(serverItem.attr("class").substringAfter("server-item-"))
val langItem = document.select("li[data-id=\"$langIdx\"] a").text()
val lang = if (langItem.contains("Subtitulado")) "[SUB]" else if (langItem.contains("Latino")) "[LAT]" else "[CAST]"
serverItem.select("li.tab-video").map { videoItem ->
val url = videoItem.attr("data-video")
serverVideoResolver(url, lang)
}.flatten()
}
}
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val embedUrl = url.lowercase()
return runCatching {
when {
embedUrl.contains("voe") -> VoeExtractor(client).videosFromUrl(url, prefix)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> OkruExtractor(client).videosFromUrl(url, prefix)
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders)
}
embedUrl.contains("uqload") -> UqloadExtractor(client).videosFromUrl(url, prefix = prefix)
embedUrl.contains("mp4upload") -> Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix)
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
}
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)!!)
}
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url, prefix = prefix)
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix)
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix)
embedUrl.contains("fastream") -> FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:")
embedUrl.contains("upstream") -> UpstreamExtractor(client).videosFromUrl(url, prefix = prefix)
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> listOf(StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")!!)
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("guccihide") || embedUrl.contains("streamvid") -> StreamHideExtractor(client).videosFromUrl(url, "$prefix StreamHide")
else -> emptyList()
}
}.getOrNull() ?: emptyList()
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por genero ignora los otros filtros"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<selecionar>", ""),
Pair("Peliculas", "peliculas"),
Pair("Series", "series"),
Pair("Estrenos", "estrenos"),
Pair("Acción", "genero/accion"),
Pair("Artes marciales", "genero/artes-marciales"),
Pair("Asesinos en serie", "genero/asesinos-en-serie"),
Pair("Aventura", "genero/aventura"),
Pair("Baile", "genero/baile"),
Pair("Bélico", "genero/belico"),
Pair("Biografico", "genero/biografico"),
Pair("Catástrofe", "genero/catastrofe"),
Pair("Ciencia Ficción", "genero/ciencia-ficcion"),
Pair("Cine Adolescente", "genero/cine-adolescente"),
Pair("Cine LGBT", "genero/cine-lgbt"),
Pair("Cine Negro", "genero/cine-negro"),
Pair("Cine Policiaco", "genero/cine-policiaco"),
Pair("Clásicas", "genero/clasicas"),
Pair("Comedia", "genero/comedia"),
Pair("Comedia Negra", "genero/comedia-negra"),
Pair("Crimen", "genero/crimen"),
Pair("DC Comics", "genero/dc-comics"),
Pair("Deportes", "genero/deportes"),
Pair("Desapariciones", "genero/desapariciones"),
Pair("Disney", "genero/disney"),
Pair("Documental", "genero/documental"),
Pair("Drama", "genero/drama"),
Pair("Familiar", "genero/familiar"),
Pair("Fantasía", "genero/fantasia"),
Pair("Historia", "genero/historia"),
Pair("Horror", "genero/horror"),
Pair("Infantil", "genero/infantil"),
Pair("Intriga", "genero/intriga"),
Pair("live action", "genero/live-action"),
Pair("Marvel Comics", "genero/marvel-comics"),
Pair("Misterio", "genero/misterio"),
Pair("Música", "genero/musica"),
Pair("Musical", "genero/musical"),
Pair("Policial", "genero/policial"),
Pair("Político", "genero/politico"),
Pair("Psicológico", "genero/psicologico"),
Pair("Reality Tv", "genero/reality-tv"),
Pair("Romance", "genero/romance"),
Pair("Secuestro", "genero/secuestro"),
Pair("Slasher", "genero/slasher"),
Pair("Sobrenatural", "genero/sobrenatural"),
Pair("Stand Up", "genero/stand-up"),
Pair("Superhéroes", "genero/superheroes"),
Pair("Suspenso", "genero/suspenso"),
Pair("Terror", "genero/terror"),
Pair("Thriller", "genero/thriller"),
Pair("Tokusatsu", "genero/tokusatsu"),
Pair("TV Series", "genero/tv-series"),
Pair("Western", "genero/western"),
Pair("Zombie", "genero/zombie"),
Pair("Acción", "genero/accion"),
Pair("Artes marciales", "genero/artes-marciales"),
Pair("Asesinos en serie", "genero/asesinos-en-serie"),
Pair("Aventura", "genero/aventura"),
Pair("Baile", "genero/baile"),
Pair("Bélico", "genero/belico"),
Pair("Biografico", "genero/biografico"),
Pair("Catástrofe", "genero/catastrofe"),
Pair("Ciencia Ficción", "genero/ciencia-ficcion"),
Pair("Cine Adolescente", "genero/cine-adolescente"),
Pair("Cine LGBT", "genero/cine-lgbt"),
Pair("Cine Negro", "genero/cine-negro"),
Pair("Cine Policiaco", "genero/cine-policiaco"),
Pair("Clásicas", "genero/clasicas"),
Pair("Comedia", "genero/comedia"),
Pair("Comedia Negra", "genero/comedia-negra"),
Pair("Crimen", "genero/crimen"),
Pair("DC Comics", "genero/dc-comics"),
Pair("Deportes", "genero/deportes"),
Pair("Desapariciones", "genero/desapariciones"),
Pair("Disney", "genero/disney"),
Pair("Documental", "genero/documental"),
Pair("Drama", "genero/drama"),
Pair("Familiar", "genero/familiar"),
Pair("Fantasía", "genero/fantasia"),
Pair("Historia", "genero/historia"),
Pair("Horror", "genero/horror"),
Pair("Infantil", "genero/infantil"),
Pair("Intriga", "genero/intriga"),
Pair("live action", "genero/live-action"),
Pair("Marvel Comics", "genero/marvel-comics"),
Pair("Misterio", "genero/misterio"),
Pair("Música", "genero/musica"),
Pair("Musical", "genero/musical"),
Pair("Policial", "genero/policial"),
Pair("Político", "genero/politico"),
Pair("Psicológico", "genero/psicologico"),
Pair("Reality Tv", "genero/reality-tv"),
Pair("Romance", "genero/romance"),
Pair("Secuestro", "genero/secuestro"),
Pair("Slasher", "genero/slasher"),
Pair("Sobrenatural", "genero/sobrenatural"),
Pair("Stand Up", "genero/stand-up"),
Pair("Superhéroes", "genero/superheroes"),
Pair("Suspenso", "genero/suspenso"),
Pair("Terror", "genero/terror"),
Pair("Thriller", "genero/thriller"),
Pair("Tokusatsu", "genero/tokusatsu"),
Pair("TV Series", "genero/tv-series"),
Pair("Western", "genero/western"),
Pair("Zombie", "genero/zombie"),
),
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_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)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
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()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
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()
}
}.also(screen::addPreference)
}
}

View file

@ -0,0 +1,302 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamHideExtractor
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.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
class Pelisplusto(override val name: String, override val baseUrl: String) : Pelisplushd(name, baseUrl) {
private val json: Json by injectLazy()
override val supportsLatest = false
companion object {
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
)
}
override fun popularAnimeSelector(): String = "article.item"
override fun popularAnimeNextPageSelector(): String = "a[rel=\"next\"]"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("abs:href"))
anime.title = element.select("a h2").text()
anime.thumbnail_url = element.select("a .item__image picture img").attr("data-src")
return anime
}
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.selectFirst(".home__slider_content div h1.slugh1")!!.text()
anime.description = document.selectFirst(".home__slider_content .description")!!.text()
anime.genre = document.select(".home__slider_content div:nth-child(5) > a").joinToString { it.text() }
anime.artist = document.selectFirst(".home__slider_content div:nth-child(7) > a")?.text()
anime.status = SAnime.COMPLETED
return anime
}
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val jsoup = response.asJsoup()
if (response.request.url.toString().contains("/pelicula/")) {
val episode = SEpisode.create().apply {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episodes.add(episode)
} else {
var jsonscript = ""
jsoup.select("script[type=text/javascript]").mapNotNull { script ->
val ssRegex = Regex("(?i)seasons")
val ss = if (script.data().contains(ssRegex)) script.data() else ""
val swaa = ss.substringAfter("seasonsJson = ").substringBefore(";")
jsonscript = swaa
}
val jsonParse = json.decodeFromString<JsonObject>(jsonscript)
var index = 0
jsonParse.entries.map {
it.value.jsonArray.reversed().map { element ->
index += 1
val jsonElement = element!!.jsonObject
val season = jsonElement["season"]!!.jsonPrimitive!!.content
val title = jsonElement["title"]!!.jsonPrimitive!!.content
val ep = jsonElement["episode"]!!.jsonPrimitive!!.content
val episode = SEpisode.create().apply {
episode_number = index.toFloat()
name = "T$season - E$ep - $title"
setUrlWithoutDomain("${response.request.url}/season/$season/episode/$ep")
}
episodes.add(episode)
}
}
}
return episodes.reversed()
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/api/search/$query", headers)
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}?page=$page")
else -> popularAnimeRequest(page)
}
}
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val regIsUrl = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex()
return document.select(".bg-tabs ul li").parallelCatchingFlatMapBlocking {
val decode = String(Base64.decode(it.attr("data-server"), Base64.DEFAULT))
val url = if (!regIsUrl.containsMatchIn(decode)) {
"$baseUrl/player/${String(Base64.encode(it.attr("data-server").toByteArray(), Base64.DEFAULT))}"
} else { decode }
val videoUrl = if (url.contains("/player/")) {
val script = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(window.onload)")?.data() ?: ""
fetchUrls(script).firstOrNull() ?: ""
} else {
url
}.replace("https://sblanh.com", "https://lvturbo.com")
.replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "")
serverVideoResolver(videoUrl)
}
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
private fun serverVideoResolver(url: String): List<Video> {
val embedUrl = url.lowercase()
return runCatching {
when {
embedUrl.contains("voe") -> VoeExtractor(client).videosFromUrl(url)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> OkruExtractor(client).videosFromUrl(url)
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders)
}
!embedUrl.contains("disable") && (embedUrl.contains("amazon") || embedUrl.contains("amz")) -> {
val body = client.newCall(GET(url)).execute().asJsoup()
return if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
listOf(Video(videoUrl, "Amazon", videoUrl))
} else {
emptyList()
}
}
embedUrl.contains("uqload") -> UqloadExtractor(client).videosFromUrl(url)
embedUrl.contains("mp4upload") -> Mp4uploadExtractor(client).videosFromUrl(url, headers)
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
}
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)!!)
}
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url)
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers)
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> BurstCloudExtractor(client).videoFromUrl(url, headers = headers)
embedUrl.contains("fastream") -> FastreamExtractor(client, headers).videosFromUrl(url, prefix = "Fastream:")
embedUrl.contains("upstream") -> UpstreamExtractor(client).videosFromUrl(url)
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> listOf(StreamTapeExtractor(client).videoFromUrl(url, quality = "StreamTape")!!)
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("guccihide") || embedUrl.contains("streamvid") -> StreamHideExtractor(client).videosFromUrl(url, "StreamHide")
else -> emptyList()
}
}.getOrNull() ?: emptyList()
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por genero ignora los otros filtros"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<selecionar>", ""),
Pair("Peliculas", "peliculas"),
Pair("Series", "series"),
Pair("Doramas", "doramas"),
Pair("Animes", "animes"),
Pair("Acción", "genres/accion"),
Pair("Action & Adventure", "genres/action-adventure"),
Pair("Animación", "genres/animacion"),
Pair("Aventura", "genres/aventura"),
Pair("Bélica", "genres/belica"),
Pair("Ciencia ficción", "genres/ciencia-ficcion"),
Pair("Comedia", "genres/comedia"),
Pair("Crimen", "genres/crimen"),
Pair("Documental", "genres/documental"),
Pair("Dorama", "genres/dorama"),
Pair("Drama", "genres/drama"),
Pair("Familia", "genres/familia"),
Pair("Fantasía", "genres/fantasia"),
Pair("Guerra", "genres/guerra"),
Pair("Historia", "genres/historia"),
Pair("Horror", "genres/horror"),
Pair("Kids", "genres/kids"),
Pair("Misterio", "genres/misterio"),
Pair("Música", "genres/musica"),
Pair("Musical", "genres/musical"),
Pair("Película de TV", "genres/pelicula-de-tv"),
Pair("Reality", "genres/reality"),
Pair("Romance", "genres/romance"),
Pair("Sci-Fi & Fantasy", "genres/sci-fi-fantasy"),
Pair("Soap", "genres/soap"),
Pair("Suspense", "genres/suspense"),
Pair("Terror", "genres/terror"),
Pair("War & Politics", "genres/war-politics"),
Pair("Western", "genres/western"),
),
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_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)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
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()
}
}.also(screen::addPreference)
}
}

View file

@ -0,0 +1,205 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors
import java.util.regex.Pattern
import kotlin.math.pow
// https://github.com/cylonu87/JsUnpacker
class JsUnpacker(packedJS: String?) {
private var packedJS: String? = null
/**
* Detects whether the javascript is P.A.C.K.E.R. coded.
*
* @return true if it's P.A.C.K.E.R. coded.
*/
fun detect(): Boolean {
val js = packedJS!!.replace(" ", "")
val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]")
val m = p.matcher(js)
return m.find()
}
/**
* Unpack the javascript
*
* @return the javascript unpacked or null.
*/
fun unpack(): String? {
val js = packedJS
runCatching {
var p =
Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL)
var m = p.matcher(js)
if (m.find() && m.groupCount() == 4) {
val payload = m.group(1).replace("\\'", "'")
val radixStr = m.group(2)
val countStr = m.group(3)
val symtab = m.group(4).split("\\|".toRegex()).toTypedArray()
val radix = radixStr.toIntOrNull() ?: 36
val count = countStr.toIntOrNull() ?: 0
if (symtab.size != count) {
throw Exception("Unknown p.a.c.k.e.r. encoding")
}
val unbase = Unbase(radix)
p = Pattern.compile("\\b\\w+\\b")
m = p.matcher(payload)
val decoded = StringBuilder(payload)
var replaceOffset = 0
while (m.find()) {
val word = m.group(0)
val x = unbase.unbase(word)
var value: String? = null
if (x < symtab.size && x >= 0) {
value = symtab[x]
}
if (value != null && value.isNotEmpty()) {
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
replaceOffset += value.length - word.length
}
}
return decoded.toString()
}
}
return null
}
private inner class Unbase(private val radix: Int) {
private val alphabet62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val alphabet95 =
" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
private var alphabet: String? = null
private var dictionary: HashMap<String, Int>? = null
fun unbase(str: String): Int {
var ret = 0
if (alphabet == null) {
ret = str.toInt(radix)
} else {
val tmp = StringBuilder(str).reverse().toString()
for (i in tmp.indices) {
ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt()
}
}
return ret
}
init {
if (radix > 36) {
when {
radix < 62 -> {
alphabet = alphabet62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = alphabet95.substring(0, radix)
}
radix == 62 -> {
alphabet = alphabet62
}
radix == 95 -> {
alphabet = alphabet95
}
}
dictionary = HashMap(95)
for (i in 0 until alphabet!!.length) {
dictionary!![alphabet!!.substring(i, i + 1)] = i
}
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
private val C =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73,
)
private val Z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64,
)
fun String.load(): String? {
return try {
var load = this
for (q in C.indices) {
if (C[q % 4] > 270) {
load += C[q % 3]
} else {
load += C[q].toChar()
}
}
Class.forName(load.substring(load.length - C.size, load.length)).name
} catch (_: Exception) {
try {
var f = C[2].toChar().toString()
for (w in Z.indices) {
f += Z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View file

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient
class StreamHideExtractor(private val client: OkHttpClient) {
// from nineanime / ask4movie FilemoonExtractor
private val subtitleRegex = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
fun videosFromUrl(url: String, name: String): List<Video> {
val page = client.newCall(GET(url)).execute().body.string()
val unpacked = JsUnpacker(page).unpack() ?: return emptyList()
val playlistUrl = unpacked.substringAfter("sources:")
.substringAfter("file:\"") // StreamHide
.substringAfter("src:\"") // StreamVid
.substringBefore('"')
val playlistData = client.newCall(GET(playlistUrl)).execute().body.string()
val subs = subtitleRegex.findAll(playlistData).map {
val urlPart = it.groupValues[2]
val subUrl = when {
!urlPart.startsWith("https:") ->
playlistUrl.substringBeforeLast("/") + "/$urlPart"
else -> urlPart
}
Track(subUrl, it.groupValues[1])
}.toList()
// The playlist usually only have one video quality.
return listOf(Video(playlistUrl, name, playlistUrl, subtitleTracks = subs))
}
}