Pelisplushd: New source added to Factory (#1598)

This commit is contained in:
imper1aldev 2023-05-10 07:10:06 -06:00 committed by GitHub
parent 4b0e6f8f04
commit 73195f0fa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 658 additions and 20 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'Pelisplushd'
pkgNameSuffix = 'es.pelisplushd'
extClass = '.PelisplushdFactory'
extVersionCode = 35
extVersionCode = 36
libVersion = '13'
}
@ -16,6 +16,7 @@ dependencies {
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-voe-extractor'))
implementation(project(':lib-okru-extractor'))
}
apply from: "$rootDir/common.gradle"

View File

@ -226,7 +226,7 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
}
}
private fun getNumberFromString(epsStr: String): String {
fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}

View File

@ -7,5 +7,6 @@ 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,337 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.FilemoonExtractor
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamlareExtractor
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
class Pelisplusph(override val name: String, override val baseUrl: String) : Pelisplushd(name, baseUrl) {
private val json: Json by injectLazy()
override val supportsLatest = false
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"
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
} else {
var index = 0
jsoup.select(".item-season").reversed().mapIndexed { idxSeas, season ->
val seasonNumber = try {
getNumberFromString(season.selectFirst(".item-season-title")!!.ownText())
} catch (_: Exception) { 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()
episode.episode_number = index.toFloat()
episode.name = "T$seasonNumber - E$noEp - ${ep.ownText()}"
episode.setUrlWithoutDomain(baseUrl + ep.attr("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()
val videoList = mutableListOf<Video>()
document.select("[class*=server-item-]").map {
val langIdx = getNumberFromString(it.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]"
it.select("li.tab-video").map { servers ->
val url = servers.attr("data-video")
loadExtractor(url, lang).let { videos ->
videoList.addAll(videos)
}
}
}
return videoList
}
private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} catch (e: Exception) { }
}
if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("sbembed.com") || embedUrl.contains("sbembed1.com") || embedUrl.contains("sbplay.org") ||
embedUrl.contains("sbvideo.net") || embedUrl.contains("streamsb.net") || embedUrl.contains("sbplay.one") ||
embedUrl.contains("cloudemb.com") || embedUrl.contains("playersb.com") || embedUrl.contains("tubesb.com") ||
embedUrl.contains("sbplay1.com") || embedUrl.contains("embedsb.com") || embedUrl.contains("watchsb.com") ||
embedUrl.contains("sbplay2.com") || embedUrl.contains("japopav.tv") || embedUrl.contains("viewsb.com") ||
embedUrl.contains("sbfast") || embedUrl.contains("sbfull.com") || embedUrl.contains("javplaya.com") ||
embedUrl.contains("ssbstream.net") || embedUrl.contains("p1ayerjavseen.com") || embedUrl.contains("sbthe.com") ||
embedUrl.contains("vidmovie.xyz") || embedUrl.contains("sbspeed.com") || embedUrl.contains("streamsss.net") ||
embedUrl.contains("sblanh.com") || embedUrl.contains("sbbrisk.com") || embedUrl.contains("lvturbo.com")
) {
runCatching {
StreamSBExtractor(client).videosFromUrl(url, headers, prefix = prefix)
}.getOrNull()?.let { videoList.addAll(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "$prefix VoeCDN")?.let { videoList.add(it) }
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videoFromUrl(url, prefix)?.let {
videoList.addAll(it)
}
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url)?.let { videoList.add(it) }
}
return videoList
}
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) {
val qualities = arrayOf(
"StreamSB:1080p", "StreamSB:720p", "StreamSB:480p", "StreamSB:360p", "StreamSB:240p", "StreamSB:144p", // StreamSB
"Fembed:1080p", "Fembed:720p", "Fembed:480p", "Fembed:360p", "Fembed:240p", "Fembed:144p", // Fembed
"DoodStream",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(videoQualityPref)
}
}

View File

@ -2,14 +2,18 @@ package eu.kanade.tachiyomi.animeextension.es.pelisplushd
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.FilemoonExtractor
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamlareExtractor
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fembedextractor.FembedExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
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 kotlinx.serialization.decodeFromString
@ -110,26 +114,79 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
.replace("https://sblanh.com", "https://watchsb.com")
.replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "")
if (link.contains("https://dood.")) {
try {
DoodExtractor(client).videoFromUrl(link, "DoodStream", false)!!.let { video ->
videoList.add(video)
}
} catch (_: Exception) {}
loadExtractor(link).let { videos ->
videoList.addAll(videos)
}
if (link.contains("fembed")) {
try {
FembedExtractor(client).videosFromUrl(link).map { video ->
videoList.add(video)
}
} catch (_: Exception) {}
}
if (link.contains("watchsb")) {
try {
StreamSBExtractor(client).videosFromUrl(link, headers).map { video -> videoList.add(video) }
} catch (_: Exception) {}
}
return videoList
}
private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} catch (e: Exception) { }
}
if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("sbembed.com") || embedUrl.contains("sbembed1.com") || embedUrl.contains("sbplay.org") ||
embedUrl.contains("sbvideo.net") || embedUrl.contains("streamsb.net") || embedUrl.contains("sbplay.one") ||
embedUrl.contains("cloudemb.com") || embedUrl.contains("playersb.com") || embedUrl.contains("tubesb.com") ||
embedUrl.contains("sbplay1.com") || embedUrl.contains("embedsb.com") || embedUrl.contains("watchsb.com") ||
embedUrl.contains("sbplay2.com") || embedUrl.contains("japopav.tv") || embedUrl.contains("viewsb.com") ||
embedUrl.contains("sbfast") || embedUrl.contains("sbfull.com") || embedUrl.contains("javplaya.com") ||
embedUrl.contains("ssbstream.net") || embedUrl.contains("p1ayerjavseen.com") || embedUrl.contains("sbthe.com") ||
embedUrl.contains("vidmovie.xyz") || embedUrl.contains("sbspeed.com") || embedUrl.contains("streamsss.net") ||
embedUrl.contains("sblanh.com") || embedUrl.contains("sbbrisk.com") || embedUrl.contains("lvturbo.com")
) {
runCatching {
StreamSBExtractor(client).videosFromUrl(url, headers, prefix = prefix)
}.getOrNull()?.let { videoList.addAll(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videoFromUrl(url, prefix)?.let {
videoList.addAll(it)
}
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url)?.let { videoList.add(it) }
}
return videoList
}

View File

@ -0,0 +1,28 @@
package eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class FilemoonExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, prefix: String = ""): MutableList<Video>? {
try {
val jsE = client.newCall(GET(url)).execute().asJsoup().selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker(jsE).unpack().toString()
.substringAfter("{file:\"").substringBefore("\"}")
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val videoList = mutableListOf<Video>()
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:")
.forEach {
val quality = "$prefix Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality.trim(), videoUrl))
}
return videoList
} catch (e: Exception) {
return null
}
}
}

View File

@ -0,0 +1,214 @@
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
try {
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()
var radix = 36
var count = 0
try {
radix = radixStr.toInt()
} catch (e: Exception) {
}
try {
count = countStr.toInt()
} catch (e: Exception) {
}
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()
}
} catch (e: Exception) {
}
return null
}
private inner class Unbase(private val radix: Int) {
private val ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val ALPHABET_95 =
" !\"#$%&\\'()*+,-./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 = ALPHABET_62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = ALPHABET_95.substring(0, radix)
}
radix == 62 -> {
alphabet = ALPHABET_62
}
radix == 95 -> {
alphabet = ALPHABET_95
}
}
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 {
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,
)
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
}
}
}
}
}