diff --git a/AnimefenixProvider/build.gradle.kts b/AnimefenixProvider/build.gradle.kts new file mode 100644 index 0000000..0266db8 --- /dev/null +++ b/AnimefenixProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "OVA", + "Anime", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animefenix.com&sz=24" +} \ No newline at end of file diff --git a/AnimefenixProvider/src/main/AndroidManifest.xml b/AnimefenixProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AnimefenixProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProvider.kt b/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProvider.kt new file mode 100644 index 0000000..203fa8d --- /dev/null +++ b/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProvider.kt @@ -0,0 +1,249 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.Jsoup +import java.util.* + + +class AnimefenixProvider:MainAPI() { + + override var mainUrl = "https://animefenix.com" + override var name = "Animefenix" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + fun getDubStatus(title: String): DubStatus { + return if (title.contains("Latino") || title.contains("Castellano")) + DubStatus.Dubbed + else DubStatus.Subbed + } + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/", "Animes"), + Pair("$mainUrl/animes?type[]=movie&order=default", "Peliculas", ), + Pair("$mainUrl/animes?type[]=ova&order=default", "OVA's", ), + ) + + val items = ArrayList() + + items.add( + HomePageList( + "Últimos episodios", + app.get(mainUrl).document.select(".capitulos-grid div.item").map { + val title = it.selectFirst("div.overtitle")?.text() + val poster = it.selectFirst("a img")?.attr("src") + val epRegex = Regex("(-(\\d+)\$|-(\\d+)\\.(\\d+))") + val url = it.selectFirst("a")?.attr("href")?.replace(epRegex,"") + ?.replace("/ver/","/") + val epNum = it.selectFirst(".is-size-7")?.text()?.replace("Episodio ","")?.toIntOrNull() + newAnimeSearchResponse(title!!, url!!) { + this.posterUrl = poster + addDubStatus(getDubStatus(title), epNum) + } + }) + ) + + urls.apmap { (url, name) -> + val response = app.get(url) + val soup = Jsoup.parse(response.text) + val home = soup.select(".list-series article").map { + val title = it.selectFirst("h3 a")?.text() + val poster = it.selectFirst("figure img")?.attr("src") + AnimeSearchResponse( + title!!, + it.selectFirst("a")?.attr("href") ?: "", + this.name, + TvType.Anime, + poster, + null, + if (title.contains("Latino")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed), + ) + } + + items.add(HomePageList(name, home)) + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/animes?q=$query").document.select(".list-series article").map { + val title = it.selectFirst("h3 a")?.text() + val href = it.selectFirst("a")?.attr("href") + val image = it.selectFirst("figure img")?.attr("src") + AnimeSearchResponse( + title!!, + href!!, + this.name, + TvType.Anime, + fixUrl(image ?: ""), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of( + DubStatus.Subbed), + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = Jsoup.parse(app.get(url, timeout = 120).text) + val poster = doc.selectFirst(".image > img")?.attr("src") + val title = doc.selectFirst("h1.title.has-text-orange")?.text() + val description = doc.selectFirst("p.has-text-light")?.text() + val genres = doc.select(".genres a").map { it.text() } + val status = when (doc.selectFirst(".is-narrow-desktop a.button")?.text()) { + "Emisión" -> ShowStatus.Ongoing + "Finalizado" -> ShowStatus.Completed + else -> null + } + val episodes = doc.select(".anime-page__episode-list li").map { + val name = it.selectFirst("span")?.text() + val link = it.selectFirst("a")?.attr("href") + Episode(link!!, name) + }.reversed() + val type = if (doc.selectFirst("ul.has-text-light")?.text() + !!.contains("Película") && episodes.size == 1 + ) TvType.AnimeMovie else TvType.Anime + return newAnimeLoadResponse(title!!, url, type) { + japName = null + engName = title + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + plot = description + tags = genres + showStatus = status + } + } + + private fun cleanStreamID(input: String): String = input.replace(Regex("player=.*&code=|&"),"") + + data class Amazon ( + @JsonProperty("file") var file : String? = null, + @JsonProperty("type") var type : String? = null, + @JsonProperty("label") var label : String? = null + ) + + private fun cleanExtractor( + source: String, + name: String, + url: String, + callback: (ExtractorLink) -> Unit + ): Boolean { + callback( + ExtractorLink( + source, + name, + url, + "", + Qualities.Unknown.value, + false + ) + ) + return true + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val soup = app.get(data).document + val script = soup.selectFirst(".player-container script")?.data() + if (script!!.contains("var tabsArray =")) { + val sourcesRegex = Regex("player=.*&code(.*)&") + val test = sourcesRegex.findAll(script).toList() + test.apmap { + val codestream = it.value + val links = when { + codestream.contains("player=2&") -> "https://embedsito.com/v/"+cleanStreamID(codestream) + codestream.contains("player=3&") -> "https://www.mp4upload.com/embed-"+cleanStreamID(codestream)+".html" + codestream.contains("player=6&") -> "https://www.yourupload.com/embed/"+cleanStreamID(codestream) + codestream.contains("player=12&") -> "http://ok.ru/videoembed/"+cleanStreamID(codestream) + codestream.contains("player=4&") -> "https://sendvid.com/"+cleanStreamID(codestream) + codestream.contains("player=9&") -> "AmaNormal https://www.animefenix.com/stream/amz.php?v="+cleanStreamID(codestream) + codestream.contains("player=11&") -> "AmazonES https://www.animefenix.com/stream/amz.php?v="+cleanStreamID(codestream) + codestream.contains("player=22&") -> "Fireload https://www.animefenix.com/stream/fl.php?v="+cleanStreamID(codestream) + + else -> "" + } + loadExtractor(links, data, subtitleCallback, callback) + + argamap({ + if (links.contains("AmaNormal")) { + val doc = app.get(links.replace("AmaNormal ","")).document + doc.select("script").map { script -> + if (script.data().contains("sources: [{\"file\"")) { + val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","") + val json = parseJson(text) + if (json.file != null) { + cleanExtractor( + "Amazon", + "Amazon ${json.label}", + json.file!!, + callback + ) + } + } + } + } + + if (links.contains("AmazonES")) { + val amazonES = links.replace("AmazonES ", "") + val doc = app.get("$amazonES&ext=es").document + doc.select("script").map { script -> + if (script.data().contains("sources: [{\"file\"")) { + val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","") + val json = parseJson(text) + if (json.file != null) { + cleanExtractor( + "AmazonES", + "AmazonES ${json.label}", + json.file!!, + callback + ) + } + } + } + } + if (links.contains("Fireload")) { + val doc = app.get(links.replace("Fireload ", "")).document + doc.select("script").map { script -> + if (script.data().contains("sources: [{\"file\"")) { + val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","") + val json = parseJson(text) + val testurl = if (json.file?.contains("fireload") == true) { + app.get("https://${json.file}").text + } else null + if (testurl?.contains("error") == true) { + // + } else if (json.file?.contains("fireload") == true) { + cleanExtractor( + "Fireload", + "Fireload ${json.label}", + "https://"+json.file!!, + callback + ) + } + } + } + } + }) + } + } + return true + } +} \ No newline at end of file diff --git a/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProviderPlugin.kt b/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProviderPlugin.kt new file mode 100644 index 0000000..459bd29 --- /dev/null +++ b/AnimefenixProvider/src/main/kotlin/com/lagradost/AnimefenixProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimefenixProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AnimefenixProvider()) + } +} \ No newline at end of file diff --git a/AnimeflvIOProvider/build.gradle.kts b/AnimeflvIOProvider/build.gradle.kts new file mode 100644 index 0000000..67fe108 --- /dev/null +++ b/AnimeflvIOProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animeflv.io&sz=24" +} \ No newline at end of file diff --git a/AnimeflvIOProvider/src/main/AndroidManifest.xml b/AnimeflvIOProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AnimeflvIOProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProvider.kt b/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProvider.kt new file mode 100644 index 0000000..5b74053 --- /dev/null +++ b/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProvider.kt @@ -0,0 +1,239 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + +class AnimeflvIOProvider : MainAPI() { + override var mainUrl = "https://animeflv.io" //Also scrapes from animeid.to + override var name = "Animeflv.io" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair("$mainUrl/series", "Series actualizadas"), + Pair("$mainUrl/peliculas", "Peliculas actualizadas"), + ) + items.add( + HomePageList( + "Estrenos", + app.get(mainUrl).document.select("div#owl-demo-premiere-movies .pull-left").map { + val title = it.selectFirst("p")?.text() ?: "" + AnimeSearchResponse( + title, + fixUrl(it.selectFirst("a")?.attr("href") ?: ""), + this.name, + TvType.Anime, + it.selectFirst("img")?.attr("src"), + it.selectFirst("span.year").toString().toIntOrNull(), + EnumSet.of(DubStatus.Subbed), + ) + }) + ) + urls.apmap { (url, name) -> + val soup = app.get(url).document + val home = soup.select("div.item-pelicula").map { + val title = it.selectFirst(".item-detail p")?.text() ?: "" + val poster = it.selectFirst("figure img")?.attr("src") + AnimeSearchResponse( + title, + fixUrl(it.selectFirst("a")?.attr("href") ?: ""), + this.name, + TvType.Anime, + poster, + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + + items.add(HomePageList(name, home)) + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val headers = mapOf( + "Host" to "animeflv.io", + "User-Agent" to USER_AGENT, + "X-Requested-With" to "XMLHttpRequest", + "DNT" to "1", + "Alt-Used" to "animeflv.io", + "Connection" to "keep-alive", + "Referer" to "https://animeflv.io", + ) + val url = "$mainUrl/search.html?keyword=$query" + val document = app.get( + url, + headers = headers + ).document + return document.select(".item-pelicula.pull-left").map { + val title = it.selectFirst("div.item-detail p")?.text() ?: "" + val href = fixUrl(it.selectFirst("a")?.attr("href") ?: "") + var image = it.selectFirst("figure img")?.attr("src") ?: "" + val isMovie = href.contains("/pelicula/") + if (image.contains("/static/img/picture.png")) { + image = "" + } + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.AnimeMovie, + image, + null + ) + } else { + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + image, + null, + EnumSet.of(DubStatus.Subbed), + ) + } + } + } + + override suspend fun load(url: String): LoadResponse? { + // Gets the url returned from searching. + val soup = app.get(url).document + val title = soup.selectFirst(".info-content h1")?.text() + val description = soup.selectFirst("span.sinopsis")?.text()?.trim() + val poster: String? = soup.selectFirst(".poster img")?.attr("src") + val episodes = soup.select(".item-season-episodes a").map { li -> + val href = fixUrl(li.selectFirst("a")?.attr("href") ?: "") + val name = li.selectFirst("a")?.text() ?: "" + Episode( + href, name, + ) + }.reversed() + + val year = Regex("(\\d*)").find(soup.select(".info-half").text()) + + val tvType = if (url.contains("/pelicula/")) TvType.AnimeMovie else TvType.Anime + val genre = soup.select(".content-type-a a") + .map { it?.text()?.trim().toString().replace(", ", "") } + val duration = Regex("""(\d*)""").find( + soup.select("p.info-half:nth-child(4)").text() + ) + + return when (tvType) { + TvType.Anime -> { + return newAnimeLoadResponse(title ?: "", url, tvType) { + japName = null + engName = title + posterUrl = poster + this.year = null + addEpisodes(DubStatus.Subbed, episodes) + plot = description + tags = genre + + showStatus = null + } + } + TvType.AnimeMovie -> { + MovieLoadResponse( + title ?: "", + url, + this.name, + tvType, + url, + poster, + year.toString().toIntOrNull(), + description, + null, + genre, + duration.toString().toIntOrNull(), + ) + } + else -> null + } + } + + data class MainJson( + @JsonProperty("source") val source: List, + @JsonProperty("source_bk") val sourceBk: String?, + @JsonProperty("track") val track: List?, + @JsonProperty("advertising") val advertising: List?, + @JsonProperty("linkiframe") val linkiframe: String? + ) + + data class Source( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + @JsonProperty("default") val default: String, + @JsonProperty("type") val type: String + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("li.tab-video").apmap { + val url = fixUrl(it.attr("data-video")) + if (url.contains("animeid")) { + val ajaxurl = url.replace("streaming.php", "ajax.php") + val ajaxurltext = app.get(ajaxurl).text + val json = parseJson(ajaxurltext) + json.source.forEach { source -> + if (source.file.contains("m3u8")) { + generateM3u8( + "Animeflv.io", + source.file, + "https://animeid.to", + headers = mapOf("Referer" to "https://animeid.to") + ).apmap { + callback( + ExtractorLink( + "Animeflv.io", + "Animeflv.io", + it.url, + "https://animeid.to", + getQualityFromName(it.quality.toString()), + it.url.contains("m3u8") + ) + ) + } + } else { + callback( + ExtractorLink( + name, + "$name ${source.label}", + source.file, + "https://animeid.to", + Qualities.Unknown.value, + isM3u8 = source.file.contains("m3u8") + ) + ) + } + } + } + loadExtractor(url, data, subtitleCallback, callback) + } + return true + } +} \ No newline at end of file diff --git a/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProviderPlugin.kt b/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProviderPlugin.kt new file mode 100644 index 0000000..f0fa600 --- /dev/null +++ b/AnimeflvIOProvider/src/main/kotlin/com/lagradost/AnimeflvIOProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeflvIOProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AnimeflvIOProvider()) + } +} \ No newline at end of file diff --git a/AnimeflvnetProvider/build.gradle.kts b/AnimeflvnetProvider/build.gradle.kts new file mode 100644 index 0000000..79a6d05 --- /dev/null +++ b/AnimeflvnetProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www3.animeflv.net&sz=24" +} \ No newline at end of file diff --git a/AnimeflvnetProvider/src/main/AndroidManifest.xml b/AnimeflvnetProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AnimeflvnetProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProvider.kt b/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProvider.kt new file mode 100644 index 0000000..c30076f --- /dev/null +++ b/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProvider.kt @@ -0,0 +1,182 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + +class AnimeflvnetProvider : MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA + else if (t.contains("Película")) TvType.AnimeMovie + else TvType.Anime + } + + fun getDubStatus(title: String): DubStatus { + return if (title.contains("Latino") || title.contains("Castellano")) + DubStatus.Dubbed + else DubStatus.Subbed + } + } + + override var mainUrl = "https://www3.animeflv.net" + override var name = "Animeflv.net" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/browse?type[]=movie&order=updated", "Películas"), + Pair("$mainUrl/browse?status[]=2&order=default", "Animes"), + Pair("$mainUrl/browse?status[]=1&order=rating", "En emision"), + ) + val items = ArrayList() + items.add( + HomePageList( + "Últimos episodios", + app.get(mainUrl).document.select("main.Main ul.ListEpisodios li").mapNotNull { + val title = it.selectFirst("strong.Title")?.text() ?: return@mapNotNull null + val poster = it.selectFirst("span img")?.attr("src") ?: return@mapNotNull null + val epRegex = Regex("(-(\\d+)\$)") + val url = it.selectFirst("a")?.attr("href")?.replace(epRegex, "") + ?.replace("ver/", "anime/") ?: return@mapNotNull null + val epNum = + it.selectFirst("span.Capi")?.text()?.replace("Episodio ", "")?.toIntOrNull() + newAnimeSearchResponse(title, url) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title), epNum) + } + }) + ) + for ((url, name) in urls) { + try { + val doc = app.get(url).document + val home = doc.select("ul.ListAnimes li article").mapNotNull { + val title = it.selectFirst("h3.Title")?.text() ?: return@mapNotNull null + val poster = it.selectFirst("figure img")?.attr("src") ?: return@mapNotNull null + newAnimeSearchResponse( + title, + fixUrl(it.selectFirst("a")?.attr("href") ?: return@mapNotNull null) + ) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title)) + } + } + + items.add(HomePageList(name, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + data class SearchObject( + @JsonProperty("id") val id: String, + @JsonProperty("title") val title: String, + @JsonProperty("type") val type: String, + @JsonProperty("last_id") val lastId: String, + @JsonProperty("slug") val slug: String + ) + + override suspend fun search(query: String): List { + val response = app.post( + "https://www3.animeflv.net/api/animes/search", + data = mapOf(Pair("value", query)) + ).text + val json = parseJson>(response) + return json.map { searchr -> + val title = searchr.title + val href = "$mainUrl/anime/${searchr.slug}" + val image = "$mainUrl/uploads/animes/covers/${searchr.id}.jpg" + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + fixUrl(image), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of( + DubStatus.Subbed + ), + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url).document + val episodes = ArrayList() + val title = doc.selectFirst("h1.Title")!!.text() + val poster = doc.selectFirst("div.AnimeCover div.Image figure img")?.attr("src")!! + val description = doc.selectFirst("div.Description p")?.text() + val type = doc.selectFirst("span.Type")?.text() ?: "" + val status = when (doc.selectFirst("p.AnmStts span")?.text()) { + "En emision" -> ShowStatus.Ongoing + "Finalizado" -> ShowStatus.Completed + else -> null + } + val genre = doc.select("nav.Nvgnrs a") + .map { it?.text()?.trim().toString() } + + doc.select("script").map { script -> + if (script.data().contains("var episodes = [")) { + val data = script.data().substringAfter("var episodes = [").substringBefore("];") + data.split("],").forEach { + val epNum = it.removePrefix("[").substringBefore(",") + // val epthumbid = it.removePrefix("[").substringAfter(",").substringBefore("]") + val animeid = doc.selectFirst("div.Strs.RateIt")?.attr("data-id") + val epthumb = "https://cdn.animeflv.net/screenshots/$animeid/$epNum/th_3.jpg" + val link = url.replace("/anime/", "/ver/") + "-$epNum" + episodes.add( + Episode( + link, + null, + posterUrl = epthumb, + episode = epNum.toIntOrNull() + ) + ) + } + } + } + return newAnimeLoadResponse(title, url, getType(type)) { + posterUrl = fixUrl(poster) + addEpisodes(DubStatus.Subbed, episodes.reversed()) + showStatus = status + plot = description + tags = genre + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("script").apmap { script -> + if (script.data().contains("var videos = {") || script.data() + .contains("var anime_id =") || script.data().contains("server") + ) { + val videos = script.data().replace("\\/", "/") + fetchUrls(videos).map { + it.replace("https://embedsb.com/e/", "https://watchsb.com/e/") + .replace("https://ok.ru", "http://ok.ru") + }.apmap { + loadExtractor(it, data, subtitleCallback, callback) + } + } + } + return true + } +} diff --git a/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProviderPlugin.kt b/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProviderPlugin.kt new file mode 100644 index 0000000..9f45880 --- /dev/null +++ b/AnimeflvnetProvider/src/main/kotlin/com/lagradost/AnimeflvnetProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeflvnetProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AnimeflvnetProvider()) + } +} \ No newline at end of file diff --git a/CinecalidadProvider/build.gradle.kts b/CinecalidadProvider/build.gradle.kts new file mode 100644 index 0000000..1f46c5d --- /dev/null +++ b/CinecalidadProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=cinecalidad.lol&sz=24" +} \ No newline at end of file diff --git a/CinecalidadProvider/src/main/AndroidManifest.xml b/CinecalidadProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/CinecalidadProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProvider.kt b/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProvider.kt new file mode 100644 index 0000000..fbeca40 --- /dev/null +++ b/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProvider.kt @@ -0,0 +1,258 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +// import com.lagradost.cloudstream3.extractors.Cinestart +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class CinecalidadProvider : MainAPI() { + override var mainUrl = "https://cinecalidad.lol" + override var name = "Cinecalidad" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading + + override val mainPage = mainPageOf( + Pair("$mainUrl/ver-serie/page/", "Series"), + Pair("$mainUrl/page/", "Peliculas"), + Pair("$mainUrl/genero-de-la-pelicula/peliculas-en-calidad-4k/page/", "4K UHD"), + ) + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse { + val url = request.data + page + + val soup = app.get(url).document + val home = soup.select(".item.movies").map { + val title = it.selectFirst("div.in_title")!!.text() + val link = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries, + it.selectFirst(".poster.custom img")!!.attr("data-src"), + null, + null, + ) + } + + return newHomePageResponse(request.name, home) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=${query}" + val document = app.get(url).document + + return document.select("article").map { + val title = it.selectFirst("div.in_title")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst(".poster.custom img")!!.attr("data-src") + val isMovie = href.contains("/ver-pelicula/") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + } + } + + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + + val title = soup.selectFirst(".single_left h1")!!.text() + val description = soup.selectFirst("div.single_left table tbody tr td p")?.text()?.trim() + val poster: String? = soup.selectFirst(".alignnone")!!.attr("data-src") + val episodes = soup.select("div.se-c div.se-a ul.episodios li").map { li -> + val href = li.selectFirst("a")!!.attr("href") + val epThumb = li.selectFirst("img.lazy")!!.attr("data-src") + val name = li.selectFirst(".episodiotitle a")!!.text() + val seasonid = + li.selectFirst(".numerando")!!.text().replace(Regex("(S|E)"), "").let { str -> + str.split("-").mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid.size == 2 + val episode = if (isValid) seasonid.getOrNull(1) else null + val season = if (isValid) seasonid.getOrNull(0) else null + Episode( + href, + name, + season, + episode, + if (epThumb.contains("svg")) null else epThumb + ) + } + return when (val tvType = + if (url.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + null, + description, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + null, + description, + ) + } + else -> null + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val datam = app.get(data) + val doc = datam.document + val datatext = datam.text + + doc.select(".dooplay_player_option").apmap { + val url = it.attr("data-option") +// if (url.startsWith("https://cinestart.net")) { +// val extractor = Cinestart() +// extractor.getSafeUrl(url, null, subtitleCallback, callback) +// } else { + loadExtractor(url, mainUrl, subtitleCallback, callback) +// } + if (url.startsWith("https://cinecalidad.lol")) { + val cineurlregex = + Regex("(https:\\/\\/cinecalidad\\.lol\\/play\\/\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + cineurlregex.findAll(url).map { + it.value.replace("/play/", "/play/r.php") + }.toList().apmap { + app.get( + it, + headers = mapOf( + "Host" to "cinecalidad.lol", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "DNT" to "1", + "Connection" to "keep-alive", + "Referer" to data, + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "Sec-Fetch-User" to "?1", + ), + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { extractedurl -> + if (extractedurl.contains("cinestart")) { + loadExtractor(extractedurl, mainUrl, subtitleCallback, callback) + } + } + } + } + } + if (datatext.contains("en castellano")) app.get("$data?ref=es").document.select(".dooplay_player_option") + .apmap { + val url = it.attr("data-option") +// if (url.startsWith("https://cinestart.net")) { +// val extractor = Cinestart() +// extractor.getSafeUrl(url, null, subtitleCallback, callback) +// } else { + loadExtractor(url, mainUrl, subtitleCallback, callback) +// } + + if (url.startsWith("https://cinecalidad.lol")) { + val cineurlregex = + Regex("(https:\\/\\/cinecalidad\\.lol\\/play\\/\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + cineurlregex.findAll(url).map { + it.value.replace("/play/", "/play/r.php") + }.toList().apmap { + app.get( + it, + headers = mapOf( + "Host" to "cinecalidad.lol", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "DNT" to "1", + "Connection" to "keep-alive", + "Referer" to data, + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "Sec-Fetch-User" to "?1", + ), + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { extractedurl -> + if (extractedurl.contains("cinestart")) { + loadExtractor(extractedurl, mainUrl, subtitleCallback, callback) + } + } + } + } + } + if (datatext.contains("Subtítulo LAT") || datatext.contains("Forzados LAT")) { + doc.select("#panel_descarga.pane a").apmap { + val link = + if (data.contains("serie") || data.contains("episodio")) "${data}${it.attr("href")}" + else it.attr("href") + val docsub = app.get(link) + val linksub = docsub.document + val validsub = docsub.text + if (validsub.contains("Subtítulo") || validsub.contains("Forzados")) { + val langregex = Regex("(Subtítulo.*\$|Forzados.*\$)") + val langdoc = linksub.selectFirst("div.titulo h3")!!.text() + val reallang = langregex.find(langdoc)?.destructured?.component1() + linksub.select("a.link").apmap { + val sublink = + if (data.contains("serie") || data.contains("episodio")) "${data}${ + it.attr("href") + }" + else it.attr("href") + subtitleCallback( + SubtitleFile(reallang!!, sublink) + ) + } + } + } + } + return true + } +} diff --git a/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProviderPlugin.kt b/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProviderPlugin.kt new file mode 100644 index 0000000..cf4a2ca --- /dev/null +++ b/CinecalidadProvider/src/main/kotlin/com/lagradost/CinecalidadProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CinecalidadProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(CinecalidadProvider()) + } +} \ No newline at end of file diff --git a/CuevanaProvider/build.gradle.kts b/CuevanaProvider/build.gradle.kts new file mode 100644 index 0000000..f8ef81e --- /dev/null +++ b/CuevanaProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=cuevana3.me&sz=24" +} \ No newline at end of file diff --git a/CuevanaProvider/src/main/AndroidManifest.xml b/CuevanaProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/CuevanaProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProvider.kt b/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProvider.kt new file mode 100644 index 0000000..9aab603 --- /dev/null +++ b/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProvider.kt @@ -0,0 +1,324 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class CuevanaProvider : MainAPI() { + override var mainUrl = "https://cuevana3.me" + override var name = "Cuevana" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair(mainUrl, "Recientemente actualizadas"), + Pair("$mainUrl/estrenos/", "Estrenos"), + ) + items.add( + HomePageList( + "Series", + app.get("$mainUrl/serie", timeout = 120).document.select("section.home-series li") + .map { + val title = it.selectFirst("h2.Title")!!.text() + val poster = it.selectFirst("img.lazy")!!.attr("data-src") + val url = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + url, + this.name, + TvType.Anime, + poster, + null, + null, + ) + }) + ) + for ((url, name) in urls) { + try { + val soup = app.get(url).document + val home = soup.select("section li.xxx.TPostMv").map { + val title = it.selectFirst("h2.Title")!!.text() + val link = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("/pelicula/")) TvType.Movie else TvType.TvSeries, + it.selectFirst("img.lazy")!!.attr("data-src"), + null, + null, + ) + } + + items.add(HomePageList(name, home)) + } catch (e: Exception) { + logError(e) + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=${query}" + val document = app.get(url).document + + return document.select("li.xxx.TPostMv").map { + val title = it.selectFirst("h2.Title")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst("img.lazy")!!.attr("data-src") + val isSerie = href.contains("/serie/") + + if (isSerie) { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } else { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } + } + } + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + val title = soup.selectFirst("h1.Title")!!.text() + val description = soup.selectFirst(".Description p")?.text()?.trim() + val poster: String? = soup.selectFirst(".movtv-info div.Image img")!!.attr("data-src") + val year1 = soup.selectFirst("footer p.meta").toString() + val yearRegex = Regex("(\\d+)") + val yearf = + yearRegex.find(year1)?.destructured?.component1()?.replace(Regex("|"), "") + val year = if (yearf.isNullOrBlank()) null else yearf.toIntOrNull() + val episodes = soup.select(".all-episodes li.TPostMv article").map { li -> + val href = li.select("a").attr("href") + val epThumb = + li.selectFirst("div.Image img")?.attr("data-src") ?: li.selectFirst("img.lazy")!! + .attr("data-srcc") + val seasonid = li.selectFirst("span.Year")!!.text().let { str -> + str.split("x").mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid.size == 2 + val episode = if (isValid) seasonid.getOrNull(1) else null + val season = if (isValid) seasonid.getOrNull(0) else null + Episode( + href, + null, + season, + episode, + fixUrl(epThumb) + ) + } + val tags = soup.select("ul.InfoList li.AAIco-adjust:contains(Genero) a").map { it.text() } + val tvType = if (episodes.isEmpty()) TvType.Movie else TvType.TvSeries + val recelement = + if (tvType == TvType.TvSeries) "main section div.series_listado.series div.xxx" + else "main section ul.MovieList li" + val recommendations = + soup.select(recelement).mapNotNull { element -> + val recTitle = element.select("h2.Title").text() ?: return@mapNotNull null + val image = element.select("figure img")?.attr("data-src") + val recUrl = fixUrl(element.select("a").attr("href")) + MovieSearchResponse( + recTitle, + recUrl, + this.name, + TvType.Movie, + image, + year = null + ) + } + + return when (tvType) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + year, + description, + tags = tags, + recommendations = recommendations + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + year, + description, + tags = tags, + recommendations = recommendations + ) + } + else -> null + } + } + + data class Femcuevana( + @JsonProperty("url") val url: String, + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("div.TPlayer.embed_div iframe").apmap { + val iframe = fixUrl(it.attr("data-src")) + if (iframe.contains("api.cuevana3.me/fembed/")) { + val femregex = + Regex("(https.\\/\\/api\\.cuevana3\\.me\\/fembed\\/\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + femregex.findAll(iframe).map { femreg -> + femreg.value + }.toList().apmap { fem -> + val key = fem.replace("https://api.cuevana3.me/fembed/?h=", "") + val url = app.post( + "https://api.cuevana3.me/fembed/api.php", + allowRedirects = false, + headers = mapOf( + "Host" to "api.cuevana3.me", + "User-Agent" to USER_AGENT, + "Accept" to "application/json, text/javascript, */*; q=0.01", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest", + "Origin" to "https://api.cuevana3.me", + "DNT" to "1", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "same-origin", + ), + data = mapOf(Pair("h", key)) + ).text + val json = parseJson(url) + val link = json.url + if (link.contains("fembed")) { + loadExtractor(link, data, subtitleCallback, callback) + } + } + } + if (iframe.contains("tomatomatela")) { + val tomatoRegex = + Regex("(\\/\\/apialfa.tomatomatela.com\\/ir\\/player.php\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + tomatoRegex.findAll(iframe).map { tomreg -> + tomreg.value + }.toList().apmap { tom -> + val tomkey = tom.replace("//apialfa.tomatomatela.com/ir/player.php?h=", "") + app.post( + "https://apialfa.tomatomatela.com/ir/rd.php", allowRedirects = false, + headers = mapOf( + "Host" to "apialfa.tomatomatela.com", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "null", + "DNT" to "1", + "Connection" to "keep-alive", + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + ), + data = mapOf(Pair("url", tomkey)) + ).okhttpResponse.headers.values("location").apmap { loc -> + if (loc.contains("goto_ddh.php")) { + val gotoregex = + Regex("(\\/\\/api.cuevana3.me\\/ir\\/goto_ddh.php\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + gotoregex.findAll(loc).map { goreg -> + goreg.value.replace("//api.cuevana3.me/ir/goto_ddh.php?h=", "") + }.toList().apmap { gotolink -> + app.post( + "https://api.cuevana3.me/ir/redirect_ddh.php", + allowRedirects = false, + headers = mapOf( + "Host" to "api.cuevana3.me", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "null", + "DNT" to "1", + "Connection" to "keep-alive", + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + ), + data = mapOf(Pair("url", gotolink)) + ).okhttpResponse.headers.values("location").apmap { golink -> + loadExtractor(golink, data, subtitleCallback, callback) + } + } + } + if (loc.contains("index.php?h=")) { + val indexRegex = + Regex("(\\/\\/api.cuevana3.me\\/sc\\/index.php\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + indexRegex.findAll(loc).map { indreg -> + indreg.value.replace("//api.cuevana3.me/sc/index.php?h=", "") + }.toList().apmap { inlink -> + app.post( + "https://api.cuevana3.me/sc/r.php", allowRedirects = false, + headers = mapOf( + "Host" to "api.cuevana3.me", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Accept-Encoding" to "gzip, deflate, br", + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "null", + "DNT" to "1", + "Connection" to "keep-alive", + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "Sec-Fetch-User" to "?1", + ), + data = mapOf(Pair("h", inlink)) + ).okhttpResponse.headers.values("location").apmap { link -> + loadExtractor(link, data, subtitleCallback, callback) + } + } + } + } + } + } + } + return true + } +} \ No newline at end of file diff --git a/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProviderPlugin.kt b/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProviderPlugin.kt new file mode 100644 index 0000000..a126075 --- /dev/null +++ b/CuevanaProvider/src/main/kotlin/com/lagradost/CuevanaProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CuevanaProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(CuevanaProvider()) + } +} \ No newline at end of file diff --git a/DoramasYTProvider/build.gradle.kts b/DoramasYTProvider/build.gradle.kts new file mode 100644 index 0000000..d590c87 --- /dev/null +++ b/DoramasYTProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=doramasyt.com&sz=24" +} \ No newline at end of file diff --git a/DoramasYTProvider/src/main/AndroidManifest.xml b/DoramasYTProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/DoramasYTProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProvider.kt b/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProvider.kt new file mode 100644 index 0000000..8f3b5a6 --- /dev/null +++ b/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProvider.kt @@ -0,0 +1,155 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +//import com.lagradost.cloudstream3.extractors.FEmbed +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + + +class DoramasYTProvider : MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA + else if (t.contains("Pelicula")) TvType.Movie + else TvType.TvSeries + } + fun getDubStatus(title: String): DubStatus { + return if (title.contains("Latino") || title.contains("Castellano")) + DubStatus.Dubbed + else DubStatus.Subbed + } + } + + override var mainUrl = "https://doramasyt.com" + override var name = "DoramasYT" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AsianDrama, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/emision", "En emisión"), + Pair( + "$mainUrl/doramas?categoria=pelicula&genero=false&fecha=false&letra=false", + "Peliculas" + ), + Pair("$mainUrl/doramas", "Doramas"), + Pair( + "$mainUrl/doramas?categoria=live-action&genero=false&fecha=false&letra=false", + "Live Action" + ), + ) + + val items = ArrayList() + + items.add( + HomePageList( + "Capítulos actualizados", + app.get(mainUrl, timeout = 120).document.select(".col-6").map { + val title = it.selectFirst("p")!!.text() + val poster = it.selectFirst(".chapter img")!!.attr("src") + val epRegex = Regex("episodio-(\\d+)") + val url = it.selectFirst("a")!!.attr("href").replace("ver/", "dorama/") + .replace(epRegex, "sub-espanol") + val epNum = it.selectFirst("h3")!!.text().toIntOrNull() + newAnimeSearchResponse(title,url) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title), epNum) + } + }) + ) + + for (i in urls) { + try { + val home = app.get(i.first, timeout = 120).document.select(".col-6").map { + val title = it.selectFirst(".animedtls p")!!.text() + val poster = it.selectFirst(".anithumb img")!!.attr("src") + newAnimeSearchResponse(title, fixUrl(it.selectFirst("a")!!.attr("href"))) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title)) + } + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/buscar?q=$query", timeout = 120).document.select(".col-6").map { + val title = it.selectFirst(".animedtls p")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst(".animes img")!!.attr("src") + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + image, + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url, timeout = 120).document + val poster = doc.selectFirst("div.flimimg img.img1")!!.attr("src") + val title = doc.selectFirst("h1")!!.text() + val type = doc.selectFirst("h4")!!.text() + val description = doc.selectFirst("p.textComplete")!!.text().replace("Ver menos", "") + val genres = doc.select(".nobel a").map { it.text() } + val status = when (doc.selectFirst(".state h6")?.text()) { + "Estreno" -> ShowStatus.Ongoing + "Finalizado" -> ShowStatus.Completed + else -> null + } + val episodes = doc.select(".heromain .col-item").map { + val name = it.selectFirst(".dtlsflim p")!!.text() + val link = it.selectFirst("a")!!.attr("href") + val epThumb = it.selectFirst(".flimimg img.img1")!!.attr("src") + Episode(link, name, posterUrl = epThumb) + } + return newAnimeLoadResponse(title, url, getType(type)) { + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + tags = genres + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("div.playother p").apmap { + val encodedurl = it.select("p").attr("data-player") + val urlDecoded = base64Decode(encodedurl) + val url = (urlDecoded).replace("https://doramasyt.com/reproductor?url=", "") + if (url.startsWith("https://www.fembed.com")) { + val extractor = FEmbed() + extractor.getUrl(url).forEach { link -> + callback.invoke(link) + } + } else { + loadExtractor(url, mainUrl, subtitleCallback, callback) + } + } + return true + } +} \ No newline at end of file diff --git a/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProviderPlugin.kt b/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProviderPlugin.kt new file mode 100644 index 0000000..e5652d9 --- /dev/null +++ b/DoramasYTProvider/src/main/kotlin/com/lagradost/DoramasYTProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class DoramasYTProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(DoramasYTProvider()) + } +} \ No newline at end of file diff --git a/DoramasYTProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt b/DoramasYTProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt new file mode 100644 index 0000000..866aee0 --- /dev/null +++ b/DoramasYTProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt @@ -0,0 +1,67 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName + +class FEmbed: XStreamCdn() { + override val name: String = "FEmbed" + override val mainUrl: String = "https://www.fembed.com" +} + +open class XStreamCdn : ExtractorApi() { + override val name: String = "XStreamCdn" + override val mainUrl: String = "https://embedsito.com" + override val requiresReferer = false + open var domainUrl: String = "embedsito.com" + + private data class ResponseData( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + //val type: String // Mp4 + ) + + private data class ResponseJson( + @JsonProperty("success") val success: Boolean, + @JsonProperty("data") val data: List? + ) + + override fun getExtractorUrl(id: String): String { + return "$domainUrl/api/source/$id" + } + + override suspend fun getUrl(url: String, referer: String?): List { + val headers = mapOf( + "Referer" to url, + "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", + ) + val id = url.trimEnd('/').split("/").last() + val newUrl = "https://${domainUrl}/api/source/${id}" + val extractedLinksList: MutableList = mutableListOf() + with(app.post(newUrl, headers = headers)) { + if (this.code != 200) return listOf() + val text = this.text + if (text.isEmpty()) return listOf() + if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf() + AppUtils.parseJson(text)?.let { + if (it.success && it.data != null) { + it.data.forEach { data -> + extractedLinksList.add( + ExtractorLink( + name, + name = name, + data.file, + url, + getQualityFromName(data.label), + ) + ) + } + } + } + } + return extractedLinksList + } +} \ No newline at end of file diff --git a/ElifilmsProvider/build.gradle.kts b/ElifilmsProvider/build.gradle.kts new file mode 100644 index 0000000..1e957a0 --- /dev/null +++ b/ElifilmsProvider/build.gradle.kts @@ -0,0 +1,23 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Movie", + ) + + } \ No newline at end of file diff --git a/ElifilmsProvider/src/main/AndroidManifest.xml b/ElifilmsProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/ElifilmsProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProvider.kt b/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProvider.kt new file mode 100644 index 0000000..f2ef65a --- /dev/null +++ b/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProvider.kt @@ -0,0 +1,94 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class ElifilmsProvider : MainAPI() { + override var mainUrl: String = "https://elifilms.net" + override var name: String = "Elifilms" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val items = ArrayList() + val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href") + val urls = listOf( + Pair(mainUrl, "Películas recientes"), + Pair("$mainUrl/4k-peliculas/", "Películas en 4k"), + Pair(newest, "Últimos estrenos"), + ) + urls.apmap { (url, name) -> + val soup = app.get(url ?: "").document + val home = soup.select("article.shortstory.cf").map { + val title = it.selectFirst(".short_header")?.text() ?: "" + val link = it.selectFirst("div a")?.attr("href") ?: "" + TvSeriesSearchResponse( + title, + link, + this.name, + TvType.Movie, + it.selectFirst("a.ah-imagge img")?.attr("data-src"), + null, + null, + ) + } + items.add(HomePageList(name, home)) + } + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=$query" + val doc = app.get(url).document + return doc.select("article.cf").map { + val href = it.selectFirst("div.short_content a")?.attr("href") ?: "" + val poster = it.selectFirst("a.ah-imagge img")?.attr("data-src") + val name = it.selectFirst(".short_header")?.text() ?: "" + (MovieSearchResponse(name, href, this.name, TvType.Movie, poster, null)) + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url, timeout = 120).document + val title = document.selectFirst(".post_title h1")?.text() ?: "" + val rating = document.select("span.imdb.rki").toString().toIntOrNull() + val poster = document.selectFirst(".poster img")?.attr("src") + val desc = document.selectFirst("div.notext .actors p")?.text() + val tags = document.select("td.notext a") + .map { it?.text()?.trim().toString() } + return MovieLoadResponse( + title, + url, + this.name, + TvType.Movie, + url, + poster, + null, + desc, + rating, + tags + ) + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("li.change-server a").apmap { + val encodedurl = it.attr("data-id") + val urlDecoded = base64Decode(encodedurl) + val url = fixUrl(urlDecoded) + loadExtractor(url, data, subtitleCallback, callback) + } + return true + } +} diff --git a/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProviderPlugin.kt b/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProviderPlugin.kt new file mode 100644 index 0000000..c719279 --- /dev/null +++ b/ElifilmsProvider/src/main/kotlin/com/lagradost/ElifilmsProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class ElifilmsProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(ElifilmsProvider()) + } +} \ No newline at end of file diff --git a/EntrepeliculasyseriesProvider/build.gradle.kts b/EntrepeliculasyseriesProvider/build.gradle.kts new file mode 100644 index 0000000..ea3351a --- /dev/null +++ b/EntrepeliculasyseriesProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=entrepeliculasyseries.nu&sz=24" +} \ No newline at end of file diff --git a/EntrepeliculasyseriesProvider/src/main/AndroidManifest.xml b/EntrepeliculasyseriesProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/EntrepeliculasyseriesProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProvider.kt b/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProvider.kt new file mode 100644 index 0000000..7832cb9 --- /dev/null +++ b/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProvider.kt @@ -0,0 +1,177 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class EntrepeliculasyseriesProvider : MainAPI() { + override var mainUrl = "https://entrepeliculasyseries.nu" + override var name = "EntrePeliculasySeries" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading + + override val mainPage = mainPageOf( + Pair("$mainUrl/series/page/", "Series"), + Pair("$mainUrl/peliculas/page/", "Peliculas"), + Pair("$mainUrl/anime/page/", "Animes"), + ) + + override suspend fun getMainPage( + page: Int, + request : MainPageRequest + ): HomePageResponse { + val url = request.data + page + + val soup = app.get(url).document + val home = soup.select("ul.list-movie li").map { + val title = it.selectFirst("a.link-title h2")!!.text() + val link = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("/pelicula/")) TvType.Movie else TvType.TvSeries, + it.selectFirst("a.poster img")!!.attr("src"), + null, + null, + ) + } + + return newHomePageResponse(request.name, home) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=${query}" + val document = app.get(url).document + + return document.select("li.xxx.TPostMv").map { + val title = it.selectFirst("h2.Title")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst("img.lazy")!!.attr("data-src") + val isMovie = href.contains("/pelicula/") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + }.toList() + } + + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + + val title = soup.selectFirst("h1.title-post")!!.text() + val description = soup.selectFirst("p.text-content:nth-child(3)")?.text()?.trim() + val poster: String? = soup.selectFirst("article.TPost img.lazy")!!.attr("data-src") + val episodes = soup.select(".TPostMv article").map { li -> + val href = (li.select("a") ?: li.select(".C a") ?: li.select("article a")).attr("href") + val epThumb = li.selectFirst("div.Image img")!!.attr("data-src") + val seasonid = li.selectFirst("span.Year")!!.text().let { str -> + str.split("x").mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid.size == 2 + val episode = if (isValid) seasonid.getOrNull(1) else null + val season = if (isValid) seasonid.getOrNull(0) else null + Episode( + href, + null, + season, + episode, + fixUrl(epThumb) + ) + } + return when (val tvType = + if (url.contains("/pelicula/")) TvType.Movie else TvType.TvSeries) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + null, + description, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + null, + description, + ) + } + else -> null + } + } + + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select(".video ul.dropdown-menu li").apmap { + val servers = it.attr("data-link") + val doc = app.get(servers).document + doc.select("input").apmap { + val postkey = it.attr("value") + app.post( + "https://entrepeliculasyseries.nu/r.php", + headers = mapOf( + "Host" to "entrepeliculasyseries.nu", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "https://entrepeliculasyseries.nu", + "DNT" to "1", + "Connection" to "keep-alive", + "Referer" to servers, + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "document", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "Sec-Fetch-User" to "?1", + ), + //params = mapOf(Pair("h", postkey)), + data = mapOf(Pair("h", postkey)), + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { + loadExtractor(it, data, subtitleCallback, callback) + } + } + } + return true + } +} \ No newline at end of file diff --git a/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProviderPlugin.kt b/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProviderPlugin.kt new file mode 100644 index 0000000..b165d5b --- /dev/null +++ b/EntrepeliculasyseriesProvider/src/main/kotlin/com/lagradost/EntrepeliculasyseriesProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class EntrepeliculasyseriesProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(EntrepeliculasyseriesProvider()) + } +} \ No newline at end of file diff --git a/EstrenosDoramasProvider/build.gradle.kts b/EstrenosDoramasProvider/build.gradle.kts new file mode 100644 index 0000000..a190f2d --- /dev/null +++ b/EstrenosDoramasProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AsianDrama", + "Movie", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www23.estrenosdoramas.net&sz=24" +} \ No newline at end of file diff --git a/EstrenosDoramasProvider/src/main/AndroidManifest.xml b/EstrenosDoramasProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/EstrenosDoramasProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProvider.kt b/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProvider.kt new file mode 100644 index 0000000..5b14c53 --- /dev/null +++ b/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProvider.kt @@ -0,0 +1,286 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.network.WebViewResolver +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import java.util.* +import kotlin.collections.ArrayList + + +class EstrenosDoramasProvider : MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA + else if (t.contains("Pelicula")) TvType.Movie + else TvType.TvSeries + } + } + + override var mainUrl = "https://www23.estrenosdoramas.net" + override var name = "EstrenosDoramas" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AsianDrama, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair(mainUrl, "Últimas series"), + Pair("$mainUrl/category/peliculas", "Películas"), + ) + + val items = ArrayList() + + urls.apmap { (url, name) -> + val home = app.get(url, timeout = 120).document.select("div.clearfix").map { + val title = cleanTitle(it.selectFirst("h3 a")?.text()!!) + val poster = it.selectFirst("img.cate_thumb")?.attr("src") + AnimeSearchResponse( + title, + it.selectFirst("a")?.attr("href")!!, + this.name, + TvType.AsianDrama, + poster, + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + items.add(HomePageList(name, home)) + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val searchob = ArrayList() + val search = + app.get("$mainUrl/?s=$query", timeout = 120).document.select("div.clearfix").map { + val title = cleanTitle(it.selectFirst("h3 a")?.text()!!) + val href = it.selectFirst("a")?.attr("href") + val image = it.selectFirst("img.cate_thumb")?.attr("src") + val lists = + AnimeSearchResponse( + title, + href!!, + this.name, + TvType.AsianDrama, + image, + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + if (href.contains("capitulo")) { + //nothing + } + else { + searchob.add(lists) + } + } + return searchob + } + + override suspend fun load(url: String): LoadResponse? { + val doc = app.get(url, timeout = 120).document + val poster = doc.selectFirst("head meta[property]")?.attr("content") + val title = doc.selectFirst("h1.titulo")?.text() + val description = try { + doc.selectFirst("div.post div.highlight div.font")?.text() + } catch (e:Exception){ + null + } + val finaldesc = description?.substringAfter("Sinopsis")?.replace(": ", "")?.trim() + val epi = ArrayList() + val episodes = doc.select("div.post .lcp_catlist a").map { + val name = it.selectFirst("a")?.text() + val link = it.selectFirst("a")?.attr("href") + val test = Episode(link!!, name) + if (!link.equals(url)) { + epi.add(test) + } + }.reversed() + return when (val type = if (episodes.isEmpty()) TvType.Movie else TvType.AsianDrama) { + TvType.AsianDrama -> { + return newAnimeLoadResponse(title!!, url, type) { + japName = null + engName = title.replace(Regex("[Pp]elicula |[Pp]elicula"),"") + posterUrl = poster + addEpisodes(DubStatus.Subbed, epi.reversed()) + plot = finaldesc + } + } + TvType.Movie -> { + MovieLoadResponse( + cleanTitle(title!!), + url, + this.name, + TvType.Movie, + url, + poster, + null, + finaldesc, + null, + null, + ) + } + else -> null + } + + } + + + + data class ReproDoramas ( + @JsonProperty("link") val link: String, + @JsonProperty("time") val time: Int + ) + + private fun cleanTitle(title: String): String = title.replace(Regex("[Pp]elicula |[Pp]elicula"),"") + + private fun cleanExtractor( + source: String, + name: String, + url: String, + referer: String, + m3u8: Boolean, + callback: (ExtractorLink) -> Unit + ): Boolean { + callback( + ExtractorLink( + source, + name, + url, + referer, + Qualities.Unknown.value, + m3u8 + ) + ) + return true + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val headers = mapOf("Host" to "repro3.estrenosdoramas.us", + "User-Agent" to USER_AGENT, + "Accept" to "*/*", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest", + "Origin" to "https://repro3.estrenosdoramas.us", + "DNT" to "1", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "same-origin", + "Cache-Control" to "max-age=0",) + + val document = app.get(data).document + document.select("div.tab_container iframe").apmap { container -> + val directlink = fixUrl(container.attr("src")) + loadExtractor(directlink, data, subtitleCallback, callback) + + if (directlink.contains("/repro/amz/")) { + val amzregex = Regex("https:\\/\\/repro3\\.estrenosdoramas\\.us\\/repro\\/amz\\/examples\\/.*\\.php\\?key=.*\$") + amzregex.findAll(directlink).map { + it.value.replace(Regex("https:\\/\\/repro3\\.estrenosdoramas\\.us\\/repro\\/amz\\/examples\\/.*\\.php\\?key="),"") + }.toList().apmap { key -> + val response = app.post("https://repro3.estrenosdoramas.us/repro/amz/examples/player/api/indexDCA.php", + headers = headers, + data = mapOf( + Pair("key",key), + Pair("token","MDAwMDAwMDAwMA=="), + ), + allowRedirects = false + ).text + val reprojson = parseJson(response) + val decodeurl = base64Decode(reprojson.link) + if (decodeurl.contains("m3u8")) + + cleanExtractor( + name, + name, + decodeurl, + "https://repro3.estrenosdoramas.us", + decodeurl.contains(".m3u8"), + callback + ) + } + } + + + if (directlink.contains("reproducir14")) { + val regex = Regex("(https:\\/\\/repro.\\.estrenosdoramas\\.us\\/repro\\/reproducir14\\.php\\?key=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + regex.findAll(directlink).map { + it.value + }.toList().apmap { + val doc = app.get(it).text + val videoid = doc.substringAfter("vid=\"").substringBefore("\" n") + val token = doc.substringAfter("name=\"").substringBefore("\" s") + val acctkn = doc.substringAfter("{ acc: \"").substringBefore("\", id:") + val link = app.post("https://repro3.estrenosdoramas.us/repro/proto4.php", + headers = headers, + data = mapOf( + Pair("acc",acctkn), + Pair("id",videoid), + Pair("tk",token)), + allowRedirects = false + ).text + val extracteklink = link.substringAfter("\"urlremoto\":\"").substringBefore("\"}") + .replace("\\/", "/").replace("//ok.ru/","http://ok.ru/") + loadExtractor(extracteklink, data, subtitleCallback, callback) + } + } + + if (directlink.contains("reproducir120")) { + val regex = Regex("(https:\\/\\/repro3.estrenosdoramas.us\\/repro\\/reproducir120\\.php\\?\\nkey=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") + regex.findAll(directlink).map { + it.value + }.toList().apmap { + val doc = app.get(it).text + val videoid = doc.substringAfter("var videoid = '").substringBefore("';") + val token = doc.substringAfter("var tokens = '").substringBefore("';") + val acctkn = doc.substringAfter("{ acc: \"").substringBefore("\", id:") + val link = app.post("https://repro3.estrenosdoramas.us/repro/api3.php", + headers = headers, + data = mapOf( + Pair("acc",acctkn), + Pair("id",videoid), + Pair("tk",token)), + allowRedirects = false + ).text + val extractedlink = link.substringAfter("\"{file:'").substringBefore("',label:") + .replace("\\/", "/") + val quality = link.substringAfter(",label:'").substringBefore("',type:") + val type = link.substringAfter("type: '").substringBefore("'}\"") + if (extractedlink.isNotBlank()) + if (quality.contains("File not found", ignoreCase = true)) { + //Nothing + } else { + cleanExtractor( + "Movil", + "Movil $quality", + extractedlink, + "", + !type.contains("mp4"), + callback + ) + } + } + } + } + + return true + } +} diff --git a/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProviderPlugin.kt b/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProviderPlugin.kt new file mode 100644 index 0000000..521bebd --- /dev/null +++ b/EstrenosDoramasProvider/src/main/kotlin/com/lagradost/EstrenosDoramasProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class EstrenosDoramasProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(EstrenosDoramasProvider()) + } +} \ No newline at end of file diff --git a/JKAnimeProvider/build.gradle.kts b/JKAnimeProvider/build.gradle.kts new file mode 100644 index 0000000..87e7d07 --- /dev/null +++ b/JKAnimeProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=jkanime.net&sz=24" +} \ No newline at end of file diff --git a/JKAnimeProvider/src/main/AndroidManifest.xml b/JKAnimeProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/JKAnimeProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProvider.kt b/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProvider.kt new file mode 100644 index 0000000..a3174ab --- /dev/null +++ b/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProvider.kt @@ -0,0 +1,319 @@ +package com.lagradost + + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + + +class JKAnimeProvider : MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA + else if (t.contains("Pelicula")) TvType.AnimeMovie + else TvType.Anime + } + } + + override var mainUrl = "https://jkanime.net" + override var name = "JKAnime" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair( + "$mainUrl/directorio/?filtro=fecha&tipo=TV&estado=1&fecha=none&temporada=none&orden=desc", + "En emisión" + ), + Pair( + "$mainUrl/directorio/?filtro=fecha&tipo=none&estado=none&fecha=none&temporada=none&orden=none", + "Animes" + ), + Pair( + "$mainUrl/directorio/?filtro=fecha&tipo=Movie&estado=none&fecha=none&temporada=none&orden=none", + "Películas" + ), + ) + + val items = ArrayList() + + items.add( + HomePageList( + "Últimos episodios", + app.get(mainUrl).document.select(".listadoanime-home a.bloqq").map { + val title = it.selectFirst("h5")?.text() + val dubstat = if (title!!.contains("Latino") || title.contains("Castellano")) + DubStatus.Dubbed else DubStatus.Subbed + val poster = + it.selectFirst(".anime__sidebar__comment__item__pic img")?.attr("src") ?: "" + val epRegex = Regex("/(\\d+)/|/especial/|/ova/") + val url = it.attr("href").replace(epRegex, "") + val epNum = + it.selectFirst("h6")?.text()?.replace("Episodio ", "")?.toIntOrNull() + newAnimeSearchResponse(title, url) { + this.posterUrl = poster + addDubStatus(dubstat, epNum) + } + }) + ) + urls.apmap { (url, name) -> + val soup = app.get(url).document + val home = soup.select(".g-0").map { + val title = it.selectFirst("h5 a")?.text() + val poster = it.selectFirst("img")?.attr("src") ?: "" + AnimeSearchResponse( + title!!, + fixUrl(it.selectFirst("a")?.attr("href") ?: ""), + this.name, + TvType.Anime, + fixUrl(poster), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + items.add(HomePageList(name, home)) + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + data class MainSearch( + @JsonProperty("animes") val animes: List, + @JsonProperty("anime_types") val animeTypes: AnimeTypes + ) + + data class Animes( + @JsonProperty("id") val id: String, + @JsonProperty("slug") val slug: String, + @JsonProperty("title") val title: String, + @JsonProperty("image") val image: String, + @JsonProperty("synopsis") val synopsis: String, + @JsonProperty("type") val type: String, + @JsonProperty("status") val status: String, + @JsonProperty("thumbnail") val thumbnail: String + ) + + data class AnimeTypes( + @JsonProperty("TV") val TV: String, + @JsonProperty("OVA") val OVA: String, + @JsonProperty("Movie") val Movie: String, + @JsonProperty("Special") val Special: String, + @JsonProperty("ONA") val ONA: String, + @JsonProperty("Music") val Music: String + ) + + override suspend fun search(query: String): List { + val main = app.get("$mainUrl/ajax/ajax_search/?q=$query").text + val json = parseJson(main) + return json.animes.map { + val title = it.title + val href = "$mainUrl/${it.slug}" + val image = "https://cdn.jkanime.net/assets/images/animes/image/${it.slug}.jpg" + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + image, + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url, timeout = 120).document + val poster = doc.selectFirst(".set-bg")?.attr("data-setbg") + val title = doc.selectFirst(".anime__details__title > h3")?.text() + val type = doc.selectFirst(".anime__details__text")?.text() + val description = doc.selectFirst(".anime__details__text > p")?.text() + val genres = doc.select("div.col-lg-6:nth-child(1) > ul:nth-child(1) > li:nth-child(2) > a") + .map { it.text() } + val status = when (doc.selectFirst("span.enemision")?.text()) { + "En emisión" -> ShowStatus.Ongoing + "Concluido" -> ShowStatus.Completed + else -> null + } + val animeID = doc.selectFirst("div.ml-2")?.attr("data-anime")?.toInt() + val animeeps = "$mainUrl/ajax/last_episode/$animeID/" + val jsoneps = app.get(animeeps).text + val lastepnum = + jsoneps.substringAfter("{\"number\":\"").substringBefore("\",\"title\"").toInt() + val episodes = (1..lastepnum).map { + val link = "${url.removeSuffix("/")}/$it" + Episode(link) + } + + return newAnimeLoadResponse(title!!, url, getType(type!!)) { + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + tags = genres + } + } + + data class Nozomi( + @JsonProperty("file") val file: String? + ) + + private fun streamClean( + name: String, + url: String, + referer: String, + quality: String?, + callback: (ExtractorLink) -> Unit, + m3u8: Boolean + ): Boolean { + callback( + ExtractorLink( + name, + name, + url, + referer, + getQualityFromName(quality), + m3u8 + ) + ) + return true + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("script").apmap { script -> + if (script.data().contains("var video = []")) { + val videos = script.data().replace("\\/", "/") + fetchUrls(videos).map { + it.replace("$mainUrl/jkfembed.php?u=", "https://embedsito.com/v/") + .replace("$mainUrl/jkokru.php?u=", "http://ok.ru/videoembed/") + .replace("$mainUrl/jkvmixdrop.php?u=", "https://mixdrop.co/e/") + .replace("$mainUrl/jk.php?u=", "$mainUrl/") + }.apmap { link -> + loadExtractor(link, data, subtitleCallback, callback) + if (link.contains("um2.php")) { + val doc = app.get(link, referer = data).document + val gsplaykey = doc.select("form input[value]").attr("value") + app.post( + "$mainUrl/gsplay/redirect_post.php", + headers = mapOf( + "Host" to "jkanime.net", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Referer" to link, + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "https://jkanime.net", + "DNT" to "1", + "Connection" to "keep-alive", + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "TE" to "trailers", + "Pragma" to "no-cache", + "Cache-Control" to "no-cache", + ), + data = mapOf(Pair("data", gsplaykey)), + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { loc -> + val postkey = loc.replace("/gsplay/player.html#", "") + val nozomitext = app.post( + "$mainUrl/gsplay/api.php", + headers = mapOf( + "Host" to "jkanime.net", + "User-Agent" to USER_AGENT, + "Accept" to "application/json, text/javascript, */*; q=0.01", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With" to "XMLHttpRequest", + "Origin" to "https://jkanime.net", + "DNT" to "1", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "same-origin", + ), + data = mapOf(Pair("v", postkey)), + allowRedirects = false + ).text + val json = parseJson(nozomitext) + val nozomiurl = listOf(json.file) + if (nozomiurl.isEmpty()) null else + nozomiurl.forEach { url -> + val nozominame = "Nozomi" + streamClean( + nozominame, + url!!, + "", + null, + callback, + url.contains(".m3u8") + ) + } + } + } + if (link.contains("um.php")) { + val desutext = app.get(link, referer = data).text + val desuRegex = Regex("((https:|http:)//.*\\.m3u8)") + val file = desuRegex.find(desutext)?.value + val namedesu = "Desu" + generateM3u8( + namedesu, + file!!, + mainUrl, + ).forEach { desurl -> + streamClean( + namedesu, + desurl.url, + mainUrl, + desurl.quality.toString(), + callback, + true + ) + } + } + if (link.contains("jkmedia")) { + app.get( + link, + referer = data, + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { xtremeurl -> + val namex = "Xtreme S" + streamClean( + namex, + xtremeurl, + "", + null, + callback, + xtremeurl.contains(".m3u8") + ) + } + } + } + } + } + return true + } +} \ No newline at end of file diff --git a/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProviderPlugin.kt b/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProviderPlugin.kt new file mode 100644 index 0000000..330bc29 --- /dev/null +++ b/JKAnimeProvider/src/main/kotlin/com/lagradost/JKAnimeProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class JKAnimeProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(JKAnimeProvider()) + } +} \ No newline at end of file diff --git a/MonoschinosProvider/build.gradle.kts b/MonoschinosProvider/build.gradle.kts new file mode 100644 index 0000000..dde6619 --- /dev/null +++ b/MonoschinosProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=monoschinos2.com&sz=24" +} \ No newline at end of file diff --git a/MonoschinosProvider/src/main/AndroidManifest.xml b/MonoschinosProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/MonoschinosProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProvider.kt b/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProvider.kt new file mode 100644 index 0000000..ba6f7ab --- /dev/null +++ b/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProvider.kt @@ -0,0 +1,155 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + + +class MonoschinosProvider : MainAPI() { + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA + else if (t.contains("Pelicula")) TvType.AnimeMovie + else TvType.Anime + } + + fun getDubStatus(title: String): DubStatus { + return if (title.contains("Latino") || title.contains("Castellano")) + DubStatus.Dubbed + else DubStatus.Subbed + } + } + + override var mainUrl = "https://monoschinos2.com" + override var name = "Monoschinos" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.AnimeMovie, + TvType.OVA, + TvType.Anime, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/emision", "En emisión"), + Pair( + "$mainUrl/animes?categoria=pelicula&genero=false&fecha=false&letra=false", + "Peliculas" + ), + Pair("$mainUrl/animes", "Animes"), + ) + + val items = ArrayList() + + items.add( + HomePageList( + "Capítulos actualizados", + app.get(mainUrl, timeout = 120).document.select(".col-6").map { + val title = it.selectFirst("p.animetitles")?.text() ?: it.selectFirst(".animetitles")?.text() ?: "" + val poster = it.selectFirst(".animeimghv")!!.attr("data-src") + val epRegex = Regex("episodio-(\\d+)") + val url = it.selectFirst("a")?.attr("href")!!.replace("ver/", "anime/") + .replace(epRegex, "sub-espanol") + val epNum = (it.selectFirst(".positioning h5")?.text() ?: it.selectFirst("div.positioning p")?.text())?.toIntOrNull() + newAnimeSearchResponse(title, url) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title), epNum) + } + }) + ) + + for (i in urls) { + try { + val home = app.get(i.first, timeout = 120).document.select(".col-6").map { + val title = it.selectFirst(".seristitles")!!.text() + val poster = it.selectFirst("img.animemainimg")!!.attr("src") + newAnimeSearchResponse(title, fixUrl(it.selectFirst("a")!!.attr("href"))) { + this.posterUrl = fixUrl(poster) + addDubStatus(getDubStatus(title)) + } + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): ArrayList { + val search = + app.get("$mainUrl/buscar?q=$query", timeout = 120).document.select(".col-6").map { + val title = it.selectFirst(".seristitles")!!.text() + val href = fixUrl(it.selectFirst("a")!!.attr("href")) + val image = it.selectFirst("img.animemainimg")!!.attr("src") + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + fixUrl(image), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + return ArrayList(search) + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url, timeout = 120).document + val poster = doc.selectFirst(".chapterpic img")!!.attr("src") + val title = doc.selectFirst(".chapterdetails h1")!!.text() + val type = doc.selectFirst("div.chapterdetls2")!!.text() + val description = doc.selectFirst("p.textComplete")!!.text().replace("Ver menos", "") + val genres = doc.select(".breadcrumb-item a").map { it.text() } + val status = when (doc.selectFirst("button.btn1")?.text()) { + "Estreno" -> ShowStatus.Ongoing + "Finalizado" -> ShowStatus.Completed + else -> null + } + val episodes = doc.select("div.col-item").map { + val name = it.selectFirst("p.animetitles")!!.text() + val link = it.selectFirst("a")!!.attr("href") + val epThumb = it.selectFirst(".animeimghv")!!.attr("data-src") + Episode(link, name, posterUrl = epThumb) + } + return newAnimeLoadResponse(title, url, getType(type)) { + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + tags = genres + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("div.playother p").forEach { + val encodedurl = it.select("p").attr("data-player") + val urlDecoded = base64Decode(encodedurl) + val url = (urlDecoded).replace("https://monoschinos2.com/reproductor?url=", "") + if (url.startsWith("https://www.fembed.com")) { + val extractor = FEmbed() + extractor.getUrl(url).forEach { link -> + callback.invoke(link) + } + } else { + loadExtractor(url, mainUrl, subtitleCallback, callback) + } + } + return true + } +} \ No newline at end of file diff --git a/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProviderPlugin.kt b/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProviderPlugin.kt new file mode 100644 index 0000000..f3b0b35 --- /dev/null +++ b/MonoschinosProvider/src/main/kotlin/com/lagradost/MonoschinosProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class MonoschinosProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(MonoschinosProvider()) + } +} \ No newline at end of file diff --git a/MonoschinosProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt b/MonoschinosProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt new file mode 100644 index 0000000..866aee0 --- /dev/null +++ b/MonoschinosProvider/src/main/kotlin/com/lagradost/XStreamCdn.kt @@ -0,0 +1,67 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.getQualityFromName + +class FEmbed: XStreamCdn() { + override val name: String = "FEmbed" + override val mainUrl: String = "https://www.fembed.com" +} + +open class XStreamCdn : ExtractorApi() { + override val name: String = "XStreamCdn" + override val mainUrl: String = "https://embedsito.com" + override val requiresReferer = false + open var domainUrl: String = "embedsito.com" + + private data class ResponseData( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String, + //val type: String // Mp4 + ) + + private data class ResponseJson( + @JsonProperty("success") val success: Boolean, + @JsonProperty("data") val data: List? + ) + + override fun getExtractorUrl(id: String): String { + return "$domainUrl/api/source/$id" + } + + override suspend fun getUrl(url: String, referer: String?): List { + val headers = mapOf( + "Referer" to url, + "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", + ) + val id = url.trimEnd('/').split("/").last() + val newUrl = "https://${domainUrl}/api/source/${id}" + val extractedLinksList: MutableList = mutableListOf() + with(app.post(newUrl, headers = headers)) { + if (this.code != 200) return listOf() + val text = this.text + if (text.isEmpty()) return listOf() + if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf() + AppUtils.parseJson(text)?.let { + if (it.success && it.data != null) { + it.data.forEach { data -> + extractedLinksList.add( + ExtractorLink( + name, + name = name, + data.file, + url, + getQualityFromName(data.label), + ) + ) + } + } + } + } + return extractedLinksList + } +} \ No newline at end of file diff --git a/MundoDonghuaProvider/build.gradle.kts b/MundoDonghuaProvider/build.gradle.kts new file mode 100644 index 0000000..cc6f854 --- /dev/null +++ b/MundoDonghuaProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "Anime", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=www.mundodonghua.com&sz=24" +} \ No newline at end of file diff --git a/MundoDonghuaProvider/src/main/AndroidManifest.xml b/MundoDonghuaProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/MundoDonghuaProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProvider.kt b/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProvider.kt new file mode 100644 index 0000000..8381b2f --- /dev/null +++ b/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProvider.kt @@ -0,0 +1,217 @@ +package com.lagradost + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 +import com.lagradost.cloudstream3.utils.getAndUnpack +import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.cloudstream3.utils.loadExtractor +import java.util.* + + +class MundoDonghuaProvider : MainAPI() { + + override var mainUrl = "https://www.mundodonghua.com" + override var name = "MundoDonghua" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Anime, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = listOf( + Pair("$mainUrl/lista-donghuas", "Donghuas"), + ) + + val items = ArrayList() + items.add( + HomePageList( + "Últimos episodios", + app.get(mainUrl, timeout = 120).document.select("div.row .col-xs-4").map { + val title = it.selectFirst("h5")?.text() ?: "" + val poster = it.selectFirst(".fit-1 img")?.attr("src") + val epRegex = Regex("(\\/(\\d+)\$)") + val url = it.selectFirst("a")?.attr("href")?.replace(epRegex,"")?.replace("/ver/","/donghua/") + val epnumRegex = Regex("((\\d+)$)") + val epNum = epnumRegex.find(title)?.value?.toIntOrNull() + val dubstat = if (title.contains("Latino") || title.contains("Castellano")) DubStatus.Dubbed else DubStatus.Subbed + newAnimeSearchResponse(title.replace(Regex("Episodio|(\\d+)"),"").trim(), fixUrl(url ?: "")) { + this.posterUrl = fixUrl(poster ?: "") + addDubStatus(dubstat, epNum) + } + }) + ) + + urls.apmap { (url, name) -> + val home = app.get(url, timeout = 120).document.select(".col-xs-4").map { + val title = it.selectFirst(".fs-14")?.text() ?: "" + val poster = it.selectFirst(".fit-1 img")?.attr("src") ?: "" + AnimeSearchResponse( + title, + fixUrl(it.selectFirst("a")?.attr("href") ?: ""), + this.name, + TvType.Anime, + fixUrl(poster), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + + items.add(HomePageList(name, home)) + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/busquedas/$query", timeout = 120).document.select(".col-xs-4").map { + val title = it.selectFirst(".fs-14")?.text() ?: "" + val href = fixUrl(it.selectFirst("a")?.attr("href") ?: "") + val image = it.selectFirst(".fit-1 img")?.attr("src") + AnimeSearchResponse( + title, + href, + this.name, + TvType.Anime, + fixUrl(image ?: ""), + null, + if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of( + DubStatus.Dubbed + ) else EnumSet.of(DubStatus.Subbed), + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val doc = app.get(url, timeout = 120).document + val poster = doc.selectFirst("head meta[property=og:image]")?.attr("content") ?: "" + val title = doc.selectFirst(".ls-title-serie")?.text() ?: "" + val description = doc.selectFirst("p.text-justify.fc-dark")?.text() ?: "" + val genres = doc.select("span.label.label-primary.f-bold").map { it.text() } + val status = when (doc.selectFirst("div.col-md-6.col-xs-6.align-center.bg-white.pt-10.pr-15.pb-0.pl-15 p span.badge.bg-default")?.text()) { + "En Emisión" -> ShowStatus.Ongoing + "Finalizada" -> ShowStatus.Completed + else -> null + } + val episodes = doc.select("ul.donghua-list a").map { + val name = it.selectFirst(".fs-16")?.text() + val link = it.attr("href") + Episode(fixUrl(link), name) + }.reversed() + val typeinfo = doc.select("div.row div.col-md-6.pl-15 p.fc-dark").text() + val tvType = if (typeinfo.contains(Regex("Tipo.*Pel.cula"))) TvType.AnimeMovie else TvType.Anime + return newAnimeLoadResponse(title, url, tvType) { + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + tags = genres + } + } + data class Protea ( + @JsonProperty("source") val source: List, + @JsonProperty("poster") val poster: String? + ) + + data class Source ( + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("default") val default: String? + ) + + private fun cleanStream( + name: String, + url: String, + qualityString: String?, + callback: (ExtractorLink) -> Unit, + isM3U8: Boolean + ): Boolean { + callback( + ExtractorLink( + name, + name, + url, + "", + getQualityFromName(qualityString), + isM3U8 + ) + ) + return true + } + + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("script").apmap { script -> + if (script.data().contains("eval(function(p,a,c,k,e")) { + val packedRegex = Regex("eval\\(function\\(p,a,c,k,e,.*\\)\\)") + packedRegex.findAll(script.data()).map { + it.value + }.toList().apmap { + val unpack = getAndUnpack(it).replace("diasfem","embedsito") + fetchUrls(unpack).apmap { url -> + loadExtractor(url, data, subtitleCallback, callback) + } + if (unpack.contains("protea_tab")) { + val protearegex = Regex("(protea_tab.*slug.*,type)") + val slug = protearegex.findAll(unpack).map { + it.value.replace(Regex("(protea_tab.*slug\":\")"),"").replace("\"},type","") + }.first() + val requestlink = "$mainUrl/api_donghua.php?slug=$slug" + val response = app.get(requestlink, headers = + mapOf("Host" to "www.mundodonghua.com", + "User-Agent" to USER_AGENT, + "Accept" to "*/*", + "Accept-Language" to "en-US,en;q=0.5", + "Referer" to data, + "X-Requested-With" to "XMLHttpRequest", + "DNT" to "1", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "no-cors", + "Sec-Fetch-Site" to "same-origin", + "TE" to "trailers", + "Pragma" to "no-cache", + "Cache-Control" to "no-cache",) + ).text.removePrefix("[").removeSuffix("]") + val json = parseJson(response) + json.source.forEach { source -> + val protename = "Protea" + cleanStream(protename, fixUrl(source.file), source.label, callback, false) + } + } + if (unpack.contains("asura_player")) { + val asuraRegex = Regex("(asura_player.*type)") + asuraRegex.findAll(unpack).map { + it.value + }.toList().apmap { protea -> + val asuraname = "Asura" + val file = protea.substringAfter("{file:\"").substringBefore("\"") + generateM3u8( + asuraname, + file, + "" + ).forEach { + cleanStream(asuraname, it.url, it.quality.toString(), callback, true) + } + } + } + } + } + } + return true + } +} \ No newline at end of file diff --git a/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProviderPlugin.kt b/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProviderPlugin.kt new file mode 100644 index 0000000..7b2574f --- /dev/null +++ b/MundoDonghuaProvider/src/main/kotlin/com/lagradost/MundoDonghuaProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class MundoDonghuaProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(MundoDonghuaProvider()) + } +} \ No newline at end of file diff --git a/PeliSmartProvider/build.gradle.kts b/PeliSmartProvider/build.gradle.kts new file mode 100644 index 0000000..10de895 --- /dev/null +++ b/PeliSmartProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + + iconUrl = "https://www.google.com/s2/favicons?domain=pelismart.com&sz=24" +} \ No newline at end of file diff --git a/PeliSmartProvider/src/main/AndroidManifest.xml b/PeliSmartProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/PeliSmartProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProvider.kt b/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProvider.kt new file mode 100644 index 0000000..2e6b489 --- /dev/null +++ b/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProvider.kt @@ -0,0 +1,160 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class PeliSmartProvider: MainAPI() { + override var mainUrl = "https://pelismart.com" + override var name = "PeliSmart" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair("$mainUrl/peliculas/", "Peliculas"), + Pair("$mainUrl/series/", "Series"), + Pair("$mainUrl/documentales/", "Documentales"), + ) + + // has no inf loading + urls.apmap { (url, name) -> + try { + val soup = app.get(url).document + val home = soup.select(".description-off").map { + val title = it.selectFirst("h3.entry-title a")!!.text() + val link = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + if (link.contains("pelicula")) TvType.Movie else TvType.TvSeries, + it.selectFirst("div img")!!.attr("src"), + null, + null, + ) + } + + items.add(HomePageList(name, home)) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl?s=${query}&post_type=post" + val document = app.get(url).document + + return document.select(".description-off").map { + val title = it.selectFirst("h3.entry-title a")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst("div img")!!.attr("src") + val isMovie = href.contains("pelicula") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + } + } + + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + val title = soup.selectFirst(".wpb_wrapper h1")!!.text() + val description = soup.selectFirst("div.wpb_wrapper p")?.text()?.trim() + val poster: String? = soup.selectFirst(".vc_single_image-img")!!.attr("src") + val episodes = soup.select("div.vc_tta-panel-body div a").map { li -> + val href = li.selectFirst("a")!!.attr("href") + val preregex = Regex("(\\d+)\\. ") + val name = li.selectFirst("a")!!.text().replace(preregex,"") + val regextest = Regex("(temporada-(\\d+)-capitulo-(\\d+)|temporada-(\\d+)-episodio-(\\d+))") + val test = regextest.find(href)?.destructured?.component1()?.replace(Regex("(temporada-|-)"),"") + val seasonid = test.let { str -> + str?.split("episodio","capitulo")?.mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid?.size == 2 + val episode = if (isValid) seasonid?.getOrNull(1) else null + val season = if (isValid) seasonid?.getOrNull(0) else null + Episode( + href, + name, + season, + episode, + ) + } + return when (val tvType = if (episodes.isEmpty()) TvType.Movie else TvType.TvSeries) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + null, + description, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + null, + description, + ) + } + else -> null + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val soup = app.get(data).text + fetchUrls(soup).apmap { + val urlc = it.replace("https://pelismart.com/p/1.php?v=","https://evoload.io/e/") + .replace("https://pelismart.com/p/2.php?v=","https://streamtape.com/e/") + .replace("https://pelismart.com/p/4.php?v=","https://dood.to/e/") + .replace("https://pelismarthd.com/p/1.php?v=","https://evoload.io/e/") + .replace("https://pelismarthd.com/p/2.php?v=","https://streamtape.com/e/") + .replace("https://pelismarthd.com/p/4.php?v=","https://dood.to/e/") + loadExtractor(urlc, data, subtitleCallback, callback) + } + return true + } +} \ No newline at end of file diff --git a/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProviderPlugin.kt b/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProviderPlugin.kt new file mode 100644 index 0000000..8ef863c --- /dev/null +++ b/PeliSmartProvider/src/main/kotlin/com/lagradost/PeliSmartProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class PeliSmartProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(PeliSmartProvider()) + } +} \ No newline at end of file diff --git a/PelisplusHDProvider/build.gradle.kts b/PelisplusHDProvider/build.gradle.kts new file mode 100644 index 0000000..5a7ea96 --- /dev/null +++ b/PelisplusHDProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + + iconUrl = "https://www.google.com/s2/favicons?domain=pelisplushd.net&sz=24" +} \ No newline at end of file diff --git a/PelisplusHDProvider/src/main/AndroidManifest.xml b/PelisplusHDProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/PelisplusHDProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProvider.kt b/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProvider.kt new file mode 100644 index 0000000..cf13381 --- /dev/null +++ b/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProvider.kt @@ -0,0 +1,173 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element + +class PelisplusHDProvider:MainAPI() { + override var mainUrl = "https://pelisplushd.net" + override var name = "PelisplusHD" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val items = ArrayList() + val document = app.get(mainUrl).document + val map = mapOf( + "Películas" to "#default-tab-1", + "Series" to "#default-tab-2", + "Anime" to "#default-tab-3", + "Doramas" to "#default-tab-4", + ) + map.forEach { + items.add(HomePageList( + it.key, + document.select(it.value).select("a.Posters-link").map { element -> + element.toSearchResult() + } + )) + } + return HomePageResponse(items) + } + private fun Element.toSearchResult(): SearchResponse { + val title = this.select(".listing-content p").text() + val href = this.select("a").attr("href") + val posterUrl = this.select(".Posters-img").attr("src") + val isMovie = href.contains("/pelicula/") + return if (isMovie) { + MovieSearchResponse( + title, + href, + name, + TvType.Movie, + posterUrl, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + name, + TvType.Movie, + posterUrl, + null, + null + ) + } + } + + override suspend fun search(query: String): List { + val url = "https://pelisplushd.net/search?s=${query}" + val document = app.get(url).document + + return document.select("a.Posters-link").map { + val title = it.selectFirst(".listing-content p")!!.text() + val href = it.selectFirst("a")!!.attr("href") + val image = it.selectFirst(".Posters-img")!!.attr("src") + val isMovie = href.contains("/pelicula/") + + if (isMovie) { + MovieSearchResponse( + title, + href, + this.name, + TvType.Movie, + image, + null + ) + } else { + TvSeriesSearchResponse( + title, + href, + this.name, + TvType.TvSeries, + image, + null, + null + ) + } + } + } + + override suspend fun load(url: String): LoadResponse? { + val soup = app.get(url, timeout = 120).document + + val title = soup.selectFirst(".m-b-5")!!.text() + val description = soup.selectFirst("div.text-large")?.text()?.trim() + val poster: String? = soup.selectFirst(".img-fluid")!!.attr("src") + val episodes = soup.select("div.tab-pane .btn").map { li -> + val href = li.selectFirst("a")!!.attr("href") + val name = li.selectFirst(".btn-primary.btn-block")!!.text() + val seasonid = href.replace("/capitulo/","-") + .replace(Regex("$mainUrl/.*/.*/temporada/"),"").let { str -> + str.split("-").mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid.size == 2 + val episode = if (isValid) seasonid.getOrNull(1) else null + val season = if (isValid) seasonid.getOrNull(0) else null + Episode( + href, + name, + season, + episode, + ) + } + + val year = soup.selectFirst(".p-r-15 .text-semibold")!!.text().toIntOrNull() + val tvType = if (url.contains("/pelicula/")) TvType.Movie else TvType.TvSeries + val tags = soup.select(".p-h-15.text-center a span.font-size-18.text-info.text-semibold") + .map { it?.text()?.trim().toString().replace(", ","") } + + return when (tvType) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + poster, + year, + description, + null, + null, + tags, + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + url, + poster, + year, + description, + null, + tags, + ) + } + else -> null + } + } + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("div.player > script").map { script -> + fetchUrls(script.data().replace("https://pelisplushd.net/fembed.php?url=","https://www.fembed.com/v/")).apmap { + loadExtractor(it, data, subtitleCallback, callback) + } + } + return true + } +} diff --git a/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProviderPlugin.kt b/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProviderPlugin.kt new file mode 100644 index 0000000..ead1a6a --- /dev/null +++ b/PelisplusHDProvider/src/main/kotlin/com/lagradost/PelisplusHDProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class PelisplusHDProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(PelisplusHDProvider()) + } +} \ No newline at end of file diff --git a/PelisplusProvider/build.gradle.kts b/PelisplusProvider/build.gradle.kts new file mode 100644 index 0000000..94afecc --- /dev/null +++ b/PelisplusProvider/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + + iconUrl = "https://www.google.com/s2/favicons?domain=pelisplus.icu&sz=24" +} \ No newline at end of file diff --git a/PelisplusProvider/src/main/AndroidManifest.xml b/PelisplusProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/PelisplusProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProvider.kt b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProvider.kt new file mode 100644 index 0000000..9e8617b --- /dev/null +++ b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProvider.kt @@ -0,0 +1,26 @@ +package com.lagradost + +import com.lagradost.cloudstream3.TvType + +/** Needs to inherit from MainAPI() to + * make the app know what functions to call + */ +class PelisplusProvider : PelisplusProviderTemplate() { + // mainUrl is good to have as a holder for the url to make future changes easier. + override var mainUrl = "https://pelisplus.icu" + + // name is for how the provider will be named which is visible in the UI, no real rules for this. + override var name = "Pelisplus" + + override val homePageUrlList = listOf( + mainUrl, + "$mainUrl/movies", + "$mainUrl/series", + "$mainUrl/new-season", + "$mainUrl/popular" + ) + + // This is just extra metadata about what type of movies the provider has. + // Needed for search functionality. + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) +} diff --git a/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderPlugin.kt b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderPlugin.kt new file mode 100644 index 0000000..e33bd42 --- /dev/null +++ b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class PelisplusProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(PelisplusProvider()) + } +} \ No newline at end of file diff --git a/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderTemplate.kt b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderTemplate.kt new file mode 100644 index 0000000..2fab513 --- /dev/null +++ b/PelisplusProvider/src/main/kotlin/com/lagradost/PelisplusProviderTemplate.kt @@ -0,0 +1,260 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.Jsoup + + +/** Needs to inherit from MainAPI() to + * make the app know what functions to call + */ + +open class PelisplusProviderTemplate : MainAPI() { + override var lang = "es" + open val homePageUrlList = listOf() + +// // mainUrl is good to have as a holder for the url to make future changes easier. +// override val mainUrl: String +// get() = "https://vidembed.cc" +// +// // name is for how the provider will be named which is visible in the UI, no real rules for this. +// override val name: String +// get() = "VidEmbed" + + // hasQuickSearch defines if quickSearch() should be called, this is only when typing the searchbar + // gives results on the site instead of bringing you to another page. + // if hasQuickSearch is true and quickSearch() hasn't been overridden you will get errors. + // VidEmbed actually has quick search on their site, but the function wasn't implemented. + override val hasQuickSearch = false + + // If getMainPage() is functional, used to display the homepage in app, an optional, but highly encouraged endevour. + override val hasMainPage = true + + // Searching returns a SearchResponse, which can be one of the following: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse + // Each of the classes requires some different data, but always has some critical things like name, poster and url. + + override suspend fun search(query: String): ArrayList { + // Simply looking at devtools network is enough to spot a request like: + // https://vidembed.cc/search.html?keyword=neverland where neverland is the query, can be written as below. + val link = "$mainUrl/search.html?keyword=$query" + val html = app.get(link).text + val soup = Jsoup.parse(html) + + return ArrayList(soup.select(".listing.items > .video-block").map { li -> + // Selects the href in + val href = fixUrl(li.selectFirst("a")!!.attr("href")) + val poster = fixUrl(li.selectFirst("img")!!.attr("src")) + + // .text() selects all the text in the element, be careful about doing this while too high up in the html hierarchy + val title = cleanName(li.selectFirst(".name")!!.text()) + // Use get(0) and toIntOrNull() to prevent any possible crashes, [0] or toInt() will error the search on unexpected values. + val year = li.selectFirst(".date")?.text()?.split("-")?.get(0)?.toIntOrNull() + + TvSeriesSearchResponse( + // .trim() removes unwanted spaces in the start and end. + if (!title.contains("Episode")) title else title.split("Episode")[0].trim(), + href, + this.name, + TvType.TvSeries, + poster, year, + // You can't get the episodes from the search bar. + null + ) + }) + } + + + // Load, like the name suggests loads the info page, where all the episodes and data usually is. + // Like search you should return either of: AnimeLoadResponse, MovieLoadResponse, TorrentLoadResponse, TvSeriesLoadResponse. + override suspend fun load(url: String): LoadResponse? { + // Gets the url returned from searching. + val html = app.get(url).text + val soup = Jsoup.parse(html) + + val title = cleanName(soup.selectFirst("h1,h2,h3")!!.text()) + val description = soup.selectFirst(".post-entry")?.text()?.trim() + val poster = soup.selectFirst("head meta[property=og:image]")!!.attr("content") + + var year : Int? = null + val episodes = soup.select(".listing.items.lists > .video-block").map { li -> + val href = fixUrl(li.selectFirst("a")!!.attr("href")) + val regexseason = Regex("(-[Tt]emporada-(\\d+)-[Cc]apitulo-(\\d+))") + val aaa = regexseason.find(href)?.destructured?.component1()?.replace(Regex("(-[Tt]emporada-|[Cc]apitulo-)"),"") + val seasonid = aaa.let { str -> + str?.split("-")?.mapNotNull { subStr -> subStr.toIntOrNull() } + } + val isValid = seasonid?.size == 2 + val episode = if (isValid) seasonid?.getOrNull(1) else null + val season = if (isValid) seasonid?.getOrNull(0) else null + val epThumb = fixUrl(li.selectFirst("img")!!.attr("src")) + val epDate = li.selectFirst(".meta > .date")!!.text() + + if(year == null) { + year = epDate?.split("-")?.get(0)?.toIntOrNull() + } + + newEpisode(li.selectFirst("a")!!.attr("href")) { + this.season = season + this.episode = episode + this.posterUrl = epThumb + addDate(epDate) + } + }.reversed() + + // Make sure to get the type right to display the correct UI. + val tvType = if (episodes.size == 1 && episodes[0].name == title) TvType.Movie else TvType.TvSeries + + return when (tvType) { + TvType.TvSeries -> { + TvSeriesLoadResponse( + title, + url, + this.name, + tvType, + episodes, + fixUrl(poster), + year, + description, + null, + null, + null + ) + } + TvType.Movie -> { + MovieLoadResponse( + title, + url, + this.name, + tvType, + episodes[0].data, + fixUrl(poster), + year, + description, + null, + null + ) + } + else -> null + } + } + + // This loads the homepage, which is basically a collection of search results with labels. + // Optional function, but make sure to enable hasMainPage if you program this. + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val urls = homePageUrlList + val homePageList = ArrayList() + // .pmap {} is used to fetch the different pages in parallel + urls.apmap { url -> + val response = app.get(url, timeout = 20).text + val document = Jsoup.parse(response) + document.select("div.main-inner")?.forEach { inner -> + // Always trim your text unless you want the risk of spaces at the start or end. + val title = cleanName(inner.select(".widget-title").text()) + val elements = inner.select(".video-block").map { + val link = fixUrl(it.select("a").attr("href")) + val image = it.select(".picture > img").attr("src").replace("//img", "https://img") + val name = cleanName(it.select("div.name").text()) + val isSeries = (name.contains("Temporada") || name.contains("Capítulo")) + + if (isSeries) { + TvSeriesSearchResponse( + name, + link, + this.name, + TvType.TvSeries, + image, + null, + null, + ) + } else { + MovieSearchResponse( + name, + link, + this.name, + TvType.Movie, + image, + null, + null, + ) + } + } + + homePageList.add( + HomePageList( + title, elements + ) + ) + + } + + } + return HomePageResponse(homePageList) + } + + + private fun cleanName(input: String): String = input.replace(Regex("([Tt]emporada (\\d+)|[Cc]apítulo (\\d+))|[Tt]emporada|[Cc]apítulo"),"").trim() + + + private suspend fun getPelisStream( + link: String, + callback: (ExtractorLink) -> Unit) : Boolean { + val soup = app.get(link).text + val m3u8regex = Regex("((https:|http:)\\/\\/.*m3u8.*expiry=(\\d+))") + val m3u8 = m3u8regex.find(soup)?.value ?: return false + + M3u8Helper.generateM3u8( + name, + m3u8, + mainUrl, + headers = mapOf("Referer" to mainUrl) + ).forEach (callback) + + return true + } + + // loadLinks gets the raw .mp4 or .m3u8 urls from the data parameter in the episodes class generated in load() + // See Episode(...) in this provider. + // The data are usually links, but can be any other string to help aid loading the links. + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + // These callbacks are functions you should call when you get a link to a subtitle file or media file. + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val doc = app.get(data).document + val info = doc.select("div.tabs-video li").text() + if (info.contains("Latino")) { + doc.select(".server-item-1 li").apmap { + val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") + loadExtractor(serverid, data, subtitleCallback, callback) + if (serverid.contains("pelisplus.icu")) { + getPelisStream(serverid, callback) + } + } + } + + if (info.contains("Subtitulado")) { + doc.select(".server-item-0 li").apmap { + val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") + loadExtractor(serverid, data, subtitleCallback, callback) + if (serverid.contains("pelisplus.icu")) { + getPelisStream(serverid, callback) + } + } + } + + if (info.contains("Castellano")) { + doc.select(".server-item-2 li").apmap { + val serverid = fixUrl(it.attr("data-video")).replace("streaming.php","play") + loadExtractor(serverid, data, subtitleCallback, callback) + if (serverid.contains("pelisplus.icu")) { + getPelisStream(serverid, callback) + } + } + } + return true + } +} diff --git a/SeriesflixProvider/build.gradle.kts b/SeriesflixProvider/build.gradle.kts new file mode 100644 index 0000000..5a9960d --- /dev/null +++ b/SeriesflixProvider/build.gradle.kts @@ -0,0 +1,25 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + // authors = listOf("Cloudburst") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "TvSeries", + "Movie", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=seriesflix.video&sz=24" +} \ No newline at end of file diff --git a/SeriesflixProvider/src/main/AndroidManifest.xml b/SeriesflixProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/SeriesflixProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProvider.kt b/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProvider.kt new file mode 100644 index 0000000..1d8930c --- /dev/null +++ b/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProvider.kt @@ -0,0 +1,226 @@ +package com.lagradost + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor + +class SeriesflixProvider : MainAPI() { + override var mainUrl = "https://seriesflix.video" + override var name = "Seriesflix" + override var lang = "es" + override val hasMainPage = true + override val hasChromecastSupport = true + override val hasDownloadSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + val items = ArrayList() + val urls = listOf( + Pair("$mainUrl/ver-series-online/", "Series"), + Pair("$mainUrl/genero/accion/", "Acción"), + Pair("$mainUrl/genero/ciencia-ficcion/", "Ciencia ficción"), + ) + for (i in urls) { + try { + val soup = app.get(i.first).document + val home = soup.select("article.TPost.B").map { + val title = it.selectFirst("h2.title")!!.text() + val link = it.selectFirst("a")!!.attr("href") + TvSeriesSearchResponse( + title, + link, + this.name, + TvType.Movie, + it.selectFirst("figure img")!!.attr("src"), + null, + null, + ) + } + + items.add(HomePageList(i.second, home)) + } catch (e: Exception) { + logError(e) + } + } + if (items.size <= 0) throw ErrorLoadingException() + return HomePageResponse(items) + } + + override suspend fun search(query: String): List { + val url = "$mainUrl/?s=$query" + val doc = app.get(url).document + return doc.select("article.TPost.B").map { + val href = it.selectFirst("a")!!.attr("href") + val poster = it.selectFirst("figure img")!!.attr("src") + val name = it.selectFirst("h2.title")!!.text() + val isMovie = href.contains("/movies/") + if (isMovie) { + MovieSearchResponse( + name, + href, + this.name, + TvType.Movie, + poster, + null + ) + } else { + TvSeriesSearchResponse( + name, + href, + this.name, + TvType.TvSeries, + poster, + null, + null + ) + } + }.toList() + } + + + override suspend fun load(url: String): LoadResponse { + val type = if (url.contains("/movies/")) TvType.Movie else TvType.TvSeries + + val document = app.get(url).document + + val title = document.selectFirst("h1.Title")!!.text() + val descRegex = Regex("(Recuerda.*Seriesflix.)") + val descipt = document.selectFirst("div.Description > p")!!.text().replace(descRegex, "") + val rating = + document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toRatingInt() + val year = document.selectFirst("span.Date")?.text() + // ?: does not work + val duration = try { + document.selectFirst("span.Time")!!.text() + } catch (e: Exception) { + null + } + val postercss = document.selectFirst("head").toString() + val posterRegex = + Regex("(\"og:image\" content=\"https://seriesflix.video/wp-content/uploads/(\\d+)/(\\d+)/?.*.jpg)") + val poster = try { + posterRegex.findAll(postercss).map { + it.value.replace("\"og:image\" content=\"", "") + }.toList().first() + } catch (e: Exception) { + document.select(".TPostBg").attr("src") + } + + if (type == TvType.TvSeries) { + val list = ArrayList>() + + document.select("main > section.SeasonBx > div > div.Title > a").forEach { element -> + val season = element.selectFirst("> span")?.text()?.toIntOrNull() + val href = element.attr("href") + if (season != null && season > 0 && !href.isNullOrBlank()) { + list.add(Pair(season, fixUrl(href))) + } + } + if (list.isEmpty()) throw ErrorLoadingException("No Seasons Found") + + val episodeList = ArrayList() + + for (season in list) { + val seasonDocument = app.get(season.second).document + val episodes = seasonDocument.select("table > tbody > tr") + if (episodes.isNotEmpty()) { + episodes.forEach { episode -> + val epNum = episode.selectFirst("> td > span.Num")?.text()?.toIntOrNull() + val epthumb = episode.selectFirst("img")?.attr("src") + val aName = episode.selectFirst("> td.MvTbTtl > a") + val name = aName!!.text() + val href = aName!!.attr("href") + val date = episode.selectFirst("> td.MvTbTtl > span")?.text() + episodeList.add( + newEpisode(href) { + this.name = name + this.season = season.first + this.episode = epNum + this.posterUrl = fixUrlNull(epthumb) + addDate(date) + } + ) + } + } + } + return TvSeriesLoadResponse( + title, + url, + this.name, + type, + episodeList, + fixUrlNull(poster), + year?.toIntOrNull(), + descipt, + null, + rating + ) + } else { + return newMovieLoadResponse( + title, + url, + type, + url + ) { + posterUrl = fixUrlNull(poster) + this.year = year?.toIntOrNull() + this.plot = descipt + this.rating = rating + addDuration(duration) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + app.get(data).document.select("ul.ListOptions li").forEach { + val movieID = it.attr("data-id") + val serverID = it.attr("data-key") + val type = if (data.contains("movies")) 1 else 2 + val url = + "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value + val doc1 = app.get(url).document + doc1.select("div.Video iframe").apmap { + val iframe = it.attr("src") + val postkey = + iframe.replace("https://sc.seriesflix.video/index.php?h=", "") // this obtains + // djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY + app.post( + "https://sc.seriesflix.video/r.php", + headers = mapOf( + "Host" to "sc.seriesflix.video", + "User-Agent" to USER_AGENT, + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language" to "en-US,en;q=0.5", + "Content-Type" to "application/x-www-form-urlencoded", + "Origin" to "null", + "DNT" to "1", + "Alt-Used" to "sc.seriesflix.video", + "Connection" to "keep-alive", + "Upgrade-Insecure-Requests" to "1", + "Sec-Fetch-Dest" to "iframe", + "Sec-Fetch-Mode" to "navigate", + "Sec-Fetch-Site" to "same-origin", + "Sec-Fetch-User" to "?1", + ), + params = mapOf(Pair("h", postkey)), + data = mapOf(Pair("h", postkey)), + allowRedirects = false + ).okhttpResponse.headers.values("location").apmap { link -> + val url1 = link.replace("#bu", "") + loadExtractor(url1, data, subtitleCallback, callback) + } + } + } + return true + } +} diff --git a/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProviderPlugin.kt b/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProviderPlugin.kt new file mode 100644 index 0000000..ee822ae --- /dev/null +++ b/SeriesflixProvider/src/main/kotlin/com/lagradost/SeriesflixProviderPlugin.kt @@ -0,0 +1,14 @@ + +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class SeriesflixProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(SeriesflixProvider()) + } +} \ No newline at end of file