diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt index 3dbd54065..293627794 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt @@ -17,7 +17,7 @@ import java.util.* internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) { override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override val configKeyDomain = ConfigKey.Domain("teamxnovel.com") + override val configKeyDomain = ConfigKey.Domain("team1x12.com") override suspend fun getListPage( page: Int, @@ -61,7 +61,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex return doc.select("div.listupd .bs .bsx").ifEmpty { doc.select("div.post-body .box") }.map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.select(".tt, h3").text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt index 96e1d0ce5..5e5327e26 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt @@ -23,17 +23,17 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte if (query != null || offset > 0) { return emptyList() } - val link = "https://${domain}/viewer_landing.php" + val link = "https://$domain/viewer_landing.php" val doc = webClient.httpGet(link).parseHtml() val mangas = doc.getElementsByClass("comicPreviewContainer") return mangas.mapNotNull { item -> val background = item.selectFirstOrThrow(".comicPreview").styleValueOrNull("background") - val href = item.selectFirst("a")?.attrAsAbsoluteUrl("href") ?: return@mapNotNull null + val href = item.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null val cover = background?.substring(background.indexOf("site/themes"), background.indexOf(")")) Manga( id = generateUid(href), title = item.selectFirst("h3")?.text() ?: return@mapNotNull null, - coverUrl = "https://${domain}/$cover", + coverUrl = "https://$domain/$cover", altTitle = null, author = "Dan Kim", rating = RATING_UNKNOWN, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt index a25e2b7d5..82d0a4a58 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt @@ -59,7 +59,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex val doc = webClient.httpGet(url).parseHtml() return doc.select("div.movie-list-index div.cartoon-box").map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h3").text(), @@ -81,7 +81,6 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex } } - override suspend fun getTags(): Set { val doc = webClient.httpGet("https://$domain/popular-comic").parseHtml() return doc.select("li.tag-item a").mapNotNullToSet { a -> @@ -146,5 +145,4 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex ) } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt index 222135271..28311d8c2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Comicastle.kt @@ -49,7 +49,6 @@ internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(contex } val postdata = "submit=Submit&search=" + query.urlEncoded() webClient.httpPost(url, postdata).parseHtml() - } else if (!tags.isNullOrEmpty()) { val url = buildString { append("https://$domain/library/search/genre/") @@ -57,7 +56,6 @@ internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(contex } val postdata = "submit=Submit&search=" + tag?.key.orEmpty() webClient.httpPost(url, postdata).parseHtml() - } else { val url = buildString { append("https://$domain") @@ -75,7 +73,7 @@ internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(contex return doc.select("div.card-body div.match-height div.col-6") .map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("p").text(), @@ -93,7 +91,6 @@ internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(contex } } - override suspend fun getTags(): Set { val doc = webClient.httpGet("https://$domain/library/").parseHtml() return doc.requireElementById("sidebar").selectFirstOrThrow(".card-body").select("button") @@ -156,5 +153,4 @@ internal class Comicastle(context: MangaLoaderContext) : PagedMangaParser(contex ) } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt index 342865932..fb63265bc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt @@ -52,11 +52,11 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont } val doc = webClient.httpGet(url).parseHtml() - //There are no images on the search page + // There are no images on the search page if (!query.isNullOrEmpty()) { return doc.select("dl.chapter-list dd") .map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("a").text(), @@ -81,7 +81,7 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont } else { return doc.select("li.span2") .map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("div.caption").text(), @@ -98,7 +98,6 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont ) } } - } override suspend fun getTags(): Set = emptySet() @@ -169,4 +168,3 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont return pages } } - diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt new file mode 100644 index 000000000..3e290adb7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt @@ -0,0 +1,165 @@ +package org.koitharu.kotatsu.parsers.site.en + +import androidx.collection.ArraySet +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +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.util.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("KSKMOE", "Ksk Moe", "en", ContentType.HENTAI) +internal class KskMoe(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.KSKMOE, 35) { + + override val sortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL) + override val configKeyDomain = ConfigKey.Domain("ksk.moe") + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + + val url = buildString { + append("https://") + append(domain) + + if (!tags.isNullOrEmpty()) { + append("/tags/") + append(tag?.key.orEmpty()) + } else { + append("/browse") + } + + if (page > 1) { + append("/page/") + append(page) + } + + when (sortOrder) { + SortOrder.POPULARITY -> append("?sort=32") + SortOrder.UPDATED -> append("") + SortOrder.NEWEST -> append("?sort=16") + SortOrder.ALPHABETICAL -> append("?sort=1") + else -> append("") + } + + if (!query.isNullOrEmpty()) { + append("?s=") + append(query.urlEncoded()) + } + } + val doc = webClient.httpGet(url).parseHtml() + + if (!doc.html().contains("pagination") && page > 1) { + return emptyList() + } + return doc.requireElementById("galleries").select("article").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectLastOrThrow("h3 span").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(), + tags = div.select("footer span").mapNotNullToSet { span -> + MangaTag( + key = span.text().urlEncoded(), + title = span.text(), + source = source, + ) + }, + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + return coroutineScope { + (1..2).map { page -> + async { getTags(page) } + } + }.awaitAll().flattenTo(ArraySet(360)) + } + + private suspend fun getTags(page: Int): Set { + val url = if (page == 1) { + "https://$domain/tags" + } else { + "https://$domain/tags/page/$page" + } + val root = webClient.httpGet(url).parseHtml().body().getElementById("tags") + return root?.parseTags().orEmpty() + } + + private fun Element.parseTags() = select("section.tags div a").mapToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/tags/"), + title = a.selectFirstOrThrow("span").text(), + source = source, + ) + } + + private val date = SimpleDateFormat("dd.MM.yyyy hh:mm 'UTC'", Locale.US) + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + + return manga.copy( + tags = doc.requireElementById("metadata").select("main div:contains(Tag) a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("/tags/"), + title = a.selectFirstOrThrow("span").text(), + source = source, + ) + }, + author = doc.requireElementById("metadata").selectFirstOrThrow("main div:contains(Artist) a span").text(), + chapters = + if ((doc.html().contains("previews"))) { + listOf( + MangaChapter( + id = generateUid(manga.id), + name = manga.title, + number = 1, + url = manga.url, + scanlator = null, + uploadDate = date.tryParse(doc.selectFirstOrThrow("time.updated").text()), + branch = null, + source = source, + ), + ) + } else { + emptyList() + }, + + ) + } + + // For the moment the pages are in poor quality. + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + return doc.requireElementById("previews").select("main div img").map { img -> + val url = img.src() ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/LikeManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/LikeManga.kt new file mode 100644 index 000000000..8c349aeff --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/LikeManga.kt @@ -0,0 +1,223 @@ +package org.koitharu.kotatsu.parsers.site.en + +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +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.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("LIKEMANGA", "LikeManga", "en") +internal class LikeManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.LIKEMANGA, 36) { + + override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) + override val configKeyDomain = ConfigKey.Domain("likemanga.io") + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val url = buildString { + append("https://") + append(domain) + append("/?act=search&f") + append("[sortby]".urlEncoded()) + append("=") + when (sortOrder) { + SortOrder.POPULARITY -> append("hot") + SortOrder.UPDATED -> append("lastest-chap") + SortOrder.NEWEST -> append("lastest-manga") + else -> append("lastest-chap") + } + if (page > 1) { + append("&pageNum=") + append(page) + } + if (!tags.isNullOrEmpty()) { + append("&f") + append("[genres]".urlEncoded()) + append("=") + append(tag?.key.orEmpty()) + } + if (!query.isNullOrEmpty()) { + append("&f") + append("[keyword]".urlEncoded()) + append("=") + append(query.urlEncoded()) + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select("div.card-body div.video").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("p.title-manga").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/genres/").parseHtml() + return doc.select("ul.nav-genres li:not(.text-center) a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val mangaId = manga.url.toAbsoluteUrl(domain).removeSuffix("/").substringAfterLast("-").toInt() + val maxPageChapterSelect = doc.getElementById("nav_list_chapter_id_detail")?.select("a:not(.next)") + var maxPageChapter = 1 + if (!maxPageChapterSelect.isNullOrEmpty()) { + maxPageChapterSelect.map { + val i = it.text().toInt() + if (i > maxPageChapter) { + maxPageChapter = i + } + } + } + return manga.copy( + altTitle = doc.selectFirstOrThrow(".list-info li.othername h2").text(), + state = null, + tags = doc.select("li.kind a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + author = doc.select("li.author p").last()?.text(), + description = doc.requireElementById("summary_shortened").text(), + chapters = run { + if (maxPageChapter == 1) { + parseChapters(doc) + } else { + coroutineScope { + val result = ArrayList(parseChapters(doc)) + result.ensureCapacity(result.size * maxPageChapter) + (2..maxPageChapter).map { i -> + async { + loadChapters(mangaId, i) + } + }.awaitAll() + .flattenTo(result) + result + } + } + }.reversed(), + ) + } + + private suspend fun loadChapters(mangaId: Int, page: Int): List { + val json = + webClient.httpGet( + "https://$domain/?act=ajax&code=load_list_chapter&manga_id=$mangaId&page_num=$page&chap_id=0&keyword=", + ) + .parseJson().getString("list_chap") + val chapters = json.split("wp-manga-chapter").drop(1) + return chapters.map { chapter -> + val url = chapter.substringAfter("href=\"").substringBefore("\">") + val name = chapter.substringAfter("/\">").substringBefore("") + val chapNum = url.substringAfter("chapter-").substringBefore("-") + val d = chapter.substringAfter("").substringBefore("") + val dateText = if (d.contains("New")) { + "today" + } else { + d + } + MangaChapter( + id = generateUid(url), + name = name, + number = chapNum.toInt(), + url = url, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + branch = null, + source = source, + ) + } + } + + private val dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) + private fun parseChapters(root: Element): List { + return root.select("li.wp-manga-chapter") + .map { li -> + val url = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val dateText = if (li.selectFirstOrThrow(".chapter-release-date").text() == "New") { + "today" + } else { + li.selectFirstOrThrow(".chapter-release-date").text() + } + val chapNum = url.substringAfter("chapter-").substringBefore("-") + + MangaChapter( + id = generateUid(url), + name = li.selectFirstOrThrow("a").text(), + number = chapNum.toInt(), + url = url, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + branch = null, + source = source, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + return doc.select(".reading-detail img").map { img -> + val url = img.src() ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } + + private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.startsWith("today") -> Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + else -> dateFormat.tryParse(date) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt index 9ef23aa77..01d70193a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt @@ -59,7 +59,7 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context val doc = webClient.httpGet(url).parseHtml() return doc.select("li.novel-item").map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h4").text(), @@ -77,7 +77,6 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context } } - override suspend fun getTags(): Set { val doc = webClient.httpGet("https://$domain/browse-comics/").parseHtml() return doc.select("label.checkbox-inline").mapNotNullToSet { label -> @@ -143,5 +142,4 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context ) } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt index 6d9aff901..35547adda 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt @@ -23,7 +23,7 @@ class Manhwa18Parser(context: MangaLoaderContext) : override suspend fun getFavicons(): Favicons { return Favicons( listOf( - Favicon("https://${domain}/uploads/logos/logo-mini.png", 92, null), + Favicon("https://$domain/uploads/logos/logo-mini.png", 92, null), ), domain, ) @@ -32,19 +32,19 @@ class Manhwa18Parser(context: MangaLoaderContext) : override suspend fun getDetails(manga: Manga): Manga { val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val cardInfoElement = docs.selectFirst("div.series-information") - val author = cardInfoElement?.selectFirst(".info-name:contains(Author(s))")?.parent() + val author = cardInfoElement?.selectFirst(".info-name:contains(Author)")?.parent() ?.select("a") ?.joinToString(", ") { it.text() } val availableTags = tagsMap.get() - val tags = cardInfoElement?.selectFirst(".info-name:contains(Genre(s))")?.parent() + val tags = cardInfoElement?.selectFirst(".info-name:contains(Genre)")?.parent() ?.select("a") ?.mapNotNullToSet { availableTags[it.text().lowercase(Locale.ENGLISH)] } val state = cardInfoElement?.selectFirst(".info-name:contains(Status)")?.parent() ?.selectFirst("a") ?.let { - when (it.text()) { - "On going" -> MangaState.ONGOING - "Completed" -> MangaState.FINISHED + when (it.text().lowercase()) { + "on going" -> MangaState.ONGOING + "completed" -> MangaState.FINISHED else -> null } } @@ -56,7 +56,6 @@ class Manhwa18Parser(context: MangaLoaderContext) : tags = tags.orEmpty(), state = state, chapters = docs.select(".card-body > .list-chapters > a").mapChapters(reversed = true) { index, element -> - // attrAsRelativeUrl only return page url without the '/' val chapterUrl = element.attrAsAbsoluteUrlOrNull("href")?.toRelativeUrl(domain) ?: return@mapChapters null val uploadDate = parseUploadDate(element.selectFirst(".chapter-time")?.text()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt new file mode 100644 index 000000000..bf5b78384 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt @@ -0,0 +1,96 @@ +package org.koitharu.kotatsu.parsers.site.en + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaParser +import org.koitharu.kotatsu.parsers.MangaSourceParser +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("PO2SCANS", "Po2Scans", "en") +internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, MangaSource.PO2SCANS) { + + override val sortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val configKeyDomain = ConfigKey.Domain("po2scans.com") + + override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + if (offset > 0) { + return emptyList() + } + val url = buildString { + append("https://$domain/series") + if (!query.isNullOrEmpty()) { + append("?search=") + append(query.urlEncoded()) + } + } + val doc = webClient.httpGet(url).parseHtml() + return doc.select(".series-list").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set = emptySet() + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val dateFormat = SimpleDateFormat("dd MMM, yy", Locale.ENGLISH) + return manga.copy( + altTitle = null, + state = when (doc.select(".status span").last()?.text()) { + "Ongoing" -> MangaState.ONGOING + "Done" -> MangaState.FINISHED + else -> null + }, + tags = emptySet(), + author = doc.select(".author span").last()?.text(), + description = doc.selectFirstOrThrow(".summary").text(), + chapters = doc.select(".chap-section .chap") + .mapChapters(reversed = true) { i, div -> + val a = div.selectFirstOrThrow("a") + val url = "/" + a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + MangaChapter( + id = generateUid(url), + name = a.text(), + number = i + 1, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(div.select(".detail span").last()?.text()), + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + return doc.select(".swiper-slide img").map { img -> + val url = img.src()?.replace("./assets", "/assets")?.toRelativeUrl(domain) + ?: img.parseFailed("Image src not found") + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt new file mode 100644 index 000000000..cb66169ce --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt @@ -0,0 +1,150 @@ +package org.koitharu.kotatsu.parsers.site.fr + +import kotlinx.coroutines.coroutineScope +import org.json.JSONArray +import org.json.JSONObject +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.util.* +import org.koitharu.kotatsu.parsers.util.json.* +import java.text.SimpleDateFormat +import java.util.* + +@MangaSourceParser("FMTEAM", "FmTeam", "fr") +internal class FmTeam(context: MangaLoaderContext) : + PagedMangaParser(context, MangaSource.FMTEAM, 0) { + + override val sortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val configKeyDomain = ConfigKey.Domain("fmteam.fr") + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + if (page > 1) { + return emptyList() + } + + val jsonManga = if (!query.isNullOrEmpty()) { + //3 letters minimum + webClient.httpGet("https://$domain/api/search/${query.urlEncoded()}").parseJson().getJSONArray("comics") + + } else { + webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") + } + + val manga = ArrayList(jsonManga.length()) + for (i in 0 until jsonManga.length()) { + val j = jsonManga.getJSONObject(i) + val href = "/api" + j.getString("url") + when { + !tags.isNullOrEmpty() -> { + val a = j.getJSONArray("genres").toString() + var found = true + tags.forEach { + if (!a.contains(it.key, ignoreCase = true)) { + found = false + } + } + if (found) { + manga.add( + addManga(href, j), + ) + } + } + + else -> { + manga.add( + addManga(href, j), + ) + } + } + } + return manga + } + + private fun addManga(href: String, j: JSONObject): Manga { + return Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = j.getString("thumbnail"), + title = j.getString("title"), + description = j.getString("description"), + altTitle = j.getJSONArray("alt_titles").toString() + .replace("[\"", "") + .replace("\"]", "") + .replace("\",\"", " , "), + rating = j.getString("rating").toFloatOrNull()?.div(10f) + ?: RATING_UNKNOWN, + tags = emptySet(), + author = j.getString("author"), + state = when (j.getString("status").lowercase()) { + "en cours" -> MangaState.ONGOING + "terminé" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = when (j.getString("adult").toInt()) { + 0 -> false + 1 -> true + else -> true + }, + ) + } + + + override suspend fun getTags(): Set = emptySet() + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val json = webClient.httpGet(fullUrl).parseJson().getJSONObject("comic") + val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + val chapters = JSONArray(json.getJSONArray("chapters").toJSONList().reversed()) + + manga.copy( + tags = json.getJSONArray("genres").toJSONList().mapNotNullToSet { + MangaTag( + key = it.getString("slug"), + title = it.getString("name"), + source = source, + ) + }, + chapters = chapters.mapJSONIndexed { i, j -> + val url = "/api" + j.getString("url").toRelativeUrl(domain) + val name = j.getString("full_title") + val date = j.getStringOrNull("updated_at") + MangaChapter( + id = generateUid(url), + name = name, + number = i + 1, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(date), + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val jsonPages = webClient.httpGet(fullUrl).parseJson().getJSONObject("chapter").getJSONArray("pages").toString() + val pages = jsonPages.replace("[", "").replace("]", "") + .replace("\\", "").split("\",\"").drop(1) + return pages.map { url -> + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt new file mode 100644 index 000000000..69913484a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt @@ -0,0 +1,144 @@ +package org.koitharu.kotatsu.parsers.site.fr + +import okhttp3.Headers +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("LIRESCAN", "Lire Scan", "fr") +internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.LIRESCAN, 20) { + + override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED) + + override val configKeyDomain = ConfigKey.Domain("lire-scan.me") + + override val headers: Headers = Headers.Builder() + .add("User-Agent", UserAgents.CHROME_MOBILE) + .build() + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + val tag = tags.oneOrThrowIfMany() + val doc = + if (!query.isNullOrEmpty()) { // search only works with 4 or more letters + if (page > 1) { + return emptyList() + } + val q = query.urlEncoded().replace("%20", "+") + val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q" + webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml() + } else { + val url = buildString { + append("https://") + append(domain) + if (!tags.isNullOrEmpty()) { + append("/manga/") + append(tag?.key.orEmpty()) + } + if (page > 1) { + append("/page/") + append(page) + append('/') + } + } + webClient.httpGet(url).parseHtml() + } + + return doc.select("div.sect__content.grid-items div.item-poster").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.select(".item-poster__title").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = div.selectFirstOrThrow(".item__rating").ownText().toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), + 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("dd-MM-yyyy", Locale.FRANCE) + return manga.copy( + altTitle = root.select("ul.pmovie__list li:contains(Nom Alternatif:)").text() + .replace("Nom Alternatif:", ""), + state = when (root.select("ul.pmovie__list li:contains(Status:)").text()) { + "Status: OnGoing", "Status: En cours" -> MangaState.ONGOING + "Status: Fini" -> MangaState.FINISHED + else -> null + }, + tags = root.select("ul.pmovie__list li:contains(Genre:)").text() + .replace("Genre:", "").split(" / ").mapNotNullToSet { tag -> + MangaTag( + key = tag.lowercase(), + title = tag, + source = source, + ) + }, + author = root.select("ul.pmovie__list li:contains(Artist(s):)").text().replace("Artist(s):", ""), + description = root.selectFirst("div.pmovie__text")?.html(), + chapters = root.select("ul li div.chapter") + .mapChapters(reversed = true) { i, div -> + val a = div.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + val name = a.text() + val dateText = div.select("p").last()?.text() + MangaChapter( + id = generateUid(href), + name = name, + number = i, + url = href, + scanlator = null, + uploadDate = dateFormat.tryParse(dateText), + branch = null, + source = source, + ) + }, + ) + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val pages = doc.selectFirstOrThrow("script:containsData(const manga = )").data() + .substringAfter("chapter1: [\"").substringBefore("\"]") + .split("\",\"") + return pages.map { img -> + MangaPage( + id = generateUid(img), + url = img, + preview = null, + source = source, + ) + } + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/").parseHtml() + return doc.select(".nav-menu li a").mapNotNullToSet { a -> + val key = a.attr("href").removeSuffix('/').substringAfterLast("manga/", "") + MangaTag( + key = key, + title = a.text(), + source = source, + ) + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt index 8239c5ae5..7ddd2cb41 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt @@ -8,7 +8,7 @@ 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.DateFormat +import org.koitharu.kotatsu.parsers.util.json.mapJSON import java.text.SimpleDateFormat import java.util.* @@ -49,124 +49,130 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont sortOrder: SortOrder, ): List { - val url = buildString { - append("https://") - append(domain) - if (sortOrder == SortOrder.ALPHABETICAL) { - append("/mangas/") - // just to stop the search of the ALPHABETICAL page because it contains all the manga and has no page function ( to change if there is a better method to stop the search ) - if (page == 2) { - append(page.toString()) // juste for break - } + if (sortOrder == SortOrder.ALPHABETICAL) { + + if (page > 1) { + return emptyList() } - if (sortOrder == SortOrder.UPDATED) { - append("/api/manga/home/getlast/") - append(page.toString()) + val url = buildString { + append("https://") + append(domain) + append("/api/get/catalog?page=0&filter=all") + } + val json = webClient.httpGet(url).parseJsonArray() + + return json.mapJSON { j -> + val urlManga = "https://$domain/api/get/card/${j.getString("slug")}" + val img = "https://$domain/upload/min_cover/${j.getString("image")}" + Manga( + id = generateUid(urlManga), + title = j.getString("title"), + altTitle = null, + url = urlManga, + publicUrl = urlManga.toAbsoluteUrl(domain), + rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + isNsfw = false, + coverUrl = img, + tags = setOf(), + state = when (j.getString("status")) { + "0" -> MangaState.ONGOING + "1" -> MangaState.FINISHED + "3" -> MangaState.ABANDONED + else -> null + }, + author = null, + source = source, + ) } - } - val doc = webClient.httpGet(url).parseHtml() - if (sortOrder == SortOrder.UPDATED) { - return doc.select(".last_chapters-element") - .map { div -> - val a = div.selectFirstOrThrow("a.last_chapters-title") - val href = a.attrAsAbsoluteUrl("href") - Manga( - id = generateUid(href), - title = a.text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = div.selectFirstOrThrow(".last_chapters-rate").ownText().toFloatOrNull()?.div(5f) - ?: -1f, - isNsfw = false, - coverUrl = div.selectFirstOrThrow(".last_chapters-image img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) - } } else { - val root = doc.selectFirstOrThrow(".catalog") - return root.select("div.element") - .map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") - Manga( - id = generateUid(href), - title = div.select("a.title").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = div.selectFirstOrThrow("div.stats").lastElementChild()?.ownText()?.toFloatOrNull() - ?.div(5f) ?: -1f, - isNsfw = false, - coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) - } - } + val url = buildString { + append("https://") + append(domain) + append("/api/get/homegrid/") + append(page) + } + val json = webClient.httpGet(url).parseJsonArray() + + return json.mapJSON { j -> + val urlManga = "https://$domain/api/get/card/${j.getString("manga_slug")}" + val img = "https://$domain/upload/min_cover/${j.getString("manga_image")}" + Manga( + id = generateUid(urlManga), + title = j.getString("manga_title"), + altTitle = null, + url = urlManga, + publicUrl = urlManga.toAbsoluteUrl(domain), + rating = j.getString("manga_rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + isNsfw = false, + coverUrl = img, + 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("dd-MM-yyyy", Locale.FRANCE) + val json = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseJson() + + val jsonManga = json.getJSONObject("manga") + val chapters = json.getJSONObject("chapters").toString().split("{\"id\":").drop(1) // Possible improvement here + val slug = manga.url.substringAfterLast("/") + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) return manga.copy( altTitle = null, - state = when (root.select("div.manga-tags")[3].select("a").text()) { - "En Cours" -> MangaState.ONGOING - "Fini", "Abandonné", "Licencier" -> MangaState.FINISHED + state = when (jsonManga.getString("status")) { + "0" -> MangaState.ONGOING + "1" -> MangaState.FINISHED + "3" -> MangaState.ABANDONED else -> null }, - - // Lists the tags but there is no search on the site so it will just come back to the a-z or last list. - tags = root.select("div.manga-tags")[1].select("a").mapNotNullToSet { a -> - MangaTag( - key = a.text(), - title = a.text().toTitleCase(), + author = jsonManga.getString("author"), + description = jsonManga.getString("description"), + chapters = chapters.mapChapters(reversed = true) { i, it -> + val id = it.substringAfter("\"chapter\":").substringBefore(",") + val url = "https://$domain/api/get/chapter/$slug/$id" + val date = getDateString( + it.substringAfter("\"date\":\"").substringBefore("\",").toLong(), + ) // Possible improvement here + + MangaChapter( + id = generateUid(url), + name = "Chapitre : $id", + number = i, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(date), + branch = null, source = source, ) }, - author = root.select("div.manga-staff").text(), - description = root.selectFirst("div.manga-description div")?.text(), - chapters = root.select("div.manga-chapters_wrapper div.manga-chapter") - .mapChapters(reversed = true) { i, div -> - - val a = div.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - val name = a.text() - - val dateText = div.select("span").last()?.text() - MangaChapter( - id = generateUid(href), - name = name, - number = i, - url = href, - scanlator = null, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), - branch = null, - source = source, - ) - }, ) } + private val simpleDateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) + private fun getDateString(time: Long): String = simpleDateFormat.format(time * 1000L) + + override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) - val doc = webClient.httpGet(fullUrl).parseHtml() - val root = doc.body().requireElementById("forgen_reader") - return root.select("img").map { img -> - val url = img.attrAsRelativeUrlOrNull("data-src") ?: img.attrAsRelativeUrlOrNull("src") - ?: img.parseFailed("Image src not found") + val jsonPage = webClient.httpGet(fullUrl).parseJson() + + val idManga = jsonPage.getJSONObject("manga").getString("id") + val slugChapter = chapter.url.substringAfterLast("/") + + val pages = jsonPage.getJSONObject("chapter").getJSONArray("files").toString() + .replace("[", "").replace("]", "").replace("\"", "") + .split(",") // Possible improvement here + + return pages.map { img -> + val url = "https://$domain/upload/chapitre/$idManga/$slugChapter/$img" MangaPage( id = generateUid(url), url = url, @@ -178,34 +184,4 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont override suspend fun getTags(): Set = emptySet() - private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { - val d = date?.lowercase() ?: return 0 - return when { - d.startsWith("il y a") -> parseRelativeDate(date) - - else -> dateFormat.tryParse(date) - } - } - - private fun parseRelativeDate(date: String): Long { - val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 - val cal = Calendar.getInstance() - - return when { - WordSet("jour", "jours").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - WordSet("heure", "heures").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - WordSet("minute", "minutes").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis - WordSet("seconde", "secondes").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - WordSet("mois").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis - WordSet("année", "années").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis - WordSet("semaine", "semaines").anyWordIn(date) -> cal.apply { - add( - Calendar.WEEK_OF_MONTH, - -number, - ) - }.timeInMillis - - else -> 0 - } - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt index 211f18e76..630bf2af5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt @@ -41,7 +41,6 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con append(page.toString()) append("/?s=") append(query.urlEncoded()) - } !tags.isNullOrEmpty() -> { @@ -55,7 +54,6 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con } else -> { - if (sortOrder == SortOrder.ALPHABETICAL) { append("/manga/") append("/page/") @@ -65,7 +63,6 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con if (sortOrder == SortOrder.UPDATED) { append("") } - } } } @@ -74,7 +71,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con val root = doc.requireElementById("dernierschapitres") return root.select("div.colonne") .map { article -> - val href = article.selectFirstOrThrow("a.index-top4-a").attrAsAbsoluteUrl("href") + val href = article.selectFirstOrThrow("a.index-top4-a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = article.select(".carteinfos a").text(), @@ -94,7 +91,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con val root = doc.requireElementById("main") return root.select("article.post-outer") .map { article -> - val href = article.selectFirstOrThrow("a.thumb-link").attrAsAbsoluteUrl("href") + val href = article.selectFirstOrThrow("a.thumb-link").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = article.select(".index-post-header a").text(), @@ -111,8 +108,6 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con ) } } - - } override suspend fun getDetails(manga: Manga): Manga { @@ -169,7 +164,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con ?: throw ParseException("Root not found", fullUrl) return root.select("img").map { img -> val url = img.attrAsRelativeUrlOrNull("data-src") ?: img.attrAsRelativeUrlOrNull("src") - ?: img.parseFailed("Image src not found") + ?: img.parseFailed("Image src not found") MangaPage( id = generateUid(url), url = url, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/PerfScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt similarity index 51% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/PerfScan.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt index e99d88f93..18a9e23bc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/PerfScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt @@ -1,20 +1,24 @@ -package org.koitharu.kotatsu.parsers.site.fr +package org.koitharu.kotatsu.parsers.site.heancms import okhttp3.Headers 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 org.koitharu.kotatsu.parsers.util.json.mapJSON -import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* -@MangaSourceParser("PERF_SCAN", "Perf Scan", "fr") -internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.PERF_SCAN, 12) { +internal abstract class HeanCms( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 20, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) override val sortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, @@ -23,12 +27,11 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.POPULARITY, ) - override val configKeyDomain = ConfigKey.Domain("perf-scan.fr") - override val headers: Headers = Headers.Builder() .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() + //For some sources, you need to send a json. For the moment, this part only works in Get. ( ex source need json gloriousscan.com , omegascans.org ) override suspend fun getListPage( page: Int, query: String?, @@ -36,8 +39,11 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, sortOrder: SortOrder, ): List { + var firstTag = false val url = buildString { - append("https://api.$domain/query?query_string=") + append("https://api.") + append(domain) + append("/query?query_string=") if (!query.isNullOrEmpty()) { append(query.urlEncoded()) @@ -55,12 +61,29 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, append("&series_type=Comic&page=") append(page) append("&perPage=12&tags_ids=") - append("[]".urlEncoded()) + append("[".urlEncoded()) + if (!tags.isNullOrEmpty()) { + for (tag in tags) { + // Just to make it fit [1,2,44] ect + if (!firstTag) { + firstTag = true + } else { + append(",") + } + append(tag.key) + } + } + append("]".urlEncoded()) } val json = webClient.httpGet(url).parseJson() return json.getJSONArray("data").mapJSON { j -> val slug = j.getString("series_slug") val urlManga = "https://$domain/series/$slug" + val cover = if (j.getString("thumbnail").contains('/')) { + j.getString("thumbnail") + } else { + "https://api.$domain/${j.getString("thumbnail")}" + } Manga( id = generateUid(urlManga), title = j.getString("title"), @@ -69,11 +92,12 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, publicUrl = urlManga, rating = RATING_UNKNOWN, isNsfw = false, - coverUrl = j.getString("thumbnail"), + coverUrl = cover, tags = setOf(), state = when (j.getString("status")) { "Ongoing" -> MangaState.ONGOING "Completed" -> MangaState.FINISHED + "Dropped" -> MangaState.ABANDONED else -> null }, author = null, @@ -82,35 +106,40 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, } } + protected open val datePattern = "yyyy-MM-dd" override suspend fun getDetails(manga: Manga): Manga { val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val dateFormat = SimpleDateFormat("MM/DD/yyyy", Locale.ENGLISH) + val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH) + + val slug = manga.url.substringAfterLast('/') + val chapter = root.selectFirstOrThrow("script:containsData(chapter_slug)").data() + .replace("\\", "") + .substringAfter("\"seasons\":") + .substringBefore("}]}],\"children\"") + .split("chapter_name") + .drop(1) return manga.copy( altTitle = root.selectFirstOrThrow("p.text-center.text-gray-400").text(), tags = emptySet(), author = root.select("div.flex.flex-col.gap-y-2 p:contains(Autor:) strong").text(), - description = root.selectFirst(".datas_synopsis")?.html(), - chapters = root.select("ul.grid a") - .mapChapters(reversed = true) { i, a -> - - val href = a.attrAsRelativeUrl("href") - val name = a.selectFirstOrThrow("span").text() - val dateText = a.selectLast("span")?.text() ?: "0" - MangaChapter( - id = generateUid(href), - name = name, - number = i + 1, - url = href, - scanlator = null, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), - branch = null, - source = source, - ) - }, + description = root.selectFirst("h5:contains(Desc) + .bg-gray-800")?.html(), + chapters = chapter.mapChapters(reversed = true) { i, it -> + val slugChapter = it.substringAfter("chapter_slug\":\"").substringBefore("\",\"") + val url = "https://$domain/series/$slug/$slugChapter" + val date = it.substringAfter("created_at\":\"").substringBefore("\",\"").substringBefore("T") + val name = slugChapter.replace("-", " ") + MangaChapter( + id = generateUid(url), + name = name, + number = i + 1, + url = url, + scanlator = null, + uploadDate = dateFormat.tryParse(date), + branch = null, + source = source, + ) + }, ) } @@ -128,36 +157,21 @@ internal class PerfScan(context: MangaLoaderContext) : PagedMangaParser(context, } } - override suspend fun getTags(): Set = emptySet() + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/comics").parseHtml() - private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { - val d = date?.lowercase() ?: return 0 - return when { - d.endsWith(" ago") -> parseRelativeDate(date) + val tags = doc.selectFirstOrThrow("script:containsData(Genres)").data() + .replace("\\", "") + .substringAfterLast("\"Genres\"") + .split("\",{\"") + .drop(1) - else -> dateFormat.tryParse(date) - } - } - - private fun parseRelativeDate(date: String): Long { - val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 - val cal = Calendar.getInstance() - - return when { - WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - WordSet("hour", "hours").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - WordSet("minute", "minutes").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis - WordSet("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - WordSet("month", "months").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis - WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis - WordSet("week").anyWordIn(date) -> cal.apply { - add( - Calendar.WEEK_OF_MONTH, - -number, - ) - }.timeInMillis - - else -> 0 + return tags.mapNotNullToSet { + MangaTag( + key = it.substringAfter("id\":").substringBefore(",\""), + title = it.substringAfter("name\":\"").substringBefore("\"}]"), + source = source, + ) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt new file mode 100644 index 000000000..f114ce946 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.heancms.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.heancms.HeanCms + +@MangaSourceParser("YUGEN_MANGAS_ES", "Yugen Mangas Es", "es", ContentType.HENTAI) +internal class YugenMangasEs(context: MangaLoaderContext) : + HeanCms(context, MangaSource.YUGEN_MANGAS_ES, "yugenmangas.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt new file mode 100644 index 000000000..f220d87b4 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.heancms.fr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.heancms.HeanCms + +@MangaSourceParser("PERF_SCAN", "Perf Scan", "fr") +internal class PerfScan(context: MangaLoaderContext) : + HeanCms(context, MangaSource.PERF_SCAN, "perf-scan.fr") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ReaperScansPt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ReaperScansPt.kt new file mode 100644 index 000000000..f58e7a2c1 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ReaperScansPt.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.heancms.pt + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.heancms.HeanCms + +@MangaSourceParser("REAPERSCANSPT", "ReaperScans Pt", "pt") +internal class ReaperScansPt(context: MangaLoaderContext) : + HeanCms(context, MangaSource.REAPERSCANSPT, "reaperscans.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt new file mode 100644 index 000000000..1c0b310db --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt @@ -0,0 +1,159 @@ +package org.koitharu.kotatsu.parsers.site.heancmsalt + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.PagedMangaParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +// Template similar to Heancms but with a different way of working + +internal abstract class HeanCmsAlt( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 18, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED) + + protected open val listUrl = "/comics" + protected open val datePattern = "MMMM d, yyyy" + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)" + protected open val selectMangaTitle = "h5" + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + // No search or tag + if (!query.isNullOrEmpty()) { + return emptyList() + } + val url = buildString { + append("https://") + append(domain) + append(listUrl) + if (page > 1) { + append("?page=") + append(page) + } + } + val doc = webClient.httpGet(url).parseHtml() + + return doc.select(selectManga).map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), + title = div.selectFirstOrThrow(selectMangaTitle).text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + } + + override suspend fun getTags(): Set = emptySet() + + protected open val selectDesc = "div.description-container" + protected open val selectAlt = "div.series-alternative-names" + protected open val selectChapter = "ul.MuiList-root a" + protected open val selectChapterTitle = "div.MuiListItemText-multiline span" + protected open val selectChapterDate = "div.MuiListItemText-multiline p" + + override suspend fun getDetails(manga: Manga): Manga { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return manga.copy( + altTitle = doc.selectFirst(selectAlt)?.text().orEmpty(), + description = doc.selectFirstOrThrow(selectDesc).html(), + chapters = doc.select(selectChapter) + .mapChapters(reversed = true) { i, a -> + val dateText = a.selectFirstOrThrow(selectChapterDate).text() + val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + MangaChapter( + id = generateUid(url), + name = a.selectFirstOrThrow(selectChapterTitle).text(), + number = i + 1, + url = url, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + branch = null, + source = source, + ) + }, + ) + } + + protected open val selectPage = "p.flex-col.items-center img" + + 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, + ) + } + } + + private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.startsWith("hace ") -> parseRelativeDate(date) + else -> dateFormat.tryParse(date) + } + } + + // Parses dates in this form: + // 21 hours ago + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minutos", "minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora", "horas").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("días", "día").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana", "semanas").anyWordIn(date) -> cal.apply { + add( + Calendar.WEEK_OF_YEAR, + -number, + ) + }.timeInMillis + + WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/CerberuSeries.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/CerberuSeries.kt new file mode 100644 index 000000000..2e06abcbf --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/CerberuSeries.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.heancmsalt.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt + +@MangaSourceParser("LEGIONSCANS", "CerberuSeries", "es") +internal class CerberuSeries(context: MangaLoaderContext) : + HeanCmsAlt(context, MangaSource.LEGIONSCANS, "cerberuseries.xyz") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/MangaEsp.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/MangaEsp.kt new file mode 100644 index 000000000..d4c95d289 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/es/MangaEsp.kt @@ -0,0 +1,24 @@ +package org.koitharu.kotatsu.parsers.site.heancmsalt.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt + +@MangaSourceParser("MANGAESP", "MangaEsp", "es") +internal class MangaEsp(context: MangaLoaderContext) : + HeanCmsAlt(context, MangaSource.MANGAESP, "mangaesp.co", 15) { + + override val listUrl = "/comic" + + override val selectManga = "div.contenedor div.grid-5 .p-relative:not(.portada-contenedor)" + override val selectMangaTitle = "div.titulo-contenedor" + + override val selectDesc = "div.project-sinopsis-contenido" + override val selectAlt = "div.project-info-opcion:contains(Altenativo) div.project-info-contenido" + override val selectChapter = "div.grid-capitulos div a" + override val selectChapterTitle = ".capitulo-info-titulo" + override val selectChapterDate = ".capitulo-info-fecha" + + override val selectPage = ".grid-center img" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaLikeOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaLikeOrg.kt new file mode 100644 index 000000000..4978b50e3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaLikeOrg.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.madara.ar + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGALIKE_ORG", "MangaLike Org", "ar") +internal class MangaLikeOrg(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGALIKE_ORG, "mangalike.org", pageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt index caee966b2..f3ac84c8e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/MangaStarz.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MANGASTARZ", "Manga Starz", "ar") internal class MangaStarz(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MANGASTARZ, "mangalike.org", pageSize = 10) { + MadaraParser(context, MangaSource.MANGASTARZ, "mangastarz.com", pageSize = 10) { override val datePattern = "d MMMM، yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/ShadowxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/ShadowxManga.kt new file mode 100644 index 000000000..65fce5340 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/ar/ShadowxManga.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.madara.ar + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("SHADOWXMANGA", "Shadow X Manga", "ar") +internal class ShadowxManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.SHADOWXMANGA, "shadowxmanga.com") { + + override val datePattern = "yyyy/MM/dd" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Stkissmanga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CreepyScans.kt similarity index 56% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Stkissmanga.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CreepyScans.kt index 7653334fb..d144a140c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Stkissmanga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/CreepyScans.kt @@ -5,9 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("STKISSMANGA", "Stkissmanga", "en") -internal class Stkissmanga(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.STKISSMANGA, "1stkissmanga.me") { - - override val datePattern = "MMMM dd, yyyy" +@MangaSourceParser("CREEPYSCANS", "CreepyScans", "en") +internal class CreepyScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.CREEPYSCANS, "creepyscans.com") { + override val stylepage = "" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt index bd5b7a47e..ba0e21065 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/HiperDex.kt @@ -8,4 +8,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("HIPERDEX", "HiperDex", "en", ContentType.HENTAI) internal class HiperDex(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.HIPERDEX, "hiperdex.com", 36) + MadaraParser(context, MangaSource.HIPERDEX, "hiperdex.xyz", 36) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1k.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1k.kt new file mode 100644 index 000000000..bf1edc592 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manga1k.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MANGA1K", "Manga1k", "en", ContentType.HENTAI) +internal class Manga1k(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MANGA1K, "manga1k.com", 20) { + override val withoutAjax = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MurimScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MurimScan.kt index ad8956062..d4ea27f9a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MurimScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MurimScan.kt @@ -9,5 +9,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser internal class MurimScan(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MURIMSCAN, "murimscan.run", 100) { + override val withoutAjax = true override val postreq = true } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/NvManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/NvManga.kt new file mode 100644 index 000000000..a5d20296a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/NvManga.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.madara.en + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("NVMANGA", "NvManga", "en") +internal class NvManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.NVMANGA, "nvmanga.com") { + override val datePattern = "dd/MM/yyyy" + override val tagPrefix = "genre/" + override val listUrl = "webtoon/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MhScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MhScans.kt new file mode 100644 index 000000000..6eaf62d88 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MhScans.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.madara.es + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("MHSCANS", "MhScans", "es") +internal class MhScans(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MHSCANS, "mhscans.com") { + override val datePattern = "d 'de' MMMMM 'de' yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DianxiaTrads.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DianxiaTrads.kt new file mode 100644 index 000000000..452e090b3 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DianxiaTrads.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.madara.pt + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("DIANXIATRADS", "Dianxia Trads", "pt") +internal class DianxiaTrads(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.DIANXIATRADS, "dianxiatrads.com", 10) { + + override val datePattern: String = "dd/MM/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Webtoonevreni.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Webtoonevreni.kt new file mode 100644 index 000000000..f92ee1828 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/Webtoonevreni.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.madara.tr + + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + + +@MangaSourceParser("WEBTOONEVRENI", "Webtoonevreni", "tr") +internal class Webtoonevreni(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.WEBTOONEVRENI, "webtoonevreni.net", 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/YaoiTr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/YaoiTr.kt new file mode 100644 index 000000000..b8b82a34c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/YaoiTr.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.madara.tr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("YAOITR", "Yaoi Tr", "tr") +internal class YaoiTr(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.YAOITR, "yaoitr.com", 16) { + + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt new file mode 100644 index 000000000..b96bec34f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/zh/Hanman18.kt @@ -0,0 +1,32 @@ +package org.koitharu.kotatsu.parsers.site.manga18.zh + +import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser +import org.koitharu.kotatsu.parsers.util.* + +@MangaSourceParser("HANMAN18", "Hanman18", "zh", ContentType.HENTAI) +internal class Hanman18(context: MangaLoaderContext) : + Manga18Parser(context, MangaSource.HANMAN18, "hanman18.com") { + + override suspend fun getChapters(manga: Manga, doc: Document): List { + return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> + val a = li.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(href), + name = a.text(), + number = i + 1, + url = href, + uploadDate = 0, + source = source, + scanlator = null, + branch = null, + ) + } + } + + override suspend fun getTags(): Set = emptySet() // search by tag does not work +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangaNeloCom.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangaNeloCom.kt new file mode 100644 index 000000000..c761418f4 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangaNeloCom.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.mangabox.en + +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.* +import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser + +@MangaSourceParser("MANGANELO_COM", "MangaNelo Com", "en") +internal class MangaNeloCom(context: MangaLoaderContext) : + MangaboxParser(context, MangaSource.MANGANELO_COM) { + override val configKeyDomain = ConfigKey.Domain("m.manganelo.com", "chapmanganelo.com") + override val otherDomain = "chapmanganelo.com" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt index 27a68c084..24c705517 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt @@ -13,7 +13,6 @@ import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat import java.util.* - internal abstract class MangaReaderParser( context: MangaLoaderContext, source: MangaSource, @@ -34,6 +33,84 @@ internal abstract class MangaReaderParser( private val mutex = Mutex() protected open var lastSearchPage = 1 + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + if (!query.isNullOrEmpty()) { + if (page > lastSearchPage) { + return emptyList() + } + + val url = buildString { + append("https://") + append(domain) + append("/page/") + append(page) + append("/?s=") + append(query.urlEncoded()) + } + + val docs = webClient.httpGet(url).parseHtml() + lastSearchPage = docs.selectFirst(".pagination .next") + ?.previousElementSibling() + ?.text()?.toIntOrNull() ?: 1 + return parseMangaList(docs) + } + + val sortQuery = when (sortOrder) { + SortOrder.ALPHABETICAL -> "title" + SortOrder.NEWEST -> "latest" + SortOrder.POPULARITY -> "popular" + SortOrder.UPDATED -> "update" + else -> "" + } + val tagKey = "genre[]".urlEncoded() + val tagQuery = + if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" } + val url = buildString { + append("https://") + append(domain) + append(listUrl) + append("/?order=") + append(sortQuery) + append(tagQuery) + append("&page=") + append(page) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + protected open val selectMangalist = ".postbody .listupd .bs .bsx" + protected open val selectMangaListImg = "img.ts-post-image" + protected open val selectMangaListTitle = "div.tt" + + protected open fun parseMangaList(docs: Document): List { + return docs.select(selectMangalist).mapNotNull { + val a = it.selectFirst("a") ?: return@mapNotNull null + val relativeUrl = a.attrAsRelativeUrl("href") + val rating = it.selectFirst(".numscore")?.text() + ?.toFloatOrNull()?.div(10) ?: RATING_UNKNOWN + + Manga( + id = generateUid(relativeUrl), + url = relativeUrl, + title = it.selectFirst(selectMangaListTitle)?.text() ?: a.attr("title"), + altTitle = null, + publicUrl = a.attrAsAbsoluteUrl("href"), + rating = rating, + isNsfw = isNsfwSource, + coverUrl = it.selectFirst(selectMangaListImg)?.src().orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + protected open val selectChapter = "#chapterlist > ul > li" override suspend fun getDetails(manga: Manga): Manga { val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() @@ -61,7 +138,6 @@ internal abstract class MangaReaderParser( docs.selectFirst("div.seriestucontent > div.seriestucontentr") ?: docs.selectFirst("div.seriestucontentr") ?: docs.selectFirst("div.seriestucon") - val tagMap = getOrCreateTagMap() val selectTag = if (tablemode != null) { @@ -72,7 +148,6 @@ internal abstract class MangaReaderParser( val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } - val stateSelect = if (tablemode != null) { tablemode.selectFirst(".infotable td:contains(Status)") ?: tablemode.selectFirst(".infotable td:contains(Statut)") @@ -141,86 +216,6 @@ internal abstract class MangaReaderParser( ) } - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (!query.isNullOrEmpty()) { - if (page > lastSearchPage) { - return emptyList() - } - - val url = buildString { - append("https://") - append(domain) - append("/page/") - append(page) - append("/?s=") - append(query.urlEncoded()) - } - - val docs = webClient.httpGet(url).parseHtml() - lastSearchPage = docs.selectFirst(".pagination .next") - ?.previousElementSibling() - ?.text()?.toIntOrNull() ?: 1 - return parseMangaList(docs) - } - - val sortQuery = when (sortOrder) { - SortOrder.ALPHABETICAL -> "title" - SortOrder.NEWEST -> "latest" - SortOrder.POPULARITY -> "popular" - SortOrder.UPDATED -> "update" - else -> "" - } - val tagKey = "genre[]".urlEncoded() - val tagQuery = - if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" } - val url = buildString { - append("https://") - append(domain) - append(listUrl) - append("/?order=") - append(sortQuery) - append(tagQuery) - append("&page=") - append(page) - } - - return parseMangaList(webClient.httpGet(url).parseHtml()) - } - - protected open val selectMangalist = ".postbody .listupd .bs .bsx" - protected open val selectMangaListImg = "img.ts-post-image" - protected open val selectMangaListTitle = "div.tt" - - protected open fun parseMangaList(docs: Document): List { - return docs.select(selectMangalist).mapNotNull { - val a = it.selectFirst("a") ?: return@mapNotNull null - val relativeUrl = a.attrAsRelativeUrl("href") - val rating = it.selectFirst(".numscore")?.text() - ?.toFloatOrNull()?.div(10) ?: RATING_UNKNOWN - - Manga( - id = generateUid(relativeUrl), - url = relativeUrl, - title = it.selectFirst(selectMangaListTitle)?.text() ?: a.attr("title"), - altTitle = null, - publicUrl = a.attrAsAbsoluteUrl("href"), - rating = rating, - isNsfw = isNsfwSource, - coverUrl = it.selectFirst(selectMangaListImg)?.src().orEmpty(), - tags = emptySet(), - state = null, - author = null, - source = source, - ) - } - } - protected open val encodedSrc = false protected open val selectScript = "div.wrapper script" protected open val selectPage = "div#readerarea img" @@ -280,10 +275,7 @@ internal abstract class MangaReaderParser( } return pages - } - - } override suspend fun getTags(): Set { @@ -293,22 +285,17 @@ internal abstract class MangaReaderParser( protected suspend fun getOrCreateTagMap(): Map = mutex.withLock { tagCache?.let { return@withLock it } val tagMap = ArrayMap() - val url = listUrl.toAbsoluteUrl(domain) val tagElements = webClient.httpGet(url).parseHtml().select("ul.genrez > li") for (el in tagElements) { if (el.text().isEmpty()) continue - tagMap[el.text()] = MangaTag( title = el.text(), key = el.selectFirst("input")?.attr("value") ?: continue, source = source, ) } - tagCache = tagMap return@withLock tagMap } - - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt index 0f3ed8364..7ed824850 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/EnAresManga.kt @@ -9,4 +9,5 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser internal class EnAresManga(context: MangaLoaderContext) : MangaReaderParser(context, MangaSource.ENARESMANGA, "en-aresmanga.com", pageSize = 20, searchPageSize = 10) { override val listUrl = "/series" + override val encodedSrc = true } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/OzulShojo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/OzulShojo.kt new file mode 100644 index 000000000..38ad6245e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/OzulShojo.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.ar + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("OZULSHOJO", "OzulShojo", "ar") +internal class OzulShojo(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.OZULSHOJO, "ozulshojo.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt index cf1dd2fe5..71cfb3095 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt @@ -10,7 +10,7 @@ import java.text.SimpleDateFormat @MangaSourceParser("SWATEAM", "Swa Team", "ar") internal class SwaTeam(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.SWATEAM, "swatmanga.co", pageSize = 42, searchPageSize = 39) { + MangaReaderParser(context, MangaSource.SWATEAM, "stmanga.me", pageSize = 42, searchPageSize = 39) { override val datePattern = "MMMM dd, yyyy" override val selectMangalist = ".listupd .bs .bsx" @@ -44,8 +44,8 @@ internal class SwaTeam(context: MangaLoaderContext) : } val sortQuery = when (sortOrder) { - SortOrder.ALPHABETICAL -> "title" - SortOrder.NEWEST -> "latest" + SortOrder.ALPHABETICAL -> "a-z" + SortOrder.NEWEST -> "added" SortOrder.POPULARITY -> "popular" SortOrder.UPDATED -> "update" else -> "" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AnigliScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AnigliScans.kt index 78bc9aa7c..2d82e0bb2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AnigliScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/AnigliScans.kt @@ -7,8 +7,6 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("ANIGLISCANS", "Anigli Scans", "en") internal class AnigliScans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.ANIGLISCANS, "anigliscans.com", pageSize = 47, searchPageSize = 47) { - + MangaReaderParser(context, MangaSource.ANIGLISCANS, "anigliscans.xyz", pageSize = 47, searchPageSize = 47) { override val listUrl = "/series" - override val datePattern = "MMM d, yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikLabParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/KomikLabParser.kt similarity index 71% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikLabParser.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/KomikLabParser.kt index 38a753044..151258009 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikLabParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/KomikLabParser.kt @@ -1,16 +1,14 @@ -package org.koitharu.kotatsu.parsers.site.mangareader.id +package org.koitharu.kotatsu.parsers.site.mangareader.en import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -import java.util.* -@MangaSourceParser("KOMIKLAB", "KomikLab", "id") +@MangaSourceParser("KOMIKLAB", "KomikLab", "en") internal class KomikLabParser(context: MangaLoaderContext) : MangaReaderParser(context, MangaSource.KOMIKLAB, "komiklab.com", pageSize = 20, searchPageSize = 10) { override val datePattern = "MMM d, yyyy" - override val sourceLocale: Locale = Locale.ENGLISH } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Nightscans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Nightscans.kt index 2d52b26e8..b7fc109dc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Nightscans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Nightscans.kt @@ -5,9 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("NIGHTSCANS", "Nightscans", "en") +@MangaSourceParser("NIGHTSCANS", "Night scans", "en") internal class Nightscans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.NIGHTSCANS, "nightscans.org", pageSize = 20, searchPageSize = 20) { - - override val datePattern = "MMM d, yyyy" + MangaReaderParser(context, MangaSource.NIGHTSCANS, "nightscans.net", pageSize = 20, searchPageSize = 10) { + override val listUrl = "/series" + override val selectMangaListImg = "img.ts-post-image, picture img" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OzulScansEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OzulScansEn.kt new file mode 100644 index 000000000..5fd9e8b53 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/OzulScansEn.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.en + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("OZULSCANSEN", "OzulScans En", "en") +internal class OzulScansEn(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.OZULSCANSEN, "ozulscansen.com", pageSize = 30, searchPageSize = 10) { + override val listUrl = "/comics" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LegionScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/InariManga.kt similarity index 54% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LegionScans.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/InariManga.kt index dc16d89c4..bd9c56030 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/LegionScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/InariManga.kt @@ -4,14 +4,10 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -import java.util.* +import java.util.Locale -@MangaSourceParser("LEGIONSCANS", "Legion Scans", "es") -internal class LegionScans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.LEGIONSCANS, "legionscans.com", pageSize = 20, searchPageSize = 20) { - - override val datePattern = "MMM d, yyyy" +@MangaSourceParser("INARIMANGA", "Inari Manga", "es") +internal class InariManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.INARIMANGA, "inarimanga.com", pageSize = 20, searchPageSize = 10) { override val sourceLocale: Locale = Locale.ENGLISH - } - diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt index 962ad755c..007c38d4f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/DoujinDesuRip.kt @@ -8,7 +8,4 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("DOUJINDESURIP", "Doujin Desu Rip", "id", ContentType.HENTAI) internal class DoujinDesuRip(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.DOUJINDESURIP, "doujindesu.rip", pageSize = 10, searchPageSize = 10) { - - override val datePattern = "MMM d, yyyy" -} + MangaReaderParser(context, MangaSource.DOUJINDESURIP, "doujindesu.cfd", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MangaShiro.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MangaShiro.kt new file mode 100644 index 000000000..425ecc0bd --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/MangaShiro.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.id + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.util.Locale + +@MangaSourceParser("MANGASHIRO", "Manga Shiro", "id") +internal class MangaShiro(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGASHIRO, "mangashiro.me", pageSize = 20, searchPageSize = 10) { + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mirrordesu.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaLand.kt similarity index 52% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mirrordesu.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaLand.kt index 264186a22..15c8dce36 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Mirrordesu.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwaLand.kt @@ -5,12 +5,10 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.util.* -@MangaSourceParser("MIRRORDESU", "Mirrordesu", "id", ContentType.HENTAI) -internal class Mirrordesu(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MIRRORDESU, "mirrordesu.ink", pageSize = 20, searchPageSize = 20) { - - override val listUrl = "/komik" - override val datePattern = "MMM d, yyyy" - +@MangaSourceParser("MANHWALAND", "Manhwa Land", "id", ContentType.HENTAI) +internal class ManhwaLand(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANHWALAND, "manhwaland.lat", pageSize = 20, searchPageSize = 10) { + override val sourceLocale: Locale = Locale.ENGLISH } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwadesuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwadesuParser.kt index 51e60de85..76678bd02 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwadesuParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/ManhwadesuParser.kt @@ -2,13 +2,12 @@ package org.koitharu.kotatsu.parsers.site.mangareader.id import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("MANHWADESU", "ManhwaDesu", "id") +@MangaSourceParser("MANHWADESU", "ManhwaDesu", "id", ContentType.HENTAI) internal class ManhwadesuParser(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MANHWADESU, "manhwadesu.top", pageSize = 20, searchPageSize = 10) { - + MangaReaderParser(context, MangaSource.MANHWADESU, "manhwadesu.one", pageSize = 20, searchPageSize = 10) { override val listUrl = "/komik" - override val datePattern = "MMM d, yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/HentaiSsssscanlator.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/HentaiSsssscanlator.kt new file mode 100644 index 000000000..089d86013 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pt/HentaiSsssscanlator.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.pt + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("HENTAISSSSSCANLATOR", "Sssscanlator Hentai", "pt", type = ContentType.HENTAI) +internal class HentaiSsssscanlator(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.HENTAISSSSSCANLATOR, "hentais.sssscanlator.com", pageSize = 20, searchPageSize = 10,) { + override val datePattern = "MMM d, yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/th/Manga168.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/th/Manga168.kt new file mode 100644 index 000000000..11df5344c --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/th/Manga168.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.th + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("MANGA168", "Manga 168", "th", ContentType.HENTAI) +internal class Manga168(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGA168, "manga168.com", pageSize = 40, searchPageSize = 30) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AsuraTRParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AsuraTRParser.kt index f2e6c93dd..f9cb2ae71 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AsuraTRParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/AsuraTRParser.kt @@ -5,9 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser -@MangaSourceParser("ASURATR", "Asura Scans (tr)", "tr") +@MangaSourceParser("ASURATR", "Armoni Scans", "tr") internal class AsuraTRParser(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.ASURATR, "asurascanstr.com", pageSize = 30, searchPageSize = 10) { - - override val datePattern = "MMM d, yyyy" -} + MangaReaderParser(context, MangaSource.ASURATR, "armoniscans.com", pageSize = 30, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MangaGezgini.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MangaGezgini.kt new file mode 100644 index 000000000..a96dd7f8f --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MangaGezgini.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.tr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("MANGAGEZGINI", "Manga Gezgini", "tr") +internal class MangaGezgini(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGAGEZGINI, "mangagezgini.com", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Mangaefendisi.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Mangaefendisi.kt new file mode 100644 index 000000000..fedb44528 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/Mangaefendisi.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.tr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("MANGAEFENDISI", "Mangaefendisi", "tr") +internal class Mangaefendisi(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGAEFENDISI, "mangaefendisi.net", pageSize = 30, searchPageSize = 20) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt index 7c694c772..e24412388 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MoonDaisyScans.kt @@ -8,4 +8,4 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("MOONDAISY_SCANS", "MoonDaisyScans", "tr", ContentType.HENTAI) internal class MoonDaisyScans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MOONDAISY_SCANS, "moondaisyscans.com", pageSize = 20, searchPageSize = 10) + MangaReaderParser(context, MangaSource.MOONDAISY_SCANS, "moondaisyscans.biz", pageSize = 20, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/TarotScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/TarotScans.kt new file mode 100644 index 000000000..7b2c9b7f9 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/TarotScans.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.tr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser + +@MangaSourceParser("TAROTSCANS", "Tarot Scans", "tr") +internal class TarotScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.TAROTSCANS, "www.tarotscans.com", pageSize = 20, searchPageSize = 10) + diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt index 0d87807b8..193a04e0c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt @@ -114,7 +114,7 @@ internal abstract class MmrcmsParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated", - title = div.selectFirstOrThrow("a").text().orEmpty(), + title = div.selectFirstOrThrow("h3 a").text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/FrScansCom.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/FrScansCom.kt index 6538c913d..a9d60be44 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/FrScansCom.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/FrScansCom.kt @@ -6,9 +6,8 @@ import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.mmrcms.MmrcmsParser import java.util.Locale -@MangaSourceParser("FRSCANSCOM", "FrScansCom", "fr") +@MangaSourceParser("FRSCANSCOM", "Fr Scans Com", "fr") internal class FrScansCom(context: MangaLoaderContext) : MmrcmsParser(context, MangaSource.FRSCANSCOM, "frscans.com") { - override val sourceLocale: Locale = Locale.ENGLISH } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpScanVf.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpScanVf.kt new file mode 100644 index 000000000..c85d258b5 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/fr/JpScanVf.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.parsers.site.mmrcms.fr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mmrcms.MmrcmsParser +import java.util.* + +@MangaSourceParser("JPSCANVF", "JpScanVf", "fr") +internal class JpScanVf(context: MangaLoaderContext) : + MmrcmsParser(context, MangaSource.JPSCANVF, "jpscan-vf.net") { + + //the search doesn't work on the source. + + override val sourceLocale: Locale = Locale.ENGLISH +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt index dbecf159d..f8ee646b8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt @@ -53,23 +53,9 @@ internal abstract class NepnepParser( !query.isNullOrEmpty() -> { if (m.getString("i").contains(query.urlEncoded(), ignoreCase = true)) { manga.add( - Manga( - id = generateUid(href), - title = m.getString("i").replace('-', ' '), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = imgUrl, - tags = emptySet(), - state = null, - author = null, - source = source, - ), + addManga(href, imgUrl, m), ) } - } !tags.isNullOrEmpty() -> { @@ -82,50 +68,40 @@ internal abstract class NepnepParser( } if (found) { manga.add( - Manga( - id = generateUid(href), - title = m.getString("i").replace('-', ' '), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = imgUrl, - tags = emptySet(), - state = null, - author = null, - source = source, - ), + addManga(href, imgUrl, m), ) } } else -> { manga.add( - Manga( - id = generateUid(href), - title = m.getString("i").replace('-', ' '), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = imgUrl, - tags = emptySet(), - state = null, - author = null, - source = source, - ), + addManga(href, imgUrl, m), ) } } } - return manga } + private fun addManga(href: String, imgUrl: String, m: JSONObject): Manga { + return Manga( + id = generateUid(href), + title = m.getString("i").replace('-', ' '), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = imgUrl, + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + override suspend fun getTags(): Set { val doc = webClient.httpGet("https://$domain/search/").parseHtml() val tags = doc.selectFirstOrThrow("script:containsData(vm.AvailableFilters)").data() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt index 4eea28c47..8971e26e4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt @@ -27,7 +27,6 @@ internal class Bakai(context: MangaLoaderContext) : PagedMangaParser(context, Ma tags: Set?, sortOrder: SortOrder, ): List { - val url = buildString { append("https://") append(domain) @@ -45,7 +44,6 @@ internal class Bakai(context: MangaLoaderContext) : PagedMangaParser(context, Ma } append("&quick=1&type=cms_records1&page=") append(page.toString()) - } else { append("/hentai/") append("page/") @@ -54,10 +52,9 @@ internal class Bakai(context: MangaLoaderContext) : PagedMangaParser(context, Ma } val doc = webClient.httpGet(url).parseHtml() if (!tags.isNullOrEmpty() or !query.isNullOrEmpty()) { - return doc.select("ol.ipsStream li.ipsStreamItem") .map { div -> - val href = div.selectFirstOrThrow("div.ipsStreamItem_snippet a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("div.ipsStreamItem_snippet a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h2.ipsStreamItem_title").text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt index 6f5c5aae8..51fdcce35 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt @@ -42,7 +42,6 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, } append("/?s=") append(query.urlEncoded()) - } else { when (sortOrder) { SortOrder.POPULARITY -> append("/") @@ -64,7 +63,7 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, } return item.map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h2").text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/GoldenManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/GoldenManga.kt index 56bcf6f99..1d7fd4829 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/GoldenManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/GoldenManga.kt @@ -15,33 +15,27 @@ import java.util.* internal class GoldenManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.GOLDENMANGA, 36) { override val sortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) - override val configKeyDomain = ConfigKey.Domain("goldenmanga.top") - override val headers: Headers = Headers.Builder() - .add("User-Agent", UserAgents.CHROME_DESKTOP) + .add("User-Agent", UserAgents.CHROME_MOBILE) .build() - override suspend fun getListPage( page: Int, query: String?, tags: Set?, sortOrder: SortOrder, ): List { - val url = buildString { append("https://") append(domain) append("/mangas") append("?pagina=") append(page.toString()) - if (!query.isNullOrEmpty()) { append("&search=") append(query.urlEncoded()) } - if (!tags.isNullOrEmpty()) { append("&genero=") for (tag in tags) { @@ -53,7 +47,7 @@ internal class GoldenManga(context: MangaLoaderContext) : PagedMangaParser(conte val doc = webClient.httpGet(url).parseHtml() return doc.select("section.row div.mangas") .map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("a h3").text(), @@ -107,7 +101,6 @@ internal class GoldenManga(context: MangaLoaderContext) : PagedMangaParser(conte author = root.select("h5.cg_color a")[1].text(), description = root.getElementById("manga_capitulo_descricao")?.html(), chapters = root.requireElementById("capitulos").select("li") - .mapChapters(reversed = true) { i, div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val dateText = div.selectFirstOrThrow("div.col-sm-5 span").text() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/MintMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/MintMangaParser.kt index d54cfc409..63c75a29c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/MintMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/MintMangaParser.kt @@ -13,6 +13,7 @@ internal class MintMangaParser( override val configKeyDomain = ConfigKey.Domain( "mintmanga.live", "mintmanga.com", + "m.mintmanga.live", ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt index c1bc6fb10..3e0594a30 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt @@ -28,7 +28,7 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang } val doc = webClient.httpGet(url).parseHtml() return doc.select(".series-list").map { div -> - val href = div.selectFirstOrThrow("a").attrAsAbsoluteUrl("href") + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h2").text(),