Pelisflix & Seriesflix sources (#756)

This commit is contained in:
miguelantonioe
2022-08-11 04:30:07 -05:00
committed by GitHub
parent 99d2b5aea4
commit bf56fe6a83
12 changed files with 559 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.animeextension" />

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Pelisflix'
pkgNameSuffix = 'es.pelisflix'
extClass = '.PelisflixFactory'
extVersionCode = 1
libVersion = '13'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,285 @@
package eu.kanade.tachiyomi.animeextension.es.pelisflix
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.lang.Exception
open class Pelisflix(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es"
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas-online/page/$page")
override fun popularAnimeSelector(): String = "#Tf-Wp div.TpRwCont ul.MovieList li.TPostMv article.TPost"
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.title = element.select("a h2.Title").text()
anime.thumbnail_url = externalOrInternalImg(element.selectFirst("a div.Image figure.Objf img.imglazy").attr("data-src"))
anime.description = element.select("div.TPMvCn div.Description p:nth-child(1)").text().removeSurrounding("\"")
return anime
}
override fun popularAnimeNextPageSelector(): String = "nav.wp-pagenavi div.nav-links a ~ a"
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val movie = document.select("ul.optnslst li div[data-url]")
val seasons = document.select("main .SeasonBx a")
if (movie.any()) {
val movieEp = SEpisode.create()
movieEp.name = "Pelicula"
movieEp.episode_number = 1f
movieEp.setUrlWithoutDomain(response.request.url.toString())
episodeList.add(movieEp)
} else if (seasons.any()) {
val seasonEp = extractEpisodesFromSeasons(seasons)
if (seasonEp.isNotEmpty()) episodeList.addAll(seasonEp)
}
return episodeList.reversed()
}
private fun extractEpisodesFromSeasons(seasons: Elements): List<SEpisode> {
val episodeList = mutableListOf<SEpisode>()
var noEp = 1f
var noTemp = 1
seasons!!.forEach {
var request = client.newCall(GET(it!!.attr("href"))).execute()
if (request.isSuccessful) {
val document = request.asJsoup()
document.select("div.TPTblCn table tbody tr.Viewed")!!.forEach { epContainer ->
val urlEp = epContainer.selectFirst("td.MvTbPly > a").attr("href")
val ep = SEpisode.create()
ep.name = "T$noTemp - Episodio $noEp"
ep.episode_number = noEp
ep.setUrlWithoutDomain(urlEp)
episodeList.add(ep)
noEp++
}
}
noTemp++
}
return episodeList
}
override fun episodeListSelector() = "uwu"
override fun episodeFromElement(element: Element) = throw Exception("not used")
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("div.TPost.A.D div.Container div.optns-bx div.drpdn button.bstd").forEach { serverList ->
val langTag = serverList.selectFirst("span").text()
val lang = if (langTag.contains("LATINO")) "LAT" else if (langTag.contains("CASTELLANO")) "CAST" else "SUB"
serverList.select("ul.optnslst li div[data-url]").forEach {
val encryptedUrl = it.attr("data-url")
val url = String(Base64.decode(encryptedUrl, Base64.DEFAULT))
if (url.contains("nupload")) {
nuploadExtractor(lang, url)!!.forEach { video -> videoList.add(video) }
}
}
}
return videoList
}
private fun nuploadExtractor(prefix: String, url: String): List<Video> {
val videoList = mutableListOf<Video>()
val request = client.newCall(GET(url)).execute()
if (request.isSuccessful) {
val document = request.asJsoup()
document.select("script").forEach { script ->
if (script!!.data().contains("var sesz=\"")) {
val key = script.data().substringAfter("var sesz=\"").substringBefore("\",")
var preUrl = script.data().substringAfter("file:\"").substringBefore("\"+")
var headersNupload = headers.newBuilder()
.set("authority", preUrl.substringAfter("https://").substringBefore("/"))
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("referer", "https://nupload.co/")
.set("dnt", "1")
.set("range", "bytes=0-")
.set("sec-ch-ua", "\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\"")
.set("sec-fetch-dest", "video")
.set("sec-fetch-mode", "no-cors")
.set("sec-fetch-site", "cross-site")
.set("sec-gpc", "1")
.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36")
.build()
videoList.add(Video(preUrl, "$prefix Nupload", preUrl + key, headers = headersNupload))
}
}
}
return videoList
}
override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "LAT Nupload")
if (quality != null) {
val newList = mutableListOf<Video>()
var preferred = 0
for (video in this) {
if (video.quality == quality) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
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/?s=$query&page=$page")
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page")
else -> popularAnimeRequest(page)
}
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter()
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("Peliculas", "peliculas-online"),
Pair("Series", "series-online"),
Pair("Estrenos", "genero/estrenos"),
Pair("Aventura", "genero/aventura"),
Pair("Acción", "genero/accion"),
Pair("Ciencia ficción", "genero/ciencia-ficcion"),
Pair("Comedia", "genero/comedia"),
Pair("Crimen", "genero/crimen"),
Pair("Drama", "genero/drama"),
Pair("Romance", "genero/romance"),
Pair("Terror", "genero/terror")
)
)
private 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 searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
var description = try {
document!!.selectFirst("article.TPost header.Container div.TPMvCn div.Description")
.text().removeSurrounding("\"")
.substringAfter("Online:")
.substringBefore("Recuerda ")
.substringBefore("Director:")
.trim()
} catch (e: IOException) {
document.selectFirst("article.TPost header div.TPMvCn div.Description p:nth-child(1)").text().removeSurrounding("\"").trim()
}
var title = try {
document.selectFirst("article.TPost header.Container div.TPMvCn h1.Title").text().removePrefix("Serie").trim()
} catch (e: IOException) {
document.selectFirst("article.TPost header.Container div.TPMvCn a h1.Title").text().removePrefix("Serie").trim()
}
anime.title = title
anime.description = description
anime.genre = document.select("article.TPost header.Container div.TPMvCn div.Description p.Genre a").joinToString { it.text().replace(",", "") }
anime.status = SAnime.UNKNOWN
return anime
}
private fun externalOrInternalImg(url: String): String {
return if (url.contains("https")) url else "$baseUrl/$url"
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("En emision") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf("LAT Nupload", "CAST Nupload", "SUB Nupload")
entryValues = arrayOf("LAT Nupload", "CAST Nupload", "SUB Nupload")
setDefaultValue("LAT Nupload")
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

@ -0,0 +1,171 @@
package eu.kanade.tachiyomi.animeextension.es.pelisflix
import eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors.DoodExtractor
import eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors.FembedExtractor
import eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors.StreamTapeExtractor
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
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.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Element
class PelisflixFactory : AnimeSourceFactory {
override fun createSources(): List<AnimeSource> = listOf(PelisflixClass(), SeriesflixClass())
}
class PelisflixClass : Pelisflix("Pelisflix", "https://pelisflix.app")
class SeriesflixClass : Pelisflix("Seriesflix", "https://seriesflix.video") {
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ver-series-online/page/$page")
override fun popularAnimeSelector() = "li[id*=post-] > article"
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.title = element.select("a h2.Title").text()
anime.thumbnail_url = element.selectFirst("a div.Image figure.Objf img").attr("src")
anime.description = element.select("div.TPMvCn div.Description p:nth-child(1)").text().removeSurrounding("\"")
return anime
}
private fun loadVideoSources(urlResponse: String, lang: String): List<Video> {
val videoList = mutableListOf<Video>()
fetchUrls(urlResponse).map { serverUrl ->
if (serverUrl.contains("fembed") || serverUrl.contains("vanfem")) {
FembedExtractor().videosFromUrl(serverUrl, lang)!!.map { video ->
videoList.add(video)
}
}
if (serverUrl.contains("doodstream")) {
val video = DoodExtractor(client).videoFromUrl(serverUrl.replace("https://doodstream.com", "https://dood.wf"), lang)
if (video != null) videoList.add(video)
}
if (serverUrl.contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(serverUrl, "$lang StreamTape")
if (video != null) videoList.add(video)
}
}
return videoList
}
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 videoList = mutableListOf<Video>()
document.select("ul.ListOptions li").forEach { serverList ->
val movieID = serverList.attr("data-id")
val serverID = serverList.attr("data-key")
val type = if (response.request.url.toString().contains("movies")) 1 else 2
val url = "$baseUrl/?trembed=$serverID&trid=$movieID&trtype=$type"
val langTag = serverList.selectFirst("p.AAIco-language").text().uppercase()
val lang = if (langTag.contains("LATINO")) "LAT" else if (langTag.contains("CASTELLANO")) "CAST" else "SUB"
var request = client.newCall(GET(url)).execute()
if (request.isSuccessful) {
val serverLinks = request.asJsoup()
serverLinks.select("div.Video iframe").map {
val iframe = it.attr("src")
if (iframe.contains("https://sc.seriesflix.video/index.php")) {
val postKey = iframe.replace("https://sc.seriesflix.video/index.php?h=", "")
val mediaType = ("application/x-www-form-urlencoded").toMediaType()
val body: RequestBody = "h=$postKey".toRequestBody(mediaType)
val newClient = OkHttpClient().newBuilder().build()
val requestServer = Request.Builder()
.url("https://sc.seriesflix.video/r.php?h=$postKey")
.method("POST", body)
.addHeader("Host", "sc.seriesflix.video")
.addHeader(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
)
.addHeader(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
)
.addHeader("Accept-Language", "en-US,en;q=0.5")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Origin", "null")
.addHeader("DNT", "1")
.addHeader("Connection", "keep-alive")
.addHeader("Upgrade-Insecure-Requests", "1")
.addHeader("Sec-Fetch-Dest", "iframe")
.addHeader("Sec-Fetch-Mode", "no-cors")
.addHeader("sec-fetch-site", "same-origin")
.addHeader("Sec-Fetch-User", "?1")
.addHeader("Alt-Used", "sc.seriesflix.video")
.addHeader("Access-Control-Allow-Methods", "POST")
.build()
val document = newClient.newCall(requestServer).execute()
val urlResponse = document!!.networkResponse!!.toString()
loadVideoSources(urlResponse, lang)!!.forEach { source ->
videoList.add(source)
}
} else {
loadVideoSources(iframe, lang)
}
}
}
}
return videoList
}
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/?s=$query&page=$page")
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page")
else -> popularAnimeRequest(page)
}
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter()
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("Acción", "genero/accion"),
Pair("Animación", "genero/animacion"),
Pair("Anime", "genero/anime"),
Pair("Antiguas", "genero/series-antiguas"),
Pair("Aventura", "genero/aventura"),
Pair("Ciencia ficción", "genero/ciencia-ficcion"),
Pair("Comedia", "genero/comedia"),
Pair("Crimen", "genero/crimen"),
Pair("DC Comics", "genero/dc-comics"),
Pair("Drama", "genero/drama"),
Pair("Dorama", "genero/dorama"),
Pair("Estrenos", "genero/estrenos"),
Pair("Fantasía", "genero/fantasia"),
Pair("Misterio", "genero/misterio"),
Pair("Romance", "genero/romance"),
Pair("Terror", "genero/terror")
)
)
private 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
}
}

