fix(pt/hinatasoul): Fix video extractor (#2386)
This commit is contained in:
@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user