From a9176c529b6c9afc0ab3cdd32e56fc29a2b4e270 Mon Sep 17 00:00:00 2001 From: are-are-are <62763969+dejavui@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:13:52 +0700 Subject: [PATCH] NhatTruyen update domain & fix missing chapter & fix search (#9175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bump version * Update domain and Fix missing chapter * fix build * Use suggest * Fix Search no results * ¯\_(ツ)_/¯ * Change fetchChapterList to chaplistRequest --- src/vi/nhattruyen/build.gradle | 4 +- .../extension/vi/nhattruyen/ChapterDTO.kt | 16 ++ .../extension/vi/nhattruyen/NhatTruyen.kt | 184 +++++------------- 3 files changed, 71 insertions(+), 133 deletions(-) create mode 100644 src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/ChapterDTO.kt diff --git a/src/vi/nhattruyen/build.gradle b/src/vi/nhattruyen/build.gradle index de3980267..2dc5b1192 100644 --- a/src/vi/nhattruyen/build.gradle +++ b/src/vi/nhattruyen/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'NhatTruyen' extClass = '.NhatTruyen' themePkg = 'wpcomics' - baseUrl = 'https://nhattruyenv.com' - overrideVersionCode = 20 + baseUrl = 'https://nhattruyenqq.com' + overrideVersionCode = 21 isNsfw = false } diff --git a/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/ChapterDTO.kt b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/ChapterDTO.kt new file mode 100644 index 000000000..c5c689e6c --- /dev/null +++ b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/ChapterDTO.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.vi.nhattruyen + +import kotlinx.serialization.Serializable + +@Serializable +class ChapterDTO( + val data: ArrayList = arrayListOf(), +) + +@Serializable +class Data( + val chapter_name: String, + val chapter_slug: String, + val updated_at: String, + +) diff --git a/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt index 8112cc8d9..92af77ec6 100644 --- a/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt +++ b/src/vi/nhattruyen/src/eu/kanade/tachiyomi/extension/vi/nhattruyen/NhatTruyen.kt @@ -4,177 +4,99 @@ import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import keiyoushi.utils.parseAs +import keiyoushi.utils.tryParse import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response -import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale class NhatTruyen : WPComics( "NhatTruyen", - "https://nhattruyenv.com", + "https://nhattruyenqq.com", "vi", dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()), gmtOffset = null, ) { override val searchPath = "tim-truyen" + override val popularPath = "truyen-tranh-hot" + /** * NetTruyen/NhatTruyen redirect back to catalog page if searching query is not found. * That makes both sites always return un-relevant results when searching should return empty. */ - override fun searchMangaParse(response: Response): MangasPage { - if (response.request.url.toString().endsWith("/$searchPath")) { - return MangasPage(mangas = emptyList(), hasNextPage = false) - } - return super.searchMangaParse(response) - } - // Advanced search override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isBlank()) { - val url = "$baseUrl/tim-truyen-nang-cao".toHttpUrl().newBuilder() + val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder() - filters.forEach { filter -> - when (filter) { - is AdvancedGenreFilter -> { - filter.included.let { url.addQueryParameter("genres", it.joinToString(",")) } - filter.excluded.let { url.addQueryParameter("notgenres", it.joinToString(",")) } - } - is AdvancedStatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) } - is ChaptersNumFilter -> filter.toUriPart()?.let { url.addQueryParameter("minchapter", it) } - is GenderFilter -> filter.toUriPart()?.let { url.addQueryParameter("gender", it) } - is OrderFilter -> filter.toUriPart()?.let { url.addQueryParameter("sort", it) } - else -> {} - } - } - - url.apply { - addQueryParameter("page", page.toString()) - } - - return GET(url.toString(), headers) - } else { - return super.searchMangaRequest(page, query, filters) - } - } - - private class AdvancedGenre(val id: String, name: String) : Filter.TriState(name) - - private class AdvancedGenreFilter(name: String, advancedGenres: List) : Filter.Group( - name, - advancedGenres.map { AdvancedGenre(it.id, it.name) }, - ) { - val included: List - get() = state.filter { it.isIncluded() }.map { it.id } - - val excluded: List - get() = state.filter { it.isExcluded() }.map { it.id } - } - - private var advancedGenresList: List = emptyList() - - private var fetchAdvancedGenresAttempts: Int = 0 - - private fun fetchAdvancedGenres() { - if (fetchAdvancedGenresAttempts < 3 && advancedGenresList.isEmpty()) { - try { - advancedGenresList = - client.newCall(advancedGenresRequest()).execute() - .asJsoup() - .let(::parseAdvancedGenres) - } catch (_: Exception) { - } finally { - fetchAdvancedGenresAttempts++ + filters.forEach { filter -> + when (filter) { + is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) } + is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) } + is OrderByFilter -> filter.toUriPart()?.let { url.addQueryParameter("sort", it) } + else -> {} } } - } - private fun advancedGenresRequest() = GET("$baseUrl/tim-truyen-nang-cao", headers) - - private fun parseAdvancedGenres(document: Document): List { - val items = document.select(".advsearch-form .genre-item") - return buildList(items.size) { - items.mapTo(this) { - AdvancedGenre( - it.select("span").attr("data-id"), - it.ownText(), - ) - } + url.apply { + addQueryParameter(queryParam, query) + addQueryParameter("page", page.toString()) } + + return GET(url.toString(), headers) } - - private class ChaptersNumFilter : UriPartFilter( - "Số lượng chapter", - listOf( - Pair("1", "> 0 chapter"), - Pair("50", ">= 50 chapter"), - Pair("100", ">= 100 chapter"), - Pair("200", ">= 200 chapter"), - Pair("300", ">= 300 chapter"), - Pair("400", ">= 400 chapter"), - Pair("500", ">= 500 chapter"), - ), - ) - - private class AdvancedStatusFilter(name: String, pairs: List>) : UriPartFilter(name, pairs) - - private fun getAdvancedStatusList(): List> = - listOf( - Pair("-1", intl["STATUS_ALL"]), - Pair("1", intl["STATUS_ONGOING"]), - Pair("2", intl["STATUS_COMPLETED"]), - ) - - private class GenderFilter : UriPartFilter( - "Dành cho", - listOf( - Pair("-1", "Tất cả"), - Pair("1", "Con gái"), - Pair("2", "Con trai"), - ), - ) - - private class OrderFilter : UriPartFilter( + private class OrderByFilter : UriPartFilter( "Sắp xếp theo", listOf( - Pair("0", "Chapter mới"), + Pair("0", "Ngày cập nhật"), Pair("15", "Truyện mới"), - Pair("10", "Xem nhiều nhất"), - Pair("11", "Xem nhiều nhất tháng"), - Pair("12", "Xem nhiều nhất tuần"), - Pair("13", "Xem nhiều nhất hôm nay"), - Pair("20", "Theo dõi nhiều nhất"), - Pair("25", "Bình luận nhiều nhất"), - Pair("30", "Số chapter nhiều nhất"), + Pair("10", "Top all"), + Pair("11", "Top tháng"), + Pair("12", "Top tuần"), + Pair("13", "Top ngày"), + Pair("20", "Top theo dõi"), + Pair("25", "Bình luận"), + Pair("30", "Số chapter"), ), ) - override fun getFilterList(): FilterList { - launchIO { fetchAdvancedGenres() } launchIO { fetchGenres() } return FilterList( - Filter.Header("Filter cho hộp tìm kiếm"), StatusFilter(intl["STATUS"], getStatusList()), + OrderByFilter(), if (genreList.isEmpty()) { Filter.Header(intl["GENRES_RESET"]) } else { GenreFilter(intl["GENRE"], genreList) }, - Filter.Separator(), - Filter.Header("Tìm truyện nâng cao\n(Không sử dụng cùng với hộp tìm kiếm)"), - if (advancedGenresList.isEmpty()) { - Filter.Header(intl["GENRES_RESET"]) - } else { - AdvancedGenreFilter(intl["GENRE"], advancedGenresList) - }, - AdvancedStatusFilter(intl["STATUS"], getAdvancedStatusList()), - ChaptersNumFilter(), - GenderFilter(), - OrderFilter(), ) } + + override fun chapterListRequest(manga: SManga): Request { + val slug = manga.url.substringAfterLast("/") // slug + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegment("Comic/Services/ComicService.asmx/ChapterList") + .addQueryParameter("slug", slug) + .build() + return GET(url, headers) + } + + override fun chapterListParse(response: Response): List { + val json = response.parseAs() + val slug = response.request.url.queryParameter("slug")!! + val chapter = json.data.map { + SChapter.create().apply { + setUrlWithoutDomain("$baseUrl/truyen-tranh/$slug/${it.chapter_slug}") + name = it.chapter_name + date_upload = dateFormatChapter.tryParse(it.updated_at) + } + } + return chapter + } + + private val dateFormatChapter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) }