[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()
This commit is contained in:
@@ -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(filter.query.urlEncoded())
|
||||||
|
}
|
||||||
|
|
||||||
append("&genre=")
|
// filter.author?.let {
|
||||||
append(filter.tags.joinToString(",") { it.key })
|
// append("&author=")
|
||||||
|
// append(filter.author.urlEncoded())
|
||||||
|
// }
|
||||||
|
|
||||||
append("&ungenre=")
|
append("&genre=")
|
||||||
append(filter.tagsExclude.joinToString(",") { it.key })
|
append(filter.tags.joinToString(",") { it.key })
|
||||||
|
|
||||||
|
append("&ungenre=")
|
||||||
|
append(filter.tagsExclude.joinToString(",") { it.key })
|
||||||
|
|
||||||
|
|
||||||
append("&sort=")
|
append("&sort=")
|
||||||
when (order) {
|
when (order) {
|
||||||
SortOrder.POPULARITY -> append("views&sort_type=DESC")
|
SortOrder.POPULARITY -> append("views&sort_type=DESC")
|
||||||
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
|
SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC")
|
||||||
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
|
SortOrder.UPDATED -> append("last_update&sort_type=DESC")
|
||||||
SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC")
|
SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC")
|
||||||
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
|
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
|
||||||
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
|
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
|
||||||
else -> append("last_update&sort_type=DESC")
|
else -> append("last_update&sort_type=DESC")
|
||||||
}
|
}
|
||||||
|
|
||||||
append("&m_status=")
|
append("&m_status=")
|
||||||
filter.states.oneOrThrowIfMany()?.let {
|
filter.states.oneOrThrowIfMany()?.let {
|
||||||
append(
|
append(
|
||||||
when (it) {
|
when (it) {
|
||||||
MangaState.ONGOING -> "2"
|
MangaState.ONGOING -> "2"
|
||||||
MangaState.FINISHED -> "1"
|
MangaState.FINISHED -> "1"
|
||||||
MangaState.ABANDONED -> "3"
|
MangaState.ABANDONED -> "3"
|
||||||
else -> ""
|
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()) {
|
if (filter.tags.isNotEmpty()) {
|
||||||
url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
|
url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.tagsExclude.isNotEmpty()) {
|
if (filter.tagsExclude.isNotEmpty()) {
|
||||||
url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key })
|
url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key })
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.states.oneOrThrowIfMany()?.let {
|
filter.states.oneOrThrowIfMany()?.let {
|
||||||
url.addQueryParameter(
|
url.addQueryParameter(
|
||||||
"state",
|
"state",
|
||||||
when (it) {
|
when (it) {
|
||||||
MangaState.ONGOING -> "1"
|
MangaState.ONGOING -> "1"
|
||||||
MangaState.FINISHED -> "2"
|
MangaState.FINISHED -> "2"
|
||||||
MangaState.PAUSED -> "3"
|
MangaState.PAUSED -> "3"
|
||||||
MangaState.ABANDONED -> "5"
|
MangaState.ABANDONED -> "5"
|
||||||
else -> "1"
|
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"
|
|
||||||
MangaState.FINISHED -> "Terminé"
|
|
||||||
MangaState.ABANDONED -> "Abandonné"
|
|
||||||
MangaState.PAUSED -> "En pause"
|
|
||||||
else -> ""
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
append("&order=&genreNames=")
|
|
||||||
append(filter.tags.joinToString(",") { it.key })
|
val url = buildString {
|
||||||
append("&type=&start=")
|
append("https://api.")
|
||||||
append(start)
|
append(domain)
|
||||||
append("&end=")
|
append("/misc/comic/home/updates?start=")
|
||||||
append(end)
|
append(start.toString())
|
||||||
|
append("&end=")
|
||||||
|
append(end.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("results"))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val url = buildString {
|
||||||
|
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,32 +23,34 @@ 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 availableSortOrders: Set<SortOrder> =
|
|
||||||
EnumSet.of(
|
|
||||||
SortOrder.UPDATED,
|
|
||||||
SortOrder.RATING,
|
|
||||||
SortOrder.ALPHABETICAL,
|
|
||||||
SortOrder.ALPHABETICAL_DESC,
|
|
||||||
SortOrder.NEWEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
|
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
|
override val filterCapabilities: MangaListFilterCapabilities
|
||||||
get() = MangaListFilterCapabilities(
|
get() = MangaListFilterCapabilities(
|
||||||
isSearchSupported = true,
|
isSearchSupported = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val availableSortOrders: Set<SortOrder> =
|
||||||
|
EnumSet.of(
|
||||||
|
SortOrder.UPDATED,
|
||||||
|
SortOrder.RATING,
|
||||||
|
SortOrder.RATING_ASC,
|
||||||
|
SortOrder.ALPHABETICAL,
|
||||||
|
SortOrder.ALPHABETICAL_DESC,
|
||||||
|
SortOrder.NEWEST,
|
||||||
|
SortOrder.NEWEST_ASC,
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||||
availableTags = fetchAvailableTags(),
|
availableTags = fetchAvailableTags(),
|
||||||
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user