Fix, change url and add broken

Fix HeamCms Chapter close #970
add Template FuzzyDoodleParser
Fix LelScanVf
Add hentaislayer , ScyllaComics
Close #609
Close #901
Close #440
Add template IkenParser
Fix MangaGalaxyParser
Add VortexScans
Fix HniScantrad
Add HastaTeam close #939
Add HotComicsParser close #962
Add HentaiCrot close #913
master
devi 2 years ago
parent 3b5a018f8c
commit a54f030c4e

@ -1,17 +0,0 @@
package org.koitharu.kotatsu.parsers.site.foolslide.fr
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
@Broken // Not dead, changed template
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
internal class HniScantrad(context: MangaLoaderContext) :
FoolSlideParser(context, MangaParserSource.HNISCANTRAD, "hni-scantrad.net") {
override val pagination = false
override val searchUrl = "lel/search/"
override val listUrl = "lel/directory/"
}

@ -0,0 +1,307 @@
package org.koitharu.kotatsu.parsers.site.fuzzydoodle
import androidx.collection.scatterSetOf
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
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 FuzzyDoodleParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
override val configKeyDomain = ConfigKey.Domain(domain)
override val isMultipleTagsSupported = true
@JvmField
protected val ongoing = scatterSetOf(
"en cours",
"ongoing",
"مستمر",
)
@JvmField
protected val finished = scatterSetOf(
"terminé",
"dropped",
"cancelled",
"متوقف",
)
@JvmField
protected val abandoned = scatterSetOf(
"canceled",
"cancelled",
"dropped",
"abandonné",
)
@JvmField
protected val paused = scatterSetOf(
"hiatus",
"on Hold",
"en pause",
"en attente",
)
protected open val ongoingValue = "ongoing"
protected open val finishedValue = "completed"
protected open val pausedValue = "haitus"
protected open val abandonedValue = "dropped"
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/manga?page=")
append(page)
when (filter) {
is MangaListFilter.Search -> {
append("&title=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
append("&type=")
append("&status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> ongoingValue
MangaState.FINISHED -> finishedValue
MangaState.PAUSED -> pausedValue
MangaState.ABANDONED -> abandonedValue
else -> ""
},
)
}
filter.tags.forEach {
append("&")
append("genre[]".urlEncoded())
append("=")
append(it.key)
}
}
null -> {}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
protected open val selectMangas = "div#card-real"
protected open fun parseMangaList(doc: Document): List<Manga> {
return doc.select(selectMangas).mapNotNull { div ->
val href = div.selectFirst("a")?.attr("href") ?: return@mapNotNull null
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = div.selectFirst("h2")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
protected open val selectAltTitle = "div.flex gap-1:contains(Alternative Titles:) span"
protected open val selectState = "a[href*=status] span"
protected open val selectAuthor =
"div#buttons + div.hidden p:contains(Auteur) span, div#buttons + div.hidden p:contains(Author) span, div#buttons + div.hidden p:contains(المؤلف) span"
protected open val selectDescription = "div:has(> p#description) p"
protected open val selectTagManga = "div.flex > a.inline-block"
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val mangaUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(mangaUrl).parseHtml()
val maxPageChapterSelect = doc.select("ul.pagination li[onclick]")
var maxPageChapter = 1
if (!maxPageChapterSelect.isNullOrEmpty()) {
maxPageChapterSelect.map {
val i = it.attr("onclick").substringAfterLast("=").substringBefore("'").toInt()
if (i > maxPageChapter) {
maxPageChapter = i
}
}
}
manga.copy(
altTitle = doc.selectLast(selectAltTitle)?.text(),
state = when (doc.selectFirst(selectState)?.text()?.lowercase().orEmpty()) {
in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED
in abandoned -> MangaState.ABANDONED
in paused -> MangaState.PAUSED
else -> null
},
author = doc.selectFirst(selectAuthor)?.text().orEmpty(),
description = doc.select(selectDescription).text(),
tags = doc.select(selectTagManga).mapNotNullToSet {
val key = it.attr("href").substringAfterLast('=')
MangaTag(
key = key,
title = it.text(),
source = source,
)
},
chapters = run {
if (maxPageChapter == 1) {
parseChapters(doc)
} else {
coroutineScope {
val result = ArrayList(parseChapters(doc))
result.ensureCapacity(result.size * maxPageChapter)
(2..maxPageChapter).map { i ->
async {
loadChapters(mangaUrl, i)
}
}.awaitAll()
.flattenTo(result)
result
}
}
}.reversed(),
)
}
private suspend fun loadChapters(baseUrl: String, page: Int): List<MangaChapter> {
return parseChapters(webClient.httpGet("$baseUrl?page=$page").parseHtml().body())
}
protected open val datePattern = "MMMM d, yyyy"
protected open val selectChapters = "div#chapters-list > a[href]"
private fun parseChapters(doc: Element): List<MangaChapter> {
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.select(selectChapters)
.mapChapters { _, a ->
val href = a.attrAsRelativeUrl("href")
val name = a.selectFirst("div.gap-2, #item-title")?.text().orEmpty()
val dateText = a.selectFirst("div.gap-3 span, div:has( #item-title) span.mt-1")?.text()
val chapterN = href.substringAfterLast('/').replace("-", ".").replace("[^0-9.]".toRegex(), "").toFloat()
MangaChapter(
id = generateUid(href),
name = name,
number = chapterN,
volume = 0,
url = href,
scanlator = null,
uploadDate = parseChapterDate(
dateFormat,
dateText,
),
branch = null,
source = source,
)
}
}
protected open val selectPages = "div#chapter-container > img"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(selectPages).map { img ->
val url = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
protected open val selectTagsList = "div.mt-1 div.items-center:has(label)"
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/manga").parseHtml()
return doc.select(selectTagsList).mapNotNullToSet {
val key = it.selectFirst("input")?.attr("value") ?: return@mapNotNullToSet null
MangaTag(
key = key,
title = it.selectFirst("label")?.text() ?: key,
source = source,
)
}
}
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
d.endsWith(" ago") ||
d.endsWith("مضت") || d.startsWith("منذ") ||
d.startsWith("il y a") -> parseRelativeDate(date)
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("detik", "segundo", "second", "ثوان")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("menit", "dakika", "min", "minute", "minutes", "minuto", "mins", "phút", "минут", "دقيقة")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "ساعات", "ساعة")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("hari", "gün", "jour", "día", "dia", "day", "days", "d", "день")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months", "أشهر", "mois")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("week", "weeks", "semana", "semanas")
.anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}
}

