diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt index e55f7416d..8f1c1595f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt @@ -8,6 +8,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MAIDSCAN", "MaidScan", "pt", ContentType.HENTAI) internal class MaidScan(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.MAIDSCAN, "maidscans.com", 10) { + MadaraParser(context, MangaParserSource.MAIDSCAN, "empreguetes.site", 10) { override val datePattern: String = "dd/MM/yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Quaanhdaocuteo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Quaanhdaocuteo.kt index b0a155e02..c8a9cdc5b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Quaanhdaocuteo.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Quaanhdaocuteo.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.madara.vi -import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.ContentType @@ -10,10 +9,9 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.util.* -@Broken @MangaSourceParser("QUAANHDAOCUTEO", "Quaanhdaocuteo", "vi", ContentType.HENTAI) internal class Quaanhdaocuteo(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.QUAANHDAOCUTEO, "qadc.top") { + MadaraParser(context, MangaParserSource.QUAANHDAOCUTEO, "qadc.xyz") { override val datePattern = "dd/MM/yyyy" override val selectPage = "p img" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt index af53853e3..8654a8917 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.parsers.site.vi +import androidx.collection.arraySetOf import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -7,17 +8,12 @@ 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.util.* -import java.text.SimpleDateFormat import java.util.* -import org.koitharu.kotatsu.parsers.Broken -@Broken -@MangaSourceParser("LXMANGA", "LxManga", "vi") +@MangaSourceParser("LXMANGA", "LxManga", "vi", type = ContentType.HENTAI) internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LXMANGA, 60) { - override val configKeyDomain = ConfigKey.Domain("lxmanga.online") - - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val configKeyDomain = ConfigKey.Domain("lxmanga.site") override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) @@ -32,14 +28,13 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.POPULARITY, ) - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), + availableTags = availableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), ) @@ -51,109 +46,138 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, when { !filter.query.isNullOrEmpty() -> { - val skey = "filter[name]=".urlEncoded() - append("/tim-kiem?$skey") + append("/tim-kiem") + append("?filter[name]=") append(filter.query.urlEncoded()) - append("&page=") - append(page.toString()) + + if (page > 1) { + append("&page=") + append(page.toString()) + } + + append("&sort=") + append(when (order) { + SortOrder.POPULARITY -> "-views" + SortOrder.UPDATED -> "-updated_at" + SortOrder.NEWEST -> "-created_at" + SortOrder.ALPHABETICAL -> "name" + SortOrder.ALPHABETICAL_DESC -> "-name" + else -> "-updated_at" + }) } - else -> { - if (filter.tags.isNotEmpty()) { - filter.tags.oneOrThrowIfMany()?.let { - append("/the-loai/") - append(it.key) - } - } else { - append("/danh-sach") - } + filter.tags.isNotEmpty() -> { + val tag = filter.tags.first() + append("/the-loai/") + append(tag.key) + append("?page=") - append(page.toString()) - - if (filter.states.isNotEmpty()) { - append("&filter[status]=") - filter.states.forEach { - append( - when (it) { - MangaState.ONGOING -> "2," - MangaState.FINISHED -> "1," - else -> "1,2" - }, - ) - } - } + append(page) + } - append("&sort=") - when (order) { - SortOrder.POPULARITY -> append("-views") - SortOrder.UPDATED -> append("-updated_at") - SortOrder.NEWEST -> append("-created_at") - SortOrder.ALPHABETICAL -> append("name") - SortOrder.ALPHABETICAL_DESC -> append("-name") - else -> append("-updated_at") - } + else -> { + append("/danh-sach") + append("?sort=") + append(when (order) { + SortOrder.POPULARITY -> "-views" + SortOrder.UPDATED -> "-updated_at" + SortOrder.NEWEST -> "-created_at" + SortOrder.ALPHABETICAL -> "name" + SortOrder.ALPHABETICAL_DESC -> "-name" + else -> "-updated_at" + }) + append("&page=") + append(page) } } + if (filter.query.isNullOrEmpty()) { + append("&sort=") + when (order) { + SortOrder.POPULARITY -> append("-views") + SortOrder.UPDATED -> append("-updated_at") + SortOrder.NEWEST -> append("-created_at") + SortOrder.ALPHABETICAL -> append("name") + SortOrder.ALPHABETICAL_DESC -> append("-name") + else -> append("-updated_at") + } + } + + if (filter.states.isNotEmpty()) { + append("&filter[status]=") + filter.states.forEach { + append( + when (it) { + MangaState.ONGOING -> "2," + MangaState.FINISHED -> "1," + else -> "1,2" + }, + ) + } + } } val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.grid div.manga-vertical") - .map { div -> - val href = div.selectFirstOrThrow("a").attr("href") - val img = div.selectFirstOrThrow(".cover").attr("style").substringAfter("url('").substringBefore("')") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("a.text-ellipsis").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = img, - tags = setOf(), - state = null, - author = null, - source = source, - ) - } + return doc.select("div.grid div.relative").map { div -> + val href = div.selectFirst("a[href^=/truyen/]")?.attr("href") + ?: div.parseFailed("Không thể tìm thấy nguồn ảnh của Manga này!") + val coverStyle = div.select("div.cover").attr("data-bg") + val coverUrl = coverStyle.takeIf { it.isNotEmpty() } ?: div.select("div.cover").attr("style") + .substringAfter("url('").substringBefore("')") + .replace("s3.lxmanga.top", domain) + + Manga( + id = generateUid(href), + title = div.select("div.p-2 a.text-ellipsis").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = coverUrl, + tags = setOf(), + state = null, + author = null, + source = source, + ) + } } override suspend fun getDetails(manga: Manga): Manga { val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) + return manga.copy( - altTitle = root.select(".divider2:contains(Noms associés :)").firstOrNull()?.text(), - state = when (root.select("div.grow div.mt-2:contains(Tình trạng) a").first()!!.text()) { + altTitle = root.select("div.grow div:contains(Tên khác) span").lastOrNull()?.text(), + state = when (root.select("div.mt-2:contains(Tình trạng) span.text-blue-500").firstOrNull()?.text()?.trim()) { "Đang tiến hành" -> MangaState.ONGOING "Đã hoàn thành" -> MangaState.FINISHED else -> null }, - tags = root.select("div.grow div.mt-2:contains(Thể loại) span a").mapNotNullToSet { a -> + tags = root.select("div.mt-2:contains(Thể loại) a.bg-gray-500").mapNotNullToSet { a -> MangaTag( key = a.attr("href").removeSuffix("/").substringAfterLast('/'), - title = a.text().toTitleCase(), + title = a.text().trim(), source = source, ) }, - author = root.select("div.grow div.mt-2:contains(Tác giả) span a") - .joinToString { it.text().trim(',', ' ') }, - description = root.selectFirst("div.py-4.border-t")?.html(), - chapters = root.select("ul.overflow-y-auto.overflow-x-hidden a") + author = root.select("div.mt-2:contains(Tác giả) span a").firstOrNull()?.text()?.trim(), + description = root.select("meta[name=description]").firstOrNull()?.attr("content")?.trim(), + chapters = root.select("div.justify-between ul.overflow-y-auto.overflow-x-hidden a") .mapChapters(reversed = true) { i, a -> - val href = a.attr("href") - val name = a.selectFirstOrThrow("span.text-ellipsis").text() - val date = a.selectFirstOrThrow("span.timeago").attr("datetime") + val name = a.selectFirst("span.text-ellipsis")?.text() ?: "" + val dateText = a.parent()?.selectFirst("span.timeago")?.attr("datetime") ?: "" + val scanlator = root.select("div.mt-2:contains(Nhóm dịch) span a").firstOrNull()?.text()?.trim() + MangaChapter( id = generateUid(href), name = name, number = i.toFloat(), volume = 0, url = href, - scanlator = null, - uploadDate = dateFormat.tryParse(date), + scanlator = scanlator, + uploadDate = parseDateTime(dateText), branch = null, source = source, ) @@ -164,27 +188,113 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - return doc.select("div.text-center img.lazy-image").map { img -> - val url = img.attrAsRelativeUrlOrNull("data-src") ?: img.attrAsRelativeUrlOrNull("src") - ?: img.parseFailed("Image src not found") - MangaPage( - id = generateUid(url), - url = url, - preview = null, - source = source, - ) - } + return doc.select("div.text-center div.lazy") + .mapNotNull { div -> + val url = div.attr("data-src") + if (url.endsWith(".jpg", ignoreCase = true) || + url.endsWith(".png", ignoreCase = true)) { + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } else null + } } - private suspend fun fetchAvailableTags(): Set { - val doc = webClient.httpGet("https://$domain/").parseHtml() - val body = doc.body() - return body.select("ul.absolute.w-full a").mapToSet { a -> - MangaTag( - key = a.attr("href").removeSuffix("/").substringAfterLast('/'), - title = a.selectFirstOrThrow("span.text-ellipsis").text(), - source = source, + private fun availableTags() = arraySetOf( + MangaTag("3D", "3d", source), + MangaTag("Adult", "adult", source), + MangaTag("Animal girl", "animal-girl", source), + MangaTag("Artist", "artist", source), + MangaTag("Bạo Dâm", "bao-dam", source), + MangaTag("CG", "cg", source), + MangaTag("Chơi Hai Lỗ", "choi-hai-lo", source), + MangaTag("Côn Trùng", "con-trung", source), + MangaTag("Cosplay", "cosplay", source), + MangaTag("Creampie", "creampie", source), + MangaTag("Doujinshi", "doujinshi", source), + MangaTag("Drama", "drama", source), + MangaTag("Đam Mỹ", "dam-my", source), + MangaTag("Đồ Bơi", "do-boi", source), + MangaTag("Elf", "elf", source), + MangaTag("Fantasy", "fantasy", source), + MangaTag("Furry", "furry", source), + MangaTag("Gangbang", "gangbang", source), + MangaTag("Gender Bender", "gender-bender", source), + MangaTag("Giáo Viên", "giao-vien", source), + MangaTag("Girl love", "girl-love", source), + MangaTag("Group", "group", source), + MangaTag("Guro", "guro", source), + MangaTag("Hài Hước", "hai-huoc", source), + MangaTag("Hãm Hiếp", "ham-hiep", source), + MangaTag("Harem", "harem", source), + MangaTag("Hầu Gái", "hau-gai", source), + MangaTag("Hậu Môn", "hau-mon", source), + MangaTag("Housewife", "housewife", source), + MangaTag("Kinh Dị", "kinh-di", source), + MangaTag("Kogal", "kogal", source), + MangaTag("Lão Già Dâm", "lao-gia-dam", source), + MangaTag("Lãng Mạn", "lang-man", source), + MangaTag("Loạn Luân", "loan-luan", source), + MangaTag("Loli", "loli", source), + MangaTag("LXHENTAI", "lxhentai", source), + MangaTag("Mang Thai", "mang-thai", source), + MangaTag("Manhwa", "manhwa", source), + MangaTag("Mắt Kính", "mat-kinh", source), + MangaTag("Mature", "mature", source), + MangaTag("Milf", "milf", source), + MangaTag("Mind Break", "mind-break", source), + MangaTag("Mind Control", "mind-control", source), + MangaTag("Monster Girl", "monster-girl", source), + MangaTag("Nàng Dâu", "nang-dau", source), + MangaTag("Netorare", "netorare", source), + MangaTag("Ngực Lớn", "nguc-lon", source), + MangaTag("Ngực Nhỏ", "nguc-nho", source), + MangaTag("Nô Lệ", "no-le", source), + MangaTag("NTR", "ntr", source), + MangaTag("Nữ Sinh", "nu-sinh", source), + MangaTag("Office lady", "office-lady", source), + MangaTag("OneShot", "oneshot", source), + MangaTag("Quái Vật", "quai-vat", source), + MangaTag("Series", "series", source), + MangaTag("Shota", "shota", source), + MangaTag("Supernatural", "supernatural", source), + MangaTag("Swinging", "swinging", source), + MangaTag("Thể Thao", "the-thao", source), + MangaTag("Three some", "three-some", source), + MangaTag("Thú Vật", "thu-vat", source), + MangaTag("Trap", "trap", source), + MangaTag("Truyện Comic", "truyen-comic", source), + MangaTag("Truyện Không Che", "truyen-khong-che", source), + MangaTag("Truyện Màu", "truyen-mau", source), + MangaTag("Truyện Ngắn", "truyen-ngan", source), + MangaTag("Virgin", "virgin", source), + MangaTag("Xúc Tua", "xuc-tua", source), + MangaTag("Y Tá", "y-ta", source), + MangaTag("Yaoi", "yaoi", source), + MangaTag("Yuri", "yuri", source) + ) + + private fun parseDateTime(dateStr: String): Long { + return try { + val parts = dateStr.split(" ") + val dateParts = parts[0].split("-") + val timeParts = parts[1].split(":") + + val calendar = Calendar.getInstance() + calendar.set( + dateParts[0].toInt(), + dateParts[1].toInt() - 1, + dateParts[2].toInt(), + timeParts[0].toInt(), + timeParts[1].toInt(), + timeParts[2].toInt() ) + calendar.timeInMillis + } catch (e: Exception) { + System.currentTimeMillis() } } } 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 52c3ee510..b4ea003fb 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 @@ -12,7 +12,7 @@ import java.util.EnumSet @MangaSourceParser("NETTRUYENLL", "NetTruyenLL", "vi") internal class NetTruyenLL(context: MangaLoaderContext) : - WpComicsParser(context, MangaParserSource.NETTRUYENLL, "nettruyenll.com", 20) { + WpComicsParser(context, MangaParserSource.NETTRUYENLL, "nettruyenuu.com", 20) { override val listUrl = "/tim-kiem-nang-cao"