|
| 1 | +package com.dutamovie |
| 2 | + |
| 3 | +import com.lagradost.cloudstream3.* |
| 4 | +import com.lagradost.cloudstream3.LoadResponse.Companion.addActors |
| 5 | +import com.lagradost.cloudstream3.LoadResponse.Companion.addScore |
| 6 | +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer |
| 7 | +import com.lagradost.cloudstream3.utils.* |
| 8 | +import org.jsoup.nodes.Element |
| 9 | +import java.net.URI |
| 10 | + |
| 11 | +class DutamovieProvider : MainAPI() { |
| 12 | + override var mainUrl = "https://offshorebankservices.com" |
| 13 | + private var directUrl: String? = null |
| 14 | + override var name = "Dutamovie" |
| 15 | + override val hasMainPage = true |
| 16 | + override var lang = "id" |
| 17 | + override val supportedTypes = setOf( |
| 18 | + TvType.Movie, |
| 19 | + TvType.TvSeries, |
| 20 | + TvType.Anime, |
| 21 | + TvType.AsianDrama |
| 22 | + ) |
| 23 | + |
| 24 | + override val mainPage = mainPageOf( |
| 25 | + "category/box-office/page/%d/" to "Box Office", |
| 26 | + "category/serial-tv/page/%d/" to "TV Series", |
| 27 | + "action/page/%d/" to "Action", |
| 28 | + "adventure/page/%d/" to "Adventure", |
| 29 | + "animation/page/%d/" to "Animation", |
| 30 | + "comedy/page/%d/" to "Comedy", |
| 31 | + "crime/page/%d/" to "Crime", |
| 32 | + "drama/page/%d/" to "Drama", |
| 33 | + "fantasy/page/%d/" to "Fantasy", |
| 34 | + "horror/page/%d/" to "Horror", |
| 35 | + "mystery/page/%d/" to "Mystery", |
| 36 | + "romance/page/%d/" to "Romance", |
| 37 | + "science-fiction/page/%d/" to "Sci-Fi", |
| 38 | + "thriller/page/%d/" to "Thriller", |
| 39 | + "country/china/page/%d/" to "China", |
| 40 | + "country/indonesia/page/%d/" to "Indonesia", |
| 41 | + "country/korea/page/%d/" to "Korea", |
| 42 | + "country/philippines/page/%d/" to "Philippines", |
| 43 | + "country/thailand/page/%d/" to "Thailand" |
| 44 | + ) |
| 45 | + |
| 46 | + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { |
| 47 | + val document = app.get("$mainUrl/${request.data.format(page)}").document |
| 48 | + val items = document.select("article.item").mapNotNull { it.toSearchResult() } |
| 49 | + return newHomePageResponse(request.name, items) |
| 50 | + } |
| 51 | + |
| 52 | + private fun Element.toSearchResult(): SearchResponse? { |
| 53 | + val title = selectFirst("h2.entry-title > a")?.text()?.trim() ?: return null |
| 54 | + val href = fixUrl(selectFirst("a")?.attr("href") ?: return null) |
| 55 | + val poster = fixUrlNull(selectFirst("a > img")?.getImageAttr())?.fixImageQuality() |
| 56 | + val quality = select("div.gmr-qual, div.gmr-quality-item > a") |
| 57 | + .text().trim().replace("-", "") |
| 58 | + val ratingText = this.selectFirst("div.gmr-rating-item")?.ownText()?.trim() |
| 59 | + |
| 60 | + return if (quality.isEmpty()) { |
| 61 | + val episode = Regex("Episode\\s?([0-9]+)").find(title) |
| 62 | + ?.groupValues?.getOrNull(1)?.toIntOrNull() |
| 63 | + ?: select("div.gmr-numbeps > span").text().toIntOrNull() |
| 64 | + |
| 65 | + newAnimeSearchResponse(title, href, TvType.TvSeries) { |
| 66 | + posterUrl = poster |
| 67 | + addSub(episode) |
| 68 | + } |
| 69 | + } else { |
| 70 | + newMovieSearchResponse(title, href, TvType.Movie) { |
| 71 | + posterUrl = poster |
| 72 | + addQuality(quality) |
| 73 | + this.score = Score.from10(ratingText?.toDoubleOrNull()) |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + override suspend fun search(query: String): List<SearchResponse> { |
| 79 | + val document = app.get("$mainUrl?s=$query&post_type[]=post&post_type[]=tv").document |
| 80 | + return document.select("article.item-infinite").mapNotNull { it.toSearchResult() } |
| 81 | + } |
| 82 | + |
| 83 | + private fun Element.toRecommendResult(): SearchResponse? { |
| 84 | + val title = selectFirst("h2.entry-title > a")?.text()?.trim() ?: return null |
| 85 | + val href = selectFirst("h2.entry-title > a")?.attr("href") ?: return null |
| 86 | + val poster = fixUrlNull(selectFirst("div.content-thumbnail img")?.attr("src"))?.fixImageQuality() |
| 87 | + val quality = select("div.gmr-qual, div.gmr-quality-item > a") |
| 88 | + .text().trim().replace("-", "") |
| 89 | + val ratingText = this.selectFirst("div.gmr-rating-item")?.ownText()?.trim() |
| 90 | + return if (quality.isEmpty()) { |
| 91 | + val episode = Regex("Episode\\s?([0-9]+)").find(title) |
| 92 | + ?.groupValues?.getOrNull(1)?.toIntOrNull() |
| 93 | + ?: select("div.gmr-numbeps > span").text().toIntOrNull() |
| 94 | + |
| 95 | + newAnimeSearchResponse(title, href, TvType.TvSeries) { |
| 96 | + posterUrl = poster |
| 97 | + addSub(episode) |
| 98 | + } |
| 99 | + } else { |
| 100 | + newMovieSearchResponse(title, href, TvType.Movie) { |
| 101 | + posterUrl = poster |
| 102 | + addQuality(quality) |
| 103 | + this.score = Score.from10(ratingText?.toDoubleOrNull()) |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + override suspend fun load(url: String): LoadResponse { |
| 109 | + val fetch = app.get(url) |
| 110 | + val document = fetch.document |
| 111 | + directUrl = getBaseUrl(fetch.url) |
| 112 | + |
| 113 | + val title = document.selectFirst("h1.entry-title")?.text() |
| 114 | + ?.substringBefore("Season")?.substringBefore("Episode")?.trim().orEmpty() |
| 115 | + |
| 116 | + val poster = fixUrlNull(document.selectFirst("figure.pull-left > img")?.getImageAttr()) |
| 117 | + ?.fixImageQuality() |
| 118 | + val tags = document.select("div.gmr-moviedata a").map { it.text() } |
| 119 | + val year = document.select("div.gmr-moviedata strong:contains(Year:) > a") |
| 120 | + .text().trim().toIntOrNull() |
| 121 | + val tvType = if (url.contains("/tv/")) TvType.TvSeries else TvType.Movie |
| 122 | + val description = document.selectFirst("div[itemprop=description] > p")?.text()?.trim() |
| 123 | + val trailer = document.selectFirst("ul.gmr-player-nav li a.gmr-trailer-popup")?.attr("href") |
| 124 | + val rating = document.selectFirst("div.gmr-meta-rating span[itemprop=ratingValue]") |
| 125 | + ?.text()?.trim() |
| 126 | + val actors = document.select("div.gmr-moviedata").last() |
| 127 | + ?.select("span[itemprop=actors] a")?.map { it.text() } |
| 128 | + val duration = document.selectFirst("div.gmr-moviedata span[property=duration]") |
| 129 | + ?.text()?.replace(Regex("\\D"), "")?.toIntOrNull() |
| 130 | + val recommendations = document.select("article.item.col-md-20").mapNotNull { it.toRecommendResult() } |
| 131 | + return if (tvType == TvType.TvSeries) { |
| 132 | + val episodes = document.select("div.vid-episodes a, div.gmr-listseries a") |
| 133 | + .mapNotNull { eps -> |
| 134 | + val href = fixUrl(eps.attr("href")) |
| 135 | + val rawTitle = eps.attr("title").takeIf { it.isNotBlank() } ?: eps.text() |
| 136 | + val cleanTitle = rawTitle.replaceFirst(Regex("(?i)Permalink to\\s*"), "").trim() |
| 137 | + |
| 138 | + val epNum = Regex("Episode\\s*(\\d+)").find(cleanTitle)?.groupValues?.getOrNull(1)?.toIntOrNull() |
| 139 | + ?: cleanTitle.split(" ").lastOrNull()?.filter { it.isDigit() }?.toIntOrNull() |
| 140 | + |
| 141 | + val formattedName = epNum?.let { "Episode $it" } ?: cleanTitle |
| 142 | + |
| 143 | + newEpisode(href) { |
| 144 | + this.name = formattedName |
| 145 | + this.episode = epNum |
| 146 | + this.posterUrl = poster |
| 147 | + } |
| 148 | + }.filter { it.episode != null } |
| 149 | + |
| 150 | + newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { |
| 151 | + posterUrl = poster |
| 152 | + this.year = year |
| 153 | + plot = description |
| 154 | + this.tags = tags |
| 155 | + addScore(rating) |
| 156 | + addActors(actors) |
| 157 | + this.recommendations = recommendations |
| 158 | + this.duration = duration ?: 0 |
| 159 | + addTrailer(trailer) |
| 160 | + } |
| 161 | + } else { |
| 162 | + newMovieLoadResponse(title, url, TvType.Movie, url) { |
| 163 | + posterUrl = poster |
| 164 | + this.year = year |
| 165 | + plot = description |
| 166 | + this.tags = tags |
| 167 | + addScore(rating) |
| 168 | + addActors(actors) |
| 169 | + this.recommendations = recommendations |
| 170 | + this.duration = duration ?: 0 |
| 171 | + addTrailer(trailer) |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + override suspend fun loadLinks( |
| 177 | + data: String, |
| 178 | + isCasting: Boolean, |
| 179 | + subtitleCallback: (SubtitleFile) -> Unit, |
| 180 | + callback: (ExtractorLink) -> Unit |
| 181 | + ): Boolean { |
| 182 | + val document = app.get(data).document |
| 183 | + val id = document.selectFirst("div#muvipro_player_content_id")?.attr("data-id") |
| 184 | + |
| 185 | + if (id.isNullOrEmpty()) { |
| 186 | + document.select("ul.muvipro-player-tabs li a").amap { ele -> |
| 187 | + val iframe = app.get(fixUrl(ele.attr("href"))).document |
| 188 | + .selectFirst("div.gmr-embed-responsive iframe") |
| 189 | + ?.getIframeAttr()?.let { httpsify(it) } ?: return@amap |
| 190 | + loadExtractor(iframe, "$directUrl/", subtitleCallback, callback) |
| 191 | + } |
| 192 | + } else { |
| 193 | + document.select("div.tab-content-ajax").amap { ele -> |
| 194 | + val server = app.post( |
| 195 | + "$directUrl/wp-admin/admin-ajax.php", |
| 196 | + data = mapOf( |
| 197 | + "action" to "muvipro_player_content", |
| 198 | + "tab" to ele.attr("id"), |
| 199 | + "post_id" to "$id" |
| 200 | + ) |
| 201 | + ).document.select("iframe").attr("src").let { httpsify(it) } |
| 202 | + loadExtractor(server, "$directUrl/", subtitleCallback, callback) |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + document.select("ul.gmr-download-list li a").forEach { link -> |
| 207 | + val downloadUrl = link.attr("href") |
| 208 | + if (downloadUrl.isNotBlank()) { |
| 209 | + loadExtractor(downloadUrl, data, subtitleCallback, callback) |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + return true |
| 214 | + } |
| 215 | + |
| 216 | + private fun Element.getImageAttr(): String = when { |
| 217 | + hasAttr("data-src") -> attr("abs:data-src") |
| 218 | + hasAttr("data-lazy-src") -> attr("abs:data-lazy-src") |
| 219 | + hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ") |
| 220 | + else -> attr("abs:src") |
| 221 | + } |
| 222 | + |
| 223 | + private fun Element?.getIframeAttr(): String? = |
| 224 | + this?.attr("data-litespeed-src").takeIf { !it.isNullOrEmpty() } ?: this?.attr("src") |
| 225 | + |
| 226 | + private fun String?.fixImageQuality(): String? { |
| 227 | + if (this == null) return null |
| 228 | + val regex = Regex("(-\\d*x\\d*)").find(this)?.value ?: return this |
| 229 | + return replace(regex, "") |
| 230 | + } |
| 231 | + |
| 232 | + private fun getBaseUrl(url: String): String = |
| 233 | + URI(url).let { "${it.scheme}://${it.host}" } |
| 234 | +} |
0 commit comments