diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt index 547ed0b3b..a399bd99c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt @@ -21,7 +21,7 @@ private const val SEARCH_PAGE_SIZE = 10 @MangaSourceParser("HENTAIVN", "HentaiVN", "vi", type = ContentType.HENTAI) internal class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HENTAIVN) { - override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("ayamehentai.cc") + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaihvn.tv", "ayamehentai.cc") override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/DocTruyen3Q.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/DocTruyen3Q.kt new file mode 100644 index 000000000..c5de80fd7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/DocTruyen3Q.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.wpcomics.vi + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaParserSource +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser + +@MangaSourceParser("DOCTRUYEN3Q", "DocTruyen3Q", "vi") +internal class DocTruyen3Q(context: MangaLoaderContext) : + WpComicsParser(context, MangaParserSource.DOCTRUYEN3Q, "doctruyen3qto.pro", 36) 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 new file mode 100644 index 000000000..62a7915b7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenUU.kt @@ -0,0 +1,133 @@ +package org.koitharu.kotatsu.parsers.site.wpcomics.vi + +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.* + +@MangaSourceParser("NETTRUYENUU", "NetTruyenUU", "vi") +internal class NetTruyenUU(context: MangaLoaderContext) : + WpComicsParser(context, MangaParserSource.NETTRUYENUU, "nettruyenuu.com", 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 + } +}