fix(pt/hinatasoul): Fix video extractor (#2386)

This commit is contained in:
Claudemirovsky
2023-10-16 07:40:58 -03:00
committed by GitHub
parent d0dc4be755
commit 87b10660e6
3 changed files with 93 additions and 87 deletions

View File

@ -7,7 +7,7 @@ ext {
extName = 'Hinata Soul' extName = 'Hinata Soul'
pkgNameSuffix = 'pt.hinatasoul' pkgNameSuffix = 'pt.hinatasoul'
extClass = '.HinataSoul' extClass = '.HinataSoul'
extVersionCode = 3 extVersionCode = 4
libVersion = '13' libVersion = '13'
} }

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -45,73 +44,32 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeSelector() = "div.FsssItem:contains(Mais Vistos) > a" override fun popularAnimeSelector() = "div.FsssItem:contains(Mais Vistos) > a"
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
title = element.text() title = element.text()
} }
override fun popularAnimeNextPageSelector(): String? = null
// ============================== Episodes ============================== override fun popularAnimeNextPageSelector() = null
override fun episodeListSelector() = "div.aniContainer a"
override fun episodeListParse(response: Response): List<SEpisode> {
var doc = getRealDoc(response.asJsoup())
val originalUrl = doc.location()
var pageNum = 1
val totalEpisodes = buildList {
do {
if (pageNum > 1) {
doc = client.newCall(GET(originalUrl + "/page/$pageNum"))
.execute()
.asJsoup()
}
doc.select(episodeListSelector()).forEach {
add(episodeFromElement(it))
}
pageNum++
} while (hasNextPage(doc))
}
return totalEpisodes.reversed()
}
override fun episodeFromElement(element: Element) = SEpisode.create().apply { // =============================== Latest ===============================
val title = element.attr("title") override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
override fun latestUpdatesSelector() =
"div.tituloContainer:contains(lançamento) + div.epiContainer a"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
name = title val img = element.selectFirst("img")!!
episode_number = title.substringAfterLast(" ").toFloatOrNull() ?: 0F thumbnail_url = img.attr("src")
date_upload = element.selectFirst("div.lancaster_episodio_info_data")!! title = img.attr("alt")
.text()
.toDate()
} }
// ============================ Video Links ============================= override fun latestUpdatesNextPageSelector() = null
override fun videoListParse(response: Response): List<Video> {
return HinataSoulExtractor(headers).getVideoList(response)
}
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeSelector(): String = episodeListSelector()
override fun searchAnimeNextPageSelector() = throw Exception("not used")
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src")
title = element.selectFirst("div.ultimosAnimesHomeItemInfosNome")!!.text()
}
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animes = document.select(searchAnimeSelector()).map {
searchAnimeFromElement(it)
}
val hasNext = hasNextPage(document)
return AnimesPage(animes, hasNext)
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> { override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { return if (query.startsWith(PREFIX_SEARCH)) {
val slug = query.removePrefix(PREFIX_SEARCH) val slug = query.removePrefix(PREFIX_SEARCH)
@ -128,9 +86,26 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(listOf(details), false) return AnimesPage(listOf(details), false)
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
GET("$baseUrl/busca?busca=$query&page=$page") GET("$baseUrl/busca?busca=$query&page=$page")
override fun searchAnimeSelector() = episodeListSelector()
override fun searchAnimeNextPageSelector() = null
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.selectFirst("img")!!.attr("src")
title = element.selectFirst("div.ultimosAnimesHomeItemInfosNome")!!.text()
}
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.use { it.asJsoup() }
val animes = document.select(searchAnimeSelector()).map(::searchAnimeFromElement)
val hasNext = hasNextPage(document)
return AnimesPage(animes, hasNext)
}
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply { override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document) val doc = getRealDoc(document)
@ -157,19 +132,43 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
// =============================== Latest =============================== // ============================== Episodes ==============================
override fun latestUpdatesNextPageSelector(): String? = null override fun episodeListSelector() = "div.aniContainer a"
override fun latestUpdatesSelector(): String = override fun episodeListParse(response: Response): List<SEpisode> {
"div.tituloContainer:contains(lançamento) + div.epiContainer a" var doc = getRealDoc(response.use { it.asJsoup() })
val totalEpisodes = buildList {
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { do {
setUrlWithoutDomain(element.attr("href")) if (isNotEmpty()) {
val img = element.selectFirst("img")!! val url = doc.selectFirst("div.mwidth > a:containsOwn(»)")!!.absUrl("href")
thumbnail_url = img.attr("src") doc = client.newCall(GET(url, headers)).execute().use { it.asJsoup() }
title = img.attr("alt") }
doc.select(episodeListSelector())
.map(::episodeFromElement)
.let(::addAll)
} while (hasNextPage(doc))
reverse()
}
return totalEpisodes
} }
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl) override fun episodeFromElement(element: Element) = SEpisode.create().apply {
val title = element.attr("title")
setUrlWithoutDomain(element.attr("href"))
name = title
episode_number = title.substringBeforeLast(" - FINAL").substringAfterLast(" ").toFloatOrNull() ?: 0F
date_upload = element.selectFirst("div.lancaster_episodio_info_data")!!
.text()
.toDate()
}
// ============================ Video Links =============================
private val extractor by lazy { HinataSoulExtractor(headers) }
override fun videoListParse(response: Response) = extractor.getVideoList(response)
override fun videoListSelector() = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
// ============================== Settings ============================== // ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -202,13 +201,18 @@ class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val currentUrl = doc.location() val currentUrl = doc.location()
val nextUrl = doc.selectFirst("a:contains(»)")!!.attr("href") val nextUrl = doc.selectFirst("a:contains(»)")!!.attr("href")
val endings = listOf("/1", "page=1") val endings = listOf("/1", "page=1")
return !endings.any(nextUrl::endsWith) && currentUrl != nextUrl return endings.none(nextUrl::endsWith) && currentUrl != nextUrl
} }
private val animeMenuSelector = "div.controlesBoxItem > a:has(i.iconLista)" private val animeMenuSelector = "div.controlesBoxItem > a:has(i.iconLista)"
private fun getRealDoc(document: Document): Document { private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/videos/")) {
return document
}
return document.selectFirst(animeMenuSelector)?.let { return document.selectFirst(animeMenuSelector)?.let {
client.newCall(GET(it.attr("href"), headers)).execute().asJsoup() client.newCall(GET(it.attr("href"), headers)).execute()
.use { r -> r.asJsoup() }
} ?: document } ?: document
} }

