@@ -25,45 +25,68 @@ class Dubbindo : MainAPI() {
2525 )
2626
2727 override val mainPage = mainPageOf(
28- " $mainUrl /videos/category/1" to " Movie" ,
29- " $mainUrl /videos/category/3" to " TV Series" ,
30- " $mainUrl /videos/category/5" to " Anime Series" ,
31- " $mainUrl /videos/category/4" to " Anime Movie" ,
28+ " $mainUrl /videos/category/1" to " Movie" ,
29+ " $mainUrl /videos/category/3" to " TV Series" ,
30+ " $mainUrl /videos/category/5" to " Anime Series" ,
31+ " $mainUrl /videos/category/4" to " Anime Movie" ,
3232 " $mainUrl /videos/category/other" to " Other" ,
3333 )
3434
3535 override suspend fun getMainPage (
3636 page : Int ,
3737 request : MainPageRequest
3838 ): HomePageResponse {
39+ // Pagination menggunakan ?page_id= (konfirmasi dari HTML)
3940 val document = app.get(" ${request.data} ?page_id=$page " ).document
4041
41- // FIX: Selector diperbarui sesuai struktur HTML aktual website.
42- // Website menggunakan div.video-list sebagai container kartu video,
43- // bukan div.video-wrapper seperti sebelumnya.
44- val home = document.select(" div.video-list" )
45- .mapNotNull {
46- it.toSearchResult()
47- }
42+ // FIX: Halaman category menggunakan "div.video-wrapper" bukan "div.video-list"
43+ val home = document.select(" div.video-wrapper" )
44+ .mapNotNull { it.toCategoryResult() }
45+
4846 return newHomePageResponse(
4947 list = HomePageList (
5048 name = request.name,
5149 list = home,
5250 isHorizontalImages = true
5351 ),
54- hasNext = true
52+ hasNext = home.isNotEmpty()
5553 )
5654 }
5755
56+ // Untuk kartu di halaman category: div.video-wrapper
57+ // Struktur: div.video-thumb > a[href] + img[src]
58+ // div.video-title > a > h4
59+ private fun Element.toCategoryResult (): TvSeriesSearchResponse ? {
60+ val title = this .selectFirst(" div.video-title h4" )?.text()?.trim()
61+ ? : return null
62+ if (title.isEmpty()) return null
63+
64+ val href = this .selectFirst(" div.video-thumb a" )?.attr(" href" )
65+ ? : this .selectFirst(" a" )?.attr(" href" )
66+ ? : return null
67+
68+ val posterUrl = fixUrlNull(
69+ this .selectFirst(" div.video-thumb img" )?.attr(" src" )
70+ )
71+
72+ return newTvSeriesSearchResponse(title, href, TvType .TvSeries ) {
73+ this .posterUrl = posterUrl
74+ }
75+ }
76+
77+ // Untuk kartu di hasil search: div.video-list
5878 private fun Element.toSearchResult (): TvSeriesSearchResponse ? {
59- // FIX: Selector title disesuaikan dengan struktur HTML baru.
60- // Judul ada di dalam div.video-list-title > a > h4
61- val title = this .selectFirst(" div.video-list-title h4, h4" )?.text()?.trim() ? : return null
62- // Ambil href dari link gambar/thumbnail (link pertama di dalam div.video-list-image)
79+ val title = this .selectFirst(" div.video-list-title h4" )?.text()?.trim()
80+ ? : this .selectFirst(" h4" )?.text()?.trim()
81+ ? : return null
82+ if (title.isEmpty()) return null
83+
6384 val href = this .selectFirst(" div.video-list-image a" )?.attr(" href" )
6485 ? : this .selectFirst(" a" )?.attr(" href" )
6586 ? : return null
87+
6688 val posterUrl = fixUrlNull(this .selectFirst(" img" )?.attr(" src" ))
89+
6790 return newTvSeriesSearchResponse(title, href, TvType .TvSeries ) {
6891 this .posterUrl = posterUrl
6992 }
@@ -72,16 +95,9 @@ class Dubbindo : MainAPI() {
7295 override suspend fun search (query : String ): List <SearchResponse > {
7396 val searchResponse = mutableListOf<SearchResponse >()
7497 for (i in 1 .. 10 ) {
75- val document =
76- app.get(
77- " $mainUrl /search?keyword=$query &page_id=$i " ,
78- ).document
79-
80- // FIX: Selector search juga diperbarui ke div.video-list
98+ val document = app.get(" $mainUrl /search?keyword=$query &page_id=$i " ).document
8199 val results = document.select(" div.video-list" )
82- .mapNotNull {
83- it.toSearchResult()
84- }
100+ .mapNotNull { it.toSearchResult() }
85101 searchResponse.addAll(results)
86102 if (results.isEmpty()) break
87103 }
@@ -91,8 +107,6 @@ class Dubbindo : MainAPI() {
91107 override suspend fun load (url : String ): LoadResponse ? {
92108 val document = app.get(url).document
93109
94- // FIX: Ambil title dari meta tag agar bisa dipakai di kedua jenis halaman,
95- // lalu bersihkan nama site-nya.
96110 val title = (document.selectFirst(" meta[name=title]" )?.attr(" content" )
97111 ? : document.title())
98112 .replace(" | UVideo" , " " ).trim()
@@ -101,22 +115,14 @@ class Dubbindo : MainAPI() {
101115 val poster = document.selectFirst(" meta[property=og:image]" )?.attr(" content" )
102116 val tags = document.select(" div.pt_categories li a" ).map { it.text() }
103117
104- // FIX: Tangani dua jenis halaman detail:
105- // 1. /articles/read/... → link video ada di div.read-article-text a
106- // 2. /watch/... → sumber video ada di video#my-video source
107118 return if (url.contains(" /articles/read/" )) {
108119 // --- Halaman Artikel ---
109120 val description = document.selectFirst(" div.read-article-description article" )?.text()
110-
111- // Kumpulkan semua link video/extractor dari body artikel
112121 val videoLinks = document.select(" div.read-article-text a" )
113122 .map { it.attr(" href" ) }
114123 .filter { it.isNotBlank() }
115-
116- // Related videos di artikel menggunakan div.related-video-wrapper
117- val recommendations = document.select(" div.related-video-wrapper" ).mapNotNull {
118- it.toRelatedResult()
119- }
124+ val recommendations = document.select(" div.related-video-wrapper" )
125+ .mapNotNull { it.toRelatedResult() }
120126
121127 newMovieLoadResponse(title, url, TvType .Movie , videoLinks.toJson()) {
122128 posterUrl = poster
@@ -126,16 +132,14 @@ class Dubbindo : MainAPI() {
126132 }
127133 } else {
128134 // --- Halaman Watch ---
129- val description = document.select(" div.watch-video-description p" ).text().replace(" \u2063 " , " " ).trim()
130-
131- val recommendations = document.select(" div.related-video-wrapper" ).mapNotNull {
132- it.toRelatedResult()
133- }
134-
135+ val description = document.select(" div.watch-video-description p" )
136+ .text().replace(" \u2063 " , " " ).trim()
137+ val recommendations = document.select(" div.related-video-wrapper" )
138+ .mapNotNull { it.toRelatedResult() }
135139 val video = document.select(" video#my-video source" ).map {
136140 Video (
137141 it.attr(" src" ),
138- it.attr(" res" ), // FIX: was "size" — atribut di HTML adalah "res"
142+ it.attr(" res" ), // atribut resolusi di HTML adalah "res"
139143 it.attr(" type" ),
140144 )
141145 }
@@ -149,8 +153,7 @@ class Dubbindo : MainAPI() {
149153 }
150154 }
151155
152- // FIX: Fungsi khusus untuk mengambil related video dari div.related-video-wrapper
153- // (struktur berbeda dari div.video-list di halaman utama)
156+ // Related video di sidebar: div.related-video-wrapper
154157 private fun Element.toRelatedResult (): TvSeriesSearchResponse ? {
155158 val title = this .selectFirst(" div.video-title a" )?.text()?.trim()
156159 ? : this .selectFirst(" a" )?.text()?.trim()
@@ -170,8 +173,7 @@ class Dubbindo : MainAPI() {
170173 subtitleCallback : (SubtitleFile ) -> Unit ,
171174 callback : (ExtractorLink ) -> Unit
172175 ): Boolean {
173-
174- // FIX: Coba parse sebagai List<Video> dulu (halaman watch)
176+ // Parse sebagai List<Video> → halaman watch (mp4 langsung)
175177 val videos = tryParseJson<List <Video >>(data)
176178 if (videos != null ) {
177179 videos.map { video ->
@@ -193,8 +195,7 @@ class Dubbindo : MainAPI() {
193195 return true
194196 }
195197
196- // FIX: Jika bukan List<Video>, coba parse sebagai List<String>
197- // (halaman artikel dengan link extractor langsung)
198+ // Parse sebagai List<String> → halaman artikel (link extractor)
198199 val urls = tryParseJson<List <String >>(data)
199200 if (urls != null ) {
200201 urls.forEach { videoUrl ->
@@ -213,5 +214,4 @@ class Dubbindo : MainAPI() {
213214 val res : String? = null ,
214215 val type : String? = null ,
215216 )
216-
217217}
0 commit comments