From 0c32b563788d45020983f7213a66bc5057c261d6 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 30 Apr 2025 00:12:23 +0700 Subject: [PATCH] [NetTruyen1975] Add source (#1736) * [UzayManga] Fix tags (Attempt 1) * [NetTruyen1975] Add source * [NetTruyen*] Fix getPages --------- Co-authored-by: Draken --- .github/summary.yaml | 2 +- .../kotatsu/parsers/site/tr/UzayManga.kt | 10 +- .../parsers/site/wpcomics/vi/NetTruyen1975.kt | 164 ++++++++++++++++++ .../parsers/site/wpcomics/vi/NetTruyenFE.kt | 3 +- .../parsers/site/wpcomics/vi/NetTruyenLL.kt | 3 +- .../parsers/site/wpcomics/vi/NetTruyenSSR.kt | 3 +- .../parsers/site/wpcomics/vi/NetTruyenUU.kt | 3 +- 7 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen1975.kt diff --git a/.github/summary.yaml b/.github/summary.yaml index fa492f1bd..956935195 100644 --- a/.github/summary.yaml +++ b/.github/summary.yaml @@ -1 +1 @@ -total: 1215 +total: 1216 \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/UzayManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/UzayManga.kt index beb747bd0..57f334654 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/UzayManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/UzayManga.kt @@ -183,17 +183,17 @@ internal class UzayManga(context: MangaLoaderContext): private suspend fun fetchTags(): Set { val doc = webClient.httpGet("https://$domain/search").parseHtml() - val script = doc.select("script").find { it.html().contains("\"category\":[{\"id\":") }?.html() ?: return emptySet() + val script = doc.select("script").find { it.html().contains("self.__next_f.push([1,\"10:[\\\"\\$,\\\"section") }?.html() + ?: return emptySet() - val jsonStr = script - .substringAfter("category\":[") - .substringBefore("],\"searchParams\"") + val jsonStr = script.substringAfter("\"category\":[") + .substringBefore("],\"searchParams\":{}") .replace("\\", "") val jsonArray = JSONArray("[$jsonStr]") return jsonArray.mapJSONToSet { jo -> MangaTag( - key = jo.getString("id"), + key = jo.getString("id"), title = jo.getString("name"), source = source ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen1975.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen1975.kt new file mode 100644 index 000000000..e05921bea --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen1975.kt @@ -0,0 +1,164 @@ +package org.koitharu.kotatsu.parsers.site.wpcomics.vi + +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Element +import androidx.collection.ArrayMap +import kotlinx.coroutines.sync.withLock +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.exception.NotFoundException +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser +import org.koitharu.kotatsu.parsers.util.* +import java.util.EnumSet + +@MangaSourceParser("NETTRUYEN1975", "NetTruyen1975", "vi") +internal class NetTruyen1975(context: MangaLoaderContext) : + WpComicsParser(context, MangaParserSource.NETTRUYEN1975, WpComicsParser.netDomain, 20) { + + override val listUrl = "/tim-kiem-nang-cao" + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.RATING, + SortOrder.NEWEST, + SortOrder.ALPHABETICAL, + SortOrder.ALPHABETICAL_DESC, + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = super.filterCapabilities.copy( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + ) + + override suspend fun getFilterOptions() = super.getFilterOptions().copy( + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + val response = + when { + !filter.query.isNullOrEmpty() -> { + val url = buildString { + append("https://") + append(domain) + append("/search") + append('/') + append(page.toString()) + append('/') + append("?keyword=") + append(filter.query.urlEncoded()) + } + + val result = runCatchingCancellable { webClient.httpGet(url) } + val exception = result.exceptionOrNull() + if (exception is NotFoundException) { + return emptyList() + } + result.getOrThrow() + } + + else -> { + val url = buildString { + append("https://") + append(domain) + append(listUrl) + + append('/') + append(page.toString()) + append('/') + + val tagQuery = filter.tags.joinToString(",") { it.key } + append("?genres=") + append(tagQuery) + + val tagQueryExclude = filter.tagsExclude.joinToString(",") { it.key } + append("¬Genres=") + append(tagQueryExclude) + + append("&sex=All") + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "on-going" + MangaState.FINISHED -> "completed" + MangaState.PAUSED -> "on-hold" + MangaState.ABANDONED -> "canceled" + else -> "-1" + }, + ) + } + + append("&chapter_count=0") + + append("&sort=") + append( + when (order) { + SortOrder.UPDATED -> "latest-updated" + SortOrder.POPULARITY -> "views" + SortOrder.NEWEST -> "new" + SortOrder.RATING -> "score" + SortOrder.ALPHABETICAL -> "az" + SortOrder.ALPHABETICAL_DESC -> "za" + else -> null + }, + ) + } + + webClient.httpGet(url) + } + } + + val tagMap = getOrCreateTagMap() + return parseMangaList(response.parseHtml(), tagMap) + } + + override suspend fun getOrCreateTagMap(): ArrayMap = mutex.withLock { + tagCache?.let { return@withLock it } + val doc = webClient.httpGet(listUrl.toAbsoluteUrl(domain)).parseHtml() + val tagItems = doc.select("div.genre-item") + val result = ArrayMap(tagItems.size) + for (item in tagItems) { + val title = item.text() + val key = item.selectFirstOrThrow("span").attr("data-id") + if (key.isNotEmpty() && title.isNotEmpty()) { + result[title] = MangaTag(title = title, key = key, source = source) + } + } + tagCache = result + result + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val parentDiv = doc.select("div.page-chapter#page_2").firstOrNull()?.parent() ?: return emptyList() + return coroutineScope { + parentDiv.select("div.page-chapter img").map { img -> + async { fetchPage(img) } + }.awaitAll().filterNotNull() + } + } + + private suspend fun fetchPage(img: Element): MangaPage? = runCatchingCancellable { + val url = img.attrAsRelativeUrlOrNull("data-original") ?: return@runCatchingCancellable null + webClient.httpHead(url).use { response -> + if (response.mimeType?.startsWith("image/") == true) { + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } else { + null + } + } + }.getOrNull() +} \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenFE.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenFE.kt index 98ba3e138..86376c98e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenFE.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenFE.kt @@ -138,8 +138,9 @@ internal class NetTruyenFE(context: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() + val parentDiv = doc.select("div.page-chapter#page_2").firstOrNull()?.parent() ?: return emptyList() return coroutineScope { - doc.select(selectPage).map { img -> + parentDiv.select("div.page-chapter img").map { img -> async { fetchPage(img) } }.awaitAll().filterNotNull() } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt index 05e9f1a41..90961691a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt @@ -138,8 +138,9 @@ internal class NetTruyenLL(context: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() + val parentDiv = doc.select("div.page-chapter#page_2").firstOrNull()?.parent() ?: return emptyList() return coroutineScope { - doc.select(selectPage).map { img -> + parentDiv.select("div.page-chapter img").map { img -> async { fetchPage(img) } }.awaitAll().filterNotNull() } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt index 7c58ab9fd..be097fd39 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt @@ -138,8 +138,9 @@ internal class NetTruyenSSR(context: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() + val parentDiv = doc.select("div.page-chapter#page_2").firstOrNull()?.parent() ?: return emptyList() return coroutineScope { - doc.select(selectPage).map { img -> + parentDiv.select("div.page-chapter img").map { img -> async { fetchPage(img) } }.awaitAll().filterNotNull() } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenUU.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenUU.kt index 57e4896db..f96b6b7de 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenUU.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenUU.kt @@ -138,8 +138,9 @@ internal class NetTruyenUU(context: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() + val parentDiv = doc.select("div.page-chapter#page_2").firstOrNull()?.parent() ?: return emptyList() return coroutineScope { - doc.select(selectPage).map { img -> + parentDiv.select("div.page-chapter img").map { img -> async { fetchPage(img) } }.awaitAll().filterNotNull() }