[fmreader] add SearchWithFilters

[LegacyScans] add ContentTypes, SortOrder.UPDATED
[MangaMana] add SortOrder.RATING_ASC , SortOrder.NEWEST_ASC
[Manhwa18.com] move to /en
Add new option on .toAbsoluteUrl()
master
devi 2 years ago
parent cc62981f12
commit 2061a971b8

@ -0,0 +1,245 @@
package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap
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("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI)
internal class Manhwa18Com(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder>
get() = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.NEWEST,
SortOrder.RATING,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = tagsMap.get().values.toSet(),
availableStates = EnumSet.of(
MangaState.ONGOING,
MangaState.FINISHED,
MangaState.PAUSED,
),
)
override suspend fun getFavicons(): Favicons {
return Favicons(
listOf(
Favicon("https://$domain/uploads/logos/logo-mini.png", 92, null),
),
domain,
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/tim-kiem?page=")
append(page.toString())
filter.query?.let {
append("&q=")
append(filter.query.urlEncoded())
}
append("&accept_genres=")
if (filter.tags.isNotEmpty()) {
append(
filter.tags.joinToString(",") { it.key },
)
}
append("&reject_genres=")
if (filter.tagsExclude.isNotEmpty()) {
append(
filter.tagsExclude.joinToString(",") { it.key },
)
}
append("&sort=")
append(
when (order) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.ALPHABETICAL_DESC -> "za"
SortOrder.POPULARITY -> "top"
SortOrder.UPDATED -> "update"
SortOrder.NEWEST -> "new"
SortOrder.RATING -> "like"
else -> "update"
},
)
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "3"
MangaState.PAUSED -> "2"
else -> ""
},
)
}
// Support author
// filter.author.let{
// the
// append("&artist=")
// append(filter.author)
// }
}
val docs = webClient.httpGet(url).parseHtml()
return docs.select(".card-body .thumb-item-flow")
.map {
val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a")
val absUrl = titleElement.attrAsAbsoluteUrl("href")
Manga(
id = generateUid(absUrl.toRelativeUrl(domain)),
title = titleElement.text(),
altTitle = null,
url = absUrl.toRelativeUrl(domain),
publicUrl = absUrl,
rating = RATING_UNKNOWN,
isNsfw = true,
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(),
tags = emptySet(),
state = null,
author = null,
largeCoverUrl = null,
description = null,
source = MangaParserSource.MANHWA18,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val cardInfoElement = docs.selectFirst("div.series-information")
val author = cardInfoElement?.selectFirst(".info-name:contains(Author)")?.parent()
?.select("a")
?.joinToString(", ") { it.text() }
val availableTags = tagsMap.get()
val tags = cardInfoElement?.selectFirst(".info-name:contains(Genre)")?.parent()
?.select("a")
?.mapNotNullToSet { availableTags[it.text().lowercase(Locale.ENGLISH)] }
val state = cardInfoElement?.selectFirst(".info-name:contains(Status)")?.parent()
?.selectFirst("a")
?.let {
when (it.text().lowercase()) {
"on going" -> MangaState.ONGOING
"completed" -> MangaState.FINISHED
"on hold" -> MangaState.PAUSED
else -> null
}
}
return manga.copy(
altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownText()?.removePrefix(": "),
author = author,
description = docs.selectFirst(".series-summary .summary-content")?.html(),
tags = tags.orEmpty(),
state = state,
chapters = docs.select(".card-body > .list-chapters > a").mapChapters(reversed = true) { index, element ->
val chapterUrl = element.attrAsAbsoluteUrlOrNull("href")?.toRelativeUrl(domain)
?: return@mapChapters null
val uploadDate = parseUploadDate(element.selectFirst(".chapter-time")?.text())
MangaChapter(
id = generateUid(chapterUrl),
name = element.selectFirst(".chapter-name")?.text().orEmpty(),
number = index + 1f,
volume = 0,
url = chapterUrl,
scanlator = null,
uploadDate = uploadDate,
branch = null,
source = MangaParserSource.MANHWA18,
)
},
)
}
private fun parseUploadDate(timeStr: String?): Long {
timeStr ?: return 0
val timeWords = timeStr.split(' ')
if (timeWords.size != 3) return 0
val timeWord = timeWords[1]
val timeAmount = timeWords[0].toIntOrNull() ?: return 0
val timeUnit = when (timeWord) {
"minute", "minutes" -> Calendar.MINUTE
"hour", "hours" -> Calendar.HOUR
"day", "days" -> Calendar.DAY_OF_YEAR
"week", "weeks" -> Calendar.WEEK_OF_YEAR
"month", "months" -> Calendar.MONTH
"year", "years" -> Calendar.YEAR
else -> return 0
}
val cal = Calendar.getInstance()
cal.add(timeUnit, -timeAmount)
return cal.time.time
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(chapterUrl).parseHtml()
return doc.requireElementById("chapter-content").select("img").mapNotNull {
val url = it.attrAsRelativeUrlOrNull("data-src")
?: it.attrAsRelativeUrlOrNull("src")
?: return@mapNotNull null
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = MangaParserSource.MANHWA18,
)
}
}
private val tagsMap = SuspendLazy(::parseTags)
private suspend fun parseTags(): Map<String, MangaTag> {
val doc = webClient.httpGet("https://$domain/tim-kiem?q=").parseHtml()
val list = doc.getElementsByAttribute("data-genre-id")
if (list.isEmpty()) {
return emptyMap()
}
val result = ArrayMap<String, MangaTag>(list.size)
for (item in list) {
val id = item.attr("data-genre-id")
val name = item.text()
result[name.lowercase(Locale.ENGLISH)] = MangaTag(
title = name.toTitleCase(Locale.ENGLISH),
key = id,
source = source,
)
}
return result
}
}

