Skip to content

Commit b09b089

Browse files
Last Sync: 2026-02-24 04:08 (Mobile)
1 parent 009ee21 commit b09b089

5 files changed

Lines changed: 416 additions & 0 deletions

File tree

DutamovieProvider/build.gradle.kts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version = 1
2+
3+
cloudstream {
4+
description = "DutaMovie - Streaming Movie and TV Series"
5+
language = "id"
6+
authors = listOf("Miku")
7+
status = 1
8+
isCrossPlatform = true
9+
tvTypes = listOf(
10+
"Movie",
11+
"TvSeries",
12+
"AsianDrama",
13+
)
14+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest package="com.dutamovie"/>
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.dutamovie
2+
3+
import android.content.Context
4+
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
5+
import com.lagradost.cloudstream3.plugins.Plugin
6+
7+
@CloudstreamPlugin
8+
class DutamovieProviderPlugin : Plugin() {
9+
override fun load(context: Context) {
10+
registerMainAPI(DutamovieProvider())
11+
registerExtractorAPI(Dingtezuni())
12+
registerExtractorAPI(Movearnpre())
13+
registerExtractorAPI(Mivalyo())
14+
registerExtractorAPI(Bingezove())
15+
registerExtractorAPI(Ryderjet())
16+
registerExtractorAPI(Ghbrisk())
17+
registerExtractorAPI(Hglink())
18+
registerExtractorAPI(Dhcplay())
19+
registerExtractorAPI(Streamcasthub())
20+
registerExtractorAPI(Dm21upns())
21+
registerExtractorAPI(Gofile())
22+
}
23+
}

0 commit comments

Comments
 (0)