[DesuMe] Genres in list

pull/154/head
Koitharu 3 years ago
parent 454b24ec88
commit f3b731114e
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site package org.koitharu.kotatsu.parsers.site
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -15,147 +16,165 @@ import java.util.*
@MangaSourceParser("DESUME", "Desu.me", "ru") @MangaSourceParser("DESUME", "Desu.me", "ru")
internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DESUME, 20) { internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DESUME, 20) {
override val configKeyDomain = ConfigKey.Domain("desu.me", null) override val configKeyDomain = ConfigKey.Domain("desu.me", null)
override val sortOrders: Set<SortOrder> = EnumSet.of( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
) )
override suspend fun getListPage( private val tagsCache = SuspendLazy(::fetchTags)
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (query != null && page != searchPaginator.firstPage) {
return emptyList()
}
val domain = domain
val url = buildString {
append("https://")
append(domain)
append("/manga/api/?limit=20&order=")
append(getSortKey(sortOrder))
append("&page=")
append(page)
if (!tags.isNullOrEmpty()) {
append("&genres=")
appendAll(tags, ",") { it.key }
}
if (query != null) {
append("&search=")
append(query)
}
}
val json = webClient.httpGet(url).parseJson().getJSONArray("response")
?: throw ParseException("Invalid response", url)
val total = json.length()
val list = ArrayList<Manga>(total)
for (i in 0 until total) {
val jo = json.getJSONObject(i)
val cover = jo.getJSONObject("image")
val id = jo.getLong("id")
list += Manga(
url = "/manga/api/$id",
publicUrl = jo.getString("url"),
source = MangaSource.DESUME,
title = jo.getString("russian"),
altTitle = jo.getString("name"),
coverUrl = cover.getString("preview"),
largeCoverUrl = cover.getString("original"),
state = when {
jo.getInt("ongoing") == 1 -> MangaState.ONGOING
else -> null
},
rating = jo.getDouble("score").toFloat().coerceIn(0f, 1f),
id = generateUid(id),
isNsfw = false,
tags = emptySet(),
author = null,
description = jo.getString("description"),
)
}
return list
}
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getListPage(
val url = manga.url.toAbsoluteUrl(domain) page: Int,
val json = webClient.httpGet(url).parseJson().getJSONObject("response") query: String?,
?: throw ParseException("Invalid response", url) tags: Set<MangaTag>?,
val baseChapterUrl = manga.url + "/chapter/" sortOrder: SortOrder,
val chaptersList = json.getJSONObject("chapters").getJSONArray("list") ): List<Manga> {
val totalChapters = chaptersList.length() if (query != null && page != searchPaginator.firstPage) {
return manga.copy( return emptyList()
tags = json.getJSONArray("genres").mapJSONToSet { }
MangaTag( val domain = domain
key = it.getString("text"), val url = buildString {
title = it.getString("russian").toTitleCase(), append("https://")
source = manga.source, append(domain)
) append("/manga/api/?limit=20&order=")
}, append(getSortKey(sortOrder))
publicUrl = json.getString("url"), append("&page=")
description = json.getString("description"), append(page)
chapters = chaptersList.mapJSONIndexed { i, it -> if (!tags.isNullOrEmpty()) {
val chid = it.getLong("id") append("&genres=")
val volChap = "Том " + it.optString("vol", "0") + ". " + "Глава " + it.optString("ch", "0") appendAll(tags, ",") { it.key }
val title = it.optString("title", "null").takeUnless { it == "null" } }
MangaChapter( if (query != null) {
id = generateUid(chid), append("&search=")
source = manga.source, append(query)
url = "$baseChapterUrl$chid", }
uploadDate = it.getLong("date") * 1000, }
name = if (title.isNullOrEmpty()) volChap else "$volChap: $title", val json = webClient.httpGet(url).parseJson().getJSONArray("response")
number = totalChapters - i, ?: throw ParseException("Invalid response", url)
scanlator = null, val total = json.length()
branch = null, val list = ArrayList<Manga>(total)
) val tagsMap = tagsCache.tryGet().getOrNull()
}.reversed(), for (i in 0 until total) {
) val jo = json.getJSONObject(i)
} val cover = jo.getJSONObject("image")
val id = jo.getLong("id")
val genres = jo.getString("genres").split(',')
list += Manga(
url = "/manga/api/$id",
publicUrl = jo.getString("url"),
source = MangaSource.DESUME,
title = jo.getString("russian"),
altTitle = jo.getString("name"),
coverUrl = cover.getString("preview"),
largeCoverUrl = cover.getString("original"),
state = when {
jo.getInt("ongoing") == 1 -> MangaState.ONGOING
else -> null
},
rating = jo.getDouble("score").toFloat().coerceIn(0f, 1f),
id = generateUid(id),
isNsfw = false,
tags = if (!tagsMap.isNullOrEmpty()) {
genres.mapNotNullToSet { g ->
tagsMap[g.trim().toTitleCase()]
}
} else {
emptySet()
},
author = null,
description = jo.getString("description"),
)
}
return list
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getDetails(manga: Manga): Manga {
val fullUrl = chapter.url.toAbsoluteUrl(domain) val url = manga.url.toAbsoluteUrl(domain)
val json = webClient.httpGet(fullUrl) val json = webClient.httpGet(url).parseJson().getJSONObject("response")
.parseJson() ?: throw ParseException("Invalid response", url)
.getJSONObject("response") ?: throw ParseException("Invalid response", fullUrl) val baseChapterUrl = manga.url + "/chapter/"
return json.getJSONObject("pages").getJSONArray("list").mapJSON { jo -> val chaptersList = json.getJSONObject("chapters").getJSONArray("list")
MangaPage( val totalChapters = chaptersList.length()
id = generateUid(jo.getLong("id")), return manga.copy(
referer = fullUrl, tags = json.getJSONArray("genres").mapJSONToSet {
preview = null, MangaTag(
source = chapter.source, key = it.getString("text"),
url = jo.getString("img"), title = it.getString("russian").toTitleCase(),
) source = manga.source,
} )
} },
publicUrl = json.getString("url"),
description = json.getString("description"),
chapters = chaptersList.mapJSONIndexed { i, it ->
val chid = it.getLong("id")
val volChap = "Том " + it.optString("vol", "0") + ". " + "Глава " + it.optString("ch", "0")
val title = it.optString("title", "null").takeUnless { it == "null" }
MangaChapter(
id = generateUid(chid),
source = manga.source,
url = "$baseChapterUrl$chid",
uploadDate = it.getLong("date") * 1000,
name = if (title.isNullOrEmpty()) volChap else "$volChap: $title",
number = totalChapters - i,
scanlator = null,
branch = null,
)
}.reversed(),
)
}
override suspend fun getTags(): Set<MangaTag> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = webClient.httpGet("https://${domain}/manga/").parseHtml() val fullUrl = chapter.url.toAbsoluteUrl(domain)
val root = doc.body().requireElementById("animeFilter") val json = webClient.httpGet(fullUrl)
.selectFirstOrThrow(".catalog-genres") .parseJson()
return root.select("li").mapToSet { .getJSONObject("response") ?: throw ParseException("Invalid response", fullUrl)
val input = it.selectFirstOrThrow("input") return json.getJSONObject("pages").getJSONArray("list").mapJSON { jo ->
MangaTag( MangaPage(
source = source, id = generateUid(jo.getLong("id")),
key = input.attr("data-genre-slug").ifEmpty { referer = fullUrl,
it.parseFailed("data-genre-slug is empty") preview = null,
}, source = chapter.source,
title = input.attr("data-genre-name").toTitleCase().ifEmpty { url = jo.getString("img"),
it.parseFailed("data-genre-name is empty") )
}, }
) }
}
}
private fun getSortKey(sortOrder: SortOrder) = override suspend fun getTags(): Set<MangaTag> {
when (sortOrder) { return tagsCache.get().values.toSet()
SortOrder.ALPHABETICAL -> "name" }
SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "updated" private fun getSortKey(sortOrder: SortOrder) =
SortOrder.NEWEST -> "id" when (sortOrder) {
else -> "updated" SortOrder.ALPHABETICAL -> "name"
} SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "updated"
SortOrder.NEWEST -> "id"
else -> "updated"
}
private suspend fun fetchTags(): Map<String, MangaTag> {
val doc = webClient.httpGet("https://${domain}/manga/").parseHtml()
val root = doc.body().requireElementById("animeFilter")
.selectFirstOrThrow(".catalog-genres")
val li = root.select("li")
val result = ArrayMap<String, MangaTag>(li.size)
li.forEach {
val input = it.selectFirstOrThrow("input")
val tag = MangaTag(
source = source,
key = input.attr("data-genre-slug").ifEmpty {
it.parseFailed("data-genre-slug is empty")
},
title = input.attr("data-genre-name").toTitleCase().ifEmpty {
it.parseFailed("data-genre-name is empty")
},
)
result[tag.title] = tag
}
return result
}
} }

@ -0,0 +1,36 @@
package org.koitharu.kotatsu.parsers.util
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class SuspendLazy<T>(
private val initializer: suspend () -> T,
) {
private val mutex = Mutex()
private var cachedValue: Any? = Uninitialized
@Suppress("UNCHECKED_CAST")
suspend fun get(): T {
// fast way
cachedValue.let {
if (it !== Uninitialized) {
return it as T
}
}
return mutex.withLock {
cachedValue.let {
if (it !== Uninitialized) {
return it as T
}
}
val result = initializer()
cachedValue = result
result
}
}
suspend fun tryGet() = runCatchingCancellable { get() }
private object Uninitialized
}
Loading…
Cancel
Save