@ -0,0 +1,19 @@
package org.koitharu.kotatsu.parsers.site.fuzzydoodle.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.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.site.fuzzydoodle.FuzzyDoodleParser
import java.util.EnumSet
@MangaSourceParser("HENTAISLAYER", "HentaiSlayer", "ar", ContentType.HENTAI)
internal class HentaiSlayer(context: MangaLoaderContext) :
FuzzyDoodleParser(context, MangaParserSource.HENTAISLAYER, "hentaislayer.net") {
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
override val ongoingValue = "مستمر"
override val finishedValue = "مكتمل"
override val abandonedValue = "متوقف"
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.fuzzydoodle.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.fuzzydoodle.FuzzyDoodleParser
@MangaSourceParser("SCYLLACOMICS", "ScyllaComics", "en")
internal class ScyllaComics(context: MangaLoaderContext) :
FuzzyDoodleParser(context, MangaParserSource.SCYLLACOMICS, "scyllacomics.xyz")

@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.site.fuzzydoodle.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.site.fuzzydoodle.FuzzyDoodleParser
import java.util.EnumSet
@MangaSourceParser("LELSCANVF", "LelScanFr", "fr")
internal class LelScanVf(context: MangaLoaderContext) :
FuzzyDoodleParser(context, MangaParserSource.LELSCANVF, "lelscanfr.com") {
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
override val ongoingValue = "en-cours"
override val finishedValue = "termin"
}

@ -9,7 +9,7 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import org.koitharu.kotatsu.parsers.util.json.toJSONList
import org.koitharu.kotatsu.parsers.util.json.unescapeJson import org.koitharu.kotatsu.parsers.util.json.unescapeJson
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -143,16 +143,16 @@ internal abstract class HeanCms(
val seriesId = manga.id val seriesId = manga.id
val url = "https://$apiPath/chapter/query?page=1&perPage=9999&series_id=$seriesId" val url = "https://$apiPath/chapter/query?page=1&perPage=9999&series_id=$seriesId"
val response = webClient.httpGet(url).parseJson() val response = webClient.httpGet(url).parseJson()
val data = response.getJSONArray("data") val data = response.getJSONArray("data").toJSONList()
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH) val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
return manga.copy( return manga.copy(
chapters = data.mapJSONIndexed { index, it -> chapters = data.mapChapters(reversed = true) { i, it ->
val chapterUrl = val chapterUrl =
"/series/${it.getJSONObject("series").getString("series_slug")}/${it.getString("chapter_slug")}" "/series/${it.getJSONObject("series").getString("series_slug")}/${it.getString("chapter_slug")}"
MangaChapter( MangaChapter(
id = it.getLong("id"), id = it.getLong("id"),
name = it.getString("chapter_name"), name = it.getString("chapter_name"),
number = (data.length() - index).toFloat(), number = i + 1f,
volume = 0, volume = 0,
url = chapterUrl, url = chapterUrl,
scanlator = null, scanlator = null,

@ -0,0 +1,201 @@
package org.koitharu.kotatsu.parsers.site.hotcomics
import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Headers
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.network.UserAgents
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.*
internal abstract class HotComicsParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
override val configKeyDomain = ConfigKey.Domain(domain)
override val isMultipleTagsSupported = false
protected open val mangasUrl = "/genres"
protected open val onePage = false
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
if (onePage && page > 1) {
return emptyList()
}
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
append("/search?keyword=")
append(filter.query.urlEncoded())
append("&page=")
append(page)
}
is MangaListFilter.Advanced -> {
append(mangasUrl)
filter.tags.oneOrThrowIfMany()?.let {
append('/')
append(it.key)
}
if (!onePage) {
append("?page=")
append(page)
}
}
null -> {
append("/genres?page=")
append(page)
}
}
}
val tagMap = getOrCreateTagMap()
return parseMangaList(webClient.httpGet(url).parseHtml(), tagMap)
}
protected open val selectMangas = "li[itemtype*=ComicSeries]:not(.no-comic)"
protected open fun parseMangaList(doc: Document, tagMap: ArrayMap<String, MangaTag>): List<Manga> {
return doc.select(selectMangas).mapNotNull { li ->
val a = li.selectFirstOrThrow("a")
val href = a.attr("href")
val url = if (href.startsWith("/")) {
"/" + href.removePrefix("/").substringAfter('/') // remove /$lang/url
} else {
href
}
val tags = li.select(".etc span").mapNotNullToSet { tagMap[it.text()] }
Manga(
id = generateUid(url),
url = url,
publicUrl = url.toAbsoluteUrl(domain),
coverUrl = li.selectFirst("img")?.src().orEmpty(),
title = li.selectFirst(".title")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
description = li.selectFirst("p[itemprop*=description]")?.text().orEmpty(),
tags = tags,
author = li.selectFirst(".writer")?.text().orEmpty(),
state = if (doc.selectFirst(".ico_fin") != null) {
MangaState.FINISHED
} else {
MangaState.ONGOING
},
source = source,
isNsfw = a.selectFirst(".ico-18plus") != null,
)
}
}
protected open val selectMangaChapters = "#tab-chapter li"
protected open val datePattern = "MMM dd, yyyy"
override suspend fun getDetails(manga: Manga): Manga {
val mangaUrl = manga.url.toAbsoluteUrl(domain)
val redirectHeaders = Headers.Builder().set("Referer", mangaUrl).build()
val doc = webClient.httpGet(mangaUrl, redirectHeaders).parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return manga.copy(
description = doc.selectFirst("div.title_content_box h2")?.text() ?: manga.description,
chapters = doc.select(selectMangaChapters)
.mapChapters { i, li ->
val a = li.selectFirstOrThrow("a")
val href = a.attr("href")
val url = if (href.startsWith("/")) {
"/" + href.removePrefix("/").substringAfter('/') // remove /$lang/url
} else if (href.startsWith("javascript")) {
val h = a.attr("onclick").substringAfterLast("href='").substringBefore("'")
"/" + h.removePrefix("/").substringAfter('/') // remove /$lang/url
} else {
href
}
val chapterNum = li.selectFirst(".num")?.text()?.toFloat() ?: (i + 1f)
MangaChapter(
id = generateUid(url),
name = "Chapter : $chapterNum",
number = chapterNum,
volume = 0,
url = url,
scanlator = null,
uploadDate = dateFormat.tryParse(li.selectFirst("time")?.attr("datetime")),
branch = null,
source = source,
)
},
)
}
protected open val selectPages = "#viewer-img img"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(selectPages).map { img ->
val url = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
val map = getOrCreateTagMap()
val tagSet = ArraySet<MangaTag>(map.size)
for (entry in map) {
tagSet.add(entry.value)
}
return tagSet
}
protected open val mutex = Mutex()
protected open var tagCache: ArrayMap<String, MangaTag>? = null
protected open val selectTagsList = ".genres-list li:not(.on) a"
protected open suspend fun getOrCreateTagMap(): ArrayMap<String, MangaTag> = mutex.withLock {
tagCache?.let { return@withLock it }
val doc = webClient.httpGet("https://$domain$mangasUrl").parseHtml()
val tagItems = doc.select(selectTagsList)
val result = ArrayMap<String, MangaTag>(tagItems.size)
for (item in tagItems) {
val title = item.text()
val key = item.attr("href").substringAfterLast('/')
if (key.isNotEmpty() && title.isNotEmpty()) {
result[title] = MangaTag(title = title, key = key, source = source)
}
}
tagCache = result
result
}
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.de
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSDE", "TooMicsDe", "de")
internal class TooMicsDe(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSDE, "toomics.com/de") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,14 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.de
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
import java.util.Locale
@MangaSourceParser("TOOMICS", "Toomics", "de")
internal class Toomics(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICS, "toomics.top/de") {
override val sourceLocale: Locale = Locale.ENGLISH
override val isSearchSupported = false
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("DAYCOMICS", "DayComics", "en")
internal class DayComics(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.DAYCOMICS, "daycomics.me/en")

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("HOTCOMICS", "HotComics", "en")
internal class HotComics(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.HOTCOMICS, "hotcomics.me/en")

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSEN", "TooMicsEn", "en")
internal class TooMicsEn(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSEN, "toomics.com/en") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.es
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSES", "TooMicsEs", "es")
internal class TooMicsEs(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSES, "toomics.com/es") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.es
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSESLA", "TooMicsEsLa", "es")
internal class TooMicsEsLa(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSESLA, "toomics.com/mx") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSFR", "TooMicsFr", "fr")
internal class TooMicsFr(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSFR, "toomics.com/fr") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.it
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSIT", "TooMicsIt", "it")
internal class TooMicsIt(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSIT, "toomics.com/it") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.ja
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSJA", "TooMicsJa", "ja")
internal class TooMicsJa(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSJA, "toomics.com/ja") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSPT", "TooMicsPt", "pt")
internal class TooMicsPt(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSPT, "toomics.com/por") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.zh
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSSC", "TooMicsSc", "zh")
internal class TooMicsSc(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSSC, "toomics.com/sc") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,18 @@
package org.koitharu.kotatsu.parsers.site.hotcomics.zh
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.hotcomics.HotComicsParser
@MangaSourceParser("TOOMICSTC", "TooMicsTc", "zh")
internal class TooMicsTc(context: MangaLoaderContext) :
HotComicsParser(context, MangaParserSource.TOOMICSTC, "toomics.com/tc") {
override val isSearchSupported = false
override val mangasUrl = "/webtoon/ranking/genre"
override val selectMangas = "li > div.visual"
override val selectMangaChapters = "li.normal_ep:has(.coin-type1)"
override val selectTagsList = "div.genre_list li:not(.on) a"
override val selectPages = "div[id^=load_image_] img"
override val onePage = true
}

@ -0,0 +1,126 @@
package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("HENTAICROT", "HentaiCrot", "id", ContentType.HENTAI)
internal class HentaiCrot(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
)
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")
override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
append("/page/")
append(page)
append("/?s=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/category/")
append(it.key)
append('/')
}
append("/page/")
append(page)
append('/')
}
null -> {
append("/page/")
append(page)
append('/')
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div#content article").mapNotNull { div ->
val href = div.selectFirst("a")?.attr("href") ?: return@mapNotNull null
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src()?.replace("-200x285", "").orEmpty(),
title = div.selectFirst("h2")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain").parseHtml()
return doc.select("ul.megamenu li").mapNotNullToSet { li ->
val key = li.selectFirstOrThrow("a").attr("href").removeSuffix('/').substringAfterLast('/')
val name = li.selectFirstOrThrow("a").text()
MangaTag(
key = key,
title = name,
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return manga.copy(
description = doc.selectFirst("div.entry-content p")?.text().orEmpty(),
altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.text().orEmpty(),
author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.text().orEmpty(),
state = null,
chapters = listOf(
MangaChapter(
id = manga.id,
name = manga.title,
number = 1f,
volume = 0,
url = fullUrl,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
),
),
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(".thumbnail img, figure.gallery-item img").map { img ->
val url = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,126 @@
package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("PIXHENTAI", "PixHentai", "id", ContentType.HENTAI)
internal class PixHentai(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
)
override val configKeyDomain = ConfigKey.Domain("pixhentai.com")
override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
append("/page/")
append(page)
append("/?s=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/genre/")
append(it.key)
append('/')
}
append("/page/")
append(page)
append('/')
}
null -> {
append("/page/")
append(page)
append('/')
}
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div#content article").mapNotNull { div ->
val href = div.selectFirst("a")?.attr("href") ?: return@mapNotNull null
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src()?.replace("-200x285", "").orEmpty(),
title = div.selectFirst("h2")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain").parseHtml()
return doc.select("ul.megamenu li").mapNotNullToSet { li ->
val key = li.selectFirstOrThrow("a").attr("href").removeSuffix('/').substringAfterLast('/')
val name = li.selectFirstOrThrow("a").text()
MangaTag(
key = key,
title = name,
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return manga.copy(
description = doc.selectFirst("div.entry-content p")?.text().orEmpty(),
altTitle = doc.selectFirst("div.entry-content ul li:contains(Alternative Name(s) :) em")?.text().orEmpty(),
author = doc.selectFirst("div.entry-content ul li:contains(Artists :) em")?.text().orEmpty(),
state = null,
chapters = listOf(
MangaChapter(
id = manga.id,
name = manga.title,
number = 1f,
volume = 0,
url = fullUrl,
scanlator = null,
uploadDate = 0,
branch = null,
source = source,
),
),
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(".thumbnail img, figure.gallery-item img").map { img ->
val url = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
}

@ -0,0 +1,154 @@
package org.koitharu.kotatsu.parsers.site.iken
import org.json.JSONObject
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 org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat
import java.util.*
internal abstract class IkenParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
pageSize: Int = 18,
) : PagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.UPCOMING)
override val configKeyDomain = ConfigKey.Domain(domain)
override val isMultipleTagsSupported = true
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/api/query?page=")
append(page)
append("&perPage=18&searchTerm=")
when (filter) {
is MangaListFilter.Search -> {
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) {
append("&genreIds=")
appendAll(filter.tags, ",") { it.key }
}
append("&seriesType=&seriesStatus=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "ONGOING"
MangaState.FINISHED -> "COMPLETED"
MangaState.UPCOMING -> "COMING_SOON"
MangaState.ABANDONED -> "DROPPED"
else -> ""
},
)
}
}
null -> {}
}
}
return parseMangaList(webClient.httpGet(url).parseJson())
}
protected open fun parseMangaList(json: JSONObject): List<Manga> {
return json.getJSONArray("posts").mapJSON {
val url = "/series/${it.getString("slug")}"
Manga(
id = it.getLong("id"),
url = url,
publicUrl = url.toAbsoluteUrl(domain),
coverUrl = it.getString("featuredImage").orEmpty(),
title = it.getString("postTitle"),
altTitle = it.getString("alternativeTitles"),
description = it.getString("postContent"),
rating = RATING_UNKNOWN,
tags = emptySet(),
author = it.getString("author"),
state = when (it.getString("seriesStatus")) {
"ONGOING" -> MangaState.ONGOING
"COMPLETED" -> MangaState.FINISHED
"DROPPED", "CANCELLED" -> MangaState.ABANDONED
"COMING_SOON" -> MangaState.UPCOMING
else -> null
},
source = source,
isNsfw = it.getBooleanOrDefault("hot", false),
)
}
}
protected open val datePattern = "yyyy-MM-dd"
override suspend fun getDetails(manga: Manga): Manga {
val seriesId = manga.id
val url = "https://$domain/api/chapters?postId=$seriesId&skip=0&take=1000&order=desc&userid="
val json = webClient.httpGet(url).parseJson().getJSONObject("post")
val slug = json.getString("slug")
val data = json.getJSONArray("chapters").toJSONList()
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
return manga.copy(
chapters = data.mapChapters(reversed = true) { i, it ->
val chapterUrl =
"/series/$slug/${it.getString("slug")}"
MangaChapter(
id = it.getLong("id"),
name = "Chapter : ${it.getInt("number")}",
number = it.getInt("number").toFloat(),
volume = 0,
url = chapterUrl,
scanlator = null,
uploadDate = dateFormat.tryParse(it.getString("createdAt").substringBefore("T")),
branch = null,
source = source,
)
},
)
}
protected open val selectPages = "main section > img"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select(selectPages).map { img ->
val url = img.src() ?: img.parseFailed("Image src not found")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/series").parseHtml()
return doc.selectLastOrThrow("select").select("option[value]").mapNotNullToSet {
val key = it.attr("value") ?: return@mapNotNullToSet null
MangaTag(
key = key,
title = it.text() ?: key,
source = source,
)
}
}
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.iken.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.iken.IkenParser
@MangaSourceParser("MANGAGALAXY", "MangaGalaxy", "en")
internal class MangaGalaxyParser(context: MangaLoaderContext) :
IkenParser(context, MangaParserSource.MANGAGALAXY, "mangagalaxy.org")

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.iken.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.iken.IkenParser
@MangaSourceParser("VORTEXSCANS", "VortexScans", "en")
internal class VortexScans(context: MangaLoaderContext) :
IkenParser(context, MangaParserSource.VORTEXSCANS, "vortexscans.org")

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.en
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@Broken // Not dead, changed template
@MangaSourceParser("MANGAGALAXY", "MangaGalaxy", "en")
internal class MangaGalaxyParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaParserSource.MANGAGALAXY, "mangagalaxy.org", 20, 10)