@ -35,17 +35,23 @@ internal abstract class FmreaderParser(
SortOrder.ALPHABETICAL_DESC, SortOrder.ALPHABETICAL_DESC,
) )
protected open val listUrl = "/manga-list.html"
protected open val datePattern = "MMMM d, yyyy"
protected open val tagPrefix = "manga-list-genre-"
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
isTagsExclusionSupported = true, isTagsExclusionSupported = true,
isSearchSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(
MangaState.ONGOING,
MangaState.FINISHED,
MangaState.ABANDONED,
),
)
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
@ -71,14 +77,9 @@ internal abstract class FmreaderParser(
"drop", "drop",
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( protected open val listUrl = "/manga-list.html"
availableTags = fetchAvailableTags(), protected open val datePattern = "MMMM d, yyyy"
availableStates = EnumSet.of( protected open val tagPrefix = "manga-list-genre-"
MangaState.ONGOING,
MangaState.FINISHED,
MangaState.ABANDONED,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString { val url = buildString {
@ -87,45 +88,45 @@ internal abstract class FmreaderParser(
append(listUrl) append(listUrl)
append("?page=") append("?page=")
append(page.toString()) append(page.toString())
when {
!filter.query.isNullOrEmpty() -> {
append("&name=")
append(filter.query.urlEncoded())
}
else -> { filter.query?.let {
append("&name=")
append("&genre=") append(filter.query.urlEncoded())
append(filter.tags.joinToString(",") { it.key }) }
append("&ungenre=")
append(filter.tagsExclude.joinToString(",") { it.key })
append("&sort=")
when (order) {
SortOrder.POPULARITY -> append("views&sort_type=DESC")
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC")
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
else -> append("last_update&sort_type=DESC")
}
append("&m_status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "2"
MangaState.FINISHED -> "1"
MangaState.ABANDONED -> "3"
else -> ""
},
)
}
} // filter.author?.let {
// append("&author=")
// append(filter.author.urlEncoded())
// }
append("&genre=")
append(filter.tags.joinToString(",") { it.key })
append("&ungenre=")
append(filter.tagsExclude.joinToString(",") { it.key })
append("&sort=")
when (order) {
SortOrder.POPULARITY -> append("views&sort_type=DESC")
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC")
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
else -> append("last_update&sort_type=DESC")
}
append("&m_status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "2"
MangaState.FINISHED -> "1"
MangaState.ABANDONED -> "3"
else -> ""
},
)
} }
} }
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
@ -139,10 +140,10 @@ internal abstract class FmreaderParser(
id = generateUid(href), id = generateUid(href),
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.selectFirst("div.img-in-ratio")?.attr("data-bg")
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("(") ?: div.selectFirst("div.img-in-ratio")?.attr("style")?.substringAfter("(")
.substringBefore(")"), ?.substringBefore(")"))?.toAbsoluteUrl(domain).orEmpty(),
title = div.selectFirstOrThrow("div.series-title").text().orEmpty(), title = div.selectFirst("div.series-title")?.text().orEmpty(),
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),

