From e67beae235b1e4048fed2d53f4b977e12c08b8cb Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 15 Jul 2023 21:20:23 +0200 Subject: [PATCH] add source request --- .../parsers/site/madara/pt/HikariScan.kt | 13 ++ .../site/mangareader/MangaReaderParser.kt | 2 +- .../parsers/site/mangareader/ar/MangaProtm.kt | 21 ++ .../parsers/site/mangareader/en/RealmScans.kt | 20 ++ .../parsers/site/mangareader/id/Komikcast.kt | 216 ++++++++++++++++++ 5 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HikariScan.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HikariScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HikariScan.kt new file mode 100644 index 00000000..704dfa74 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HikariScan.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("HIKARISCAN", "Hikari Scan", "pt") +internal class HikariScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.HIKARISCAN, "hikariscan.com.br", pageSize = 10) { + + override val datePattern: String = "dd 'de' MMMMM 'de' yyyy" +} 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 5f38d19d..83a38464 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 @@ -204,7 +204,7 @@ internal abstract class MangaReaderParser( return parseMangaList(webClient.httpGet(url).parseHtml()) } - private fun parseMangaList(docs: Document): List { + protected open fun parseMangaList(docs: Document): List { return docs.select(".postbody .listupd .bs .bsx").mapNotNull { val a = it.selectFirst("a") ?: return@mapNotNull null val relativeUrl = a.attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt new file mode 100644 index 00000000..07c9fd55 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/MangaProtm.kt @@ -0,0 +1,21 @@ +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.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.text.SimpleDateFormat +import java.util.Locale + +@MangaSourceParser("MANGAPROTM", "MangaProtm", "ar") +internal class MangaProtm(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGAPROTM, pageSize = 20, searchPageSize = 20) { + + override val configKeyDomain: ConfigKey.Domain + get() = ConfigKey.Domain("mangaprotm.com") + + override val listUrl = "/series" + override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar", "AR")) + +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt new file mode 100644 index 00000000..4dfa880d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RealmScans.kt @@ -0,0 +1,20 @@ +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.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import java.text.SimpleDateFormat +import java.util.Locale + + +@MangaSourceParser("REALMSCANS", "RealmScans", "en") +internal class RealmScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.REALMSCANS, pageSize = 30, searchPageSize = 50) { + override val configKeyDomain: ConfigKey.Domain + get() = ConfigKey.Domain("realmscans.xyz") + + override val listUrl = "/series" + override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH) +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt new file mode 100644 index 00000000..2a58038e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt @@ -0,0 +1,216 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.id + +import org.json.JSONObject +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaChapter +import org.koitharu.kotatsu.parsers.model.MangaPage +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.model.MangaState +import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN +import org.koitharu.kotatsu.parsers.model.WordSet +import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser +import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl +import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrlOrNull +import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.generateUid +import org.koitharu.kotatsu.parsers.util.mapChapters +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet +import org.koitharu.kotatsu.parsers.util.parseHtml +import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow +import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl +import org.koitharu.kotatsu.parsers.util.tryParse +import java.text.DateFormat +import java.util.Calendar + +@MangaSourceParser("KOMIKCAST", "Komikcast", "id") +internal class Komikcast(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.KOMIKCAST, pageSize = 60, searchPageSize = 28) { + override val configKeyDomain: ConfigKey.Domain + get() = ConfigKey.Domain("komikcast.io") + + override val listUrl = "/daftar-komik" + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val chapters = docs.select("#chapter-wrapper > li").mapChapters(reversed = true) { index, element -> + val url = element.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapChapters null + MangaChapter( + id = generateUid(url), + name = docs.selectFirst("a.chapter-link-item")?.ownText().orEmpty(), + url = url, + number = index + 1, + scanlator = null, + uploadDate = parseChapterDate( + chapterDateFormat, + element.selectFirst("div.chapter-link-time")?.text(), + ), + branch = null, + source = source, + ) + } + return parseInfo(docs, manga, chapters) + } + + override suspend fun parseInfo(docs: Document, manga: Manga, chapters: List): Manga { + + /// set if is table + + val tagMap = getOrCreateTagMap() + + val tags = docs.select(".komik_info-content-genre > a").mapNotNullToSet { tagMap[it.text()] } + + val state = docs.selectFirst(".komik_info-content-meta span:contains(Status)")?.lastElementChild() + + val mangaState = state?.let { + when (it.text()) { + "Ongoing" -> MangaState.ONGOING + "Completed" -> MangaState.FINISHED + else -> null + } + } + + + val author = docs.selectFirst(".komik_info-content-meta span:contains(Author)")?.lastElementChild()?.text() + + val nsfw = docs.selectFirst(".restrictcontainer") != null + || docs.selectFirst(".info-right .alr") != null + || docs.selectFirst(".postbody .alr") != null + + return manga.copy( + description = docs.selectFirst("div.komik_info-description-sinopsis")?.text(), + state = mangaState, + author = author, + isNsfw = manga.isNsfw || nsfw, + tags = tags, + chapters = chapters, + ) + } + + + override fun parseMangaList(docs: Document): List { + return docs.select("div.list-update_item").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 + + val name = it.selectFirst("h3.title")?.text().orEmpty() + Manga( + id = generateUid(relativeUrl), + url = relativeUrl, + title = name, + altTitle = null, + publicUrl = a.attrAsAbsoluteUrl("href"), + rating = rating, + isNsfw = isNsfwSource, + coverUrl = it.selectFirst("img.ts-post-image")?.imageUrl().orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + + private fun Element.imageUrl(): String { + return attrAsAbsoluteUrlOrNull("src") + ?: attrAsAbsoluteUrlOrNull("data-src") + ?: attrAsAbsoluteUrlOrNull("data-cfsrc") + ?: "" + } + + override suspend fun getPages(chapter: MangaChapter): List { + val chapterUrl = chapter.url.toAbsoluteUrl(domain) + val docs = webClient.httpGet(chapterUrl).parseHtml() + + val test = docs.select("script:containsData(ts_reader)") + if (test.isNullOrEmpty()) { + return docs.select("div#chapter_body img").map { img -> + val url = img.imageUrl() + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = source, + ) + } + } else { + val script = docs.selectFirstOrThrow("script:containsData(ts_reader)") + val images = JSONObject(script.data().substringAfter('(').substringBeforeLast(')')) + .getJSONArray("sources") + .getJSONObject(0) + .getJSONArray("images") + val pages = ArrayList(images.length()) + for (i in 0 until images.length()) { + pages.add( + MangaPage( + id = generateUid(images.getString(i)), + url = images.getString(i), + preview = null, + source = source, + ), + ) + } + + return pages + + } + + } + + protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + date ?: return 0 + return when { + date.endsWith(" ago", ignoreCase = true) -> { + 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( + "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( + "mins", + ).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 + else -> 0 + } + } +}