add filter on FmreaderParser, LugnicaScans

Fix GetList on FoolSlideParser , FuryoSociety , LegacyScans, LireScan, ScansMangas.me, ScantradUnion
pull/401/head
devi 2 years ago
parent 14b2457627
commit db770351f3

@ -27,7 +27,11 @@ internal abstract class FmreaderParser(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
) )
override val isMultipleTagsSupported = false override val availableStates: Set<MangaState> = EnumSet.of(
MangaState.ONGOING,
MangaState.FINISHED,
MangaState.ABANDONED,
)
protected open val listUrl = "/manga-list.html" protected open val listUrl = "/manga-list.html"
protected open val datePattern = "MMMM d, yyyy" protected open val datePattern = "MMMM d, yyyy"
@ -40,58 +44,73 @@ internal abstract class FmreaderParser(
@JvmField @JvmField
protected val ongoing: Set<String> = setOf( protected val ongoing: Set<String> = setOf(
"On going", "on going",
"Incomplete", "incomplete",
"En curso", "en curso",
) )
@JvmField @JvmField
protected val finished: Set<String> = setOf( protected val finished: Set<String> = setOf(
"Completed", "completed",
"Completado", "completado",
) )
@JvmField @JvmField
protected val abandoned: Set<String> = hashSetOf( protected val abandoned: Set<String> = hashSetOf(
"Canceled", "canceled",
"Cancelled", "cancelled",
"Drop", "drop",
) )
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append(listUrl) append(listUrl)
append("?page=") append("?page=")
append(page.toString()) append(page.toString())
when { when (filter) {
!query.isNullOrEmpty() -> { is MangaListFilter.Search -> {
append("&name=") append("&name=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
} }
!tags.isNullOrEmpty() -> { is MangaListFilter.Advanced -> {
append("&genre=") append("&genre=")
append(tag?.key.orEmpty()) append(filter.tags.joinToString(",") { it.key })
append("&sort=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
}
append("&m_status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "2"
MangaState.FINISHED -> "1"
MangaState.ABANDONED -> "3"
else -> ""
},
)
}
} }
}
append("&sort=") null -> append("&sort=last_update")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
} }
} }
val doc = webClient.httpGet(url).parseHtml() return parseMangaList(webClient.httpGet(url).parseHtml())
}
protected open fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.thumb-item-flow").map { div -> return doc.select("div.thumb-item-flow").map { div ->
val href = div.selectFirstOrThrow("div.series-title a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("div.series-title a").attrAsRelativeUrl("href")
Manga( Manga(
@ -99,8 +118,8 @@ internal abstract class FmreaderParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain), publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirstOrThrow("div.img-in-ratio").attr("data-bg") coverUrl = div.selectFirstOrThrow("div.img-in-ratio").attr("data-bg")
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("('") ?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("(")
.substringBeforeLast("')"), .substringBefore(")"),
title = div.selectFirstOrThrow("div.series-title").text().orEmpty(), title = div.selectFirstOrThrow("div.series-title").text().orEmpty(),
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
@ -140,7 +159,7 @@ internal abstract class FmreaderParser(
val desc = doc.selectFirst(selectDesc)?.html() val desc = doc.selectFirst(selectDesc)?.html()
val stateDiv = doc.selectFirst(selectState) val stateDiv = doc.selectFirst(selectState)
val state = stateDiv?.let { val state = stateDiv?.let {
when (it.text()) { when (it.text().lowercase()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED in finished -> MangaState.FINISHED
in abandoned -> MangaState.ABANDONED in abandoned -> MangaState.ABANDONED

@ -20,79 +20,67 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
override val selectTag = "div.info-item:contains(Genre) span.info-value a" override val selectTag = "div.info-item:contains(Genre) span.info-value a"
override val datePattern = "dd/MM/yyyy" override val datePattern = "dd/MM/yyyy"
override val selectPage = "div#chapter-content img" override val selectPage = "div#chapter-content img"
override val selectBodyTag = "div.genres-menu a" override val selectBodyTag = "div.advanced-wrapper .genre_label"
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
if (!tags.isNullOrEmpty()) { append("/tim-kiem?page=")
append("/genre/") append(page.toString())
append(tag?.key.orEmpty())
append("?page=") when (filter) {
append(page.toString()) is MangaListFilter.Search -> {
append("&sort=") append("&q=")
when (sortOrder) { append(filter.query.urlEncoded())
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
} }
} else {
append(listUrl) is MangaListFilter.Advanced -> {
append("?page=")
append(page.toString()) append("&accept_genres=")
when { if (filter.tags.isNotEmpty()) {
!query.isNullOrEmpty() -> { append(
append("&q=") filter.tags.joinToString(",") { it.key },
append(query.urlEncoded()) )
}
append("&sort=")
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.POPULARITY -> "top"
SortOrder.UPDATED -> "update"
SortOrder.NEWEST -> "new"
SortOrder.RATING -> "like"
},
)
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "3"
MangaState.PAUSED -> "2"
else -> ""
},
)
} }
}
append("&sort=")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
} }
null -> append("&sort=update")
} }
} }
val doc = webClient.httpGet(url).parseHtml() return parseMangaList(webClient.httpGet(url).parseHtml())
return doc.select("div.thumb-item-flow").map { div ->
val href = div.selectFirstOrThrow("div.series-title a").attrAsRelativeUrl("href")
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirstOrThrow("div.img-in-ratio").attr("data-bg")
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("('")
.substringBeforeLast("')"),
title = div.selectFirstOrThrow("div.series-title").text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = isNsfwSource,
)
}
} }
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select(selectBodyTag).mapNotNullToSet { a -> return doc.select(selectBodyTag).mapNotNullToSet { label ->
val href = a.attr("href").substringAfterLast("/") val key = label.attr("data-genre-id")
MangaTag( MangaTag(
key = href, key = key,
title = a.text(), title = label.selectFirstOrThrow(".gerne-name").text(),
source = source, source = source,
) )
} }
@ -105,7 +93,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
val desc = doc.selectFirstOrThrow(selectDesc).html() val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.selectFirst(selectState) val stateDiv = doc.selectFirst(selectState)
val state = stateDiv?.let { val state = stateDiv?.let {
when (it.text()) { when (it.text().lowercase()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED in finished -> MangaState.FINISHED
else -> null else -> null

@ -14,37 +14,60 @@ internal class OlimpoScans(context: MangaLoaderContext) :
override val selectAlt = "ul.manga-info li:contains(Otros nombres)" override val selectAlt = "ul.manga-info li:contains(Otros nombres)"
override val selectTag = "ul.manga-info li:contains(Género) a" override val selectTag = "ul.manga-info li:contains(Género) a"
override val tagPrefix = "lista-de-comics-genero-" override val tagPrefix = "lista-de-comics-genero-"
override val isMultipleTagsSupported = false
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append(listUrl) when (filter) {
append("?page=") is MangaListFilter.Search -> {
append(page.toString()) append(listUrl)
when { append("?page=")
!query.isNullOrEmpty() -> { append(page.toString())
append("&name=") append("&name=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
} }
!tags.isNullOrEmpty() -> { is MangaListFilter.Advanced -> {
append("&genre=") if (filter.tags.isNotEmpty()) {
append(tag?.key.orEmpty()) filter.tags.oneOrThrowIfMany()?.let {
append("/lista-de-comics-genero-")
append(it.key)
append(".html")
}
} else {
append(listUrl)
append("?page=")
append(page.toString())
append("&sort=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
}
}
append("&m_status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "2"
MangaState.FINISHED -> "1"
MangaState.ABANDONED -> "3"
else -> ""
},
)
}
}
null -> {
append(listUrl)
append("?page=")
append(page.toString())
append("&sort=last_update")
} }
}
append("&sort=")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
} }
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()

@ -3,13 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fmreader.ja
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
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.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -27,40 +21,7 @@ internal class Klz9(context: MangaLoaderContext) :
override val selectPage = "img" override val selectPage = "img"
override val selectBodyTag = "div.panel-body a" override val selectBodyTag = "div.panel-body a"
override suspend fun getListPage( override fun parseMangaList(doc: Document): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
append("/$listUrl")
append("?page=")
append(page.toString())
when {
!query.isNullOrEmpty() -> {
append("&name=")
append(query.urlEncoded())
}
!tags.isNullOrEmpty() -> {
append("&genre=")
append(tag?.key.orEmpty())
}
}
append("&sort=")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
else -> append("last_update")
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.thumb-item-flow").map { div -> return doc.select("div.thumb-item-flow").map { div ->
val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(
@ -112,7 +73,6 @@ internal class Klz9(context: MangaLoaderContext) :
val docLoad = webClient.httpGet("https://$domain/app/manga/controllers/cont.listImg.php?cid=$cid").parseHtml() val docLoad = webClient.httpGet("https://$domain/app/manga/controllers/cont.listImg.php?cid=$cid").parseHtml()
return docLoad.select(selectPage).map { img -> return docLoad.select(selectPage).map { img ->
val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found") val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found")
MangaPage( MangaPage(
id = generateUid(url), id = generateUid(url),
url = url, url = url,

@ -32,35 +32,63 @@ internal abstract class FoolSlideParser(
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int, val doc =
query: String?, when (filter) {
tags: Set<MangaTag>?, is MangaListFilter.Search -> {
sortOrder: SortOrder,
): List<Manga> {
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) { if (page > 1) {
return emptyList() return emptyList()
} }
} else {
append(page.toString()) val url = buildString {
append("https://")
append(domain)
append("/")
append(searchUrl)
}
webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml()
}
is MangaListFilter.Advanced -> {
val url = buildString {
append("https://")
append(domain)
append("/")
append(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()
}
null -> {
val url = buildString {
append("https://")
append(domain)
append("/")
append(listUrl)
if (!pagination) {
if (page > 1) {
return emptyList()
}
} else {
append(page.toString())
}
}
webClient.httpGet(url).parseHtml()
} }
} }
webClient.httpGet(url).parseHtml()
}
return doc.select("div.list div.group").map { div -> return doc.select("div.list div.group").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(

@ -16,40 +16,30 @@ internal class AssortedScans(context: MangaLoaderContext) :
override val pagination = false override val pagination = false
override val selectInfo = "div.#series-info" override val selectInfo = "div.#series-info"
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int, if (page > 1) {
query: String?, return emptyList()
tags: Set<MangaTag>?, }
sortOrder: SortOrder,
): List<Manga> {
val doc = if (!query.isNullOrEmpty()) { val url = buildString {
if (page > 1) { append("https://")
return emptyList() append(domain)
} append('/')
val url = buildString { when (filter) {
append("https://") is MangaListFilter.Search -> {
append(domain) append(searchUrl)
append('/') append("?q=")
append(searchUrl) append(filter.query.urlEncoded())
append("?q=")
append(query.urlEncoded())
}
webClient.httpGet(url).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())
} }
is MangaListFilter.Advanced -> {
append(listUrl)
}
null -> append(listUrl)
} }
webClient.httpGet(url).parseHtml()
} }
val doc = webClient.httpGet(url).parseHtml()
return doc.select("section.series, tr.result").map { div -> return doc.select("section.series, tr.result").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(

@ -5,7 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
@MangaSourceParser("HNISCANTRAD", "Hni Scantrad", "fr") @MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
internal class HniScantrad(context: MangaLoaderContext) : internal class HniScantrad(context: MangaLoaderContext) :
FoolSlideParser(context, MangaSource.HNISCANTRAD, "hni-scantrad.com") { FoolSlideParser(context, MangaSource.HNISCANTRAD, "hni-scantrad.com") {
override val pagination = false override val pagination = false

@ -1,12 +1,10 @@
package org.koitharu.kotatsu.parsers.site.foolslide.it package org.koitharu.kotatsu.parsers.site.foolslide.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
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
@MangaSourceParser("POWERMANGA", "PowerManga", "it") @MangaSourceParser("POWERMANGA", "PowerManga", "it")
internal class PowerManga(context: MangaLoaderContext) : internal class PowerManga(context: MangaLoaderContext) :
FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") { FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") {

@ -1,12 +1,10 @@
package org.koitharu.kotatsu.parsers.site.foolslide.it package org.koitharu.kotatsu.parsers.site.foolslide.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
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
@MangaSourceParser("RAMAREADER", "RamaReader", "it") @MangaSourceParser("RAMAREADER", "RamaReader", "it")
internal class Ramareader(context: MangaLoaderContext) : internal class Ramareader(context: MangaLoaderContext) :
FoolSlideParser(context, MangaSource.RAMAREADER, "www.ramareader.it") { FoolSlideParser(context, MangaSource.RAMAREADER, "www.ramareader.it") {

@ -1,12 +1,10 @@
package org.koitharu.kotatsu.parsers.site.foolslide.it package org.koitharu.kotatsu.parsers.site.foolslide.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
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
@MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it") @MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it")
internal class ReadNifteam(context: MangaLoaderContext) : internal class ReadNifteam(context: MangaLoaderContext) :
FoolSlideParser(context, MangaSource.READNIFTEAM, "read-nifteam.info") { FoolSlideParser(context, MangaSource.READNIFTEAM, "read-nifteam.info") {

@ -10,6 +10,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -35,21 +36,27 @@ internal class FuryoSociety(context: MangaLoaderContext) :
) )
} }
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int, if (page > 1) {
query: String?, return emptyList()
tags: Set<MangaTag>?, }
sortOrder: SortOrder,
): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
if (page == 1) { when (filter) {
if (sortOrder == SortOrder.ALPHABETICAL) { is MangaListFilter.Search -> {
append("/mangas") throw IllegalArgumentException("Search is not supported by this source")
}
is MangaListFilter.Advanced -> {
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
append("/mangas")
}
} }
} else {
return emptyList() null -> {}
} }
} }

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site.fr package org.koitharu.kotatsu.parsers.site.fr
import okhttp3.Headers import okhttp3.Headers
import org.json.JSONObject
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.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -16,9 +17,7 @@ import java.util.*
internal class LegacyScansParser(context: MangaLoaderContext) : internal class LegacyScansParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.LEGACY_SCANS, 18) { PagedMangaParser(context, MangaSource.LEGACY_SCANS, 18) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
SortOrder.POPULARITY,
)
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com") override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")
@ -26,86 +25,96 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
.add("User-Agent", UserAgents.CHROME_MOBILE) .add("User-Agent", UserAgents.CHROME_MOBILE)
.build() .build()
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val end = page * pageSize val end = page * pageSize
val start = end - (pageSize - 1) val start = end - (pageSize - 1)
val url = if (!query.isNullOrEmpty()) {
if (page > 1) { when (filter) {
return emptyList() is MangaListFilter.Search -> {
} if (page > 1) {
buildString { return emptyList()
append("https://api.$domain/misc/home/search?title=")
append(query.urlEncoded())
}
} else {
buildString {
append("https://api.$domain/misc/comic/search/query?status=&order=&genreNames=")
if (!tags.isNullOrEmpty()) {
for (tag in tags) {
append(tag.key)
append(',')
}
} }
append("&type=&start=") val url = buildString {
append(start) append("https://api.$domain/misc/home/search?title=")
append("&end=") append(filter.query.urlEncoded())
append(end) }
return parseMangaListQuery(webClient.httpGet(url).parseJson())
} }
} is MangaListFilter.Advanced -> {
val json = webClient.httpGet(url).parseJson() val url = buildString {
return if (!query.isNullOrEmpty()) { append("https://api.")
json.getJSONArray("results").mapJSON { j -> append(domain)
val slug = j.getString("slug") append("/misc/comic/search/query?status=&order=&genreNames=")
val urlManga = "https://$domain/comics/$slug" append(filter.tags.joinToString(",") { it.key })
Manga( append("&type=&start=")
id = generateUid(urlManga), append(start)
title = j.getString("title"), append("&end=")
altTitle = null, append(end)
url = urlManga, }
publicUrl = urlManga, return parseMangaList(webClient.httpGet(url).parseJson())
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = "",
tags = setOf(),
state = null,
author = null,
source = source,
)
} }
} else {
json.getJSONArray("comics").mapJSON { j -> null -> {
val slug = j.getString("slug") val url = buildString {
val urlManga = "https://$domain/comics/$slug" append("https://api.")
Manga( append(domain)
id = generateUid(urlManga), append("/misc/comic/search/query?status=&order=&genreNames=&type=&start=")
title = j.getString("title"), append(start)
altTitle = null, append("&end=")
url = urlManga, append(end)
publicUrl = urlManga, }
rating = RATING_UNKNOWN, return parseMangaList(webClient.httpGet(url).parseJson())
isNsfw = false,
coverUrl = "https://api.$domain/" + j.getString("cover"),
tags = setOf(),
state = null,
author = null,
source = source,
)
} }
} }
}
private fun parseMangaList(json: JSONObject): List<Manga> {
return json.getJSONArray("comics").mapJSON { j ->
val slug = j.getString("slug")
val urlManga = "https://$domain/comics/$slug"
Manga(
id = generateUid(urlManga),
title = j.getString("title"),
altTitle = null,
url = urlManga,
publicUrl = urlManga,
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = "https://api.$domain/" + j.getString("cover"),
tags = setOf(),
state = null,
author = null,
source = source,
)
}
}
private fun parseMangaListQuery(json: JSONObject): List<Manga> {
return json.getJSONArray("results").mapJSON { j ->
val slug = j.getString("slug")
val urlManga = "https://$domain/comics/$slug"
Manga(
id = generateUid(urlManga),
title = j.getString("title"),
altTitle = null,
url = urlManga,
publicUrl = urlManga,
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = "",
tags = setOf(),
state = null,
author = null,
source = source,
)
}
} }
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH) val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH)
return manga.copy( return manga.copy(
altTitle = null, altTitle = null,
tags = root.select("div.serieGenre span").mapNotNullToSet { span -> tags = root.select("div.serieGenre span").mapNotNullToSet { span ->
@ -156,7 +165,6 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
val script = doc.requireElementById("__NUXT_DATA__").data() val script = doc.requireElementById("__NUXT_DATA__").data()
.substringAfterLast("\"genres\"").substringBeforeLast("\"comics\"") .substringAfterLast("\"genres\"").substringBeforeLast("\"comics\"")
.split("\",\"").drop(1) .split("\",\"").drop(1)
return script.mapNotNullToSet { tag -> return script.mapNotNullToSet { tag ->
MangaTag( MangaTag(
key = tag.substringBeforeLast("\",{"), key = tag.substringBeforeLast("\",{"),

@ -24,36 +24,50 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
.add("User-Agent", UserAgents.CHROME_MOBILE) .add("User-Agent", UserAgents.CHROME_MOBILE)
.build() .build()
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val doc = val doc =
if (!query.isNullOrEmpty()) { // search only works with 4 or more letters when (filter) {
if (page > 1) { is MangaListFilter.Search -> {
return emptyList() if (page > 1) {
return emptyList()
}
val q = filter.query.urlEncoded().replace("%20", "+")
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q"
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml()
} }
val q = query.urlEncoded().replace("%20", "+")
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q" is MangaListFilter.Advanced -> {
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml() val url = buildString {
} else { append("https://")
val url = buildString { append(domain)
append("https://")
append(domain) filter.tags.oneOrThrowIfMany()?.let {
if (!tags.isNullOrEmpty()) { append("/manga/")
append("/manga/") append(it.key)
append(tag?.key.orEmpty()) }
if (page > 1) {
append("/page/")
append(page)
append('/')
}
} }
if (page > 1) { webClient.httpGet(url).parseHtml()
append("/page/") }
append(page)
append('/') null -> {
val url = buildString {
append("https://")
append(domain)
if (page > 1) {
append("/page/")
append(page)
append('/')
}
} }
webClient.httpGet(url).parseHtml()
} }
webClient.httpGet(url).parseHtml()
} }
return doc.select("div.sect__content.grid-items div.item-poster").map { div -> return doc.select("div.sect__content.grid-items div.item-poster").map { div ->

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site.fr package org.koitharu.kotatsu.parsers.site.fr
import okhttp3.Headers import okhttp3.Headers
import org.json.JSONArray
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.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -21,6 +22,8 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com") override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
override val headers: Headers = Headers.Builder() override val headers: Headers = Headers.Builder()
@ -43,77 +46,102 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
) )
} }
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int, when (filter) {
query: String?, is MangaListFilter.Search -> {
tags: Set<MangaTag>?, throw IllegalArgumentException("Search is not supported by this source")
sortOrder: SortOrder,
): List<Manga> {
if (!query.isNullOrEmpty()) {
throw IllegalArgumentException("Search is not supported by this source")
}
if (sortOrder == SortOrder.ALPHABETICAL) {
if (page > 1) {
return emptyList()
}
val url = buildString {
append("https://")
append(domain)
append("/api/get/catalog?page=0&filter=all")
}
val json = webClient.httpGet(url).parseJsonArray()
return json.mapJSON { j ->
val urlManga = "https://$domain/api/get/card/${j.getString("slug")}"
val img = "https://$domain/upload/min_cover/${j.getString("image")}"
Manga(
id = generateUid(urlManga),
title = j.getString("title"),
altTitle = null,
url = urlManga,
publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = false,
coverUrl = img,
tags = setOf(),
state = when (j.getString("status")) {
"0" -> MangaState.ONGOING
"1" -> MangaState.FINISHED
"3" -> MangaState.ABANDONED
else -> null
},
author = null,
source = source,
)
} }
} else {
val url = buildString { is MangaListFilter.Advanced -> {
append("https://")
append(domain) if (filter.sortOrder == SortOrder.ALPHABETICAL) {
append("/api/get/homegrid/") if (page > 1) {
append(page) return emptyList()
}
val url = buildString {
append("https://")
append(domain)
append("/api/get/catalog?page=0&filter=")
filter.states.oneOrThrowIfMany()?.let {
when (it) {
MangaState.ONGOING -> append("0")
MangaState.FINISHED -> append("1")
MangaState.PAUSED -> append("4")
MangaState.ABANDONED -> append("3")
}
}
}
return parseMangaListAlpha(webClient.httpGet(url).parseJsonArray())
} else {
val url = buildString {
append("https://")
append(domain)
append("/api/get/homegrid/")
append(page)
}
return parseMangaList(webClient.httpGet(url).parseJsonArray())
}
} }
val json = webClient.httpGet(url).parseJsonArray()
return json.mapJSON { j -> null -> {
val urlManga = "https://$domain/api/get/card/${j.getString("manga_slug")}" val url = buildString {
val img = "https://$domain/upload/min_cover/${j.getString("manga_image")}" append("https://")
Manga( append(domain)
id = generateUid(urlManga), append("/api/get/homegrid/")
title = j.getString("manga_title"), append(page)
altTitle = null, }
url = urlManga, return parseMangaList(webClient.httpGet(url).parseJsonArray())
publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getString("manga_rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = false,
coverUrl = img,
tags = setOf(),
state = null,
author = null,
source = source,
)
} }
}
}
private fun parseMangaList(json: JSONArray): List<Manga> {
return json.mapJSON { j ->
val urlManga = "https://$domain/api/get/card/${j.getString("manga_slug")}"
val img = "https://$domain/upload/min_cover/${j.getString("manga_image")}"
Manga(
id = generateUid(urlManga),
title = j.getString("manga_title"),
altTitle = null,
url = urlManga,
publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getString("manga_rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = false,
coverUrl = img,
tags = setOf(),
state = null,
author = null,
source = source,
)
} }
}
private fun parseMangaListAlpha(json: JSONArray): List<Manga> {
return json.mapJSON { j ->
val urlManga = "https://$domain/api/get/card/${j.getString("slug")}"
val img = "https://$domain/upload/min_cover/${j.getString("image")}"
Manga(
id = generateUid(urlManga),
title = j.getString("title"),
altTitle = null,
url = urlManga,
publicUrl = urlManga.toAbsoluteUrl(domain),
rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
isNsfw = false,
coverUrl = img,
tags = setOf(),
state = when (j.getString("status")) {
"0" -> MangaState.ONGOING
"1" -> MangaState.FINISHED
"3" -> MangaState.ABANDONED
else -> null
},
author = null,
source = source,
)
}
} }
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {

@ -30,46 +30,45 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
.add("User-Agent", UserAgents.CHROME_DESKTOP) .add("User-Agent", UserAgents.CHROME_DESKTOP)
.build() .build()
override val isMultipleTagsSupported = false
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int, if (page > 1) {
query: String?, return emptyList()
tags: Set<MangaTag>?, }
sortOrder: SortOrder,
): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
if (page == 1) { when (filter) {
if (!query.isNullOrEmpty()) { is MangaListFilter.Search -> {
append("/?s=") append("/?s=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
append("&post_type=manga") append("&post_type=manga")
}
} else if (!tags.isNullOrEmpty()) { is MangaListFilter.Advanced -> {
append("/genres/") if (filter.tags.isNotEmpty()) {
for (tag in tags) { append("/genres/")
append(tag.key) filter.tags.oneOrThrowIfMany()?.let {
} append(it.key)
} else { }
append("/tous-nos-mangas/") } else {
append("?order=") append("/tous-nos-mangas/?order=")
when (sortOrder) { when (filter.sortOrder) {
SortOrder.POPULARITY -> append("popular") SortOrder.POPULARITY -> append("popular")
SortOrder.UPDATED -> append("update") SortOrder.UPDATED -> append("update")
SortOrder.ALPHABETICAL -> append("title") SortOrder.ALPHABETICAL -> append("title")
SortOrder.NEWEST -> append("create") SortOrder.NEWEST -> append("create")
else -> append("update") else -> append("update")
}
} }
} }
} else {
return emptyList()
}
null -> append("/tous-nos-mangas/?order=update")
}
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.postbody .bs .bsx").map { div -> return doc.select("div.postbody .bs .bsx").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(
@ -90,7 +89,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/tous-nos-mangas/").parseHtml() val doc = webClient.httpGet("https://$domain/tous-nos-mangas/").parseHtml()
return doc.select("ul.genre li").mapNotNullToSet { li -> return doc.select("ul.genre li").mapNotNullToSet { li ->
@ -104,25 +102,18 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
} }
} }
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = getChapters(doc) val chaptersDeferred = getChapters(doc)
val desc = doc.selectFirstOrThrow("div.desc").html() val desc = doc.selectFirstOrThrow("div.desc").html()
val state = if (doc.select("div.spe span:contains(En cours)").isNullOrEmpty()) { val state = if (doc.select("div.spe span:contains(En cours)").isNullOrEmpty()) {
MangaState.FINISHED MangaState.FINISHED
} else { } else {
MangaState.ONGOING MangaState.ONGOING
} }
val alt = doc.body().select("div.infox span.alter").text() val alt = doc.body().select("div.infox span.alter").text()
val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "") val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "")
manga.copy( manga.copy(
tags = doc.select("div.spe span:contains(Genres) a").mapNotNullToSet { a -> tags = doc.select("div.spe span:contains(Genres) a").mapNotNullToSet { a ->
MangaTag( MangaTag(
@ -140,7 +131,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
) )
} }
private fun getChapters(doc: Document): List<MangaChapter> { private fun getChapters(doc: Document): List<MangaChapter> {
return doc.body().requireElementById("chapter_list").select("li").mapChapters(reversed = true) { i, li -> return doc.body().requireElementById("chapter_list").select("li").mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a") val a = li.selectFirstOrThrow("a")
@ -161,10 +151,8 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain) val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val script = doc.selectFirstOrThrow("script:containsData(page_image)") val script = doc.selectFirstOrThrow("script:containsData(page_image)")
val images = JSONArray(script.data().substringAfterLast("var pages = ").substringBefore(';')) val images = JSONArray(script.data().substringAfterLast("var pages = ").substringBefore(';'))
val pages = ArrayList<MangaPage>(images.length()) val pages = ArrayList<MangaPage>(images.length())
for (i in 0 until images.length()) { for (i in 0 until images.length()) {
val pageTake = images.getJSONObject(i) val pageTake = images.getJSONObject(i)
@ -177,7 +165,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
), ),
) )
} }
return pages return pages
} }
} }