@ -1,140 +0,0 @@
package org.koitharu.kotatsu.parsers.site.fmreader.en
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", ContentType.HENTAI)
internal class Manhwa18Com(context: MangaLoaderContext) :
FmreaderParser(context, MangaParserSource.MANHWA18COM, "manhwa18.com") {
override val listUrl = "/tim-kiem"
override val selectState = "div.info-item:contains(Status) span.info-value "
override val selectAlt = "div.info-item:contains(Other name) span.info-value "
override val selectTag = "div.info-item:contains(Genre) span.info-value a"
override val datePattern = "dd/MM/yyyy"
override val selectPage = "div#chapter-content img"
override val selectBodyTag = "div.advanced-wrapper .genre_label"
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/tim-kiem?page=")
append(page.toString())
when {
!filter.query.isNullOrEmpty() -> {
append("&q=")
append(filter.query.urlEncoded())
}
else -> {
append("&accept_genres=")
append(filter.tags.joinToString(",") { it.key })
append("&reject_genres=")
append(filter.tagsExclude.joinToString(",") { it.key })
append("&sort=")
append(
when (order) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.ALPHABETICAL_DESC -> "za"
SortOrder.POPULARITY -> "top"
SortOrder.UPDATED -> "update"
SortOrder.NEWEST -> "new"
SortOrder.RATING -> "like"
else -> null
},
)
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "3"
MangaState.PAUSED -> "2"
else -> ""
},
)
}
}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select(selectBodyTag).mapNotNullToSet { label ->
val key = label.attr("data-genre-id")
MangaTag(
key = key,
title = label.selectFirstOrThrow(".gerne-name").text(),
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(doc) }
val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.selectFirst(selectState)
val state = stateDiv?.let {
when (it.text().lowercase()) {
in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED
else -> null
}
}
val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other name", "")
val auth = doc.body().selectFirst(selectAut)?.text()
manga.copy(
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").substringAfter("manga-list-genre-").substringBeforeLast(".html"),
title = a.text().toTitleCase(),
source = source,
)
},
description = desc,
altTitle = alt,
author = auth,
state = state,
chapters = chaptersDeferred.await(),
)
}
override suspend fun getChapters(doc: Document): List<MangaChapter> {
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, a ->
val href = a.attrAsRelativeUrl("href")
val dateText = a.selectFirst(selectDate)?.text()?.substringAfter("- ")
MangaChapter(
id = generateUid(href),
name = a.selectFirstOrThrow("div.chapter-name").text(),
number = i + 1f,
volume = 0,
url = href,
uploadDate = parseChapterDate(
dateFormat,
dateText,
),
source = source,
scanlator = null,
branch = null,
)
}
}
}

@ -15,88 +15,6 @@ internal class OlimpoScans(context: MangaLoaderContext) :
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 filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isMultipleTagsSupported = false,
isTagsExclusionSupported = false,
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when {
!filter.query.isNullOrEmpty() -> {
append(listUrl)
append("?page=")
append(page.toString())
append("&name=")
append(filter.query.urlEncoded())
}
else -> {
if (filter.tags.isNotEmpty()) {
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 (order) {
SortOrder.POPULARITY -> append("views&sort_type=DESC")
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC")
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
else -> append("last_update&sort_type=DESC")
}
}
append("&m_status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "2"
MangaState.FINISHED -> "1"
MangaState.ABANDONED -> "3"
else -> ""
},
)
}
}
}
}
val doc = webClient.httpGet(url).parseHtml()
val lastPage =
doc.selectLast(".pagination a")?.attr("href")?.substringAfterLast("page=")?.substringBeforeLast("&artist")
?.toInt() ?: 1
if (lastPage < page) {
return emptyList()
}
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").toAbsoluteUrl(domain),
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 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()