View File

@ -8,22 +8,24 @@ import okhttp3.Response
class HinataSoulExtractor(private val headers: Headers) { class HinataSoulExtractor(private val headers: Headers) {
fun getVideoList(response: Response): List<Video> { fun getVideoList(response: Response): List<Video> {
val html = response.body.string() val doc = response.use { it.asJsoup() }
val doc = response.asJsoup(html) val hasFHD = doc.selectFirst("div.abaItem:contains(FULLHD)") != null
val hasFHD = doc.selectFirst("div.Aba:contains(FULLHD)") != null val serverUrl = doc.selectFirst("meta[itemprop=contentURL]")!!
val regex = Regex("""file: '(\S+?)',""") .attr("content")
return regex.findAll(html).mapNotNull { .replace("cdn1", "cdn3")
val videoUrl = it.groupValues[1] val type = serverUrl.split('/').get(3)
// prevent some http 404 due to the source returning false-positives val qualities = listOfNotNull("SD", "HD", "FULLHD".takeIf { hasFHD })
if ("appfullhd" in videoUrl && !hasFHD) { val paths = listOf("appsd", "apphd").let {
null if (type.endsWith("2")) {
it.map { path -> path + "2" }
} else { } else {
val quality = videoUrl.substringAfter("app") it
.substringBefore("/")
.substringBefore("2") // prevents "HD2", "SD2" etc
.uppercase()
Video(videoUrl, quality, videoUrl, headers = headers)
} }
}.toList() } + listOfNotNull("appfullhd".takeIf { hasFHD })
return qualities.mapIndexed { index, quality ->
val path = paths[index]
val url = serverUrl.replace(type, path)
Video(url, quality, url, headers = headers)
}.reversed()
} }
} }