@ -1,8 +1,9 @@
package org.koitharu.kotatsu.parsers.site.it.mangaworld package org.koitharu.kotatsu.parsers.site.mangaworld
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.mangaworld.it.MangaWorldParser
@MangaSourceParser("MANGAWORLD", "MangaWorld", "it") @MangaSourceParser("MANGAWORLD", "MangaWorld", "it")
internal class MangaWorld( internal class MangaWorld(

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site.it.mangaworld package org.koitharu.kotatsu.parsers.site.mangaworld.it
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.parsers.site.it.mangaworld package org.koitharu.kotatsu.parsers.site.mangaworld.it
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mmrcms.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.mmrcms.MmrcmsParser
import java.util.*
@MangaSourceParser("LELSCANVF", "LelScanVf", "fr")
internal class LelScanVf(context: MangaLoaderContext) :
MmrcmsParser(context, MangaParserSource.LELSCANVF, "lelscanfr.com") {
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.parsers.site.pizzareader.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.pizzareader.PizzaReaderParser
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
internal class HniScantrad(context: MangaLoaderContext) :
PizzaReaderParser(context, MangaParserSource.HNISCANTRAD, "hni-scantrad.net") {
override val ongoingFilter = "en cours"
override val completedFilter = "terminé"
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.pizzareader.it
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.pizzareader.PizzaReaderParser
@MangaSourceParser("HASTATEAM", "HastaTeam", "it")
internal class HastaTeam(context: MangaLoaderContext) :
PizzaReaderParser(context, MangaParserSource.HASTATEAM, "ddt.hastateam.com")
Loading…
Cancel
Save