Adding some small details to Hentaivn work better. (#15125)

* 1.Add rate limit 2. Add filter search all 3. Add page filters author, 4. Add search elements so that when using search filters and search ids do not show thumbnails and name 5. Update version ext

* Update PageListparse

* Code formating

* Remove brackets

Co-authored-by: beerpsi <92439990+beerpiss@users.noreply.github.com>

Co-authored-by: beerpsi <92439990+beerpiss@users.noreply.github.com>
Co-authored-by: Carlos <2092019+CarlosEsco@users.noreply.github.com>
This commit is contained in:
are-are-are 2023-01-27 19:06:26 +07:00 committed by GitHub
parent 398897a16a
commit 3e0bf78691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 52 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'HentaiVN' extName = 'HentaiVN'
pkgNameSuffix = 'vi.hentaivn' pkgNameSuffix = 'vi.hentaivn'
extClass = '.HentaiVN' extClass = '.HentaiVN'
extVersionCode = 21 extVersionCode = 22
isNsfw = true isNsfw = true
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.vi.hentaivn
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.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@ -33,6 +34,7 @@ class HentaiVN : ParsedHttpSource() {
private val searchUrl = "$baseUrl/forum/search-plus.php" private val searchUrl = "$baseUrl/forum/search-plus.php"
private val searchByAuthorUrl = "$baseUrl/tim-kiem-tac-gia.html" private val searchByAuthorUrl = "$baseUrl/tim-kiem-tac-gia.html"
private val searchAllURL = "$baseUrl/tim-kiem-truyen.html"
private val searchClient = network.cloudflareClient private val searchClient = network.cloudflareClient
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
@ -46,6 +48,7 @@ class HentaiVN : ParsedHttpSource() {
else -> chain.proceed(originalRequest) else -> chain.proceed(originalRequest)
} }
} }
.rateLimit(3)
.build() .build()
override fun headersBuilder(): Headers.Builder = super.headersBuilder() override fun headersBuilder(): Headers.Builder = super.headersBuilder()
@ -54,6 +57,34 @@ class HentaiVN : ParsedHttpSource() {
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
//latestUpdates
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/chap-moi.html?page=$page", headers)
}
override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item"
override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)"
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
element.select(".box-description a").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text().trim()
}
manga.thumbnail_url = element.select(".box-cover a img").attr("data-src")
return manga
}
//Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/danh-sach.html?page=$page", headers)
}
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun popularMangaSelector() = latestUpdatesSelector()
//Chapter
override fun chapterListSelector() = "table.listing > tbody > tr"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
if (element.select("a").isEmpty()) throw Exception(element.select("h2").html()) if (element.select("a").isEmpty()) throw Exception(element.select("h2").html())
val chapter = SChapter.create() val chapter = SChapter.create()
@ -65,6 +96,11 @@ class HentaiVN : ParsedHttpSource() {
return chapter return chapter
} }
override fun chapterListRequest(manga: SManga): Request {
val mangaId = manga.url.substringAfterLast("/").substringBefore('-')
return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers)
}
private fun parseDate(dateString: String): Long { private fun parseDate(dateString: String): Long {
return try { return try {
dateFormat.parse(dateString)?.time ?: 0L dateFormat.parse(dateString)?.time ?: 0L
@ -73,33 +109,9 @@ class HentaiVN : ParsedHttpSource() {
} }
} }
override fun chapterListSelector() = "table.listing > tbody > tr"
override fun chapterListRequest(manga: SManga): Request {
val mangaId = manga.url.substringAfterLast("/").substringBefore('-')
return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers)
}
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
override fun latestUpdatesFromElement(element: Element): SManga { //Detail
val manga = SManga.create()
element.select(".box-description a").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text().trim()
}
manga.thumbnail_url = element.select(".box-cover a img").attr("data-src")
return manga
}
override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)"
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/chap-moi.html?page=$page", headers)
}
override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select(".main > .page-left > .left-info > .page-info") val infoElement = document.select(".main > .page-left > .left-info > .page-info")
val manga = SManga.create() val manga = SManga.create()
@ -107,8 +119,10 @@ class HentaiVN : ParsedHttpSource() {
manga.author = infoElement.select("p:contains(Tác giả:) a").text() manga.author = infoElement.select("p:contains(Tác giả:) a").text()
manga.description = infoElement.select(":root > p:contains(Nội dung:) + p").text() manga.description = infoElement.select(":root > p:contains(Nội dung:) + p").text()
manga.genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() } manga.genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() }
manga.thumbnail_url = document.select(".main > .page-right > .right-info > .page-ava > img").attr("src") manga.thumbnail_url =
manga.status = parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()) document.select(".main > .page-right > .right-info > .page-ava > img").attr("src")
manga.status =
parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text())
return manga return manga
} }
@ -119,28 +133,19 @@ class HentaiVN : ParsedHttpSource() {
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
//Pages
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() return document.select("#image > img").mapIndexed { i, e ->
val pageUrl = document.select("link[rel=canonical]").attr("href") Page(i, imageUrl = e.attr("abs:src"))
document.select("#image > img").forEachIndexed { i, e ->
pages.add(Page(i, pageUrl, e.attr("abs:src")))
} }
return pages
} }
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) //Search
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/danh-sach.html?page=$page", headers)
}
override fun popularMangaSelector() = latestUpdatesSelector()
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
if (document.select("p").toString().contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên")) if (document.select("p").toString()
.contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên")
)
throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm") throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm")
val mangas = document.select(searchMangaSelector()).map { element -> val mangas = document.select(searchMangaSelector()).map { element ->
@ -156,36 +161,58 @@ class HentaiVN : ParsedHttpSource() {
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select(".search-des > a").first().let { element.select(".search-des > a, .box-description a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text().trim() manga.title = it.text().trim()
} }
manga.thumbnail_url = element.select("div.search-img img").attr("abs:src") manga.thumbnail_url = element.select("div.search-img img, .box-cover a img").attr("abs:src")
return manga return manga
} }
override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)" override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)"
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/tim-kiem-truyen.html?key=$id", headers) private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers)
private fun searchMangaByIdParse(response: Response, ids: String): MangasPage { private fun searchMangaByIdParse(response: Response, ids: String): MangasPage {
val details = mangaDetailsParse(response) val details = mangaDetailsParse(response)
details.url = "/$ids-doc-truyen-id.html" details.url = "/$ids-doc-truyen-id.html"
return MangasPage(listOf(details), false) return MangasPage(listOf(details), false)
} }
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(
val authorFilter = (if (filters.isEmpty()) getFilterList() else filters).find { it is Author } as Author page: Int,
query: String,
filters: FilterList
): Observable<MangasPage> {
val authorFilter =
(if (filters.isEmpty()) getFilterList() else filters).find { it is Author } as Author
val searchAllFilter =
(if (filters.isEmpty()) getFilterList() else filters).find { it is Alls } as Alls
return when { return when {
authorFilter.state.isNotEmpty() -> client.newCall( authorFilter.state.isNotEmpty() -> client.newCall(
GET( GET(
searchByAuthorUrl.toHttpUrl().newBuilder() searchByAuthorUrl.toHttpUrl().newBuilder()
.addQueryParameter("key", authorFilter.state) .addQueryParameter("key", authorFilter.state)
.addQueryParameter("page", page.toString())
.build().toString(), .build().toString(),
headers headers
) )
) )
.asObservableSuccess() .asObservableSuccess()
.map { response -> latestUpdatesParse(response) } .map { response -> latestUpdatesParse(response) }
// Some manga that are not searchable in advanced search create this filter to fix
searchAllFilter.state.isNotEmpty() -> client.newCall(
GET(
searchAllURL.toHttpUrl().newBuilder()
.addQueryParameter("key", searchAllFilter.state)
.addQueryParameter("page", page.toString())
.build().toString(),
headers
)
)
.asObservableSuccess()
.map { response -> latestUpdatesParse(response) }
query.startsWith(PREFIX_ID_SEARCH) -> { query.startsWith(PREFIX_ID_SEARCH) -> {
val ids = query.removePrefix(PREFIX_ID_SEARCH) val ids = query.removePrefix(PREFIX_ID_SEARCH)
client.newCall(searchMangaByIdRequest(ids)) client.newCall(searchMangaByIdRequest(ids))
@ -200,12 +227,14 @@ class HentaiVN : ParsedHttpSource() {
else -> super.fetchSearchManga(page, query, filters) else -> super.fetchSearchManga(page, query, filters)
} }
} }
companion object { companion object {
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!!.newBuilder() val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!!
.newBuilder()
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
@ -225,8 +254,10 @@ class HentaiVN : ParsedHttpSource() {
return GET(url.toString(), headers) return GET(url.toString(), headers)
} }
override fun searchMangaSelector() = ".search-ul .search-li" override fun searchMangaSelector() =
".search-ul .search-li, .main > .block-left > .block-item > ul > li.item"
private class Alls : Filter.Text("Tìm tất cả")
private class Author : Filter.Text("Tác giả") private class Author : Filter.Text("Tác giả")
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class Genre(name: String, val id: String) : Filter.CheckBox(name) private class Genre(name: String, val id: String) : Filter.CheckBox(name)
@ -236,10 +267,14 @@ class HentaiVN : ParsedHttpSource() {
return name return name
} }
} }
private class GroupList(groups: Array<TransGroup>) : Filter.Select<TransGroup>("Nhóm dịch", groups)
private class GroupList(groups: Array<TransGroup>) :
Filter.Select<TransGroup>("Nhóm dịch", groups)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Filter.Header("Bộ lọc tìm tất cả không dùng được với bộ lọc khác!"),
Filter.Header("Bộ lọc tác giả không dùng được với các bộ lọc khác!"), Filter.Header("Bộ lọc tác giả không dùng được với các bộ lọc khác!"),
Alls(),
Author(), Author(),
TextField("Doujinshi", "dou"), TextField("Doujinshi", "dou"),
TextField("Nhân vật", "char"), TextField("Nhân vật", "char"),