Skip to content

Commit cd46106

Browse files
test
1 parent 0f0e55c commit cd46106

1 file changed

Lines changed: 72 additions & 46 deletions

File tree

  • DubbindoProvider/src/main/kotlin/com/dubbindo

DubbindoProvider/src/main/kotlin/com/dubbindo/Dubbindo.kt

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ class Dubbindo : MainAPI() {
3232
// ── Session ───────────────────────────────────────────────────────────────
3333
private var sessionCookie = ""
3434

35-
// Track channel ID yang sudah disubscribe agar tidak kirim request berulang
36-
private val subscribedChannels = mutableSetOf<String>()
37-
3835
private val baseHeaders get() = mapOf(
3936
"User-Agent" to "Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 Chrome/124.0 Mobile Safari/537.36",
4037
"Referer" to "$mainUrl/"
@@ -55,7 +52,7 @@ class Dubbindo : MainAPI() {
5552

5653
private suspend fun doLogin(): Boolean {
5754
// Step 1 – GET /login untuk PHPSESSID awal
58-
val getResp = app.get("$mainUrl/login", headers = baseHeaders)
55+
val getResp = app.get("$mainUrl/login", headers = baseHeaders)
5956
val initCookies = getResp.headers
6057
.filter { it.first.equals("set-cookie", ignoreCase = true) }
6158
.mapNotNull { parseCookiePair(it.second) }
@@ -96,35 +93,43 @@ class Dubbindo : MainAPI() {
9693
doLogin()
9794
}
9895

99-
// ── Auto-subscribe ────────────────────────────────────────────────────────
96+
// ── Subscribe ─────────────────────────────────────────────────────────────
10097

10198
/**
102-
* Cek apakah ada tombol Subscribe di halaman, lalu POST ke /aj/subscribe.
103-
* Dipanggil setiap load() agar video dari channel berlangganan bisa diakses.
104-
*
105-
* Selector: <button class="btn-subscribe" data-id="CHANNEL_ID">
106-
* atau: <input id="profile-id" value="CHANNEL_ID">
99+
* Ambil channel ID dari halaman, lalu POST ke /aj/subscribe.
100+
* Mengambil main_session dari halaman untuk header Referer yang benar.
101+
* Return true jika berhasil menemukan channel ID dan mengirim request.
107102
*/
108-
private suspend fun autoSubscribe(document: Document) {
109-
val channelId = document.selectFirst("button.btn-subscribe[data-id]")
103+
private suspend fun subscribeChannel(document: Document, pageUrl: String): Boolean {
104+
// Cari channel ID dari tombol subscribe di halaman watch/channel
105+
val channelId = document
106+
.selectFirst("button.btn-subscribe[data-id], .subscribe-btn-container button[data-id]")
110107
?.attr("data-id")?.trim()
111-
?: document.selectFirst("input#profile-id")
112-
?.attr("value")?.trim()
113-
?: return
108+
?: document.selectFirst("input#profile-id")?.attr("value")?.trim()
109+
?: return false
114110

115-
if (channelId.isBlank() || subscribedChannels.contains(channelId)) return
111+
if (channelId.isBlank()) return false
116112

117-
app.post(
113+
val resp = app.post(
118114
"$mainUrl/aj/subscribe",
119115
data = mapOf("user_id" to channelId),
120116
headers = authedHeaders + mapOf(
121-
"Content-Type" to "application/x-www-form-urlencoded",
117+
"Content-Type" to "application/x-www-form-urlencoded",
122118
"X-Requested-With" to "XMLHttpRequest",
123-
"Referer" to "$mainUrl/"
119+
"Referer" to pageUrl,
120+
"Origin" to mainUrl
124121
)
125122
)
126123

127-
subscribedChannels.add(channelId)
124+
return resp.isSuccessful
125+
}
126+
127+
/** Deteksi apakah halaman masih terkunci (subscribe wall). */
128+
private fun isSubscribeWall(document: Document): Boolean {
129+
val playerArea = document.selectFirst("div.video-processing, div.video-player")
130+
?.text().orEmpty()
131+
return playerArea.contains("subscribe to watch", ignoreCase = true) ||
132+
document.select("video#my-video source, video source").isEmpty()
128133
}
129134

130135
// ── Main page ─────────────────────────────────────────────────────────────
@@ -153,7 +158,7 @@ class Dubbindo : MainAPI() {
153158
val href = selectFirst("div.video-thumb a")?.attr("href")
154159
?: selectFirst("a")?.attr("href") ?: return null
155160
return newTvSeriesSearchResponse(title, href, TvType.TvSeries) {
156-
posterUrl = fixUrlNull(selectFirst("div.video-thumb img")?.attr("src"))
161+
posterUrl = fixUrlNull(selectFirst("div.video-thumb img")?.attr("src"))
157162
posterHeaders = mapOf("Referer" to mainUrl)
158163
}
159164
}
@@ -165,7 +170,7 @@ class Dubbindo : MainAPI() {
165170
val href = selectFirst("div.video-list-image a")?.attr("href")
166171
?: selectFirst("a")?.attr("href") ?: return null
167172
return newTvSeriesSearchResponse(title, href, TvType.TvSeries) {
168-
posterUrl = fixUrlNull(selectFirst("img")?.attr("src"))
173+
posterUrl = fixUrlNull(selectFirst("img")?.attr("src"))
169174
posterHeaders = mapOf("Referer" to mainUrl)
170175
}
171176
}
@@ -176,7 +181,7 @@ class Dubbindo : MainAPI() {
176181
val href = selectFirst("div.ra-thumb a")?.attr("href")
177182
?: selectFirst("a")?.attr("href") ?: return null
178183
return newTvSeriesSearchResponse(title, href, TvType.TvSeries) {
179-
posterUrl = fixUrlNull(selectFirst("img")?.attr("src"))
184+
posterUrl = fixUrlNull(selectFirst("img")?.attr("src"))
180185
posterHeaders = mapOf("Referer" to mainUrl)
181186
}
182187
}
@@ -198,7 +203,6 @@ class Dubbindo : MainAPI() {
198203

199204
// ── Load ──────────────────────────────────────────────────────────────────
200205

201-
/** Ekstrak semua <source> dari elemen video di halaman. */
202206
private fun parseVideoSources(doc: Document): List<Video> =
203207
doc.select("video#my-video source, video source").mapNotNull { el ->
204208
val src = el.attr("src").trim().ifEmpty { return@mapNotNull null }
@@ -209,10 +213,49 @@ class Dubbindo : MainAPI() {
209213
)
210214
}
211215

216+
/**
217+
* Ambil video sources dari URL, dengan logika:
218+
* 1. Load halaman
219+
* 2. Jika kena subscribe wall → subscribe → reload
220+
* 3. Jika masih wall → sesi expired → login ulang → subscribe → reload
221+
*/
222+
private suspend fun fetchVideoSources(url: String): Pair<Document, List<Video>> {
223+
var doc = app.get(url, headers = authedHeaders).document
224+
var videos = parseVideoSources(doc)
225+
226+
if (videos.isNotEmpty()) return doc to videos
227+
228+
// Kena subscribe wall — coba subscribe lalu reload
229+
if (isSubscribeWall(doc)) {
230+
subscribeChannel(doc, url)
231+
doc = app.get(url, headers = authedHeaders).document
232+
videos = parseVideoSources(doc)
233+
}
234+
235+
if (videos.isNotEmpty()) return doc to videos
236+
237+
// Masih kosong — mungkin sesi expired, login ulang
238+
sessionCookie = ""
239+
if (doLogin()) {
240+
// Muat ulang halaman dengan sesi baru
241+
doc = app.get(url, headers = authedHeaders).document
242+
videos = parseVideoSources(doc)
243+
244+
// Masih kena wall setelah re-login → subscribe ulang dengan sesi baru
245+
if (videos.isEmpty() && isSubscribeWall(doc)) {
246+
subscribeChannel(doc, url)
247+
doc = app.get(url, headers = authedHeaders).document
248+
videos = parseVideoSources(doc)
249+
}
250+
}
251+
252+
return doc to videos
253+
}
254+
212255
override suspend fun load(url: String): LoadResponse? {
213256
ensureSession()
214257

215-
var document = app.get(url, headers = authedHeaders).document
258+
val (document, _) = fetchVideoSources(url).let { it } // load awal
216259

217260
val title = (document.selectFirst("meta[name=title]")?.attr("content")
218261
?: document.title()).replace(" | UVideo", "").trim()
@@ -222,8 +265,8 @@ class Dubbindo : MainAPI() {
222265
val tags = document.select("div.pt_categories li a").map { it.text() }
223266

224267
return if (url.contains("/articles/read/")) {
225-
val description = document.selectFirst("div.read-article-description article")?.text()
226-
val videoLinks = document.select("div.read-article-text a")
268+
val description = document.selectFirst("div.read-article-description article")?.text()
269+
val videoLinks = document.select("div.read-article-text a")
227270
.map { it.attr("href") }.filter { it.isNotBlank() }
228271
val recommendations = document.select("div.related-video-wrapper")
229272
.mapNotNull { it.toRelatedResult() }
@@ -237,25 +280,8 @@ class Dubbindo : MainAPI() {
237280
val recommendations = document.select("div.related-video-wrapper")
238281
.mapNotNull { it.toRelatedResult() }
239282

240-
var videos = parseVideoSources(document)
241-
242-
if (videos.isEmpty()) {
243-
// Video kosong → subscribe ke channel lalu reload
244-
autoSubscribe(document)
245-
document = app.get(url, headers = authedHeaders).document
246-
videos = parseVideoSources(document)
247-
248-
// Masih kosong → sesi expired, login ulang
249-
if (videos.isEmpty()) {
250-
sessionCookie = ""
251-
if (doLogin()) {
252-
// Subscribe ulang setelah login baru
253-
autoSubscribe(document)
254-
document = app.get(url, headers = authedHeaders).document
255-
videos = parseVideoSources(document)
256-
}
257-
}
258-
}
283+
// Jalankan fetchVideoSources untuk dapat dokumen + sources final
284+
val (_, videos) = fetchVideoSources(url)
259285

260286
newMovieLoadResponse(title, url, TvType.Movie, videos.toJson()) {
261287
posterUrl = poster; plot = description

0 commit comments

Comments
 (0)