@ -45,19 +45,18 @@ internal class Klz9(context: MangaLoaderContext) :
private val chapterListSelector = "div#list-chapters p, table.table tr, .list-chapters > a" private val chapterListSelector = "div#list-chapters p, table.table tr, .list-chapters > a"
private fun generateRandomStr(length: Int): String { private fun generateRandomStr(): String {
return (1..length).map { toPathCharacters.random() }.joinToString("") return (1..25).map { toPathCharacters.random() }.joinToString("")
} }
private val toPathCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" private val toPathCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
override suspend fun getChapters(doc: Document): List<MangaChapter> { override suspend fun getChapters(doc: Document): List<MangaChapter> {
val slug = doc.selectFirstOrThrow("div.h0rating").attr("slug") val slug = doc.selectFirstOrThrow("div.h0rating").attr("slug")
val xhrUrl = "https://$domain/${generateRandomStr(25)}.lstc".toHttpUrl().newBuilder() val xhrUrl = "https://$domain/${generateRandomStr()}.lstc".toHttpUrl().newBuilder()
.addQueryParameter("slug", slug) .addQueryParameter("slug", slug)
.build() .build()
val docLoad = val docLoad = webClient.httpGet(xhrUrl).parseHtml()
webClient.httpGet(xhrUrl).parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale) val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return docLoad.body().select(chapterListSelector).mapChapters(reversed = true) { i, a -> return docLoad.body().select(chapterListSelector).mapChapters(reversed = true) { i, a ->

@ -66,7 +66,7 @@ internal abstract class FoolSlideParser(
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append("/") append('/')
append(listUrl) append(listUrl)
// For some sites that don't have enough manga and page 2 links to page 1 // For some sites that don't have enough manga and page 2 links to page 1
if (!pagination) { if (!pagination) {
@ -89,7 +89,7 @@ internal abstract class FoolSlideParser(
url = href, url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain), publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),// in search no img coverUrl = div.selectFirst("img")?.src().orEmpty(),// in search no img
title = div.selectFirstOrThrow(".title a").text().orEmpty(), title = div.selectFirst(".title a")?.text().orEmpty(),
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),
@ -113,21 +113,21 @@ internal abstract class FoolSlideParser(
testAdultPage testAdultPage
} }
val chapters = getChapters(doc) val chapters = getChapters(doc)
val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("</b>")) { val desc = if (doc.selectFirst(selectInfo)?.html()?.contains("</b>") == true) {
doc.selectFirstOrThrow(selectInfo).text().substringAfterLast(": ") doc.selectFirst(selectInfo)?.text()?.substringAfterLast(": ")
} else { } else {
doc.selectFirstOrThrow(selectInfo).text() doc.selectFirst(selectInfo)?.text()
} }
val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("</b>")) { val author = if (doc.selectFirst(selectInfo)?.html()?.contains("</b>") == true) {
doc.selectFirstOrThrow(selectInfo).text().substringAfter(": ").substringBefore("Art") doc.selectFirst(selectInfo)?.text()?.substringAfter(": ")?.substringBefore("Art")
} else { } else {
null null
} }
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl,
description = desc, description = desc.orEmpty(),
altTitle = null, altTitle = null,
author = author, author = author.orEmpty(),
state = null, state = null,
chapters = chapters, chapters = chapters,
) )
@ -142,14 +142,14 @@ internal abstract class FoolSlideParser(
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, div -> return doc.body().select(selectChapter).mapChapters(reversed = true) { i, div ->
val a = div.selectFirstOrThrow(".title a") val a = div.selectFirstOrThrow(".title a")
val href = a.attrAsRelativeUrl("href") val href = a.attrAsRelativeUrl("href")
val dateText = div.selectFirstOrThrow(selectDate).text().substringAfter(", ") val dateText = div.selectFirst(selectDate)?.text()?.substringAfter(", ")
MangaChapter( MangaChapter(
id = generateUid(href), id = generateUid(href),
name = a.text(), name = a.text(),
number = i + 1f, number = i + 1f,
volume = 0, volume = 0,
url = href, url = href,
uploadDate = if (div.selectFirstOrThrow(selectDate).text().contains(", ")) { uploadDate = if (div.selectFirst(selectDate)?.text()?.contains(", ") == true) {
dateFormat.tryParse(dateText) dateFormat.tryParse(dateText)
} else { } else {
0 0

@ -22,21 +22,21 @@ internal class Pzykosis666hFansub(context: MangaLoaderContext) :
testAdultPage testAdultPage
} }
val chapters = getChapters(doc) val chapters = getChapters(doc)
val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("Descripción")) { val desc = if (doc.selectFirst(selectInfo)?.html()?.contains("Descripción") == true) {
doc.selectFirstOrThrow(selectInfo).text().substringAfter("Descripción: ").substringBefore("Lecturas") doc.selectFirst(selectInfo)?.text()?.substringAfter("Descripción: ")?.substringBefore("Lecturas")
} else { } else {
doc.selectFirstOrThrow(selectInfo).text() doc.selectFirst(selectInfo)?.text()
} }
val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("Author")) { val author = if (doc.selectFirst(selectInfo)?.html()?.contains("Author") == true) {
doc.selectFirstOrThrow(selectInfo).text().substringAfter("Author: ").substringBefore("Art") doc.selectFirst(selectInfo)?.text()?.substringAfter("Author: ")?.substringBefore("Art")
} else { } else {
null null
} }
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl,
description = desc, description = desc.orEmpty(),
altTitle = null, altTitle = null,
author = author, author = author.orEmpty(),
state = null, state = null,
chapters = chapters, chapters = chapters,
) )

