[Liliana] Fix chapters order

This commit is contained in:
2024-10-13 15:26:58 +03:00
parent 1406165789
commit 0150ede4cb
2 changed files with 186 additions and 186 deletions

View File

@@ -1 +1 @@
total: 1121 total: 1122

View File

@@ -1,204 +1,204 @@
package org.koitharu.kotatsu.parsers.site.liliana package org.koitharu.kotatsu.parsers.site.liliana
import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.coroutineScope import org.jsoup.Jsoup
import org.json.JSONObject import org.jsoup.nodes.Element
import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.model.* import java.util.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import org.jsoup.Jsoup
import java.util.*
internal abstract class LilianaParser( internal abstract class LilianaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24 pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : PagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.NEWEST, SortOrder.NEWEST,
SortOrder.RATING_ASC SortOrder.RATING_ASC,
) )
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = true isSearchWithFiltersSupported = true,
) )
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://")
append(domain) append(domain)
when { when {
!filter.query.isNullOrEmpty() -> { !filter.query.isNullOrEmpty() -> {
append("/search") append("/search")
append("?keyword=") append("?keyword=")
append(filter.query.urlEncoded()) append(filter.query.urlEncoded())
} }
else -> {
append("/filter")
}
}
append("/")
append(page)
append("/")
when (order) {
SortOrder.UPDATED -> append("?sort=latest-updated")
SortOrder.POPULARITY -> append("?sort=views")
SortOrder.ALPHABETICAL -> append("?sort=az")
SortOrder.NEWEST -> append("?sort=new")
SortOrder.RATING_ASC -> append("?sort=score")
else -> append("?sort=default")
}
filter.tags.forEach { tag ->
append("&genres=")
append(tag.key)
}
if (filter.states.isNotEmpty()) {
append("&status=")
append(when (filter.states.first()) {
MangaState.ONGOING -> "on-going"
MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "on-hold"
MangaState.ABANDONED -> "canceled"
else -> "all"
})
}
}
val doc = webClient.httpGet(url).parseHtml() else -> {
return doc.select("div#main div.grid > div").map { parseSearchManga(it) } append("/filter")
} }
}
append("/")
append(page)
append("/")
private fun parseSearchManga(element: Element): Manga { when (order) {
val href = element.selectFirstOrThrow("a").attrAsRelativeUrl("href") SortOrder.UPDATED -> append("?sort=latest-updated")
return Manga( SortOrder.POPULARITY -> append("?sort=views")
id = generateUid(href), SortOrder.ALPHABETICAL -> append("?sort=az")
url = href, SortOrder.NEWEST -> append("?sort=new")
publicUrl = href.toAbsoluteUrl(domain), SortOrder.RATING_ASC -> append("?sort=score")
coverUrl = element.selectFirst("img")?.src().orEmpty(), else -> append("?sort=default")
title = element.selectFirst(".text-center a")?.text().orEmpty(), }
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = false,
)
}
override suspend fun getDetails(manga: Manga): Manga { filter.tags.forEach { tag ->
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() append("&genres=")
return manga.copy( append(tag.key)
description = doc.selectFirst("div#syn-target")?.text(), }
largeCoverUrl = doc.selectFirst(".a1 > figure img")?.src(),
tags = doc.select(".a2 div > a[rel='tag'].label").mapToSet { a ->
MangaTag(
key = a.attr("href").substringAfterLast('/'),
title = a.text().toTitleCase(sourceLocale),
source = source,
)
},
author = doc.selectFirst("div.y6x11p i.fas.fa-user + span.dt")?.text()?.takeUnless {
it.equals("updating", true)
},
state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase()) {
"on-going", "đang tiến hành", "進行中" -> MangaState.ONGOING
"completed", "hoàn thành", "完了" -> MangaState.FINISHED
"on-hold", "tạm dừng", "一時停止" -> MangaState.PAUSED
"canceled", "đã huỷ bỏ", "キャンセル" -> MangaState.ABANDONED
else -> null
},
chapters = doc.select("ul > li.chapter").mapChapters { i, element ->
val href = element.selectFirstOrThrow("a").attrAsRelativeUrl("href")
MangaChapter(
id = generateUid(href),
name = element.selectFirst("a")?.text().orEmpty(),
number = doc.select("ul > li.chapter").size - i.toFloat(),
url = href,
scanlator = null,
uploadDate = element.selectFirst("time[datetime]")?.attr("datetime")?.toLongOrNull()?.times(1000) ?: 0L,
branch = null,
source = source,
volume = 0
)
}
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { if (filter.states.isNotEmpty()) {
val fullUrl = chapter.url.toAbsoluteUrl(domain) append("&status=")
val doc = webClient.httpGet(fullUrl).parseHtml() append(
when (filter.states.first()) {
val script = doc.selectFirst("script:containsData(const CHAPTER_ID)")?.data() MangaState.ONGOING -> "on-going"
?: throw Exception("Failed to get chapter id") MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "on-hold"
MangaState.ABANDONED -> "canceled"
else -> "all"
},
)
}
}
val chapterId = script.substringAfter("const CHAPTER_ID = ").substringBefore(';') val doc = webClient.httpGet(url).parseHtml()
return doc.select("div#main div.grid > div").map { parseSearchManga(it) }
}
val ajaxUrl = buildString { private fun parseSearchManga(element: Element): Manga {
append("https://") val href = element.selectFirstOrThrow("a").attrAsRelativeUrl("href")
append(domain) return Manga(
append("/ajax/image/list/chap/") id = generateUid(href),
append(chapterId) url = href,
} publicUrl = href.toAbsoluteUrl(domain),
coverUrl = element.selectFirst("img")?.src().orEmpty(),
title = element.selectFirst(".text-center a")?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
state = null,
source = source,
isNsfw = false,
)
}
val responseJson = webClient.httpGet(ajaxUrl).parseJson() override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
return manga.copy(
description = doc.selectFirst("div#syn-target")?.text(),
largeCoverUrl = doc.selectFirst(".a1 > figure img")?.src(),
tags = doc.select(".a2 div > a[rel='tag'].label").mapToSet { a ->
MangaTag(
key = a.attr("href").substringAfterLast('/'),
title = a.text().toTitleCase(sourceLocale),
source = source,
)
},
author = doc.selectFirst("div.y6x11p i.fas.fa-user + span.dt")?.text()?.takeUnless {
it.equals("updating", true)
},
state = when (doc.selectFirst("div.y6x11p i.fas.fa-rss + span.dt")?.text()?.lowercase()) {
"on-going", "đang tiến hành", "進行中" -> MangaState.ONGOING
"completed", "hoàn thành", "完了" -> MangaState.FINISHED
"on-hold", "tạm dừng", "一時停止" -> MangaState.PAUSED
"canceled", "đã huỷ bỏ", "キャンセル" -> MangaState.ABANDONED
else -> null
},
chapters = doc.select("ul > li.chapter").mapChapters(reversed = true) { i, element ->
val href = element.selectFirstOrThrow("a").attrAsRelativeUrl("href")
MangaChapter(
id = generateUid(href),
name = element.selectFirst("a")?.text().orEmpty(),
number = i + 1f,
url = href,
scanlator = null,
uploadDate = element.selectFirst("time[datetime]")?.attr("datetime")?.toLongOrNull()?.times(1000)
?: 0L,
branch = null,
source = source,
volume = 0,
)
},
)
}
if (!responseJson.optBoolean("status", false)) { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
throw Exception(responseJson.optString("msg")) val fullUrl = chapter.url.toAbsoluteUrl(domain)
} val doc = webClient.httpGet(fullUrl).parseHtml()
val pageListHtml = responseJson.getString("html") val script = doc.selectFirst("script:containsData(const CHAPTER_ID)")?.data()
val pageListDoc = Jsoup.parse(pageListHtml) ?: throw Exception("Failed to get chapter id")
return pageListDoc.select("div.iv-card").mapIndexed { index, div -> val chapterId = script.substringAfter("const CHAPTER_ID = ").substringBefore(';')
val img = div.selectFirst("img")
val url = img?.attr("data-src") ?: img?.attr("src") ?: throw Exception("Failed to get image url")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
protected open suspend fun getAvailableTags(): Set<MangaTag> = coroutineScope { val ajaxUrl = buildString {
val doc = webClient.httpGet("https://$domain/filter").parseHtml() append("https://")
doc.select("div.advanced-genres > div > .advance-item").mapToSet { element -> append(domain)
MangaTag( append("/ajax/image/list/chap/")
key = element.selectFirstOrThrow("span[data-genre]").attr("data-genre"), append(chapterId)
title = element.text().toTitleCase(sourceLocale), }
source = source,
)
}
}
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions( val responseJson = webClient.httpGet(ajaxUrl).parseJson()
availableTags = getAvailableTags(),
availableStates = setOf(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) if (!responseJson.optBoolean("status", false)) {
) throw Exception(responseJson.optString("msg"))
} }
val pageListHtml = responseJson.getString("html")
val pageListDoc = Jsoup.parse(pageListHtml)
return pageListDoc.select("div.iv-card").mapIndexed { index, div ->
val img = div.selectFirst("img")
val url = img?.attr("data-src") ?: img?.attr("src") ?: throw Exception("Failed to get image url")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
protected open suspend fun getAvailableTags(): Set<MangaTag> = coroutineScope {
val doc = webClient.httpGet("https://$domain/filter").parseHtml()
doc.select("div.advanced-genres > div > .advance-item").mapToSet { element ->
MangaTag(
key = element.selectFirstOrThrow("span[data-genre]").attr("data-genre"),
title = element.text().toTitleCase(sourceLocale),
source = source,
)
}
}
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions(
availableTags = getAvailableTags(),
availableStates = setOf(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
)
}