diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5e17d0d..c7297f57 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,12 +19,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v4 with: path: "src" - name: Checkout builds - uses: actions/checkout@master + uses: actions/checkout@v4 with: ref: "builds" path: "builds" @@ -33,8 +33,9 @@ jobs: run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true - name: Setup JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: "adopt" java-version: 11 - name: Setup Android SDK @@ -54,7 +55,11 @@ jobs: ZSHOW_API: ${{ secrets.ZSHOW_API }} SFMOVIES_API: ${{ secrets.SFMOVIES_API }} CINEMATV_API: ${{ secrets.CINEMATV_API }} - OMOVIES_API: ${{ secrets.OMOVIES_API }} + GHOSTX_API: ${{ secrets.GHOSTX_API }} + SUPERSTREAM_FIRST_API: ${{ secrets.SUPERSTREAM_FIRST_API }} + SUPERSTREAM_SECOND_API: ${{ secrets.SUPERSTREAM_SECOND_API }} + SUPERSTREAM_THIRD_API: ${{ secrets.SUPERSTREAM_THIRD_API }} + SUPERSTREAM_FOURTH_API: ${{ secrets.SUPERSTREAM_FOURTH_API }} run: | cd $GITHUB_WORKSPACE/src echo TMDB_API=$TMDB_API >> local.properties @@ -69,7 +74,11 @@ jobs: echo ZSHOW_API=$ZSHOW_API >> local.properties echo SFMOVIES_API=$SFMOVIES_API >> local.properties echo CINEMATV_API=$CINEMATV_API >> local.properties - echo OMOVIES_API=$OMOVIES_API >> local.properties + echo GHOSTX_API=$GHOSTX_API >> local.properties + echo SUPERSTREAM_FIRST_API=$SUPERSTREAM_FIRST_API >> local.properties + echo SUPERSTREAM_SECOND_API=$SUPERSTREAM_SECOND_API >> local.properties + echo SUPERSTREAM_THIRD_API=$SUPERSTREAM_THIRD_API >> local.properties + echo SUPERSTREAM_FOURTH_API=$SUPERSTREAM_FOURTH_API >> local.properties - name: Build Plugins run: | diff --git a/.gitignore b/.gitignore index f587ae0b..d34d3c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ .cxx local.properties .vscode -/ExampleProvider/ +/ExampleProvider/ \ No newline at end of file diff --git a/Anichi/build.gradle.kts b/Anichi/build.gradle.kts index 643809b0..4201a7b2 100644 --- a/Anichi/build.gradle.kts +++ b/Anichi/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 9 +version = 10 android { defaultConfig { @@ -38,5 +38,5 @@ cloudstream { "OVA", ) - iconUrl = "https://media.discordapp.net/attachments/1059306855865782282/1123970193274712096/Anichi.png" + iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1200425504432472176/Anichi.png" } \ No newline at end of file diff --git a/Anichi/src/main/kotlin/com/hexated/Anichi.kt b/Anichi/src/main/kotlin/com/hexated/Anichi.kt index bbad3ae7..0ddfae48 100644 --- a/Anichi/src/main/kotlin/com/hexated/Anichi.kt +++ b/Anichi/src/main/kotlin/com/hexated/Anichi.kt @@ -1,6 +1,5 @@ package com.hexated -import com.hexated.AnichiExtractors.invokeExternalSources import com.hexated.AnichiExtractors.invokeInternalSources import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addActors @@ -214,25 +213,12 @@ open class Anichi : MainAPI() { val loadData = parseJson(data) - argamap( - { - invokeInternalSources( - loadData.hash, - loadData.dubStatus, - loadData.episode, - subtitleCallback, - callback - ) - }, - { - invokeExternalSources( - loadData.idMal, - loadData.dubStatus, - loadData.episode, - subtitleCallback, - callback - ) - } + invokeInternalSources( + loadData.hash, + loadData.dubStatus, + loadData.episode, + subtitleCallback, + callback ) return true @@ -245,7 +231,6 @@ open class Anichi : MainAPI() { const val anilistApi = "https://graphql.anilist.co" const val jikanApi = "https://api.jikan.moe/v4" - const val marinHost = "https://marin.moe" private const val mainHash = "e42a4466d984b2c0a2cecae5dd13aa68867f634b16ee0f17b380047d14482406" private const val popularHash = "31a117653812a2547fd981632e8c99fa8bf8a75c4ef1a77a1567ef1741a7ab9c" diff --git a/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt b/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt index 86d452ed..a41b2c54 100644 --- a/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt +++ b/Anichi/src/main/kotlin/com/hexated/AnichiExtractors.kt @@ -121,67 +121,6 @@ object AnichiExtractors : Anichi() { } } - suspend fun invokeExternalSources( - idMal: Int? = null, - dubStatus: String, - episode: String, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit, - ) { - val ids = app.get("https://api.malsync.moe/mal/anime/${idMal ?: return}") - .parsedSafe()?.sites - - if (dubStatus == "sub") invokeMarin(ids?.marin?.keys?.firstOrNull(), episode, callback) - - } - - private suspend fun invokeMarin( - id: String? = null, - episode: String, - callback: (ExtractorLink) -> Unit - ) { - val url = "$marinHost/anime/${id ?: return}/$episode" - val cookies = app.get( - "$marinHost/anime", - headers = mapOf( - "Cookie" to "__ddg1_=;__ddg2_=;" - ), - referer = "$marinHost/anime", - ).cookies.let { - decode(it["XSRF-TOKEN"].toString()) to decode(it["marin_session"].toString()) - } - - val json = app.get( - url, - headers = mapOf( - "Accept" to "text/html, application/xhtml+xml", - "Cookie" to "__ddg1=;__ddg2_=;XSRF-TOKEN=${cookies.first};marin_session=${cookies.second};", - "X-XSRF-TOKEN" to cookies.first - ), - referer = "$marinHost/anime/$id" - ).document.selectFirst("div#app")?.attr("data-page") - tryParseJson(json)?.props?.video?.data?.mirror?.map { video -> - callback.invoke( - ExtractorLink( - "Marin", - "Marin", - video.code?.file ?: return@map, - url, - video.code.height ?: Qualities.Unknown.value, - headers = mapOf( - "Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5", - "Accept-Language" to "en-US,en;q=0.5", - "Cookie" to "__ddg1=;__ddg2_=; XSRF-TOKEN=${cookies.first}; marin_session=${cookies.second};", - "Connection" to "keep-alive", - "Sec-Fetch-Dest" to "video", - "Sec-Fetch-Mode" to "cors", - "Sec-Fetch-Site" to "cross-site", - ) - ) - ) - } - } - private suspend fun invokeGogo( link: String, subtitleCallback: (SubtitleFile) -> Unit, diff --git a/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt b/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt index 6cbe56ec..8b9165eb 100644 --- a/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt +++ b/Anichi/src/main/kotlin/com/hexated/AnichiParser.kt @@ -240,7 +240,6 @@ data class PageStatus( @JsonProperty("__typename") val _typename: String? = null ) - data class Recommendations( @JsonProperty("anyCard") val anyCard: AnyCard? = null, @JsonProperty("pageStatus") val pageStatus: PageStatus? = PageStatus(), @@ -255,38 +254,4 @@ data class QueryPopular( data class DataPopular( @JsonProperty("queryPopular") val queryPopular: QueryPopular? = QueryPopular() -) - -data class MALSyncSites( - @JsonProperty("Zoro") val zoro: HashMap>? = hashMapOf(), - @JsonProperty("Marin") val marin: HashMap>? = hashMapOf(), -) - -data class MALSyncResponses( - @JsonProperty("Sites") val sites: MALSyncSites? = null, -) - -data class MarinCode( - @JsonProperty("file") val file: String? = null, - @JsonProperty("height") val height: Int? = null, -) - -data class MarinMirror( - @JsonProperty("code") val code: MarinCode? = null, -) - -data class MarinData( - @JsonProperty("mirror") val mirror: ArrayList? = arrayListOf(), -) - -data class MarinVideos( - @JsonProperty("data") val data: MarinData? = null, -) - -data class MarinProps( - @JsonProperty("video") val video: MarinVideos? = null, -) - -data class MarinResponses( - @JsonProperty("props") val props: MarinProps? = null, ) \ No newline at end of file diff --git a/Anilibria/build.gradle.kts b/Anilibria/build.gradle.kts index 061d5f39..f29ed62f 100644 --- a/Anilibria/build.gradle.kts +++ b/Anilibria/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 2 +version = 3 cloudstream { diff --git a/Anilibria/src/main/kotlin/com/hexated/Anilibria.kt b/Anilibria/src/main/kotlin/com/hexated/Anilibria.kt index c51afb3c..254fdade 100644 --- a/Anilibria/src/main/kotlin/com/hexated/Anilibria.kt +++ b/Anilibria/src/main/kotlin/com/hexated/Anilibria.kt @@ -84,17 +84,13 @@ class Anilibria : MainAPI() { val document = app.get(url).document val title = document.selectFirst("h1.release-title")?.text() ?: return null - val poster = fixUrlNull(document.selectFirst("img#adminPoster")?.attr("src")) - val trackTitle = (document.selectFirst("h1.release-title br")?.nextSibling() + val enTitle = (document.selectFirst("h1.release-title br")?.nextSibling() ?: document.selectFirst("h1.release-title")?.text()?.substringAfter("/")?.trim()).toString() + val poster = fixUrlNull(document.selectFirst("img#adminPoster")?.attr("src")) val type = document.selectFirst("div#xreleaseInfo b:contains(Тип:)")?.nextSibling() .toString().substringBefore(",").trim() - val trackType = type.let { - if(it.contains("Фильм", true)) "movie" else "tv" - } val year = document.selectFirst("div#xreleaseInfo b:contains(Сезон:)")?.nextElementSibling() ?.text()?.filter { it.isDigit() }?.toIntOrNull() - val (malId, anilistId, image, cover) = getTracker(trackTitle, trackType, year) val episodes = document.select("script").find { it.data().contains("var player =") }?.data() ?.substringAfter("file:[")?.substringBefore("],")?.let { data -> tryParseJson>("[$data]")?.mapNotNull { eps -> @@ -106,15 +102,14 @@ class Anilibria : MainAPI() { } } return newAnimeLoadResponse(title, url, getType(type)) { - posterUrl = image ?: poster - backgroundPosterUrl = cover ?: image ?: poster + japName = enTitle + posterUrl = poster + backgroundPosterUrl = poster this.year = year addEpisodes(DubStatus.Subbed, episodes) plot = document.select("p.detail-description").text().trim() this.tags = document.selectFirst("div#xreleaseInfo b:contains(Жанры:)")?.nextSibling() .toString().split(",").map { it.trim() } - addMalId(malId) - addAniListId(anilistId?.toIntOrNull()) } } @@ -143,43 +138,6 @@ class Anilibria : MainAPI() { return true } - private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker { - val res = app.get("https://api.consumet.org/meta/anilist/$title") - .parsedSafe()?.results?.find { media -> - (media.title?.english.equals(title, true) || media.title?.romaji.equals( - title, - true - )) || (media.type.equals(type, true) && media.releaseDate == year) - } - return Tracker(res?.malId, res?.aniId, res?.image, res?.cover) - } - - data class Tracker( - val malId: Int? = null, - val aniId: String? = null, - val image: String? = null, - val cover: String? = null, - ) - - data class Title( - @JsonProperty("romaji") val romaji: String? = null, - @JsonProperty("english") val english: String? = null, - ) - - data class Results( - @JsonProperty("id") val aniId: String? = null, - @JsonProperty("malId") val malId: Int? = null, - @JsonProperty("title") val title: Title? = null, - @JsonProperty("releaseDate") val releaseDate: Int? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("image") val image: String? = null, - @JsonProperty("cover") val cover: String? = null, - ) - - data class AniSearch( - @JsonProperty("results") val results: ArrayList? = arrayListOf(), - ) - private data class Episodes( @JsonProperty("file") val file: String? = null, @JsonProperty("title") val title: String? = null, @@ -194,4 +152,4 @@ class Anilibria : MainAPI() { @JsonProperty("mes") val mes: String? = null, ) -} \ No newline at end of file +} diff --git a/AnimeDekhoProvider/build.gradle.kts b/AnimeDekhoProvider/build.gradle.kts new file mode 100644 index 00000000..38570fc9 --- /dev/null +++ b/AnimeDekhoProvider/build.gradle.kts @@ -0,0 +1,23 @@ +version = 4 + +cloudstream { + language = "hi" + authors = listOf("anon") + + /** + * 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", + "Anime", + "Cartoon" + ) + + iconUrl = "https://animedekho.com/wp-content/uploads/2023/07/AnimeDekho-Logo-300x-1.pngg" +} diff --git a/AnimeDekhoProvider/src/main/AndroidManifest.xml b/AnimeDekhoProvider/src/main/AndroidManifest.xml new file mode 100644 index 00000000..01dbafa1 --- /dev/null +++ b/AnimeDekhoProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoPlugin.kt b/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoPlugin.kt new file mode 100644 index 00000000..78711060 --- /dev/null +++ b/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoPlugin.kt @@ -0,0 +1,13 @@ +package com.anon + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeDekhoPlugin: Plugin() { + override fun load(context: Context) { + registerMainAPI(AnimeDekhoProvider()) + //addExtractor(MultiQualityXYZ()) + } +} \ No newline at end of file diff --git a/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoProvider.kt b/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoProvider.kt new file mode 100644 index 00000000..c69a9c0c --- /dev/null +++ b/AnimeDekhoProvider/src/main/kotlin/com/anon/AnimeDekhoProvider.kt @@ -0,0 +1,148 @@ +package com.anon + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import org.jsoup.nodes.Element + +class AnimeDekhoProvider : MainAPI() { + override var mainUrl = "https://animedekho.com" + override var name = "Anime Dekho" + override val hasMainPage = true + override var lang = "hi" + override val hasDownloadSupport = true + private val serverUrl = "https://vidxstream.xyz" + + override val supportedTypes = + setOf( + TvType.Cartoon, + TvType.Anime, + TvType.AnimeMovie, + TvType.Movie, + ) + + override val mainPage = + mainPageOf( + "/series/" to "Series", + "/movie/" to "Movies", + "/category/anime/" to "Anime", + "/category/cartoon/" to "Cartoon", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest, + ): HomePageResponse { + val link = "$mainUrl${request.data}" + val document = app.get(link).document + val home = + document.select("article").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val href = this.selectFirst("a.lnk-blk")?.attr("href") ?: return null + val title = this.selectFirst("header h2")?.text() ?: "null" + val posterUrl = this.selectFirst("div figure img")?.attr("src") + + return newAnimeSearchResponse(title, Media(href, posterUrl).toJson(), TvType.Anime, false) { + this.posterUrl = posterUrl + addDubStatus(dubExist = true, subExist = true) + } + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/?s=$query").document + return document.select("ul[data-results] li article").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse { + + val media = parseJson(url) + val document = app.get(media.url).document + + val title = document.selectFirst("h1.entry-title")?.text()?.trim() + ?: document.selectFirst("meta[property=og:image:alt]")?.attr("content") ?: "No Title" + val poster = fixUrlNull(document.selectFirst("div.post-thumbnail figure img")?.attr("src") ?: media.poster) + val plot = document.selectFirst("div.entry-content p")?.text()?.trim() + ?: document.selectFirst("meta[name=twitter:description]")?.attr("content") + val year = (document.selectFirst("span.year")?.text()?.trim() + ?: document.selectFirst("meta[property=og:updated_time]")?.attr("content") + ?.substringBefore("-"))?.toIntOrNull() + val lst = document.select("ul.seasons-lst li") + + return if (lst.isEmpty()) { + newMovieLoadResponse(title, url, TvType.Movie, Media(media.url, mediaType = 1).toJson()) { + this.posterUrl = poster + this.plot = plot + this.year = year + } + } else { + val episodes = document.select("ul.seasons-lst li").mapNotNull { + val name = it.selectFirst("h3.title")?.text() ?: "null" + val href = it.selectFirst("a")?.attr("href") ?: return@mapNotNull null + Episode(Media(href, mediaType = 2).toJson(), name) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.plot = plot + this.year = year + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ): Boolean { + val media = parseJson(data) + val body = app.get(media.url).document.selectFirst("body")?.attr("class") ?: return false + val term = Regex("""(?:term|postid)-(\d+)""").find(body)?.groupValues?.get(1) ?: throw ErrorLoadingException("no id found") + val vidLink = app.get("$mainUrl/?trembed=0&trid=$term&trtype=${media.mediaType}") + .document.selectFirst("iframe")?.attr("src") + ?: throw ErrorLoadingException("no iframe found") + + val doc = app.get(vidLink).text + val master = Regex("""JScript[\w+]?\s*=\s*'([^']+)""").find(doc)!!.groupValues[1] + val decrypt = cryptoAESHandler(master, "a7igbpIApajDyNe".toByteArray(), false)?.replace("\\", "") + ?: throw ErrorLoadingException("error decrypting") + val vidFinal = Regex("""file:\s*"(https:[^"]+)"""").find(decrypt)!!.groupValues[1] + + val headers = + mapOf( + "accept" to "*/*", + "accept-language" to "en-US,en;q=0.5", + "Origin" to serverUrl, + "Accept-Encoding" to "gzip, deflate, br", + "Connection" to "keep-alive", + // "Referer" to "https://vidxstream.xyz/", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "cross-site", + "user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", + ) + + callback.invoke( + ExtractorLink( + source = "Toon", + name = "Toon", + url = vidFinal, + referer = "$serverUrl/", + quality = Qualities.Unknown.value, + isM3u8 = true, + headers = headers, + ), + ) + return true + } + + data class Media(val url: String, val poster: String? = null, val mediaType: Int? = null) +} \ No newline at end of file diff --git a/AnimeIndoProvider/build.gradle.kts b/AnimeIndoProvider/build.gradle.kts index af852c5c..9c838888 100644 --- a/AnimeIndoProvider/build.gradle.kts +++ b/AnimeIndoProvider/build.gradle.kts @@ -23,5 +23,5 @@ cloudstream { "Anime", ) - iconUrl = "https://www.google.com/s2/favicons?domain=animeindo.fun&sz=%size%" + iconUrl = "https://animeindo.quest/wp-content/uploads/2023/05/favicon-300x300.png" } \ No newline at end of file diff --git a/Cinemathek/build.gradle.kts b/Cinemathek/build.gradle.kts new file mode 100644 index 00000000..f8a7cf30 --- /dev/null +++ b/Cinemathek/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 2 + + +cloudstream { + language = "de" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * 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=cinemathek.net&sz=%size%" +} \ No newline at end of file diff --git a/Cinemathek/src/main/AndroidManifest.xml b/Cinemathek/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Cinemathek/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Cinemathek/src/main/kotlin/com/hexated/Cinemathek.kt b/Cinemathek/src/main/kotlin/com/hexated/Cinemathek.kt new file mode 100644 index 00000000..82640438 --- /dev/null +++ b/Cinemathek/src/main/kotlin/com/hexated/Cinemathek.kt @@ -0,0 +1,202 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.extractors.DoodLaExtractor +import com.lagradost.cloudstream3.extractors.Filesim +import com.lagradost.cloudstream3.utils.* +import org.jsoup.nodes.Element + +class Cinemathek : MainAPI() { + override var mainUrl = "https://cinemathek.net" + override var name = "Cinemathek" + override val hasMainPage = true + override var lang = "de" + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + ) + + override val mainPage = mainPageOf( + "filme" to "Filme", + "serien" to "TV Shows", + "episoden" to "Episodes", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val document = app.get("$mainUrl/${request.data}/page/$page/").document + val home = + document.select("div.items.full article, div#archive-content article").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse( + list = HomePageList( + name = request.name, + list = home, + request.data == "episoden" + ), + hasNext = true + ) + } + + private fun getProperLink(uri: String): String { + return when { + uri.contains("/episoden/") -> { + uri.replace(Regex("-\\d+x\\d+"), "").replace("/episoden/", "/serien/") + } + + else -> { + uri + } + } + } + + private fun Element.toSearchResult(): SearchResponse { + val title = this.selectFirst("h3 > a")!!.text() + val href = getProperLink(this.selectFirst("h3 > a")!!.attr("href")) + val posterUrl = this.select("div.poster > img").attr("src").toString() + val quality = getQualityFromString(this.select("span.quality").text()) + return newMovieSearchResponse(title, href, TvType.Movie) { + this.posterUrl = posterUrl + this.quality = quality + } + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/search/$query").document + return document.select("div.result-item").map { + val title = it.selectFirst("div.title > a")!!.text() + val href = getProperLink(it.selectFirst("div.title > a")!!.attr("href")) + val posterUrl = it.selectFirst("img")!!.attr("src").toString() + newMovieSearchResponse(title, href, TvType.TvSeries) { + this.posterUrl = posterUrl + } + } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + val title = document.selectFirst("div.data > h1")?.text() ?: "" + val poster = document.select("div.poster > img").attr("src").toString() + val tags = document.select("div.sgeneros > a").map { it.text() } + + val year = Regex(",\\s?(\\d+)").find( + document.select("span.date").text().trim() + )?.groupValues?.get(1).toString().toIntOrNull() + val tvType = if (document.select("ul#section > li:nth-child(1)").text().contains("Episodes") + ) TvType.TvSeries else TvType.Movie + val description = document.select("div.wp-content > p").text().trim() + val trailer = document.selectFirst("div.embed iframe")?.attr("src") + val rating = + document.selectFirst("span.dt_rating_vgs")?.text()?.toRatingInt() + val actors = document.select("div.persons > div[itemprop=actor]").map { + Actor(it.select("meta[itemprop=name]").attr("content"), it.select("img").attr("src")) + } + + val recommendations = document.select("div.owl-item").map { + val recName = + it.selectFirst("a")!!.attr("href").toString().removeSuffix("/").split("/").last() + val recHref = it.selectFirst("a")!!.attr("href") + val recPosterUrl = it.selectFirst("img")?.attr("src").toString() + newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) { + this.posterUrl = recPosterUrl + } + } + + return if (tvType == TvType.TvSeries) { + val episodes = document.select("ul.episodios > li").map { + val href = it.select("a").attr("href") + val name = fixTitle(it.select("div.episodiotitle > a").text().trim()) + val image = it.select("div.imagen > img").attr("src") + val episode = it.select("div.numerando").text().replace(" ", "").split("-").last() + .toIntOrNull() + val season = it.select("div.numerando").text().replace(" ", "").split("-").first() + .toIntOrNull() + Episode( + href, + name, + season, + episode, + image + ) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } else { + newMovieLoadResponse(title, url, TvType.Movie, url) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + addTrailer(trailer) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val document = app.get(data).document + document.select("ul#playeroptionsul > li").map { + Triple( + it.attr("data-post"), + it.attr("data-nume"), + it.attr("data-type") + ) + }.apmap { (id, nume, type) -> + val iframe = app.get( + url = "$mainUrl/wp-json/dooplayer/v2/$id/$type/$nume", + referer = data, + headers = mapOf("Accept" to "*/*", "X-Requested-With" to "XMLHttpRequest") + ).parsedSafe()?.embedUrl ?: return@apmap + + if (!iframe.contains("youtube")) loadExtractor(iframe, "$mainUrl/", subtitleCallback, callback) + } + + return true + } + + data class ResponseHash( + @JsonProperty("embed_url") val embedUrl: String, + ) + +} + +class StreamwishCom : Filesim() { + override val name = "Streamwish" + override var mainUrl = "https://streamwish.com" +} + +class Ds2play : DoodLaExtractor() { + override var name = "Ds2play" + override var mainUrl = "https://ds2play.com" +} + +class Do0od : DoodLaExtractor() { + override var name = "Do0od" + override var mainUrl = "https://do0od.com" +} + +class Filelions : Filesim() { + override val name = "Filelions" + override var mainUrl = "https://filelions.live" +} \ No newline at end of file diff --git a/Cinemathek/src/main/kotlin/com/hexated/CinemathekPlugin.kt b/Cinemathek/src/main/kotlin/com/hexated/CinemathekPlugin.kt new file mode 100644 index 00000000..3c7a7699 --- /dev/null +++ b/Cinemathek/src/main/kotlin/com/hexated/CinemathekPlugin.kt @@ -0,0 +1,18 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class CinemathekPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Cinemathek()) + registerExtractorAPI(StreamwishCom()) + registerExtractorAPI(Ds2play()) + registerExtractorAPI(Do0od()) + registerExtractorAPI(Filelions()) + } +} \ No newline at end of file diff --git a/DramaSerial/build.gradle.kts b/DramaSerial/build.gradle.kts index e00ab652..48e00692 100644 --- a/DramaSerial/build.gradle.kts +++ b/DramaSerial/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 5 +version = 6 cloudstream { diff --git a/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt b/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt index 1c1113dc..c5da4dda 100644 --- a/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt +++ b/DramaSerial/src/main/kotlin/com/hexated/DramaSerial.kt @@ -6,13 +6,14 @@ import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.INFER_TYPE +import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getQualityFromName -import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element +import java.net.URI class DramaSerial : MainAPI() { override var mainUrl = "https://tv3.dramaserial.id" - private var serverUrl = "http://31.220.73.179/" + private var serverUrl = "https://juraganfilm.info" override var name = "DramaSerial" override val hasMainPage = true override var lang = "id" @@ -72,8 +73,12 @@ class DramaSerial : MainAPI() { val duration = document.selectFirst("div.gmr-movie-innermeta span:contains(Duration:)")?.text() ?.filter { it.isDigit() }?.toIntOrNull() - val description = document.select("div.entry-content.entry-content-single div.entry-content.entry-content-single").text().trim() - val type = if(document.select("div.page-links").isNullOrEmpty()) TvType.Movie else TvType.AsianDrama + val description = + document.select("div.entry-content.entry-content-single div.entry-content.entry-content-single") + .text().trim() + val type = if (document.select("div.page-links") + .isNullOrEmpty() + ) TvType.Movie else TvType.AsianDrama if (type == TvType.Movie) { return newMovieLoadResponse(title, url, TvType.Movie, url) { @@ -84,18 +89,19 @@ class DramaSerial : MainAPI() { this.duration = duration } } else { - val episodes = document.select("div.page-links span.page-link-number").mapNotNull { eps -> - val episode = eps.text().filter { it.isDigit() }.toIntOrNull() - val link = if(episode == 1) { - url - } else { - eps.parent()?.attr("href") + val episodes = + document.select("div.page-links span.page-link-number").mapNotNull { eps -> + val episode = eps.text().filter { it.isDigit() }.toIntOrNull() + val link = if (episode == 1) { + url + } else { + eps.parent()?.attr("href") + } + Episode( + link ?: return@mapNotNull null, + episode = episode, + ) } - Episode( - link ?: return@mapNotNull null, - episode = episode, - ) - } return newTvSeriesLoadResponse(title, url, TvType.AsianDrama, episodes = episodes) { posterUrl = poster this.year = year @@ -107,6 +113,7 @@ class DramaSerial : MainAPI() { } private suspend fun invokeGetbk( + name: String, url: String, callback: (ExtractorLink) -> Unit ) { @@ -115,12 +122,12 @@ class DramaSerial : MainAPI() { referer = "$serverUrl/" ).document.selectFirst("script:containsData(sources)")?.data() ?: return - val json = "\"sources\":\\s*\\[(.*)]".toRegex().find(script)?.groupValues?.get(1) + val json = "sources:\\s*\\[(.*)]".toRegex().find(script)?.groupValues?.get(1) AppUtils.tryParseJson>("[$json]")?.map { callback.invoke( ExtractorLink( - "Getbk", - "Getbk", + name, + name, it.file ?: return@map, "$serverUrl/", getQualityFromName(it.label), @@ -131,6 +138,34 @@ class DramaSerial : MainAPI() { } + private suspend fun invokeGdrive( + name: String, + url: String, + callback: (ExtractorLink) -> Unit + ) { + + val embedUrl = app.get( + url, + referer = "$serverUrl/" + ).document.selectFirst("iframe")?.attr("src")?.let { fixUrl(it) } ?: return + + val req = app.get(embedUrl) + val host = getBaseUrl(embedUrl) + val token = req.document.selectFirst("div#token")?.text() ?: return + + callback.invoke( + ExtractorLink( + name, + name, + "$host/hlsplaylist.php?idhls=${token.trim()}.m3u8", + "$host/", + Qualities.Unknown.value, + true + ) + ) + + } + override suspend fun loadLinks( data: String, isCasting: Boolean, @@ -142,19 +177,23 @@ class DramaSerial : MainAPI() { val iframe = document.select("iframe[name=juraganfilm]").attr("src") app.get(iframe, referer = "$mainUrl/").document.select("div#header-slider ul li") .apmap { mLink -> - mLink.attr("onclick").substringAfter("frame('").substringBefore("')").let { iLink -> - val iMovie = iLink.substringAfter("movie=").substringBefore("&") - val mIframe = iLink.substringAfter("iframe=") - val iUrl = "$serverUrl/stream/$mIframe.php?movie=$iMovie" - if(mIframe == "getbk") { - invokeGetbk(iUrl, callback) - } else { - val link = app.get( - iUrl, - referer = "$serverUrl/" - ).document.selectFirst("iframe")?.attr("src") ?: return@apmap null - loadExtractor(fixUrl(link), "$serverUrl/", subtitleCallback, callback) + val iLink = mLink.attr("onclick").substringAfter("frame('").substringBefore("')") + serverUrl = getBaseUrl(iLink) + val iMovie = iLink.substringAfter("movie=").substringBefore("&") + val mIframe = iLink.substringAfter("iframe=") + val serverName = fixTitle(mIframe) + when (mIframe) { + "getbk" -> { + invokeGetbk( + serverName, + "$serverUrl/stream/$mIframe.php?movie=$iMovie", + callback + ) } + "gdrivehls", "gdriveplayer" -> { + invokeGdrive(serverName, iLink, callback) + } + else -> {} } } @@ -162,6 +201,12 @@ class DramaSerial : MainAPI() { } + private fun getBaseUrl(url: String): String { + return URI(url).let { + "${it.scheme}://${it.host}" + } + } + private data class Sources( @JsonProperty("file") val file: String? = null, @JsonProperty("label") val label: String? = null, diff --git a/DubokuProvider/build.gradle.kts b/DubokuProvider/build.gradle.kts index c5826742..6582031c 100644 --- a/DubokuProvider/build.gradle.kts +++ b/DubokuProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 3 cloudstream { diff --git a/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt b/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt index 1262fb5d..998e1018 100644 --- a/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt +++ b/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt @@ -3,13 +3,14 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper import org.jsoup.nodes.Element +import java.net.URLDecoder class DubokuProvider : MainAPI() { override var mainUrl = "https://www.duboku.tv" + private var serverUrl = "https://w.duboku.io" override var name = "Duboku" override val hasMainPage = true override var lang = "zh" @@ -106,27 +107,41 @@ class DubokuProvider : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - app.get(data).document.select("script").map { script -> - if (script.data().contains("var player_data={")) { - val dataJson = - script.data().substringAfter("var player_data={").substringBefore("}") - tryParseJson("{$dataJson}")?.let { source -> - M3u8Helper.generateM3u8( - this.name, - source.url ?: return@map, - referer = "https://w.duboku.io/", - headers = mapOf("Origin" to "https://w.duboku.io") - ).forEach(callback) - } - } - } - + val dataJson = + app.get(data).document.selectFirst("script:containsData(var player_data={)")?.data() + ?.substringAfter("var player_data={")?.substringBefore("}") + ?: throw IllegalArgumentException() + val source = tryParseJson("{$dataJson}") + callback.invoke( + ExtractorLink( + this.name, + this.name, + "${decode(base64Decode(source?.url ?: return false))}${getSign(source.from, data)}", + "$serverUrl/", + Qualities.Unknown.value, + INFER_TYPE, + headers = mapOf( + "Accept-Language" to "en-US,en;q=0.5", + "Origin" to serverUrl + ), + ) + ) return true } + private suspend fun getSign(server: String? = "vidjs24", ref: String): String { + return app.get( + "$serverUrl/static/player/$server.php", + referer = ref + ).text.substringAfter("PlayUrl+'").substringBefore("'") + } + + private fun decode(input: String): String = URLDecoder.decode(input, "utf-8") + data class Sources( @JsonProperty("url") val url: String?, + @JsonProperty("from") val from: String?, ) diff --git a/FilmPalast/build.gradle.kts b/FilmPalast/build.gradle.kts index 6f3a4bdc..d434c85a 100644 --- a/FilmPalast/build.gradle.kts +++ b/FilmPalast/build.gradle.kts @@ -21,4 +21,6 @@ cloudstream { // You can find a list of avaliable types here: // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html tvTypes = listOf("TvSeries", "Movie") + + iconUrl = "https://www.google.com/s2/favicons?domain=filmpalast.to&sz=%size%" } diff --git a/Gomov/build.gradle.kts b/Gomov/build.gradle.kts index dd797cdd..b9ed7bbb 100644 --- a/Gomov/build.gradle.kts +++ b/Gomov/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 29 +version = 31 android { defaultConfig { diff --git a/Gomov/src/main/kotlin/com/hexated/DutaMovie.kt b/Gomov/src/main/kotlin/com/hexated/DutaMovie.kt index 33cbe769..9b62f141 100644 --- a/Gomov/src/main/kotlin/com/hexated/DutaMovie.kt +++ b/Gomov/src/main/kotlin/com/hexated/DutaMovie.kt @@ -6,8 +6,9 @@ import com.lagradost.cloudstream3.utils.httpsify import com.lagradost.cloudstream3.utils.loadExtractor class DutaMovie : Gomov() { + override var mainUrl = "https://viral.dutamovie21.tech" - override var name = "DutaMovie" +override var name = "DutaMovie" override val mainPage = mainPageOf( "category/box-office/page/%d/" to "Box Office", "category/serial-tv/page/%d/" to "Serial TV", diff --git a/Gomov/src/main/kotlin/com/hexated/Gomov.kt b/Gomov/src/main/kotlin/com/hexated/Gomov.kt index 0bbeac86..be8f721e 100644 --- a/Gomov/src/main/kotlin/com/hexated/Gomov.kt +++ b/Gomov/src/main/kotlin/com/hexated/Gomov.kt @@ -10,7 +10,9 @@ import org.jsoup.nodes.Element import java.net.URI open class Gomov : MainAPI() { + override var mainUrl = "https://gomov.info" + private var directUrl: String? = null override var name = "Gomov" override val hasMainPage = true diff --git a/Gomov/src/main/kotlin/com/hexated/Multiplex.kt b/Gomov/src/main/kotlin/com/hexated/Multiplex.kt index c53b0b05..2b2d4bae 100644 --- a/Gomov/src/main/kotlin/com/hexated/Multiplex.kt +++ b/Gomov/src/main/kotlin/com/hexated/Multiplex.kt @@ -3,7 +3,9 @@ package com.hexated import com.lagradost.cloudstream3.mainPageOf class Multiplex : Gomov() { + override var mainUrl = "http://95.111.236.109" + override var name = "Multiplex" override val mainPage = mainPageOf( "country/usa/page/%d/" to "Movie", diff --git a/Gomov/src/main/kotlin/com/hexated/Ngefilm.kt b/Gomov/src/main/kotlin/com/hexated/Ngefilm.kt index f10f0ce9..18c180fe 100644 --- a/Gomov/src/main/kotlin/com/hexated/Ngefilm.kt +++ b/Gomov/src/main/kotlin/com/hexated/Ngefilm.kt @@ -3,7 +3,9 @@ package com.hexated import com.lagradost.cloudstream3.mainPageOf class Ngefilm : Gomov() { + override var mainUrl = "https://ngefilm21.pics" + override var name = "Ngefilm" override val mainPage = mainPageOf( "/page/%d/?s&search=advanced&post_type=movie&index&orderby&genre&movieyear&country&quality=" to "Movies Terbaru", diff --git a/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt b/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt index c697f403..90de7c15 100644 --- a/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt +++ b/Gomov/src/main/kotlin/com/hexated/Nodrakorid.kt @@ -9,7 +9,7 @@ import org.jsoup.nodes.Element import java.net.URI class Nodrakorid : Gomov() { - override var mainUrl = "https://no-dra-kor-id.shop" + override var mainUrl = "https://no1.nodrakor.store" override var name = "Nodrakorid" override val mainPage = mainPageOf( diff --git a/Gomov/src/main/kotlin/com/hexated/Pusatfilm.kt b/Gomov/src/main/kotlin/com/hexated/Pusatfilm.kt index f0aa0727..283a620b 100644 --- a/Gomov/src/main/kotlin/com/hexated/Pusatfilm.kt +++ b/Gomov/src/main/kotlin/com/hexated/Pusatfilm.kt @@ -6,7 +6,9 @@ import com.lagradost.cloudstream3.TvSeriesLoadResponse import com.lagradost.cloudstream3.* class Pusatfilm : Gomov() { + override var mainUrl = "http://37.60.238.37/Genre/pusatfilm21" + override var name = "Pusatfilm" override val mainPage = mainPageOf( "film-terbaru/page/%d/" to "Film Terbaru", diff --git a/HDrezkaProvider/build.gradle.kts b/HDrezkaProvider/build.gradle.kts index 2fc9aa63..2a5e6c60 100644 --- a/HDrezkaProvider/build.gradle.kts +++ b/HDrezkaProvider/build.gradle.kts @@ -24,5 +24,5 @@ cloudstream { "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=hdrezka19139.org&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=rezka.ag&sz=%size%" } \ No newline at end of file diff --git a/Hentaiheaven/build.gradle.kts b/Hentaiheaven/build.gradle.kts index 017bc97a..1306ce5e 100644 --- a/Hentaiheaven/build.gradle.kts +++ b/Hentaiheaven/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 3 +version = 4 cloudstream { diff --git a/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt b/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt index aea799a0..568c1417 100644 --- a/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt +++ b/Hentaiheaven/src/main/kotlin/com/hexated/Hentaiheaven.kt @@ -55,7 +55,7 @@ class Hentaiheaven : MainAPI() { val link = "$mainUrl/?s=$query&post_type=wp-manga" val document = app.get(link).document - return document.select("div.c-tabs-item div.row.c-tabs-item__content").mapNotNull { + return document.select("div.c-tabs-item > div.c-tabs-item__content").mapNotNull { it.toSearchResult() } } diff --git a/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt b/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt index 75184a6e..2ca2465e 100644 --- a/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt +++ b/Kickassanime/src/main/kotlin/com/hexated/Kickassanime.kt @@ -35,8 +35,8 @@ open class Kickassanime : MainAPI() { companion object { const val kaast = "https://kaast1.com" - private const val consumetAnilist = "https://api.consumet.org/meta/anilist" - private const val consumetMal = "https://api.consumet.org/meta/mal" + private const val consumetAnilist = "https://consumet-instance.vercel.app/meta/anilist" + private const val consumetMal = "https://consumet-instance.vercel.app/meta/mal" fun getType(t: String): TvType { return when { t.contains("Ova", true) -> TvType.OVA @@ -379,4 +379,4 @@ open class Kickassanime : MainAPI() { @JsonProperty("title") val title: SyncTitle? = null, ) -} \ No newline at end of file +} diff --git a/Kinoger/build.gradle.kts b/Kinoger/build.gradle.kts new file mode 100644 index 00000000..f7decb7b --- /dev/null +++ b/Kinoger/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 3 + + +cloudstream { + language = "de" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * 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=kinoger.com&sz=%size%" +} \ No newline at end of file diff --git a/Kinoger/src/main/AndroidManifest.xml b/Kinoger/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Kinoger/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt b/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt new file mode 100644 index 00000000..d53d819f --- /dev/null +++ b/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt @@ -0,0 +1,157 @@ +package com.hexated + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.Chillx +import com.lagradost.cloudstream3.network.CloudflareKiller +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.loadExtractor +import org.jsoup.nodes.Element + +class Kinoger : MainAPI() { + override var name = "Kinoger" + override var mainUrl = "https://kinoger.to" + override var lang = "de" + override val hasMainPage = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) + + override val mainPage = mainPageOf( + "" to "Alle Filme", + "stream/action" to "Action", + "stream/fantasy" to "Fantasy", + "stream/drama" to "Drama", + "stream/mystery" to "Mystery", + "stream/romance" to "Romance", + "stream/animation" to "Animation", + "stream/horror" to "Horror", + "stream/familie" to "Familie", + "stream/komdie" to "Komdie", + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val document = app.get("$mainUrl/${request.data}/page/$page").document + val home = document.select("div#dle-content div.short").mapNotNull { + it.toSearchResult() + } + return newHomePageResponse(request.name, home) + } + + private fun getProperLink(uri: String): String { + return if (uri.contains("-episode-")) { + "$mainUrl/series/" + Regex("$mainUrl/(.+)-ep.+").find(uri)?.groupValues?.get(1) + } else { + uri + } + } + + private fun Element.toSearchResult(): SearchResponse? { + val href = getProperLink(this.selectFirst("a")?.attr("href") ?: return null) + val title = this.selectFirst("a")?.text() ?: this.selectFirst("img")?.attr("alt") + ?: this.selectFirst("a")?.attr("title") ?: return null + val posterUrl = fixUrlNull( + (this.selectFirst("div.content_text img") ?: this.nextElementSibling()?.selectFirst("div.content_text img") ?: this.selectFirst("img"))?.getImageAttr() + ) + + return newTvSeriesSearchResponse(title, href, TvType.AsianDrama) { + this.posterUrl = posterUrl + } + } + + override suspend fun search(query: String): List { + return app.get("$mainUrl/?do=search&subaction=search&titleonly=3&story=$query&x=0&y=0&submit=submit").document.select( + "div#dle-content div.titlecontrol" + ).mapNotNull { it.toSearchResult() } + } + + override suspend fun load(url: String): LoadResponse { + val document = app.get(url).document + val title = document.selectFirst("h1#news-title")?.text() ?: "" + val poster = fixUrlNull(document.selectFirst("div.images-border img")?.getImageAttr()) + val description = document.select("div.images-border").text() + val year = """\((\d{4})\)""".toRegex().find(title)?.groupValues?.get(1)?.toIntOrNull() + val tags = document.select("li.category a").map { it.text() } + + val recommendations = document.select("ul.ul_related li").mapNotNull { + it.toSearchResult() + } + + val script = document.selectFirst("script:containsData(pw.show)")?.data() + val data = script?.substringAfter("[")?.substringBeforeLast("]")?.replace("\'", "\"") + val json = AppUtils.tryParseJson>>("[$data]") + + val type = if(script?.substringBeforeLast(")")?.substringAfterLast(",") == "0.2") TvType.Movie else TvType.TvSeries + + val episodes = json?.flatMapIndexed { season: Int, iframes: List -> + iframes.mapIndexed { episode, iframe -> + Episode( + iframe.trim(), + season = season + 1, + episode = episode + 1 + ) + } + } ?: emptyList() + + return newTvSeriesLoadResponse(title, url, type, episodes) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.recommendations = recommendations + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + loadCustomExtractor(data, "$mainUrl/", subtitleCallback, callback) + return true + } + + private suspend fun loadCustomExtractor( + url: String, + referer: String? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + quality: Int? = null, + ) { + loadExtractor(url, referer, subtitleCallback) { link -> + if(link.quality == Qualities.Unknown.value) { + callback.invoke( + ExtractorLink( + link.source, + link.name, + link.url, + link.referer, + when (link.type) { + ExtractorLinkType.M3U8 -> link.quality + else -> quality ?: link.quality + }, + link.type, + link.headers, + link.extractorData + ) + ) + } + } + } + + private fun Element.getImageAttr(): String? { + return when { + this.hasAttr("data-src") -> this.attr("data-src") + this.hasAttr("data-lazy-src") -> this.attr("data-lazy-src") + this.hasAttr("srcset") -> this.attr("srcset").substringBefore(" ") + else -> this.attr("src") + } + } + +} + +class Kinogeru : Chillx() { + override val name = "Kinoger" + override val mainUrl = "https://kinoger.ru" +} \ No newline at end of file diff --git a/Kinoger/src/main/kotlin/com/hexated/KinogerPlugin.kt b/Kinoger/src/main/kotlin/com/hexated/KinogerPlugin.kt new file mode 100644 index 00000000..2b9fc736 --- /dev/null +++ b/Kinoger/src/main/kotlin/com/hexated/KinogerPlugin.kt @@ -0,0 +1,15 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class KinogerPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Kinoger()) + registerExtractorAPI(Kinogeru()) + } +} \ No newline at end of file diff --git a/KisskhProvider/build.gradle.kts b/KisskhProvider/build.gradle.kts index d2bcff25..00324bad 100644 --- a/KisskhProvider/build.gradle.kts +++ b/KisskhProvider/build.gradle.kts @@ -24,5 +24,5 @@ cloudstream { "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=kisskh.me&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=kisskh.co&sz=%size%" } \ No newline at end of file diff --git a/KuramanimeProvider/build.gradle.kts b/KuramanimeProvider/build.gradle.kts index fac8e2b3..ae77707b 100644 --- a/KuramanimeProvider/build.gradle.kts +++ b/KuramanimeProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 34 +version = 39 cloudstream { diff --git a/KuramanimeProvider/src/main/kotlin/com/hexated/KuramanimeProvider.kt b/KuramanimeProvider/src/main/kotlin/com/hexated/KuramanimeProvider.kt index 82d488e4..b911d57a 100644 --- a/KuramanimeProvider/src/main/kotlin/com/hexated/KuramanimeProvider.kt +++ b/KuramanimeProvider/src/main/kotlin/com/hexated/KuramanimeProvider.kt @@ -3,13 +3,7 @@ package com.hexated import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId -import com.lagradost.cloudstream3.network.WebViewResolver import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.loadExtractor -import com.lagradost.nicehttp.requestCreator -import okhttp3.Headers -import okhttp3.HttpUrl import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -20,13 +14,10 @@ class KuramanimeProvider : MainAPI() { override val hasMainPage = true override var lang = "id" override val hasDownloadSupport = true - private var params: AuthParams? = null - private var headers: Map = mapOf() - private var cookies: Map = mapOf() override val supportedTypes = setOf( - TvType.Anime, - TvType.AnimeMovie, - TvType.OVA + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA ) companion object { @@ -46,15 +37,15 @@ class KuramanimeProvider : MainAPI() { } override val mainPage = mainPageOf( - "$mainUrl/anime/ongoing?order_by=updated&page=" to "Sedang Tayang", - "$mainUrl/anime/finished?order_by=updated&page=" to "Selesai Tayang", - "$mainUrl/properties/season/summer-2022?order_by=most_viewed&page=" to "Dilihat Terbanyak Musim Ini", - "$mainUrl/anime/movie?order_by=updated&page=" to "Film Layar Lebar", + "$mainUrl/anime/ongoing?order_by=updated&page=" to "Sedang Tayang", + "$mainUrl/anime/finished?order_by=updated&page=" to "Selesai Tayang", + "$mainUrl/properties/season/summer-2022?order_by=most_viewed&page=" to "Dilihat Terbanyak Musim Ini", + "$mainUrl/anime/movie?order_by=updated&page=" to "Film Layar Lebar", ) override suspend fun getMainPage( - page: Int, - request: MainPageRequest + page: Int, + request: MainPageRequest ): HomePageResponse { val document = app.get(request.data + page).document @@ -102,35 +93,40 @@ class KuramanimeProvider : MainAPI() { val title = document.selectFirst(".anime__details__title > h3")!!.text().trim() val poster = document.selectFirst(".anime__details__pic")?.attr("data-setbg") - val tags = document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)") - .text().trim().replace("Genre: ", "").split(", ") + val tags = + document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)") + .text().trim().replace("Genre: ", "").split(", ") val year = Regex("\\D").replace( - document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)") - .text().trim().replace("Musim: ", ""), "" + document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)") + .text().trim().replace("Musim: ", ""), "" ).toIntOrNull() val status = getStatus( - document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)") - .text().trim().replace("Status: ", "") + document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)") + .text().trim().replace("Status: ", "") ) val description = document.select(".anime__details__text > p").text().trim() val episodes = mutableListOf() - for (i in 1..6) { + for (i in 1..10) { val doc = app.get("$url?page=$i").document - val eps = Jsoup.parse(doc.select("#episodeLists").attr("data-content")).select("a.btn.btn-sm.btn-danger") - .mapNotNull { - val name = it.text().trim() - val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0) - ?.toIntOrNull() - val link = it.attr("href") - Episode(link, episode = episode) - } - if(eps.isEmpty()) break else episodes.addAll(eps) + val eps = Jsoup.parse(doc.select("#episodeLists").attr("data-content")) + .select("a.btn.btn-sm.btn-danger") + .mapNotNull { + val name = it.text().trim() + val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0) + ?.toIntOrNull() + val link = it.attr("href") + Episode(link, episode = episode) + } + if (eps.isEmpty()) break else episodes.addAll(eps) } - val type = getType(document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text()?.lowercase() ?: "tv", episodes.size) + val type = getType( + document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text() + ?.lowercase() ?: "tv", episodes.size + ) val recommendations = document.select("div#randomList > a").mapNotNull { val epHref = it.attr("href") val epTitle = it.select("h5.sidebar-title-h5.px-2.py-2").text() @@ -141,7 +137,7 @@ class KuramanimeProvider : MainAPI() { } } - val tracker = APIHolder.getTracker(listOf(title),TrackerType.getTypes(type),year,true) + val tracker = APIHolder.getTracker(listOf(title), TrackerType.getTypes(type), year, true) return newAnimeLoadResponse(title, url, type) { engName = title @@ -159,112 +155,14 @@ class KuramanimeProvider : MainAPI() { } - private suspend fun invokeLocalSource( - url: String, - server: String, - ref: String, - callback: (ExtractorLink) -> Unit - ) { - val document = app.get( - url, - referer = ref, - headers = headers, - cookies = cookies - ).document - document.select("video#player > source").map { - val link = fixUrl(it.attr("src")) - val quality = it.attr("size").toIntOrNull() - callback.invoke( - ExtractorLink( - fixTitle(server), - fixTitle(server), - link, - referer = "", - quality = quality ?: Qualities.Unknown.value, - headers = mapOf( - "Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5", - "Range" to "bytes=0-", - "Sec-Fetch-Dest" to "video", - "Sec-Fetch-Mode" to "no-cors", - ), - ) - ) - } - } - override suspend fun loadLinks( - data: String, - isCasting: Boolean, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit ): Boolean { - val req = app.get(data) - val res = req.document - - argamap( - { - val auth = getAuth(data) - headers = auth.authHeader?.associate { it.first to it.second }?.filter { it.key != "Cookie" }!! - cookies = req.cookies - res.select("select#changeServer option").apmap { source -> - val server = source.attr("value") - val query = auth.serverUrl?.queryParameterNames?.map { it } ?: return@apmap - val link = "$data?${query[0]}=${getMisc(auth.authUrl)}&${query[1]}=$server" - if (server.contains(Regex("(?i)kuramadrive|archive"))) { - invokeLocalSource(link, server, data, callback) - } else { - app.get( - link, - referer = data, - headers = headers, - cookies = cookies - ).document.select("div.iframe-container iframe").attr("src").let { videoUrl -> - loadExtractor(fixUrl(videoUrl), "$mainUrl/", subtitleCallback, callback) - } - } - } - }, - { - res.select("div#animeDownloadLink a").apmap { - loadExtractor(it.attr("href"), "$mainUrl/", subtitleCallback, callback) - } - } - ) - return true } - private suspend fun fetchAuth(url: String) : AuthParams { - val regex = Regex("""$mainUrl/\S+""") - val found = WebViewResolver( - Regex("""$url(?!\?page=)\?"""), - additionalUrls = listOf(regex) - ).resolveUsingWebView( - requestCreator( - "GET", url - ) - ) - val addition = found.second.findLast { it.headers["X-Requested-With"] == "XMLHttpRequest" } - return AuthParams(found.first?.url, addition?.url.toString(), addition?.headers) - } - - private suspend fun getAuth(url: String) = params ?: fetchAuth(url).also { params = it } - - private suspend fun getMisc(url: String?): String { - val misc = app.get( - "$url", - headers = headers, - cookies = cookies - ) - cookies = misc.cookies - return misc.parsed() - } - - data class AuthParams ( - val serverUrl: HttpUrl?, - val authUrl: String?, - val authHeader: Headers?, - ) - } \ No newline at end of file diff --git a/KuronimeProvider/build.gradle.kts b/KuronimeProvider/build.gradle.kts index 863fbfc2..d68a2267 100644 --- a/KuronimeProvider/build.gradle.kts +++ b/KuronimeProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 19 +version = 20 cloudstream { diff --git a/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt b/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt index 0ebfafbf..57eccd6e 100644 --- a/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt +++ b/KuronimeProvider/src/main/kotlin/com/hexated/KuronimeProvider.kt @@ -16,7 +16,7 @@ import java.net.URI import java.util.ArrayList class KuronimeProvider : MainAPI() { - override var mainUrl = "https://tv.kuronime.vip" + override var mainUrl = "https://tv1.kuronime.vip" private var animekuUrl = "https://animeku.org" override var name = "Kuronime" override val hasQuickSearch = true diff --git a/LayarKacaProvider/build.gradle.kts b/LayarKacaProvider/build.gradle.kts index 371b1926..cde22772 100644 --- a/LayarKacaProvider/build.gradle.kts +++ b/LayarKacaProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 17 +version = 20 cloudstream { @@ -23,5 +23,7 @@ cloudstream { "Movie", ) + iconUrl = "https://www.google.com/s2/favicons?domain=amp.lk21official.mom&sz=%size%" + } diff --git a/LayarKacaProvider/src/main/kotlin/com/hexated/Extractors.kt b/LayarKacaProvider/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..8aa99fd9 --- /dev/null +++ b/LayarKacaProvider/src/main/kotlin/com/hexated/Extractors.kt @@ -0,0 +1,86 @@ +package com.hexated + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.extractors.Filesim +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.getQualityFromName + +open class Emturbovid : ExtractorApi() { + override val name = "Emturbovid" + override val mainUrl = "https://emturbovid.com" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val response = app.get(url, referer = referer) + val m3u8 = Regex("[\"'](.*?master\\.m3u8.*?)[\"']").find(response.text)?.groupValues?.getOrNull(1) + M3u8Helper.generateM3u8( + name, + m3u8 ?: return, + mainUrl + ).forEach(callback) + } + +} + +open class Hownetwork : ExtractorApi() { + override val name = "Hownetwork" + override val mainUrl = "https://stream.hownetwork.xyz" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val id = url.substringAfter("id=") + val res = app.post( + "$mainUrl/api.php?id=$id", + data = mapOf( + "r" to "https://playeriframe.shop/", + "d" to "stream.hownetwork.xyz", + ), + referer = url, + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe() + + res?.data?.map { + callback.invoke( + ExtractorLink( + this.name, + this.name, + it.file, + url, + getQualityFromName(it.label), + INFER_TYPE + ) + ) + } + + } + + data class Sources( + val data: ArrayList + ) { + data class Data( + val file: String, + val label: String?, + ) + } +} + +class Furher : Filesim() { + override val name = "Furher" + override var mainUrl = "https://furher.in" +} \ No newline at end of file diff --git a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt index dd3e380f..009dda96 100644 --- a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt +++ b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProvider.kt @@ -8,29 +8,31 @@ import com.lagradost.cloudstream3.utils.* import org.jsoup.nodes.Element class LayarKacaProvider : MainAPI() { + override var mainUrl = "https://amp.lk21official.mom" private var seriesUrl = "https://tv12.nontondrama.click/" + override var name = "LayarKaca" override val hasMainPage = true override var lang = "id" override val supportedTypes = setOf( - TvType.Movie, - TvType.TvSeries, - TvType.AsianDrama + TvType.Movie, + TvType.TvSeries, + TvType.AsianDrama ) override val mainPage = mainPageOf( - "$mainUrl/populer/page/" to "Film Terplopuler", - "$mainUrl/rating/page/" to "Film Berdasarkan IMDb Rating", - "$mainUrl/most-commented/page/" to "Film Dengan Komentar Terbanyak", - "$seriesUrl/latest/page/" to "Series Terbaru", - "$seriesUrl/series/asian/page/" to "Film Asian Terbaru", - "$mainUrl/latest/page/" to "Film Upload Terbaru", + "$mainUrl/populer/page/" to "Film Terplopuler", + "$mainUrl/rating/page/" to "Film Berdasarkan IMDb Rating", + "$mainUrl/most-commented/page/" to "Film Dengan Komentar Terbanyak", + "$seriesUrl/latest-series/page/" to "Series Terbaru", + "$seriesUrl/series/asian/page/" to "Film Asian Terbaru", + "$mainUrl/latest/page/" to "Film Upload Terbaru", ) override suspend fun getMainPage( - page: Int, - request: MainPageRequest + page: Int, + request: MainPageRequest ): HomePageResponse { val document = app.get(request.data + page).document val home = document.select("article.mega-item").mapNotNull { @@ -40,6 +42,7 @@ class LayarKacaProvider : MainAPI() { } private suspend fun getProperLink(url: String): String? { + if(url.startsWith(seriesUrl)) return url val res = app.get(url).document return if (res.select("title").text().contains("- Nontondrama", true)) { res.selectFirst("div#content a")?.attr("href") @@ -53,10 +56,10 @@ class LayarKacaProvider : MainAPI() { val href = fixUrl(this.selectFirst("a")!!.attr("href")) val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src")) val type = - if (this.selectFirst("div.last-episode") == null) TvType.Movie else TvType.TvSeries + if (this.selectFirst("div.last-episode") == null) TvType.Movie else TvType.TvSeries return if (type == TvType.TvSeries) { val episode = this.selectFirst("div.last-episode span")?.text()?.filter { it.isDigit() } - ?.toIntOrNull() + ?.toIntOrNull() newAnimeSearchResponse(title, href, TvType.TvSeries) { this.posterUrl = posterUrl addSub(episode) @@ -91,23 +94,23 @@ class LayarKacaProvider : MainAPI() { val tags = document.select("div.content > div:nth-child(5) > h3 > a").map { it.text() } val year = Regex("\\d, (\\d+)").find( - document.select("div.content > div:nth-child(7) > h3").text().trim() + document.select("div.content > div:nth-child(7) > h3").text().trim() )?.groupValues?.get(1).toString().toIntOrNull() val tvType = if (document.select("div.serial-wrapper") - .isNotEmpty() + .isNotEmpty() ) TvType.TvSeries else TvType.Movie val description = document.select("div.content > blockquote").text().trim() val trailer = document.selectFirst("div.action-player li > a.fancybox")?.attr("href") val rating = - document.selectFirst("div.content > div:nth-child(6) > h3")?.text()?.toRatingInt() + document.selectFirst("div.content > div:nth-child(6) > h3")?.text()?.toRatingInt() val actors = - document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { it.text() } + document.select("div.col-xs-9.content > div:nth-child(3) > h3 > a").map { it.text() } val recommendations = document.select("div.row.item-media").map { val recName = it.selectFirst("h3")?.text()?.trim().toString() val recHref = it.selectFirst(".content-media > a")!!.attr("href") val recPosterUrl = - fixUrl(it.selectFirst(".poster-media > a > img")?.attr("src").toString()) + fixUrl(it.selectFirst(".poster-media > a > img")?.attr("src").toString()) newTvSeriesSearchResponse(recName, recHref, TvType.TvSeries) { this.posterUrl = recPosterUrl } @@ -118,12 +121,12 @@ class LayarKacaProvider : MainAPI() { val href = fixUrl(it.attr("href")) val episode = it.text().toIntOrNull() val season = - it.attr("href").substringAfter("season-").substringBefore("-").toIntOrNull() + it.attr("href").substringAfter("season-").substringBefore("-").toIntOrNull() Episode( - href, - "Episode $episode", - season, - episode, + href, + "Episode $episode", + season, + episode, ) }.reversed() newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { @@ -151,10 +154,10 @@ class LayarKacaProvider : MainAPI() { } override suspend fun loadLinks( - data: String, - isCasting: Boolean, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit ): Boolean { val document = app.get(data).document @@ -172,30 +175,3 @@ class LayarKacaProvider : MainAPI() { } } - -open class Emturbovid : ExtractorApi() { - override val name = "Emturbovid" - override val mainUrl = "https://emturbovid.com" - override val requiresReferer = true - - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val response = app.get(url, referer = referer) - val m3u8 = Regex("[\"'](.*?master\\.m3u8.*?)[\"']").find(response.text)?.groupValues?.getOrNull(1) - M3u8Helper.generateM3u8( - name, - m3u8 ?: return, - mainUrl - ).forEach(callback) - } - -} - -class Furher : Filesim() { - override val name = "Furher" - override var mainUrl = "https://furher.in" -} diff --git a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt index 9a73cdc1..d3a57242 100644 --- a/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt +++ b/LayarKacaProvider/src/main/kotlin/com/hexated/LayarKacaProviderPlugin.kt @@ -12,5 +12,6 @@ class LayarKacaProviderPlugin: Plugin() { registerMainAPI(LayarKacaProvider()) registerExtractorAPI(Emturbovid()) registerExtractorAPI(Furher()) + registerExtractorAPI(Hownetwork()) } } \ No newline at end of file diff --git a/Loklok/src/main/kotlin/com/hexated/Loklok.kt b/Loklok/src/main/kotlin/com/hexated/Loklok.kt index 5e8c8ca5..143d5c01 100644 --- a/Loklok/src/main/kotlin/com/hexated/Loklok.kt +++ b/Loklok/src/main/kotlin/com/hexated/Loklok.kt @@ -268,7 +268,7 @@ class Loklok : MainAPI() { } private suspend fun getTracker(title: String?, type: String?, year: Int?): Tracker { - val res = app.get("https://api.consumet.org/meta/anilist/$title") + val res = app.get("https://consumet-instance.vercel.app/meta/anilist/$title") .parsedSafe()?.results?.find { media -> (media.title?.english.equals(title, true) || media.title?.romaji.equals( title, diff --git a/Minioppai/build.gradle.kts b/Minioppai/build.gradle.kts index 87203b16..08ef2a02 100644 --- a/Minioppai/build.gradle.kts +++ b/Minioppai/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 9 +version = 11 cloudstream { diff --git a/Minioppai/src/main/kotlin/com/hexated/Extractors.kt b/Minioppai/src/main/kotlin/com/hexated/Extractors.kt index 3e172453..3b8b8470 100644 --- a/Minioppai/src/main/kotlin/com/hexated/Extractors.kt +++ b/Minioppai/src/main/kotlin/com/hexated/Extractors.kt @@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.fixTitle import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.fixUrl import com.lagradost.cloudstream3.utils.getAndUnpack import com.lagradost.cloudstream3.utils.getQualityFromName @@ -32,28 +33,30 @@ open class Streampai : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val res = app.get(url, referer = referer).text - val data = getAndUnpack(res) + val res = app.get(url, referer = referer).document + val data = res.selectFirst("script:containsData(player =)")?.data() ?: return + + val sources = data.substringAfter("sources: [").substringBefore("]") + .addMarks("src") + .addMarks("type") + .addMarks("size") + .replace("\'", "\"") + + val tracks = data.substringAfter("tracks: [").substringBefore("]") + .replace("\'", "\"") - val sources = data.substringAfter("sources:[").substringBefore("]").replace("\'", "\"") - val tracks = data.substringAfter("\"tracks\":[").substringBefore("]") tryParseJson>("[$sources]")?.forEach { callback.invoke( ExtractorLink( this.name, this.name, - fixUrl(it.file), + fixUrl(it.src), url, - getQualityFromName(it.label), + it.size ?: Qualities.Unknown.value, headers = mapOf( - "Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5", - "Accept-Language" to "en-US,en;q=0.5", - "DNT" to "1", "Range" to "bytes=0-", - "Sec-Fetch-Dest" to "video", - "Sec-Fetch-Mode" to "no-cors", - ) + ), ) ) } @@ -61,17 +64,22 @@ open class Streampai : ExtractorApi() { tryParseJson>("[$tracks]")?.forEach { subtitleCallback.invoke( SubtitleFile( - fixTitle(it.label ?: ""), - fixUrl(it.file), + fixTitle(it.label ?: return@forEach), + fixUrl(it.src) ) ) } } + private fun String.addMarks(str: String): String { + return this.replace(Regex("\"?$str\"?"), "\"$str\"") + } + data class Responses( - @JsonProperty("file") val file: String, + @JsonProperty("src") val src: String, @JsonProperty("type") val type: String?, - @JsonProperty("label") val label: String? + @JsonProperty("label") val label: String?, + @JsonProperty("size") val size: Int? ) } \ No newline at end of file diff --git a/Moflix/build.gradle.kts b/Moflix/build.gradle.kts new file mode 100644 index 00000000..738f38de --- /dev/null +++ b/Moflix/build.gradle.kts @@ -0,0 +1,26 @@ +// use an integer for version numbers +version = 3 + + +cloudstream { + language = "de" + // All of these properties are optional, you can safely remove them + + description = "Include: Cineclix" + authors = listOf("Hexated") + + /** + * 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=moflix-stream.xyz&sz=%size%" +} \ No newline at end of file diff --git a/Moflix/src/main/AndroidManifest.xml b/Moflix/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Moflix/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Moflix/src/main/kotlin/com/hexated/Cineclix.kt b/Moflix/src/main/kotlin/com/hexated/Cineclix.kt new file mode 100644 index 00000000..59a65732 --- /dev/null +++ b/Moflix/src/main/kotlin/com/hexated/Cineclix.kt @@ -0,0 +1,14 @@ +package com.hexated + +import com.lagradost.cloudstream3.mainPageOf + +class Cineclix : Moflix() { + override var name = "Cineclix" + override var mainUrl = "https://cineclix.in" + override val mainPage = mainPageOf( + "77/created_at:desc" to "Neuerscheinungen Filme", + "82/created_at:desc" to "Neuerscheinungen Serien", + "77/popularity:desc" to "Filme", + "82/popularity:desc" to "Serien", + ) +} \ No newline at end of file diff --git a/Moflix/src/main/kotlin/com/hexated/Extractors.kt b/Moflix/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..e2a67320 --- /dev/null +++ b/Moflix/src/main/kotlin/com/hexated/Extractors.kt @@ -0,0 +1,94 @@ +package com.hexated + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.* +import java.net.URI + +class MoflixLink : MoflixClick() { + override val name = "MoflixLink" + override val mainUrl = "https://moflix-stream.link" +} + +class MoflixFans : MoflixClick() { + override val name = "MoflixFans" + override val mainUrl = "https://moflix-stream.fans" +} + +class Highstream : MoflixClick() { + override val name = "Highstream" + override val mainUrl = "https://highstream.tv" +} + +open class MoflixClick : ExtractorApi() { + override val name = "MoflixClick" + override val mainUrl = "https://moflix-stream.click" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val response = app.get(url, referer = referer) + val script = if (!getPacked(response.text).isNullOrEmpty()) { + getAndUnpack(response.text) + } else { + response.document.selectFirst("script:containsData(sources:)")?.data() + } + val m3u8 = + Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) + callback.invoke( + ExtractorLink( + name, + name, + m3u8 ?: return, + "$mainUrl/", + Qualities.Unknown.value, + INFER_TYPE + ) + ) + } + +} + +open class Doodstream : ExtractorApi() { + override val name = "Doodstream" + override val mainUrl = "https://doodstream.com" + override val requiresReferer = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val req = app.get(url) + val host = getBaseUrl(req.url) + val response0 = req.text + val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) + val trueUrl = + app.get(md5, referer = req.url).text + "qWMG3yc6F5?token=" + md5.substringAfterLast("/") + val quality = Regex("\\d{3,4}p").find( + response0.substringAfter("").substringBefore("") + )?.groupValues?.get(0) + callback.invoke( + ExtractorLink( + this.name, + this.name, + trueUrl, + mainUrl, + getQualityFromName(quality), + false + ) + ) + } + + private fun getBaseUrl(url: String): String { + return URI(url).let { + "${it.scheme}://${it.host}" + } + } + +} \ No newline at end of file diff --git a/Moflix/src/main/kotlin/com/hexated/Moflix.kt b/Moflix/src/main/kotlin/com/hexated/Moflix.kt new file mode 100644 index 00000000..a8547ae8 --- /dev/null +++ b/Moflix/src/main/kotlin/com/hexated/Moflix.kt @@ -0,0 +1,341 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId +import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import org.jsoup.Jsoup +import kotlin.math.roundToInt + +open class Moflix : MainAPI() { + override var name = "Moflix" + override var mainUrl = "https://moflix-stream.xyz" + override var lang = "de" + override val hasMainPage = true + override val hasQuickSearch = true + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) + + companion object { + fun getType(isSeries: Boolean?): TvType { + return when (isSeries) { + true -> TvType.TvSeries + else -> TvType.Movie + } + } + + fun getStatus(t: String?): ShowStatus { + return when (t) { + "ongoing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override val mainPage = mainPageOf( + "351/channelables.order:asc" to "Kürzlich hinzugefügt", + "345/popularity:desc" to "Movie-Datenbank", + "352/channelables.order:asc" to "Angesagte Serien", + "358/channelables.order:asc" to "Kinder & Familien", + ) + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val query = request.data.split("/") + val home = app.get( + "$mainUrl/api/v1/channel/${query.first()}?returnContentOnly=true&restriction=&order=${query.last()}&paginate=simple&perPage=50&query=&page=$page", + referer = "$mainUrl/" + ).parsedSafe()?.pagination?.data?.mapNotNull { it.toSearchResponse() } + ?: emptyList() + + return newHomePageResponse(request.name, home) + } + + private fun Data.toSearchResponse(): SearchResponse? { + return newTvSeriesSearchResponse( + this.name ?: return null, + "${this.id}", + TvType.TvSeries, + false + ) { + posterUrl = this@toSearchResponse.poster?.compress() + } + } + + override suspend fun quickSearch(query: String): List? = search(query) + + override suspend fun search(query: String): List? { + return app.get("$mainUrl/api/v1/search/$query?loader=searchPage", referer = "$mainUrl/") + .parsedSafe()?.results?.mapNotNull { it.toSearchResponse() } + } + + override suspend fun load(url: String): LoadResponse { + val res = app.get( + "$mainUrl/api/v1/titles/${url.fixId()}?loader=titlePage", + referer = "$mainUrl/" + ).parsedSafe() + + val uri = Jsoup.parse(res?.seo.toString()).selectFirst("link[rel=canonical]")?.attr("href") + val id = res?.title?.id + val title = res?.title?.name ?: "" + val poster = res?.title?.poster + val backdrop = res?.title?.backdrop + val tags = res?.title?.keywords?.mapNotNull { it.displayName } + val year = res?.title?.year + val isSeries = res?.title?.isSeries + val certification = res?.title?.certification + val duration = res?.title?.runtime + val type = getType(isSeries) + val description = res?.title?.description + val trailers = res?.title?.videos?.filter { it.category.equals("trailer", true) } + ?.mapNotNull { it.src } + val rating = "${res?.title?.rating}".toRatingInt() + val actors = res?.credits?.actors?.mapNotNull { + ActorData( + Actor(it.name ?: return@mapNotNull null, it.poster), + roleString = it.pivot?.character + ) + } + val recommendations = app.get("$mainUrl/api/v1/titles/$id/related", referer = "$mainUrl/") + .parsedSafe()?.titles?.mapNotNull { it.toSearchResponse() } + + return if (type == TvType.TvSeries) { + val episodes = res?.seasons?.data?.mapNotNull { season -> + app.get( + "$mainUrl/api/v1/titles/${res.title?.id}/seasons/${season.number}?loader=seasonPage", + referer = "$mainUrl/" + ).parsedSafe()?.episodes?.data?.map { episode -> + val status = + if (episode.status.equals("upcoming", true)) " • [UPCOMING]" else "" + Episode( + LoadData( + id, + episode.seasonNumber, + episode.episodeNumber, + res.title?.isSeries + ).toJson(), + episode.name + status, + episode.seasonNumber, + episode.episodeNumber, + episode.poster, + episode.rating?.times(10)?.roundToInt(), + episode.description, + ).apply { + this.addDate(episode.releaseDate?.substringBefore("T")) + } + } + }?.flatten() ?: emptyList() + newTvSeriesLoadResponse(title, uri ?: url, TvType.TvSeries, episodes) { + this.posterUrl = poster + this.backgroundPosterUrl = backdrop + this.year = year + this.showStatus = getStatus(res?.title?.status) + this.plot = description + this.tags = tags + this.rating = rating + this.actors = actors + this.duration = duration + this.recommendations = recommendations + this.contentRating = certification + addTrailer(trailers) + addImdbId(res?.title?.imdbId) + addTMDbId(res?.title?.tmdbId) + } + } else { + val urls = res?.title?.videos?.filter { it.category.equals("full", true) } + + newMovieLoadResponse( + title, + uri ?: url, + TvType.Movie, + LoadData(isSeries = isSeries, urls = urls) + ) { + this.posterUrl = poster + this.backgroundPosterUrl = backdrop + this.year = year + this.comingSoon = res?.title?.status.equals("upcoming", true) + this.plot = description + this.tags = tags + this.rating = rating + this.actors = actors + this.duration = duration + this.recommendations = recommendations + this.contentRating = certification + addTrailer(trailers) + addImdbId(res?.title?.imdbId) + addTMDbId(res?.title?.tmdbId) + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val json = parseJson(data) + + val iframes = if (json.isSeries == true) { + app.get( + "$mainUrl/api/v1/titles/${json.id}/seasons/${json.season}/episodes/${json.episode}?loader=episodePage", + referer = "$mainUrl/" + ).parsedSafe()?.episode?.videos?.filter { it.category.equals("full", true) } + } else { + json.urls + } + + iframes?.apmap { iframe -> + loadCustomExtractor( + iframe.src ?: return@apmap, + "$mainUrl/", + subtitleCallback, + callback, + iframe.quality?.substringBefore("/")?.filter { it.isDigit() }?.toIntOrNull() + ) + } + + return true + } + + private fun String.fixId(): String { + val chunk = "/titles/" + return if (this.contains(chunk)) this.substringAfter(chunk) + .substringBefore("/") else this.substringAfterLast("/") + } + + private suspend fun loadCustomExtractor( + url: String, + referer: String? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + quality: Int? = null, + ) { + loadExtractor(url, referer, subtitleCallback) { link -> + if (link.quality == Qualities.Unknown.value || !link.isM3u8) { + callback.invoke( + ExtractorLink( + link.source, + link.name, + link.url, + link.referer, + quality ?: link.quality, + link.type, + link.headers, + link.extractorData + ) + ) + } + } + } + + private fun String.compress(): String { + return this.replace("/original/", "/w500/") + } + + data class LoadData( + val id: Int? = null, + val season: Int? = null, + val episode: Int? = null, + val isSeries: Boolean? = null, + val urls: List? = listOf(), + ) + + data class Responses( + @JsonProperty("pagination") val pagination: Pagination? = null, + @JsonProperty("title") val title: Title? = null, + @JsonProperty("seo") val seo: String? = null, + @JsonProperty("credits") val credits: Credits? = null, + @JsonProperty("seasons") val seasons: Seasons? = null, + @JsonProperty("episodes") val episodes: Episodes? = null, + @JsonProperty("titles") val titles: ArrayList? = arrayListOf(), + @JsonProperty("results") val results: ArrayList? = arrayListOf(), + ) + + data class Seasons( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), + ) { + data class Data( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("number") val number: Int? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("release_date") val releaseDate: String? = null, + ) + } + + data class Episodes( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), + @JsonProperty("episode") val episode: Data? = null, + ) { + data class Data( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("description") val description: String? = null, + @JsonProperty("season_number") val seasonNumber: Int? = null, + @JsonProperty("episode_number") val episodeNumber: Int? = null, + @JsonProperty("rating") val rating: Float? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("release_date") val releaseDate: String? = null, + @JsonProperty("status") val status: String? = null, + @JsonProperty("videos") val videos: ArrayList? = arrayListOf(), + ) + } + + data class Pagination( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), + ) + + data class Data( + @JsonProperty("id") val id: Any? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("backdrop") val backdrop: String? = null, + ) + + data class Credits( + @JsonProperty("actors") val actors: ArrayList? = arrayListOf(), + ) { + data class Actors( + @JsonProperty("name") val name: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("pivot") val pivot: Pivot? = null, + ) { + data class Pivot( + @JsonProperty("character") val character: String? = null, + ) + } + } + + data class Videos( + @JsonProperty("category") val category: String? = null, + @JsonProperty("src") val src: String? = null, + @JsonProperty("quality") val quality: String? = null, + ) + + data class Title( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("release_date") val releaseDate: String? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("runtime") val runtime: Int? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("backdrop") val backdrop: String? = null, + @JsonProperty("description") val description: String? = null, + @JsonProperty("certification") val certification: String? = null, + @JsonProperty("rating") val rating: Float? = null, + @JsonProperty("imdb_id") val imdbId: String? = null, + @JsonProperty("tmdb_id") val tmdbId: String? = null, + @JsonProperty("status") val status: String? = null, + @JsonProperty("is_series") val isSeries: Boolean? = null, + @JsonProperty("videos") val videos: ArrayList? = arrayListOf(), + @JsonProperty("keywords") val keywords: ArrayList? = arrayListOf(), + ) { + data class Keywords( + @JsonProperty("display_name") val displayName: String? = null, + ) + } + +} \ No newline at end of file diff --git a/Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt b/Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt new file mode 100644 index 00000000..42ade584 --- /dev/null +++ b/Moflix/src/main/kotlin/com/hexated/MoflixPlugin.kt @@ -0,0 +1,20 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class MoflixPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Moflix()) + registerMainAPI(Cineclix()) + registerExtractorAPI(MoflixClick()) + registerExtractorAPI(Highstream()) + registerExtractorAPI(MoflixFans()) + registerExtractorAPI(MoflixLink()) + registerExtractorAPI(Doodstream()) + } +} \ No newline at end of file diff --git a/Movierulzhd/build.gradle.kts b/Movierulzhd/build.gradle.kts index 08c2a426..7f643139 100644 --- a/Movierulzhd/build.gradle.kts +++ b/Movierulzhd/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 55 +version = 63 cloudstream { diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Animesaga.kt b/Movierulzhd/src/main/kotlin/com/hexated/Animesaga.kt index 524ac176..11dd68d3 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Animesaga.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Animesaga.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.mainPageOf class Animesaga : Movierulzhd() { - override var mainUrl = "https://www.animesaga.in" + override var mainUrl = "https://anplay.in" override var name = "Animesaga" override val supportedTypes = setOf( TvType.Anime, @@ -18,6 +18,4 @@ class Animesaga : Movierulzhd() { "tvshows" to "TV-Shows", "genre/hindi-dub" to "Hindi Dub", ) - - } \ No newline at end of file diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt index c3a84b83..3daf620d 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt @@ -68,5 +68,5 @@ open class Akamaicdn : ExtractorApi() { class AnimesagaStream : Chillx() { override val name = "AnimesagaStream" - override val mainUrl = "https://stream.animesaga.in" + override val mainUrl = "https://stream.anplay.in" } diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Hdmovie2.kt b/Movierulzhd/src/main/kotlin/com/hexated/Hdmovie2.kt index 4e0f12b7..59eaf0a5 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Hdmovie2.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Hdmovie2.kt @@ -12,14 +12,14 @@ import org.jsoup.Jsoup class Hdmovie2 : Movierulzhd() { - override var mainUrl = "https://hdmovie2.li" + override var mainUrl = "https://hdmovie2.tax" override var name = "Hdmovie2" override val mainPage = mainPageOf( "trending" to "Trending", "movies" to "Movies", - "genre/tv-series" to "TV-Series", + "genre/tv-series" to "TV Shows", "genre/netflix" to "Netflix", - "genre/zee5-tv-series" to "Zee5 TV Series", + "genre/zee5-tv-series" to "Zee5", ) override suspend fun loadLinks( diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt index 77260037..9954eb5d 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt @@ -11,7 +11,7 @@ import java.net.URI open class Movierulzhd : MainAPI() { - override var mainUrl = "https://movierulzhd.bet" + override var mainUrl = "https://movierulzhd.cafe" var directUrl = "" override var name = "Movierulzhd" override val hasMainPage = true diff --git a/NeonimeProvider/build.gradle.kts b/NeonimeProvider/build.gradle.kts index bfa7a96c..90714e5d 100644 --- a/NeonimeProvider/build.gradle.kts +++ b/NeonimeProvider/build.gradle.kts @@ -24,5 +24,5 @@ cloudstream { "OVA", ) - iconUrl = "https://www.google.com/s2/favicons?domain=neonime.watch&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=neonime.ink&sz=%size%" } \ No newline at end of file diff --git a/Nimegami/build.gradle.kts b/Nimegami/build.gradle.kts index 153e146c..29da8e82 100644 --- a/Nimegami/build.gradle.kts +++ b/Nimegami/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 7 +version = 11 cloudstream { diff --git a/Nimegami/src/main/kotlin/com/hexated/Extractors.kt b/Nimegami/src/main/kotlin/com/hexated/Extractors.kt index 62a47223..f67b59d6 100644 --- a/Nimegami/src/main/kotlin/com/hexated/Extractors.kt +++ b/Nimegami/src/main/kotlin/com/hexated/Extractors.kt @@ -1,12 +1,8 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.INFER_TYPE -import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.* open class Mitedrive : ExtractorApi() { override val name = "Mitedrive" @@ -50,4 +46,50 @@ open class Mitedrive : ExtractorApi() { @JsonProperty("data") val data: Data? = null, ) +} + +open class Berkasdrive : ExtractorApi() { + override val name = "Berkasdrive" + override val mainUrl = "https://dl.berkasdrive.com" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val res = app.get(url, referer = referer).document + val video = res.select("video#player source").attr("src") + + callback.invoke( + ExtractorLink( + this.name, + this.name, + video, + "$mainUrl/", + Qualities.Unknown.value, + INFER_TYPE + ) + ) + + } + +} + +open class Videogami : ExtractorApi() { + override val name = "Videogami" + override val mainUrl = "https://video.nimegami.id" + override val requiresReferer = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val id = base64Decode(url.substringAfter("url=")).substringAfterLast("/") + loadExtractor("https://hxfile.co/embed-$id.html", "$mainUrl/", subtitleCallback, callback) + } + } \ No newline at end of file diff --git a/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt b/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt index ed1353f7..8c8079cf 100644 --- a/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt +++ b/Nimegami/src/main/kotlin/com/hexated/Nimegami.kt @@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import org.jsoup.nodes.Element import org.jsoup.select.Elements -import java.net.URI class Nimegami : MainAPI() { override var mainUrl = "https://nimegami.id" @@ -77,14 +76,18 @@ class Nimegami : MainAPI() { this.posterUrl = posterUrl addSub(episode) } - } override suspend fun search(query: String): List { - return app.get("$mainUrl/?s=$query&post_type=post").document.select("div.archive article") - .mapNotNull { - it.toSearchResult() - } + val searchResponse = mutableListOf() + for (i in 1..2) { + val res = app.get("$mainUrl/page/$i/?s=$query&post_type=post").document.select("div.archive article") + .mapNotNull { + it.toSearchResult() + } + searchResponse.addAll(res) + } + return searchResponse } override suspend fun load(url: String): LoadResponse { @@ -150,7 +153,7 @@ class Nimegami : MainAPI() { tryParseJson>(base64Decode(data))?.map { sources -> sources.url?.apmap { url -> loadFixedExtractor( - url.fixIframe(), + url, sources.format, "$mainUrl/", subtitleCallback, @@ -185,32 +188,10 @@ class Nimegami : MainAPI() { } } - private fun getBaseUrl(url: String): String { - return URI(url).let { - "${it.scheme}://${it.host}" - } - } - private fun Elements.getContent(css: String): Elements { return this.select("tr:contains($css) td:last-child") } - private fun String.fixIframe(): String { - val url = base64Decode(this.substringAfter("url=").substringAfter("id=")) - return when { - url.contains("hxfile") -> { - val host = getBaseUrl(url) - val id = url.substringAfterLast("/") - "$host/embed-$id.html" - } - url.startsWith("https://mitedrive.my.id") -> url.replace( - "https://mitedrive.my.id", - "https://mitedrive.com" - ) - else -> fixUrl(url) - } - } - data class Sources( @JsonProperty("format") val format: String? = null, @JsonProperty("url") val url: ArrayList? = arrayListOf(), diff --git a/Nimegami/src/main/kotlin/com/hexated/NimegamiPlugin.kt b/Nimegami/src/main/kotlin/com/hexated/NimegamiPlugin.kt index 8ab01378..fe6de7b7 100644 --- a/Nimegami/src/main/kotlin/com/hexated/NimegamiPlugin.kt +++ b/Nimegami/src/main/kotlin/com/hexated/NimegamiPlugin.kt @@ -11,5 +11,7 @@ class NimegamiPlugin: Plugin() { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(Nimegami()) registerExtractorAPI(Mitedrive()) + registerExtractorAPI(Berkasdrive()) + registerExtractorAPI(Videogami()) } } \ No newline at end of file diff --git a/NontonAnimeIDProvider/build.gradle.kts b/NontonAnimeIDProvider/build.gradle.kts index 10f41f92..84af76c6 100644 --- a/NontonAnimeIDProvider/build.gradle.kts +++ b/NontonAnimeIDProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 19 +version = 21 cloudstream { @@ -23,5 +23,5 @@ cloudstream { "OVA", ) - iconUrl = "https://www.google.com/s2/favicons?domain=nontonanimeid.site&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=nontonanimeid.org&sz=%size%" } \ No newline at end of file diff --git a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/Extractors.kt b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..c71d79d2 --- /dev/null +++ b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/Extractors.kt @@ -0,0 +1,86 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.extractors.Hxfile +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities + +open class Gdplayer : ExtractorApi() { + override val name = "Gdplayer" + override val mainUrl = "https://gdplayer.to" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val res = app.get(url, referer = referer).document + val script = res.selectFirst("script:containsData(player = \"\")")?.data() + val kaken = script?.substringAfter("kaken = \"")?.substringBefore("\"") + + val json = app.get( + "$mainUrl/api/?${kaken ?: return}=&_=${APIHolder.unixTimeMS}", + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe() + + json?.sources?.map { + callback.invoke( + ExtractorLink( + this.name, + this.name, + it.file ?: return@map, + "", + getQuality(json.title) + ) + ) + } + } + + private fun getQuality(str: String?): Int { + return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull() + ?: Qualities.Unknown.value + } + + data class Response( + @JsonProperty("title") val title: String? = null, + @JsonProperty("sources") val sources: ArrayList? = null, + ) { + data class Sources( + @JsonProperty("file") val file: String? = null, + @JsonProperty("type") val type: String? = null, + ) + } + +} + +class Nontonanimeid : Hxfile() { + override val name = "Nontonanimeid" + override val mainUrl = "https://nontonanimeid.com" + override val requiresReferer = true +} + +class EmbedKotakAnimeid : Hxfile() { + override val name = "EmbedKotakAnimeid" + override val mainUrl = "https://embed2.kotakanimeid.com" + override val requiresReferer = true +} + +class Kotaksb : Hxfile() { + override val name = "Kotaksb" + override val mainUrl = "https://kotaksb.fun" + override val requiresReferer = true +} + +class KotakAnimeidCom : Hxfile() { + override val name = "KotakAnimeid" + override val mainUrl = "https://kotakanimeid.com" + override val requiresReferer = true +} \ No newline at end of file diff --git a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt index 1735efe3..53a745c8 100644 --- a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt +++ b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProvider.kt @@ -5,7 +5,7 @@ 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.extractors.Hxfile +import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup @@ -13,7 +13,7 @@ import org.jsoup.nodes.Element import java.net.URI class NontonAnimeIDProvider : MainAPI() { - override var mainUrl = "https://nontonanimeid.top" + override var mainUrl = "https://nontonanimeid.org" override var name = "NontonAnimeID" override val hasQuickSearch = false override val hasMainPage = true @@ -29,8 +29,8 @@ class NontonAnimeIDProvider : MainAPI() { companion object { fun getType(t: String): TvType { return when { - t.contains("TV",true) -> TvType.Anime - t.contains("Movie",true) -> TvType.AnimeMovie + t.contains("TV", true) -> TvType.Anime + t.contains("Movie", true) -> TvType.AnimeMovie else -> TvType.OVA } } @@ -50,7 +50,7 @@ class NontonAnimeIDProvider : MainAPI() { "popular-series/" to "Popular Series", ) - override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val document = app.get("$mainUrl/${request.data}").document val home = document.select(".animeseries").mapNotNull { it.toSearchResult() @@ -61,7 +61,7 @@ class NontonAnimeIDProvider : MainAPI() { private fun Element.toSearchResult(): AnimeSearchResponse { val href = fixUrl(this.selectFirst("a")!!.attr("href")) val title = this.selectFirst(".title")?.text() ?: "" - val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("data-src")) + val posterUrl = fixUrlNull(this.selectFirst("img")?.getImageAttr()) return newAnimeSearchResponse(title, href, TvType.Anime) { this.posterUrl = posterUrl @@ -76,7 +76,7 @@ class NontonAnimeIDProvider : MainAPI() { return document.select(".result > ul > li").mapNotNull { val title = it.selectFirst("h2")!!.text().trim() - val poster = it.selectFirst("img")!!.attr("src") + val poster = it.selectFirst("img")?.getImageAttr() val tvType = getType( it.selectFirst(".boxinfores > span.typeseries")!!.text().toString() ) @@ -100,8 +100,9 @@ class NontonAnimeIDProvider : MainAPI() { mainUrl = getBaseUrl(req.url) val document = req.document - val title = document.selectFirst("h1.entry-title.cs")!!.text().removeSurrounding("Nonton Anime", "Sub Indo").trim() - val poster = document.selectFirst(".poster > img")?.attr("data-src") + val title = document.selectFirst("h1.entry-title.cs")!!.text() + .removeSurrounding("Nonton Anime", "Sub Indo").trim() + val poster = document.selectFirst(".poster > img")?.getImageAttr() val tags = document.select(".tagline > a").map { it.text() } val year = Regex("\\d, (\\d*)").find( @@ -150,14 +151,14 @@ class NontonAnimeIDProvider : MainAPI() { val recommendations = document.select(".result > li").mapNotNull { val epHref = it.selectFirst("a")!!.attr("href") val epTitle = it.selectFirst("h3")!!.text() - val epPoster = it.select(".top > img").attr("data-src") + val epPoster = it.selectFirst(".top > img")?.getImageAttr() newAnimeSearchResponse(epTitle, epHref, TvType.Anime) { this.posterUrl = epPoster addDubStatus(dubExist = false, subExist = true) } } - val tracker = APIHolder.getTracker(listOf(title),TrackerType.getTypes(type),year,true) + val tracker = APIHolder.getTracker(listOf(title), TrackerType.getTypes(type), year, true) return newAnimeLoadResponse(title, url, type) { engName = title @@ -186,6 +187,12 @@ class NontonAnimeIDProvider : MainAPI() { val document = app.get(data).document + val nonce = + document.select("script#ajax_video-js-extra").attr("src").substringAfter("base64,") + .let { + AppUtils.parseJson>(base64Decode(it).substringAfter("="))["nonce"] + } + document.select(".container1 > ul > li:not(.boxtab)").apmap { val dataPost = it.attr("data-post") val dataNume = it.attr("data-nume") @@ -198,13 +205,13 @@ class NontonAnimeIDProvider : MainAPI() { "post" to dataPost, "nume" to dataNume, "type" to dataType, - "nonce" to "e4dd8e45c2" + "nonce" to "$nonce" ), referer = data, headers = mapOf("X-Requested-With" to "XMLHttpRequest") ).document.selectFirst("iframe")?.attr("src") - loadExtractor(iframe ?: return@apmap , "$mainUrl/", subtitleCallback, callback) + loadExtractor(iframe ?: return@apmap, "$mainUrl/", subtitleCallback, callback) } return true @@ -215,6 +222,16 @@ class NontonAnimeIDProvider : MainAPI() { "${it.scheme}://${it.host}" } } + + private fun Element.getImageAttr(): String? { + return when { + this.hasAttr("data-src") -> this.attr("abs:data-src") + this.hasAttr("data-lazy-src") -> this.attr("abs:data-lazy-src") + this.hasAttr("srcset") -> this.attr("abs:srcset").substringBefore(" ") + else -> this.attr("abs:src") + } + } + private data class EpResponse( @JsonProperty("posts") val posts: String?, @JsonProperty("max_page") val max_page: Int?, @@ -223,21 +240,3 @@ class NontonAnimeIDProvider : MainAPI() { ) } - -class KotakAnimeid2 : Hxfile() { - override val name = "KotakAnimeid2" - override val mainUrl = "https://embed2.kotakanimeid.com" - override val requiresReferer = true -} - -class KotakAnimeidCom : Hxfile() { - override val name = "KotakAnimeid" - override val mainUrl = "https://nontonanimeid.com" - override val requiresReferer = true -} - -class EmbedKotakAnimeid : Hxfile() { - override val name = "EmbedKotakAnimeid" - override val mainUrl = "https://embed2.kotakanimeid.com" - override val requiresReferer = true -} diff --git a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProviderPlugin.kt b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProviderPlugin.kt index 763e337e..27d493b3 100644 --- a/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProviderPlugin.kt +++ b/NontonAnimeIDProvider/src/main/kotlin/com/hexated/NontonAnimeIDProviderPlugin.kt @@ -10,8 +10,10 @@ class NontonAnimeIDProviderPlugin: Plugin() { override fun load(context: Context) { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(NontonAnimeIDProvider()) - registerExtractorAPI(KotakAnimeid2()) - registerExtractorAPI(KotakAnimeidCom()) + registerExtractorAPI(Nontonanimeid()) registerExtractorAPI(EmbedKotakAnimeid()) + registerExtractorAPI(KotakAnimeidCom()) + registerExtractorAPI(Gdplayer()) + registerExtractorAPI(Kotaksb()) } } \ No newline at end of file diff --git a/OploverzProvider/build.gradle.kts b/OploverzProvider/build.gradle.kts index e2ebbfc0..e021c6c2 100644 --- a/OploverzProvider/build.gradle.kts +++ b/OploverzProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 24 +version = 30 cloudstream { diff --git a/OploverzProvider/src/main/kotlin/com/hexated/Extractors.kt b/OploverzProvider/src/main/kotlin/com/hexated/Extractors.kt index c38c13b8..dc673cb2 100644 --- a/OploverzProvider/src/main/kotlin/com/hexated/Extractors.kt +++ b/OploverzProvider/src/main/kotlin/com/hexated/Extractors.kt @@ -27,10 +27,7 @@ open class Qiwi : ExtractorApi() { "$mainUrl/", getIndexQuality(title), headers = mapOf( - "Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5", "Range" to "bytes=0-", - "Sec-Fetch-Dest" to "video", - "Sec-Fetch-Mode" to "no-cors", ) ) ) diff --git a/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt b/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt index 8ed03196..37c0cf9e 100644 --- a/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt +++ b/OploverzProvider/src/main/kotlin/com/hexated/OploverzProvider.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.* import org.jsoup.nodes.Element class OploverzProvider : MainAPI() { - override var mainUrl = "https://oploverz.red" + override var mainUrl = "https://oploverz.gold" override var name = "Oploverz" override val hasMainPage = true override var lang = "id" @@ -20,7 +20,6 @@ class OploverzProvider : MainAPI() { ) companion object { - const val acefile = "https://acefile.co" fun getType(t: String): TvType { return if (t.contains("OVA", true) || t.contains("Special")) TvType.OVA else if (t.contains("Movie", true)) TvType.AnimeMovie @@ -170,14 +169,14 @@ class OploverzProvider : MainAPI() { headers = mapOf("X-Requested-With" to "XMLHttpRequest") ).document.select("iframe").attr("src") - loadExtractor(fixedIframe(iframe), "$mainUrl/", subtitleCallback, callback) + loadExtractor(fixUrl(iframe), "$mainUrl/", subtitleCallback, callback) } }, { - document.select("div#download tr").map { el -> + document.select("div#download tr").apmap { el -> el.select("a").apmap { - loadFixedExtractor(fixedIframe(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback) + loadFixedExtractor(fixUrl(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback) } } } @@ -217,12 +216,4 @@ class OploverzProvider : MainAPI() { } } - private fun fixedIframe(url: String): String { - val id = Regex("""(?:/f/|/file/)(\w+)""").find(url)?.groupValues?.getOrNull(1) - return when { - url.startsWith(acefile) -> "${acefile}/player/$id" - else -> fixUrl(url) - } - } - } diff --git a/PhimmoichillProvider/build.gradle.kts b/PhimmoichillProvider/build.gradle.kts index ec3f3dc3..1a6207b8 100644 --- a/PhimmoichillProvider/build.gradle.kts +++ b/PhimmoichillProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 6 +version = 7 cloudstream { @@ -24,5 +24,5 @@ cloudstream { "Movie", ) - iconUrl = "https://www.google.com/s2/favicons?domain=phimmoichilla.net&sz=%size%" + iconUrl = "https://www.google.com/s2/favicons?domain=phimmoichillk.net&sz=%size%" } diff --git a/PhimmoichillProvider/src/main/kotlin/com/hexated/PhimmoichillProvider.kt b/PhimmoichillProvider/src/main/kotlin/com/hexated/PhimmoichillProvider.kt index 2d0d256f..3c915e03 100644 --- a/PhimmoichillProvider/src/main/kotlin/com/hexated/PhimmoichillProvider.kt +++ b/PhimmoichillProvider/src/main/kotlin/com/hexated/PhimmoichillProvider.kt @@ -9,7 +9,7 @@ import java.net.URI import java.net.URLDecoder class PhimmoichillProvider : MainAPI() { - override var mainUrl = "https://phimmoichillh.net" + override var mainUrl = "https://phimmoichillk.net" private var directUrl = mainUrl override var name = "Phimmoichill" override val hasMainPage = true diff --git a/Raveeflix/build.gradle.kts b/Raveeflix/build.gradle.kts new file mode 100644 index 00000000..a367f9d5 --- /dev/null +++ b/Raveeflix/build.gradle.kts @@ -0,0 +1,27 @@ +// use an integer for version numbers +version = 3 + + +cloudstream { + language = "id" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AsianDrama", + "TvSeries", + "Movie", + ) + + iconUrl = "https://raveeflix.my.id/favicon.ico" +} diff --git a/Raveeflix/src/main/AndroidManifest.xml b/Raveeflix/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Raveeflix/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt b/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt new file mode 100644 index 00000000..fec8358b --- /dev/null +++ b/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt @@ -0,0 +1,227 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors +import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import org.jsoup.nodes.Element +import org.jsoup.select.Elements + +class Raveeflix : MainAPI() { + override var mainUrl = "https://raveeflix.my.id" + override var name = "Raveeflix" + override val hasMainPage = true + override var lang = "id" + override val supportedTypes = + setOf( + TvType.Movie, + TvType.TvSeries, + TvType.AsianDrama, + ) + + override val mainPage = + mainPageOf( + "categories/trending" to "Trending", + "movies" to "Movies", + "tv" to "Tv-Shows", + "drakor" to "Drakor", + "categories/anime" to "Anime", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest, + ): HomePageResponse { + val pages = if (page > 1) "page/$page/" else "" + val document = app.get("$mainUrl/${request.data}/$pages").document + val home = document.select("section.w-full a.min-w-full").mapNotNull { it.toSearchResult() } + return newHomePageResponse( + HomePageList( + request.name, home, true + ) + ) + } + + private fun Element.toSearchResult(): SearchResponse? { + val title = this.selectFirst("div.text-xl")?.text() ?: return null + val href = fixUrl(this.attr("href")) + val posterUrl = this.selectFirst("div.thumbnail_card")?.attr("style")?.getPoster() + + return newMovieSearchResponse(title, Media(href, posterUrl).toJson(), TvType.Movie, false) { + this.posterUrl = posterUrl + } + } + + override suspend fun search(query: String): List? { + val res = + app.get("$mainUrl/index.json").text.let { AppUtils.tryParseJson>(it) } + return res?.filter { + it.title?.contains( + query, + true + ) == true && !it.section.equals("Categories", true) && !it.section.equals( + "Tags", + true + ) && it.permalink?.contains("/episode") == false + }?.mapNotNull { + newMovieSearchResponse( + it.title ?: return@mapNotNull null, + Media( + fixUrl( + it.permalink?.substringBefore("episode")?.substringBefore("season") + ?: return@mapNotNull null + ) + ).toJson(), + TvType.Movie, + false, + ) + } + } + + override suspend fun load(url: String): LoadResponse { + val media = parseJson(url) + val document = app.get(media.url).document + val title = document.selectFirst("h1.text-4xl")?.text() ?: "No Title" + val poster = media.poster ?: document.selectFirst("div.thumbnail_card, div.w-full.thumbnail_card_related") + ?.attr("style")?.getPoster() + val type = + if (document.select("mux-player").isNullOrEmpty()) TvType.TvSeries else TvType.Movie + val tags = + if (type == TvType.TvSeries) { + document.selectFirst("div.movie-details > p:nth-child(1)") + ?.ownText()?.split(",") + ?.map { it.trim() } + } else { + document.select("span.mr-2") + .map { it.text() }.distinct() + } + + val year = + document.selectFirst("div.movie-details > p:nth-child(2), div.max-w-prose.mb-20 > ul > li:nth-child(2) span") + ?.ownText()?.substringAfter(",")?.toIntOrNull() + val description = + document.selectFirst("div.lead.text-neutral-500, span#storyline") + ?.text()?.trim() + val rating = + document.selectFirst("span#rating")?.text() + ?.toRatingInt() + val actors = + document.select("span#cast").text().split(", ") + .map { it.trim() } + + val recommendations = + document.select("section.w-full a.min-w-full").mapNotNull { it.toSearchResult() } + + return if (type == TvType.TvSeries) { + val sectionSelector = "div.relative > section.w-full a.min-w-full" + val section = document.select(sectionSelector) + val hasMultipleSeason = section.any { it.attr("href").contains("/season-") } + val episodes = if (hasMultipleSeason) { + section.apmap { ss -> + fetchEpisodesFromPages( + ss.attr("href"), + 5, + sectionSelector, + true, + ss.selectFirst("div.text-xl")?.text()?.filter { it.isDigit() } + ?.toIntOrNull() + ) + }.toMutableList().flatten() + } else { + fetchEpisodesFromPages(media.url, 5, sectionSelector, false) + } + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.reversed()) { + this.posterUrl = poster + this.year = year + this.seasonNames + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + } + } else { + newMovieLoadResponse(title, url, TvType.Movie, media.url) { + this.posterUrl = poster + this.year = year + this.plot = description + this.tags = tags + this.rating = rating + addActors(actors) + this.recommendations = recommendations + } + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ): Boolean { + + val video = app.get(data).document.select("mux-player").attr("src") + + callback.invoke( + ExtractorLink( + name, + name, + video, + "", + Qualities.Unknown.value, + ) + ) + + return true + } + + private suspend fun fetchEpisodesFromPages( + baseUrl: String, + maxPages: Int, + sectionSelector: String, + hasMultipleSeasons: Boolean, + season: Int? = null + ): MutableList { + val epsData = mutableListOf() + for (index in 1..maxPages) { + val pageUrl = if (index == 1) baseUrl else "${baseUrl.removeSuffix("/")}/page/$index/" + val episodeVo = app.get(fixUrl(pageUrl)).document.select(sectionSelector) + .getEpisodes(if (hasMultipleSeasons) season else null) + if (episodeVo.isEmpty()) break + epsData.addAll(episodeVo) + } + return epsData + } + + private fun Elements.getEpisodes(season: Int? = 1): List { + return this.mapNotNull { eps -> + val name = eps.selectFirst("div.text-xl")?.text() ?: return@mapNotNull null + val href = fixUrl(eps.attr("href")) + val posterUrl = + eps.selectFirst("div.thumbnail_card")?.attr("style")?.getPoster() + Episode( + href, + name, + posterUrl = posterUrl, + season = season + ) + } + } + + private fun String.getPoster(): String? { + return fixUrlNull( + this.substringAfter("(") + .substringBefore(")"), + ) + } + + data class Media(val url: String, val poster: String? = null) + + data class Index( + @JsonProperty("title") val title: String? = null, + @JsonProperty("permalink") val permalink: String? = null, + @JsonProperty("section") val section: String? = null, + ) +} diff --git a/Raveeflix/src/main/kotlin/com/hexated/RaveeflixPlugin.kt b/Raveeflix/src/main/kotlin/com/hexated/RaveeflixPlugin.kt new file mode 100644 index 00000000..9d379d11 --- /dev/null +++ b/Raveeflix/src/main/kotlin/com/hexated/RaveeflixPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class RaveeflixPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Raveeflix()) + } +} \ No newline at end of file diff --git a/RebahinProvider/build.gradle.kts b/RebahinProvider/build.gradle.kts index 6f308335..0108e9c8 100644 --- a/RebahinProvider/build.gradle.kts +++ b/RebahinProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 8 +version = 9 cloudstream { diff --git a/RebahinProvider/src/main/kotlin/com/hexated/Cgvindo.kt b/RebahinProvider/src/main/kotlin/com/hexated/Cgvindo.kt index cb789f57..19ca8e05 100644 --- a/RebahinProvider/src/main/kotlin/com/hexated/Cgvindo.kt +++ b/RebahinProvider/src/main/kotlin/com/hexated/Cgvindo.kt @@ -3,7 +3,9 @@ package com.hexated import com.lagradost.cloudstream3.TvType class Cgvindo : RebahinProvider() { + override var mainUrl = "http://51.68.185.138/" + override var name = "Cgvindo" } diff --git a/Samehadaku/build.gradle.kts b/Samehadaku/build.gradle.kts index ad80241e..94602b00 100644 --- a/Samehadaku/build.gradle.kts +++ b/Samehadaku/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 17 +version = 22 cloudstream { diff --git a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt index 51404e62..1a6c1c70 100644 --- a/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt +++ b/Samehadaku/src/main/kotlin/com/hexated/Samehadaku.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.nodes.Element class Samehadaku : MainAPI() { - override var mainUrl = "https://samehadaku.digital/" + override var mainUrl = "https://samehadaku.show" override var name = "Samehadaku" override val hasMainPage = true override var lang = "id" @@ -20,10 +20,7 @@ class Samehadaku : MainAPI() { TvType.AnimeMovie, TvType.OVA ) - companion object { - const val acefile = "https://acefile.co" - fun getType(t: String): TvType { return if (t.contains("OVA", true) || t.contains("Special", true)) TvType.OVA else if (t.contains("Movie", true)) TvType.AnimeMovie @@ -63,9 +60,9 @@ class Samehadaku : MainAPI() { if (request.name == "Episode Terbaru") { val home = app.get(request.data + page).document.selectFirst("div.post-show")?.select("ul li") - ?.mapNotNull { - it.toSearchResult() - } ?: throw ErrorLoadingException("No Media Found") + ?.mapNotNull { + it.toSearchResult() + } ?: throw ErrorLoadingException("No Media Found") items.add(HomePageList(request.name, home, true)) } @@ -158,37 +155,11 @@ class Samehadaku : MainAPI() { val document = app.get(data).document - argamap( - { - document.select("div#server ul li div").apmap { - val dataPost = it.attr("data-post") - val dataNume = it.attr("data-nume") - val dataType = it.attr("data-type") - - val iframe = app.post( - url = "$mainUrl/wp-admin/admin-ajax.php", - data = mapOf( - "action" to "player_ajax", - "post" to dataPost, - "nume" to dataNume, - "type" to dataType - ), - referer = data, - headers = mapOf("X-Requested-With" to "XMLHttpRequest"), - ).document.select("iframe").attr("src") - - loadFixedExtractor(fixedIframe(iframe), it.text(), "$mainUrl/", subtitleCallback, callback) - - } - }, - { - document.select("div#downloadb li").map { el -> - el.select("a").apmap { - loadFixedExtractor(fixedIframe(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback) - } - } + document.select("div#downloadb li").map { el -> + el.select("a").apmap { + loadFixedExtractor(fixUrl(it.attr("href")), el.select("strong").text(), "$mainUrl/", subtitleCallback, callback) } - ) + } return true } @@ -217,21 +188,14 @@ class Samehadaku : MainAPI() { } private fun String.fixQuality() : Int { - return when(this) { - "MP4HD" -> Qualities.P720.value + return when(this.uppercase()) { + "4K" -> Qualities.P2160.value "FULLHD" -> Qualities.P1080.value + "MP4HD" -> Qualities.P720.value else -> this.filter { it.isDigit() }.toIntOrNull() ?: Qualities.Unknown.value } } - private fun fixedIframe(url: String): String { - val id = Regex("""(?:/f/|/file/)(\w+)""").find(url)?.groupValues?.getOrNull(1) - return when { - url.startsWith(acefile) -> "${acefile}/player/$id" - else -> fixUrl(url) - } - } - private fun String.removeBloat(): String { return this.replace(Regex("(Nonton)|(Anime)|(Subtitle\\sIndonesia)"), "").trim() } diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 4896f243..4f2c8e13 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 = 206 +version = 225 android { defaultConfig { @@ -9,7 +9,7 @@ android { properties.load(project.rootProject.file("local.properties").inputStream()) buildConfigField("String", "TMDB_API", "\"${properties.getProperty("TMDB_API")}\"") - buildConfigField("String", "OMOVIES_API", "\"${properties.getProperty("OMOVIES_API")}\"") + buildConfigField("String", "GHOSTX_API", "\"${properties.getProperty("GHOSTX_API")}\"") buildConfigField("String", "CINEMATV_API", "\"${properties.getProperty("CINEMATV_API")}\"") buildConfigField("String", "SFMOVIES_API", "\"${properties.getProperty("SFMOVIES_API")}\"") buildConfigField("String", "ZSHOW_API", "\"${properties.getProperty("ZSHOW_API")}\"") @@ -42,5 +42,5 @@ cloudstream { "Movie", ) - iconUrl = "https://raw.githubusercontent.com/hexated/cloudstream-extensions-hexated/master/SoraStream/Icon.png" + iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1193122096159674448/2-modified.png" } diff --git a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt index 0d72837f..e6c623f4 100644 --- a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt +++ b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt @@ -10,8 +10,8 @@ import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.extractors.Pixeldrain -import com.lagradost.cloudstream3.extractors.Vidplay +import com.lagradost.cloudstream3.extractors.Jeniusplay +import com.lagradost.cloudstream3.extractors.PixelDrain import com.lagradost.cloudstream3.utils.* import java.math.BigInteger import java.security.MessageDigest @@ -124,8 +124,8 @@ open class Playm4u : ExtractorApi() { return "$this\\s*=\\s*[\"'](\\S+)[\"'];".toRegex().find(data)?.groupValues?.get(1) ?: "" } - private fun String.toLanguage() : String { - return if(this == "EN") "English" else this + private fun String.toLanguage(): String { + return if (this == "EN") "English" else this } data class Source( @@ -209,7 +209,10 @@ open class VCloud : ExtractorApi() { ) ).document.select("p.text-success ~ a").apmap { val link = it.attr("href") - if (link.contains("workers.dev") || it.text().contains("[Server : 1]") || link.contains("/dl.php?")) { + if (link.contains("workers.dev") || it.text().contains("[Server : 1]") || link.contains( + "/dl.php?" + ) + ) { callback.invoke( ExtractorLink( this.name, @@ -221,7 +224,7 @@ open class VCloud : ExtractorApi() { ) ) } else { - val direct = if(link.contains("gofile.io")) app.get(link).url else link + val direct = if (link.contains("gofile.io")) app.get(link).url else link loadExtractor(direct, referer, subtitleCallback, callback) } } @@ -247,19 +250,20 @@ open class Streamruby : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val id = "/e/(\\w+)".toRegex().find(url)?.groupValues?.get(1) ?: return - val response = app.post("$mainUrl/dl", data = mapOf( - "op" to "embed", - "file_code" to id, - "auto" to "1", - "referer" to "", - ), referer = referer) + val response = app.post( + "$mainUrl/dl", data = mapOf( + "op" to "embed", + "file_code" to id, + "auto" to "1", + "referer" to "", + ), referer = referer + ) val script = if (!getPacked(response.text).isNullOrEmpty()) { getAndUnpack(response.text) } else { response.document.selectFirst("script:containsData(sources:)")?.data() } - val m3u8 = - Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) + val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) M3u8Helper.generateM3u8( name, m3u8 ?: return, @@ -282,17 +286,25 @@ open class Uploadever : ExtractorApi() { ) { var res = app.get(url, referer = referer).document val formUrl = res.select("form").attr("action") - var formData = res.select("form input").associate { it.attr("name") to it.attr("value") }.filterKeys { it != "go" } + var formData = res.select("form input").associate { it.attr("name") to it.attr("value") } + .filterKeys { it != "go" } .toMutableMap() val formReq = app.post(formUrl, data = formData) res = formReq.document - val captchaKey = res.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src").substringAfter("render=") + val captchaKey = + res.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src") + .substringAfter("render=") val token = getCaptchaToken(url, captchaKey, referer = "$mainUrl/") - formData = res.select("form#down input").associate { it.attr("name") to it.attr("value") }.toMutableMap() + formData = res.select("form#down input").associate { it.attr("name") to it.attr("value") } + .toMutableMap() formData["adblock_detected"] = "0" formData["referer"] = url - res = app.post(formReq.url, data = formData + mapOf("g-recaptcha-response" to "$token"), cookies = formReq.cookies).document + res = app.post( + formReq.url, + data = formData + mapOf("g-recaptcha-response" to "$token"), + cookies = formReq.cookies + ).document val video = res.select("div.download-button a.btn.btn-dow.recaptchav2").attr("href") callback.invoke( @@ -325,31 +337,130 @@ open class Netembed : ExtractorApi() { val script = getAndUnpack(response.text) val m3u8 = Regex("((https:|http:)//.*\\.m3u8)").find(script)?.groupValues?.getOrNull(1) ?: return - if(m3u8.startsWith("https://www.febbox.com")) { - callback.invoke( - ExtractorLink( - this.name, - this.name, - m3u8, - "$mainUrl/", - getQuality(m3u8), - INFER_TYPE - ) - ) + M3u8Helper.generateM3u8(this.name, m3u8, "$mainUrl/").forEach(callback) + } +} + +open class Ridoo : ExtractorApi() { + override val name = "Ridoo" + override var mainUrl = "https://ridoo.net" + override val requiresReferer = true + open val defaulQuality = Qualities.P1080.value + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val response = app.get(url, referer = referer) + val script = if (!getPacked(response.text).isNullOrEmpty()) { + getAndUnpack(response.text) } else { - M3u8Helper.generateM3u8( + response.document.selectFirst("script:containsData(sources:)")?.data() + } + val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) + val quality = "qualityLabels.*\"(\\d{3,4})[pP]\"".toRegex().find(script)?.groupValues?.get(1) + callback.invoke( + ExtractorLink( this.name, - m3u8, - "$mainUrl/", - ).forEach(callback) + this.name, + m3u8 ?: return, + mainUrl, + quality?.toIntOrNull() ?: defaulQuality, + INFER_TYPE + ) + ) + } + +} + +open class Gdmirrorbot : ExtractorApi() { + override val name = "Gdmirrorbot" + override val mainUrl = "https://gdmirrorbot.nl" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + app.get(url, referer = referer).document.select("ul#videoLinks li").apmap { + loadExtractor(it.attr("data-link"), "$mainUrl/", subtitleCallback, callback) } } - private suspend fun getQuality(url: String) : Int { - val res = app.get(url, referer = "$mainUrl/").text - val regex = "#quality:\\s*(\\S+)".toRegex().find(res)?.groupValues?.get(1) - return getQualityFromName(regex) +} + +open class Streamvid : ExtractorApi() { + override val name = "Streamvid" + override val mainUrl = "https://streamvid.net" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val response = app.get(url, referer = referer) + val script = if (!getPacked(response.text).isNullOrEmpty()) { + getAndUnpack(response.text) + } else { + response.document.selectFirst("script:containsData(sources:)")?.data() + } + val m3u8 = + Regex("src:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) + M3u8Helper.generateM3u8( + name, + m3u8 ?: return, + mainUrl + ).forEach(callback) } + +} + +open class Embedrise : ExtractorApi() { + override val name = "Embedrise" + override val mainUrl = "https://embedrise.com" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val res = app.get(url, referer = referer).document + val title = res.select("title").text() + val video = res.select("video#player source").attr("src") + + callback.invoke( + ExtractorLink( + this.name, + this.name, + video, + "$mainUrl/", + getIndexQuality(title), + INFER_TYPE + ) + ) + + } + +} + +class FilemoonNl : Ridoo() { + override val name = "FilemoonNl" + override var mainUrl = "https://filemoon.nl" + override val defaulQuality = Qualities.Unknown.value +} + +class Alions : Ridoo() { + override val name = "Alions" + override var mainUrl = "https://alions.pro" + override val defaulQuality = Qualities.Unknown.value } class Streamwish : Filesim() { @@ -357,9 +468,9 @@ class Streamwish : Filesim() { override var mainUrl = "https://streamwish.to" } -class Wishfast : Filesim() { - override val name = "Wishfast" - override var mainUrl = "https://wishfast.top" +class UqloadsXyz : Filesim() { + override val name = "Uqloads" + override var mainUrl = "https://uqloads.xyz" } class FilelionsTo : Filesim() { @@ -367,7 +478,7 @@ class FilelionsTo : Filesim() { override var mainUrl = "https://filelions.to" } -class Pixeldra : Pixeldrain() { +class Pixeldra : PixelDrain() { override val mainUrl = "https://pixeldra.in" } @@ -386,7 +497,7 @@ class Animefever : Filesim() { override var mainUrl = "https://animefever.fun" } -class Multimovies : Filesim() { +class Multimovies : Ridoo() { override val name = "Multimovies" override var mainUrl = "https://multimovies.cloud" } @@ -405,12 +516,13 @@ class Embedwish : Filesim() { override val name = "Embedwish" override var mainUrl = "https://embedwish.com" } - -class Vidplay2 : Vidplay() { - override val mainUrl = "https://vidplay.online" -} - -class Flaswish : Filesim() { +class Flaswish : Ridoo() { override val name = "Flaswish" override var mainUrl = "https://flaswish.com" + override val defaulQuality = Qualities.Unknown.value +} + +class Comedyshow : Jeniusplay() { + override val mainUrl = "https://comedyshow.to" + override val name = "Comedyshow" } \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index ef4bc6d9..3a5873df 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -1,20 +1,17 @@ package com.hexated -import com.hexated.AESGCM.decrypt import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS 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.helper.AesHelper.cryptoAESHandler -import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.internal.closeQuietly import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.select.Elements @@ -32,40 +29,41 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val headers = mapOf( - "X-Requested-With" to "XMLHttpRequest" - ) + val headers = mapOf("X-Requested-With" to "XMLHttpRequest") fun Document.getServers(): List> { return this.select("a").map { it.attr("data-id") to it.text() } } - val media = app.get( - "$gokuAPI/ajax/movie/search?keyword=$title", headers = headers - ).document.select("div.item").find { ele -> - val url = ele.selectFirst("a.movie-link")?.attr("href") - val titleMedia = ele.select("h3.movie-name").text() - val titleSlug = title.createSlug() - val yearMedia = ele.select("div.info-split > div:first-child").text().toIntOrNull() - val lastSeasonMedia = - ele.select("div.info-split > div:nth-child(2)").text().substringAfter("SS") - .substringBefore("/").trim().toIntOrNull() - (titleMedia.equals(title, true) || titleMedia.createSlug() - .equals(titleSlug) || url?.contains("$titleSlug-") == true) && (if (season == null) { - yearMedia == year && url?.contains("/movie/") == true - } else { - lastSeasonMedia == lastSeason && url?.contains("/series/") == true - }) - } ?: return + val media = + app.get("$gokuAPI/ajax/movie/search?keyword=$title", headers = headers).document.select( + "div.item" + ).find { ele -> + val url = ele.selectFirst("a.movie-link")?.attr("href") + val titleMedia = ele.select("h3.movie-name").text() + val titleSlug = title.createSlug() + val yearMedia = ele.select("div.info-split > div:first-child").text().toIntOrNull() + val lastSeasonMedia = + ele.select("div.info-split > div:nth-child(2)").text().substringAfter("SS") + .substringBefore("/").trim().toIntOrNull() + (titleMedia.equals(title, true) || titleMedia.createSlug() + .equals(titleSlug) || url?.contains("$titleSlug-") == true) && (if (season == null) { + yearMedia == year && url?.contains("/movie/") == true + } else { + lastSeasonMedia == lastSeason && url?.contains("/series/") == true + }) + } ?: return val serversId = if (season == null) { val movieId = app.get( fixUrl( - media.selectFirst("a")?.attr("href") ?: return, gokuAPI + media.selectFirst("a")?.attr("href") + ?: return, gokuAPI ) ).url.substringAfterLast("/") app.get( - "$gokuAPI/ajax/movie/episode/servers/$movieId", headers = headers + "$gokuAPI/ajax/movie/episode/servers/$movieId", + headers = headers ).document.getServers() } else { val seasonId = app.get( @@ -75,20 +73,23 @@ object SoraExtractor : SoraStream() { ).document.select("a.ss-item").find { it.ownText().equals("Season $season", true) } ?.attr("data-id") val episodeId = app.get( - "$gokuAPI/ajax/movie/season/episodes/${seasonId ?: return}", headers = headers + "$gokuAPI/ajax/movie/season/episodes/${seasonId ?: return}", + headers = headers ).document.select("div.item").find { it.selectFirst("strong")?.text().equals("Eps $episode:", true) }?.selectFirst("a")?.attr("data-id") app.get( - "$gokuAPI/ajax/movie/episode/servers/${episodeId ?: return}", headers = headers + "$gokuAPI/ajax/movie/episode/servers/${episodeId ?: return}", + headers = headers ).document.getServers() } serversId.apmap { (id, name) -> val iframe = app.get("$gokuAPI/ajax/movie/episode/server/sources/$id", headers = headers) - .parsedSafe()?.data?.link ?: return@apmap + .parsedSafe()?.data?.link + ?: return@apmap loadCustomExtractor( name, iframe, @@ -103,7 +104,6 @@ object SoraExtractor : SoraStream() { id: Int? = null, season: Int? = null, episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { val url = if (season == null) { @@ -124,89 +124,17 @@ object SoraExtractor : SoraStream() { httpsify(srcrcp), referer = iframedoc ).document.selectFirst("script:containsData(Playerjs)")?.data() - val video = script?.substringAfter("file:\"#2")?.substringBefore("\"") - ?.replace(Regex("(//\\S+?=)"), "")?.let { base64Decode(it) } + val video = script?.substringAfter("file:\"#9")?.substringBefore("\"") + ?.replace(Regex("/@#@\\S+?=?="), "")?.let { base64Decode(it) } callback.invoke( ExtractorLink( - "Vidsrc", - "Vidsrc", - video ?: return, - "https://vidsrc.stream/", - Qualities.P1080.value, - INFER_TYPE + "Vidsrc", "Vidsrc", video + ?: return, "https://vidsrc.stream/", Qualities.P1080.value, INFER_TYPE ) ) } - suspend fun invokeDbgo( - id: String? = null, - season: Int? = null, - episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - - val iframeDbgo: String? - val script = if (season == null) { - val doc = app.get("$dbgoAPI/imdb.php?id=$id").document - iframeDbgo = doc.select("div.myvideo iframe").attr("src") - app.get(iframeDbgo, referer = "$dbgoAPI/").document.select("script") - .find { it.data().contains("CDNplayerConfig =") }?.data() - } else { - val doc = app.get("$dbgoAPI/tv-imdb.php?id=$id&s=$season").document - iframeDbgo = doc.select("div.myvideo iframe").attr("src") - val token = app.get( - iframeDbgo, referer = "$dbgoAPI/" - ).document.selectFirst("select#translator-name option")?.attr("data-token") - app.get("https://voidboost.net/serial/$token/iframe?s=$season&e=$episode&h=dbgo.fun").document.select( - "script" - ).find { it.data().contains("CDNplayerConfig =") }?.data() - } ?: return - - val source = - Regex("['|\"]file['|\"]:\\s['|\"](#\\S+?)['|\"]").find(script)?.groupValues?.get( - 1 - ) ?: return - val subtitle = - Regex("['|\"]subtitle['|\"]:\\s['|\"](\\S+?)['|\"]").find(script)?.groupValues?.get( - 1 - ) - - val ref = getBaseUrl(iframeDbgo) - decryptStreamUrl(source).split(",").map { links -> - val quality = Regex("\\[(\\d*p.*?)]").find(links)?.groupValues?.getOrNull(1)?.trim() - ?: return@map null - links.replace("[$quality]", "").split(" or ").map { it.trim() }.map { link -> - val name = if (link.contains(".m3u8")) "Dbgo (Main)" else "Dbgo (Backup)" - callback.invoke( - ExtractorLink( - name, - name, - link, - "$ref/", - getQuality(quality), - isM3u8 = link.contains(".m3u8"), - headers = mapOf( - "Origin" to ref - ) - ) - ) - } - } - - subtitle?.split(",")?.map { sub -> - val language = Regex("\\[(.*)]").find(sub)?.groupValues?.getOrNull(1) ?: return@map null - val link = sub.replace("[$language]", "").trim() - subtitleCallback.invoke( - SubtitleFile( - getDbgoLanguage(language), link - ) - ) - } - - } - suspend fun invokeDreamfilm( title: String? = null, season: Int? = null, @@ -227,7 +155,8 @@ object SoraExtractor : SoraStream() { ?.attr("data-src") loadCustomExtractor( null, - iframe ?: return@apmap, + iframe + ?: return@apmap, "$dreamfilmAPI/", subtitleCallback, callback, @@ -253,9 +182,9 @@ object SoraExtractor : SoraStream() { val req = app.get(url) val directUrl = getBaseUrl(req.url) val iframe = req.document.selectFirst("div.pframe iframe")?.attr("src") ?: return - if(!iframe.contains("youtube")) { + if (!iframe.contains("youtube")) { loadExtractor(iframe, "$directUrl/", subtitleCallback) { link -> - if(link.quality == Qualities.Unknown.value) { + if (link.quality == Qualities.Unknown.value) { callback.invoke( ExtractorLink( link.source, @@ -273,6 +202,48 @@ object SoraExtractor : SoraStream() { } } + suspend fun invokeMoviefiction( + title: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title?.createSlug() + val url = if (season == null) { + "$moviefictionAPI/movies/$fixTitle" + } else { + "$moviefictionAPI/episode/$fixTitle-${season}x${episode}" + } + val req = app.get(url) + val directUrl = getBaseUrl(req.url) + req.document.select("ul.bx-lst.aa-tbs li a").apmap { + val iframe = app.get(base64Decode(it.attr("data-src"))).document.selectFirst("iframe") + ?.attr("src") ?: return@apmap + loadExtractor(iframe, "$directUrl/", subtitleCallback) { link -> + when { + link.name == "Bestx" && link.quality == Qualities.Unknown.value -> { + callback.invoke( + ExtractorLink( + "Moviefiction", + "Moviefiction", + link.url, + link.referer, + Qualities.P1080.value, + link.type, + link.headers, + link.extractorData + ) + ) + } + link.name != "Bestx" -> { + callback.invoke(link) + } + } + } + } + } + suspend fun invokeAoneroom( title: String? = null, year: Int? = null, @@ -281,9 +252,8 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { - val headers = mapOf( - "Authorization" to "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjcyODc3MjQ5OTg4MzA0NzM5NzYsInV0cCI6MSwiZXhwIjoxNzEwMzg4NzczLCJpYXQiOjE3MDI2MTI3NzN9.Myt-gVHfPfQFbFyRX3WXtiiwvRzDwBrXTEKy1l-GDRU" - ) + val headers = + mapOf("Authorization" to "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjcyODc3MjQ5OTg4MzA0NzM5NzYsInV0cCI6MSwiZXhwIjoxNzEwMzg4NzczLCJpYXQiOjE3MDI2MTI3NzN9.Myt-gVHfPfQFbFyRX3WXtiiwvRzDwBrXTEKy1l-GDRU") val subjectId = app.post( "$aoneroomAPI/wefeed-mobile-bff/subject-api/search", data = mapOf( "page" to "1", @@ -296,7 +266,7 @@ object SoraExtractor : SoraStream() { }?.subjectId val data = app.get( - "$aoneroomAPI/wefeed-mobile-bff/subject-api/resource?subjectId=${subjectId ?: return}&page=1&perPage=10000&all=0&startPosition=1&endPosition=1&pagerMode=0&resolution=480", + "$aoneroomAPI/wefeed-mobile-bff/subject-api/resource?subjectId=${subjectId ?: return}&page=1&perPage=20&all=0&startPosition=1&endPosition=1&pagerMode=0&resolution=480", headers = headers ).parsedSafe()?.data?.list?.findLast { it.se == (season ?: 0) && it.ep == (episode ?: 0) @@ -304,12 +274,8 @@ object SoraExtractor : SoraStream() { callback.invoke( ExtractorLink( - "Aoneroom", - "Aoneroom", - data?.resourceLink ?: return, - "", - data.resolution ?: Qualities.Unknown.value, - INFER_TYPE + "Aoneroom", "Aoneroom", data?.resourceLink + ?: return, "", data.resolution ?: Qualities.Unknown.value, INFER_TYPE ) ) @@ -324,6 +290,50 @@ object SoraExtractor : SoraStream() { } + suspend fun invokeWatchCartoon( + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.createSlug() + val url = if (season == null) { + "$watchCartoonAPI/movies/$fixTitle-$year" + } else { + "$watchCartoonAPI/episode/$fixTitle-season-$season-episode-$episode" + } + + val req = app.get(url) + val host = getBaseUrl(req.url) + val doc = req.document + + val id = doc.select("link[rel=shortlink]").attr("href").substringAfterLast("=") + doc.select("div.form-group.list-server option").apmap { + val server = app.get( + "$host/ajax-get-link-stream/?server=${it.attr("value")}&filmId=$id", + headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).text + loadExtractor(server, "$host/", subtitleCallback) { link -> + if (link.quality == Qualities.Unknown.value) { + callback.invoke( + ExtractorLink( + "WatchCartoon", + "WatchCartoon", + link.url, + link.referer, + Qualities.P720.value, + link.type, + link.headers, + link.extractorData + ) + ) + } + } + } + } + suspend fun invokeNetmovies( title: String? = null, year: Int? = null, @@ -358,6 +368,30 @@ object SoraExtractor : SoraStream() { invokeWpmovies("ZShow", url, subtitleCallback, callback, encrypt = true) } + suspend fun invokeMMovies( + title: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.createSlug() + val url = if (season == null) { + "$mMoviesAPI/movies/$fixTitle" + } else { + "$mMoviesAPI/episodes/$fixTitle-${season}x${episode}" + } + + invokeWpmovies( + null, + url, + subtitleCallback, + callback, + true, + hasCloudflare = true, + ) + } + private suspend fun invokeWpmovies( name: String? = null, url: String? = null, @@ -365,36 +399,38 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, fixIframe: Boolean = false, encrypt: Boolean = false, + hasCloudflare: Boolean = false, + interceptor: Interceptor? = null, ) { fun String.fixBloat(): String { return this.replace("\"", "").replace("\\", "") } - val res = app.get(url ?: return) + val res = app.get(url ?: return, interceptor = if (hasCloudflare) interceptor else null) val referer = getBaseUrl(res.url) val document = res.document document.select("ul#playeroptionsul > li").map { - Triple( - it.attr("data-post"), it.attr("data-nume"), it.attr("data-type") - ) + Triple(it.attr("data-post"), it.attr("data-nume"), it.attr("data-type")) }.apmap { (id, nume, type) -> delay(1000) val json = app.post( url = "$referer/wp-admin/admin-ajax.php", data = mapOf( - "action" to "doo_player_ajax", "post" to id, "nume" to nume, "type" to type + "action" to "doo_player_ajax", + "post" to id, + "nume" to nume, + "type" to type ), headers = mapOf("Accept" to "*/*", "X-Requested-With" to "XMLHttpRequest"), - referer = url + referer = url, + interceptor = if (hasCloudflare) interceptor else null ) val source = tryParseJson(json.text)?.let { when { encrypt -> { val meta = tryParseJson(it.embed_url)?.meta ?: return@apmap val key = generateWpKey(it.key ?: return@apmap, meta) - cryptoAESHandler( - it.embed_url, key.toByteArray(), false - )?.fixBloat() + cryptoAESHandler(it.embed_url, key.toByteArray(), false)?.fixBloat() } fixIframe -> Jsoup.parse(it.embed_url).select("IFRAME").attr("SRC") @@ -421,7 +457,9 @@ object SoraExtractor : SoraStream() { .filter { element -> element.select("span.flag img").attr("src").contains("/en.") } .map { Triple( - it.attr("data-post"), it.attr("data-nume"), it.attr("data-type") + it.attr("data-post"), + it.attr("data-nume"), + it.attr("data-type") ) }.apmap { (id, nume, type) -> val source = app.get( @@ -430,7 +468,21 @@ object SoraExtractor : SoraStream() { referer = "$host/" ).parsed().embed_url if (!source.contains("youtube")) { - loadExtractor(source, "$host/", subtitleCallback, callback) + if (source.startsWith("https://voe.sx")) { + val req = app.get(source, referer = "$host/") + val server = getBaseUrl(req.url) + val script = req.text.substringAfter("wc0 = '").substringBefore("'") + val video = + tryParseJson>(base64Decode(script))?.get("file") + M3u8Helper.generateM3u8( + "Voe", + video ?: return@apmap, + "$server/", + headers = mapOf("Origin" to server) + ).forEach(callback) + } else { + loadExtractor(source, "$host/", subtitleCallback, callback) + } } } } @@ -463,8 +515,8 @@ object SoraExtractor : SoraStream() { delay(4000) links.map { (link, quality) -> - val name = - quality?.replace(Regex("\\d{3,4}p"), "Noverse")?.replace(".", " ") ?: "Noverse" + val name = quality?.replace(Regex("\\d{3,4}p"), "Noverse")?.replace(".", " ") + ?: "Noverse" callback.invoke( ExtractorLink( "Noverse", @@ -520,15 +572,14 @@ object SoraExtractor : SoraStream() { subSourcesData?.get("s$seasonSlug")?.get("e$episodeSlug") } - val scriptUser = - doc.select("script").find { it.data().contains("var userNonce") }?.data() ?: return + val scriptUser = doc.select("script").find { it.data().contains("var userNonce") }?.data() + ?: return val userNonce = Regex("var\\suserNonce.*?[\"|'](\\S+?)[\"|'];").find(scriptUser)?.groupValues?.get(1) val userId = Regex("var\\suser_id.*?[\"|'](\\S+?)[\"|'];").find(scriptUser)?.groupValues?.get(1) - val listSources = sources.withIndex() - .groupBy { it.index / 2 } + val listSources = sources.withIndex().groupBy { it.index / 2 } .map { entry -> entry.value.map { it.value } } listSources.apmap { src -> @@ -557,11 +608,8 @@ object SoraExtractor : SoraStream() { callback.invoke( ExtractorLink( - "Filmxy", - "Filmxy $server [$size]", - link ?: return@map, - "$filmxyAPI/", - getQualityFromName(quality) + "Filmxy", "Filmxy $server [$size]", link + ?: return@map, "$filmxyAPI/", getQualityFromName(quality) ) ) } @@ -570,8 +618,8 @@ object SoraExtractor : SoraStream() { subSources?.mapKeys { sub -> subtitleCallback.invoke( SubtitleFile( - SubtitleHelper.fromTwoLettersToLanguage(sub.key) ?: return@mapKeys, - "https://www.mysubs.org/get-subtitle/${sub.value}" + SubtitleHelper.fromTwoLettersToLanguage(sub.key) + ?: return@mapKeys, "https://www.mysubs.org/get-subtitle/${sub.value}" ) ) } @@ -604,15 +652,17 @@ object SoraExtractor : SoraStream() { val servers = if (season == null) { val player = res.select("div.tabs__pane p a[href*=https://ouo]").attr("href") val ouo = bypassOuo(player) - app.get(ouo ?: return).document.select("article p:matches(\\d{3,4}[pP]) + p:has(a)") - .flatMap { ele -> - val entry = ele.previousElementSibling()?.text() ?: "" - ele.select("a").map { - Triple(entry.getQuality(), entry.getTag(), it.attr("href")) - }.filter { - it.third.startsWith("https://pixeldrain.com") || it.third.startsWith("https://krakenfiles.com") - } + app.get( + ouo + ?: return + ).document.select("article p:matches(\\d{3,4}[pP]) + p:has(a)").flatMap { ele -> + val entry = ele.previousElementSibling()?.text() ?: "" + ele.select("a").map { + Triple(entry.getQuality(), entry.getTag(), it.attr("href")) + }.filter { + it.third.startsWith("https://pixeldrain.com") || it.third.startsWith("https://krakenfiles.com") } + } } else { val data = res.select("tbody tr:has(td[data-order=${epsSlug.second}])") val qualities = @@ -627,7 +677,8 @@ object SoraExtractor : SoraStream() { val server = if (it.third.startsWith("https://ouo")) bypassOuo(it.third) else it.third loadCustomTagExtractor( it.second, - server ?: return@apmap, + server + ?: return@apmap, "$dramadayAPI/", subtitleCallback, callback, @@ -637,55 +688,6 @@ object SoraExtractor : SoraStream() { } - suspend fun invokeWatchflx( - tmdbId: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit - ) { - val epsSlug = getEpisodeSlug(season, episode) - val cookies = getWatchflxCookies() - val url = if (season == null) { - "$watchflxAPI/browse/playmovie/$tmdbId/directplay" - } else { - "$watchflxAPI/Playseries/series/$tmdbId/directplay" - } - val res = app.get(url, cookies = cookies).document - - val showUrl = if (season == null) { - res.selectFirst("iframe.movie_player")?.attr("src") - } else { - val seasonUrl = - res.select("ul.nav.nav-tabs.tabs-left li:matches(Season $season\$) a").attr("href") - val episodeUrl = app.get( - seasonUrl, cookies = cookies - ).document.select("div.thumb-nail-list a:contains(${epsSlug.second}:)").attr("href") - app.get(episodeUrl, cookies = cookies).document.selectFirst("iframe.movie_player") - ?.attr("src") - } - val iframe = app.get( - showUrl ?: return, referer = "$watchflxAPI/" - ).document.selectFirst("div#the_frame iframe")?.attr("src") - ?.let { fixUrl(it, getBaseUrl(showUrl)) } ?: return - - val video = app.get(iframe.replace("/loc/", "/pro/"), referer = iframe).text.let { - """mp4_url\s*=\s*["'](.*)["'];""".toRegex().find(it)?.groupValues?.getOrNull(1) - } - - callback.invoke( - ExtractorLink( - "Watchflx", - "Watchflx", - video ?: return, - "$watchflxAPI/", - Qualities.P1080.value, - INFER_TYPE - ) - ) - - - } - suspend fun invokeKimcartoon( title: String? = null, season: Int? = null, @@ -735,7 +737,10 @@ object SoraExtractor : SoraStream() { json.subtitlingList?.map { sub -> subtitleCallback.invoke( SubtitleFile( - getVipLanguage(sub.languageAbbr ?: return@map), sub.subtitlingUrl ?: return@map + getVipLanguage( + sub.languageAbbr + ?: return@map + ), sub.subtitlingUrl ?: return@map ) ) } @@ -754,29 +759,27 @@ object SoraExtractor : SoraStream() { "$vidsrctoAPI/embed/tv/$imdbId/$season/$episode" } - val mediaId = - app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return + val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") + ?: return - app.get("$vidsrctoAPI/ajax/embed/episode/$mediaId/sources") - .parsedSafe()?.result?.apmap { - val encUrl = app.get("$vidsrctoAPI/ajax/embed/source/${it.id}") - .parsedSafe()?.result?.url - loadExtractor( - vidsrctoDecrypt(encUrl ?: return@apmap), - "$vidsrctoAPI/", - subtitleCallback, - callback - ) - } + app.get( + "$vidsrctoAPI/ajax/embed/episode/$mediaId/sources", headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe()?.result?.apmap { + val encUrl = app.get("$vidsrctoAPI/ajax/embed/source/${it.id}") + .parsedSafe()?.result?.url + loadExtractor( + vidsrctoDecrypt( + encUrl + ?: return@apmap + ), "$vidsrctoAPI/", subtitleCallback, callback + ) + } val subtitles = app.get("$vidsrctoAPI/ajax/embed/episode/$mediaId/subtitles").text tryParseJson>(subtitles)?.map { - subtitleCallback.invoke( - SubtitleFile( - it.label ?: "", - it.file ?: return@map - ) - ) + subtitleCallback.invoke(SubtitleFile(it.label ?: "", it.file ?: return@map)) } } @@ -797,7 +800,8 @@ object SoraExtractor : SoraStream() { else -> "1" } val res = app.get( - "$kissKhAPI/api/DramaList/Search?q=$title&type=$type", referer = "$kissKhAPI/" + "$kissKhAPI/api/DramaList/Search?q=$title&type=$type", + referer = "$kissKhAPI/" ).text.let { tryParseJson>(it) } ?: return @@ -838,14 +842,15 @@ object SoraExtractor : SoraStream() { listOf(source.video, source.thirdParty).apmap { link -> if (link?.contains(".m3u8") == true) { M3u8Helper.generateM3u8( - "Kisskh", link, "$kissKhAPI/", headers = mapOf("Origin" to kissKhAPI) + "Kisskh", + link, + "$kissKhAPI/", + headers = mapOf("Origin" to kissKhAPI) ).forEach(callback) } else { loadExtractor( - link?.substringBefore("=http") ?: return@apmap null, - "$kissKhAPI/", - subtitleCallback, - callback + link?.substringBefore("=http") + ?: return@apmap null, "$kissKhAPI/", subtitleCallback, callback ) } } @@ -855,7 +860,8 @@ object SoraExtractor : SoraStream() { tryParseJson>(resSub)?.map { sub -> subtitleCallback.invoke( SubtitleFile( - getLanguage(sub.label ?: return@map), sub.src ?: return@map + getLanguage(sub.label ?: return@map), sub.src + ?: return@map ) ) } @@ -876,18 +882,74 @@ object SoraExtractor : SoraStream() { ) { val (aniId, malId) = convertTmdbToAnimeId( - title, date, airedDate, if (season == null) TvType.AnimeMovie else TvType.Anime + title, + date, + airedDate, + if (season == null) TvType.AnimeMovie else TvType.Anime ) - argamap({ - invokeAnimetosho(malId, season, episode, subtitleCallback, callback) - }, { - invokeAniwatch(malId, episode, subtitleCallback, callback) - }, { - if (season != null) invokeCrunchyroll( - aniId, malId, epsTitle, season, episode, subtitleCallback, callback + val malsync = app.get("$malsyncAPI/mal/anime/${malId ?: return}") + .parsedSafe()?.sites + + val zoroIds = malsync?.zoro?.keys?.map { it } + val aniwaveId = malsync?.nineAnime?.firstNotNullOf { it.value["url"] } + + argamap( + { + invokeAnimetosho(malId, season, episode, subtitleCallback, callback) + }, + { + invokeAniwatch(zoroIds, episode, subtitleCallback, callback) + }, + { + invokeAniwave(aniwaveId, episode, subtitleCallback, callback) + }, + { + if (season != null) invokeCrunchyroll( + aniId, + malId, + epsTitle, + season, + episode, + subtitleCallback, + callback + ) + } + ) + } + + private suspend fun invokeAniwave( + url: String? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val res = app.get(url ?: return).document + val id = res.select("div#watch-main").attr("data-id") + val episodeId = + app.get("$aniwaveAPI/ajax/episode/list/$id?vrf=${AniwaveUtils.encodeVrf(id)}") + .parsedSafe()?.asJsoup() + ?.selectFirst("ul.ep-range li a[data-num=${episode ?: 1}]")?.attr("data-ids") + ?: return + val servers = + app.get("$aniwaveAPI/ajax/server/list/$episodeId?vrf=${AniwaveUtils.encodeVrf(episodeId)}") + .parsedSafe()?.asJsoup() + ?.select("div.servers > div[data-type!=sub] ul li") ?: return + + servers.apmap { + val linkId = it.attr("data-link-id") + val iframe = + app.get("$aniwaveAPI/ajax/server/$linkId?vrf=${AniwaveUtils.encodeVrf(linkId)}") + .parsedSafe()?.result?.decrypt() + val audio = if (it.attr("data-cmid").endsWith("softsub")) "Raw" else "English Dub" + loadCustomExtractor( + "${it.text()} [$audio]", + iframe ?: return@apmap, + "$aniwaveAPI/", + subtitleCallback, + callback, ) - }) + } } private suspend fun invokeAnimetosho( @@ -924,14 +986,19 @@ object SoraExtractor : SoraStream() { servers.filter { it.third in arrayOf(Qualities.P1080.value, Qualities.P720.value) }.apmap { loadCustomTagExtractor( - it.second, it.first, "$animetoshoAPI/", subtitleCallback, callback, it.third + it.second, + it.first, + "$animetoshoAPI/", + subtitleCallback, + callback, + it.third ) } } private suspend fun invokeAniwatch( - malId: Int? = null, + animeIds: List? = null, episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit @@ -939,11 +1006,10 @@ object SoraExtractor : SoraStream() { val headers = mapOf( "X-Requested-With" to "XMLHttpRequest", ) - val animeId = app.get("$malsyncAPI/mal/anime/${malId ?: return}") - .parsedSafe()?.sites?.zoro?.keys?.map { it } - animeId?.apmap { id -> + animeIds?.apmap { id -> val episodeId = app.get( - "$aniwatchAPI/ajax/v2/episode/list/${id ?: return@apmap}", headers = headers + "$aniwatchAPI/ajax/v2/episode/list/${id ?: return@apmap}", + headers = headers ).parsedSafe()?.html?.let { Jsoup.parse(it) }?.select("div.ss-list a")?.find { it.attr("data-number") == "${episode ?: 1}" } @@ -965,7 +1031,8 @@ object SoraExtractor : SoraStream() { val iframe = app.get( "$aniwatchAPI/ajax/v2/episode/sources?id=${server.second ?: return@servers}", headers = headers - ).parsedSafe()?.link ?: return@servers + ).parsedSafe()?.link + ?: return@servers val audio = if (server.third == "sub") "Raw" else "English Dub" loadCustomExtractor( "${server.first} [$audio]", @@ -1006,7 +1073,8 @@ object SoraExtractor : SoraStream() { } else { scriptData.find { it.first?.contains( - "$fixTitle", true + "$fixTitle", + true ) == true && it.second?.contains("$year") == true } } @@ -1023,15 +1091,20 @@ object SoraExtractor : SoraStream() { val link = Regex("((https:|http:)//.*\\.mp4)").find(source.text)?.value ?: return callback.invoke( ExtractorLink( - "Ling", "Ling", "$link/index.m3u8", "$lingAPI/", Qualities.P720.value, INFER_TYPE + "Ling", + "Ling", + "$link/index.m3u8", + "$lingAPI/", + Qualities.P720.value, + INFER_TYPE ) ) source.document.select("div#player-tracks track").map { subtitleCallback.invoke( SubtitleFile( - SubtitleHelper.fromTwoLettersToLanguage(it.attr("srclang")) ?: return@map null, - it.attr("src") + SubtitleHelper.fromTwoLettersToLanguage(it.attr("srclang")) + ?: return@map null, it.attr("src") ) ) } @@ -1069,7 +1142,7 @@ object SoraExtractor : SoraStream() { child.select("span").text().equals("Episode $episode", true) }?.attr("href") } - }.filter { it.first.contains(Regex("(2160p)|(1080p)")) } + }.filter { it.first.contains(Regex("(2160p)|(1080p)")) }.reversed().takeLast(3) iframeList.apmap { (quality, link) -> val driveLink = bypassHrefli(link ?: return@apmap) @@ -1099,11 +1172,8 @@ object SoraExtractor : SoraStream() { val size = getIndexSize(quality) callback.invoke( ExtractorLink( - "UHDMovies", - "UHDMovies $tags [$size]", - downloadLink ?: return@apmap, - "", - qualities + "UHDMovies", "UHDMovies $tags [$size]", downloadLink + ?: return@apmap, "", qualities ) ) } @@ -1176,27 +1246,28 @@ object SoraExtractor : SoraStream() { val hTag = if (season == null) "h5" else "h3" val aTag = if (season == null) "Download Now" else "V-Cloud" val sTag = if (season == null) "" else "(Season $season|S$seasonSlug)" - val entry = res.select("div.entry-content > $hTag:matches((?i)$sTag.*(1080p|2160p))") - .findLast { element -> !element.text().contains("Download", true) } ?: return - val tags = - """(?:1080p|2160p)(.*)""".toRegex().find(entry.text())?.groupValues?.get(1)?.trim() - val href = - entry.nextElementSibling()?.select("a:contains($aTag)")?.attr("href") - val selector = - if (season == null) "p a:contains(V-Cloud)" else "h4:matches(0?$episode) + p a:contains(V-Cloud)" - val server = app.get( - href ?: return, interceptor = CloudflareKiller() - ).document.selectFirst("div.entry-content > $selector") - ?.attr("href") ?: return + val entries = res.select("div.entry-content > $hTag:matches((?i)$sTag.*(1080p|2160p))") + .filter { element -> !element.text().contains("Download", true) }.takeLast(2) + entries.apmap { + val tags = + """(?:1080p|2160p)(.*)""".toRegex().find(it.text())?.groupValues?.get(1)?.trim() + val href = it.nextElementSibling()?.select("a:contains($aTag)")?.attr("href") + val selector = + if (season == null) "p a:contains(V-Cloud)" else "h4:matches(0?$episode) + p a:contains(V-Cloud)" + val server = app.get( + href ?: return@apmap, interceptor = wpRedisInterceptor + ).document.selectFirst("div.entry-content > $selector") + ?.attr("href") ?: return@apmap - loadCustomTagExtractor( - tags, - server, - "$api/", - subtitleCallback, - callback, - getIndexQuality(entry.text()) - ) + loadCustomTagExtractor( + tags, + server, + "$api/", + subtitleCallback, + callback, + getIndexQuality(it.text()) + ) + } } suspend fun invokeHdmovies4u( @@ -1213,14 +1284,11 @@ object SoraExtractor : SoraStream() { val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode) val media = app.get("$hdmovies4uAPI/?s=${if (season == null) imdbId else title}").document.let { - val selector = - if (season == null) "a" else "a:matches((?i)$title.*Season $season)" + val selector = if (season == null) "a" else "a:matches((?i)$title.*Season $season)" it.selectFirst("div.gridxw.gridxe $selector")?.attr("href") } val selector = if (season == null) "1080p|2160p" else "(?i)Episode.*(?:1080p|2160p)" - app.get( - media ?: return - ).document.select("section h4:matches($selector)").apmap { ele -> + app.get(media ?: return).document.select("section h4:matches($selector)").apmap { ele -> val (tags, size) = ele.select("span").map { it.text() }.let { it[it.lastIndex - 1] to it.last().substringAfter("-").trim() } @@ -1239,51 +1307,6 @@ object SoraExtractor : SoraStream() { } } - suspend fun invokeGMovies( - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val fixTitle = title.createSlug() - val url = if (season == null || season == 1) { - "$gMoviesAPI/$fixTitle-$year" - } else { - "$gMoviesAPI/$fixTitle-$year-season-$season" - } - - val doc = app.get(url).document - - val iframe = (if (season == null) { - doc.select("div.is-content-justification-center div.wp-block-button").map { - it.select("a").attr("href") to it.text() - } - } else { - doc.select("div.is-content-justification-center").find { - it.previousElementSibling()?.text() - ?.contains(Regex("(?i)episode\\s?$episode")) == true - }?.select("div.wp-block-button")?.map { - it.select("a").attr("href") to it.text() - } - })?.filter { - it.second.contains(Regex("(?i)(4k|1080p)")) - } ?: return - - iframe.apmap { (iframeLink, title) -> - val size = Regex("(?i)\\s(\\S+gb|mb)").find(title)?.groupValues?.getOrNull(1) - loadCustomTagExtractor( - "[$size]", - iframeLink, - "$gMoviesAPI/", - subtitleCallback, - callback, - getIndexQuality(title) - ) - } - } - suspend fun invokeFDMovies( title: String? = null, season: Int? = null, @@ -1320,7 +1343,7 @@ object SoraExtractor : SoraStream() { } type.contains("oiya") || type.contains("rarbgx") -> { - val oiyaLink = extractOiya(fdLink ?: return@apmap null, qualities) + val oiyaLink = extractOiya(fdLink ?: return@apmap null) if (oiyaLink?.contains("gdtot") == true) { val gdBotLink = extractGdbot(oiyaLink) extractGdflix(gdBotLink ?: return@apmap null) @@ -1336,11 +1359,8 @@ object SoraExtractor : SoraStream() { callback.invoke( ExtractorLink( - "FDMovies", - "FDMovies [$size]", - videoLink ?: return@apmap null, - "", - getQualityFromName(qualities) + "FDMovies", "FDMovies [$size]", videoLink + ?: return@apmap null, "", getQualityFromName(qualities) ) ) } @@ -1355,30 +1375,20 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { + val slugTitle = title?.createSlug() val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode) - val req = app.get("$m4uhdAPI/search/${title.createSlug()}.html") + val req = app.get("$m4uhdAPI/search/$slugTitle.html", timeout = 20) val referer = getBaseUrl(req.url) - val scriptData = req.document.select("div.row div.item").map { ele -> - Triple( - ele.select("div.tiptitle p").text(), - ele.select("div.jtip-top div:last-child").text().substringBefore("–") - .filter { it.isDigit() }, - ele.selectFirst("a")?.attr("href") - ) - } - val script = if (scriptData.size == 1) { - scriptData.firstOrNull() + val media = req.document.select("div.row div.item > a").map { it.attr("href") } + val mediaUrl = if(media.size == 1) { + media.first() } else { - scriptData.find { - it.first.contains(Regex("(?i)$title \\($year\\s?\\)")) && if (season != null) it.third?.contains( - "-tvshow-" - ) == true else it.third?.contains("-movie-") == true - } + media.find { it.contains("-$slugTitle-") && it.contains("-$year-") } } - val link = fixUrl(script?.third ?: return, referer) - val request = app.get(link) + val link = fixUrl(mediaUrl ?: return, referer) + val request = app.get(link, timeout = 20) var cookies = request.cookies val headers = mapOf("Accept" to "*/*", "X-Requested-With" to "XMLHttpRequest") @@ -1388,13 +1398,13 @@ object SoraExtractor : SoraStream() { doc.select("div.le-server span").map { it.attr("data") } } else { val idepisode = - doc.selectFirst("div.detail > p:matches((?i)S$seasonSlug-E$episodeSlug) button") + doc.selectFirst("div.season > p:matches((?i)S$seasonSlug-E$episodeSlug) button") ?.attr("idepisode") ?: return val requestEmbed = app.post( "$referer/ajaxtv", data = mapOf( "idepisode" to idepisode, "_token" to "$token" - ), referer = link, headers = headers, cookies = cookies + ), referer = link, headers = headers, cookies = cookies, timeout = 20 ) cookies = requestEmbed.cookies requestEmbed.document.select("div.le-server span").map { it.attr("data") } @@ -1403,12 +1413,11 @@ object SoraExtractor : SoraStream() { m4uData.apmap { data -> val iframe = app.post( "$referer/ajax", - data = mapOf( - "m4u" to data, "_token" to "$token" - ), + data = mapOf("m4u" to data, "_token" to "$token"), referer = link, headers = headers, cookies = cookies, + timeout = 20 ).document.select("iframe").attr("src") loadExtractor(iframe, referer, subtitleCallback, callback) @@ -1436,11 +1445,8 @@ object SoraExtractor : SoraStream() { callback.invoke( ExtractorLink( - "TVMovies", - "TVMovies [${videoData?.second}]", - videoData?.first ?: return, - "", - quality ?: Qualities.Unknown.value + "TVMovies", "TVMovies [${videoData?.second}]", videoData?.first + ?: return, "", quality ?: Qualities.Unknown.value ) ) @@ -1463,9 +1469,11 @@ object SoraExtractor : SoraStream() { "en-US", "zh-CN", ) - val headers = getCrunchyrollToken() + val token = getCrunchyrollToken() + val headers = mapOf("Authorization" to "${token.tokenType} ${token.accessToken}") val seasonIdData = app.get( - "$crunchyrollAPI/content/v2/cms/series/${id ?: return}/seasons", headers = headers + "$crunchyrollAPI/content/v2/cms/series/${id ?: return}/seasons", + headers = headers ).parsedSafe()?.data?.let { s -> if (s.size == 1) { s.firstOrNull() @@ -1480,7 +1488,8 @@ object SoraExtractor : SoraStream() { } } val seasonId = seasonIdData?.versions?.filter { it.audio_locale in audioLocal } - ?.map { it.guid to it.audio_locale } ?: listOf(seasonIdData?.id to "ja-JP") + ?.map { it.guid to it.audio_locale } + ?: listOf(seasonIdData?.id to "ja-JP") seasonId.apmap { (sId, audioL) -> val streamsLink = app.get( @@ -1488,139 +1497,38 @@ object SoraExtractor : SoraStream() { headers = headers ).parsedSafe()?.data?.find { it.title.equals(epsTitle, true) || it.slug_title.equals( - epsTitle.createSlug(), true + epsTitle.createSlug(), + true ) || it.episode_number == episode - }?.streams_link - val sources = - app.get(fixUrl(streamsLink ?: return@apmap, crunchyrollAPI), headers = headers) - .parsedSafe() + }?.streams_link?.substringAfter("/videos/")?.substringBefore("/streams") ?: return@apmap + val sources = app.get( + "$crunchyrollAPI/cms/v2${token.bucket}/videos/$streamsLink/streams?Policy=${token.policy}&Signature=${token.signature}&Key-Pair-Id=${token.key_pair_id}", + headers = headers + ).parsedSafe() - listOf( - "adaptive_hls", "vo_adaptive_hls" - ).map { hls -> + listOf("adaptive_hls", "vo_adaptive_hls").map { hls -> val name = if (hls == "adaptive_hls") "Crunchyroll" else "Vrv" val audio = if (audioL == "en-US") "English Dub" else "Raw" - val source = sources?.data?.firstOrNull()?.let { + val source = sources?.streams?.let { if (hls == "adaptive_hls") it.adaptive_hls else it.vo_adaptive_hls } M3u8Helper.generateM3u8( - "$name [$audio]", - source?.get("")?.get("url") ?: return@map, - "https://static.crunchyroll.com/" + "$name [$audio]", source?.get("")?.get("url") + ?: return@map, "https://static.crunchyroll.com/" ).forEach(callback) } - sources?.meta?.subtitles?.map { sub -> + sources?.subtitles?.map { sub -> subtitleCallback.invoke( SubtitleFile( - "${fixCrunchyrollLang(sub.key) ?: sub.key} [ass]", - sub.value["url"] ?: return@map null + "${fixCrunchyrollLang(sub.key) ?: sub.key} [ass]", sub.value["url"] + ?: return@map null ) ) } - - } } - suspend fun invokeMoviezAdd( - apiUrl: String? = null, - api: String? = null, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit - ) { - invokeBloginguru(apiUrl, api, title, year, season, episode, callback) - } - - suspend fun invokeBollyMaza( - apiUrl: String? = null, - api: String? = null, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit - ) { - invokeBloginguru(apiUrl, api, title, year, season, episode, callback) - } - - private suspend fun invokeBloginguru( - apiUrl: String? = null, - api: String? = null, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit - ) { - val fixTitle = title?.createSlug()?.replace("-", " ") - val doc = app.get("$apiUrl/?s=$fixTitle").document - - val matchMedia = doc.select("article.mh-loop-item").map { - it.select("a").attr("href") to it.select("a").text() - }.find { - if (season == null) { - it.second.contains(Regex("(?i)($fixTitle)|($title)")) && it.first.contains("$year") - } else { - it.second.contains(Regex("(?i)($fixTitle)|($title)")) && it.second.contains(Regex("(?i)(Season\\s?$season)|(S0?$season)")) - } - } - - val mediaLink = - app.get(matchMedia?.first ?: return).document.selectFirst("a#jake1")?.attr("href") - val detailDoc = app.get(mediaLink ?: return).document - val media = detailDoc.selectFirst("div.entry-content pre span")?.text() - ?.split("|") - ?.map { it.trim() } - - val iframe = (if (season == null) { - media?.mapIndexed { index, name -> - detailDoc.select("div.entry-content > h2")[index].selectFirst("a") - ?.attr("href") to name - } - } else { - media?.mapIndexed { index, name -> - val linkMedia = - detailDoc.select("div.entry-content > h2")[index].selectFirst("a") - ?.attr("href") - app.get( - linkMedia ?: return@mapIndexed null - ).document.selectFirst("div.entry-content strong:matches((?i)S0?${season}E0?${episode}) a") - ?.attr("href") to name - } - })?.filter { it?.first?.startsWith("http") == true } - - iframe?.apmap { - val iframeDoc = app.get(it?.first ?: return@apmap).document - val formUrl = iframeDoc.select("form").attr("action") - val formData = - iframeDoc.select("form button").associate { v -> v.attr("name") to v.attr("value") } - - val videoUrl = app.post( - formUrl, - data = formData, - referer = it.first - ).document.selectFirst("div.d-flex.justify-content-center.flex-wrap a")?.attr("href") - val quality = - Regex("(\\d{3,4})p").find(it.second)?.groupValues?.getOrNull(1)?.toIntOrNull() - val qualityName = it.second.replace("${quality}p", "").trim() - - callback.invoke( - ExtractorLink( - "$api", - "$api $qualityName", - videoUrl ?: return@apmap, - "", - quality ?: Qualities.Unknown.value - ) - ) - } - - } - suspend fun invokeRStream( id: Int? = null, season: Int? = null, @@ -1637,11 +1545,16 @@ object SoraExtractor : SoraStream() { "$url&apikey=whXgvN4kVyoubGwqXpw26Oy3PVryl8dm", referer = "https://watcha.movie/" ).text - val link = Regex("\"file\":\"(http.*?)\"").find(res)?.groupValues?.getOrNull(1) ?: return + val link = Regex("\"file\":\"(http.*?)\"").find(res)?.groupValues?.getOrNull(1) callback.invoke( ExtractorLink( - "RStream", "RStream", link, "$rStreamAPI/", Qualities.P1080.value, INFER_TYPE + "RStream", + "RStream", + link ?: return, + "$rStreamAPI/", + Qualities.P1080.value, + INFER_TYPE ) ) } @@ -1651,9 +1564,9 @@ object SoraExtractor : SoraStream() { imdbId: String? = null, season: Int? = null, episode: Int? = null, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, + onionUrl: String = "https://onionplay.se/" ) { - val onionUrl = "https://onionplay.se/" val request = if (season == null) { val res = app.get("$flixonAPI/$imdbId", referer = onionUrl) if (res.text.contains("BEGIN PGP SIGNED MESSAGE")) app.get( @@ -1676,21 +1589,24 @@ object SoraExtractor : SoraStream() { delay(1000) val unPacker = app.get( - iframe ?: return, referer = "$flixonAPI/" + iframe + ?: return, referer = "$flixonAPI/" ).document.selectFirst("script:containsData(JuicyCodes.Run)")?.data() ?.substringAfter("JuicyCodes.Run(")?.substringBefore(");")?.split("+") ?.joinToString("") { it.replace("\"", "").trim() } ?.let { getAndUnpack(base64Decode(it)) } val link = Regex("[\"']file[\"']:[\"'](.+?)[\"'],").find( - unPacker ?: return + unPacker + ?: return )?.groupValues?.getOrNull(1) callback.invoke( ExtractorLink( "Flixon", "Flixon", - link ?: return, + link + ?: return, "https://onionplay.stream/", Qualities.P720.value, link.contains(".m3u8") @@ -1700,30 +1616,31 @@ object SoraExtractor : SoraStream() { } suspend fun invokeSmashyStream( - imdbId: String? = null, + tmdbId: Int? = null, season: Int? = null, episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { val url = if (season == null) { - "$smashyStreamAPI/playere.php?imdb=$imdbId" + "$smashyStreamAPI/playere.php?tmdb=$tmdbId" } else { - "$smashyStreamAPI/playere.php?imdb=$imdbId&season=$season&episode=$episode" + "$smashyStreamAPI/playere.php?tmdb=$tmdbId&season=$season&episode=$episode" } app.get( - url, referer = "https://smashystream.com/" + url, + referer = "https://smashystream.xyz/" ).document.select("div#_default-servers a.server").map { it.attr("data-url") to it.text() }.apmap { when (it.second) { "Player F" -> { - invokeSmashyFfix(it.second, it.first, url, callback) + invokeSmashyFfix(it.second, it.first, url, subtitleCallback, callback) } - "Player D (Hindi)" -> { - invokeSmashyD(it.first, url, callback) + "Player SU" -> { + invokeSmashySu(it.second, it.first, url, callback) } else -> return@apmap @@ -1732,6 +1649,108 @@ object SoraExtractor : SoraStream() { } + suspend fun invokeNepu( + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit + ) { + val slug = title?.createSlug() + val headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + val data = app.get("$nepuAPI/ajax/posts?q=$title", headers = headers, referer = "$nepuAPI/") + .parsedSafe()?.data + + val media = + data?.find { it.url?.startsWith(if (season == null) "/movie/$slug-$year-" else "/serie/$slug-$year-") == true } + ?: data?.find { + (it.name.equals( + title, + true + ) && it.type == if (season == null) "Movie" else "Serie") + } + + if (media?.url == null) return + val mediaUrl = if (season == null) { + media.url + } else { + "${media.url}/season/$season/episode/$episode" + } + + val dataId = app.get(fixUrl(mediaUrl, nepuAPI)).document.selectFirst("a[data-embed]")?.attr("data-embed") ?: return + val res = app.post( + "$nepuAPI/ajax/embed", data = mapOf( + "id" to dataId + ), referer = mediaUrl, headers = headers + ).text + + val m3u8 = "(http[^\"]+)".toRegex().find(res)?.groupValues?.get(1) + + callback.invoke( + ExtractorLink( + "Nepu", + "Nepu", + m3u8 ?: return, + "$nepuAPI/", + Qualities.P1080.value, + INFER_TYPE + ) + ) + + } + + suspend fun invokeMoflix( + tmdbId: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit + ) { + val id = (if (season == null) { + "tmdb|movie|$tmdbId" + } else { + "tmdb|series|$tmdbId" + }).let { base64Encode(it.toByteArray()) } + + val loaderUrl = "$moflixAPI/api/v1/titles/$id?loader=titlePage" + val url = if (season == null) { + loaderUrl + } else { + val mediaId = app.get(loaderUrl, referer = "$moflixAPI/").parsedSafe()?.title?.id + "$moflixAPI/api/v1/titles/$mediaId/seasons/$season/episodes/$episode?loader=episodePage" + } + + val res = app.get(url, referer = "$moflixAPI/").parsedSafe() + (res?.episode ?: res?.title)?.videos?.filter { it.category.equals("full", true) } + ?.apmap { iframe -> + val response = app.get(iframe.src ?: return@apmap, referer = "$moflixAPI/") + val host = getBaseUrl(iframe.src) + val doc = response.document.selectFirst("script:containsData(sources:)")?.data() + val script = if (doc.isNullOrEmpty()) { + getAndUnpack(response.text) + } else { + doc + } + val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find( + script ?: return@apmap + )?.groupValues?.getOrNull(1) + if (m3u8?.haveDub("$host/") == false) return@apmap + callback.invoke( + ExtractorLink( + "Moflix", + "Moflix [${iframe.name}]", + m3u8 ?: return@apmap, + "$host/", + iframe.quality?.filter { it.isDigit() }?.toIntOrNull() + ?: Qualities.Unknown.value, + INFER_TYPE + ) + ) + } + + } + //TODO only subs suspend fun invokeWatchsomuch( imdbId: String? = null, @@ -1741,13 +1760,15 @@ object SoraExtractor : SoraStream() { ) { val id = imdbId?.removePrefix("tt") val epsId = app.post( - "$watchSomuchAPI/Watch/ajMovieTorrents.aspx", data = mapOf( + "$watchSomuchAPI/Watch/ajMovieTorrents.aspx", + data = mapOf( "index" to "0", "mid" to "$id", - "wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb", + "wsk" to "30fb68aa-1c71-4b8c-b5d4-4ca9222cfb45", "lid" to "", "liu" to "" - ), headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ), + headers = mapOf("X-Requested-With" to "XMLHttpRequest") ).parsedSafe()?.movie?.torrents?.let { eps -> if (season == null) { eps.firstOrNull()?.id @@ -1767,7 +1788,10 @@ object SoraExtractor : SoraStream() { app.get(subUrl).parsedSafe()?.subtitles?.map { sub -> subtitleCallback.invoke( SubtitleFile( - sub.label ?: "", fixUrl(sub.url ?: return@map null, watchSomuchAPI) + sub.label ?: "", fixUrl( + sub.url + ?: return@map null, watchSomuchAPI + ) ) ) } @@ -1805,9 +1829,7 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, password: String = "", ) { - val passHeaders = mapOf( - "Authorization" to password - ) + val passHeaders = mapOf("Authorization" to password) val query = getIndexQuery(title, year, season, episode).let { if (api in mkvIndex) "$it mkv" else it } @@ -1815,9 +1837,7 @@ object SoraExtractor : SoraStream() { """{"q":"$query","password":null,"page_token":null,"page_index":0}""".toRequestBody( RequestBodyTypes.JSON.toMediaTypeOrNull() ) - val data = mapOf( - "q" to query, "page_token" to "", "page_index" to "0" - ) + val data = mapOf("q" to query, "page_token" to "", "page_index" to "0") val search = if (api in encodedIndex) { decodeIndexJson( if (api in lockedIndex) app.post( @@ -1826,20 +1846,22 @@ object SoraExtractor : SoraStream() { headers = passHeaders, referer = apiUrl, timeout = 120L - ).text else app.post( - "${apiUrl}search", data = data, referer = apiUrl - ).text + ).text else app.post("${apiUrl}search", data = data, referer = apiUrl).text ) } else { app.post("${apiUrl}search", requestBody = body, referer = apiUrl, timeout = 120L).text } val media = if (api in untrimmedIndex) searchIndex( - title, season, episode, year, search, false + title, + season, + episode, + year, + search, + false ) else searchIndex(title, season, episode, year, search) media?.apmap { file -> - val pathBody = """{"id":"${file.id ?: return@apmap null}"}""".toRequestBody( - RequestBodyTypes.JSON.toMediaTypeOrNull() - ) + val pathBody = + """{"id":"${file.id ?: return@apmap null}"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) val pathData = mapOf( "id" to file.id, ) @@ -1853,13 +1875,14 @@ object SoraExtractor : SoraStream() { timeout = 120L ) } else { - app.post( - "${apiUrl}id2path", data = pathData, referer = apiUrl, timeout = 120L - ) + app.post("${apiUrl}id2path", data = pathData, referer = apiUrl, timeout = 120L) } } else { app.post( - "${apiUrl}id2path", requestBody = pathBody, referer = apiUrl, timeout = 120L + "${apiUrl}id2path", + requestBody = pathBody, + referer = apiUrl, + timeout = 120L ) }).text.let { path -> if (api in ddomainIndex) { @@ -1877,8 +1900,12 @@ object SoraExtractor : SoraStream() { } }.encodeUrl() - val size = - "%.2f GB".format(bytesToGigaBytes(file.size?.toDouble() ?: return@apmap null)) + val size = "%.2f GB".format( + bytesToGigaBytes( + file.size?.toDouble() + ?: return@apmap null + ) + ) val quality = getIndexQuality(file.name) val tags = getIndexQualityTags(file.name) @@ -1905,9 +1932,7 @@ object SoraExtractor : SoraStream() { ) { val query = getIndexQuery(title, null, season, episode) val files = app.get("$gdbot/search?q=$query").document.select("ul.divide-y li").map { - Triple( - it.select("a").attr("href"), it.select("a").text(), it.select("span").text() - ) + Triple(it.select("a").attr("href"), it.select("a").text(), it.select("span").text()) }.filter { matchingIndex( it.second, @@ -1922,7 +1947,8 @@ object SoraExtractor : SoraStream() { } files.let { file -> - listOfNotNull(file.find { it.second.contains("2160p", true) }, + listOfNotNull( + file.find { it.second.contains("2160p", true) }, file.find { it.second.contains("1080p", true) }) }.apmap { file -> val videoUrl = extractGdflix(file.first) @@ -1957,7 +1983,7 @@ object SoraExtractor : SoraStream() { "$dahmerMoviesAPI/tvs/${title?.replace(":", " -")}/Season $season/" } - val request = app.get(url, timeout = 120L) + val request = app.get(url, timeout = 60L) if (!request.isSuccessful) return val paths = request.document.select("a").map { it.text() to it.attr("href") @@ -2000,15 +2026,15 @@ object SoraExtractor : SoraStream() { "$twoEmbedAPI/embedtv/$imdbId&s=$season&e=$episode" } - val framesrc = - app.get(url).document.selectFirst("iframe#iframesrc")?.attr("data-src") ?: return + val framesrc = app.get(url).document.selectFirst("iframe#iframesrc")?.attr("data-src") + ?: return val ref = getBaseUrl(framesrc) val id = framesrc.substringAfter("id=").substringBefore("&") - loadExtractor("https://wishfast.top/e/$id", "$ref/", subtitleCallback, callback) + loadExtractor("https://uqloads.xyz/e/$id", "$ref/", subtitleCallback, callback) } - suspend fun invokeOmovies( + suspend fun invokeGhostx( title: String? = null, year: Int? = null, season: Int? = null, @@ -2021,8 +2047,8 @@ object SoraExtractor : SoraStream() { season, episode, callback, - BuildConfig.OMOVIES_API, - "Omovies", + BuildConfig.GHOSTX_API, + "Ghostx", base64Decode("X3NtUWFtQlFzRVRi"), base64Decode("X3NCV2NxYlRCTWFU") ) @@ -2040,7 +2066,7 @@ object SoraExtractor : SoraStream() { episodeSelector: String, ) { fun String.decrypt(key: String): List? { - return tryParseJson>(base64Decode(this).decodePrimewireXor(key)) + return tryParseJson>(base64Decode(this).xorDecrypt(key)) } val slug = getEpisodeSlug(season, episode) @@ -2050,14 +2076,14 @@ object SoraExtractor : SoraStream() { "$title Season $season" } val savedCookies = mapOf( - "_identitygomovies7" to "52fdc70b008c0b1d881dac0f01cca819edd512de01cc8bbc1224ed4aafb78b52a%3A2%3A%7Bi%3A0%3Bs%3A18%3A%22_identitygomovies7%22%3Bi%3A1%3Bs%3A52%3A%22%5B2050366%2C%22HnVRRAObTASOJEr45YyCM8wiHol0V1ko%22%2C2592000%5D%22%3B%7D", + base64Decode("X2lkZW50aXR5Z29tb3ZpZXM3") to base64Decode("NTJmZGM3MGIwMDhjMGIxZDg4MWRhYzBmMDFjY2E4MTllZGQ1MTJkZTAxY2M4YmJjMTIyNGVkNGFhZmI3OGI1MmElM0EyJTNBJTdCaSUzQTAlM0JzJTNBMTglM0ElMjJfaWRlbnRpdHlnb21vdmllczclMjIlM0JpJTNBMSUzQnMlM0E1MiUzQSUyMiU1QjIwNTAzNjYlMkMlMjJIblZSUkFPYlRBU09KRXI0NVl5Q004d2lIb2wwVjFrbyUyMiUyQzI1OTIwMDAlNUQlMjIlM0IlN0Q="), ) - val req = app.get("$api/search/$query") - val doc = req.document + + var res = app.get("$api/search/$query", timeout = 20) + val cookies = savedCookies + res.cookies + val doc = res.document val media = doc.select("div.$mediaSelector").map { - Triple( - it.attr("data-filmName"), it.attr("data-year"), it.select("a").attr("href") - ) + Triple(it.attr("data-filmName"), it.attr("data-year"), it.select("a").attr("href")) }.let { el -> if (el.size == 1) { el.firstOrNull() @@ -2065,7 +2091,8 @@ object SoraExtractor : SoraStream() { el.find { if (season == null) { (it.first.equals(title, true) || it.first.equals( - "$title ($year)", true + "$title ($year)", + true )) && it.second.equals("$year") } else { it.first.equals("$title - Season $season", true) @@ -2077,48 +2104,42 @@ object SoraExtractor : SoraStream() { val iframe = if (season == null) { media.third } else { - val res = app.get( - fixUrl( - media.third, - api - ) - ) - res.document.selectFirst("div#$episodeSelector a:contains(Episode ${slug.second})") + app.get(fixUrl(media.third, api), cookies = cookies, timeout = 20) + .document.selectFirst("div#$episodeSelector a:contains(Episode ${slug.second})") ?.attr("href") - } ?: return - - val users = if (season == null) { - media.third.substringAfterLast("/") to "0" - } else { - media.third.substringAfterLast("/") to iframe.substringAfterLast("/") - .substringBefore("-") } - val res = app.get(fixUrl(iframe, api), verify = false) - delay(1000) - val serverUrl = res.document.selectFirst("script:containsData(pushState)")?.data()?.let { - """,\s*'([^']+)""".toRegex().find(it)?.groupValues?.get(1) - } ?: return - val cookies = savedCookies + res.cookies + + res = app.get(fixUrl(iframe ?: return, api), cookies = cookies, timeout = 20) val url = res.document.select("meta[property=og:url]").attr("content") val headers = mapOf("X-Requested-With" to "XMLHttpRequest") val qualities = intArrayOf(2160, 1440, 1080, 720, 480, 360) + + val (serverId, episodeId) = if (season == null) { + url.substringAfterLast("/") to "0" + } else { + url.substringBeforeLast("/").substringAfterLast("/") to url.substringAfterLast("/") + .substringBefore("-") + } val serverRes = app.get( - "$api/user/servers/${users.first}?ep=${users.second}", - cookies = cookies, referer = url, headers = headers + "$api/user/servers/$serverId?ep=$episodeId", + cookies = cookies, + referer = url, + headers = headers, + timeout = 20 ) - val unpack = getAndUnpack(serverRes.text) - val key = unpack.substringAfter("(key=").substringBefore(")") - val key2 = unpack.substringAfter("<\"").substringBefore("\".") - serverRes.document.select("ul li").amap { el -> + val script = getAndUnpack(serverRes.text) + val key = """key\s*=\s*(\d+)""".toRegex().find(script)?.groupValues?.get(1) ?: return + serverRes.document.select("ul li").apmap { el -> val server = el.attr("data-value") val encryptedData = app.get( - "${fixUrl(serverUrl, api)}?server=$server&_=$unixTimeMS", + "$url?server=$server&_=$unixTimeMS", cookies = cookies, referer = url, - headers = headers + headers = headers, + timeout = 20 ).text - val links = encryptedData.decrypt(key) ?: encryptedData.decrypt(key2) ?: return@amap - links.forEach { video -> + val links = encryptedData.decrypt(key) + links?.forEach { video -> qualities.filter { it <= video.max.toInt() }.forEach { callback( ExtractorLink( @@ -2135,52 +2156,6 @@ object SoraExtractor : SoraStream() { } - suspend fun invokeBlackvid( - tmdbId: Int? = null, - season: Int? = null, - episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit, - ) { - val key = "b6055c533c19131a638c3d2299d525d5ec08a814" - val url = if (season == null) { - "$blackvidAPI/v3/movie/sources/$tmdbId?key=$key" - } else { - "$blackvidAPI/v3/tv/sources/$tmdbId/$season/$episode?key=$key" - } - - val res = request(url).body - val bytes = res.bytes().also { res.closeQuietly() } - val data = bytes.decrypt("2378f8e4e844f2dc839ab48f66e00acc2305a401") - val json = tryParseJson(data) - - json?.sources?.map { source -> - source.sources.map s@{ s -> - callback.invoke( - ExtractorLink( - "Blackvid", - "Blackvid${source.label}", - s.url ?: return@s, - "https://blackvid.space/", - if (s.quality.equals("4k")) Qualities.P2160.value else s.quality?.toIntOrNull() - ?: Qualities.P1080.value, - INFER_TYPE - ) - ) - } - } - - json?.subtitles?.map { sub -> - subtitleCallback.invoke( - SubtitleFile( - sub.language.takeIf { it?.isNotEmpty() == true } ?: return@map, - sub.url ?: return@map, - ) - ) - } - - } - suspend fun invokeShowflix( title: String? = null, year: Int? = null, @@ -2188,6 +2163,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, + api: String = "https://parse.showflix.online" ) { val where = if (season == null) "movieName" else "seriesName" val classes = if (season == null) "movies" else "series" @@ -2204,18 +2180,15 @@ object SoraExtractor : SoraStream() { "_ApplicationId": "SHOWFLIXAPPID", "_JavaScriptKey": "SHOWFLIXMASTERKEY", "_ClientVersion": "js3.4.1", - "_InstallationId": "6d19fd87-a0e8-47b2-a3c0-48c16add928b" + "_InstallationId": "58f0e9ca-f164-42e0-a683-a1450ccf0221" } """.trimIndent().toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull()) val data = - app.post("https://parse.showflix.tk/parse/classes/$classes", requestBody = body).text + app.post("$api/parse/classes/$classes", requestBody = body).text val iframes = if (season == null) { val result = tryParseJson(data)?.resultsMovies?.find { - it.movieName.equals( - "$title ($year)", - true - ) + it.movieName.equals("$title ($year)", true) } listOf( "https://streamwish.to/e/${result?.streamwish}", @@ -2224,10 +2197,7 @@ object SoraExtractor : SoraStream() { ) } else { val result = tryParseJson(data)?.resultsSeries?.find { - it.seriesName.equals( - title, - true - ) + it.seriesName.equals(title, true) } listOf( result?.streamwish?.get("Season $season")?.get(episode!!), @@ -2242,6 +2212,46 @@ object SoraExtractor : SoraStream() { } + suspend fun invokeZoechip( + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit, + ) { + val slug = title?.createSlug() + val url = if (season == null) { + "$zoechipAPI/film/${title?.createSlug()}-$year" + } else { + "$zoechipAPI/episode/$slug-season-$season-episode-$episode" + } + + val id = app.get(url).document.selectFirst("div#show_player_ajax")?.attr("movie-id") ?: return + + val server = app.post( + "$zoechipAPI/wp-admin/admin-ajax.php", data = mapOf( + "action" to "lazy_player", + "movieID" to id, + ), referer = url, headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + ).document.selectFirst("ul.nav a:contains(Filemoon)")?.attr("data-server") + + val res = app.get(server ?: return, referer = "$zoechipAPI/") + val host = getBaseUrl(res.url) + val script = res.document.select("script:containsData(function(p,a,c,k,e,d))").last()?.data() + val unpacked = getAndUnpack(script ?: return) + + val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(unpacked)?.groupValues?.getOrNull(1) + + M3u8Helper.generateM3u8( + "Zoechip", + m3u8 ?: return, + "$host/", + ).forEach(callback) + + } + suspend fun invokeCinemaTv( imdbId: String? = null, title: String? = null, @@ -2252,20 +2262,16 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, ) { val id = imdbId?.removePrefix("tt") - val slug = title.createSlug() + val slug = title?.createSlug() val url = if (season == null) { "$cinemaTvAPI/movies/play/$id-$slug-$year" } else { "$cinemaTvAPI/shows/play/$id-$slug-$year" } - val session = "PHPSESSID=ngr4cudjrimdnhkth30ssohs0n; _csrf=a6ffd7bb7654083fce6df528225a238d0e85aa1fb885dc7638c1259ec1ba0d5ca%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22_csrf%22%3Bi%3A1%3Bs%3A32%3A%22mTTLiDLjxohs-CpKk0bjRH3HdYMB9uBV%22%3B%7D; _ga=GA1.1.1195498587.1701871187; _ga_VZD7HJ3WK6=GS1.1.$unixTime.4.0.1.$unixTime.0.0.0" val headers = mapOf( - "Cookie" to session, - "Connection" to "keep-alive", "x-requested-with" to "XMLHttpRequest", ) - val doc = app.get(url, headers = headers).document val script = doc.selectFirst("script:containsData(hash:)")?.data() val hash = Regex("hash:\\s*['\"](\\S+)['\"]").find(script ?: return)?.groupValues?.get(1) @@ -2285,7 +2291,7 @@ object SoraExtractor : SoraStream() { val sources = app.get( videoUrl, referer = url, - headers = mapOf("X-Requested-With" to "XMLHttpRequest") + headers = headers ).parsedSafe() sources?.streams?.mapKeys { source -> @@ -2338,15 +2344,23 @@ object SoraExtractor : SoraStream() { imdbId: String? = null, season: Int? = null, episode: Int? = null, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, + referer: String = "https://bflix.gs/" ) { - val referer = "https://bflix.gs/" + suspend fun String.isSuccess(): Boolean { + return app.get(this, referer = referer).isSuccessful + } + val slug = getEpisodeSlug(season, episode) var url = if (season == null) "$nowTvAPI/$tmdbId.mp4" else "$nowTvAPI/tv/$tmdbId/s${season}e${slug.second}.mp4" - if (!app.get(url, referer = referer).isSuccessful) { - url = - if (season == null) "$nowTvAPI/$imdbId.mp4" else "$nowTvAPI/tv/$imdbId/s${season}e${slug.second}.mp4" + if (!url.isSuccess()) { + url = if (season == null) { + val temp = "$nowTvAPI/$imdbId.mp4" + if (temp.isSuccess()) temp else "$nowTvAPI/$tmdbId-1.mp4" + } else { + "$nowTvAPI/tv/$imdbId/s${season}e${slug.second}.mp4" + } if (!app.get(url, referer = referer).isSuccessful) return } callback.invoke( @@ -2363,22 +2377,27 @@ object SoraExtractor : SoraStream() { suspend fun invokeRidomovies( tmdbId: Int? = null, imdbId: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { - val slug = app.get("$ridomoviesAPI/core/api/search?q=$imdbId") + val mediaSlug = app.get("$ridomoviesAPI/core/api/search?q=$imdbId") .parsedSafe()?.data?.items?.find { - it.contentable?.tmdbId == tmdbId || it.contentable?.imdbId == imdbId - }?.slug ?: return - app.get("$ridomoviesAPI/core/api/movies/$slug/videos") - .parsedSafe()?.data?.apmap { link -> - val iframe = Jsoup.parse(link.url ?: return@apmap).select("iframe").attr("data-src") - val unpacked = - getAndUnpack( - app.get( - iframe, - referer = "$ridomoviesAPI/" - ).text - ) + it.contentable?.tmdbId == tmdbId || it.contentable?.imdbId == imdbId + }?.slug ?: return + + val id = season?.let { + val episodeUrl = "$ridomoviesAPI/tv/$mediaSlug/season-$it/episode-$episode" + app.get(episodeUrl).text.substringAfterLast("""postid\":\"""").substringBefore("""\""") + } ?: mediaSlug + + val url = + "$ridomoviesAPI/core/api/${if (season == null) "movies" else "episodes"}/$id/videos" + app.get(url).parsedSafe()?.data?.apmap { link -> + val iframe = Jsoup.parse(link.url ?: return@apmap).select("iframe").attr("data-src") + if (iframe.startsWith("https://closeload.top")) { + val unpacked = getAndUnpack(app.get(iframe, referer = "$ridomoviesAPI/").text) val video = Regex("=\"(aHR.*?)\";").find(unpacked)?.groupValues?.get(1) callback.invoke( ExtractorLink( @@ -2390,66 +2409,52 @@ object SoraExtractor : SoraStream() { isM3u8 = true ) ) + } else { + loadExtractor(iframe, "$ridomoviesAPI/", subtitleCallback, callback) } + } } - suspend fun invokeNavy( - imdbId: String? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - invokeHindi(navyAPI, navyAPI, imdbId, season, episode, callback) - } - - suspend fun invokeMoment( - imdbId: String? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - invokeHindi(momentAPI, "https://hdmovies4u.band", imdbId, season, episode, callback) - } - - private suspend fun invokeHindi( - host: String? = null, - referer: String? = null, + suspend fun invokeAllMovieland( imdbId: String? = null, season: Int? = null, episode: Int? = null, callback: (ExtractorLink) -> Unit, + host: String = "https://dozzlegram-duj-i-280.site", ) { val res = app.get( - "$host/play/$imdbId", referer = "$referer/" + "$host/play/$imdbId", + referer = "$allmovielandAPI/" ).document.selectFirst("script:containsData(player =)")?.data()?.substringAfter("{") ?.substringBefore(";")?.substringBefore(")") - val json = tryParseJson("{${res ?: return}") - val headers = mapOf( - "X-CSRF-TOKEN" to "${json?.key}" - ) + val json = tryParseJson("{${res ?: return}") + val headers = mapOf("X-CSRF-TOKEN" to "${json?.key}") val serverRes = app.get( - fixUrl(json?.file ?: return, navyAPI), headers = headers, referer = "$referer/" + fixUrl( + json?.file + ?: return, host + ), headers = headers, referer = "$allmovielandAPI/" ).text.replace(Regex(""",\s*\[]"""), "") - val server = tryParseJson>(serverRes).let { server -> + val servers = tryParseJson>(serverRes).let { server -> if (season == null) { - server?.find { it.title == "English" }?.file + server?.map { it.file to it.title } } else { - server?.find { it.id.equals("$season") }?.folder?.find { it.episode.equals("$episode") }?.folder?.find { - it.title.equals( - "English" - ) - }?.file + server?.find { it.id.equals("$season") }?.folder?.find { it.episode.equals("$episode") }?.folder?.map { + it.file to it.title + } } } - val path = app.post( - "${host}/playlist/${server ?: return}.txt", headers = headers, referer = "$referer/" - ).text - - M3u8Helper.generateM3u8( - if (host == navyAPI) "Navy" else "Moment", path, "${referer}/" - ).forEach(callback) + servers?.apmap { (server, lang) -> + val path = app.post( + "${host}/playlist/${server ?: return@apmap}.txt", + headers = headers, + referer = "$allmovielandAPI/" + ).text + M3u8Helper.generateM3u8("Allmovieland [$lang]", path, "$allmovielandAPI/") + .forEach(callback) + } } @@ -2483,14 +2488,14 @@ object SoraExtractor : SoraStream() { val server = app.get( "$emoviesAPI/ajax/v4_get_sources?s=oserver&id=${id ?: return}&_=${unixTimeMS}", - headers = mapOf( - "X-Requested-With" to "XMLHttpRequest" - ) + headers = mapOf("X-Requested-With" to "XMLHttpRequest") ).parsedSafe()?.value val script = app.get( - server ?: return, referer = "$emoviesAPI/" - ).document.selectFirst("script:containsData(sources:)")?.data() ?: return + server + ?: return, referer = "$emoviesAPI/" + ).document.selectFirst("script:containsData(sources:)")?.data() + ?: return val sources = Regex("sources:\\s*\\[(.*)],").find(script)?.groupValues?.get(1)?.let { tryParseJson>("[$it]") } @@ -2500,7 +2505,8 @@ object SoraExtractor : SoraStream() { sources?.map { source -> M3u8Helper.generateM3u8( - "Emovies", source.file ?: return@map, "https://embed.vodstream.xyz/" + "Emovies", source.file + ?: return@map, "https://embed.vodstream.xyz/" ).forEach(callback) } @@ -2529,8 +2535,7 @@ object SoraExtractor : SoraStream() { val data = app.get( "${BuildConfig.SFMOVIES_API}/api/mains?filters[title][\$contains]=$title", headers = headers - ) - .parsedSafe()?.data + ).parsedSafe()?.data val media = data?.find { it.attributes?.contentId.equals("$tmdbId") || (it.attributes?.title.equals( title, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt index fb2b3ef5..0fdb7778 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraParser.kt @@ -1,6 +1,17 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty +import org.jsoup.Jsoup +import org.jsoup.nodes.Document + +data class CrunchyrollAccessToken( + val accessToken: String? = null, + val tokenType: String? = null, + val bucket: String? = null, + val policy: String? = null, + val signature: String? = null, + val key_pair_id: String? = null, +) data class FDMovieIFrame( val link: String, @@ -9,32 +20,60 @@ data class FDMovieIFrame( val type: String, ) -data class AniIds( - var id: Int? = null, - var idMal: Int? = null -) +data class AniIds(var id: Int? = null, var idMal: Int? = null) data class TmdbDate( val today: String, val nextWeek: String, ) +data class AniwaveResponse( + val result: String +) { + fun asJsoup(): Document { + return Jsoup.parse(result) + } +} + +data class AniwaveServer( + val result: Result +) { + data class Result( + val url: String + ) { + fun decrypt(): String { + return AniwaveUtils.decodeVrf(url) + } + } +} + +data class MoflixResponse( + @JsonProperty("title") val title: Episode? = null, + @JsonProperty("episode") val episode: Episode? = null, +) { + data class Episode( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("videos") val videos: ArrayList? = arrayListOf(), + ) { + data class Videos( + @JsonProperty("name") val name: String? = null, + @JsonProperty("category") val category: String? = null, + @JsonProperty("src") val src: String? = null, + @JsonProperty("quality") val quality: String? = null, + ) + } +} + data class AniMedia( @JsonProperty("id") var id: Int? = null, @JsonProperty("idMal") var idMal: Int? = null ) -data class AniPage( - @JsonProperty("media") var media: java.util.ArrayList = arrayListOf() -) +data class AniPage(@JsonProperty("media") var media: java.util.ArrayList = arrayListOf()) -data class AniData( - @JsonProperty("Page") var Page: AniPage? = AniPage() -) +data class AniData(@JsonProperty("Page") var Page: AniPage? = AniPage()) -data class AniSearch( - @JsonProperty("data") var data: AniData? = AniData() -) +data class AniSearch(@JsonProperty("data") var data: AniData? = AniData()) data class GpressSources( @JsonProperty("src") val src: String, @@ -152,16 +191,6 @@ data class JikanResponse( @JsonProperty("data") val data: JikanData? = null, ) -data class CinemaTvSubtitles( - @JsonProperty("language") val language: String? = null, - @JsonProperty("file") val file: Any? = null, -) - -data class CinemaTvResponse( - @JsonProperty("streams") val streams: HashMap? = null, - @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), -) - data class VidsrctoResult( @JsonProperty("id") val id: String? = null, @JsonProperty("title") val title: String? = null, @@ -188,25 +217,24 @@ data class AnilistExternalLinks( @JsonProperty("type") var type: String? = null, ) -data class AnilistMedia( - @JsonProperty("externalLinks") var externalLinks: ArrayList = arrayListOf() -) +data class AnilistMedia(@JsonProperty("externalLinks") var externalLinks: ArrayList = arrayListOf()) -data class AnilistData( - @JsonProperty("Media") var Media: AnilistMedia? = AnilistMedia() -) +data class AnilistData(@JsonProperty("Media") var Media: AnilistMedia? = AnilistMedia()) -data class AnilistResponses( - @JsonProperty("data") var data: AnilistData? = AnilistData() -) +data class AnilistResponses(@JsonProperty("data") var data: AnilistData? = AnilistData()) data class CrunchyrollToken( @JsonProperty("access_token") val accessToken: String? = null, - @JsonProperty("expires_in") val expiresIn: Int? = null, @JsonProperty("token_type") val tokenType: String? = null, - @JsonProperty("scope") val scope: String? = null, - @JsonProperty("country") val country: String? = null -) + @JsonProperty("cms") val cms: Cms? = null, +) { + data class Cms( + @JsonProperty("bucket") var bucket: String? = null, + @JsonProperty("policy") var policy: String? = null, + @JsonProperty("signature") var signature: String? = null, + @JsonProperty("key_pair_id") var key_pair_id: String? = null, + ) +} data class CrunchyrollVersions( @JsonProperty("audio_locale") val audio_locale: String? = null, @@ -221,25 +249,25 @@ data class CrunchyrollData( @JsonProperty("episode_number") val episode_number: Int? = null, @JsonProperty("versions") val versions: ArrayList? = null, @JsonProperty("streams_link") val streams_link: String? = null, - @JsonProperty("adaptive_hls") val adaptive_hls: HashMap>? = hashMapOf(), - @JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap>? = hashMapOf(), ) data class CrunchyrollResponses( @JsonProperty("data") val data: ArrayList? = arrayListOf(), ) -data class CrunchyrollMeta( - @JsonProperty("subtitles") val subtitles: HashMap>? = hashMapOf(), -) - data class CrunchyrollSourcesResponses( - @JsonProperty("data") val data: ArrayList? = arrayListOf(), - @JsonProperty("meta") val meta: CrunchyrollMeta? = null, -) + @JsonProperty("streams") val streams: Streams? = Streams(), + @JsonProperty("subtitles") val subtitles: HashMap>? = hashMapOf(), +) { + data class Streams( + @JsonProperty("adaptive_hls") val adaptive_hls: HashMap>? = hashMapOf(), + @JsonProperty("vo_adaptive_hls") val vo_adaptive_hls: HashMap>? = hashMapOf(), + ) +} data class MALSyncSites( @JsonProperty("Zoro") val zoro: HashMap>? = hashMapOf(), + @JsonProperty("9anime") val nineAnime: HashMap>? = hashMapOf(), ) data class MALSyncResponses( @@ -263,26 +291,26 @@ data class GokuServer( @JsonProperty("data") val data: GokuData? = GokuData(), ) -data class NavyEpisodeFolder( +data class AllMovielandEpisodeFolder( @JsonProperty("title") val title: String? = null, @JsonProperty("id") val id: String? = null, @JsonProperty("file") val file: String? = null, ) -data class NavySeasonFolder( +data class AllMovielandSeasonFolder( @JsonProperty("episode") val episode: String? = null, @JsonProperty("id") val id: String? = null, - @JsonProperty("folder") val folder: ArrayList? = arrayListOf(), + @JsonProperty("folder") val folder: ArrayList? = arrayListOf(), ) -data class NavyServer( +data class AllMovielandServer( @JsonProperty("title") val title: String? = null, @JsonProperty("id") val id: String? = null, @JsonProperty("file") val file: String? = null, - @JsonProperty("folder") val folder: ArrayList? = arrayListOf(), + @JsonProperty("folder") val folder: ArrayList? = arrayListOf(), ) -data class NavyPlaylist( +data class AllMovielandPlaylist( @JsonProperty("file") val file: String? = null, @JsonProperty("key") val key: String? = null, @JsonProperty("href") val href: String? = null, @@ -334,26 +362,6 @@ data class EMovieTraks( @JsonProperty("label") val label: String? = null, ) -data class BlackvidSubtitles( - @JsonProperty("language") val language: String? = null, - @JsonProperty("url") val url: String? = null, -) - -data class BlackvidSource( - @JsonProperty("quality") var quality: String? = null, - @JsonProperty("url") var url: String? = null, -) - -data class BlackvidSources( - @JsonProperty("label") var label: String? = null, - @JsonProperty("sources") var sources: ArrayList = arrayListOf() -) - -data class BlackvidResponses( - @JsonProperty("sources") var sources: ArrayList = arrayListOf(), - @JsonProperty("subtitles") var subtitles: ArrayList = arrayListOf() -) - data class ShowflixResultsMovies( @JsonProperty("movieName") val movieName: String? = null, @JsonProperty("streamwish") val streamwish: String? = null, @@ -426,15 +434,6 @@ data class SmashySources( @JsonProperty("subtitleUrls") var subtitleUrls: String? = null, ) -data class SmashyDSources( - @JsonProperty("sourceUrls") var sourceUrls: ArrayList? = arrayListOf(), -) - -data class SmashyDSourcesUrls( - @JsonProperty("file") var file: String? = null, - @JsonProperty("title") var title: String? = null, -) - data class AoneroomResponse( @JsonProperty("data") val data: Data? = null, ) { @@ -461,4 +460,24 @@ data class AoneroomResponse( ) } } +} + +data class CinemaTvResponse( + @JsonProperty("streams") val streams: HashMap? = null, + @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), +) { + data class Subtitles( + @JsonProperty("language") val language: String? = null, + @JsonProperty("file") val file: Any? = null, + ) +} + +data class NepuSearch( + @JsonProperty("data") val data: ArrayList? = arrayListOf(), +) { + data class Data( + @JsonProperty("url") val url: String? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("type") val type: String? = null, + ) } \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 859c9037..8df050d4 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -2,11 +2,9 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.hexated.SoraExtractor.invoke2embed +import com.hexated.SoraExtractor.invokeAllMovieland import com.hexated.SoraExtractor.invokeAnimes import com.hexated.SoraExtractor.invokeAoneroom -import com.hexated.SoraExtractor.invokeBlackvid -import com.hexated.SoraExtractor.invokeBollyMaza -import com.hexated.SoraExtractor.invokeDbgo import com.hexated.SoraExtractor.invokeFilmxy import com.hexated.SoraExtractor.invokeKimcartoon import com.hexated.SoraExtractor.invokeVidSrc @@ -20,13 +18,10 @@ import com.hexated.SoraExtractor.invokeDramaday import com.hexated.SoraExtractor.invokeDreamfilm import com.hexated.SoraExtractor.invokeFDMovies import com.hexated.SoraExtractor.invokeFlixon -import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeGoku import com.hexated.SoraExtractor.invokeKisskh import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeM4uhd -import com.hexated.SoraExtractor.invokeMoviezAdd -import com.hexated.SoraExtractor.invokeNavy import com.hexated.SoraExtractor.invokeNinetv import com.hexated.SoraExtractor.invokeNowTv import com.hexated.SoraExtractor.invokeRStream @@ -35,21 +30,24 @@ import com.hexated.SoraExtractor.invokeSmashyStream import com.hexated.SoraExtractor.invokeDumpStream import com.hexated.SoraExtractor.invokeEmovies import com.hexated.SoraExtractor.invokeHdmovies4u -import com.hexated.SoraExtractor.invokeMoment import com.hexated.SoraExtractor.invokeMultimovies import com.hexated.SoraExtractor.invokeNetmovies -import com.hexated.SoraExtractor.invokeSFMovies import com.hexated.SoraExtractor.invokeShowflix import com.hexated.SoraExtractor.invokeTvMovies import com.hexated.SoraExtractor.invokeUhdmovies import com.hexated.SoraExtractor.invokeVegamovies import com.hexated.SoraExtractor.invokeVidsrcto import com.hexated.SoraExtractor.invokeCinemaTv -import com.hexated.SoraExtractor.invokeOmovies +import com.hexated.SoraExtractor.invokeMoflix +import com.hexated.SoraExtractor.invokeGhostx +import com.hexated.SoraExtractor.invokeNepu +import com.hexated.SoraExtractor.invokeWatchCartoon import com.hexated.SoraExtractor.invokeWatchsomuch +import com.hexated.SoraExtractor.invokeZoechip import com.hexated.SoraExtractor.invokeZshow import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId +import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink @@ -67,6 +65,8 @@ open class SoraStream : TmdbProvider() { TvType.Anime, ) + val wpRedisInterceptor by lazy { CloudflareKiller() } + /** AUTHOR : Hexated & Sora */ companion object { /** TOOLS */ @@ -81,16 +81,16 @@ open class SoraStream : TmdbProvider() { /** ALL SOURCES */ const val twoEmbedAPI = "https://www.2embed.cc" const val vidSrcAPI = "https://vidsrc.me" - const val dbgoAPI = "https://dbgo.fun" const val dreamfilmAPI = "https://dreamfilmsw.net" const val noverseAPI = "https://www.nollyverse.com" const val filmxyAPI = "https://www.filmxy.vip" const val kimcartoonAPI = "https://kimcartoon.li" const val aniwatchAPI = "https://aniwatch.to" + const val aniwaveAPI = "https://aniwave.to" const val crunchyrollAPI = "https://beta-api.crunchyroll.com" const val kissKhAPI = "https://kisskh.co" const val lingAPI = "https://ling-online.net" - const val m4uhdAPI = "https://ww2.m4ufree.com" + const val m4uhdAPI = "https://m4umv.org" const val rStreamAPI = "https://remotestream.cc" const val flixonAPI = "https://flixon.lol" const val smashyStreamAPI = "https://embed.smashystream.com" @@ -100,31 +100,30 @@ open class SoraStream : TmdbProvider() { const val nowTvAPI = "https://myfilestorage.xyz" const val gokuAPI = "https://goku.sx" const val zshowAPI = BuildConfig.ZSHOW_API - const val ridomoviesAPI = "https://ridomovies.pw" - const val navyAPI = "https://navy-issue-i-239.site" + const val ridomoviesAPI = "https://ridomovies.tv" const val emoviesAPI = "https://emovies.si" const val multimoviesAPI = "https://multimovies.top" const val multimovies2API = "https://multimovies.click" const val netmoviesAPI = "https://netmovies.to" - const val momentAPI = "https://izzillent-dickstonyx-i-262.site" + const val allmovielandAPI = "https://allmovieland.fun" const val doomoviesAPI = "https://doomovies.net" const val vidsrctoAPI = "https://vidsrc.to" const val dramadayAPI = "https://dramaday.me" const val animetoshoAPI = "https://animetosho.org" - const val watchflxAPI = "https://watchflx.tv" - const val blackvidAPI = "https://prod.api.blackvid.space" - const val showflixAPI = "https://showflix.space" + const val showflixAPI = "https://showflix.lol" const val aoneroomAPI = "https://api3.aoneroom.com" - - const val fdMoviesAPI = "https://freedrivemovie.lol" - const val uhdmoviesAPI = "https://uhdmovies.zip" - const val gMoviesAPI = "https://gdrivemovies.xyz" - const val hdmovies4uAPI = "https://hdmovies4u.band" - const val vegaMoviesAPI = "https://vegamovies.dad" - const val dotmoviesAPI = "https://dotmovies.bet" + const val mMoviesAPI = "https://multimovies.uno" + const val watchCartoonAPI = "https://www1.watchcartoononline.bz" + const val moflixAPI = "https://moflix-stream.xyz" + const val moviefictionAPI = "https://moviefiction.com" + const val zoechipAPI = "https://zoechip.org" + const val nepuAPI = "https://nepu.to" + const val fdMoviesAPI = "https://freedrivemovie.com" + const val uhdmoviesAPI = "https://uhdmovies.asia" + const val hdmovies4uAPI = "https://hdmovies4u.day" + const val vegaMoviesAPI = "https://vegamovies.ong" + const val dotmoviesAPI = "https://dotmovies.one" const val tvMoviesAPI = "https://www.tvseriesnmovies.com" - const val moviezAddAPI = "https://ww3.moviezaddiction.click" - const val bollyMazaAPI = "https://ww3.bollymaza.click" const val dahmerMoviesAPI = "https://odd-bird-1319.zwuhygoaqe.workers.dev" fun getType(t: String?): TvType { @@ -176,16 +175,12 @@ open class SoraStream : TmdbProvider() { return if (link.startsWith("/")) "https://image.tmdb.org/t/p/original/$link" else link } - override suspend fun getMainPage( - page: Int, - request: MainPageRequest - ): HomePageResponse { + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val adultQuery = if (settingsForProvider.enableAdult) "" else "&without_keywords=190370|13059|226161|195669" val type = if (request.data.contains("/movie")) "movie" else "tv" val home = app.get("${request.data}$adultQuery&page=$page") - .parsedSafe()?.results - ?.mapNotNull { media -> + .parsedSafe()?.results?.mapNotNull { media -> media.toSearchResponse(type) } ?: throw ErrorLoadingException("Invalid Json reponse") return newHomePageResponse(request.name, home) @@ -204,11 +199,10 @@ open class SoraStream : TmdbProvider() { override suspend fun quickSearch(query: String): List? = search(query) override suspend fun search(query: String): List? { - return app.get( - "$tmdbAPI/search/multi?api_key=$apiKey&language=en-US&query=$query&page=1&include_adult=${settingsForProvider.enableAdult}" - ).parsedSafe()?.results?.mapNotNull { media -> - media.toSearchResponse() - } + return app.get("$tmdbAPI/search/multi?api_key=$apiKey&language=en-US&query=$query&page=1&include_adult=${settingsForProvider.enableAdult}") + .parsedSafe()?.results?.mapNotNull { media -> + media.toSearchResponse() + } } override suspend fun load(url: String): LoadResponse? { @@ -243,17 +237,15 @@ open class SoraStream : TmdbProvider() { val actors = res.credits?.cast?.mapNotNull { cast -> ActorData( Actor( - cast.name ?: cast.originalName ?: return@mapNotNull null, - getImageUrl(cast.profilePath) - ), - roleString = cast.character + cast.name ?: cast.originalName + ?: return@mapNotNull null, getImageUrl(cast.profilePath) + ), roleString = cast.character ) } ?: return null val recommendations = res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() } val trailer = res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" } - ?.randomOrNull() return if (type == TvType.TvSeries) { val lastSeason = res.last_episode_to_air?.season_number @@ -277,12 +269,13 @@ open class SoraStream : TmdbProvider() { epsTitle = eps.name, jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title, date = season.airDate, - airedDate = res.releaseDate ?: res.firstAirDate, + airedDate = res.releaseDate + ?: res.firstAirDate, isAsian = isAsian, isBollywood = isBollywood, isCartoon = isCartoon ).toJson(), - name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "", + name = eps.name + if (isUpcoming(eps.airDate)) " • [UPCOMING]" else "", season = eps.seasonNumber, episode = eps.episodeNumber, posterUrl = getImageUrl(eps.stillPath), @@ -303,7 +296,7 @@ open class SoraStream : TmdbProvider() { this.backgroundPosterUrl = bgPoster this.year = year this.plot = res.overview - this.tags = if (isAnime) keywords else genres + this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres this.rating = rating this.showStatus = getStatus(res.status) this.recommendations = recommendations @@ -328,7 +321,8 @@ open class SoraStream : TmdbProvider() { orgTitle = orgTitle, isAnime = isAnime, jpTitle = res.alternative_titles?.results?.find { it.iso_3166_1 == "JP" }?.title, - airedDate = res.releaseDate ?: res.firstAirDate, + airedDate = res.releaseDate + ?: res.firstAirDate, isAsian = isAsian, isBollywood = isBollywood ).toJson(), @@ -339,7 +333,7 @@ open class SoraStream : TmdbProvider() { this.year = year this.plot = res.overview this.duration = res.runtime - this.tags = if (isAnime) keywords else genres + this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres this.rating = rating this.recommendations = recommendations this.actors = actors @@ -361,15 +355,6 @@ open class SoraStream : TmdbProvider() { val res = parseJson(data) argamap( - { - if (!res.isAnime) invokeBlackvid( - res.id, - res.season, - res.episode, - subtitleCallback, - callback - ) - }, { invokeDumpStream( res.title, @@ -392,19 +377,12 @@ open class SoraStream : TmdbProvider() { ) }, { - invokeVidSrc(res.id, res.season, res.episode, subtitleCallback, callback) - }, - { - invokeDbgo(res.imdbId, res.season, res.episode, subtitleCallback, callback) + invokeVidSrc(res.id, res.season, res.episode, callback) }, { if (!res.isAnime) invokeAoneroom( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { @@ -449,6 +427,16 @@ open class SoraStream : TmdbProvider() { callback ) }, + { + if (!res.isAnime && res.isCartoon) invokeWatchCartoon( + res.title, + res.year, + res.season, + res.episode, + subtitleCallback, + callback + ) + }, { if (!res.isAnime) invokeVidsrcto( res.imdbId, @@ -470,7 +458,7 @@ open class SoraStream : TmdbProvider() { ) }, { - if (!res.isAnime) invokeOmovies ( + if (!res.isAnime) invokeGhostx( res.title, res.year, res.season, @@ -480,12 +468,8 @@ open class SoraStream : TmdbProvider() { }, { if (!res.isAnime) invokeLing( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { @@ -498,58 +482,17 @@ open class SoraStream : TmdbProvider() { ) }, { - if (!res.isAnime) invokeGMovies( - res.title, - res.year, - res.season, - res.episode, - subtitleCallback, - callback - ) - }, - { - if (!res.isAnime) invokeFDMovies( - res.title, - res.season, - res.episode, - callback - ) + if (!res.isAnime) invokeFDMovies(res.title, res.season, res.episode, callback) }, { if (!res.isAnime) invokeM4uhd( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { if (!res.isAnime) invokeTvMovies(res.title, res.season, res.episode, callback) }, - { - if (!res.isAnime) invokeMoviezAdd( - moviezAddAPI, - "MoviezAdd", - res.title, - res.year, - res.season, - res.episode, - callback - ) - }, - { - if (!res.isAnime && res.isBollywood) invokeBollyMaza( - bollyMazaAPI, - "BollyMaza", - res.title, - res.year, - res.season, - res.episode, - callback - ) - }, { if (!res.isAnime) invokeRStream(res.id, res.season, res.episode, callback) }, @@ -564,7 +507,7 @@ open class SoraStream : TmdbProvider() { }, { if (!res.isAnime) invokeSmashyStream( - res.imdbId, + res.id, res.season, res.episode, subtitleCallback, @@ -589,19 +532,21 @@ open class SoraStream : TmdbProvider() { ) }, { - invokeDahmerMovies( - res.title, - res.year, - res.season, - res.episode, - callback - ) + invokeDahmerMovies(res.title, res.year, res.season, res.episode, callback) }, { invokeCinemaTv( + res.imdbId, res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback + ) + }, + { + if (!res.isAnime) invokeNowTv(res.id, res.imdbId, res.season, res.episode, callback) + }, + { + if (!res.isAnime) invokeRidomovies( + res.id, res.imdbId, - res.title, - res.airedYear ?: res.year, res.season, res.episode, subtitleCallback, @@ -609,20 +554,7 @@ open class SoraStream : TmdbProvider() { ) }, { - if (!res.isAnime) invokeNowTv(res.id, res.imdbId, res.season, res.episode, callback) - }, - { - if (!res.isAnime && res.season == null) invokeRidomovies( - res.id, - res.imdbId, - callback - ) - }, - { - invokeNavy(res.imdbId, res.season, res.episode, callback) - }, - { - invokeMoment(res.imdbId, res.season, res.episode, callback) + if (!res.isAnime) invokeAllMovieland(res.imdbId, res.season, res.episode, callback) }, { if (!res.isAnime) invokeEmovies( @@ -657,10 +589,24 @@ open class SoraStream : TmdbProvider() { ) }, { - if(res.isBollywood) invokeMultimovies(multimoviesAPI, res.title, res.season, res.episode, subtitleCallback, callback) + if (res.isBollywood) invokeMultimovies( + multimoviesAPI, + res.title, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { - if(res.isBollywood) invokeMultimovies(multimovies2API, res.title, res.season, res.episode, subtitleCallback, callback) + if (res.isBollywood) invokeMultimovies( + multimovies2API, + res.title, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { invokeNetmovies( @@ -690,7 +636,13 @@ open class SoraStream : TmdbProvider() { ) }, { - if (!res.isAnime) invoke2embed(res.imdbId, res.season, res.episode, subtitleCallback, callback) + if (!res.isAnime) invoke2embed( + res.imdbId, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { if (!res.isAnime) invokeHdmovies4u( @@ -723,8 +675,13 @@ open class SoraStream : TmdbProvider() { ) }, { - if (!res.isAnime) invokeSFMovies( - res.id, + if (!res.isAnime) invokeMoflix(res.id, res.season, res.episode, callback) + }, + { + if (!res.isAnime) invokeZoechip(res.title, res.year, res.season, res.episode, callback) + }, + { + if (!res.isAnime) invokeNepu( res.title, res.airedYear ?: res.year, res.season, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index d4b25315..122f4c45 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -1,10 +1,9 @@ package com.hexated import com.hexated.SoraExtractor.invoke2embed +import com.hexated.SoraExtractor.invokeAllMovieland import com.hexated.SoraExtractor.invokeAnimes import com.hexated.SoraExtractor.invokeAoneroom -import com.hexated.SoraExtractor.invokeBlackvid -import com.hexated.SoraExtractor.invokeDbgo import com.hexated.SoraExtractor.invokeDoomovies import com.hexated.SoraExtractor.invokeDramaday import com.hexated.SoraExtractor.invokeDreamfilm @@ -15,7 +14,6 @@ import com.hexated.SoraExtractor.invokeKimcartoon import com.hexated.SoraExtractor.invokeKisskh import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeM4uhd -import com.hexated.SoraExtractor.invokeNavy import com.hexated.SoraExtractor.invokeNinetv import com.hexated.SoraExtractor.invokeNowTv import com.hexated.SoraExtractor.invokeRStream @@ -23,16 +21,19 @@ import com.hexated.SoraExtractor.invokeRidomovies import com.hexated.SoraExtractor.invokeSmashyStream import com.hexated.SoraExtractor.invokeDumpStream import com.hexated.SoraExtractor.invokeEmovies -import com.hexated.SoraExtractor.invokeMoment import com.hexated.SoraExtractor.invokeMultimovies import com.hexated.SoraExtractor.invokeNetmovies -import com.hexated.SoraExtractor.invokeSFMovies import com.hexated.SoraExtractor.invokeShowflix import com.hexated.SoraExtractor.invokeVidSrc import com.hexated.SoraExtractor.invokeVidsrcto import com.hexated.SoraExtractor.invokeCinemaTv -import com.hexated.SoraExtractor.invokeOmovies +import com.hexated.SoraExtractor.invokeMoflix +import com.hexated.SoraExtractor.invokeGhostx +import com.hexated.SoraExtractor.invokeMoviefiction +import com.hexated.SoraExtractor.invokeNepu +import com.hexated.SoraExtractor.invokeWatchCartoon import com.hexated.SoraExtractor.invokeWatchsomuch +import com.hexated.SoraExtractor.invokeZoechip import com.hexated.SoraExtractor.invokeZshow import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.argamap @@ -53,13 +54,7 @@ class SoraStreamLite : SoraStream() { argamap( { - if (!res.isAnime) invokeBlackvid( - res.id, - res.season, - res.episode, - subtitleCallback, - callback - ) + if (!res.isAnime) invokeMoflix(res.id, res.season, res.episode, callback) }, { if (!res.isAnime) invokeWatchsomuch( @@ -100,10 +95,17 @@ class SoraStreamLite : SoraStream() { ) }, { - invokeVidSrc(res.id, res.season, res.episode, subtitleCallback, callback) + invokeVidSrc(res.id, res.season, res.episode, callback) }, { - invokeDbgo(res.imdbId, res.season, res.episode, subtitleCallback, callback) + if (!res.isAnime && res.isCartoon) invokeWatchCartoon( + res.title, + res.year, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { if (res.isAnime) invokeAnimes( @@ -136,7 +138,7 @@ class SoraStreamLite : SoraStream() { ) }, { - if (!res.isAnime) invokeOmovies( + if (!res.isAnime) invokeGhostx( res.title, res.year, res.season, @@ -155,7 +157,7 @@ class SoraStreamLite : SoraStream() { }, { if (!res.isAnime) invokeSmashyStream( - res.imdbId, + res.id, res.season, res.episode, subtitleCallback, @@ -184,39 +186,32 @@ class SoraStreamLite : SoraStream() { }, { if (!res.isAnime) invokeLing( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { - if(!res.isAnime) invokeM4uhd( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + if (!res.isAnime) invokeM4uhd( + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { if (!res.isAnime) invokeRStream(res.id, res.season, res.episode, callback) }, { - if (!res.isAnime) invokeFlixon(res.id, res.imdbId, res.season, res.episode, callback) + if (!res.isAnime) invokeFlixon( + res.id, + res.imdbId, + res.season, + res.episode, + callback + ) }, { invokeCinemaTv( - res.imdbId, - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.imdbId, res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { @@ -224,21 +219,17 @@ class SoraStreamLite : SoraStream() { }, { if (!res.isAnime) invokeAoneroom( - res.title, - res.airedYear ?: res.year, - res.season, - res.episode, - subtitleCallback, - callback + res.title, res.airedYear + ?: res.year, res.season, res.episode, subtitleCallback, callback ) }, { - invokeNavy(res.imdbId, res.season, res.episode, callback) - }, - { - if (!res.isAnime && res.season == null) invokeRidomovies( + if (!res.isAnime) invokeRidomovies( res.id, res.imdbId, + res.season, + res.episode, + subtitleCallback, callback ) }, @@ -253,10 +244,24 @@ class SoraStreamLite : SoraStream() { ) }, { - if(res.isBollywood) invokeMultimovies(multimoviesAPI, res.title, res.season, res.episode, subtitleCallback, callback) + if (res.isBollywood) invokeMultimovies( + multimoviesAPI, + res.title, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { - if(res.isBollywood) invokeMultimovies(multimovies2API, res.title, res.season, res.episode, subtitleCallback, callback) + if (res.isBollywood) invokeMultimovies( + multimovies2API, + res.title, + res.season, + res.episode, + subtitleCallback, + callback + ) }, { invokeNetmovies( @@ -269,7 +274,7 @@ class SoraStreamLite : SoraStream() { ) }, { - invokeMoment(res.imdbId, res.season, res.episode, callback) + if (!res.isAnime) invokeAllMovieland(res.imdbId, res.season, res.episode, callback) }, { if (!res.isAnime && res.season == null) invokeDoomovies( @@ -279,7 +284,7 @@ class SoraStreamLite : SoraStream() { ) }, { - if(res.isAsian) invokeDramaday( + if (res.isAsian) invokeDramaday( res.title, res.year, res.season, @@ -289,7 +294,7 @@ class SoraStreamLite : SoraStream() { ) }, { - if(!res.isAnime) invoke2embed( + if (!res.isAnime) invoke2embed( res.imdbId, res.season, res.episode, @@ -318,8 +323,16 @@ class SoraStreamLite : SoraStream() { ) }, { - if(!res.isAnime) invokeSFMovies( - res.id, + if (!res.isAnime) invokeZoechip( + res.title, + res.year, + res.season, + res.episode, + callback + ) + }, + { + if (!res.isAnime) invokeNepu( res.title, res.airedYear ?: res.year, res.season, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt index 23cc44f2..f0f54b5e 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt @@ -25,10 +25,16 @@ class SoraStreamPlugin: Plugin() { registerExtractorAPI(Streamwish()) registerExtractorAPI(FilelionsTo()) registerExtractorAPI(Embedwish()) - registerExtractorAPI(Wishfast()) + registerExtractorAPI(UqloadsXyz()) registerExtractorAPI(Uploadever()) registerExtractorAPI(Netembed()) - registerExtractorAPI(Vidplay2()) registerExtractorAPI(Flaswish()) + registerExtractorAPI(Comedyshow()) + registerExtractorAPI(Ridoo()) + registerExtractorAPI(Streamvid()) + registerExtractorAPI(Embedrise()) + registerExtractorAPI(Gdmirrorbot()) + registerExtractorAPI(FilemoonNl()) + registerExtractorAPI(Alions()) } } \ 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 f5895a6e..f6b3f13d 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -9,7 +9,6 @@ import com.hexated.SoraStream.Companion.gdbot import com.hexated.SoraStream.Companion.hdmovies4uAPI import com.hexated.SoraStream.Companion.malsyncAPI import com.hexated.SoraStream.Companion.tvMoviesAPI -import com.hexated.SoraStream.Companion.watchflxAPI import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.APIHolder.unixTimeMS @@ -19,16 +18,12 @@ import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.RequestBodyTypes -import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.nicehttp.requestCreator import kotlinx.coroutines.delay import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient -import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response import org.jsoup.nodes.Document import java.math.BigInteger import java.net.* @@ -38,7 +33,6 @@ import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.TimeUnit import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.IvParameterSpec @@ -46,7 +40,6 @@ import javax.crypto.spec.SecretKeySpec import kotlin.collections.ArrayList import kotlin.math.min -var watchflxCookies: Map? = null var filmxyCookies: Map? = null var sfServer: String? = null @@ -330,10 +323,8 @@ suspend fun getDrivebotLink(url: String?): String? { ?.data()?.substringAfter("window.open('")?.substringBefore("')") } -suspend fun extractOiya(url: String, quality: String): String? { - val doc = app.get(url).document - return doc.selectFirst("div.wp-block-button a:matches((?i)$quality)")?.attr("href") - ?: doc.selectFirst("div.wp-block-button a")?.attr("href") +suspend fun extractOiya(url: String): String? { + return app.get(url).document.selectFirst("div.wp-block-button a")?.attr("href") } fun deobfstr(hash: String, index: String): String { @@ -404,6 +395,7 @@ suspend fun invokeSmashyFfix( name: String, url: String, ref: String, + subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { val json = app.get(url, referer = ref, headers = mapOf("X-Requested-With" to "XMLHttpRequest")) @@ -416,23 +408,41 @@ suspend fun invokeSmashyFfix( ).forEach(callback) } + json?.subtitleUrls?.split(",")?.map { sub -> + val lang = "\\[(.*)]".toRegex().find(sub)?.groupValues?.get(1) + val subUrl = sub.replace("[$lang]", "").trim() + subtitleCallback.invoke( + SubtitleFile( + lang ?: return@map, + subUrl + ) + ) + } + } -suspend fun invokeSmashyD( +suspend fun invokeSmashySu( + name: String, url: String, ref: String, callback: (ExtractorLink) -> Unit, ) { val json = app.get(url, referer = ref, headers = mapOf("X-Requested-With" to "XMLHttpRequest")) - .parsedSafe() - json?.sourceUrls?.apmap { - M3u8Helper.generateM3u8( - "Smashy [Player D ${it.title}]", - it.file ?: return@apmap, - "" - ).forEach(callback) + .parsedSafe() + json?.sourceUrls?.firstOrNull()?.removeSuffix(",")?.split(",")?.forEach { links -> + val quality = Regex("\\[(\\S+)]").find(links)?.groupValues?.getOrNull(1) ?: return@forEach + val trimmedLink = links.removePrefix("[$quality]").trim() + callback.invoke( + ExtractorLink( + "Smashy [$name]", + "Smashy [$name]", + trimmedLink, + "", + getQualityFromName(quality), + INFER_TYPE + ) + ) } - } suspend fun getDumpIdAndType(title: String?, year: Int?, season: Int?): Pair { @@ -588,10 +598,11 @@ suspend fun bypassFdAds(url: String?): String? { } suspend fun bypassHrefli(url: String): String? { - fun Document.getFormUrl() : String { + fun Document.getFormUrl(): String { return this.select("form#landing").attr("action") } - fun Document.getFormData() : Map { + + fun Document.getFormData(): Map { return this.select("form#landing input").associate { it.attr("name") to it.attr("value") } } @@ -605,7 +616,8 @@ suspend fun bypassHrefli(url: String): String? { formData = res.getFormData() res = app.post(formUrl, data = formData).document - val skToken = res.selectFirst("script:containsData(?go=)")?.data()?.substringAfter("?go=")?.substringBefore("\"") ?: return null + val skToken = res.selectFirst("script:containsData(?go=)")?.data()?.substringAfter("?go=") + ?.substringBefore("\"") ?: return null val driveUrl = app.get( "$host?go=$skToken", cookies = mapOf( skToken to "${formData["_wp_http2"]}" @@ -699,30 +711,13 @@ suspend fun fetchFilmxyCookies(url: String): Map { return cookieJar.plus(defaultCookies) } -suspend fun getWatchflxCookies() = - watchflxCookies ?: fetchWatchflxCookies().also { watchflxCookies = it } - -suspend fun fetchWatchflxCookies(): Map { - session.get(watchflxAPI) - val cookies = session.baseClient.cookieJar.loadForRequest(watchflxAPI.toHttpUrl()) - .associate { it.name to it.value } - val loginUrl = "$watchflxAPI/cookie-based-login" - session.post( - loginUrl, data = mapOf( - "continue_as_temp" to "true" - ), cookies = cookies, headers = mapOf("X-Requested-With" to "XMLHttpRequest") - ) - return session.baseClient.cookieJar.loadForRequest(loginUrl.toHttpUrl()) - .associate { it.name to it.value } -} - fun Document.findTvMoviesIframe(): String? { return this.selectFirst("script:containsData(var seconds)")?.data()?.substringAfter("href='") ?.substringBefore("'>") } //modified code from https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt -suspend fun getCrunchyrollToken(): Map { +suspend fun getCrunchyrollToken(): CrunchyrollAccessToken { val client = app.baseClient.newBuilder() .proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("cr-unblocker.us.to", 1080))) .build() @@ -748,9 +743,18 @@ suspend fun getCrunchyrollToken(): Map { ) ) - val response = tryParseJson(client.newCall(request).execute().body.string()) - return mapOf("Authorization" to "${response?.tokenType} ${response?.accessToken}") - + val token = tryParseJson(client.newCall(request).execute().body.string()) + val headers = mapOf("Authorization" to "${token?.tokenType} ${token?.accessToken}") + val cms = + app.get("$crunchyrollAPI/index/v2", headers = headers).parsedSafe()?.cms + return CrunchyrollAccessToken( + token?.accessToken, + token?.tokenType, + cms?.bucket, + cms?.policy, + cms?.signature, + cms?.key_pair_id, + ) } suspend fun getCrunchyrollId(aniId: String?): String? { @@ -784,7 +788,7 @@ suspend fun getCrunchyrollId(aniId: String?): String? { return (externalLinks?.find { it.site == "VRV" } ?: externalLinks?.find { it.site == "Crunchyroll" })?.url?.let { - Regex("series/(\\w+)/?").find(it)?.groupValues?.get(1) + app.get(it).url.substringAfter("/series/").substringBefore("/") } } @@ -797,6 +801,10 @@ suspend fun getCrunchyrollIdFromMalSync(aniId: String?): String? { ?: regex.find("$crunchyroll")?.groupValues?.getOrNull(1) } +suspend fun String.haveDub(referer: String) : Boolean { + return app.get(this,referer=referer).text.contains("TYPE=AUDIO") +} + suspend fun convertTmdbToAnimeId( title: String?, date: String?, @@ -1031,7 +1039,7 @@ fun decodeIndexJson(json: String): String { return base64Decode(slug.substring(0, slug.length - 20)) } -fun String.decodePrimewireXor(key: String): String { +fun String.xorDecrypt(key: String): String { val sb = StringBuilder() var i = 0 while (i < this.length) { @@ -1057,9 +1065,9 @@ fun vidsrctoDecrypt(text: String): String { } fun String?.createSlug(): String? { - return this?.replace(Regex("[^\\w\\s-]"), "") - ?.replace(" ", "-") - ?.replace(Regex("( – )|( -)|(- )|(--)"), "-") + return this?.filter { it.isWhitespace() || it.isLetterOrDigit() } + ?.trim() + ?.replace("\\s+".toRegex(), "-") ?.lowercase() } @@ -1145,14 +1153,6 @@ fun getVipLanguage(str: String): String { } } -fun getDbgoLanguage(str: String): String { - return when (str) { - "Русский" -> "Russian" - "Українська" -> "Ukrainian" - else -> str - } -} - fun fixCrunchyrollLang(language: String?): String? { return SubtitleHelper.fromTwoLettersToLanguage(language ?: return null) ?: SubtitleHelper.fromTwoLettersToLanguage(language.substringBefore("-")) @@ -1209,37 +1209,6 @@ fun base64DecodeAPI(api: String): String { return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("") } -fun decryptStreamUrl(data: String): String { - - fun getTrash(arr: List, item: Int): List { - val trash = ArrayList>() - for (i in 1..item) { - trash.add(arr) - } - return trash.reduce { acc, list -> - val temp = ArrayList() - acc.forEach { ac -> - list.forEach { li -> - temp.add(ac.plus(li)) - } - } - return@reduce temp - } - } - - val trashList = listOf("@", "#", "!", "^", "$") - val trashSet = getTrash(trashList, 2) + getTrash(trashList, 3) - var trashString = data.replace("#2", "").split("//_//").joinToString("") - - trashSet.forEach { - val temp = base64Encode(it.toByteArray()) - trashString = trashString.replace(temp, "") - } - - return base64Decode(trashString) - -} - fun fixUrl(url: String, domain: String): String { if (url.startsWith("http")) { return url @@ -1283,23 +1252,53 @@ private enum class Symbol(val decimalValue: Int) { } } -suspend fun request( - url: String, - allowRedirects: Boolean = true, - timeout: Long = 60L -): Response { - val client = OkHttpClient().newBuilder() - .connectTimeout(timeout, TimeUnit.SECONDS) - .readTimeout(timeout, TimeUnit.SECONDS) - .writeTimeout(timeout, TimeUnit.SECONDS) - .followRedirects(allowRedirects) - .followSslRedirects(allowRedirects) - .build() +// steal from https://github.com/aniyomiorg/aniyomi-extensions/blob/master/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt +// credits to @samfundev +object AniwaveUtils { - val request: Request = Request.Builder() - .url(url) - .build() - return client.newCall(request).await() + fun encodeVrf(input: String): String { + val rc4Key = SecretKeySpec("ysJhV6U27FVIjjuk".toByteArray(), "RC4") + val cipher = Cipher.getInstance("RC4") + cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) + var vrf = cipher.doFinal(input.toByteArray()) + vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) + vrf = Base64.encode(vrf, Base64.DEFAULT or Base64.NO_WRAP) + vrf = vrfShift(vrf) + vrf = Base64.encode(vrf, Base64.DEFAULT) + vrf = rot13(vrf) + val stringVrf = vrf.toString(Charsets.UTF_8) + return encode(stringVrf) + } + + fun decodeVrf(input: String): String { + var vrf = input.toByteArray() + vrf = Base64.decode(vrf, Base64.URL_SAFE) + val rc4Key = SecretKeySpec("hlPeNwkncH0fq9so".toByteArray(), "RC4") + val cipher = Cipher.getInstance("RC4") + cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) + vrf = cipher.doFinal(vrf) + return decode(vrf.toString(Charsets.UTF_8)) + } + + private fun rot13(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val byte = vrf[i] + if (byte in 'A'.code..'Z'.code) { + vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte() + } else if (byte in 'a'.code..'z'.code) { + vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte() + } + } + return vrf + } + + private fun vrfShift(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val shift = arrayOf(-3, 3, -4, 2, -2, 5, 4, 5)[i % 8] + vrf[i] = vrf[i].plus(shift).toByte() + } + return vrf + } } object DumpUtils { diff --git a/StremioX/build.gradle.kts b/StremioX/build.gradle.kts index c08ea2b1..6c7bb75e 100644 --- a/StremioX/build.gradle.kts +++ b/StremioX/build.gradle.kts @@ -1,6 +1,16 @@ -// use an integer for version numbers -version = 12 +import org.jetbrains.kotlin.konan.properties.Properties +// use an integer for version numbers +version = 13 + +android { + defaultConfig { + val properties = Properties() + properties.load(project.rootProject.file("local.properties").inputStream()) + + buildConfigField("String", "TMDB_API", "\"${properties.getProperty("TMDB_API")}\"") + } +} cloudstream { language = "en" diff --git a/StremioX/src/main/kotlin/com/hexated/StremioC.kt b/StremioX/src/main/kotlin/com/hexated/StremioC.kt index 4d972d31..b4871b26 100644 --- a/StremioX/src/main/kotlin/com/hexated/StremioC.kt +++ b/StremioX/src/main/kotlin/com/hexated/StremioC.kt @@ -10,24 +10,23 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import java.net.URI - -private const val TRACKER_LIST_URL = - "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" class StremioC : MainAPI() { override var mainUrl = "https://stremio.github.io/stremio-static-addon-example" override var name = "StremioC" override val supportedTypes = setOf(TvType.Others) override val hasMainPage = true - private val cinemataUrl = "https://v3-cinemeta.strem.io" - override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse? { + companion object { + private const val cinemataUrl = "https://v3-cinemeta.strem.io" + private const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" + } + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { mainUrl = mainUrl.fixSourceUrl() - val res = tryParseJson(request("${mainUrl}/manifest.json").body.string()) ?: return null + val res = app.get("${mainUrl}/manifest.json").parsedSafe() val lists = mutableListOf() - res.catalogs.apmap { catalog -> + res?.catalogs?.apmap { catalog -> catalog.toHomePageList(this).let { if (it.list.isNotEmpty()) lists.add(it) } @@ -38,11 +37,11 @@ class StremioC : MainAPI() { ) } - override suspend fun search(query: String): List? { + override suspend fun search(query: String): List { mainUrl = mainUrl.fixSourceUrl() - val res = tryParseJson(request("${mainUrl}/manifest.json").body.string()) ?: return null + val res = app.get("${mainUrl}/manifest.json").parsedSafe() val list = mutableListOf() - res.catalogs.apmap { catalog -> + res?.catalogs?.apmap { catalog -> list.addAll(catalog.search(query, this)) } return list.distinct() @@ -64,10 +63,13 @@ class StremioC : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { val loadData = parseJson(data) - val request = request("${mainUrl}/stream/${loadData.type}/${loadData.id}.json") - if (request.code.isSuccessful()) { - val res = tryParseJson(request.body.string()) ?: return false - res.streams.forEach { stream -> + val request = app.get( + "${mainUrl}/stream/${loadData.type}/${loadData.id}.json", + timeout = 120L + ) + if (request.isSuccessful) { + val res = request.parsedSafe() + res?.streams?.forEach { stream -> stream.runCallback(subtitleCallback, callback) } } else { @@ -103,15 +105,14 @@ class StremioC : MainAPI() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val sites = - AcraApplication.getKey>(USER_PROVIDER_API)?.toMutableList() - ?: mutableListOf() + val sites = AcraApplication.getKey>(USER_PROVIDER_API)?.toMutableList() + ?: mutableListOf() sites.filter { it.parentJavaClass == "StremioX" }.apmap { site -> - val request = request("${site.url.fixSourceUrl()}/stream/${type}/${id}.json").body.string() - val res = - tryParseJson(request) - ?: return@apmap - res.streams.forEach { stream -> + val res = app.get( + "${site.url.fixSourceUrl()}/stream/${type}/${id}.json", + timeout = 120L + ).parsedSafe() + res?.streams?.forEach { stream -> stream.runCallback(subtitleCallback, callback) } } @@ -151,11 +152,11 @@ class StremioC : MainAPI() { suspend fun search(query: String, provider: StremioC): List { val entries = mutableListOf() types.forEach { type -> - val json = request("${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json").body.string() - val res = - tryParseJson(json) - ?: return@forEach - res.metas?.forEach { entry -> + val res = app.get( + "${provider.mainUrl}/catalog/${type}/${id}/search=${query}.json", + timeout = 120L + ).parsedSafe() + res?.metas?.forEach { entry -> entries.add(entry.toSearchResponse(provider)) } } @@ -165,11 +166,11 @@ class StremioC : MainAPI() { suspend fun toHomePageList(provider: StremioC): HomePageList { val entries = mutableListOf() types.forEach { type -> - val json = request("${provider.mainUrl}/catalog/${type}/${id}.json").body.string() - val res = - tryParseJson(json) - ?: return@forEach - res.metas?.forEach { entry -> + val res = app.get( + "${provider.mainUrl}/catalog/${type}/${id}.json", + timeout = 120L + ).parsedSafe() + res?.metas?.forEach { entry -> entries.add(entry.toSearchResponse(provider)) } } @@ -186,6 +187,7 @@ class StremioC : MainAPI() { val source: String?, val type: String? ) + private data class CatalogEntry( @JsonProperty("name") val name: String, @JsonProperty("id") val id: String, @@ -226,7 +228,7 @@ class StremioC : MainAPI() { year = yearNum?.toIntOrNull() tags = genre ?: genres addActors(cast) - addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull()) + addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }) addImdbId(imdbId) } } else { @@ -245,7 +247,8 @@ class StremioC : MainAPI() { year = yearNum?.toIntOrNull() tags = genre ?: genres addActors(cast) - addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" }?.randomOrNull()) + addTrailer(trailersSources?.map { "https://www.youtube.com/watch?v=${it.source}" } + ?.randomOrNull()) addImdbId(imdbId) } } @@ -285,13 +288,14 @@ class StremioC : MainAPI() { ) private data class ProxyHeaders( - val request: Map?, + val request: Map?, ) private data class BehaviorHints( val proxyHeaders: ProxyHeaders?, - val headers: Map?, + val headers: Map?, ) + private data class Stream( val name: String?, val title: String?, @@ -312,12 +316,13 @@ class StremioC : MainAPI() { callback.invoke( ExtractorLink( name ?: "", - fixRDSourceName(name, title), + fixSourceName(name, title), url, "", - getQualityFromName(description), - headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(), - isM3u8 = URI(url).path.endsWith(".m3u8") + getQuality(listOf(description,title,name)), + headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers + ?: mapOf(), + type = INFER_TYPE ) ) subtitles.map { sub -> diff --git a/StremioX/src/main/kotlin/com/hexated/StremioX.kt b/StremioX/src/main/kotlin/com/hexated/StremioX.kt index 314e124f..44d98f47 100644 --- a/StremioX/src/main/kotlin/com/hexated/StremioX.kt +++ b/StremioX/src/main/kotlin/com/hexated/StremioX.kt @@ -10,26 +10,21 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson -import java.net.URI import java.util.ArrayList import kotlin.math.roundToInt import com.lagradost.cloudstream3.metaproviders.TmdbProvider -open class StremioX : TmdbProvider() { +class StremioX : TmdbProvider() { override var mainUrl = "https://torrentio.strem.fun" override var name = "StremioX" override val hasMainPage = true override val hasQuickSearch = true - override val supportedTypes = setOf( - TvType.Others, - ) + override val supportedTypes = setOf(TvType.Others) companion object { - const val TRACKER_LIST_URL = - "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" + const val TRACKER_LIST_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt" private const val tmdbAPI = "https://api.themoviedb.org/3" - private val apiKey = - base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL + private const val apiKey = BuildConfig.TMDB_API fun getType(t: String?): TvType { return when (t) { @@ -44,11 +39,6 @@ open class StremioX : TmdbProvider() { else -> ShowStatus.Completed } } - - private fun base64DecodeAPI(api: String): String { - return api.chunked(4).map { base64Decode(it) }.reversed().joinToString("") - } - } override val mainPage = mainPageOf( @@ -159,7 +149,7 @@ open class StremioX : TmdbProvider() { eps.seasonNumber, eps.episodeNumber ).toJson(), - name = eps.name + if (isUpcoming(eps.airDate)) " - [UPCOMING]" else "", + name = eps.name + if (isUpcoming(eps.airDate)) " • [UPCOMING]" else "", season = eps.seasonNumber, episode = eps.episodeNumber, posterUrl = getImageUrl(eps.stillPath), @@ -177,7 +167,7 @@ open class StremioX : TmdbProvider() { this.backgroundPosterUrl = bgPoster this.year = year this.plot = res.overview - this.tags = if (isAnime) keywords else genres + this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres this.rating = rating this.showStatus = getStatus(res.status) this.recommendations = recommendations @@ -200,7 +190,7 @@ open class StremioX : TmdbProvider() { this.year = year this.plot = res.overview this.duration = res.runtime - this.tags = if (isAnime) keywords else genres + this.tags = keywords.takeIf { !it.isNullOrEmpty() } ?: genres this.rating = rating this.recommendations = recommendations this.actors = actors @@ -243,13 +233,13 @@ open class StremioX : TmdbProvider() { callback: (ExtractorLink) -> Unit ) { val fixMainUrl = mainUrl.fixSourceUrl() - val url = if(season == null) { + val url = if (season == null) { "$fixMainUrl/stream/movie/$imdbId.json" } else { "$fixMainUrl/stream/series/$imdbId:$season:$episode.json" } - val res = AppUtils.tryParseJson(request(url).body.string()) ?: return - res.streams.forEach { stream -> + val res = app.get(url, timeout = 120L).parsedSafe() + res?.streams?.forEach { stream -> stream.runCallback(subtitleCallback, callback) } } @@ -262,12 +252,12 @@ open class StremioX : TmdbProvider() { ) private data class ProxyHeaders( - val request: Map?, + val request: Map?, ) private data class BehaviorHints( val proxyHeaders: ProxyHeaders?, - val headers: Map?, + val headers: Map?, ) private data class Stream( @@ -290,12 +280,13 @@ open class StremioX : TmdbProvider() { callback.invoke( ExtractorLink( name ?: "", - fixRDSourceName(name, title), + fixSourceName(name, title), url, "", - getQualityFromName(description), - headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers ?: mapOf(), - isM3u8 = URI(url).path.endsWith(".m3u8") + getQuality(listOf(description,title,name)), + headers = behaviorHints?.proxyHeaders?.request ?: behaviorHints?.headers + ?: mapOf(), + type = INFER_TYPE ) ) subtitles.map { sub -> diff --git a/StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt b/StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt index 367114bd..0af2d9fd 100644 --- a/StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt +++ b/StremioX/src/main/kotlin/com/hexated/SubsExtractors.kt @@ -3,10 +3,9 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.utils.SubtitleHelper -const val openSubAPI = "https://opensubtitles.strem.io/stremio/v1" +const val openSubAPI = "https://opensubtitles-v3.strem.io" const val watchSomuchAPI = "https://watchsomuch.tv" object SubsExtractors { @@ -16,22 +15,20 @@ object SubsExtractors { episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, ) { - val id = if(season == null) { - imdbId + val slug = if(season == null) { + "movie/$imdbId" } else { - "$imdbId $season $episode" + "series/$imdbId:$season:$episode" } - val data = base64Encode("""{"id":1,"jsonrpc":"2.0","method":"subtitles.find","params":[null,{"query":{"itemHash":"$id"}}]}""".toByteArray()) - app.get("${openSubAPI}/q.json?b=$data").parsedSafe()?.result?.all?.map { sub -> + app.get("${openSubAPI}/subtitles/$slug.json", timeout = 120L).parsedSafe()?.subtitles?.map { sub -> subtitleCallback.invoke( SubtitleFile( SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang - ?: "", + ?: return@map, sub.url ?: return@map ) ) } - } suspend fun invokeWatchsomuch( @@ -45,7 +42,7 @@ object SubsExtractors { "${watchSomuchAPI}/Watch/ajMovieTorrents.aspx", data = mapOf( "index" to "0", "mid" to "$id", - "wsk" to "f6ea6cde-e42b-4c26-98d3-b4fe48cdd4fb", + "wsk" to "30fb68aa-1c71-4b8c-b5d4-4ca9222cfb45", "lid" to "", "liu" to "" ), headers = mapOf("X-Requested-With" to "XMLHttpRequest") @@ -81,12 +78,8 @@ object SubsExtractors { @JsonProperty("lang") val lang: String? = null, ) - data class OsAll( - @JsonProperty("all") val all: ArrayList? = arrayListOf(), - ) - data class OsResult( - @JsonProperty("result") val result: OsAll? = null, + @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), ) data class WatchsomuchTorrents( diff --git a/StremioX/src/main/kotlin/com/hexated/Utils.kt b/StremioX/src/main/kotlin/com/hexated/Utils.kt index 30b676de..51feea3f 100644 --- a/StremioX/src/main/kotlin/com/hexated/Utils.kt +++ b/StremioX/src/main/kotlin/com/hexated/Utils.kt @@ -1,53 +1,35 @@ package com.hexated -import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.nicehttp.Requests.Companion.await -import okhttp3.OkHttpClient -import okhttp3.Request +import com.lagradost.cloudstream3.utils.getQualityFromName +import okhttp3.Interceptor import okhttp3.Response import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit -const val defaultTimeOut = 30L -suspend fun request( - url: String, - allowRedirects: Boolean = true, - timeout: Long = defaultTimeOut -): Response { - val client = OkHttpClient().newBuilder() - .connectTimeout(timeout, TimeUnit.SECONDS) - .readTimeout(timeout, TimeUnit.SECONDS) - .writeTimeout(timeout, TimeUnit.SECONDS) - .followRedirects(allowRedirects) - .followSslRedirects(allowRedirects) - .build() - - val request: Request = Request.Builder() - .url(url) - .build() - return client.newCall(request).await() -} - -fun Int.isSuccessful() : Boolean { - return this in 200..299 -} - fun String.fixSourceUrl(): String { return this.replace("/manifest.json", "").replace("stremio://", "https://") } -fun fixRDSourceName(name: String?, title: String?): String { +fun fixSourceName(name: String?, title: String?): String { return when { name?.contains("[RD+]", true) == true -> "[RD+] $title" - name?.contains("[RD download]", true) == true -> "[RD] $title" + name?.contains("[RD download]", true) == true -> "[RD download] $title" !name.isNullOrEmpty() && !title.isNullOrEmpty() -> "$name $title" else -> title ?: name ?: "" } } +fun getQuality(qualities: List): Int { + fun String.getQuality(): String? { + return Regex("(\\d{3,4}[pP])").find(this)?.groupValues?.getOrNull(1) + } + val quality = qualities.firstNotNullOfOrNull { it?.getQuality() } + return getQualityFromName(quality) +} + fun getEpisodeSlug( season: Int? = null, episode: Int? = null, diff --git a/Superstream/build.gradle.kts b/Superstream/build.gradle.kts new file mode 100644 index 00000000..2bf7d8a9 --- /dev/null +++ b/Superstream/build.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.konan.properties.Properties + +// use an integer for version numbers +version = 3 + +android { + defaultConfig { + val properties = Properties() + properties.load(project.rootProject.file("local.properties").inputStream()) + + buildConfigField("String", "SUPERSTREAM_FIRST_API", "\"${properties.getProperty("SUPERSTREAM_FIRST_API")}\"") + buildConfigField("String", "SUPERSTREAM_SECOND_API", "\"${properties.getProperty("SUPERSTREAM_SECOND_API")}\"") + buildConfigField("String", "SUPERSTREAM_THIRD_API", "\"${properties.getProperty("SUPERSTREAM_THIRD_API")}\"") + buildConfigField("String", "SUPERSTREAM_FOURTH_API", "\"${properties.getProperty("SUPERSTREAM_FOURTH_API")}\"") + } +} + +cloudstream { + language = "en" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Blatzar") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AsianDrama", + "Anime", + "TvSeries", + "Movie", + ) + + + iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1196694385061003334/icon.png" +} \ No newline at end of file diff --git a/Superstream/src/main/AndroidManifest.xml b/Superstream/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c98063f8 --- /dev/null +++ b/Superstream/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Superstream/src/main/kotlin/com/hexated/Extractors.kt b/Superstream/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..1d0fd0e3 --- /dev/null +++ b/Superstream/src/main/kotlin/com/hexated/Extractors.kt @@ -0,0 +1,243 @@ +package com.hexated + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.utils.* +import java.net.URL + +object Extractors : Superstream() { + + suspend fun invokeInternalSource( + id: Int? = null, + type: Int? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + fun LinkList.toExtractorLink(): ExtractorLink? { + if (this.path.isNullOrBlank()) return null + return ExtractorLink( + "Internal", + "Internal [${this.size}]", + this.path.replace("\\/", ""), + "", + getQualityFromName(this.quality), + ) + } + + // No childmode when getting links + // New api does not return video links :( + val query = if (type == ResponseTypes.Movies.value) { + """{"childmode":"0","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_downloadurl_v3","channel":"Website","mid":"$id","lang":"","expired_date":"${getExpiryDate()}","platform":"android","oss":"1","group":""}""" + } else { + """{"childmode":"0","app_version":"11.5","module":"TV_downloadurl_v3","channel":"Website","episode":"$episode","expired_date":"${getExpiryDate()}","platform":"android","tid":"$id","oss":"1","uid":"","appid":"$appId","season":"$season","lang":"en","group":""}""" + } + + val linkData = queryApiParsed(query, false) + linkData.data?.list?.forEach { + callback.invoke(it.toExtractorLink() ?: return@forEach) + } + + // Should really run this query for every link :( + val fid = linkData.data?.list?.firstOrNull { it.fid != null }?.fid + + val subtitleQuery = if (type == ResponseTypes.Movies.value) { + """{"childmode":"0","fid":"$fid","uid":"","app_version":"11.5","appid":"$appId","module":"Movie_srt_list_v2","channel":"Website","mid":"$id","lang":"en","expired_date":"${getExpiryDate()}","platform":"android"}""" + } else { + """{"childmode":"0","fid":"$fid","app_version":"11.5","module":"TV_srt_list_v2","channel":"Website","episode":"$episode","expired_date":"${getExpiryDate()}","platform":"android","tid":"$id","uid":"","appid":"$appId","season":"$season","lang":"en"}""" + } + + val subtitles = queryApiParsed(subtitleQuery).data + subtitles?.list?.forEach { subs -> + val sub = subs.subtitles.maxByOrNull { it.support_total ?: 0 } + subtitleCallback.invoke( + SubtitleFile( + sub?.language ?: sub?.lang ?: return@forEach, + sub?.filePath ?: return@forEach + ) + ) + } + } + + suspend fun invokeExternalSource( + mediaId: Int? = null, + type: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit, + ) { + val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode) + val shareKey = app.get("$fourthAPI/index/share_link?id=${mediaId}&type=$type") + .parsedSafe()?.data?.link?.substringAfterLast("/") ?: return + + val headers = mapOf("Accept-Language" to "en") + val shareRes = app.get("$thirdAPI/file/file_share_list?share_key=$shareKey", headers = headers) + .parsedSafe()?.data ?: return + + val fids = if (season == null) { + shareRes.file_list + } else { + val parentId = shareRes.file_list?.find { it.file_name.equals("season $season", true) }?.fid + app.get("$thirdAPI/file/file_share_list?share_key=$shareKey&parent_id=$parentId&page=1", headers = headers) + .parsedSafe()?.data?.file_list?.filter { + it.file_name?.contains("s${seasonSlug}e${episodeSlug}", true) == true + } + } ?: return + + fids.apmapIndexed { index, fileList -> + val player = app.get("$thirdAPI/file/player?fid=${fileList.fid}&share_key=$shareKey").text + val sources = "sources\\s*=\\s*(.*);".toRegex().find(player)?.groupValues?.get(1) + val qualities = "quality_list\\s*=\\s*(.*);".toRegex().find(player)?.groupValues?.get(1) + listOf(sources, qualities).forEach { + AppUtils.tryParseJson>(it)?.forEach org@{ source -> + val format = if (source.type == "video/mp4") ExtractorLinkType.VIDEO else ExtractorLinkType.M3U8 + val label = if (format == ExtractorLinkType.M3U8) "Hls" else "Mp4" + if(!(source.label == "AUTO" || format == ExtractorLinkType.VIDEO)) return@org + callback.invoke( + ExtractorLink( + "External", + "External $label [Server ${index + 1}]", + (source.m3u8_url ?: source.file)?.replace("\\/", "/") ?: return@org, + "", + getIndexQuality(if (format == ExtractorLinkType.M3U8) fileList.file_name else source.label), + type = format, + ) + ) + } + } + } + } + + suspend fun invokeWatchsomuch( + imdbId: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + ) { + val id = imdbId?.removePrefix("tt") + val epsId = app.post( + "$watchSomuchAPI/Watch/ajMovieTorrents.aspx", + data = mapOf( + "index" to "0", + "mid" to "$id", + "wsk" to "30fb68aa-1c71-4b8c-b5d4-4ca9222cfb45", + "lid" to "", + "liu" to "" + ), headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).parsedSafe()?.movie?.torrents?.let { eps -> + if (season == null) { + eps.firstOrNull()?.id + } else { + eps.find { it.episode == episode && it.season == season }?.id + } + } ?: return + + val (seasonSlug, episodeSlug) = getEpisodeSlug( + season, + episode + ) + + val subUrl = if (season == null) { + "$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=" + } else { + "$watchSomuchAPI/Watch/ajMovieSubtitles.aspx?mid=$id&tid=$epsId&part=S${seasonSlug}E${episodeSlug}" + } + + app.get(subUrl) + .parsedSafe()?.subtitles + ?.map { sub -> + subtitleCallback.invoke( + SubtitleFile( + sub.label ?: "", + fixUrl(sub.url ?: return@map null, watchSomuchAPI) + ) + ) + } + + + } + + suspend fun invokeOpenSubs( + imdbId: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + ) { + val slug = if(season == null) { + "movie/$imdbId" + } else { + "series/$imdbId:$season:$episode" + } + app.get("${openSubAPI}/subtitles/$slug.json", timeout = 120L).parsedSafe()?.subtitles?.map { sub -> + subtitleCallback.invoke( + SubtitleFile( + SubtitleHelper.fromThreeLettersToLanguage(sub.lang ?: "") ?: sub.lang + ?: return@map, + sub.url ?: return@map + ) + ) + } + } + + suspend fun invokeVidsrcto( + imdbId: String?, + season: Int?, + episode: Int?, + subtitleCallback: (SubtitleFile) -> Unit, + ) { + val url = if (season == null) { + "$vidsrctoAPI/embed/movie/$imdbId" + } else { + "$vidsrctoAPI/embed/tv/$imdbId/$season/$episode" + } + + val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return + val subtitles = app.get("$vidsrctoAPI/ajax/embed/episode/$mediaId/subtitles").text + AppUtils.tryParseJson>(subtitles)?.map { + subtitleCallback.invoke( + SubtitleFile( + it.label ?: "", + it.file ?: return@map + ) + ) + } + + } + + private fun fixUrl(url: String, domain: String): String { + if (url.startsWith("http")) { + return url + } + if (url.isEmpty()) { + return "" + } + + val startsWithNoHttp = url.startsWith("//") + if (startsWithNoHttp) { + return "https:$url" + } else { + if (url.startsWith('/')) { + return domain + url + } + return "$domain/$url" + } + } + + private fun getIndexQuality(str: String?): Int { + return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull() + ?: Qualities.Unknown.value + } + + private fun getEpisodeSlug( + season: Int? = null, + episode: Int? = null, + ): Pair { + return if (season == null && episode == null) { + "" to "" + } else { + (if (season!! < 10) "0$season" else "$season") to (if (episode!! < 10) "0$episode" else "$episode") + } + } + +} \ No newline at end of file diff --git a/Superstream/src/main/kotlin/com/hexated/Superstream.kt b/Superstream/src/main/kotlin/com/hexated/Superstream.kt new file mode 100644 index 00000000..bd971a67 --- /dev/null +++ b/Superstream/src/main/kotlin/com/hexated/Superstream.kt @@ -0,0 +1,823 @@ +package com.hexated + +import android.util.Base64 +import com.fasterxml.jackson.annotation.JsonProperty +import com.hexated.Extractors.invokeExternalSource +import com.hexated.Extractors.invokeInternalSource +import com.hexated.Extractors.invokeOpenSubs +import com.hexated.Extractors.invokeVidsrcto +import com.hexated.Extractors.invokeWatchsomuch +import com.hexated.Superstream.CipherUtils.getVerify +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.capitalize +import com.lagradost.cloudstream3.APIHolder.unixTime +import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.nicehttp.NiceResponse +import okhttp3.Interceptor +import okhttp3.Response +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import javax.crypto.Cipher +import javax.crypto.Cipher.DECRYPT_MODE +import javax.crypto.Cipher.ENCRYPT_MODE +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import kotlin.math.roundToInt + +open class Superstream : MainAPI() { + private val timeout = 60L + override var name = "SuperStream" + override val hasMainPage = true + override val hasChromecastSupport = true + override val supportedTypes = setOf( + TvType.Movie, + TvType.TvSeries, + TvType.Anime, + TvType.AnimeMovie, + ) + + enum class ResponseTypes(val value: Int) { + Series(2), + Movies(1); + + fun toTvType(): TvType { + return if (this == Series) TvType.TvSeries else TvType.Movie + } + + companion object { + fun getResponseType(value: Int?): ResponseTypes { + return values().firstOrNull { it.value == value } ?: Movies + } + } + } + + override val instantLinkLoading = true + + private val interceptor = UserAgentInterceptor() + + private val headers = mapOf( + "Platform" to "android", + "Accept" to "charset=utf-8", + ) + + private class UserAgentInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + return chain.proceed( + chain.request() + .newBuilder() + .removeHeader("user-agent") + .build() + ) + } + } + + // Random 32 length string + private fun randomToken(): String { + return (0..31).joinToString("") { + (('0'..'9') + ('a'..'f')).random().toString() + } + } + + private val token = randomToken() + + private object CipherUtils { + private const val ALGORITHM = "DESede" + private const val TRANSFORMATION = "DESede/CBC/PKCS5Padding" + fun encrypt(str: String, key: String, iv: String): String? { + return try { + val cipher: Cipher = Cipher.getInstance(TRANSFORMATION) + val bArr = ByteArray(24) + val bytes: ByteArray = key.toByteArray() + var length = if (bytes.size <= 24) bytes.size else 24 + System.arraycopy(bytes, 0, bArr, 0, length) + while (length < 24) { + bArr[length] = 0 + length++ + } + cipher.init( + ENCRYPT_MODE, + SecretKeySpec(bArr, ALGORITHM), + IvParameterSpec(iv.toByteArray()) + ) + + String(Base64.encode(cipher.doFinal(str.toByteArray()), 2), StandardCharsets.UTF_8) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + // Useful for deobfuscation + fun decrypt(str: String, key: String, iv: String): String? { + return try { + val cipher: Cipher = Cipher.getInstance(TRANSFORMATION) + val bArr = ByteArray(24) + val bytes: ByteArray = key.toByteArray() + var length = if (bytes.size <= 24) bytes.size else 24 + System.arraycopy(bytes, 0, bArr, 0, length) + while (length < 24) { + bArr[length] = 0 + length++ + } + cipher.init( + DECRYPT_MODE, + SecretKeySpec(bArr, ALGORITHM), + IvParameterSpec(iv.toByteArray()) + ) + val inputStr = Base64.decode(str.toByteArray(), Base64.DEFAULT) + cipher.doFinal(inputStr).decodeToString() + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + fun md5(str: String): String? { + return MD5Util.md5(str)?.let { HexDump.toHexString(it).lowercase() } + } + + fun getVerify(str: String?, str2: String, str3: String): String? { + if (str != null) { + return md5(md5(str2) + str3 + str) + } + return null + } + } + + private object HexDump { + private val HEX_DIGITS = charArrayOf( + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F' + ) + + @JvmOverloads + fun toHexString(bArr: ByteArray, i: Int = 0, i2: Int = bArr.size): String { + val cArr = CharArray(i2 * 2) + var i3 = 0 + for (i4 in i until i + i2) { + val b = bArr[i4].toInt() + val i5 = i3 + 1 + val cArr2 = HEX_DIGITS + cArr[i3] = cArr2[b ushr 4 and 15] + i3 = i5 + 1 + cArr[i5] = cArr2[b and 15] + } + return String(cArr) + } + } + + private object MD5Util { + fun md5(str: String): ByteArray? { + return md5(str.toByteArray()) + } + + fun md5(bArr: ByteArray?): ByteArray? { + return try { + val digest = MessageDigest.getInstance("MD5") + digest.update(bArr ?: return null) + digest.digest() + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + null + } + } + } + + suspend fun queryApi(query: String, useAlternativeApi: Boolean): NiceResponse { + val encryptedQuery = CipherUtils.encrypt(query, key, iv)!! + val appKeyHash = CipherUtils.md5(appKey)!! + val newBody = + """{"app_key":"$appKeyHash","verify":"${ + getVerify( + encryptedQuery, + appKey, + key + ) + }","encrypt_data":"$encryptedQuery"}""" + val base64Body = String(Base64.encode(newBody.toByteArray(), Base64.DEFAULT)) + + val data = mapOf( + "data" to base64Body, + "appid" to "27", + "platform" to "android", + "version" to appVersionCode, + // Probably best to randomize this + "medium" to "Website&token$token" + ) + + val url = if (useAlternativeApi) secondAPI else firstAPI + return app.post( + url, + headers = headers, + data = data, + timeout = timeout, + interceptor = interceptor + ) + } + + suspend inline fun queryApiParsed( + query: String, + useAlternativeApi: Boolean = true + ): T { + return queryApi(query, useAlternativeApi).parsed() + } + + fun getExpiryDate(): Long { + // Current time + 12 hours + return unixTime + 60 * 60 * 12 + } + + private data class PostJSON( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("poster_2") val poster2: String? = null, + @JsonProperty("box_type") val boxType: Int? = null, + @JsonProperty("imdb_rating") val imdbRating: String? = null, + @JsonProperty("quality_tag") val quality_tag: String? = null, + ) + + private data class ListJSON( + @JsonProperty("code") val code: Int? = null, + @JsonProperty("type") val type: String? = null, + @JsonProperty("name") val name: String? = null, + @JsonProperty("box_type") val boxType: Int? = null, + @JsonProperty("list") val list: ArrayList = arrayListOf(), + ) + + private data class DataJSON( + @JsonProperty("data") val data: ArrayList = arrayListOf() + ) + + // We do not want content scanners to notice this scraping going on so we've hidden all constants + // The source has its origins in China so I added some extra security with banned words + // Mayhaps a tiny bit unethical, but this source is just too good :) + // If you are copying this code please use precautions so they do not change their api. + + // Free Tibet, The Tienanmen Square protests of 1989 + private val iv = base64Decode("d0VpcGhUbiE=") + private val key = base64Decode("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2") + + private val firstAPI = BuildConfig.SUPERSTREAM_FIRST_API + + // Another url because the first one sucks at searching + // This one was revealed to me in a dream + private val secondAPI = BuildConfig.SUPERSTREAM_SECOND_API + + val thirdAPI = BuildConfig.SUPERSTREAM_THIRD_API + val fourthAPI = BuildConfig.SUPERSTREAM_FOURTH_API + + val watchSomuchAPI = "https://watchsomuch.tv" + val openSubAPI = "https://opensubtitles-v3.strem.io" + val vidsrctoAPI = "https://vidsrc.to" + + private val appKey = base64Decode("bW92aWVib3g=") + val appId = base64Decode("Y29tLnRkby5zaG93Ym94") + private val appIdSecond = base64Decode("Y29tLm1vdmllYm94cHJvLmFuZHJvaWQ=") + private val appVersion = "11.5" + private val appVersionCode = "129" + + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 + val data = queryApiParsed( + """{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Home_list_type_v5","channel":"Website","page":"$page","lang":"en","type":"all","pagelimit":"10","expired_date":"${getExpiryDate()}","platform":"android"} + """.trimIndent() + ) + + // Cut off the first row (featured) + val pages = data.data.let { it.subList(minOf(it.size, 1), it.size) } + .mapNotNull { + var name = it.name + if (name.isNullOrEmpty()) name = "Featured" + val postList = it.list.mapNotNull second@{ post -> + val type = if (post.boxType == 1) TvType.Movie else TvType.TvSeries + newMovieSearchResponse( + name = post.title ?: return@second null, + url = LoadData(post.id ?: return@mapNotNull null, post.boxType).toJson(), + type = type, + fix = false + ) { + posterUrl = post.poster ?: post.poster2 + quality = getQualityFromString(post.quality_tag ?: "") + } + } + if (postList.isEmpty()) return@mapNotNull null + HomePageList(name, postList) + } + return HomePageResponse(pages, hasNext = !pages.any { it.list.isEmpty() }) + } + + private data class Data( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("mid") val mid: Int? = null, + @JsonProperty("box_type") val boxType: Int? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("poster_org") val posterOrg: String? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("cats") val cats: String? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("imdb_rating") val imdbRating: String? = null, + @JsonProperty("quality_tag") val qualityTag: String? = null, + ) { + fun toSearchResponse(api: MainAPI): MovieSearchResponse? { + return api.newMovieSearchResponse( + this.title ?: "", + LoadData( + this.id ?: this.mid ?: return null, + this.boxType ?: ResponseTypes.Movies.value + ).toJson(), + ResponseTypes.getResponseType(this.boxType).toTvType(), + false + ) { + posterUrl = this@Data.posterOrg ?: this@Data.poster + year = this@Data.year + quality = getQualityFromString(this@Data.qualityTag?.replace("-", "") ?: "") + } + } + } + + private data class MainDataList( + @JsonProperty("list") val list: ArrayList = arrayListOf() + ) + + private data class MainData( + @JsonProperty("data") val data: MainDataList + ) + + override suspend fun search(query: String): List { + val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 + val apiQuery = + // Originally 8 pagelimit + """{"childmode":"$hideNsfw","app_version":"$appVersion","appid":"$appIdSecond","module":"Search4","channel":"Website","page":"1","lang":"en","type":"all","keyword":"$query","pagelimit":"20","expired_date":"${getExpiryDate()}","platform":"android"}""" + val searchResponse = queryApiParsed(apiQuery, true).data.list.mapNotNull { + it.toSearchResponse(this) + } + return searchResponse + } + + private data class LoadData( + val id: Int, + val type: Int? + ) + + private data class MovieData( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("director") val director: String? = null, + @JsonProperty("writer") val writer: String? = null, + @JsonProperty("actors") val actors: String? = null, + @JsonProperty("runtime") val runtime: Int? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("description") val description: String? = null, + @JsonProperty("cats") val cats: String? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("imdb_id") val imdbId: String? = null, + @JsonProperty("imdb_rating") val imdbRating: String? = null, + @JsonProperty("trailer") val trailer: String? = null, + @JsonProperty("released") val released: String? = null, + @JsonProperty("content_rating") val contentRating: String? = null, + @JsonProperty("tmdb_id") val tmdbId: Int? = null, + @JsonProperty("tomato_meter") val tomatoMeter: Int? = null, + @JsonProperty("poster_org") val posterOrg: String? = null, + @JsonProperty("trailer_url") val trailerUrl: String? = null, + @JsonProperty("imdb_link") val imdbLink: String? = null, + @JsonProperty("box_type") val boxType: Int? = null, + @JsonProperty("recommend") val recommend: List = listOf(), + ) + + private data class MovieDataProp( + @JsonProperty("data") val data: MovieData? = MovieData() + ) + + + private data class SeriesDataProp( + @JsonProperty("code") val code: Int? = null, + @JsonProperty("msg") val msg: String? = null, + @JsonProperty("data") val data: SeriesData? = SeriesData() + ) + + private data class SeriesSeasonProp( + @JsonProperty("code") val code: Int? = null, + @JsonProperty("msg") val msg: String? = null, + @JsonProperty("data") val data: ArrayList? = arrayListOf() + ) +// data class PlayProgress ( +// +// @JsonProperty("over" ) val over : Int? = null, +// @JsonProperty("seconds" ) val seconds : Int? = null, +// @JsonProperty("mp4_id" ) val mp4Id : Int? = null, +// @JsonProperty("last_time" ) val lastTime : Int? = null +// +//) + + private data class SeriesEpisode( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("tid") val tid: Int? = null, + @JsonProperty("mb_id") val mbId: Int? = null, + @JsonProperty("imdb_id") val imdbId: String? = null, + @JsonProperty("imdb_id_status") val imdbIdStatus: Int? = null, + @JsonProperty("srt_status") val srtStatus: Int? = null, + @JsonProperty("season") val season: Int? = null, + @JsonProperty("episode") val episode: Int? = null, + @JsonProperty("state") val state: Int? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("thumbs") val thumbs: String? = null, + @JsonProperty("thumbs_bak") val thumbsBak: String? = null, + @JsonProperty("thumbs_original") val thumbsOriginal: String? = null, + @JsonProperty("poster_imdb") val posterImdb: Int? = null, + @JsonProperty("synopsis") val synopsis: String? = null, + @JsonProperty("runtime") val runtime: Int? = null, + @JsonProperty("view") val view: Int? = null, + @JsonProperty("download") val download: Int? = null, + @JsonProperty("source_file") val sourceFile: Int? = null, + @JsonProperty("code_file") val codeFile: Int? = null, + @JsonProperty("add_time") val addTime: Int? = null, + @JsonProperty("update_time") val updateTime: Int? = null, + @JsonProperty("released") val released: String? = null, + @JsonProperty("released_timestamp") val releasedTimestamp: Long? = null, + @JsonProperty("audio_lang") val audioLang: String? = null, + @JsonProperty("quality_tag") val qualityTag: String? = null, + @JsonProperty("3d") val _3d: Int? = null, + @JsonProperty("remark") val remark: String? = null, + @JsonProperty("pending") val pending: String? = null, + @JsonProperty("imdb_rating") val imdbRating: String? = null, + @JsonProperty("display") val display: Int? = null, + @JsonProperty("sync") val sync: Int? = null, + @JsonProperty("tomato_meter") val tomatoMeter: Int? = null, + @JsonProperty("tomato_meter_count") val tomatoMeterCount: Int? = null, + @JsonProperty("tomato_audience") val tomatoAudience: Int? = null, + @JsonProperty("tomato_audience_count") val tomatoAudienceCount: Int? = null, + @JsonProperty("thumbs_min") val thumbsMin: String? = null, + @JsonProperty("thumbs_org") val thumbsOrg: String? = null, + @JsonProperty("imdb_link") val imdbLink: String? = null, +// @JsonProperty("quality_tags") val qualityTags: ArrayList = arrayListOf(), +// @JsonProperty("play_progress" ) val playProgress : PlayProgress? = PlayProgress() + + ) + + private data class SeriesLanguage( + @JsonProperty("title") val title: String? = null, + @JsonProperty("lang") val lang: String? = null + ) + + private data class SeriesData( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("mb_id") val mbId: Int? = null, + @JsonProperty("title") val title: String? = null, + @JsonProperty("display") val display: Int? = null, + @JsonProperty("state") val state: Int? = null, + @JsonProperty("vip_only") val vipOnly: Int? = null, + @JsonProperty("code_file") val codeFile: Int? = null, + @JsonProperty("director") val director: String? = null, + @JsonProperty("writer") val writer: String? = null, + @JsonProperty("actors") val actors: String? = null, + @JsonProperty("add_time") val addTime: Int? = null, + @JsonProperty("poster") val poster: String? = null, + @JsonProperty("poster_imdb") val posterImdb: Int? = null, + @JsonProperty("banner_mini") val bannerMini: String? = null, + @JsonProperty("description") val description: String? = null, + @JsonProperty("imdb_id") val imdbId: String? = null, + @JsonProperty("cats") val cats: String? = null, + @JsonProperty("year") val year: Int? = null, + @JsonProperty("collect") val collect: Int? = null, + @JsonProperty("view") val view: Int? = null, + @JsonProperty("download") val download: Int? = null, + @JsonProperty("update_time") val updateTime: String? = null, + @JsonProperty("released") val released: String? = null, + @JsonProperty("released_timestamp") val releasedTimestamp: Int? = null, + @JsonProperty("episode_released") val episodeReleased: String? = null, + @JsonProperty("episode_released_timestamp") val episodeReleasedTimestamp: Int? = null, + @JsonProperty("max_season") val maxSeason: Int? = null, + @JsonProperty("max_episode") val maxEpisode: Int? = null, + @JsonProperty("remark") val remark: String? = null, + @JsonProperty("imdb_rating") val imdbRating: String? = null, + @JsonProperty("content_rating") val contentRating: String? = null, + @JsonProperty("tmdb_id") val tmdbId: Int? = null, + @JsonProperty("tomato_url") val tomatoUrl: String? = null, + @JsonProperty("tomato_meter") val tomatoMeter: Int? = null, + @JsonProperty("tomato_meter_count") val tomatoMeterCount: Int? = null, + @JsonProperty("tomato_meter_state") val tomatoMeterState: String? = null, + @JsonProperty("reelgood_url") val reelgoodUrl: String? = null, + @JsonProperty("audience_score") val audienceScore: Int? = null, + @JsonProperty("audience_score_count") val audienceScoreCount: Int? = null, + @JsonProperty("no_tomato_url") val noTomatoUrl: Int? = null, + @JsonProperty("order_year") val orderYear: Int? = null, + @JsonProperty("episodate_id") val episodateId: String? = null, + @JsonProperty("weights_day") val weightsDay: Double? = null, + @JsonProperty("poster_min") val posterMin: String? = null, + @JsonProperty("poster_org") val posterOrg: String? = null, + @JsonProperty("banner_mini_min") val bannerMiniMin: String? = null, + @JsonProperty("banner_mini_org") val bannerMiniOrg: String? = null, + @JsonProperty("trailer_url") val trailerUrl: String? = null, + @JsonProperty("years") val years: ArrayList = arrayListOf(), + @JsonProperty("season") val season: ArrayList = arrayListOf(), + @JsonProperty("history") val history: ArrayList = arrayListOf(), + @JsonProperty("imdb_link") val imdbLink: String? = null, + @JsonProperty("episode") val episode: ArrayList = arrayListOf(), +// @JsonProperty("is_collect") val isCollect: Int? = null, + @JsonProperty("language") val language: ArrayList = arrayListOf(), + @JsonProperty("box_type") val boxType: Int? = null, + @JsonProperty("year_year") val yearYear: String? = null, + @JsonProperty("season_episode") val seasonEpisode: String? = null + ) + + + override suspend fun load(url: String): LoadResponse { + val loadData = parseJson(url) + // val module = if(type === "TvType.Movie") "Movie_detail" else "*tv series module*" + + val isMovie = loadData.type == ResponseTypes.Movies.value + val hideNsfw = if (settingsForProvider.enableAdult) 0 else 1 + if (isMovie) { // 1 = Movie + val apiQuery = + """{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"Movie_detail","channel":"Website","mid":"${loadData.id}","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","oss":"","group":""}""" + val data = (queryApiParsed(apiQuery)).data + ?: throw RuntimeException("API error") + + return newMovieLoadResponse( + data.title ?: "", + url, + TvType.Movie, + LinkData( + data.id ?: throw RuntimeException("No movie ID"), + ResponseTypes.Movies.value, + null, + null, + data.id, + data.imdbId + ), + ) { + this.recommendations = + data.recommend.mapNotNull { it.toSearchResponse(this@Superstream) } + this.posterUrl = data.posterOrg ?: data.poster + this.year = data.year + this.plot = data.description + this.tags = data.cats?.split(",")?.map { it.capitalize() } + this.rating = data.imdbRating?.split("/")?.get(0)?.toIntOrNull() + addTrailer(data.trailerUrl) + this.addImdbId(data.imdbId) + } + } else { // 2 Series + val apiQuery = + """{"childmode":"$hideNsfw","uid":"","app_version":"$appVersion","appid":"$appIdSecond","module":"TV_detail_1","display_all":"1","channel":"Website","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" + val data = (queryApiParsed(apiQuery)).data + ?: throw RuntimeException("API error") + + val episodes = data.season.mapNotNull { + val seasonQuery = + """{"childmode":"$hideNsfw","app_version":"$appVersion","year":"0","appid":"$appIdSecond","module":"TV_episode","display_all":"1","channel":"Website","season":"$it","lang":"en","expired_date":"${getExpiryDate()}","platform":"android","tid":"${loadData.id}"}""" + (queryApiParsed(seasonQuery)).data + }.flatten() + + return newTvSeriesLoadResponse( + data.title ?: "", + url, + TvType.TvSeries, + episodes.mapNotNull { + Episode( + LinkData( + it.tid ?: it.id ?: return@mapNotNull null, + ResponseTypes.Series.value, + it.season, + it.episode, + data.id, + data.imdbId + ).toJson(), + it.title, + it.season, + it.episode, + it.thumbs ?: it.thumbsBak ?: it.thumbsMin ?: it.thumbsOriginal + ?: it.thumbsOrg, + it.imdbRating?.toDoubleOrNull()?.times(10)?.roundToInt(), + it.synopsis, + it.releasedTimestamp + ) + } + ) { + this.year = data.year + this.plot = data.description + this.posterUrl = data.posterOrg ?: data.poster + this.rating = data.imdbRating?.split("/")?.get(0)?.toIntOrNull() + this.tags = data.cats?.split(",")?.map { it.capitalize() } + this.addImdbId(data.imdbId) + } + } + } + + private data class LinkData( + val id: Int, + val type: Int, + val season: Int?, + val episode: Int?, + val mediaId: Int?, + val imdbId: String?, + ) + + data class LinkDataProp( + @JsonProperty("code") val code: Int? = null, + @JsonProperty("msg") val msg: String? = null, + @JsonProperty("data") val data: ParsedLinkData? = ParsedLinkData() + ) + + data class LinkList( + @JsonProperty("path") val path: String? = null, + @JsonProperty("quality") val quality: String? = null, + @JsonProperty("real_quality") val realQuality: String? = null, + @JsonProperty("format") val format: String? = null, + @JsonProperty("size") val size: String? = null, + @JsonProperty("size_bytes") val sizeBytes: Long? = null, + @JsonProperty("count") val count: Int? = null, + @JsonProperty("dateline") val dateline: Long? = null, + @JsonProperty("fid") val fid: Int? = null, + @JsonProperty("mmfid") val mmfid: Int? = null, + @JsonProperty("h265") val h265: Int? = null, + @JsonProperty("hdr") val hdr: Int? = null, + @JsonProperty("filename") val filename: String? = null, + @JsonProperty("original") val original: Int? = null, + @JsonProperty("colorbit") val colorbit: Int? = null, + @JsonProperty("success") val success: Int? = null, + @JsonProperty("timeout") val timeout: Int? = null, + @JsonProperty("vip_link") val vipLink: Int? = null, + @JsonProperty("fps") val fps: Int? = null, + @JsonProperty("bitstream") val bitstream: String? = null, + @JsonProperty("width") val width: Int? = null, + @JsonProperty("height") val height: Int? = null + ) + + data class ParsedLinkData( + @JsonProperty("seconds") val seconds: Int? = null, + @JsonProperty("quality") val quality: ArrayList = arrayListOf(), + @JsonProperty("list") val list: ArrayList = arrayListOf() + ) + + data class SubtitleDataProp( + @JsonProperty("code") val code: Int? = null, + @JsonProperty("msg") val msg: String? = null, + @JsonProperty("data") val data: PrivateSubtitleData? = PrivateSubtitleData() + ) + + data class Subtitles( + @JsonProperty("sid") val sid: Int? = null, + @JsonProperty("mid") val mid: String? = null, + @JsonProperty("file_path") val filePath: String? = null, + @JsonProperty("lang") val lang: String? = null, + @JsonProperty("language") val language: String? = null, + @JsonProperty("delay") val delay: Int? = null, + @JsonProperty("point") val point: String? = null, + @JsonProperty("order") val order: Int? = null, + @JsonProperty("support_total") val support_total: Int? = null, + @JsonProperty("admin_order") val adminOrder: Int? = null, + @JsonProperty("myselect") val myselect: Int? = null, + @JsonProperty("add_time") val addTime: Long? = null, + @JsonProperty("count") val count: Int? = null + ) + + data class SubtitleList( + @JsonProperty("language") val language: String? = null, + @JsonProperty("subtitles") val subtitles: ArrayList = arrayListOf() + ) + + data class PrivateSubtitleData( + @JsonProperty("select") val select: ArrayList = arrayListOf(), + @JsonProperty("list") val list: ArrayList = arrayListOf() + ) + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val parsed = parseJson(data) + + argamap( + { + invokeVidsrcto( + parsed.imdbId, + parsed.season, + parsed.episode, + subtitleCallback + ) + }, + { + invokeExternalSource( + parsed.mediaId, + parsed.type, + parsed.season, + parsed.episode, + callback + ) + }, + { + invokeInternalSource( + parsed.id, + parsed.type, + parsed.season, + parsed.episode, + subtitleCallback, + callback + ) + }, + { + invokeOpenSubs( + parsed.imdbId, + parsed.season, + parsed.episode, + subtitleCallback + ) + }, + { + invokeWatchsomuch( + parsed.imdbId, + parsed.season, + parsed.episode, + subtitleCallback + ) + } + ) + + return true + } + + data class ExternalResponse( + @JsonProperty("data") val data: Data? = null, + ) { + data class Data( + @JsonProperty("link") val link: String? = null, + @JsonProperty("file_list") val file_list: ArrayList? = arrayListOf(), + ) { + data class FileList( + @JsonProperty("fid") val fid: Long? = null, + @JsonProperty("file_name") val file_name: String? = null, + @JsonProperty("oss_fid") val oss_fid: Long? = null, + ) + } + } + + data class ExternalSources( + @JsonProperty("m3u8_url") val m3u8_url: String? = null, + @JsonProperty("file") val file: String? = null, + @JsonProperty("label") val label: String? = null, + @JsonProperty("type") val type: String? = null, + ) + + data class WatchsomuchTorrents( + @JsonProperty("id") val id: Int? = null, + @JsonProperty("movieId") val movieId: Int? = null, + @JsonProperty("season") val season: Int? = null, + @JsonProperty("episode") val episode: Int? = null, + ) + + data class WatchsomuchMovies( + @JsonProperty("torrents") val torrents: ArrayList? = arrayListOf(), + ) + + data class WatchsomuchResponses( + @JsonProperty("movie") val movie: WatchsomuchMovies? = null, + ) + + data class WatchsomuchSubtitles( + @JsonProperty("url") val url: String? = null, + @JsonProperty("label") val label: String? = null, + ) + + data class WatchsomuchSubResponses( + @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), + ) + + data class OsSubtitles( + @JsonProperty("url") val url: String? = null, + @JsonProperty("lang") val lang: String? = null, + ) + + data class OsResult( + @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), + ) + + data class VidsrcSubtitles( + @JsonProperty("label") val label: String? = null, + @JsonProperty("file") val file: String? = null, + ) + +} + diff --git a/Superstream/src/main/kotlin/com/hexated/SuperstreamPlugin.kt b/Superstream/src/main/kotlin/com/hexated/SuperstreamPlugin.kt new file mode 100644 index 00000000..099210fe --- /dev/null +++ b/Superstream/src/main/kotlin/com/hexated/SuperstreamPlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class SuperstreamPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(Superstream()) + } +} \ No newline at end of file diff --git a/TimefourTv/build.gradle.kts b/TimefourTv/build.gradle.kts index 7a9bafec..a28f5bbf 100644 --- a/TimefourTv/build.gradle.kts +++ b/TimefourTv/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 20 +version = 23 cloudstream { @@ -21,5 +21,5 @@ cloudstream { "Live", ) - iconUrl = "https://www.google.com/s2/favicons?domain=time4tv.stream&sz=%size%" + iconUrl = "https://cdn.discordapp.com/attachments/1109266606292488297/1193088870212976640/Untitled.jpg" } diff --git a/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt b/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt index e301ae02..53eef45d 100644 --- a/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt +++ b/TimefourTv/src/main/kotlin/com/hexated/TimefourTv.kt @@ -1,193 +1,109 @@ package com.hexated -import com.hexated.TimefourTvExtractor.getBaseUrl -import com.hexated.TimefourTvExtractor.getLink import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper -import org.jsoup.Jsoup +import com.lagradost.cloudstream3.utils.Qualities import org.jsoup.nodes.Element +import java.net.URI -open class TimefourTv : MainAPI() { - final override var mainUrl = "https://time4tv.stream" - var daddyUrl = "https://daddylivehd.com" +class TimefourTv : MainAPI() { + override var mainUrl = "https://dlhd.sx" override var name = "Time4tv" override val hasDownloadSupport = false override val hasMainPage = true - private val time4tvPoster = "$mainUrl/images/logo.png" override val supportedTypes = setOf( TvType.Live ) - override val mainPage = mainPageOf( -// "$mainUrl/tv-channels" to "All Channels", -// "$mainUrl/usa-channels" to "USA Channels", -// "$mainUrl/uk-channels" to "UK Channels", -// "$mainUrl/sports-channels" to "Sport Channels", -// "$mainUrl/live-sports-streams" to "Live Sport Channels", -// "$mainUrl/news-channels" to "News Channels", -// "$mainUrl/schedule.php" to "Schedule", - "$daddyUrl/24-7-channels.php" to "DaddyHD Channels" - ) + private val homePoster = + "https://cdn.discordapp.com/attachments/1109266606292488297/1193060449193840681/Screenshot_2024-01-06_at_12-14-16_Logo_Maker_Used_By_2.3_Million_Startups.png" + private val detailPoster = + "https://cdn.discordapp.com/attachments/1109266606292488297/1193060448929595454/Screenshot_2024-01-06_at_12-13-02_Logo_Maker_Used_By_2.3_Million_Startups.png" - private fun fixDetailLink(link: String?): String? { - if (link == null) return null - return if (link.startsWith("/")) "$daddyUrl$link" else link - } + override val mainPage = mainPageOf( + "$mainUrl/24-7-channels.php" to "24/7 Channels", + "$mainUrl/schedule/schedule-generated.json" to "Schedule Channels" + ) override suspend fun getMainPage( page: Int, request: MainPageRequest ): HomePageResponse { val items = mutableListOf() - val nonPaged = request.name != "All Channels" && page <= 1 - if (nonPaged) { - val res = app.get("${request.data}.php").document - val home = res.select("div.tab-content ul li").mapNotNull { - it.toSearchResult() + if (request.name == "24/7 Channels") { + val req = app.get(request.data) + mainUrl = getBaseUrl(req.url) + val res = req.document + val channels = res.select("div.grid-container div.grid-item").mapNotNull { + it.toSearchResponse() } - if (home.isNotEmpty()) items.add(HomePageList(request.name, home, true)) - } - if (request.name == "All Channels") { - val res = if (page == 1) { - app.get("${request.data}.php").document - } else { - app.get("${request.data}${page.minus(1)}.php").document + if (channels.isNotEmpty()) items.add(HomePageList(request.name, channels, true)) + } else { + val res = app.get(request.data).parsedSafe>>>() + res?.forEach { tag -> + val header = tag.key + val channels = tag.value.mapNotNull { + LiveSearchResponse( + it.key, + Item(it.key, items = it.value.toJson()).toJson(), + this@TimefourTv.name, + TvType.Live, + posterUrl = homePoster, + ) + } + if (channels.isNotEmpty()) items.add(HomePageList(header, channels, true)) } - val home = res.select("div.tab-content ul li").mapNotNull { - it.toSearchResult() - } - if (home.isNotEmpty()) items.add(HomePageList(request.name, home, true)) } - if (nonPaged && request.name == "DaddyHD Channels") { - val res = app.get(request.data).document - val channelDaddy = res.select("div.grid-container div.grid-item").mapNotNull { - it.toSearchDaddy() - } - if (channelDaddy.isNotEmpty()) items.add(HomePageList(request.name, channelDaddy, true)) - } - - if (nonPaged && request.name == "Schedule") { - val res = app.get(request.data).document - val schedule = res.select("div.search_p h1,div.search_p h2").mapNotNull { - it.toSearchSchedule() - } - if (schedule.isNotEmpty()) items.add(HomePageList(request.name, schedule, true)) - } - - return newHomePageResponse(items) + return newHomePageResponse(items, false) } - private fun Element.toSearchDaddy(): LiveSearchResponse? { + private fun Element.toSearchResponse(): LiveSearchResponse { + val title = this.select("strong").text() + val href = fixUrl(this.select("a").attr("href")) return LiveSearchResponse( - this.select("strong").text() ?: return null, - fixDetailLink(this.select("a").attr("href")) ?: return null, + title, + Item(title, href).toJson(), this@TimefourTv.name, TvType.Live, - posterUrl = time4tvPoster + posterUrl = homePoster, ) } - private fun Element.toSearchSchedule(): LiveSearchResponse? { - return LiveSearchResponse( - this.text() ?: return null, - this.text(), - this@TimefourTv.name, - TvType.Live, - posterUrl = time4tvPoster - ) - } - - private fun Element.toSearchResult(): LiveSearchResponse? { - return LiveSearchResponse( - this.selectFirst("div.channelName")?.text() ?: return null, - fixUrl(this.selectFirst("a")!!.attr("href")), - this@TimefourTv.name, - TvType.Live, - fixUrlNull(this.selectFirst("img")?.attr("src")), - ) - - } - override suspend fun search(query: String): List { - val document = app.get("$daddyUrl/24-7-channels.php").document + val document = app.get("$mainUrl/24-7-channels.php").document return document.select("div.grid-container div.grid-item:contains($query)").mapNotNull { - it.toSearchDaddy() + it.toSearchResponse() } } - private suspend fun loadSchedule(url: String): LoadResponse { - val name = url.removePrefix("$mainUrl/") - val doc = app.get("$mainUrl/schedule.php").document - val episode = mutableListOf() - doc.selectFirst("div.search_p h2:contains($name)")?.nextElementSiblings()?.toString() - ?.substringBefore("")?.map { - val desc = it.substringBefore(""), "").replace("

", "") - Jsoup.parse(it).select("span").map { ele -> - val title = ele.select("a").text() - val href = ele.select("a").attr("href") - episode.add( - Episode( - href, - title, - description = desc, - posterUrl = time4tvPoster - ) - ) - } + + override suspend fun load(url: String): LoadResponse { + val data = AppUtils.parseJson(url) + val episodes = if (data.items.isNullOrEmpty()) { + listOf(Episode(arrayListOf(Channels(data.title, data.url)).toJson())) + } else { + val items = AppUtils.parseJson>(data.items) + items.mapNotNull { eps -> + Episode( + data = eps.channels?.toJson() ?: return@mapNotNull null, + name = "${eps.event} • ${eps.time}", + description = eps.channels.map { it.channel_name }.joinToString(" • "), + posterUrl = detailPoster, + ) } - - return newTvSeriesLoadResponse(name, url, TvType.TvSeries, episode) { } - } - - override suspend fun load(url: String): LoadResponse? { - - val res = app.get(url) - daddyUrl = getBaseUrl(res.url) - if (!res.isSuccessful) return loadSchedule(url) - - val document = res.document - val title = - document.selectFirst("div.channelHeading h1")?.text() ?: document.selectFirst("title") - ?.text()?.substringBefore("HD")?.trim() ?: return null - val poster = - fixUrlNull(document.selectFirst("meta[property=\"og:image\"]")?.attr("content")) ?: time4tvPoster - val description = document.selectFirst("div.tvText")?.text() - ?: document.selectFirst("meta[name=description]")?.attr("content") ?: return null - val episodes = document.selectFirst("div.playit")?.attr("onclick")?.substringAfter("open('") - ?.substringBefore("',")?.let { link -> - val doc = app.get(link).document.selectFirst("div.tv_palyer iframe")?.attr("src") - ?.let { iframe -> - app.get(fixUrl(iframe), referer = link).document - } - if (doc?.select("div.stream_button").isNullOrEmpty()) { - doc?.select("iframe")?.mapIndexed { eps, ele -> - Episode( - fixUrl(ele.attr("src")), - "Server ${eps.plus(1)}" - ) - } - } else { - doc?.select("div.stream_button a")?.map { - Episode( - fixUrl(it.attr("href")), - it.text() - ) - } - } - } ?: listOf( - newEpisode( - document.selectFirst("div#content iframe#thatframe")?.attr("src") ?: return null - ) { - this.name = title - } - ) ?: throw ErrorLoadingException("Refresh page") - return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { - this.posterUrl = poster - this.plot = description + return newTvSeriesLoadResponse( + data.title ?: "", + url, + TvType.TvSeries, + episodes = episodes + ) { + posterUrl = homePoster } + } override suspend fun loadLinks( @@ -197,24 +113,75 @@ open class TimefourTv : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - val link = when { - data.contains("/channel") -> app.get(data).document.selectFirst("div.tv_palyer iframe") - ?.attr("src") - data.startsWith(mainUrl) -> app.get( - data, - allowRedirects = false - ).document.selectFirst("iframe")?.attr("src") - else -> data - } ?: throw ErrorLoadingException() - getLink(fixUrl(link))?.let { m3uLink -> - val url = app.get(m3uLink, referer = "$mainServer/") - M3u8Helper.generateM3u8( - this.name, - url.url, - "$mainServer/", - ).forEach(callback) + val json = AppUtils.parseJson>(data) + + json.apmap { + val iframe = app.get( + fixChannelUrl( + it.channel_id ?: return@apmap + ) + ).document.selectFirst("iframe#thatframe")?.attr("src") + ?: throw ErrorLoadingException("No Iframe Found") + val host = getBaseUrl(iframe) + val video = extractVideo(iframe) + + callback.invoke( + ExtractorLink( + this.name, + it.channel_name ?: return@apmap, + video ?: return@apmap, + "$host/", + Qualities.Unknown.value, + isM3u8 = true, + ) + ) } + return true } + private suspend fun extractVideo(url: String): String? { + val res = app.get(url, referer = mainUrl) + return Regex("""source:['"](\S+.m3u8)['"],""").find(res.text)?.groupValues?.getOrNull( + 1 + ) ?: run { + val scriptData = + res.document.selectFirst("div#player")?.nextElementSibling()?.data() + ?.substringAfterLast("return(")?.substringBefore(".join") + scriptData?.removeSurrounding("[", "]")?.replace("\"", "")?.split(",") + ?.joinToString("") + } + } + + private fun fixChannelUrl(url: String): String { + return if (url.startsWith(mainUrl)) { + url + } else { + "$mainUrl/stream/stream-$url.php" + } + } + + private fun getBaseUrl(url: String): String { + return URI(url).let { + "${it.scheme}://${it.host}" + } + } + + data class Item( + val title: String? = null, + val url: String? = null, + val items: String? = null, + ) + + data class Items( + val time: String? = null, + val event: String? = null, + val channels: ArrayList? = arrayListOf(), + ) + + data class Channels( + val channel_name: String? = null, + val channel_id: String? = null, + ) + } \ No newline at end of file diff --git a/TimefourTv/src/main/kotlin/com/hexated/TimefourTvExtractor.kt b/TimefourTv/src/main/kotlin/com/hexated/TimefourTvExtractor.kt deleted file mode 100644 index 60c415cb..00000000 --- a/TimefourTv/src/main/kotlin/com/hexated/TimefourTvExtractor.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.hexated - -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.fixUrl -import com.lagradost.cloudstream3.utils.getAndUnpack -import com.lagradost.nicehttp.NiceResponse -import java.net.URI - -var mainServer: String? = null - -object TimefourTvExtractor : TimefourTv() { - - fun getBaseUrl(url: String): String { - return URI(url).let { - "${it.scheme}://${it.host}" - } - } - - private suspend fun getSportLink(url: String): String? { - val iframe = app.get(url, referer = "$mainUrl/").document.select("iframe").attr("src") - .let { fixUrl(it) } - val ref = getBaseUrl(url) - val data = app.get(iframe, referer = ref).document.select("script") - .find { it.data().contains("eval(function(p,a,c,k,e,d)") }?.data() - .let { getAndUnpack(it.toString()) } - - mainServer = getBaseUrl(iframe) - - return Regex("var\\ssrc=['|\"](.*?.m3u8.*?)['|\"];").find(data)?.groupValues?.get(1) - - } - - private suspend fun getCricfreeLink(url: String, ref: String): String? { - val doc = app.get(url, referer = ref).document - val iframe = doc.select("iframe").attr("src") - val channel = iframe.split("/").last().removeSuffix(".php") - val refTwo = getBaseUrl(url) - - val docTwo = - app.get(fixUrl(iframe), referer = refTwo).document.selectFirst("script[src*=embed.]") - ?.attr("src") - val refThree = getBaseUrl(iframe) - val linkTwo = fixUrl("${docTwo?.replace(".js", ".php")}?player=desktop&live=$channel") - - val docThree = app.get( - linkTwo, - referer = "$refThree/", - ) - mainServer = getBaseUrl(linkTwo) - - val scriptData = docThree.document.selectFirst("div#player")?.nextElementSibling()?.data() - ?.substringAfterLast("return(")?.substringBefore(".join") - val link = scriptData?.removeSurrounding("[", "]")?.replace("\"", "")?.split(",") - ?.joinToString("") ?: return null - return app.get(link, referer = "$mainUrl/").url - } - - private suspend fun getFootyhunter(url: String, ref: String): String? { - val doc = app.get(url, referer = ref).document - val iframe = doc.selectFirst("iframe")?.attr("src") ?: return null - val referer = getBaseUrl(url) - val docTwo = app.get(fixUrl(iframe), referer = "$referer/").text - mainServer = getBaseUrl(iframe) - val link = Regex("""source:['|"](\S+.m3u8)['|"],""").find(docTwo)?.groupValues?.getOrNull(1) - ?: return null - return app.get(link, referer = "$mainUrl/").url - } - - suspend fun getLink(url: String): String? { - - if (url.contains("sportzonline") || url.contains("sportsonline")) { - return getSportLink(url) - } - - val daddyHost = daddyUrl.getHost() - - if(url.contains(daddyHost, true)) { - mainServer = getBaseUrl(url) - return getFinalLink(app.get(url, referer = daddyUrl)) - } - - val (channel, iframe) = if (url.contains("width=") || url.contains("/link")) { - val doc = app.get(url, referer = "$mainUrl/").document - val tempIframe = doc.selectFirst("iframe")?.attr("src") ?: return null - - if (tempIframe.contains("cricfree")) { - return getCricfreeLink(tempIframe, getBaseUrl(url)) - } - if (tempIframe.contains("footyhunter")) { - return getFootyhunter(tempIframe, getBaseUrl(url)) - } - - val doctwo = app.get(fixUrl(tempIframe), referer = url).text - listOf( - tempIframe.split("?").last().removePrefix("id=").replace(".php", ""), - doctwo.substringAfterLast("