diff --git a/.github/summary.yaml b/.github/summary.yaml index db78ea60..d52f241f 100644 --- a/.github/summary.yaml +++ b/.github/summary.yaml @@ -1 +1 @@ -total: 1152 \ No newline at end of file +total: 1154 \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt new file mode 100644 index 00000000..39bc1398 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt @@ -0,0 +1,153 @@ +package org.koitharu.kotatsu.parsers.site.en + +import androidx.collection.arraySetOf +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.network.UserAgents +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("MYCOMICLIST", "MyComicList", "en", ContentType.COMICS) +internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MYCOMICLIST, 24) { + + override val configKeyDomain = ConfigKey.Domain("mycomiclist.org") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + SortOrder.ALPHABETICAL + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + 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) + when { + !filter.query.isNullOrEmpty() -> { + append("/comic-search?key=") + append(filter.query.urlEncoded()) + } + filter.tags.isNotEmpty() -> { + append("/") + append(filter.tags.first().key) + append("-comic") + } + else -> when (order) { + SortOrder.UPDATED -> append("/hot-comic") + SortOrder.POPULARITY -> append("/popular-comic") + SortOrder.NEWEST -> append("/new-comic") + else -> append("/ongoing-comic") + } + } + if (page > 1) { + append("?page=") + append(page) + } + } + + val doc = webClient.httpGet(url).parseHtml() + return doc.select("div.manga-box").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val img = div.selectFirst("img.lazyload") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + title = div.selectFirst("h3 a")?.text().orEmpty(), + altTitle = null, + author = null, + tags = emptySet(), + rating = RATING_UNKNOWN, + isNsfw = isNsfwSource, + coverUrl = img?.attr("data-src").orEmpty(), + state = null, + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + return manga.copy( + tags = doc.select("td:contains(Genres:) + td a").mapToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast('/').substringBefore("-comic"), + title = a.text().toTitleCase(sourceLocale), + source = source + ) + }, + author = doc.selectFirst("td:contains(Author:) + td")?.textOrNull(), + state = when(doc.selectFirst("td:contains(Status:) + td a")?.text()?.lowercase()) { + "ongoing" -> MangaState.ONGOING + "completed" -> MangaState.FINISHED + else -> null + }, + description = doc.selectFirst("div.manga-desc p.pdesc")?.html(), + chapters = doc.select("ul.basic-list li").mapChapters(reversed = true) { i, li -> + val a = li.selectFirst("a.ch-name") ?: return@mapChapters null + val href = a.attrAsRelativeUrl("href") + val name = a.text() + + MangaChapter( + id = generateUid(href), + name = name, + number = name.substringAfter('#').toFloatOrNull() ?: (i + 1f), + url = href, + scanlator = null, + uploadDate = 0L, + branch = null, + source = source, + volume = 0, + ) + } + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + "/all" + val doc = webClient.httpGet(fullUrl).parseHtml() + + return doc.select("img.chapter_img.lazyload").mapNotNull { img -> + val imageUrl = img.attrOrNull("data-src") ?: return@mapNotNull null + MangaPage( + id = generateUid(imageUrl), + url = imageUrl, + preview = null, + source = source + ) + } + } + + private suspend fun fetchAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain").parseHtml() + return doc.select("div.cr-anime-box.genre-box a.genre-name").mapToSet { a -> + val href = a.attr("href") + val key = href.substringAfterLast('/').substringBefore("-comic") + MangaTag( + key = key, + title = a.text().toTitleCase(sourceLocale), + source = source + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HariManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HariManga.kt index 5e8bff77..d1f73b0d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HariManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HariManga.kt @@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("HARIMANGA", "HariManga", "en") internal class HariManga(context: MangaLoaderContext) : - MadaraParser(context, MangaParserSource.HARIMANGA, "harimanga.com", pageSize = 10) + MadaraParser(context, MangaParserSource.HARIMANGA, "harimanga.me", pageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Mgkomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Mgkomik.kt index b273174d..3f6184d3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Mgkomik.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/Mgkomik.kt @@ -5,12 +5,19 @@ 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.madara.MadaraParser +import org.koitharu.kotatsu.parsers.config.ConfigKey import java.util.* import kotlin.random.Random @MangaSourceParser("MGKOMIK", "MgKomik", "id") internal class Mgkomik(context: MangaLoaderContext) : MadaraParser(context, MangaParserSource.MGKOMIK, "mgkomik.id", 20) { + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val tagPrefix = "genres/" override val listUrl = "komik/" override val datePattern = "dd MMM yy" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt new file mode 100644 index 00000000..ccf5be94 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt @@ -0,0 +1,171 @@ +package org.koitharu.kotatsu.parsers.site.vi + +import org.json.JSONArray +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.PagedMangaParser +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.* + +@MangaSourceParser("DUALEOTRUYEN", "DuaLeoTruyen", "vi", type = ContentType.HENTAI) +internal class DuaLeoTruyen(context: MangaLoaderContext) : + PagedMangaParser(context, MangaParserSource.DUALEOTRUYEN, 60) { + + override val configKeyDomain: ConfigKey.Domain + get() = ConfigKey.Domain("dualeotruyenomega.com") + + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + ) + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + val url = buildString { + append("https://") + append(domain) + when { + !filter.query.isNullOrEmpty() -> { + append("/tim-kiem.html") + append("?key=") + append(filter.query.urlEncoded()) + } + filter.tags.isNotEmpty() -> { + append("/the-loai/") + append(filter.tags.first().key) + append(".html") + } + else -> when (order) { + SortOrder.POPULARITY -> append("/top-ngay.html") + else -> append("/truyen-moi-cap-nhat.html") + } + } + if (page > 1) { + append("?page=") + append(page) + } + } + + val doc = webClient.httpGet(url).parseHtml() + return doc.select(".box_list > .li_truyen").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = li.selectFirst(".name")?.text().orEmpty(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = isNsfwSource, + coverUrl = li.selectFirst("img")?.absUrl("data-src").orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) + + return manga.copy( + altTitle = doc.selectFirst(".box_info_right h2")?.textOrNull(), + tags = doc.select("ul.list-tag-story li a").mapToSet { + MangaTag( + key = it.attr("href").substringAfterLast('/').substringBefore('.'), + title = it.text().toTitleCase(sourceLocale), + source = source + ) + }, + state = when (doc.selectFirst(".info-item:has(.fa-rss)")?.text()?.removePrefix("Tình trang: ")) { + "Đang cập nhật" -> MangaState.ONGOING + "Full" -> MangaState.FINISHED + else -> null + }, + author = doc.selectFirst(".info-item:has(.fa-user)")?.textOrNull()?.removePrefix("Tác giả: "), + description = doc.selectFirst(".story-detail-info")?.html(), + chapters = doc.select(".list-chapters .chapter-item").mapChapters(reversed = true) { i, div -> + val a = div.selectFirstOrThrow(".chap_name a") + val href = a.attrAsRelativeUrl("href") + val dateText = div.selectFirst(".chap_update")?.text() + MangaChapter( + id = generateUid(href), + name = a.text(), + number = i + 1f, + url = href, + scanlator = null, + uploadDate = dateFormat.tryParse(dateText), + branch = null, + source = source, + volume = 0, + ) + } + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + + val chapterId = doc.selectFirst("input[name=chap]")?.`val`() + val comicsId = doc.selectFirst("input[name=truyen]")?.`val`() + if (chapterId != null && comicsId != null) { + webClient.httpPost( + url = "https://$domain/process.php", + form = mapOf( + "action" to "update_view_chap", + "truyen" to comicsId, + "chap" to chapterId + ) + ) + } + + return doc.select(".content_view_chap img").mapIndexed { i, img -> + val url = img.absUrl("data-original") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source + ) + } + } + + private suspend fun fetchAvailableTags(): Set { + return listOf( + "18+", "Đam Mỹ", "Harem", "Truyện Màu", "BoyLove", "GirlLove", + "Phiêu lưu", "Yaoi", "Hài Hước", "Bách Hợp", "Chuyển Sinh", "Drama", + "Hành Động", "Kịch Tính", "Cổ Đại", "Ecchi", "Hentai", "Lãng Mạn", + "Người Thú", "Tình Cảm", "Yuri", "Oneshot", "Doujinshi", "ABO" + ).mapToSet { name -> + MangaTag( + key = name.lowercase().replace(' ', '-'), + title = name, + source = source + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt index b9d0cd4c..c0174fb5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt @@ -183,9 +183,10 @@ internal class XoxoComics(context: MangaLoaderContext) : val doc = webClient.httpGet(fullUrl).parseHtml() return doc.select(selectPage).mapNotNull { url -> val img = url.src()?.toRelativeUrl(domain) ?: return@mapNotNull null + val originalImage = img.replace("[", "").replace("]", "") MangaPage( id = generateUid(img), - url = img, + url = originalImage, preview = null, source = source, )