@ -23,20 +23,20 @@ internal class SeinagiAdulto(context: MangaLoaderContext) :
} }
val chapters = getChapters(doc) val chapters = getChapters(doc)
val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("Descripción")) { val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("Descripción")) {
doc.selectFirstOrThrow(selectInfo).text().substringAfter("Descripción: ").substringBefore("Lecturas") doc.selectFirst(selectInfo)?.text()?.substringAfter("Descripción: ")?.substringBefore("Lecturas")
} else { } else {
doc.selectFirstOrThrow(selectInfo).text() doc.selectFirst(selectInfo)?.text()
} }
val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("Author")) { val author = if (doc.selectFirst(selectInfo)?.html()?.contains("Author") == true) {
doc.selectFirstOrThrow(selectInfo).text().substringAfter("Author: ").substringBefore("Art") doc.selectFirst(selectInfo)?.text()?.substringAfter("Author: ")?.substringBefore("Art")
} else { } else {
null null
} }
manga.copy( manga.copy(
coverUrl = doc.selectFirst(".thumbnail img")?.src().orEmpty(),// for manga result on search coverUrl = doc.selectFirst(".thumbnail img")?.src().orEmpty(),// for manga result on search
description = desc, description = desc.orEmpty(),
altTitle = null, altTitle = null,
author = author, author = author.orEmpty(),
state = null, state = null,
chapters = chapters, chapters = chapters,
) )

