diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt index 34961d96..e0f45467 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt @@ -29,10 +29,10 @@ internal class CuuTruyenParser(context: MangaLoaderContext) : override val userAgentKey = ConfigKey.UserAgent(UserAgents.KOTATSU) override val configKeyDomain = ConfigKey.Domain( - "cuutruyenpip7z.site", "cuutruyen.net", "nettrom.com", - "hetcuutruyen.net" + "hetcuutruyen.net", + "cuutruyenpip7z.site" ) override fun onCreateConfig(keys: MutableCollection>) { 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 index 0bb2c5d4..3438c7dc 100644 --- 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 @@ -1,61 +1,108 @@ package org.koitharu.kotatsu.parsers.site.wpcomics.vi import org.jsoup.nodes.Document -import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* -import org.koitharu.kotatsu.parsers.network.UserAgents -import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat +import java.util.* -@Broken @MangaSourceParser("DOCTRUYEN3Q", "DocTruyen3Q", "vi") internal class DocTruyen3Q(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.DOCTRUYEN3Q, "doctruyen3qw.pro", 36) { + + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( + "doctruyen3qw.pro", "doctruyen3qvip.com", "doctruyen3q.link", + ) - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val datePattern = "dd/MM/yyyy" - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + ) - override val datePattern = "dd/MM/yyyy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + isTagsExclusionSupported = true, + isMultipleTagsSupported = false, + ) + + override suspend fun getFilterOptions(): MangaListFilterOptions { + return MangaListFilterOptions( + availableTags = availableTags(), + 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-truyen") + + if (filter.tags.isNotEmpty()) { + append("/") + append(filter.tags.first().key) + } + + val params = mutableListOf() + + when (order) { + SortOrder.UPDATED -> params.add("sort=1") + SortOrder.POPULARITY -> params.add("sort=2") + else -> {} + } + + filter.states.oneOrThrowIfMany()?.let { state -> + when (state) { + MangaState.ONGOING -> params.add("status=0") + MangaState.FINISHED -> params.add("status=1") + else -> params.add("status=2") + } + } + if (filter.query?.isNotEmpty() == true) { - append("?keyword=") - append(filter.query.urlEncoded()) + params.add("keyword=${filter.query.urlEncoded()}") } + if (page > 1) { - append(if (filter.query?.isNotEmpty() == true) "&" else "?") - append("page=") - append(page.toString()) + params.add("page=$page") + } + + if (params.isNotEmpty()) { + append("?") + append(params.joinToString("&")) } } + val doc = webClient.httpGet(url).parseHtml() return parseMangaList(doc) } private fun parseMangaList(doc: Document): List { - return doc.select("div.list-story-item").map { div -> - val href = div.selectFirstOrThrow("h3.title-book a").attrAsRelativeUrl("href") + val tags = availableTags() + return doc.select("div.item-manga").mapNotNull { div -> + val href = div.selectFirst("h3 a")?.attrAsRelativeUrlOrNull("href") ?: return@mapNotNull null + val tagElements = div.select("p.info a[href*=tim-truyen]") + val mangaTags = tagElements.mapNotNullToSet { a -> + val key = a.attr("href").substringAfterLast('/') + tags.find { it.key == key } + } + Manga( id = generateUid(href), url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("div.image img")?.src().orEmpty(), - title = div.selectFirst("h3.title-book a")?.text().orEmpty(), + coverUrl = div.selectFirst("div.image-item img")?.src().orEmpty(), + title = div.selectFirst("h3 a")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, - tags = emptySet(), + tags = mangaTags, author = null, state = null, source = source, @@ -65,53 +112,116 @@ internal class DocTruyen3Q(context: MangaLoaderContext) : } override suspend fun getDetails(manga: Manga): Manga { - val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + val availableTags = availableTags() + val tagElements = doc.select("li.category.row p.detail-info a[href*=tim-truyen]") + val tags = tagElements.mapNotNullToSet { a -> + val key = a.attr("href").substringAfterLast('/') + availableTags.find { it.key == key } + } + + val description = doc.selectFirst("div.summary-content p.detail-summary")?.html() + val authorText = doc.selectFirst("li.author.row p.detail-info")?.text().orEmpty() + + val author = if (authorText.isNotEmpty() && authorText != "Đang cập nhật") { + authorText + } else { + null + } + + val altTitleText = doc.selectFirst("li.name-other.row p.detail-info")?.text() + val altTitle = if (!altTitleText.isNullOrEmpty() && altTitleText != "Đang cập nhật") { + altTitleText + } else { + null + } + + val stateText = doc.selectFirst("li.status.row p.detail-info span.label")?.text() + val state = when (stateText) { + "Đang cập nhật" -> MangaState.ONGOING + "Đã hoàn thành" -> MangaState.FINISHED + else -> null + } + return manga.copy( - tags = doc.select("li.kind a").mapToSet { a -> - MangaTag( - key = a.attr("href").substringAfterLast('/'), - title = a.text().toTitleCase(sourceLocale), - source = source, - ) - }, - author = doc.selectFirst("li.author")?.text()?.substringAfter(':')?.trim(), - description = doc.selectFirst("div.detail-content p")?.html(), - state = when (doc.selectFirst("li.status")?.text()?.substringAfter(':')?.trim()) { - "Đang tiến hành" -> MangaState.ONGOING - "Đã hoàn thành" -> MangaState.FINISHED - else -> null - }, + author = author, + description = description, + state = state, + tags = tags, chapters = getChapters(doc), + altTitle = altTitle ) } override suspend fun getChapters(doc: Document): List { - val dateFormat = SimpleDateFormat(datePattern, sourceLocale) - return doc.select("div.list-chapter > a").mapChapters(reversed = true) { i, a -> - val href = a.attrAsRelativeUrl("href") - val dateText = a.selectFirst("span.chapter-time")?.text() + return doc.select("li.row:not([style*='display: none'])").mapChapters(reversed = true) { _, element -> + val chapterLink = element.selectFirst("a.chapter") ?: return@mapChapters null + val href = chapterLink.attrAsAbsoluteUrlOrNull("href") ?: return@mapChapters null + val name = chapterLink.text() + val number = chapterLink.attr("data-chapter")?.toFloatOrNull() ?: 0f + val timeElement = element.select("div.style-chap").firstOrNull() + val timeText = timeElement?.text() MangaChapter( id = generateUid(href), - name = a.selectFirst("span.chapter-text")?.text() ?: "Chapter ${i + 1}", - number = i + 1f, + name = name, + number = number, url = href, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), + uploadDate = parseChapterDate(timeText), source = source, scanlator = null, branch = null, - volume = 0, + volume = 0 ) } } + protected fun parseChapterDate(dateText: String?): Long { + if (dateText == null) return 0 + + val relativeTimePattern = Regex("(\\d+)\\s*(phút|giờ|ngày|tuần) trước") + val absoluteTimePattern = Regex("(\\d{2}-\\d{2}-\\d{4})") + + return when { + dateText.contains("phút trước") -> { + val match = relativeTimePattern.find(dateText) + val minutes = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - minutes * 60 * 1000 + } + dateText.contains("giờ trước") -> { + val match = relativeTimePattern.find(dateText) + val hours = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - hours * 3600 * 1000 + } + dateText.contains("ngày trước") -> { + val match = relativeTimePattern.find(dateText) + val days = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - days * 86400 * 1000 + } + dateText.contains("tuần trước") -> { + val match = relativeTimePattern.find(dateText) + val weeks = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - weeks * 7 * 86400 * 1000 + } + absoluteTimePattern.matches(dateText) -> { + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + try { + val parsedDate = formatter.parse(dateText) + parsedDate?.time ?: 0L + } catch (e: Exception) { + 0L + } + } + else -> 0L + } + } + override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - return doc.select("div.page-chapter img").map { img -> - val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") + return doc.select("div.page-chapter img").mapNotNull { img -> + val url = img.src()?.toRelativeUrl(domain) ?: return@mapNotNull null MangaPage( id = generateUid(url), url = url, @@ -120,4 +230,66 @@ internal class DocTruyen3Q(context: MangaLoaderContext) : ) } } + + private fun availableTags(): Set = setOf( + MangaTag("Action", "action", source), + MangaTag("Adult", "truong-thanh", source), + MangaTag("Adventure", "phieu-luu", source), + MangaTag("Anime", "anime", source), + MangaTag("Chuyển Sinh", "chuyen-sinh", source), + MangaTag("Comedy", "comedy", source), + MangaTag("Comic", "comic", source), + MangaTag("Cooking", "nau-an", source), + MangaTag("Cổ Đại", "co-dai", source), + MangaTag("Doujinshi", "doujinshi", source), + MangaTag("Drama", "drama", source), + MangaTag("Đam Mỹ", "dam-my", source), + MangaTag("Ecchi", "ecchi", source), + MangaTag("Fantasy", "fantasy", source), + MangaTag("Gender Bender", "gender-bender", source), + MangaTag("Harem", "harem", source), + MangaTag("Historical", "historical", source), + MangaTag("Horror", "horror", source), + MangaTag("Hệ Thống", "he-thong", source), + MangaTag("Josei", "josei", source), + MangaTag("Live action", "live-action", source), + MangaTag("Manga", "manga", source), + MangaTag("Manhua", "manhua", source), + MangaTag("Manhwa", "manhwa", source), + MangaTag("Martial Arts", "martial-arts", source), + MangaTag("Mature", "mature", source), + MangaTag("Mecha", "mecha", source), + MangaTag("Mystery", "mystery", source), + MangaTag("Ngôn Tình", "ngon-tinh", source), + MangaTag("NTR", "ntr", source), + MangaTag("One shot", "one-shot", source), + MangaTag("Psychological", "psychological", source), + MangaTag("Romance", "romance", source), + MangaTag("School Life", "school-life", source), + MangaTag("Sci-fi", "sci-fi", source), + MangaTag("Seinen", "seinen", source), + MangaTag("Shoujo", "shoujo", source), + MangaTag("Shoujo Ai", "shoujo-ai", source), + MangaTag("Shounen", "shounen", source), + MangaTag("Slice of Life", "slice-of-life", source), + MangaTag("Smut", "smut", source), + MangaTag("Soft Yaoi", "soft-yaoi", source), + MangaTag("Soft Yuri", "soft-yuri", source), + MangaTag("Sports", "sports", source), + MangaTag("Supernatural", "supernatural", source), + MangaTag("Thiếu Nhi", "thieu-nhi", source), + MangaTag("Tragedy", "tragedy", source), + MangaTag("Trinh Thám", "trinh-tham", source), + MangaTag("Truyện Màu", "truyen-mau", source), + MangaTag("Webtoon", "webtoon", source), + MangaTag("Xuyên Không", "xuyen-khong", source), + MangaTag("Yaoi", "yaoi", source), + MangaTag("Yuri", "yuri", source), + MangaTag("16+", "16", source), + MangaTag("18+", "18", source), + MangaTag("ABO", "abo", source), + MangaTag("BoyLove", "boylove", source), + MangaTag("Girl Love", "girl-love", source), + MangaTag("Người Thú", "nguoi-thu", source), + ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt index 2660cf18..a6295dd6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt @@ -8,8 +8,8 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser @MangaSourceParser("NETTRUYEN", "NetTruyen", "vi") internal class NetTruyen(context: MangaLoaderContext) : - WpComicsParser(context, MangaParserSource.NETTRUYEN, "www.nettruyenupp.com", 44) { + WpComicsParser(context, MangaParserSource.NETTRUYEN, "nettruyenww.com", 44) { override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( - "nettruyenupp.com", "nettruyenww.com", "nettruyenx.com", + "nettruyenww.com", "nettruyenx.com", ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyen.kt new file mode 100644 index 00000000..21b8ecba --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyen.kt @@ -0,0 +1,286 @@ +package org.koitharu.kotatsu.parsers.site.wpcomics.vi + +import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser +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("TOPTRUYEN", "TopTruyen", "vi") +internal class TopTruyen(context: MangaLoaderContext) : + WpComicsParser(context, MangaParserSource.TOPTRUYEN, "www.toptruyenww.pro", 36) { + + override val datePattern = "dd/MM/yyyy" + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + isTagsExclusionSupported = true, + isMultipleTagsSupported = false, + ) + + override suspend fun getFilterOptions(): MangaListFilterOptions { + return MangaListFilterOptions( + availableTags = availableTags(), + 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-truyen") + + if (filter.tags.isNotEmpty()) { + append("/") + append(filter.tags.first().key) + } + + val params = mutableListOf() + + when (order) { + SortOrder.UPDATED -> params.add("sort=1") + SortOrder.POPULARITY -> params.add("sort=2") + else -> {} + } + + filter.states.oneOrThrowIfMany()?.let { state -> + when (state) { + MangaState.ONGOING -> params.add("status=0") + MangaState.FINISHED -> params.add("status=1") + else -> params.add("status=2") + } + } + + if (filter.query?.isNotEmpty() == true) { + params.add("keyword=${filter.query.urlEncoded()}") + } + + if (page > 1) { + params.add("page=$page") + } + + if (params.isNotEmpty()) { + append("?") + params.joinTo(this, "&") + } + } + + val doc = webClient.httpGet(url).parseHtml() + return parseMangaList(doc) + } + + private fun parseMangaList(doc: Document): List { + val tags = availableTags() + return doc.select("div.item-manga").mapNotNull { div -> + val href = div.selectFirst("h3 a")?.attrAsRelativeUrlOrNull("href") ?: return@mapNotNull null + val tagElements = div.select("p.info a[href*=tim-truyen]") + val mangaTags = tagElements.mapNotNullToSet { a -> + val key = a.attr("href").substringAfterLast('/') + tags.find { it.key == key } + } + + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("div.image-item img")?.src().orEmpty(), + title = div.selectFirst("h3 a")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = mangaTags, + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + val availableTags = availableTags() + val tagElements = doc.select("li.category.row p.detail-info a[href*=tim-truyen]") + val tags = tagElements.mapNotNullToSet { a -> + val key = a.attr("href").substringAfterLast('/') + availableTags.find { it.key == key } + } + + val description = doc.selectFirst("div.summary-content p.detail-summary")?.html() + val authorText = doc.selectFirst("li.author.row p.detail-info")?.text().orEmpty() + + val author = if (authorText.isNotEmpty() && authorText != "Đang cập nhật") { + authorText + } else { + null + } + + val altTitleText = doc.selectFirst("li.name-other.row p.detail-info")?.text() + val altTitle = if (!altTitleText.isNullOrEmpty() && altTitleText != "Đang cập nhật") { + altTitleText + } else { + null + } + + val stateText = doc.selectFirst("li.status.row p.detail-info span.label")?.text() + val state = when (stateText) { + "Đang cập nhật" -> MangaState.ONGOING + "Đã hoàn thành" -> MangaState.FINISHED + else -> null + } + + return manga.copy( + author = author, + description = description, + state = state, + tags = tags, + chapters = getChapters(doc), + altTitle = altTitle + ) + } + + override suspend fun getChapters(doc: Document): List { + return doc.select("li.row:not([style*='display: none'])").mapChapters(reversed = true) { _, element -> + val chapterLink = element.selectFirst("a.chapter") ?: return@mapChapters null + val href = chapterLink.attrAsAbsoluteUrlOrNull("href") ?: return@mapChapters null + val name = chapterLink.text() + val number = chapterLink.attr("data-chapter")?.toFloatOrNull() ?: 0f + val timeElement = element.select("div.style-chap").firstOrNull() + val timeText = timeElement?.text() + MangaChapter( + id = generateUid(href), + name = name, + number = number, + url = href, + uploadDate = parseChapterDate(timeText), + source = source, + scanlator = null, + branch = null, + volume = 0 + ) + } + } + + protected fun parseChapterDate(dateText: String?): Long { + if (dateText == null) return 0 + + val relativeTimePattern = Regex("(\\d+)\\s*(phút|giờ|ngày|tuần) trước") + val absoluteTimePattern = Regex("(\\d{2}-\\d{2}-\\d{4})") + + return when { + dateText.contains("phút trước") -> { + val match = relativeTimePattern.find(dateText) + val minutes = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - minutes * 60 * 1000 + } + dateText.contains("giờ trước") -> { + val match = relativeTimePattern.find(dateText) + val hours = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - hours * 3600 * 1000 + } + dateText.contains("ngày trước") -> { + val match = relativeTimePattern.find(dateText) + val days = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - days * 86400 * 1000 + } + dateText.contains("tuần trước") -> { + val match = relativeTimePattern.find(dateText) + val weeks = match?.groups?.get(1)?.value?.toIntOrNull() ?: 0 + System.currentTimeMillis() - weeks * 7 * 86400 * 1000 + } + absoluteTimePattern.matches(dateText) -> { + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + try { + val parsedDate = formatter.parse(dateText) + parsedDate?.time ?: 0L + } catch (e: Exception) { + 0L + } + } + else -> 0L + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + return doc.select("div.page-chapter img").mapNotNull { img -> + val url = img.src()?.toRelativeUrl(domain) ?: return@mapNotNull null + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + private fun availableTags(): Set = setOf( + MangaTag("Action", "action", source), + MangaTag("Adult", "truong-thanh", source), + MangaTag("Adventure", "phieu-luu", source), + MangaTag("Anime", "anime", source), + MangaTag("Chuyển Sinh", "chuyen-sinh", source), + MangaTag("Comedy", "comedy", source), + MangaTag("Comic", "comic", source), + MangaTag("Cooking", "nau-an", source), + MangaTag("Cổ Đại", "co-dai", source), + MangaTag("Doujinshi", "doujinshi", source), + MangaTag("Drama", "drama", source), + MangaTag("Đam Mỹ", "dam-my", source), + MangaTag("Ecchi", "ecchi", source), + MangaTag("Fantasy", "fantasy", source), + MangaTag("Gender Bender", "gender-bender", source), + MangaTag("Harem", "harem", source), + MangaTag("Historical", "historical", source), + MangaTag("Horror", "horror", source), + MangaTag("Hệ Thống", "he-thong", source), + MangaTag("Josei", "josei", source), + MangaTag("Live action", "live-action", source), + MangaTag("Manga", "manga", source), + MangaTag("Manhua", "manhua", source), + MangaTag("Manhwa", "manhwa", source), + MangaTag("Martial Arts", "martial-arts", source), + MangaTag("Mature", "mature", source), + MangaTag("Mecha", "mecha", source), + MangaTag("Mystery", "mystery", source), + MangaTag("Ngôn Tình", "ngon-tinh", source), + MangaTag("One shot", "one-shot", source), + MangaTag("Psychological", "psychological", source), + MangaTag("Romance", "romance", source), + MangaTag("School Life", "school-life", source), + MangaTag("Sci-fi", "sci-fi", source), + MangaTag("Seinen", "seinen", source), + MangaTag("Shoujo", "shoujo", source), + MangaTag("Shoujo Ai", "shoujo-ai", source), + MangaTag("Shounen", "shounen", source), + MangaTag("Slice of Life", "slice-of-life", source), + MangaTag("Smut", "smut", source), + MangaTag("Soft Yaoi", "soft-yaoi", source), + MangaTag("Soft Yuri", "soft-yuri", source), + MangaTag("Sports", "sports", source), + MangaTag("Supernatural", "supernatural", source), + MangaTag("Thiếu Nhi", "thieu-nhi", source), + MangaTag("Tragedy", "tragedy", source), + MangaTag("Trinh Thám", "trinh-tham", source), + MangaTag("Truyện Màu", "truyen-mau", source), + MangaTag("Webtoon", "webtoon", source), + MangaTag("Xuyên Không", "xuyen-khong", source), + MangaTag("Yaoi", "yaoi", source), + MangaTag("Yuri", "yuri", source), + MangaTag("BoyLove", "boylove", source), + MangaTag("16+", "16", source), + ) +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyenViet.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyenViet.kt deleted file mode 100644 index 7140c7ad..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/TopTruyenViet.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.wpcomics.vi - -import org.jsoup.nodes.Document -import org.koitharu.kotatsu.parsers.Broken -import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaSourceParser -import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaPage -import org.koitharu.kotatsu.parsers.model.MangaParserSource -import org.koitharu.kotatsu.parsers.network.UserAgents -import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser -import org.koitharu.kotatsu.parsers.util.* -import java.text.SimpleDateFormat - -@Broken -@MangaSourceParser("TOPTRUYENVIET", "TopTruyen.pro", "vi") -internal class TopTruyenViet(context: MangaLoaderContext) : - WpComicsParser(context, MangaParserSource.TOPTRUYENVIET, "www.toptruyenww.pro", 36) { - - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) - - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - - override val selectPage = "div.page-chapter > img, div.chapter-content > img" - override val selectChapter = "div.list-chapter > a" - override val selectDate = "div.col-xs-4" - override val datePattern = "dd/MM/yyyy" - - override suspend fun getChapters(doc: Document): List { - val dateFormat = SimpleDateFormat(datePattern, sourceLocale) - return doc.select(selectChapter).mapChapters(reversed = true) { i, a -> - val href = a.attrAsRelativeUrl("href") - val dateText = a.selectFirst(selectDate)?.text() - MangaChapter( - id = generateUid(href), - name = a.text(), - number = i + 1f, - url = href, - uploadDate = parseChapterDate(dateFormat, dateText), - source = source, - scanlator = null, - branch = null, - volume = 0, - ) - } - } - - override suspend fun getPages(chapter: MangaChapter): List { - val fullUrl = chapter.url.toAbsoluteUrl(domain) - val doc = webClient.httpGet(fullUrl).parseHtml() - return doc.select(selectPage).map { img -> - val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") - MangaPage( - id = generateUid(url), - url = url, - preview = null, - source = source, - ) - } - } -}