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 54fc4898d..7f5157205 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 @@ -184,7 +184,7 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( if (script1 != null) { val data = script1.data() - val regexParams = """\{uniqid:'(.+)',cascade:(.+)}""".toRegex() + val regexParams = """\{uniqid:'(.+)',cascade:(.+)\}""".toRegex() val regexAction = """form\.action\s?=\s?'(.+)'""".toRegex() val params = regexParams.find(data)!! val action = regexAction.find(data)!!.groupValues[1].toHttpUrl() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt new file mode 100644 index 000000000..547e01526 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt @@ -0,0 +1,172 @@ +package org.koitharu.kotatsu.parsers.site.foolslide + +import kotlinx.coroutines.coroutineScope +import org.json.JSONArray +import org.jsoup.nodes.Document +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.SimpleDateFormat +import java.util.* + +internal abstract class FoolSlideParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 25, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + + protected open val listUrl = "directory/" + protected open val searchUrl = "search/" + protected open val pagination = true // false if the manga list has no pages + protected open val datePattern = "yyyy.MM.dd" + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + + val doc = if (!query.isNullOrEmpty()) { + + val url = buildString { + append("https://$domain/$searchUrl") + + if (page > 1) { + return emptyList() + } + } + val q = query.urlEncoded() + webClient.httpPost(url, "search=$q").parseHtml() + } else { + val url = buildString { + append("https://$domain/$listUrl") + // For some sites that don't have enough manga and page 2 links to page 1 + if (!pagination) { + if (page > 1) { + return emptyList() + } + } else { + append(page.toString()) + } + } + webClient.httpGet(url).parseHtml() + } + + return doc.select("div.list div.group").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")?.src().orEmpty(),// in search no img + title = div.selectFirstOrThrow(".title").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 selectInfo = "div.info" + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val testAdultPage = webClient.httpGet(fullUrl).parseHtml() + + val doc = if (testAdultPage.selectFirst("div.info form") != null) { + webClient.httpPost(fullUrl, "adult=true").parseHtml() + } else { + testAdultPage + } + val chapters = getChapters(manga, doc) + + val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("")) { + doc.selectFirstOrThrow(selectInfo).text().substringAfterLast(": ") + } else { + doc.selectFirstOrThrow(selectInfo).text() + } + + val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("")) { + doc.selectFirstOrThrow(selectInfo).text().substringAfter(": ").substringBefore("Art") + } else { + null + } + + manga.copy( + tags = emptySet(), + coverUrl = doc.selectFirst(".thumbnail img")?.src().orEmpty(),// for manga result on search + description = desc, + altTitle = null, + author = author, + state = null, + chapters = chapters, + ) + } + + + protected open val selectDate = ".meta_r" + protected open val selectChapter = "div.list div.element" + + protected open suspend fun getChapters(manga: Manga, doc: Document): List { + val dateFormat = SimpleDateFormat(datePattern, sourceLocale) + return doc.body().select(selectChapter).mapChapters(reversed = true) { i, div -> + val a = div.selectFirstOrThrow(".title a") + val href = a.attrAsRelativeUrl("href") + val dateText = div.selectFirstOrThrow(selectDate).text().substringAfter(", ") + MangaChapter( + id = generateUid(href), + name = a.text(), + number = i + 1, + url = href, + uploadDate = if (div.selectFirstOrThrow(selectDate).text().contains(", ")) { + dateFormat.tryParse(dateText) + } else { + 0 + }, + source = source, + scanlator = null, + branch = null, + ) + } + } + + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(fullUrl).parseHtml() + val script = doc.selectFirstOrThrow("script:containsData(var pages = )") + val images = JSONArray(script.data().substringAfterLast("var pages = ").substringBefore(';')) + val pages = ArrayList(images.length()) + for (i in 0 until images.length()) { + val pageTake = images.getJSONObject(i) + pages.add( + MangaPage( + id = generateUid(pageTake.getString("url")), + url = pageTake.getString("url"), + preview = null, + source = source, + ), + ) + } + return pages + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Deathtollscans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Deathtollscans.kt new file mode 100644 index 000000000..8571c7e9e --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Deathtollscans.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("DEATHTOLLSCANS", "Deathtollscans", "en") +internal class Deathtollscans(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.DEATHTOLLSCANS, "reader.deathtollscans.net", 26) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Mangatellers.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Mangatellers.kt new file mode 100644 index 000000000..608abf256 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/Mangatellers.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("MANGATELLERS", "Mangatellers", "en") +internal class Mangatellers(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.MANGATELLERS, "reader.mangatellers.gr") { + override val pagination = false +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/ReaderEvilflowers.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/ReaderEvilflowers.kt new file mode 100644 index 000000000..64e035db9 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/ReaderEvilflowers.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("READER_EVILFLOWERS", "Evilflowers", "en") +internal class ReaderEvilflowers(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.READER_EVILFLOWERS, "reader.evilflowers.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/SilentskyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/SilentskyScans.kt new file mode 100644 index 000000000..939d997e7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/SilentskyScans.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("SILENTSKYSCANS", "Silent Sky Scans", "en") +internal class SilentskyScans(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.SILENTSKYSCANS, "reader.silentsky-scans.net") { + override val pagination = false +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/MenudoFansub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/MenudoFansub.kt new file mode 100644 index 000000000..dff5558b6 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/MenudoFansub.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("MENUDO_FANSUB", "Menudo Fansub", "es") +internal class MenudoFansub(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.MENUDO_FANSUB, "www.menudo-fansub.com", 25) { + override val searchUrl = "slide/search/" + override val listUrl = "slide/directory/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt new file mode 100644 index 000000000..8bfdc8195 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("HNISCANTRAD", "Hni Scantrad", "fr") +internal class HniScantrad(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.HNISCANTRAD, "hni-scantrad.com") { + + override val pagination = false + override val searchUrl = "lel/search/" + override val listUrl = "lel/directory/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt new file mode 100644 index 000000000..0261fb856 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("POWERMANGA", "Power Manga", "it") +internal class PowerManga(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") { + override val pagination = false +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt new file mode 100644 index 000000000..9aad40e8d --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("RAMAREADER", "Rama Reader", "it") +internal class Ramareader(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.RAMAREADER, "www.ramareader.it") { + override val searchUrl = "read/search/" + override val listUrl = "read/directory/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt new file mode 100644 index 000000000..f62db59d5 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.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.foolslide.FoolSlideParser + + +@MangaSourceParser("READNIFTEAM", "Read Nif Team", "it") +internal class ReadNifteam(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.READNIFTEAM, "read-nifteam.info") { + override val searchUrl = "slide/search/" + override val listUrl = "slide/directory/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/pl/Onepiecenakama.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/pl/Onepiecenakama.kt new file mode 100644 index 000000000..bfe3244c6 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/pl/Onepiecenakama.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.parsers.site.foolslide.pl + + +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.foolslide.FoolSlideParser + + +@MangaSourceParser("ONEPIECENAKAMA", "Onepiecenakama", "pl") +internal class Onepiecenakama(context: MangaLoaderContext) : + FoolSlideParser(context, MangaSource.ONEPIECENAKAMA, "reader.onepiecenakama.pl") 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 3867e32ca..e02271021 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 @@ -28,6 +28,7 @@ internal abstract class MadaraParser( SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, + SortOrder.RATING, ) protected open val tagPrefix = "manga-genre/" @@ -176,14 +177,18 @@ internal abstract class MadaraParser( SortOrder.UPDATED -> append("latest") SortOrder.NEWEST -> append("new-manga") SortOrder.ALPHABETICAL -> append("alphabet") - else -> append("latest") + SortOrder.RATING -> append("rating") } } webClient.httpGet(url).parseHtml() } else { val tag = tags.oneOrThrowIfMany() - val payload = createRequestTemplate() - payload["page"] = page.toString() + + val payload = if (sortOrder == SortOrder.RATING) { + createRequestTemplate(ratingRequest) + } else { + createRequestTemplate(defaultRequest) + } when (sortOrder) { SortOrder.POPULARITY -> payload["vars[meta_key]"] = "_wp_manga_views" SortOrder.UPDATED -> payload["vars[meta_key]"] = "_latest_update" @@ -193,8 +198,9 @@ internal abstract class MadaraParser( payload["vars[order]"] = "ASC" } - else -> payload["vars[meta_key]"] = "_latest_update" + SortOrder.RATING -> {} } + payload["page"] = page.toString() payload["vars[wp-manga-genre]"] = tag?.key.orEmpty() payload["vars[s]"] = query?.urlEncoded().orEmpty() webClient.httpPost( @@ -547,14 +553,11 @@ internal abstract class MadaraParser( } } + private val ratingRequest = "action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5Borderby%5D%5Bquery_avarage_reviews%5D=DESC&vars%5Borderby%5D%5Bquery_total_reviews%5D=DESC&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5B0%5D%5Bquery_avarage_reviews%5D%5Bkey%5D=_manga_avarage_reviews&vars%5Bmeta_query%5D%5B0%5D%5Bquery_total_reviews%5D%5Bkey%5D=_manga_total_votes&vars%5Bmeta_query%5D%5Brelation%5D=AND&vars%5Bpost_type%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmanga_archives_item_layout%5D=default" + private val defaultRequest = "action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5Borderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border%5D=desc&vars%5Bmanga_archives_item_layout%5D=default" private companion object { - - private fun createRequestTemplate() = - ("action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5B" + - "orderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query" + - "%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type" + - "%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border" + - "%5D=desc&vars%5Bmanga_archives_item_layout%5D=default").split( + private fun createRequestTemplate(query : String) = + (query).split( '&', ).map { val pos = it.indexOf('=') diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TempleScanEsp.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TempleScanEsp.kt new file mode 100644 index 000000000..7c55526e5 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TempleScanEsp.kt @@ -0,0 +1,16 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es" , ContentType.HENTAI) +internal class TempleScanEsp(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.TEMPLESCANESP, "templescanesp.com") { + + override val listUrl = "series/" + override val tagPrefix = "genero/" + override val datePattern = "dd.MM.yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TenkaiScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TenkaiScan.kt new file mode 100644 index 000000000..d8a1d2052 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TenkaiScan.kt @@ -0,0 +1,11 @@ +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.ContentType +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.madara.MadaraParser + +@MangaSourceParser("TENKAISCAN", "Tenkai Scan", "es" , ContentType.HENTAI) +internal class TenkaiScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.TENKAISCAN, "tenkaiscan.net") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/NinjaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/NinjaScan.kt new file mode 100644 index 000000000..6729886d0 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/pt/NinjaScan.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("NINJASCAN", "Ninja Scan", "pt") +internal class NinjaScan(context: MangaLoaderContext) : + MadaraParser(context, MangaSource.NINJASCAN, "ninjascan.site") { + + override val datePattern = "dd 'de' MMMMM 'de' yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Ozulscans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Ozulscans.kt index dc70d3078..2e8ebbe4b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Ozulscans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/Ozulscans.kt @@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser @MangaSourceParser("OZULSCANS", "Ozulscans", "ar") internal class Ozulscans(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.OZULSCANS, "ozulscans.com", pageSize = 30, searchPageSize = 30) + MangaReaderParser(context, MangaSource.OZULSCANS, "ozulmanga.com", pageSize = 30, searchPageSize = 30) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/BirdManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/BirdManga.kt new file mode 100644 index 000000000..616b0a530 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/BirdManga.kt @@ -0,0 +1,13 @@ +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("BIRDMANGA", "Bird Manga", "en") +internal class BirdManga(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.BIRDMANGA, "birdmanga.com", pageSize = 20, searchPageSize = 10) { + override val encodedSrc = true +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ja/MangaMate.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ja/MangaMate.kt index 8e4ed7c38..14f5b19f1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ja/MangaMate.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ja/MangaMate.kt @@ -4,7 +4,7 @@ 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("MANGAMATE", "Manga Mate", "ja") internal class MangaMate(context: MangaLoaderContext) : diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt new file mode 100644 index 000000000..9becaf005 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/pl/SkanlacjeFeniksy.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.parsers.site.mangareader.pl + +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("SKANLACJEFENIKSY", "SkanlacjeFeniksy", "pl") +internal class SkanlacjeFeniksy(context: MangaLoaderContext) : + MangaReaderParser(context, MangaSource.SKANLACJEFENIKSY, "skanlacje-feniksy.pl", pageSize = 10, searchPageSize = 10) { + + override val datePattern = "d MMMM, yyyy" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt index 9f2d6965a..78f950e9c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import okhttp3.Headers import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext @@ -9,6 +10,7 @@ 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.network.UserAgents import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat import java.util.* @@ -21,6 +23,11 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaivn.autos", "hentaivn.tv") + // hentaivn has created 2 different interfaces for mobile and desktop, and Cloudflare detects whether it's mobile or not even with a desktop user agent. + override val headers: Headers = Headers.Builder() + .add("User-Agent", UserAgents.CHROME_MOBILE) + .build() + override val sortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.POPULARITY, @@ -33,23 +40,25 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val chapterDeferred = async { fetchChapters(manga.url) } val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val infoEl = docs.selectFirstOrThrow("div.container") - .selectFirstOrThrow("div.page-info") + + val id = docs.location().substringAfterLast("/").substringBefore("-") + + val genreUrl = Regex(""""(list-info-theloai-mobile\.php?.+)"""").find(docs.toString())?.groupValues?.get(1) + val genre = async { webClient.httpGet("https://$domain/$genreUrl").parseHtml().select("a.tag") }.await() + + val infoEl = async { webClient.httpGet("/list-info-all-mobile.php?id_anime=$id".toAbsoluteUrl(domain)).parseHtml() }.await() + val stateDoc = async { webClient.httpGet("/list-info-time-mobile.php?id_anime=$id".toAbsoluteUrl(domain)).parseHtml() }.await() manga.copy( altTitle = infoEl.infoText("Tên Khác:"), - author = infoEl.infoText("Tác giả"), - description = infoEl.selectFirst("p:contains(Nội dung:)") - ?.nextElementSibling() - ?.outerHtml(), + author = infoEl.select("p:contains(Tác giả:) a").text(), + description = infoEl.select("p:contains(Nội dung:) + p").html(), tags = tagCache.tryGet().getOrNull()?.let { tagMap -> - infoEl.selectFirst("p:contains(Thể Loại:)") - ?.select("span > a") - ?.mapNotNullToSet { - tagMap[it.text()] - } + genre.mapNotNullToSet { + tagMap[it.text()] + } }.orEmpty(), - state = infoEl.infoText("Tình Trạng:")?.let { + state = stateDoc.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()?.let { when (it) { "Đã hoàn thành" -> MangaState.FINISHED "Đang tiến hành" -> MangaState.ONGOING @@ -111,13 +120,9 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo } override suspend fun getPages(chapter: MangaChapter): List { - val ids = chapter.url.removePrefix("/").split('-').take(2) - val mangaId = ids[0].toInt() - val chapterId = ids[1].toInt() - val contentUrl = "/list-loadchapter.php?id_episode=$chapterId&idchapshowz=$mangaId".toAbsoluteUrl(domain) - val docs = webClient.httpGet(contentUrl).parseHtml() - return docs.select("img").map { - val pageUrl = it.attrAsAbsoluteUrl("src") + val docs = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() + return docs.select("#image > img").map { + val pageUrl = it.src() ?: throw Exception(it.html()) MangaPage( id = generateUid(pageUrl), url = pageUrl, @@ -157,7 +162,7 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo } private fun parseMainList(docs: Document, page: Int): List { - val realPage = docs.selectFirst("ul.pagination > li > b")?.text()?.toIntOrNull() ?: 1 + val realPage = docs.selectFirst("div.pagination b.pagination-selected")?.text()?.toIntOrNull() ?: 1 if (page > realPage) { return emptyList() } @@ -166,17 +171,17 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo .selectFirstOrThrow("div.block-item") .select("ul > li.item") .map { el -> - val relativeUrl = el.selectFirstOrThrow("div.box-cover > a").attrAsRelativeUrl("href") - val descriptionsEl = el.selectFirstOrThrow("div.box-description") + val relativeUrl = el.selectFirstOrThrow("div.box-cover-2 > a").attrAsRelativeUrl("href") + val descriptionsEl = el.selectFirstOrThrow("div.box-description-2") Manga( id = generateUid(relativeUrl), - title = descriptionsEl.selectFirst("p > a")?.text().orEmpty(), + title = descriptionsEl.selectFirst("a")?.text().orEmpty(), altTitle = null, url = relativeUrl, publicUrl = relativeUrl.toAbsoluteUrl(domain), rating = RATING_UNKNOWN, isNsfw = true, - coverUrl = el.selectFirst("div.box-cover img").imageUrl(), + coverUrl = el.selectFirst("div.box-cover-2 img")?.src().orEmpty(), tags = emptySet(), state = null, author = null, @@ -217,11 +222,8 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo } private suspend fun fetchChapters(mangaUrl: String): List { - val slug = mangaUrl.substringAfterLast("/") - .removeSuffix(".html") - val name = slug.substringAfter("-") - val id = slug.substringBefore("-").toInt() - val chaptersAjax = "/list-showchapter.php?idchapshow=$id&idlinkanime=$name".toAbsoluteUrl(domain) + val id = mangaUrl.substringAfterLast("/").substringBefore('-') + val chaptersAjax = "/list-showchapter.php?idchapshow=$id".toAbsoluteUrl(domain) val chaptersEl = webClient.httpGet(chaptersAjax).parseHtml() val chapterDateFormat = SimpleDateFormat("dd/MM/yyyy") return chaptersEl.select("tbody > tr") @@ -241,17 +243,6 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo } } - private fun Element?.imageUrl(): String { - if (this == null) { - return "" - } - - return attrAsRelativeUrlOrNull("data-src") - ?: attrAsRelativeUrlOrNull("data-srcset") - ?: attrAsRelativeUrlOrNull("src") - ?: "" - } - private fun Element.infoText(title: String) = selectFirst("span.info:contains($title)") ?.parent() ?.select("span:not(.info) > a") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt new file mode 100644 index 000000000..dd6015ee9 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt @@ -0,0 +1,138 @@ +package org.koitharu.kotatsu.parsers.site.vmp + +import kotlinx.coroutines.coroutineScope +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.util.* + +internal abstract class VmpParser( + context: MangaLoaderContext, + source: MangaSource, + domain: String, + pageSize: Int = 24, +) : PagedMangaParser(context, source, pageSize) { + + override val configKeyDomain = ConfigKey.Domain(domain) + + override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED) + + protected open val listUrl = "xxx/" + protected open val geneUrl = "genero/" + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage( + page: Int, + query: String?, + tags: Set?, + sortOrder: SortOrder, + ): List { + + val url = buildString { + append("https://$domain/") + if(!tags.isNullOrEmpty()) + { + append(geneUrl) + for (tag in tags) { + append(tag.key) + } + append("/page/") + append(page.toString()) + + }else + { + append(listUrl) + append("/page/") + append(page.toString()) + if (!query.isNullOrEmpty()) { + append("?s=") + append(query.urlEncoded()) + } + } + } + + val doc = webClient.httpGet(url).parseHtml() + + return doc.select("div.blog-list-items div.entry").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")?.src().orEmpty(), + title = div.selectFirstOrThrow("h2").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } + + } + + override suspend fun getTags(): Set { + val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() + return doc.select("div.tagcloud a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast(geneUrl, ""), + title = a.text().toTitleCase(), + source = source, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val fullUrl = manga.url.toAbsoluteUrl(domain) + val doc= webClient.httpGet(fullUrl).parseHtml() + + manga.copy( + tags = doc.select("div.tax_box div.links ul:not(.post-categories) li a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix("/").substringAfterLast(geneUrl, ""), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = null, + altTitle = null, + author = null, + state = null, + chapters = listOf( + MangaChapter( + id = manga.id, + name = manga.title, + number = 1, + url = manga.url, + scanlator = null, + uploadDate = 0, + 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("div.wp-content img").map { div -> + val img = div.selectFirstOrThrow("img") + val url = img.src()?.toRelativeUrl(domain) ?: div.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/vmp/es/VerComicsPorno.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/es/VerComicsPorno.kt new file mode 100644 index 000000000..5d44a9f05 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/es/VerComicsPorno.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.parsers.site.vmp.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.vmp.VmpParser + +// Other domain name : toonx.net +@MangaSourceParser("VERCOMICSPORNO", "VerComicsPorno", "es", ContentType.HENTAI) +internal class VerComicsPorno(context: MangaLoaderContext) : + VmpParser(context, MangaSource.VERCOMICSPORNO, "vercomicsporno.com") { + override val listUrl = "comics-porno/" + override val geneUrl = "etiquetas/" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/es/VerMangasPorno.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/es/VerMangasPorno.kt new file mode 100644 index 000000000..09dec9095 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/es/VerMangasPorno.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.parsers.site.vmp.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.vmp.VmpParser + +@MangaSourceParser("VERMANGASPORNO", "VerMangasPorno", "es", ContentType.HENTAI) +internal class VerMangasPorno(context: MangaLoaderContext) : + VmpParser(context, MangaSource.VERMANGASPORNO, "vermangasporno.com") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt index 657b56a94..2f8e9c5a1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt @@ -155,7 +155,7 @@ fun Element.attrOrNull(vararg names: String): String? { } @JvmOverloads -fun Element.src(names: Array = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "src")): String? { +fun Element.src(names: Array = arrayOf("data-src", "data-cfsrc", "data-original", "data-cdn", "data-sizes", "data-lazy-src", "data-srcset", "src")): String? { for (name in names) { val value = attrAsAbsoluteUrlOrNull(name) if (value != null) {