UHDMovies: fixes (#1451)
* update domain dynamically when redirected on init * fix video fetching and other css fixes
This commit is contained in:
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = 'UHD Movies'
|
extName = 'UHD Movies'
|
||||||
pkgNameSuffix = 'en.uhdmovies'
|
pkgNameSuffix = 'en.uhdmovies'
|
||||||
extClass = '.UHDMovies'
|
extClass = '.UHDMovies'
|
||||||
extVersionCode = 10
|
extVersionCode = 11
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@ -27,6 +28,7 @@ import okhttp3.FormBody
|
|||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -39,7 +41,7 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override val name = "UHD Movies"
|
override val name = "UHD Movies"
|
||||||
|
|
||||||
override val baseUrl = "https://uhdmovies.world"
|
override val baseUrl by lazy { preferences.getString("pref_domain", "https://uhdmovies.vip")!! }
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
@ -53,9 +55,31 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var currentBaseUrl: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
currentBaseUrl = client.newBuilder()
|
||||||
|
.followRedirects(false)
|
||||||
|
.build()
|
||||||
|
.newCall(GET("$baseUrl/")).execute().let { resp ->
|
||||||
|
when (resp.code) {
|
||||||
|
301 -> {
|
||||||
|
(resp.headers["location"]?.substringBeforeLast("/") ?: baseUrl).also {
|
||||||
|
preferences.edit().putString("pref_domain", it).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> baseUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/page/$page/")
|
override fun popularAnimeRequest(page: Int): Request = GET("$currentBaseUrl/page/$page/")
|
||||||
|
|
||||||
override fun popularAnimeSelector(): String = "div#content div.gridlove-posts > div.layout-masonry"
|
override fun popularAnimeSelector(): String = "div#content div.gridlove-posts > div.layout-masonry"
|
||||||
|
|
||||||
@ -85,7 +109,7 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
val cleanQuery = query.replace(" ", "+").lowercase()
|
val cleanQuery = query.replace(" ", "+").lowercase()
|
||||||
return GET("$baseUrl/page/$page/?s=$cleanQuery")
|
return GET("$currentBaseUrl/page/$page/?s=$cleanQuery")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
||||||
@ -98,19 +122,20 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun animeDetailsParse(document: Document): SAnime {
|
override fun animeDetailsParse(document: Document): SAnime {
|
||||||
return SAnime.create().apply {
|
return SAnime.create().apply {
|
||||||
title = document.selectFirst("h2")!!.text()
|
initialized = true
|
||||||
|
title = document.selectFirst(".entry-title")!!.text()
|
||||||
.replace("Download", "", true).trim()
|
.replace("Download", "", true).trim()
|
||||||
status = SAnime.COMPLETED
|
status = SAnime.COMPLETED
|
||||||
|
description = document.selectFirst("pre:contains(plot)")?.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
|
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||||
val response = client.newCall(GET(baseUrl + anime.url)).execute()
|
val resp = client.newCall(GET(currentBaseUrl + anime.url)).execute().asJsoup()
|
||||||
val resp = response.asJsoup()
|
|
||||||
val episodeList = mutableListOf<SEpisode>()
|
val episodeList = mutableListOf<SEpisode>()
|
||||||
val episodeElements = resp.select("p:has(a[href*=?id])[style*=center],p:has(a[href*=?id]):has(span.maxbutton-1-center)")
|
val episodeElements = resp.select("p:has(a[href*=?id]):has(a[class*=maxbutton])[style*=center],p:has(a[href*=?id]):has(span.maxbutton-1-center)")
|
||||||
val qualityRegex = "\\d{3,4}p".toRegex(RegexOption.IGNORE_CASE)
|
val qualityRegex = "\\d{3,4}p".toRegex(RegexOption.IGNORE_CASE)
|
||||||
val firstText = episodeElements.first()!!.text()
|
val firstText = episodeElements.first()!!.text()
|
||||||
if (firstText.contains("Episode", true) ||
|
if (firstText.contains("Episode", true) ||
|
||||||
@ -182,14 +207,16 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
qualityMatchOwn?.value ?: "HD"
|
qualityMatchOwn?.value ?: "HD"
|
||||||
}
|
}
|
||||||
|
|
||||||
val collectionName = row.previousElementSiblings().prev("h1,h2,h3,pre").first()!!.text()
|
val collectionName = row.previousElementSiblings().let { prevElem ->
|
||||||
.replace("Download", "", true).trim().let {
|
(prevElem.prev("h1,h2,h3,pre:not(:contains(plot))").first()?.text() ?: "Movie - $quality")
|
||||||
if (it.contains("Collection", true)) {
|
.replace("Download", "", true).trim().let {
|
||||||
row.previousElementSibling()!!.ownText()
|
if (it.contains("Collection", true)) {
|
||||||
} else {
|
row.previousElementSibling()!!.ownText()
|
||||||
it
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row.select("a").map { linkElement ->
|
row.select("a").map { linkElement ->
|
||||||
Triple(linkElement.attr("href"), quality, collectionName)
|
Triple(linkElement.attr("href"), quality, collectionName)
|
||||||
@ -258,25 +285,30 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
private fun extractVideo(epUrl: EpUrl): Pair<List<Video>, String> {
|
private fun extractVideo(epUrl: EpUrl): Pair<List<Video>, String> {
|
||||||
val postLink = epUrl.url.substringBefore("?id=").substringAfter("/?")
|
val postLink = epUrl.url.substringBefore("?id=").substringAfter("/?")
|
||||||
val formData = FormBody.Builder().add("_wp_http", epUrl.url.substringAfter("?id=")).build()
|
val formData = FormBody.Builder().add("_wp_http_c", epUrl.url.substringAfter("?id=")).build()
|
||||||
val response = client.newCall(POST(postLink, body = formData)).execute().asJsoup()
|
val response = client.newCall(POST(postLink, body = formData)).execute().body.string()
|
||||||
val link = response.selectFirst("form#landing")!!.attr("action")
|
val (longC, catC, _) = getCookiesDetail(response)
|
||||||
val wpHttp = response.selectFirst("input[name=_wp_http2]")!!.attr("value")
|
val cookieHeader = Headers.headersOf("Cookie", "$longC; $catC")
|
||||||
val token = response.selectFirst("input[name=token]")!!.attr("value")
|
val parsedSoup = Jsoup.parse(response)
|
||||||
val blogFormData = FormBody.Builder()
|
val link = parsedSoup.selectFirst("center > a")!!.attr("href")
|
||||||
.add("_wp_http2", wpHttp)
|
|
||||||
.add("token", token)
|
val response2 = client.newCall(GET(link, cookieHeader)).execute().body.string()
|
||||||
.build()
|
val (longC2, _, postC) = getCookiesDetail(response2)
|
||||||
val blogResponse = client.newCall(POST(link, body = blogFormData)).execute().body.string()
|
val cookieHeader2 = Headers.headersOf("Cookie", "$catC; $longC2; $postC")
|
||||||
val skToken = blogResponse.substringAfter("?go=").substringBefore("\"")
|
val parsedSoup2 = Jsoup.parse(response2)
|
||||||
val tokenUrl = "$postLink?go=$skToken"
|
val link2 = parsedSoup2.selectFirst("center > a")!!.attr("href")
|
||||||
val cookieHeader = Headers.headersOf("Cookie", "$skToken=$wpHttp")
|
|
||||||
val tokenResponse = client.newBuilder().followRedirects(false).build()
|
val tokenResp = client.newCall(GET(link2, cookieHeader2)).execute().body.string()
|
||||||
.newCall(GET(tokenUrl, cookieHeader)).execute().asJsoup()
|
val goToken = tokenResp.substringAfter("?go=").substringBefore("\"")
|
||||||
|
val tokenUrl = "$postLink?go=$goToken"
|
||||||
|
val newLongC = "$goToken=" + longC2.substringAfter("=")
|
||||||
|
val tokenCookie = Headers.headersOf("Cookie", "$catC; rdst_post=; $newLongC")
|
||||||
|
|
||||||
|
val noRedirectClient = client.newBuilder().followRedirects(false).build()
|
||||||
|
val tokenResponse = noRedirectClient.newCall(GET(tokenUrl, tokenCookie)).execute().asJsoup()
|
||||||
val redirectUrl = tokenResponse.select("meta[http-equiv=refresh]").attr("content")
|
val redirectUrl = tokenResponse.select("meta[http-equiv=refresh]").attr("content")
|
||||||
.substringAfter("url=").substringBefore("\"")
|
.substringAfter("url=").substringBefore("\"")
|
||||||
val mediaResponse = client.newBuilder().followRedirects(false).build()
|
val mediaResponse = noRedirectClient.newCall(GET(redirectUrl)).execute()
|
||||||
.newCall(GET(redirectUrl)).execute()
|
|
||||||
val path = mediaResponse.body.string().substringAfter("replace(\"").substringBefore("\"")
|
val path = mediaResponse.body.string().substringAfter("replace(\"").substringBefore("\"")
|
||||||
if (path == "/404") return Pair(emptyList(), "")
|
if (path == "/404") return Pair(emptyList(), "")
|
||||||
val mediaUrl = "https://" + mediaResponse.request.url.host + path
|
val mediaUrl = "https://" + mediaResponse.request.url.host + path
|
||||||
@ -290,6 +322,30 @@ class UHDMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
return Pair(videoList, mediaUrl)
|
return Pair(videoList, mediaUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCookiesDetail(page: String): Triple<String, String, String> {
|
||||||
|
val cat = "rdst_cat"
|
||||||
|
val post = "rdst_post"
|
||||||
|
val longC = page.substringAfter(".setTime")
|
||||||
|
.substringAfter("document.cookie = \"")
|
||||||
|
.substringBefore("\"")
|
||||||
|
.substringBefore(";")
|
||||||
|
val catC = if (page.contains("$cat=")) {
|
||||||
|
page.substringAfterLast("$cat=")
|
||||||
|
.substringBefore(";").let {
|
||||||
|
"$cat=$it"
|
||||||
|
}
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
|
val postC = if (page.contains("$post=")) {
|
||||||
|
page.substringAfterLast("$post=")
|
||||||
|
.substringBefore(";").let {
|
||||||
|
"$post=$it"
|
||||||
|
}
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
|
return Triple(longC, catC, postC)
|
||||||
|
}
|
||||||
|
|
||||||
private val sizeRegex = "\\[((?:.(?!\\[))+)] *\$".toRegex(RegexOption.IGNORE_CASE)
|
private val sizeRegex = "\\[((?:.(?!\\[))+)] *\$".toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
private fun extractWorkerLinks(mediaUrl: String, quality: String, type: Int): List<Video> {
|
private fun extractWorkerLinks(mediaUrl: String, quality: String, type: Int): List<Video> {
|
||||||
|
Reference in New Issue
Block a user