From 36e77332c55db47dceac753adcfbc2a5d71cd511 Mon Sep 17 00:00:00 2001 From: hexated Date: Fri, 15 Sep 2023 01:39:07 +0700 Subject: [PATCH] sora: added netflix --- NontonAnimeIDProvider/build.gradle.kts | 2 +- .../com/hexated/NontonAnimeIDProvider.kt | 6 +- Samehadaku/build.gradle.kts | 2 +- .../src/main/kotlin/com/hexated/Samehadaku.kt | 36 ++++++++--- SoraStream/build.gradle.kts | 2 +- .../src/main/kotlin/com/hexated/Extractors.kt | 16 ++++- .../main/kotlin/com/hexated/SoraExtractor.kt | 60 +++++++++++++++---- .../src/main/kotlin/com/hexated/SoraParser.kt | 33 ++++++++++ .../src/main/kotlin/com/hexated/SoraStream.kt | 11 ++++ .../main/kotlin/com/hexated/SoraStreamLite.kt | 9 +++ .../kotlin/com/hexated/SoraStreamPlugin.kt | 2 + .../src/main/kotlin/com/hexated/SoraUtils.kt | 16 ++++- 12 files changed, 167 insertions(+), 28 deletions(-) diff --git a/NontonAnimeIDProvider/build.gradle.kts b/NontonAnimeIDProvider/build.gradle.kts index 14a56948..7cefbfbb 100644 --- a/NontonAnimeIDProvider/build.gradle.kts +++ b/NontonAnimeIDProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 17 +version = 18 cloudstream { diff --git a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt index 6f068fba..e3949a64 100644 --- a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt +++ b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt @@ -59,7 +59,7 @@ class NontonAnimeIDProvider : MainAPI() { document.select("aside#sidebar_right > div.side").forEach { block -> val header = block.selectFirst("h3")!!.ownText().trim() - val animes = block.select("ul li.fullwdth").mapNotNull { + val animes = block.select("div.bor").mapNotNull { it.toSearchResultPopular() } if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes)) @@ -71,7 +71,7 @@ class NontonAnimeIDProvider : MainAPI() { private fun Element.toSearchResult(): AnimeSearchResponse? { val href = fixUrl(this.selectFirst("a")!!.attr("href")) val title = this.selectFirst("h3.title")?.text() ?: return null - val posterUrl = fixUrl(this.select("img").attr("data-src")) + val posterUrl = fixUrl(this.select("img").attr("src")) return newAnimeSearchResponse(title, href, TvType.Anime) { this.posterUrl = posterUrl @@ -83,7 +83,7 @@ class NontonAnimeIDProvider : MainAPI() { private fun Element.toSearchResultPopular(): AnimeSearchResponse? { val href = fixUrl(this.selectFirst("a")!!.attr("href")) val title = this.selectFirst("h4")?.text()?.trim() ?: return null - val posterUrl = fixUrl(this.select("img").attr("data-src")) + val posterUrl = fixUrl(this.select("img").attr("src")) return newAnimeSearchResponse(title, href, TvType.Anime) { this.posterUrl = posterUrl diff --git a/Samehadaku/build.gradle.kts b/Samehadaku/build.gradle.kts index f3101866..a3eeec24 100644 --- a/Samehadaku/build.gradle.kts +++ b/Samehadaku/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 15 +version = 16 cloudstream { diff --git a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt index d97e5426..ce686341 100644 --- a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt +++ b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt @@ -4,9 +4,13 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.loadExtractor +import okhttp3.Interceptor +import okhttp3.Response +import org.jsoup.Jsoup import org.jsoup.nodes.Element class Samehadaku : MainAPI() { @@ -15,13 +19,26 @@ class Samehadaku : MainAPI() { override val hasMainPage = true override var lang = "id" override val hasDownloadSupport = true - + private val cloudflareKiller by lazy { CloudflareKiller() } + private val interceptor by lazy { CloudflareInterceptor(cloudflareKiller) } override val supportedTypes = setOf( TvType.Anime, TvType.AnimeMovie, TvType.OVA ) + class CloudflareInterceptor(private val cloudflareKiller: CloudflareKiller): Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + val doc = Jsoup.parse(response.peekBody(1024 * 1024).string()) + if (doc.select("title").text() == "Just a moment...") { + return cloudflareKiller.intercept(chain) + } + return response + } + } + companion object { const val acefile = "https://acefile.co" @@ -52,7 +69,7 @@ class Samehadaku : MainAPI() { val items = mutableListOf() if (request.name != "Episode Terbaru" && page <= 1) { - val doc = app.get(request.data).document + val doc = app.get(request.data, interceptor = interceptor).document doc.select("div.widget_senction:not(:contains(Baca Komik))").forEach { block -> val header = block.selectFirst("div.widget-title h3")?.ownText() ?: return@forEach val home = block.select("div.animepost").mapNotNull { @@ -64,7 +81,7 @@ class Samehadaku : MainAPI() { if (request.name == "Episode Terbaru") { val home = - app.get(request.data + page).document.selectFirst("div.post-show")?.select("ul li") + app.get(request.data + page, interceptor = interceptor).document.selectFirst("div.post-show")?.select("ul li") ?.mapNotNull { it.toSearchResult() } ?: throw ErrorLoadingException("No Media Found") @@ -84,12 +101,13 @@ class Samehadaku : MainAPI() { return newAnimeSearchResponse(title, href ?: return null, TvType.Anime) { this.posterUrl = posterUrl addSub(epNum) + posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap() } } override suspend fun search(query: String): List { - val document = app.get("$mainUrl/?s=$query").document + val document = app.get("$mainUrl/?s=$query", interceptor = interceptor).document return document.select("main#main div.animepost").mapNotNull { it.toSearchResult() } @@ -99,10 +117,10 @@ class Samehadaku : MainAPI() { val fixUrl = if (url.contains("/anime/")) { url } else { - app.get(url).document.selectFirst("div.nvs.nvsc a")?.attr("href") + app.get(url, interceptor = interceptor).document.selectFirst("div.nvs.nvsc a")?.attr("href") } - val document = app.get(fixUrl ?: return null).document + val document = app.get(fixUrl ?: return null, interceptor = interceptor).document val title = document.selectFirst("h1.entry-title")?.text()?.removeBloat() ?: return null val poster = document.selectFirst("div.thumb > img")?.attr("src") val tags = document.select("div.genre-info > a").map { it.text() } @@ -147,6 +165,7 @@ class Samehadaku : MainAPI() { this.recommendations = recommendations addMalId(tracker?.malId) addAniListId(tracker?.aniId?.toIntOrNull()) + posterHeaders = cloudflareKiller.getCookieHeaders(mainUrl).toMap() } } @@ -158,7 +177,7 @@ class Samehadaku : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - val document = app.get(data).document + val document = app.get(data, interceptor = interceptor).document argamap( { @@ -176,7 +195,8 @@ class Samehadaku : MainAPI() { "type" to dataType ), referer = data, - headers = mapOf("X-Requested-With" to "XMLHttpRequest") + headers = mapOf("X-Requested-With" to "XMLHttpRequest"), + interceptor = interceptor ).document.select("iframe").attr("src") loadFixedExtractor(fixedIframe(iframe), it.text(), "$mainUrl/", subtitleCallback, callback) diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 26983187..35b0760a 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 168 +version = 169 android { defaultConfig { diff --git a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt index ef393288..af9fdff7 100644 --- a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt +++ b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.extractors.Pixeldrain import com.lagradost.cloudstream3.utils.* import java.math.BigInteger +import java.net.URI import java.security.MessageDigest open class Playm4u : ExtractorApi() { @@ -150,7 +151,7 @@ open class VCloud : ExtractorApi() { val changedLink = doc.selectFirst("script:containsData(url =)")?.data()?.let { """url\s*=\s*['"](.*)['"];""".toRegex().find(it)?.groupValues?.get(1) ?.substringAfter("r=") - } + } ?: doc.selectFirst("div.div.vd.d-none a")?.attr("href") val header = doc.selectFirst("div.card-header")?.text() app.get( base64Decode(changedLink ?: return), cookies = res.cookies, headers = mapOf( @@ -158,7 +159,8 @@ open class VCloud : ExtractorApi() { ) ).document.select("p.text-success ~ a").apmap { val link = it.attr("href") - if (it.text().contains(Regex("Server : 1|2"))) { + val uri = URI(link) + if (uri.path.contains("workers.dev")) { callback.invoke( ExtractorLink( this.name, @@ -184,6 +186,16 @@ open class VCloud : ExtractorApi() { } +class HubcloudLol : VCloud() { + override val name = "Hubcloud" + override val mainUrl = "https://hubcloud.lol" +} + +class Hubcloud : VCloud() { + override val name = "Hubcloud" + override val mainUrl = "https://hubcloud.in" +} + class Pixeldra : Pixeldrain() { override val mainUrl = "https://pixeldra.in" } diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index a2928700..53f50a01 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -5,12 +5,7 @@ import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Session -import com.lagradost.cloudstream3.extractors.Filesim -import com.lagradost.cloudstream3.extractors.GMPlayer -import com.lagradost.cloudstream3.extractors.StreamSB -import com.lagradost.cloudstream3.extractors.Voe import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler -import com.lagradost.cloudstream3.extractors.helper.GogoHelper import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay @@ -319,7 +314,6 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, fixIframe: Boolean = false, encrypt: Boolean = false, - key: String? = null, ) { fun String.fixBloat() : String { return this.replace("\"", "").replace("\\", "") @@ -572,8 +566,8 @@ object SoraExtractor : SoraStream() { "${link.name} ${it.second}", link.url, link.referer, - when { - link.type == ExtractorLinkType.M3U8 -> link.quality + when (link.type) { + ExtractorLinkType.M3U8 -> link.quality else -> getQualityFromName(it.first) }, link.type, @@ -900,8 +894,8 @@ object SoraExtractor : SoraStream() { "${link.name} [${it.second}]", link.url, link.referer, - when { - link.type == ExtractorLinkType.M3U8 -> link.quality + when (link.type) { + ExtractorLinkType.M3U8 -> link.quality else -> it.third }, link.type, @@ -2756,6 +2750,52 @@ object SoraExtractor : SoraStream() { ) } + suspend fun invokeNetflix( + imdbId: String? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit, + ) { + val headers = mapOf("X-Requested-With" to "XMLHttpRequest", "Cookie" to "hd=on") + val netflixId = imdbToNetflixId(imdbId, season) + val (title, id) = app.get( + "$netflixAPI/post.php?id=${netflixId ?: return}&t=${APIHolder.unixTime}", + headers = headers + ).parsedSafe().let { media -> + if (season == null) { + media?.title to netflixId + } else { + val seasonId = media?.season?.find { it.s == "$season" }?.id + val episodeId = + app.get( + "$netflixAPI/episodes.php?s=${seasonId}&series=$netflixId&t=${APIHolder.unixTime}", + headers = headers + ) + .parsedSafe()?.episodes?.find { it.ep == "E$episode" }?.id + media?.title to episodeId + } + } + + app.get( + "$netflixAPI/playlist.php?id=${id ?: return}&t=${title ?: return}&tm=${APIHolder.unixTime}", + headers = headers + ).text.let { + tryParseJson>(it) + }?.firstOrNull()?.sources?.map { + callback.invoke( + ExtractorLink( + "Netflix", + "Netflix", + fixUrl(it.file ?: return@map, netflixAPI), + "$netflixAPI/", + getQualityFromName(it.file.substringAfter("q=")), + INFER_TYPE, + headers = mapOf("Cookie" to "hd=on") + ) + ) + } + + } } diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt index 483be230..e97f9ccb 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt @@ -428,3 +428,36 @@ data class EMovieTraks( data class FourCartoonSources( @JsonProperty("videoSource") val videoSource: String? = null, ) + +data class WatchhubStream( + @JsonProperty("name") val name: String? = null, + @JsonProperty("externalUrl") val externalUrl: String? = null, +) + +data class WatchhubResponse( + @JsonProperty("streams") val streams: ArrayList? = arrayListOf(), +) + +data class NetflixSources( + @JsonProperty("file") val file: String? = null, + @JsonProperty("label") val label: String? = null, +) + +data class NetflixEpisodes( + @JsonProperty("id") val id: String? = null, + @JsonProperty("t") val t: String? = null, + @JsonProperty("s") val s: String? = null, + @JsonProperty("ep") val ep: String? = null, +) + +data class NetflixSeason( + @JsonProperty("s") val s: String? = null, + @JsonProperty("id") val id: String? = null, +) + +data class NetflixResponse( + @JsonProperty("title") val title: String? = null, + @JsonProperty("season") val season: ArrayList? = arrayListOf(), + @JsonProperty("episodes") val episodes: ArrayList? = arrayListOf(), + @JsonProperty("sources") val sources: ArrayList? = arrayListOf(), +) diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index d8c31be6..6ec44cd5 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -41,6 +41,7 @@ import com.hexated.SoraExtractor.invokeFourCartoon import com.hexated.SoraExtractor.invokeJump1 import com.hexated.SoraExtractor.invokeMoment import com.hexated.SoraExtractor.invokeMultimovies +import com.hexated.SoraExtractor.invokeNetflix import com.hexated.SoraExtractor.invokeNetmovies import com.hexated.SoraExtractor.invokePobmovies import com.hexated.SoraExtractor.invokePrimewire @@ -79,6 +80,7 @@ open class SoraStream : TmdbProvider() { const val malsyncAPI = "https://api.malsync.moe" const val consumetHelper = "https://api.consumet.org/anime/9anime/helper" const val jikanAPI = "https://api.jikan.moe/v4" + const val watchhubApi = "https://watchhub.strem.io" private val apiKey = base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL @@ -131,6 +133,7 @@ open class SoraStream : TmdbProvider() { const val susflixAPI = "https://susflix.tv" const val jump1API = "https://ca.jump1.net" const val vegaMoviesAPI = "https://vegamovies.im" + const val netflixAPI = "https://m.netflixmirror.com" // INDEX SITE const val dahmerMoviesAPI = "https://edytjedhgmdhm.abfhaqrhbnf.workers.dev" @@ -738,6 +741,14 @@ open class SoraStream : TmdbProvider() { callback ) }, + { + if (!res.isAnime) invokeNetflix( + res.imdbId, + res.season, + res.episode, + callback + ) + } ) return true diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index 88c15f3e..ada25e6a 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -28,6 +28,7 @@ import com.hexated.SoraExtractor.invokeFourCartoon import com.hexated.SoraExtractor.invokeJump1 import com.hexated.SoraExtractor.invokeMoment import com.hexated.SoraExtractor.invokeMultimovies +import com.hexated.SoraExtractor.invokeNetflix import com.hexated.SoraExtractor.invokeNetmovies import com.hexated.SoraExtractor.invokePrimewire import com.hexated.SoraExtractor.invokeVidSrc @@ -327,6 +328,14 @@ class SoraStreamLite : SoraStream() { callback ) }, + { + if (!res.isAnime) invokeNetflix( + res.imdbId, + res.season, + res.episode, + callback + ) + } ) return true diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt index 7e2e5914..22d8b615 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt @@ -20,5 +20,7 @@ class SoraStreamPlugin: Plugin() { registerExtractorAPI(Playm4u()) registerExtractorAPI(VCloud()) registerExtractorAPI(Pixeldra()) + registerExtractorAPI(Hubcloud()) + registerExtractorAPI(HubcloudLol()) } } \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index f9d02a50..b1187efa 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -13,6 +13,7 @@ import com.hexated.SoraStream.Companion.malsyncAPI import com.hexated.SoraStream.Companion.smashyStreamAPI import com.hexated.SoraStream.Companion.tvMoviesAPI import com.hexated.SoraStream.Companion.watchOnlineAPI +import com.hexated.SoraStream.Companion.watchhubApi import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.APIHolder.unixTimeMS @@ -1139,6 +1140,17 @@ suspend fun tmdbToAnimeId(title: String?, year: Int?, season: String?, type: TvT } +suspend fun imdbToNetflixId(imdbId: String?, season: Int?): String? { + val url = if (season == null) { + "$watchhubApi/stream/movie/$imdbId.json" + } else { + "$watchhubApi/stream/series/$imdbId:1:1.json" + } + return app.get(url) + .parsedSafe()?.streams?.find { it.name == "Netflix" }?.externalUrl + ?.substringAfterLast("/") +} + suspend fun loadCustomExtractor( name: String? = null, url: String, @@ -1154,8 +1166,8 @@ suspend fun loadCustomExtractor( name ?: link.name, link.url, link.referer, - when { - link.type == ExtractorLinkType.M3U8 -> link.quality + when (link.type) { + ExtractorLinkType.M3U8 -> link.quality else -> quality ?: link.quality }, link.type,