View File

@ -0,0 +1,41 @@
package eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
import java.io.IOException
class DoodExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, qualityPrefix: String = ""): Video? {
val response = client.newCall(GET(url)).execute()
val doodTld = url.substringAfter("https://dood.").substringBefore("/")
val content = response.body!!.string()
if (!content.contains("'/pass_md5/")) return null
val quality = try { "\\d{3,4}p".toRegex().find(content)!!.value.trim() } catch (e: IOException) { "" }
val md5 = content.substringAfter("'/pass_md5/").substringBefore("',")
val token = md5.substringAfterLast("/")
val randomString = getRandomString()
val expiry = System.currentTimeMillis()
val videoUrlStart = client.newCall(
GET(
"https://dood.$doodTld/pass_md5/$md5",
Headers.headersOf("referer", url)
)
).execute().body!!.string()
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
return Video(url, "$qualityPrefix Doodstream:$quality", videoUrl, headers = doodHeaders(doodTld))
}
private fun getRandomString(length: Int = 10): String {
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
return (1..length)
.map { allowedChars.random() }
.joinToString("")
}
private fun doodHeaders(tld: String) = Headers.Builder().apply {
add("User-Agent", "Aniyomi")
add("Referer", "https://dood.$tld/")
}.build()
}

View File

@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import org.json.JSONObject
import org.jsoup.Connection
import org.jsoup.Jsoup
class FembedExtractor {
fun videosFromUrl(url: String, qualityPrefix: String = ""): List<Video> {
var videoApi = url.replace("/v/", "/api/source/")
var json = JSONObject(Jsoup.connect(videoApi).ignoreContentType(true).method(Connection.Method.POST).execute().body())
val videoList = mutableListOf<Video>()
if (json.getBoolean("success")) {
val videoList = mutableListOf<Video>()
val jsonArray = json.getJSONArray("data")
for (i in 0 until jsonArray.length()) {
val `object` = jsonArray.getJSONObject(i)
val videoUrl = `object`.getString("file")
val quality = "$qualityPrefix Fembed:" + `object`.getString("label")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList
} else {
val videoUrl = "not used"
val quality = "Video taken down for dmca"
videoList.add(Video(videoUrl, quality, videoUrl))
}
return videoList
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.tachiyomi.animeextension.es.pelisflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class StreamTapeExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String = "StreamTape"): Video? {
val document = client.newCall(GET(url)).execute().asJsoup()
val script = document.select("script:containsData(document.getElementById('robotlink'))")
.firstOrNull()?.data()?.substringAfter("document.getElementById('robotlink').innerHTML = '")
?: return null
val videoUrl = "https:" + script.substringBefore("'") +
script.substringAfter("+ ('xcd").substringBefore("'")
return Video(url, quality, videoUrl)
}
}