@ -59,58 +59,53 @@ internal class BentomangaParser(context: MangaLoaderContext) :
.host(domain) .host(domain)
.addPathSegment("manga_list") .addPathSegment("manga_list")
.addQueryParameter("limit", page.toString()) .addQueryParameter("limit", page.toString())
when {
!filter.query.isNullOrEmpty() -> {
url.addQueryParameter("search", filter.query)
}
else -> { filter.query?.let {
url.addQueryParameter("search", filter.query)
}
when (order) { when (order) {
SortOrder.UPDATED -> url.addQueryParameter("order_by", "update") SortOrder.UPDATED -> url.addQueryParameter("order_by", "update")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
SortOrder.POPULARITY -> url.addQueryParameter("order_by", "views") SortOrder.POPULARITY -> url.addQueryParameter("order_by", "views")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
SortOrder.RATING -> url.addQueryParameter("order_by", "top") SortOrder.RATING -> url.addQueryParameter("order_by", "top")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
SortOrder.NEWEST -> url.addQueryParameter("order_by", "create") SortOrder.NEWEST -> url.addQueryParameter("order_by", "create")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
SortOrder.ALPHABETICAL -> url.addQueryParameter("order_by", "name") SortOrder.ALPHABETICAL -> url.addQueryParameter("order_by", "name")
.addQueryParameter("order", "asc") .addQueryParameter("order", "asc")
SortOrder.ALPHABETICAL_DESC -> url.addQueryParameter("order_by", "name") SortOrder.ALPHABETICAL_DESC -> url.addQueryParameter("order_by", "name")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
else -> url.addQueryParameter("order_by", "update") else -> url.addQueryParameter("order_by", "update")
.addQueryParameter("order", "desc") .addQueryParameter("order", "desc")
} }
if (filter.tags.isNotEmpty()) {
url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
}
if (filter.tagsExclude.isNotEmpty()) { if (filter.tags.isNotEmpty()) {
url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key }) url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
} }
filter.states.oneOrThrowIfMany()?.let { if (filter.tagsExclude.isNotEmpty()) {
url.addQueryParameter( url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key })
"state", }
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "2"
MangaState.PAUSED -> "3"
MangaState.ABANDONED -> "5"
else -> "1"
},
)
}
} filter.states.oneOrThrowIfMany()?.let {
url.addQueryParameter(
"state",
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "2"
MangaState.PAUSED -> "3"
MangaState.ABANDONED -> "5"
else -> "1"
},
)
} }
val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content") val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content")
return root.select(".manga[data-manga]").map { div -> return root.select(".manga[data-manga]").map { div ->
@ -118,7 +113,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
val href = header.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = header.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(
id = generateUid(href), id = generateUid(href),
title = div.selectFirstOrThrow("h1").text(), title = div.selectFirst("h1")?.text().orEmpty(),
altTitle = null, altTitle = null,
url = href, url = href,
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
@ -129,7 +124,7 @@ internal class BentomangaParser(context: MangaLoaderContext) :
?.div(10f) ?.div(10f)
?: RATING_UNKNOWN, ?: RATING_UNKNOWN,
isNsfw = div.selectFirst(".badge-adult_content") != null, isNsfw = div.selectFirst(".badge-adult_content") != null,
coverUrl = div.selectFirstOrThrow("img").src().assertNotNull("src").orEmpty(), coverUrl = div.selectFirst("img")?.src().assertNotNull("src").orEmpty(),
tags = div.selectFirst(".component-manga-categories") tags = div.selectFirst(".component-manga-categories")
.assertNotNull("tags") .assertNotNull("tags")
?.select("a") ?.select("a")

@ -87,7 +87,7 @@ internal class FuryoSociety(context: MangaLoaderContext) :
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = getChapters(doc) val chaptersDeferred = getChapters(doc)
manga.copy( manga.copy(
description = doc.selectFirstOrThrow("div.fs-comic-description").html(), description = doc.selectFirst("div.fs-comic-description")?.html().orEmpty(),
chapters = chaptersDeferred, chapters = chaptersDeferred,
isNsfw = doc.selectFirst(".adult-text") != null, isNsfw = doc.selectFirst(".adult-text") != null,
) )
@ -98,10 +98,10 @@ internal class FuryoSociety(context: MangaLoaderContext) :
val a = div.selectFirstOrThrow("div.title a") val a = div.selectFirstOrThrow("div.title a")
val href = a.attrAsRelativeUrl("href") val href = a.attrAsRelativeUrl("href")
val dateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale) val dateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale)
val dateText = div.selectFirstOrThrow("div.meta_r").text().replace("Hier", "1 jour") val dateText = div.selectFirst("div.meta_r")?.text()?.replace("Hier", "1 jour")
MangaChapter( MangaChapter(
id = generateUid(href), id = generateUid(href),
name = div.selectFirstOrThrow("div.title").text() + " : " + div.selectFirstOrThrow("div.name").text(), name = div.selectFirst("div.title")?.text() + " : " + div.selectFirst("div.name")?.text(),
number = i + 1f, number = i + 1f,
volume = 0, volume = 0,
url = href, url = href,
@ -133,13 +133,16 @@ internal class FuryoSociety(context: MangaLoaderContext) :
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.startsWith("il y a") || // Handle translated 'ago' in French.
d.endsWith(" an") || d.endsWith(" ans") || WordSet("il y a").startsWith(d) -> {
d.endsWith(" mois") || parseRelativeDate(d)
d.endsWith(" jour") || d.endsWith(" jours") || }
d.endsWith(" heure") || d.endsWith(" heures") ||
d.endsWith(" seconde") || d.endsWith(" secondes") || WordSet(
d.endsWith(" minute") || d.endsWith(" minutes") -> parseRelativeDate(date) " an", " mois", " jour", " jours", " heure", " heures", " minute", " minutes", " seconde", " secondes",
).endsWith(d) -> {
parseRelativeDate(d)
}
else -> dateFormat.tryParse(date) else -> dateFormat.tryParse(date)
} }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.fr package org.koitharu.kotatsu.parsers.site.fr
import org.json.JSONArray
import org.json.JSONObject 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
@ -15,10 +16,15 @@ import java.util.*
internal class LegacyScansParser(context: MangaLoaderContext) : internal class LegacyScansParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) { PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com") override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
@ -28,13 +34,14 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.ONE_SHOT,
),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val end = page * pageSize val end = page * pageSize
val start = end - (pageSize - 1) val start = end - (pageSize - 1)
@ -52,36 +59,74 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
} }
else -> { else -> {
val url = buildString {
append("https://api.") if (order == SortOrder.UPDATED) {
append(domain)
append("/misc/comic/search/query?status=") if (filter.states.isNotEmpty() || filter.tags.isNotEmpty() || filter.types.isNotEmpty()) {
filter.states.oneOrThrowIfMany()?.let { throw IllegalArgumentException("La recherche part mis à jour + des filtres n'est pas supporté par cette source.")
append( }
when (it) {
MangaState.ONGOING -> "En cours" val url = buildString {
MangaState.FINISHED -> "Terminé" append("https://api.")
MangaState.ABANDONED -> "Abandonné" append(domain)
MangaState.PAUSED -> "En pause" append("/misc/comic/home/updates?start=")
else -> "" append(start.toString())
}, append("&end=")
) append(end.toString())
} }
append("&order=&genreNames=")
append(filter.tags.joinToString(",") { it.key }) return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("results"))
append("&type=&start=")
append(start) } else {
append("&end=") val url = buildString {
append(end) append("https://api.")
append(domain)
append("/misc/comic/search/query?status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "En cours"
MangaState.FINISHED -> "Terminé"
MangaState.ABANDONED -> "Abandonné"
MangaState.PAUSED -> "En pause"
else -> ""
},
)
}
append("&order=&genreNames=")
append(filter.tags.joinToString(",") { it.key })
append("&type=")
filter.types.forEach {
append(
when (it) {
ContentType.MANGA -> "Manga"
ContentType.MANHWA -> "Manhwa"
ContentType.MANHUA -> "Manhua"
ContentType.ONE_SHOT -> "One shot"
else -> ""
},
)
}
append("&start=")
append(start.toString())
append("&end=")
append(end.toString())
}
return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("comics"))
} }
return parseMangaList(webClient.httpGet(url).parseJson())
} }
} }
} }
private fun parseMangaList(json: JSONObject): List<Manga> { private fun parseMangaList(json: JSONArray): List<Manga> {
return json.getJSONArray("comics").mapJSON { j -> return json.mapJSON { j ->
val slug = j.getString("slug") val slug = j.getString("slug")
val urlManga = "https://$domain/comics/$slug" val urlManga = "https://$domain/comics/$slug"
Manga( Manga(
@ -134,17 +179,17 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
source = source, source = source,
) )
}, },
coverUrl = root.selectFirstOrThrow("div.serieImg img").attr("src"), coverUrl = root.selectFirst("div.serieImg img")?.attr("src").orEmpty(),
author = root.select("div.serieAdd p:contains(Auteur:) strong").text(), author = root.select("div.serieAdd p:contains(Auteur:) strong").text(),
description = root.selectFirst("div.serieDescription div")?.html(), description = root.selectFirst("div.serieDescription div")?.html(),
chapters = root.select("div.chapterList a") chapters = root.select("div.chapterList a")
.mapChapters(reversed = true) { i, a -> .mapChapters(reversed = true) { i, a ->
val href = a.attrAsRelativeUrl("href") val href = a.attrAsRelativeUrl("href")
val name = a.selectFirstOrThrow("span").text() val name = a.selectFirst("span")?.text()
val dateText = a.selectLast("span")?.text() ?: "0" val dateText = a.selectLast("span")?.text() ?: "0"
MangaChapter( MangaChapter(
id = generateUid(href), id = generateUid(href),
name = name, name = name ?: "Chapitre : ${i + 1f}",
number = i + 1f, number = i + 1f,
volume = 0, volume = 0,
url = href, url = href,

@ -18,15 +18,20 @@ import java.util.*
internal class LugnicaScans(context: MangaLoaderContext) : internal class LugnicaScans(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) { PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
@ -36,11 +41,6 @@ internal class LugnicaScans(context: MangaLoaderContext) :
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
init { init {
context.cookieJar.insertCookies( context.cookieJar.insertCookies(
domain, domain,

@ -14,25 +14,25 @@ import java.util.*
@MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr") @MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr")
internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) { internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) {
override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io") override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
) )
override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override fun getRequestHeaders(): Headers = Headers.Builder() override fun getRequestHeaders(): Headers = Headers.Builder()
.add("Accept-Language", "fr") .add("Accept-Language", "fr")
.build() .build()
@ -52,7 +52,7 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
else -> { else -> {
if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) { if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) {
throw IllegalArgumentException("Filtrer part tag n'est pas disponible avec le tri pas mis à jour") throw IllegalArgumentException("Filtrer par tag n'est pas avec le tri pas mis à jour")
} }
if (order == SortOrder.ALPHABETICAL) { if (order == SortOrder.ALPHABETICAL) {
@ -81,7 +81,7 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte
url = href, url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain), publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = (div.selectFirst("img")?.src() ?: a.attr("data-bg")).orEmpty(), coverUrl = (div.selectFirst("img")?.src() ?: a.attr("data-bg")).orEmpty(),
title = div.selectFirstOrThrow("h4, .media-thumbnail__name").text().orEmpty(), title = div.selectFirst("h4, .media-thumbnail__name")?.text().orEmpty(),
altTitle = null, altTitle = null,
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),

@ -23,20 +23,27 @@ import java.util.*
@MangaSourceParser("MANGAMANA", "MangaMana", "fr") @MangaSourceParser("MANGAMANA", "MangaMana", "fr")
internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) { internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
)
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of( EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.RATING, SortOrder.RATING,
SortOrder.RATING_ASC,
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC, SortOrder.ALPHABETICAL_DESC,
SortOrder.NEWEST, SortOrder.NEWEST,
) SortOrder.NEWEST_ASC,
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
@ -44,11 +51,6 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val postData = buildString { val postData = buildString {
append("page=") append("page=")
@ -97,7 +99,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
if (order == SortOrder.UPDATED) { if (order == SortOrder.UPDATED) {
if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) { if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) {
throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec les genres ou les statuts n'est pas pris en charge par cette source.") throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec d'autres n'est pas pris en charge par cette source.")
} }
val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml() val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml()
@ -146,7 +148,9 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
append("&sort_by=") append("&sort_by=")
when (order) { when (order) {
SortOrder.RATING -> append("score&sort_dir=desc") SortOrder.RATING -> append("score&sort_dir=desc")
SortOrder.RATING_ASC -> append("score&sort_dir=asc")
SortOrder.NEWEST -> append("updated_at&sort_dir=desc") SortOrder.NEWEST -> append("updated_at&sort_dir=desc")
SortOrder.NEWEST_ASC -> append("updated_at&sort_dir=asc")
SortOrder.ALPHABETICAL -> append("name&sort_dir=asc") SortOrder.ALPHABETICAL -> append("name&sort_dir=asc")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_dir=desc") SortOrder.ALPHABETICAL_DESC -> append("name&sort_dir=desc")
else -> append("updated_at&sort_dir=desc") else -> append("updated_at&sort_dir=desc")

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
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.SinglePageMangaParser import org.koitharu.kotatsu.parsers.SinglePageMangaParser
@ -12,6 +13,7 @@ import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@Broken
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr") @MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
internal class ScansMangasMe(context: MangaLoaderContext) : internal class ScansMangasMe(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) { SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {

@ -15,15 +15,20 @@ import java.util.*
internal class ScantradUnion(context: MangaLoaderContext) : internal class ScantradUnion(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) { PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
@ -33,11 +38,6 @@ internal class ScantradUnion(context: MangaLoaderContext) :
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -89,7 +89,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, isNsfw = false,
coverUrl = article.selectFirstOrThrow("img.attachment-thumbnail").attrAsAbsoluteUrl("src"), coverUrl = article.selectFirst("img.attachment-thumbnail")?.attrAsAbsoluteUrl("src").orEmpty(),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, author = null,
@ -109,7 +109,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
publicUrl = href.toAbsoluteUrl(domain), publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
isNsfw = false, isNsfw = false,
coverUrl = article.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), coverUrl = article.selectFirst("img")?.attrAsAbsoluteUrl("src").orEmpty(),
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, author = null,
@ -151,7 +151,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
} else { } else {
"Chapter $i" "Chapter $i"
} }
val date = li.select(".name-chapter").first()!!.children().elementAt(2).text() val date = li.select(".name-chapter").first()?.children()?.elementAt(2)?.text()
MangaChapter( MangaChapter(
id = generateUid(href), id = generateUid(href),
name = name, name = name,

@ -317,7 +317,7 @@ internal abstract class GroupleParser(
SortOrder.UPDATED -> "updated" SortOrder.UPDATED -> "updated"
SortOrder.ADDED, SortOrder.ADDED,
SortOrder.NEWEST, SortOrder.NEWEST,
-> "created" -> "created"
SortOrder.RATING -> "votes" SortOrder.RATING -> "votes"
else -> "rate" else -> "rate"

@ -79,7 +79,8 @@ public fun String.toRelativeUrl(domain: String): String {
public fun String.toAbsoluteUrl(domain: String): String = when { public fun String.toAbsoluteUrl(domain: String): String = when {
this.startsWith("//") -> "https:$this" this.startsWith("//") -> "https:$this"
this.startsWith('/') -> "https://$domain$this" this.startsWith('/') -> "https://$domain$this"
else -> this this.startsWith("https://") -> this
else -> "https://$domain/$this"
} }
public fun concatUrl(host: String, path: String): String { public fun concatUrl(host: String, path: String): String {

Loading…
Cancel
Save