diff --git a/DubokuProvider/build.gradle.kts b/DubokuProvider/build.gradle.kts index c5826742..75e75012 100644 --- a/DubokuProvider/build.gradle.kts +++ b/DubokuProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 1 +version = 2 cloudstream { diff --git a/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt b/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt index 1262fb5d..b0919422 100644 --- a/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt +++ b/DubokuProvider/src/main/kotlin/com/hexated/DubokuProvider.kt @@ -3,10 +3,10 @@ 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" @@ -110,21 +110,29 @@ class DubokuProvider : MainAPI() { if (script.data().contains("var player_data={")) { val dataJson = script.data().substringAfter("var player_data={").substringBefore("}") - tryParseJson("{$dataJson}")?.let { source -> - M3u8Helper.generateM3u8( + val source = tryParseJson("{$dataJson}") + callback.invoke( + ExtractorLink( this.name, - source.url ?: return@map, - referer = "https://w.duboku.io/", - headers = mapOf("Origin" to "https://w.duboku.io") - ).forEach(callback) - } + this.name, + decode(base64Decode(source?.url ?: return@map)), + "https://w.duboku.io/", + Qualities.Unknown.value, + INFER_TYPE, + headers = mapOf( + "Accept-Language" to "en-US,en;q=0.5", + "Origin" to "https://w.duboku.io" + ), + ) + ) } } - return true } + private fun decode(input: String): String = URLDecoder.decode(input, "utf-8") + data class Sources( @JsonProperty("url") val url: String?, ) diff --git a/Kinoger/build.gradle.kts b/Kinoger/build.gradle.kts index 68c19e49..f7decb7b 100644 --- a/Kinoger/build.gradle.kts +++ b/Kinoger/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 2 +version = 3 cloudstream { diff --git a/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt b/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt index 30e78a2a..d53d819f 100644 --- a/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt +++ b/Kinoger/src/main/kotlin/com/hexated/Kinoger.kt @@ -2,6 +2,7 @@ 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 @@ -11,7 +12,7 @@ import org.jsoup.nodes.Element class Kinoger : MainAPI() { override var name = "Kinoger" - override var mainUrl = "https://kinoger.com" + override var mainUrl = "https://kinoger.to" override var lang = "de" override val hasMainPage = true override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) diff --git a/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt b/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt index 39c494ab..98216d4d 100644 --- a/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt +++ b/Raveeflix/src/main/kotlin/com/hexated/Raveeflix.kt @@ -4,7 +4,10 @@ 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" @@ -21,6 +24,7 @@ class Raveeflix : MainAPI() { override val mainPage = mainPageOf( "categories/trending" to "Trending", + "movies" to "Movies", "tv" to "Tv-Shows", "drakor" to "Drakor", "categories/anime" to "Anime", @@ -45,34 +49,41 @@ class Raveeflix : MainAPI() { val href = fixUrl(this.attr("href")) val posterUrl = this.selectFirst("div.thumbnail_card")?.attr("style")?.getPoster() - return newMovieSearchResponse(title, href, TvType.Movie) { + return newMovieSearchResponse(title, Media(href, posterUrl).toJson(), TvType.Movie) { this.posterUrl = posterUrl } } override suspend fun search(query: String): List? { - val res = app.get("$mainUrl/index.json").text.let { AppUtils.tryParseJson>(it) } + 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 + ) == true && !it.section.equals("Categories", true) && !it.section.equals( + "Tags", + true + ) && it.permalink?.contains("/episode") == false }?.mapNotNull { newMovieSearchResponse( it.title ?: return@mapNotNull null, - fixUrl( - it.permalink?.substringBefore("episode")?.substringBefore("season") - ?: return@mapNotNull null, - ), + Media( + fixUrl( + it.permalink?.substringBefore("episode")?.substringBefore("season") + ?: return@mapNotNull null + ) + ).toJson(), TvType.Movie, ) } } override suspend fun load(url: String): LoadResponse { - val document = app.get(url).document + val media = parseJson(url) + val document = app.get(media.url).document val title = document.selectFirst("h1.text-4xl")?.text() ?: "No Title" - val poster = document.selectFirst("div.thumbnail_card, div.w-full.thumbnail_card_related") + 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 @@ -103,42 +114,24 @@ class Raveeflix : MainAPI() { document.select("section.w-full a.min-w-full").mapNotNull { it.toSearchResult() } return if (type == TvType.TvSeries) { - val section = document.select("div.relative > section.w-full a.min-w-full") + 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 -> - val season = ss.selectFirst("div.text-xl")?.text()?.filter { it.isDigit() } + val episodes = if (hasMultipleSeason) { + section.apmap { ss -> + fetchEpisodesFromPages( + ss.attr("href"), + 5, + sectionSelector, + true, + ss.selectFirst("div.text-xl")?.text()?.filter { it.isDigit() } ?.toIntOrNull() - app.get(fixUrl(ss.attr("href"))).document.select("div.relative > section.w-full a.min-w-full") - .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 - ) - } - }.flatten() - } else { - section.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, - ) - } - } - newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.reversed()) { + ) + }.toMutableList().flatten() + } else { + fetchEpisodesFromPages(media.url, 5, sectionSelector, false) + } + newTvSeriesLoadResponse(title, media.url, TvType.TvSeries, episodes.reversed()) { this.posterUrl = poster this.year = year this.seasonNames @@ -149,7 +142,7 @@ class Raveeflix : MainAPI() { this.recommendations = recommendations } } else { - newMovieLoadResponse(title, url, TvType.Movie, url) { + newMovieLoadResponse(title, media.url, TvType.Movie, media.url) { this.posterUrl = poster this.year = year this.plot = description @@ -183,6 +176,39 @@ class Raveeflix : MainAPI() { 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("(") @@ -190,6 +216,8 @@ class Raveeflix : MainAPI() { ) } + 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,