diff --git a/.github/summary.yaml b/.github/summary.yaml index 2ff30448..f53e1972 100644 --- a/.github/summary.yaml +++ b/.github/summary.yaml @@ -1 +1 @@ -total: 1196 \ No newline at end of file +total: 1197 \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DamCoNuong.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DamCoNuong.kt new file mode 100644 index 00000000..a782c45c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DamCoNuong.kt @@ -0,0 +1,208 @@ +package org.koitharu.kotatsu.parsers.site.vi + +import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("DAMCONUONG", "Dâm Cô Nương", "vi", type = ContentType.HENTAI) +internal class DamCoNuong(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.DAMCONUONG, 30) { + + override val configKeyDomain = ConfigKey.Domain("damconuong.fit") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.ALPHABETICAL, + SortOrder.ALPHABETICAL_DESC, + SortOrder.UPDATED, + SortOrder.NEWEST, + SortOrder.POPULARITY, + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + val url = buildString { + append("https://") + append(domain) + append("/tim-kiem") + + append("?sort=") + append( + when (order) { + SortOrder.UPDATED -> "-updated_at" + SortOrder.NEWEST -> "-created_at" + SortOrder.POPULARITY -> "-views" + SortOrder.ALPHABETICAL -> "name" + SortOrder.ALPHABETICAL_DESC -> "-name" + else -> "-updated_at" + } + ) + + if (filter.states.isNotEmpty()) { + append("&filter[status]=") + filter.states.forEach { + append( + when (it) { + MangaState.ONGOING -> "2" + MangaState.FINISHED -> "1" + else -> "2,1" + }, + ) + } + } + + if (filter.tags.isNotEmpty()) { + append("&filter[accept_genres]=") + append(filter.tags.joinToString(",") { (it.key.toInt() + 1).toString() }) + } + + append("&page=$page") + } + + val doc = webClient.httpGet(url).parseHtml() + return parseMangaList(doc) + } + + private fun parseMangaList(doc: Document): List { + return doc.select("div.text-white.capitalize").map { element -> + val a = element.selectFirst("a") + val href = a?.attr("href") ?: "" + val title = a?.text().orEmpty() + + Manga( + id = generateUid(href), + title = title, + altTitles = emptySet(), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + contentRating = ContentRating.ADULT, + coverUrl = "", + tags = emptySet(), + state = null, + authors = emptySet(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val url = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(url).parseHtml() + + val altTitles = doc.select("div.mt-2:contains(Tên khác:) span").map { it.text() }.toSet().orEmpty() + val tags = doc.select("div.mt-2:contains(Thể loại:) a").map { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text(), + source = source, + ) + }.toSet() + + val stateText = doc.selectFirst("div.mt-2:contains(Tình trạng:) span")?.text() + val state = when (stateText) { + "Đang tiến hành" -> MangaState.ONGOING + else -> MangaState.FINISHED + } + + val chapterElements = doc.select("ul#chapterList li") + val chapters = chapterElements.mapIndexed { index, li -> + val title = li.selectFirst("span.text-ellipsis")?.text().orEmpty() + val href = li.selectFirst("a")?.attr("href")?.toAbsoluteUrl(domain) ?: "" + val uploadDate = li.selectFirst("span.ml-2.whitespace-nowrap")?.text().orEmpty() + + MangaChapter( + id = generateUid(href), + title = title, + number = (chapterElements.size - index).toFloat(), + volume = 0, + url = href, + scanlator = null, + uploadDate = parseChapterDate(uploadDate), + branch = null, + source = source, + ) + }.reversed() + + return manga.copy( + altTitles = altTitles, + tags = tags, + state = state, + chapters = chapters, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() + return doc.select("img.max-w-full.my-0.mx-auto").map { img -> + val url = img.requireSrc() + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + private fun parseChapterDate(date: String?): Long { + if (date == null) return 0 + return when { + date.contains("giây trước") -> System.currentTimeMillis() - date.removeSuffix(" giây trước").toLong() * 1000 + date.contains("phút trước") -> System.currentTimeMillis() - date.removeSuffix(" phút trước") + .toLong() * 60 * 1000 + + date.contains("giờ trước") -> System.currentTimeMillis() - date.removeSuffix(" giờ trước") + .toLong() * 60 * 60 * 1000 + + date.contains("ngày trước") -> System.currentTimeMillis() - date.removeSuffix(" ngày trước") + .toLong() * 24 * 60 * 60 * 1000 + + date.contains("tuần trước") -> System.currentTimeMillis() - date.removeSuffix(" tuần trước") + .toLong() * 7 * 24 * 60 * 60 * 1000 + + date.contains("tháng trước") -> System.currentTimeMillis() - date.removeSuffix(" tháng trước") + .toLong() * 30 * 24 * 60 * 60 * 1000 + + date.contains("năm trước") -> System.currentTimeMillis() - date.removeSuffix(" năm trước") + .toLong() * 365 * 24 * 60 * 60 * 1000 + + else -> SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L + } + } + + private suspend fun fetchTags(): Set { + val doc = webClient.httpGet("https://$domain/tim-kiem").parseHtml() + return doc.select("label.ml-3.inline-flex.items-center.cursor-pointer.select-none").map { label -> + val key = label.attr("onclick").substringAfter("toggleGenre('").substringBefore("')") + val title = label.selectFirst("span.ml-2.text-sm.font-semibold.text-blueGray-600.text-ellipsis")?.text() ?: "" + MangaTag( + key = key, + title = title, + source = source, + ) + }.toSet() + } + +} \ No newline at end of file