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 00000000..28eb0293 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt @@ -0,0 +1,176 @@ +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()) + } + + if (!query.isNullOrEmpty()) { + append(query.urlEncoded()) + } + } + 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 00000000..8571c7e9 --- /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 00000000..608abf25 --- /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 00000000..64e035db --- /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 00000000..939d997e --- /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 00000000..dff5558b --- /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 00000000..8bfdc819 --- /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 00000000..0261fb85 --- /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 00000000..9aad40e8 --- /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 00000000..f62db59d --- /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 00000000..bfe3244c --- /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")