@ -20,49 +20,54 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val isMultipleTagsSupported = false
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com") override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
override val headers: Headers = Headers.Builder() override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP) .add("User-Agent", UserAgents.CHROME_DESKTOP)
.build() .build()
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
when { when (filter) {
!query.isNullOrEmpty() -> { is MangaListFilter.Search -> {
append("/page/") append("/page/")
append(page.toString()) append(page.toString())
append("/?s=") append("/?s=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
} }
!tags.isNullOrEmpty() -> { is MangaListFilter.Advanced -> {
append("/tag/") if (filter.tags.isNotEmpty()) {
for (tag in tags) { filter.tags.oneOrThrowIfMany()?.let {
append(tag.key) append("/tag/")
append(',') append(it.key)
} append("/page/")
append("/page/") append(page.toString())
append(page.toString()) append("/")
} }
} else {
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
append("/manga/page/")
append(page.toString())
append("/")
}
else -> { if (filter.sortOrder == SortOrder.UPDATED && page > 1) {
if (sortOrder == SortOrder.ALPHABETICAL) { return emptyList()
append("/manga/") }
append("/page/")
append(page.toString())
}
if (sortOrder == SortOrder.UPDATED) {
append("")
} }
}
null -> {
append("/manga/page/")
append(page.toString())
append("/")
} }
} }
} }
@ -180,9 +185,8 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con
val root = body.select(".asp_gochosen")[1] val root = body.select(".asp_gochosen")[1]
val list = root?.select("option").orEmpty() val list = root?.select("option").orEmpty()
return list.mapToSet { li -> return list.mapToSet { li ->
MangaTag( MangaTag(
key = li.text(), key = li.text().lowercase().replace(" ", "-"),
title = li.text(), title = li.text(),
source = source, source = source,
) )

Loading…
Cancel
Save