diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt index 1d6d60d6..53169c8b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt @@ -104,7 +104,12 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( filter.locale?.let { append("&langs=") - append(it.language) + if (it.language == "in") { + append("id") + } else { + append(it.language) + } + } append("&genres=") 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 0322983b..d7408639 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 @@ -31,7 +31,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex when (filter) { is MangaListFilter.Search -> { - append("/series?search=") + append("/?search=") append(filter.query.urlEncoded()) if (page > 1) { append("&page=") @@ -95,7 +95,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex publicUrl = href.toAbsoluteUrl(domain), rating = RATING_UNKNOWN, isNsfw = false, - coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), + coverUrl = div.selectFirstOrThrow("img").src()?.replace("thumbnail_", "").orEmpty(), tags = emptySet(), state = when (div.selectFirst(".status")?.text()) { "مستمرة" -> MangaState.ONGOING 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 a3212738..5b57905f 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 @@ -2,13 +2,11 @@ package org.koitharu.kotatsu.parsers.site.en import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -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.* @@ -23,9 +21,12 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context override val isMultipleTagsSupported = false - override val headers: Headers = Headers.Builder() - .add("User-Agent", UserAgents.CHROME_DESKTOP) - .build() + private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent()) + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt index 4353adca..a2e23791 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt @@ -174,10 +174,10 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val root = doc.body().selectFirstOrThrow("div.page_select") - val isManga = root.select("select") + val root = doc.body().selectFirst("div.page_select") + val isManga = root?.select("select") - if (isManga.isEmpty()) {//Webtoon + if (isManga.isNullOrEmpty()) {//Webtoon val imgElements = doc.select("div#viewer.read_img img.image") return imgElements.map { val href = it.attr("src") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt index 03bdb293..894782d5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt @@ -207,6 +207,9 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( private suspend fun redirectToReadingPage(document: Document): Document { val script1 = document.selectFirst("script:containsData(uniqid)") val script2 = document.selectFirst("script:containsData(window.location.replace)") + val script3 = document.selectFirst("script:containsData(redirectUrl)") + val script4 = document.selectFirst("input#redir") + val script5 = document.selectFirst("script:containsData(window.opener):containsData(location.replace)") val redirectHeaders = Headers.Builder().set("Referer", document.baseUri()).build() @@ -228,15 +231,52 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( if (script2 != null) { val data = script2.data() - val regexRedirect = """window\.location\.replace\('(.+)'\)""".toRegex() - val url = regexRedirect.find(data)!!.groupValues[1] + val regexRedirect = """window\.location\.replace\(['"](.+)['"]\)""".toRegex() + val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() + + if (url != null) { + return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml()) + } + } + + if (script3 != null) { + val data = script3.data() + val regexRedirect = """redirectUrl\s*=\s*'(.+)'""".toRegex() + val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() + + if (url != null) { + return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml()) + } + } + + if (script4 != null) { + val url = script4.attr("value").unescapeUrl() return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml()) } + if (script5 != null) { + val data = script5.data() + val regexRedirect = """;[^.]location\.replace\(['"](.+)['"]\)""".toRegex() + val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() + + if (url != null) { + return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml()) + } + } + return document } + private fun String.unescapeUrl(): String { + return if (this.startsWith("http:\\/\\/") || this.startsWith("https:\\/\\/")) { + this.replace("\\/", "/") + } else { + this + } + } + + override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/library", headers).parseHtml() val elements = doc.body().select("div#books-genders > div > div") 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 index 8e37063d..844093cb 100644 --- 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 @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.parsers.site.heancms.es +import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* @@ -7,6 +8,7 @@ import org.koitharu.kotatsu.parsers.site.heancms.HeanCms import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.* +@Broken // Not dead but changed template and url visualikigai.com @MangaSourceParser("YUGEN_MANGAS_ES", "YugenMangas.lat", "es", ContentType.HENTAI) internal class YugenMangasEs(context: MangaLoaderContext) : HeanCms(context, MangaSource.YUGEN_MANGAS_ES, "yugenmangas.lat") { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt new file mode 100644 index 00000000..a807d132 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt @@ -0,0 +1,307 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp + +import androidx.collection.scatterSetOf +import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +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.* + +internal abstract class KeyoappParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 24, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent()) + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val isMultipleTagsSupported = false + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.NEWEST, + ) + + protected open val listUrl = "series/" + protected open val datePattern = "MMM d, yyyy" + + + @JvmField + protected val ongoing = scatterSetOf( + "ongoing", + ) + + @JvmField + protected val finished = scatterSetOf( + "completed", + ) + + @JvmField + protected val paused = scatterSetOf( + "paused", + ) + + @JvmField + protected val upcoming = scatterSetOf( + "dropped", + ) + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + var query = "" + var tag = "" + + if (page > 1) { + return emptyList() + } + + val url = urlBuilder().apply { + + when (filter) { + is MangaListFilter.Search -> { + addPathSegment("series") + query = filter.query + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + tag = it.title + } + } + + when (filter.sortOrder) { + SortOrder.UPDATED -> addPathSegment("latest") + SortOrder.NEWEST -> addPathSegment("series") + else -> addPathSegment("latest") + } + + } + + null -> addPathSegment("latest") + } + }.build() + + return parseMangaList(webClient.httpGet(url).parseHtml(), tag, query) + } + + + protected open fun parseMangaList(doc: Document, tag: String, query: String): List { + + val manga = ArrayList() + + doc.select("#searched_series_page button").ifEmpty { + doc.select("div.grid > div.group") + }.map { div -> + + val title = div.selectFirstOrThrow("h3").text().orEmpty() + if (query.isNotEmpty() && title.contains(query, ignoreCase = true)) { + manga.add(addManga(div)) + } + + // Not all tags are present in UPDATED + val tags = div.attr("tags") ?: div.select("div.gap-1 a").joinToString() + if (tag.isNotEmpty() && tags.contains(tag, ignoreCase = true)) { + manga.add(addManga(div)) + } + + if (query.isEmpty() && tag.isEmpty()) { + manga.add(addManga(div)) + } + + } + + return manga + } + + + private fun addManga(div: Element): Manga { + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val cover = div.selectFirst("div.h-full") ?: div.selectFirst("a") + return Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = cover?.styleValueOrNull("background-image")?.cssUrl().orEmpty(), + title = div.selectFirstOrThrow("h3").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = div.select("div.gap-1 a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast('='), + title = a.text().toTitleCase(), + source = source, + ) + }, + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + + private fun String.cssUrl(): String? { + val fromIndex = indexOf("url(") + if (fromIndex == -1) { + return null + } + val toIndex = indexOf(')', startIndex = fromIndex) + return if (toIndex == -1) { + null + } else { + substring(fromIndex + 4, toIndex).trim() + } + } + + + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() + return doc.requireElementById("series_tags_page").select("button").mapNotNullToSet { button -> + val key = button.attr("tag") ?: return@mapNotNullToSet null + val name = button.text().toTitleCase(sourceLocale) + MangaTag( + key = key, + title = name, + source = source, + ) + } + } + + protected open val selectDesc = "div.grid > div.overflow-hidden > p" + protected open val selectState = "div[alt=Status]" + protected open val selectTag = "div.grid:has(>h1) > div > a" + protected open val selectAuthor = "div[alt=Author]" + protected open val selectChapter = "#chapters > a:not(:has(.text-sm span:matches(Upcoming)))" + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + manga.copy( + tags = doc.body().select(selectTag).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast('='), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = doc.selectFirstOrThrow(selectDesc).html(), + state = when ( + doc.selectFirstOrThrow(selectState).text().lowercase() + ) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + in paused -> MangaState.PAUSED + in upcoming -> MangaState.UPCOMING + else -> null + }, + chapters = doc.select(selectChapter) + .mapChapters(reversed = true) { i, a -> + val href = a.attrAsRelativeUrl("href") + val name = a.selectFirstOrThrow("span.truncate").text() + val dateText = a.selectLast("div.text-xs.w-fit")?.text() ?: "0" + MangaChapter( + id = generateUid(href), + name = name, + number = i + 1f, + volume = 0, + url = href, + scanlator = null, + uploadDate = parseChapterDate( + dateFormat, + dateText, + ), + branch = null, + source = source, + ) + }, + ) + } + + protected open val selectPage = "#pages > 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, + ) + } + } + + protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { + val d = date?.lowercase() ?: return 0 + return when { + d.endsWith(" ago") -> parseRelativeDate(date) + + d.startsWith("year") -> Calendar.getInstance().apply { + add(Calendar.DAY_OF_MONTH, -1) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + + 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 + + date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { + if (it.contains(Regex("""\d\D\D"""))) { + it.replace(Regex("""\D"""), "") + } else { + it + } + }.let { dateFormat.tryParse(it.joinToString(" ")) } + + 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("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + + WordSet("minute", "minutes").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + + WordSet("hour", "hours").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + + WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -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 + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/EzManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/EzManga.kt new file mode 100644 index 00000000..20d8507e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/EzManga.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("EZMANGA", "EzManga", "en") +internal class EzManga(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.EZMANGA, "ezmanga.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/KewnScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/KewnScans.kt new file mode 100644 index 00000000..2e6cc01e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/KewnScans.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("KEWNSCANS", "KewnScans", "en") +internal class KewnScans(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.KEWNSCANS, "kewnscans.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/LaidBackScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/LaidBackScans.kt new file mode 100644 index 00000000..24d90fd2 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/en/LaidBackScans.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("LAIDBACKSCANS", "LaidBackScans", "en") +internal class LaidBackScans(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.LAIDBACKSCANS, "laidbackscans.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/AnteikuScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/AnteikuScan.kt new file mode 100644 index 00000000..2f9b891a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/AnteikuScan.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("ANTEIKUSCAN", "AnteikuScan", "fr") +internal class AnteikuScan(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.ANTEIKUSCAN, "anteikuscan.fr") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/Astrames.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/Astrames.kt new file mode 100644 index 00000000..2f0dfeaf --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/Astrames.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("ASTRAMES", "Astrames", "fr") +internal class Astrames(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.ASTRAMES, "astrames.fr") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/EdScanlation.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/EdScanlation.kt new file mode 100644 index 00000000..d89564af --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/EdScanlation.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("EDSCANLATION", "EdScanlation", "fr") +internal class EdScanlation(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.EDSCANLATION, "edscanlation.fr") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/StarboundScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/StarboundScans.kt new file mode 100644 index 00000000..27abcf94 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/fr/StarboundScans.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.keyoapp.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.keyoapp.KeyoappParser + +@MangaSourceParser("STARBOUNDSCANS", "StarboundScans", "fr") +internal class StarboundScans(context: MangaLoaderContext) : + KeyoappParser(context, MangaSource.STARBOUNDSCANS, "starboundscans.org") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index f811530f..5f43a3fc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt @@ -23,8 +23,14 @@ internal abstract class MadaraParser( ) : PagedMangaParser(context, source, pageSize) { override val configKeyDomain = ConfigKey.Domain(domain) + private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent()) + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val isMultipleTagsSupported = false override val availableSortOrders: Set = EnumSet.of( @@ -333,7 +339,7 @@ internal abstract class MadaraParser( publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4") - ?: div.selectFirst(".manga-name"))?.text().orEmpty(), + ?: div.selectFirst(".manga-name") ?: div.selectFirst(".post-title"))?.text().orEmpty(), altTitle = null, rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a -> @@ -651,11 +657,6 @@ internal abstract class MadaraParser( } } - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - // Parses dates in this form: // 21 hours ago private fun parseRelativeDate(date: String): Long { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/TopManhua.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/TopManhua.kt index 0965e9d0..eb6712a2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/TopManhua.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/TopManhua.kt @@ -5,9 +5,10 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("TOPMANHUA", "TopManhua", "en") +@MangaSourceParser("TOPMANHUA", "ManhuaTop", "en") internal class TopManhua(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.TOPMANHUA, "topmanhua.com") { + MadaraParser(context, MangaSource.TOPMANHUA, "manhuatop.org") { override val tagPrefix = "manhua-genre/" + override val listUrl = "manhua/" override val datePattern = "MM/dd/yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonSoft.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonSoft.kt new file mode 100644 index 00000000..ccd5c956 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonSoft.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.madara.fr + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("EPSILONSOFT", "EpsilonSoft", "fr") +internal class EpsilonSoft(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.EPSILONSOFT, "epsilonsoft.to") { + override val datePattern = "dd/MM/yy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/EpsilonscanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonscanParser.kt similarity index 64% rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/EpsilonscanParser.kt rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonscanParser.kt index 563d3a05..ddea9da8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/EpsilonscanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/EpsilonscanParser.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.parsers.site.mangareader.fr +package org.koitharu.kotatsu.parsers.site.madara.fr import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser @@ -7,7 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("EPSILONSCAN", "EpsilonScan", "fr", ContentType.HENTAI) internal class EpsilonscanParser(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.EPSILONSCAN, "epsilonscan.fr") { - override val withoutAjax = true - override val isTagsExclusionSupported = false + MadaraParser(context, MangaSource.EPSILONSCAN, "epsilonscan.to") { + override val datePattern = "dd/MM/yy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/HarmonyScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/HarmonyScan.kt new file mode 100644 index 00000000..569b4e70 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/fr/HarmonyScan.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.madara.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.madara.MadaraParser + +@MangaSourceParser("HARMONYSCAN", "HarmonyScan", "fr") +internal class HarmonyScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.HARMONYSCAN, "harmony-scan.fr") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CafecomYaoi.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CafecomYaoi.kt index c35a1291..c25237bb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CafecomYaoi.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/CafecomYaoi.kt @@ -2,12 +2,12 @@ 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.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.madara.MadaraParser -@MangaSourceParser("CAFECOMYAOI", "CafecomYaoi", "pt") +@MangaSourceParser("CAFECOMYAOI", "CafecomYaoi", "pt", ContentType.HENTAI) internal class CafecomYaoi(context: MangaLoaderContext) : MadaraParser(context, MangaSource.CAFECOMYAOI, "cafecomyaoi.com.br") { override val datePattern = "dd/MM/yyyy" - override val postReq = true } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DreamScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DreamScan.kt new file mode 100644 index 00000000..16871d71 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/DreamScan.kt @@ -0,0 +1,10 @@ +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("DREAMSCAN", "DreamScan", "pt") +internal class DreamScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.DREAMSCAN, "dreamscan.com.br") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HuntersScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HuntersScan.kt index cb48a452..2cf9455a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HuntersScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/HuntersScan.kt @@ -11,5 +11,4 @@ internal class HuntersScan(context: MangaLoaderContext) : override val withoutAjax = true override val datePattern = "MM/dd/yyyy" override val tagPrefix = "series-genre/" - override val listUrl = "manga/" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LeitorDeManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LeitorDeManga.kt new file mode 100644 index 00000000..4d51b988 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/LeitorDeManga.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("LEITORDEMANGA", "LeitorDeManga", "pt") +internal class LeitorDeManga(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.LEITORDEMANGA, "leitordemanga.com", 10) { + override val datePattern = "dd/MM/yyyy" + override val listUrl = "ler-manga/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt index d0877738..44749036 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MaidScan.kt @@ -8,6 +8,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("MAIDSCAN", "MaidScan", "pt", ContentType.HENTAI) internal class MaidScan(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.MAIDSCAN, "maidscan.com.br", 10) { - override val datePattern: String = "dd 'de' MMMMM 'de' yyyy" + MadaraParser(context, MangaSource.MAIDSCAN, "maidscans.com", 10) { + override val datePattern: String = "dd/MM/yyyy" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MugiwarasOficial.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MugiwarasOficial.kt new file mode 100644 index 00000000..8d024f3a --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/MugiwarasOficial.kt @@ -0,0 +1,12 @@ +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("MUGIWARASOFICIAL", "MugiwarasOficial", "pt") +internal class MugiwarasOficial(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MUGIWARASOFICIAL, "mugiwarasoficial.com") { + override val datePattern: String = "dd/MM/yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/SussyScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/SussyScan.kt index 671b5ed1..5e364f8c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/SussyScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/SussyScan.kt @@ -7,6 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser @MangaSourceParser("SUSSYSCAN", "SussyScan", "pt") internal class SussyScan(context: MangaLoaderContext) : - MadaraParser(context, MangaSource.SUSSYSCAN, "sussyscan.com") { - override val datePattern: String = "dd/MM/yyyy" -} + MadaraParser(context, MangaSource.SUSSYSCAN, "oldi.sussytoons.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MilaSub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MilaSub.kt new file mode 100644 index 00000000..19dde699 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/tr/MilaSub.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("MILASUB", "MilaSub", "tr") +internal class MilaSub(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.MILASUB, "www.milasub.co", 20) { + override val datePattern = "d MMMM yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt index 09d7f52c..b29db16f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt @@ -32,7 +32,9 @@ internal class Mangakakalot(context: MangaLoaderContext) : is MangaListFilter.Search -> { append(searchUrl) - append(filter.query.urlEncoded()) + val regex = Regex("[^A-Za-z0-9 ]") + val q = regex.replace(filter.query, "") + append(q.replace(" ", "_")) append("?page=") } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/MangasScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/MangasScans.kt new file mode 100644 index 00000000..1badafa7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/MangasScans.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser + +@MangaSourceParser("MANGASSCANS", "MangasScans", "fr") +internal class MangasScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.MANGASSCANS, "mangas-scans.com", pageSize = 30, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RimuScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RimuScans.kt new file mode 100644 index 00000000..7baf51a5 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/RimuScans.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.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.mangareader.MangaReaderParser + +@MangaSourceParser("RIMUSCANS", "RimuScans", "fr") +internal class RimuScans(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.RIMUSCANS, "rimuscans.fr", pageSize = 30, searchPageSize = 10) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikTapParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikTapParser.kt index 6a5eef33..52ff8d29 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikTapParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikTapParser.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 -import java.util.Locale -@MangaSourceParser("KOMIKTAP", "KomikTap", "id") +@MangaSourceParser("KOMIKTAP", "KomikTap", "id", ContentType.HENTAI) internal class KomikTapParser(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.KOMIKTAP, "komiktap.me", pageSize = 25, searchPageSize = 10) { - override val sourceLocale: Locale = Locale.ENGLISH + MangaReaderParser(context, MangaSource.KOMIKTAP, "komiktap.info", pageSize = 25, searchPageSize = 10) { override val isTagsExclusionSupported = false } 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 index eec44203..7f50fb00 100644 --- 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 @@ -13,7 +13,7 @@ import java.util.* @MangaSourceParser("KOMIKCAST", "KomikCast", "id") internal class Komikcast(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.KOMIKCAST, "komikcast.lol", pageSize = 60, searchPageSize = 28) { + MangaReaderParser(context, MangaSource.KOMIKCAST, "komikcast.cz", pageSize = 60, searchPageSize = 28) { override val listUrl = "/daftar-komik" override val datePattern = "MMM d, yyyy" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MilaSub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MilaSub.kt deleted file mode 100644 index 9c5217a9..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/tr/MilaSub.kt +++ /dev/null @@ -1,12 +0,0 @@ -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("MILASUB", "MilaSub", "tr") -internal class MilaSub(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.MILASUB, "www.milasub.com", pageSize = 20, searchPageSize = 10) { - override val isTagsExclusionSupported = false -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt index 6c240306..31b0f4a9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt @@ -15,7 +15,7 @@ import java.util.* @MangaSourceParser("YUGENMANGAS", "YugenApp", "pt") class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.YUGENMANGAS, 28) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("yugenapp.lat") override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { @@ -40,13 +40,22 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga is MangaListFilter.Advanced -> { - - val url = buildString { - append("https://api.") - append(domain) - append("/api/latest_updates/") + if (filter.sortOrder == SortOrder.UPDATED) { + val url = buildString { + append("https://api.") + append(domain) + append("/api/latest_updates/") + } + webClient.httpGet(url).parseJsonArray() + } else { + val url = buildString { + append("https://api.") + append(domain) + append("/api/series_novels/all_series/") + } + webClient.httpGet(url).parseJson().getJSONArray("series") } - webClient.httpGet(url).parseJsonArray() + } null -> { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt index f3b5a9d6..cbd39d9f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt @@ -3,7 +3,6 @@ package org.koitharu.kotatsu.parsers.site.scan import androidx.collection.ArrayMap import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey @@ -21,17 +20,20 @@ internal abstract class ScanParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING) - override val isSearchSupported = false override val configKeyDomain = ConfigKey.Domain(domain) override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + var query = false + val url = buildString { append("https://") append(domain) when (filter) { is MangaListFilter.Search -> { - throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED) // TODO + append("/search?q=") + append(filter.query.urlEncoded()) + query = true } is MangaListFilter.Advanced -> { @@ -64,24 +66,58 @@ internal abstract class ScanParser( } } - val doc = webClient.httpGet(url).parseHtml() - return doc.select(".series-paginated .series").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("img")?.attr("data-src")?.replace("\t", "").orEmpty(), - title = div.selectFirstOrThrow(".link-series h3").text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) + if (query) { + val doc = webClient.httpGet(url).parseRaw() + + val list = if (doc.contains("grid-item-series")) { + doc.split("grid-item-series").drop(1) + } else { + doc.split("class=\\u0022series\\u0022\\").drop(1) + } + + return list.map { l -> + val href = l.substringAfter("href=\\u0022\\").substringBefore("\\u0022").replace("\\", "") + val cover = l.substringAfter("data-src=\\u0022").substringBefore("\\u0022\\u003E").replace("\\", "") + val title = l.substringAfter("item-title\\u0022\\u003E").substringBefore("\\u003C\\/p\\u003E").ifEmpty { + l.substringAfter("\\u003Ch3\\u003E").substringBefore("\\u003C\\/h3\\u003E") + } + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = cover, + title = title, + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + + } else { + val doc = webClient.httpGet(url).parseHtml() + return doc.select(".series-paginated .series, .series-paginated .grid-item-series").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.attr("data-src")?.replace("\t", "").orEmpty(), + title = div.selectFirstOrThrow(".link-series h3, .item-title").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } } + } private var tagCache: ArrayMap? = null @@ -95,7 +131,8 @@ internal abstract class ScanParser( tagCache?.let { return@withLock it } val tagMap = ArrayMap() val tagElements = webClient.httpGet("https://$domain/manga").parseHtml() - .requireElementById("filter-wrapper").select(".form-filters div.form-check") + .requireElementById("filter-wrapper") + .select(".form-filters div.form-check, .form-filters div.custom-control") for (el in tagElements) { val name = el.selectFirstOrThrow("label").text() if (name.isEmpty()) continue @@ -113,29 +150,33 @@ internal abstract class ScanParser( val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val dateFormat = SimpleDateFormat("MM-dd-yyyy", sourceLocale) val tagMap = getOrCreateTagMap() - val selectTag = doc.select(".card-series-detail .col-6:contains(Categorie) div") + val selectTag = + doc.select(".card-series-detail .col-6:contains(Categorie) div, .card-series-about .mb-3:contains(Categorie) a, .card-series-about .mb-3:contains(Categorias) a") val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } return manga.copy( - rating = doc.selectFirst(".card-series-detail .rate-value span")?.ownText()?.toFloatOrNull()?.div(5f) + rating = doc.selectFirst(".card-series-detail .rate-value span, .card-series-about .rate-value span") + ?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, tags = tags, - author = doc.selectFirst(".card-series-detail .col-6:contains(Autore) div")?.text(), - altTitle = doc.selectFirst(".card div.col-12.mb-4 h2")?.text().orEmpty(), - description = doc.selectFirst(".card div.col-12.mb-4 p")?.html().orEmpty(), - chapters = doc.select(".chapters-list .col-chapter").mapChapters(reversed = true) { i, div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - MangaChapter( - id = generateUid(href), - name = div.selectFirstOrThrow("h5").html().substringBefore(""), - number = i + 1f, - volume = 0, - url = href, - scanlator = null, - uploadDate = dateFormat.tryParse(doc.selectFirstOrThrow("h5 div").text()), - branch = null, - source = source, - ) - }, + author = doc.selectFirst(".card-series-detail .col-6:contains(Autore) div, .card-series-about .mb-3:contains(Autore) a") + ?.text(), + altTitle = doc.selectFirst(".card div.col-12.mb-4 h2, .card-series-about .h6")?.text().orEmpty(), + description = doc.selectFirst(".card div.col-12.mb-4 p, .card-series-desc .mb-4 p")?.html().orEmpty(), + chapters = doc.select(".chapters-list .col-chapter, .card-list-chapter .col-chapter") + .mapChapters(reversed = true) { i, div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + MangaChapter( + id = generateUid(href), + name = div.selectFirstOrThrow("h5").html().substringBefore(""), + number = i + 1f, + volume = 0, + url = href, + scanlator = null, + uploadDate = dateFormat.tryParse(doc.selectFirstOrThrow("h5 div").text()), + branch = null, + source = source, + ) + }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/MangaItalia.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/MangaItalia.kt new file mode 100644 index 00000000..0ee53dae --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/MangaItalia.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.scan.it + +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.scan.ScanParser + +@MangaSourceParser("MANGAITALIA", "MangaItalia", "pt") +internal class MangaItalia(context: MangaLoaderContext) : + ScanParser(context, MangaSource.MANGAITALIA, "manga-italia.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaBr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaBr.kt new file mode 100644 index 00000000..f33ef885 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaBr.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.scan.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.scan.ScanParser + +@MangaSourceParser("MANGABR", "MangaBr", "pt") +internal class MangaBr(context: MangaLoaderContext) : + ScanParser(context, MangaSource.MANGABR, "mangabr.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaTerra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaTerra.kt new file mode 100644 index 00000000..d3318917 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/pt/MangaTerra.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.parsers.site.scan.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.scan.ScanParser + +@MangaSourceParser("MANGATERRA", "MangaTerra", "pt") +internal class MangaTerra(context: MangaLoaderContext) : + ScanParser(context, MangaSource.MANGATERRA, "manga-terra.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/MangaHub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/MangaHub.kt new file mode 100644 index 00000000..99b89be2 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/MangaHub.kt @@ -0,0 +1,38 @@ +package org.koitharu.kotatsu.parsers.site.zeistmanga.ar + +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.model.MangaState +import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser +import org.koitharu.kotatsu.parsers.util.domain +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet +import org.koitharu.kotatsu.parsers.util.parseHtml +import org.koitharu.kotatsu.parsers.util.requireElementById +import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow +import org.koitharu.kotatsu.parsers.util.toTitleCase +import java.util.EnumSet + +@MangaSourceParser("MANGAHUB_LINK", "MangaHub.link", "ar", ContentType.HENTAI) +internal class MangaHub(context: MangaLoaderContext) : + ZeistMangaParser(context, MangaSource.MANGAHUB_LINK, "www.mangahub.link") { + + override val availableStates: Set = + EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override val sateOngoing: String = "مستمر" + override val sateFinished: String = "مكتمل" + + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain").parseHtml() + return doc.requireElementById("Genre").select("div.items-center").mapNotNullToSet { + MangaTag( + key = it.selectFirstOrThrow("input").attr("value"), + title = it.selectFirstOrThrow("label").text().substringBefore(')').toTitleCase(sourceLocale), + source = source, + ) + } + } +}