From fa78d5dd6faa2087ce2dd974b1c105b4e9ce9142 Mon Sep 17 00:00:00 2001 From: vianh Date: Tue, 21 Feb 2023 00:47:53 +0700 Subject: [PATCH] [TruyentranhLH]: New source --- .../parsers/site/TruyentranhLHParser.kt | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/TruyentranhLHParser.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/TruyentranhLHParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/TruyentranhLHParser.kt new file mode 100644 index 00000000..715c3a1a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/TruyentranhLHParser.kt @@ -0,0 +1,154 @@ +package org.koitharu.kotatsu.parsers.site + +import androidx.collection.ArrayMap +import androidx.collection.ArraySet +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("TRUYENTRANHLH", "TruyentranhLH", "vi") +class TruyentranhLHParser(context: MangaLoaderContext) : + PagedMangaParser(context, source = MangaSource.TRUYENTRANHLH, pageSize = 18) { + + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyentranhlh.net", null) + override val sortOrders: Set = EnumSet.allOf(SortOrder::class.java) + + private val mutex = Mutex() + private var tagCache: Map? = null + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val infoHeaderEl = docs.selectFirst("main.section-body") + val infoEl = docs.selectFirst("main.section-body .series-information") + val tags = infoEl?.select(".info-item:contains(Thể loại) > .info-value > a")?.mapNotNullToSet { + getOrCreateTagMap()[it.text().trim()] + } + val state = when (infoEl?.selectFirst(".info-item:contains(Tình trạng) > .info-value")?.text()) { + "Đang tiến hành" -> MangaState.ONGOING + "Đã hoàn thành" -> MangaState.FINISHED + else -> null + } + val rating = infoHeaderEl?.let { + val like = it.selectFirst("#like .block.feature-name")?.text()?.toIntOrNull() + val disLike = it.selectFirst("#dislike .block.feature-name")?.text()?.toIntOrNull() + when { + like == null || disLike == null -> RATING_UNKNOWN + like == 0 && disLike == 0 -> RATING_UNKNOWN + else -> like.toFloat() / (like + disLike) + } + } + val chapterDateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US) + + return manga.copy( + altTitle = infoEl?.selectFirst(".info-item:contains(Tên khác) > .info-value")?.text(), + author = infoEl?.select(".info-item:contains(Tác giả) > .info-value")?.joinToString { it.text() }, + tags = tags ?: emptySet(), + state = state, + rating = rating ?: RATING_UNKNOWN, + description = infoHeaderEl?.selectFirst(".series-summary .summary-content")?.html(), + chapters = docs.select("ul.list-chapters.at-series > a").reversed().mapChapters { index, element -> + MangaChapter( + id = generateUid(element.attrAsRelativeUrl("href")), + name = element.selectFirst(".chapter-name")?.text()?.trim().orEmpty(), + number = index + 1, + url = element.attrAsRelativeUrl("href"), + scanlator = null, + uploadDate = chapterDateFormat.tryParse(element.selectFirst(".chapter-time")?.text()), + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val sortQuery = when (sortOrder) { + SortOrder.UPDATED -> "update" + SortOrder.NEWEST -> "new" + SortOrder.RATING -> "like" + SortOrder.POPULARITY -> "top" + SortOrder.ALPHABETICAL -> "az" + } + val url = urlBuilder().apply { + addPathSegment("tim-kiem") + addQueryParameter("sort", sortQuery) + addQueryParameter("page", page.toString()) + if (!query.isNullOrEmpty()) { + addQueryParameter("q", query) + } + if (!tags.isNullOrEmpty()) { + val tagsQuery = tags.joinToString(separator = ",") { it.key } + addEncodedQueryParameter("accept_genres", tagsQuery) + } + }.build() + + return webClient.httpGet(url).parseHtml() + .select(".container .card.card-dark .row > .thumb-item-flow") + .mapNotNull { + val a = it.selectFirstOrThrow(".thumb-wrapper a") + Manga( + id = generateUid(a.attrAsRelativeUrl("href")), + url = a.attrAsRelativeUrl("href"), + publicUrl = a.attrAsAbsoluteUrl("href"), + title = it.select(".thumb_attr.series-title").text(), + altTitle = null, + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = a.selectFirst("div[data-bg]")?.attrAsAbsoluteUrl("data-bg").orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val url = chapter.url.toAbsoluteUrl(domain) + return webClient.httpGet(url).parseHtml().select("#chapter-content > img").mapNotNull { + val imageUrl = it.attrAsRelativeUrlOrNull("data-src") + ?: it.attrAsRelativeUrlOrNull("src") + ?: return@mapNotNull null + MangaPage( + id = generateUid(imageUrl), + url = imageUrl, + referer = url, + preview = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + return ArraySet(getOrCreateTagMap().values) + } + + private suspend fun getOrCreateTagMap(): Map = mutex.withLock { + tagCache?.let { return it } + val docs = webClient.httpGet("/tim-kiem".toAbsoluteUrl(domain)).parseHtml() + val tags = docs.select(".search-border-left .row > .search-gerne_item").mapNotNull { + MangaTag( + title = it.text().trim(), + key = it.selectFirst("label[data-genre-id]") + ?.attr("data-genre-id") + ?.trim() ?: return@mapNotNull null, + source = source, + ) + } + val tagMap = tags.associateByTo(ArrayMap(tags.size)) { it.title } + tagCache = tagMap + return tagMap + } +}