Refactoring

master
Koitharu 2 years ago
parent 7ce2a97c1f
commit 166c5be5d6
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -10,7 +10,6 @@ import com.google.devtools.ksp.validate
import java.io.File import java.io.File
import java.io.Writer import java.io.Writer
import java.util.* import java.util.*
import kotlin.math.log
class ParserProcessor( class ParserProcessor(
private val codeGenerator: CodeGenerator, private val codeGenerator: CodeGenerator,
@ -76,7 +75,6 @@ class ParserProcessor(
""".trimIndent(), """.trimIndent(),
) )
//language=kotlin
sourcesWriter?.write( sourcesWriter?.write(
""" """
package org.koitharu.kotatsu.parsers.model package org.koitharu.kotatsu.parsers.model

@ -3,11 +3,11 @@ package org.koitharu.kotatsu.parsers.exception
import okio.IOException import okio.IOException
import org.json.JSONArray import org.json.JSONArray
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
public class GraphQLException @InternalParsersApi constructor(private val errors: JSONArray) : IOException() { public class GraphQLException @InternalParsersApi constructor(errors: JSONArray) : IOException() {
public val messages = errors.mapJSON { public val messages: List<String> = errors.mapJSONNotNull {
it.getString("message") it.getString("message")
} }

@ -410,9 +410,7 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
for (i in 0.until(numberOfKeys)) { for (i in 0.until(numberOfKeys)) {
val keySize = buffer.int val keySize = buffer.int
if (keySize == 0 || keySize > 32) { check(keySize in 1..32) { "Invalid key size $keySize" }
throw Exception("fatal: !keySize || keySize > 32")
}
keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize))) keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize)))
buffer.position(buffer.position() + keySize) buffer.position(buffer.position() + keySize)
@ -482,14 +480,14 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
title = doc.selectFirstOrThrow("h1").text(), title = doc.selectFirstOrThrow("h1").text(),
url = id.toString(), url = id.toString(),
coverUrl = coverUrl =
"https:" + "https:" +
doc.selectFirstOrThrow("picture > source") doc.selectFirstOrThrow("picture > source")
.attr("data-srcset") .attr("data-srcset")
.substringBefore(" "), .substringBefore(" "),
publicUrl = publicUrl =
doc.selectFirstOrThrow("h1 > a") doc.selectFirstOrThrow("h1 > a")
.attrAsRelativeUrl("href") .attrAsRelativeUrl("href")
.toAbsoluteUrl(domain), .toAbsoluteUrl(domain),
author = null, author = null,
tags = emptySet(), tags = emptySet(),
isNsfw = true, isNsfw = true,
@ -512,37 +510,37 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
return manga.copy( return manga.copy(
title = json.getString("title"), title = json.getString("title"),
largeCoverUrl = largeCoverUrl =
json.getJSONArray("files").getJSONObject(0).let { json.getJSONArray("files").getJSONObject(0).let {
val hash = it.getString("hash") val hash = it.getString("hash")
val commonId = commonImageId() val commonId = commonImageId()
val imageId = imageIdFromHash(hash) val imageId = imageIdFromHash(hash)
val subDomain = 'a' + subdomainOffset(imageId) val subDomain = 'a' + subdomainOffset(imageId)
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp" "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
}, },
author = author =
json.optJSONArray("artists") json.optJSONArray("artists")
?.mapJSON { it.getString("artist").toCamelCase() } ?.mapJSON { it.getString("artist").toCamelCase() }
?.joinToString(), ?.joinToString(),
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain), publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
tags = tags =
buildSet { buildSet {
json.optJSONArray("characters") json.optJSONArray("characters")
?.mapToTags("character") ?.mapToTags("character")
?.let(::addAll) ?.let(::addAll)
json.optJSONArray("tags") json.optJSONArray("tags")
?.mapToTags("tag") ?.mapToTags("tag")
?.let(::addAll) ?.let(::addAll)
json.optJSONArray("artists") json.optJSONArray("artists")
?.mapToTags("artist") ?.mapToTags("artist")
?.let(::addAll) ?.let(::addAll)
json.optJSONArray("parodys") json.optJSONArray("parodys")
?.mapToTags("parody") ?.mapToTags("parody")
?.let(::addAll) ?.let(::addAll)
json.optJSONArray("groups") json.optJSONArray("groups")
?.mapToTags("group") ?.mapToTags("group")
?.let(::addAll) ?.let(::addAll)
}, },
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = generateUid(manga.url), id = generateUid(manga.url),
@ -566,15 +564,15 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
mapJSON { mapJSON {
MangaTag( MangaTag(
title = title =
it.getString(key).toCamelCase().let { title -> it.getString(key).toCamelCase().let { title ->
if (it.getStringOrNull("female")?.toIntOrNull() == 1) { if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
"$title" "$title"
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) { } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
"$title" "$title"
} else { } else {
title title
} }
}, },
key = it.getString("url").tagUrlToTag(), key = it.getString("url").tagUrlToTag(),
source = source, source = source,
).let(tags::add) ).let(tags::add)
@ -681,11 +679,12 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
return hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1") return hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1")
} }
private suspend fun subdomainFromURL(url: String, base: String? = null): String { private suspend fun subdomainFromURL(url: String, base: String?): String {
var retval = "b" var retval = "b"
if (!base.isNullOrBlank()) if (!base.isNullOrBlank()) {
retval = base retval = base
}
val regex = Regex("""/[0-9a-f]{61}([0-9a-f]{2})([0-9a-f])""") val regex = Regex("""/[0-9a-f]{61}([0-9a-f]{2})([0-9a-f])""")
val hashMatch = regex.find(url) ?: return "a" val hashMatch = regex.find(url) ?: return "a"

@ -20,7 +20,7 @@ import java.util.*
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI) @MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
internal class NineNineNineHentaiParser(context: MangaLoaderContext) : internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, size), Interceptor { PagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, PAGE_SIZE), Interceptor {
override val configKeyDomain = ConfigKey.Domain("animeh.to") override val configKeyDomain = ConfigKey.Domain("animeh.to")
@ -126,7 +126,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
): List<Manga> { ): List<Manga> {
val query = """ val query = """
queryPopularChapters( queryPopularChapters(
size: $size size: $PAGE_SIZE
language: "${locale.getSiteLang()}" language: "${locale.getSiteLang()}"
dateRange: 1 dateRange: 1
page: $page page: $page
@ -167,7 +167,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
} }
val query = """ val query = """
queryChapters( queryChapters(
limit: $size limit: $PAGE_SIZE
search: {$searchPayload} search: {$searchPayload}
page: $page page: $page
) { ) {
@ -322,7 +322,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
_id: "${seed.url}" _id: "${seed.url}"
search: {sortBy:Popular} search: {sortBy:Popular}
page: 1 page: 1
size: $size size: $PAGE_SIZE
) { ) {
chapters { chapters {
_id _id
@ -384,7 +384,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
} }
companion object { companion object {
private const val size = 20 private const val PAGE_SIZE = 20
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""") private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
} }

@ -13,6 +13,7 @@ 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
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
@ -61,9 +62,10 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
return emptyList() return emptyList()
} }
val domainCdn = "cdn" + domain.removePrefix("www") val domainCdn = "cdn" + domain.removePrefix("www")
val json = webClient.httpGet("https://$domain/search-live?q=${filter.query}").parseJsonArray() val url = "https://$domain/search-live?q=${filter.query.urlEncoded()}"
val json = webClient.httpGet(url).parseJsonArray()
return json.mapJSON { jo -> return json.mapJSON { jo ->
val slug = jo.getString("slug") ?: throw Exception("Missing Slug") val slug = jo.getString("slug") ?: throw ParseException("Missing Slug", url)
val url = "https://$domain/m/$slug" val url = "https://$domain/m/$slug"
val img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg" val img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg"
Manga( Manga(

@ -80,14 +80,12 @@ internal abstract class Manga18Parser(
} }
} }
if (filter.query != null) { if (!filter.query.isNullOrEmpty()) {
filter.query.let { append(listUrl)
append(listUrl) append(page.toString())
append(page.toString()) append("?search=")
append("?search=") append(filter.query.urlEncoded())
append(filter.query.urlEncoded()) append("&order_by=latest")
append("&order_by=latest")
}
} }
append("?order_by=") append("?order_by=")

@ -136,7 +136,7 @@ internal class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val docs = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() val docs = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
return docs.select("#image > img").map { return docs.select("#image > img").map {
val pageUrl = it.src() ?: throw Exception(it.html()) val pageUrl = it.src() ?: it.parseFailed("Image src not found")
MangaPage( MangaPage(
id = generateUid(pageUrl), id = generateUid(pageUrl),
url = pageUrl, url = pageUrl,

@ -1,181 +1,184 @@
package org.koitharu.kotatsu.parsers.site.vi package org.koitharu.kotatsu.parsers.site.vi
import java.util.concurrent.atomic.AtomicReference
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
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("SAYHENTAI", "SayHentai", "vi", ContentType.HENTAI) @MangaSourceParser("SAYHENTAI", "SayHentai", "vi", ContentType.HENTAI)
internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) { internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) {
override val configKeyDomain = ConfigKey.Domain("sayhentai.one") override val configKeyDomain = ConfigKey.Domain("sayhentai.one")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
private val tagsCache = AtomicReference<Set<MangaTag>?>(null) override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
override val availableSortOrders: Set<SortOrder> = EnumSet.of( SortOrder.POPULARITY,
SortOrder.UPDATED, SortOrder.ALPHABETICAL,
SortOrder.POPULARITY, SortOrder.RATING,
SortOrder.ALPHABETICAL, )
SortOrder.RATING
) override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
override val filterCapabilities: MangaListFilterCapabilities isSearchSupported = true,
get() = MangaListFilterCapabilities( )
isSearchSupported = true,
) override suspend fun getFilterOptions(): MangaListFilterOptions {
return MangaListFilterOptions(
override suspend fun getFilterOptions(): MangaListFilterOptions { availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
return MangaListFilterOptions( availableTags = fetchTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), )
availableTags = fetchTags(), }
)
} override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { append("https://")
val url = buildString { append(domain)
append("https://") if (!filter.query.isNullOrEmpty()) {
append(domain) append("/search?s=")
if (!filter.query.isNullOrEmpty()) { append(filter.query.urlEncoded())
append("/search?s=") append("&page=")
append(filter.query.urlEncoded()) append(page.toString())
append("&page=") } else {
append(page.toString()) if (filter.tags.isNotEmpty()) {
} else { append("/genre/")
if (filter.tags.isNotEmpty()) { append(filter.tags.first().key)
append("/genre/") append("/")
append(filter.tags.first().key) } else {
append("/") append("/")
} else { }
append("/") append("?page=")
} append(page.toString())
append("?page=") val sortQuery = getSortOrderQuery(order, filter.tags.isNotEmpty())
append(page.toString()) if (sortQuery.isNotEmpty()) {
val sortQuery = getSortOrderQuery(order, filter.tags.isNotEmpty()) append("&")
if (sortQuery.isNotEmpty()) { append(sortQuery)
append("&") }
append(sortQuery) }
} }
}
} val doc = webClient.httpGet(url).parseHtml()
return doc.select(".page-item-detail").mapNotNull { element ->
val doc = webClient.httpGet(url).parseHtml() val href = element.selectFirst(".item-summary a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null
return doc.select(".page-item-detail").mapNotNull { element -> Manga(
val href = element.selectFirst(".item-summary a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null id = generateUid(href),
Manga( url = href,
id = generateUid(href), publicUrl = href.toAbsoluteUrl(domain),
url = href, title = element.selectFirst(".item-summary a")?.text().orEmpty(),
publicUrl = href.toAbsoluteUrl(domain), coverUrl = element.selectFirst(".item-thumb img")?.src().orEmpty(),
title = element.selectFirst(".item-summary a")?.text().orEmpty(), altTitle = null,
coverUrl = element.selectFirst(".item-thumb img")?.src().orEmpty(), rating = RATING_UNKNOWN,
altTitle = null, tags = emptySet(),
rating = RATING_UNKNOWN, author = null,
tags = emptySet(), state = null,
author = null, source = source,
state = null, isNsfw = isNsfwSource,
source = source, )
isNsfw = isNsfwSource }
) }
}
} override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
override suspend fun getDetails(manga: Manga): Manga { return manga.copy(
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() altTitle = doc.selectFirst("h2.other-name")?.text(),
return manga.copy( author = doc.selectFirst("div.summary-heading:contains(Tác giả) + div.summary-content")?.text(),
altTitle = doc.selectFirst("h2.other-name")?.text(), tags = doc.select("div.genres-content a[rel=tag]").mapToSet { a ->
author = doc.selectFirst("div.summary-heading:contains(Tác giả) + div.summary-content")?.text(), MangaTag(
tags = doc.select("div.genres-content a[rel=tag]").mapToSet { a -> key = a.attr("href").substringAfterLast('/'),
MangaTag( title = a.text().toTitleCase(sourceLocale),
key = a.attr("href").substringAfterLast('/'), source = source,
title = a.text().toTitleCase(sourceLocale), )
source = source },
) description = doc.selectFirst("div.summary__content")?.html(),
}, state = when (doc.selectFirst("div.summary-heading:contains(Trạng thái) + div.summary-content")?.text()
description = doc.selectFirst("div.summary__content")?.html(), ?.lowercase()) {
state = when (doc.selectFirst("div.summary-heading:contains(Trạng thái) + div.summary-content")?.text()?.lowercase()) { "đang ra" -> MangaState.ONGOING
"đang ra" -> MangaState.ONGOING "hoàn thành" -> MangaState.FINISHED
"hoàn thành" -> MangaState.FINISHED else -> null
else -> null },
}, chapters = doc.select("li.wp-manga-chapter").mapChapters(reversed = true) { i, element ->
chapters = doc.select("li.wp-manga-chapter").mapChapters(reversed = true) { i, element -> val a = element.selectFirst("a") ?: return@mapChapters null
val a = element.selectFirst("a") ?: return@mapChapters null MangaChapter(
MangaChapter( id = generateUid(a.attrAsRelativeUrl("href")),
id = generateUid(a.attrAsRelativeUrl("href")), name = a.text(),
name = a.text(), number = i + 1f,
number = i + 1f, url = a.attrAsRelativeUrl("href"),
url = a.attrAsRelativeUrl("href"), uploadDate = parseChapterDate(element.selectFirst("span.chapter-release-date")?.text()),
uploadDate = parseChapterDate(element.selectFirst("span.chapter-release-date")?.text()), branch = null,
branch = null, scanlator = null,
scanlator = null, source = source,
source = source, volume = 0,
volume = 0 )
) },
} )
) }
}
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() return doc.selectOrThrow("div.page-break img").mapIndexed { i, img ->
return doc.select("div.page-break img").mapIndexed { i, img -> val url = img.src().orEmpty()
val url = img.src().orEmpty() MangaPage(
MangaPage( id = generateUid(url),
id = generateUid(url), url = url,
url = url, preview = null,
preview = null, source = source,
source = source )
) }
} }
}
private fun parseChapterDate(date: String?): Long {
private fun parseChapterDate(date: String?): Long { if (date == null) return 0
if (date == null) return 0 return when {
return when { date.contains("giây trước") -> System.currentTimeMillis() - date.removeSuffix(" giây trước").toLong() * 1000
date.contains("giây trước") -> System.currentTimeMillis() - date.removeSuffix(" giây trước").toLong() * 1000 date.contains("phút trước") -> System.currentTimeMillis() - date.removeSuffix(" phút trước")
date.contains("phút trước") -> System.currentTimeMillis() - date.removeSuffix(" phút trước").toLong() * 60 * 1000 .toLong() * 60 * 1000
date.contains("giờ trước") -> System.currentTimeMillis() - date.removeSuffix(" giờ trước").toLong() * 60 * 60 * 1000
date.contains("ngày trước") -> System.currentTimeMillis() - date.removeSuffix(" ngày trước").toLong() * 24 * 60 * 60 * 1000 date.contains("giờ trước") -> System.currentTimeMillis() - date.removeSuffix(" giờ trước")
date.contains("tuần trước") -> System.currentTimeMillis() - date.removeSuffix(" tuần trước").toLong() * 7 * 24 * 60 * 60 * 1000 .toLong() * 60 * 60 * 1000
date.contains("tháng trước") -> System.currentTimeMillis() - date.removeSuffix(" tháng trước").toLong() * 30 * 24 * 60 * 60 * 1000
date.contains("năm trước") -> System.currentTimeMillis() - date.removeSuffix(" năm trước").toLong() * 365 * 24 * 60 * 60 * 1000 date.contains("ngày trước") -> System.currentTimeMillis() - date.removeSuffix(" ngày trước")
else -> SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L .toLong() * 24 * 60 * 60 * 1000
}
} date.contains("tuần trước") -> System.currentTimeMillis() - date.removeSuffix(" tuần trước")
.toLong() * 7 * 24 * 60 * 60 * 1000
private fun getSortOrderQuery(order: SortOrder, hasTags: Boolean): String {
if (!hasTags) return "" date.contains("tháng trước") -> System.currentTimeMillis() - date.removeSuffix(" tháng trước")
return when (order) { .toLong() * 30 * 24 * 60 * 60 * 1000
SortOrder.UPDATED -> "m_orderby=latest"
SortOrder.POPULARITY -> "m_orderby=views" date.contains("năm trước") -> System.currentTimeMillis() - date.removeSuffix(" năm trước")
SortOrder.ALPHABETICAL -> "m_orderby=alphabet" .toLong() * 365 * 24 * 60 * 60 * 1000
SortOrder.RATING -> "m_orderby=rating"
else -> "m_orderby=latest" else -> SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L
} }
} }
private suspend fun fetchTags(): Set<MangaTag> { private fun getSortOrderQuery(order: SortOrder, hasTags: Boolean): String {
return tagsCache.get() ?: run { if (!hasTags) return ""
val tags = webClient.httpGet("https://$domain/genre").parseHtml() return when (order) {
.select("ul.page-genres li a") SortOrder.UPDATED -> "m_orderby=latest"
.mapToSet { a -> SortOrder.POPULARITY -> "m_orderby=views"
val title = a.ownText().toTitleCase(sourceLocale) SortOrder.ALPHABETICAL -> "m_orderby=alphabet"
MangaTag( SortOrder.RATING -> "m_orderby=rating"
key = a.attr("href").substringAfterLast("/"), else -> "m_orderby=latest"
title = title, }
source = source }
)
} private suspend fun fetchTags(): Set<MangaTag> = webClient.httpGet("https://$domain/genre").parseHtml()
tagsCache.set(tags) .select("ul.page-genres li a")
tags .mapToSet { a ->
} val title = a.ownText().toTitleCase(sourceLocale)
} MangaTag(
key = a.attr("href").substringAfterLast("/"),
title = title,
source = source,
)
}
} }

@ -248,7 +248,7 @@ internal abstract class ZeistMangaParser(
chapterRegex chapterRegex
.find(script.html()) .find(script.html())
?.groupValues?.get(1) ?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed") ?: doc.parseFailed("Failed to find chapter feed")
} else if (doc.selectFirst("#clwd > script") != null) { } else if (doc.selectFirst("#clwd > script") != null) {
val chapterRegex = """clwd\.run\('([^']+)'""".toRegex() val chapterRegex = """clwd\.run\('([^']+)'""".toRegex()
@ -257,7 +257,7 @@ internal abstract class ZeistMangaParser(
chapterRegex chapterRegex
.find(script.html()) .find(script.html())
?.groupValues?.get(1) ?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed") ?: doc.parseFailed("Failed to find chapter feed")
} else if (doc.selectFirst("#chapterlist") != null) { } else if (doc.selectFirst("#chapterlist") != null) {
doc.selectFirstOrThrow("#chapterlist").attr("data-post-title") doc.selectFirstOrThrow("#chapterlist").attr("data-post-title")

@ -11,14 +11,14 @@ public fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
addAll(subject) addAll(subject)
} }
fun <T, C : MutableCollection<in T>> Iterable<Iterable<T>>.flattenTo(destination: C): C { public fun <T, C : MutableCollection<in T>> Iterable<Iterable<T>>.flattenTo(destination: C): C {
for (element in this) { for (element in this) {
destination.addAll(element) destination.addAll(element)
} }
return destination return destination
} }
fun <T> List<T>.medianOrNull(): T? = when { public fun <T> List<T>.medianOrNull(): T? = when {
isEmpty() -> null isEmpty() -> null
else -> get((size / 2).coerceIn(indices)) else -> get((size / 2).coerceIn(indices))
} }
@ -27,7 +27,7 @@ public inline fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
return mapTo(ArraySet(size), transform) return mapTo(ArraySet(size), transform)
} }
inline fun <T, R> Collection<T>.mapNotNullToSet(transform: (T) -> R?): Set<R> { public inline fun <T, R> Collection<T>.mapNotNullToSet(transform: (T) -> R?): Set<R> {
val destination = ArraySet<R>(size) val destination = ArraySet<R>(size)
for (item in this) { for (item in this) {
destination.add(transform(item) ?: continue) destination.add(transform(item) ?: continue)
@ -39,7 +39,7 @@ public inline fun <T, reified R> Array<T>.mapToArray(transform: (T) -> R): Array
transform(get(i)) transform(get(i))
} }
fun <K, V> List<Pair<K, V>>.toMutableMap(): MutableMap<K, V> = toMap(ArrayMap(size)) public fun <K, V> List<Pair<K, V>>.toMutableMap(): MutableMap<K, V> = toMap(ArrayMap(size))
public fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) { public fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) {
if (sourceIndex <= targetIndex) { if (sourceIndex <= targetIndex) {
@ -49,23 +49,9 @@ public fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) {
} }
} }
inline fun <T> List<T>.areItemsEquals(other: List<T>, equals: (T, T) -> Boolean): Boolean { public fun <T> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
if (size != other.size) {
return false
}
for (i in indices) {
val a = this[i]
val b = other[i]
if (!equals(a, b)) {
return false
}
}
return true
}
fun <T> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
inline fun <T, K, V> Collection<T>.associateGrouping(transform: (T) -> Pair<K, V>): Map<K, Set<V>> { public inline fun <T, K, V> Collection<T>.associateGrouping(transform: (T) -> Pair<K, V>): Map<K, Set<V>> {
val result = LinkedHashMap<K, MutableSet<V>>(size) val result = LinkedHashMap<K, MutableSet<V>>(size)
for (item in this) { for (item in this) {
val (k, v) = transform(item) val (k, v) = transform(item)
@ -74,7 +60,7 @@ inline fun <T, K, V> Collection<T>.associateGrouping(transform: (T) -> Pair<K, V
return result return result
} }
fun <K> MutableMap<K, Int>.incrementAndGet(key: K): Int { public fun <K> MutableMap<K, Int>.incrementAndGet(key: K): Int {
var value = get(key) ?: 0 var value = get(key) ?: 0
value++ value++
put(key, value) put(key, value)

@ -3,11 +3,9 @@ package org.koitharu.kotatsu.parsers.util
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import java.security.MessageDigest import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.Throws
private const val HASH_CIPHER = "AES/CBC/PKCS7PADDING" private const val HASH_CIPHER = "AES/CBC/PKCS7PADDING"
private const val AES = "AES" private const val AES = "AES"
@ -32,8 +30,8 @@ public class CryptoAES(
@Throws(Exception::class) @Throws(Exception::class)
public fun decrypt(cipherText: String, password: String): String { public fun decrypt(cipherText: String, password: String): String {
val ctBytes = context.decodeBase64(cipherText) val ctBytes = context.decodeBase64(cipherText)
val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) val saltBytes = ctBytes.copyOfRange(8, 16)
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) val cipherTextBytes = ctBytes.copyOfRange(16, ctBytes.size)
val md5: MessageDigest = MessageDigest.getInstance(KDF_DIGEST) val md5: MessageDigest = MessageDigest.getInstance(KDF_DIGEST)
val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5) val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5)
return decryptAES( return decryptAES(

@ -78,19 +78,9 @@ public class FaviconParser(
) )
} }
private fun String.resolveLink(): String { private fun String.resolveLink(): String = when {
return when { startsWith("http:") || startsWith("https:") -> this
startsWith("http:") || startsWith("https:") -> { startsWith('/') -> "https://$domain$this"
this else -> "https://$domain/$this"
}
startsWith('/') -> {
"https://$domain$this"
}
else -> {
"https://$domain/$this"
}
}
} }
} }

@ -10,7 +10,7 @@ import org.jsoup.select.Selector
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
val Element.host: String? public val Element.host: String?
get() { get() {
val uri = baseUri() val uri = baseUri()
return if (uri.isEmpty()) { return if (uri.isEmpty()) {
@ -24,14 +24,14 @@ val Element.host: String?
* Return an attribute value or null if it is missing or empty * Return an attribute value or null if it is missing or empty
* @see [Element.attr] which returns empty string instead of null * @see [Element.attr] which returns empty string instead of null
*/ */
fun Element.attrOrNull(attributeKey: String) = attr(attributeKey).takeUnless { it.isBlank() }?.trim() public fun Element.attrOrNull(attributeKey: String) = attr(attributeKey).takeUnless { it.isBlank() }?.trim()
/** /**
* Return an attribute value or throw an exception if it is missing * Return an attribute value or throw an exception if it is missing
* @see [Element.attr] which returns empty string instead * @see [Element.attr] which returns empty string instead
*/ */
fun Element.attrOrThrow(attributeKey: String): String = if (hasAttr(attributeKey)) { public fun Element.attrOrThrow(attributeKey: String): String = if (hasAttr(attributeKey)) {
attr(attributeKey) attr(attributeKey)
} else { } else {
throw ParseException("Attribute \"$attributeKey\" is missing at element \"$this\"", baseUri()) throw ParseException("Attribute \"$attributeKey\" is missing at element \"$this\"", baseUri())
@ -43,7 +43,7 @@ fun Element.attrOrThrow(attributeKey: String): String = if (hasAttr(attributeKey
* @see attrAsAbsoluteUrlOrNull * @see attrAsAbsoluteUrlOrNull
* @see attrAsAbsoluteUrl * @see attrAsAbsoluteUrl
*/ */
fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? { public fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? {
val attr = attrOrNull(attributeKey) ?: return null val attr = attrOrNull(attributeKey) ?: return null
if (attr.isEmpty() || attr.startsWith("data:")) { if (attr.isEmpty() || attr.startsWith("data:")) {
return null return null
@ -62,7 +62,7 @@ fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? {
* @see attrAsAbsoluteUrlOrNull * @see attrAsAbsoluteUrlOrNull
* @see attrAsAbsoluteUrl * @see attrAsAbsoluteUrl
*/ */
fun Element.attrAsRelativeUrl(attributeKey: String): String { public fun Element.attrAsRelativeUrl(attributeKey: String): String {
return requireNotNull(attrAsRelativeUrlOrNull(attributeKey)) { return requireNotNull(attrAsRelativeUrlOrNull(attributeKey)) {
"Cannot get relative url for $attributeKey: \"${attr(attributeKey)}\"" "Cannot get relative url for $attributeKey: \"${attr(attributeKey)}\""
} }
@ -74,7 +74,7 @@ fun Element.attrAsRelativeUrl(attributeKey: String): String {
* @see attrAsRelativeUrl * @see attrAsRelativeUrl
* @see attrAsRelativeUrlOrNull * @see attrAsRelativeUrlOrNull
*/ */
fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? { public fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? {
val attr = attrOrNull(attributeKey) ?: return null val attr = attrOrNull(attributeKey) ?: return null
if (attr.isEmpty() || attr.startsWith("data:")) { if (attr.isEmpty() || attr.startsWith("data:")) {
return null return null
@ -89,7 +89,7 @@ fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? {
* @see attrAsRelativeUrl * @see attrAsRelativeUrl
* @see attrAsRelativeUrlOrNull * @see attrAsRelativeUrlOrNull
*/ */
fun Element.attrAsAbsoluteUrl(attributeKey: String): String { public fun Element.attrAsAbsoluteUrl(attributeKey: String): String {
return requireNotNull(attrAsAbsoluteUrlOrNull(attributeKey)) { return requireNotNull(attrAsAbsoluteUrlOrNull(attributeKey)) {
"Cannot get absolute url for $attributeKey: \"${attr(attributeKey)}\"" "Cannot get absolute url for $attributeKey: \"${attr(attributeKey)}\""
} }
@ -98,7 +98,7 @@ fun Element.attrAsAbsoluteUrl(attributeKey: String): String {
/** /**
* Return css value from `style` attribute or null if it is missing * Return css value from `style` attribute or null if it is missing
*/ */
fun Element.styleValueOrNull(property: String): String? { public fun Element.styleValueOrNull(property: String): String? {
val regex = Regex("${Regex.escape(property)}\\s*:\\s*[^;]+") val regex = Regex("${Regex.escape(property)}\\s*:\\s*[^;]+")
val css = attr("style").find(regex) ?: return null val css = attr("style").find(regex) ?: return null
return css.substringAfter(':').removeSuffix(';').trim() return css.substringAfter(':').removeSuffix(';').trim()
@ -107,33 +107,33 @@ fun Element.styleValueOrNull(property: String): String? {
/** /**
* Like a `expectFirst` but with detailed error message * Like a `expectFirst` but with detailed error message
*/ */
fun Element.selectFirstOrThrow(cssQuery: String): Element { public fun Element.selectFirstOrThrow(cssQuery: String): Element {
return Selector.selectFirst(cssQuery, this) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri()) return Selector.selectFirst(cssQuery, this) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri())
} }
fun Element.selectOrThrow(cssQuery: String): Elements { public fun Element.selectOrThrow(cssQuery: String): Elements {
return Selector.select(cssQuery, this).ifEmpty { return Selector.select(cssQuery, this).ifEmpty {
throw ParseException("Empty result for \"$cssQuery\"", baseUri()) throw ParseException("Empty result for \"$cssQuery\"", baseUri())
} }
} }
fun Element.requireElementById(id: String): Element { public fun Element.requireElementById(id: String): Element {
return getElementById(id) ?: throw ParseException("Cannot find \"#$id\"", baseUri()) return getElementById(id) ?: throw ParseException("Cannot find \"#$id\"", baseUri())
} }
fun Element.selectLast(cssQuery: String): Element? { public fun Element.selectLast(cssQuery: String): Element? {
return select(cssQuery).lastOrNull() return select(cssQuery).lastOrNull()
} }
fun Element.selectLastOrThrow(cssQuery: String): Element { public fun Element.selectLastOrThrow(cssQuery: String): Element {
return selectLast(cssQuery) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri()) return selectLast(cssQuery) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri())
} }
fun Element.textOrNull(): String? = text().takeUnless { it.isEmpty() } public fun Element.textOrNull(): String? = text().takeUnless { it.isEmpty() }
fun Element.ownTextOrNull(): String? = ownText().takeUnless { it.isEmpty() } public fun Element.ownTextOrNull(): String? = ownText().takeUnless { it.isEmpty() }
fun Element.selectFirstParent(query: String): Element? { public fun Element.selectFirstParent(query: String): Element? {
val selector = QueryParser.parse(query) val selector = QueryParser.parse(query)
val parents = parents() val parents = parents()
val root = parents.lastOrNull() ?: return null val root = parents.lastOrNull() ?: return null
@ -145,7 +145,7 @@ fun Element.selectFirstParent(query: String): Element? {
/** /**
* Return a first non-empty attribute value of [names] or null if it is missing or empty * Return a first non-empty attribute value of [names] or null if it is missing or empty
*/ */
fun Element.attrOrNull(vararg names: String): String? { public fun Element.attrOrNull(vararg names: String): String? {
for (name in names) { for (name in names) {
val value = attr(name) val value = attr(name)
if (value.isNotEmpty()) { if (value.isNotEmpty()) {

@ -5,6 +5,8 @@ package org.koitharu.kotatsu.parsers.util
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
public fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String { public fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String {
@ -56,8 +58,14 @@ public fun Number.formatSimple(): String {
} }
} }
public inline fun Int.ifZero(defaultVale: () -> Int): Int = if (this == 0) { public inline fun Int.ifZero(defaultVale: () -> Int): Int {
defaultVale() contract {
} else { callsInPlace(defaultVale, InvocationKind.AT_MOST_ONCE)
this }
@Suppress("KotlinConstantConditions")
return if (this == 0) {
defaultVale()
} else {
this
}
} }

@ -7,6 +7,8 @@ import okhttp3.Call
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody import okhttp3.ResponseBody
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
public suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation -> public suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
val callback = ContinuationCallCallback(this, continuation) val callback = ContinuationCallCallback(this, continuation)
@ -39,8 +41,13 @@ public fun Response.Builder.setHeader(name: String, value: String?): Response.Bu
header(name, value) header(name, value)
} }
public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response = body?.use { responseBody -> public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response {
newBuilder() contract {
.body(mapper(responseBody)) callsInPlace(mapper, InvocationKind.AT_MOST_ONCE)
.build() }
} ?: this return body?.use { responseBody ->
newBuilder()
.body(mapper(responseBody))
.build()
} ?: this
}

@ -1,8 +1,14 @@
package org.koitharu.kotatsu.parsers.util package org.koitharu.kotatsu.parsers.util
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@Suppress("WRONG_INVOCATION_KIND") // https://youtrack.jetbrains.com/issue/KT-70714
public inline fun <T, R> T.runCatchingCancellable(block: T.() -> R): Result<R> { public inline fun <T, R> T.runCatchingCancellable(block: T.() -> R): Result<R> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try { return try {
Result.success(block()) Result.success(block())
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
@ -15,6 +21,9 @@ public inline fun <T, R> T.runCatchingCancellable(block: T.() -> R): Result<R> {
} }
public inline fun <R, T : R> Result<T>.recoverCatchingCancellable(transform: (exception: Throwable) -> R): Result<R> { public inline fun <R, T : R> Result<T>.recoverCatchingCancellable(transform: (exception: Throwable) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (val exception = exceptionOrNull()) { return when (val exception = exceptionOrNull()) {
null -> this null -> this
else -> runCatchingCancellable { transform(exception) } else -> runCatchingCancellable { transform(exception) }
@ -22,6 +31,9 @@ public inline fun <R, T : R> Result<T>.recoverCatchingCancellable(transform: (ex
} }
public inline fun <R : Any, T : R> Result<T>.recoverNotNull(transform: (exception: Throwable) -> R?): Result<R> { public inline fun <R : Any, T : R> Result<T>.recoverNotNull(transform: (exception: Throwable) -> R?): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (val exception = exceptionOrNull()) { return when (val exception = exceptionOrNull()) {
null -> this null -> this
else -> transform(exception)?.let(Result.Companion::success) ?: this else -> transform(exception)?.let(Result.Companion::success) ?: this

@ -6,7 +6,7 @@ import org.json.JSONObject
import java.util.* import java.util.*
import kotlin.contracts.contract import kotlin.contracts.contract
inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONTo( public inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONTo(
destination: C, destination: C,
block: (JSONObject) -> R, block: (JSONObject) -> R,
): C { ): C {
@ -18,7 +18,7 @@ inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONTo(
return destination return destination
} }
inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo( public inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo(
destination: C, destination: C,
block: (JSONObject) -> R?, block: (JSONObject) -> R?,
): C { ): C {
@ -30,15 +30,15 @@ inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo(
return destination return destination
} }
inline fun <T> JSONArray.mapJSON(block: (JSONObject) -> T): List<T> { public inline fun <T> JSONArray.mapJSON(block: (JSONObject) -> T): List<T> {
return mapJSONTo(ArrayList(length()), block) return mapJSONTo(ArrayList(length()), block)
} }
inline fun <T> JSONArray.mapJSONNotNull(block: (JSONObject) -> T?): List<T> { public inline fun <T> JSONArray.mapJSONNotNull(block: (JSONObject) -> T?): List<T> {
return mapJSONNotNullTo(ArrayList(length()), block) return mapJSONNotNullTo(ArrayList(length()), block)
} }
fun <T> JSONArray.mapJSONIndexed(block: (Int, JSONObject) -> T): List<T> { public fun <T> JSONArray.mapJSONIndexed(block: (Int, JSONObject) -> T): List<T> {
val len = length() val len = length()
val result = ArrayList<T>(len) val result = ArrayList<T>(len)
for (i in 0 until len) { for (i in 0 until len) {
@ -48,13 +48,13 @@ fun <T> JSONArray.mapJSONIndexed(block: (Int, JSONObject) -> T): List<T> {
return result return result
} }
fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.takeUnless { public fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.takeUnless {
it === JSONObject.NULL it === JSONObject.NULL
}?.toString()?.takeUnless { }?.toString()?.takeUnless {
it.isEmpty() it.isEmpty()
} }
fun JSONObject.getBooleanOrDefault(name: String, defaultValue: Boolean): Boolean { public fun JSONObject.getBooleanOrDefault(name: String, defaultValue: Boolean): Boolean {
return when (val rawValue = opt(name)) { return when (val rawValue = opt(name)) {
null, JSONObject.NULL -> defaultValue null, JSONObject.NULL -> defaultValue
is Boolean -> rawValue is Boolean -> rawValue
@ -64,7 +64,7 @@ fun JSONObject.getBooleanOrDefault(name: String, defaultValue: Boolean): Boolean
} }
} }
fun JSONObject.getLongOrDefault(name: String, defaultValue: Long): Long { public fun JSONObject.getLongOrDefault(name: String, defaultValue: Long): Long {
return when (val rawValue = opt(name)) { return when (val rawValue = opt(name)) {
null, JSONObject.NULL -> defaultValue null, JSONObject.NULL -> defaultValue
is Long -> rawValue is Long -> rawValue
@ -74,7 +74,7 @@ fun JSONObject.getLongOrDefault(name: String, defaultValue: Long): Long {
} }
} }
fun JSONObject.getIntOrDefault(name: String, defaultValue: Int): Int { public fun JSONObject.getIntOrDefault(name: String, defaultValue: Int): Int {
return when (val rawValue = opt(name)) { return when (val rawValue = opt(name)) {
null, JSONObject.NULL -> defaultValue null, JSONObject.NULL -> defaultValue
is Int -> rawValue is Int -> rawValue
@ -84,7 +84,7 @@ fun JSONObject.getIntOrDefault(name: String, defaultValue: Int): Int {
} }
} }
fun JSONObject.getDoubleOrDefault(name: String, defaultValue: Double): Double { public fun JSONObject.getDoubleOrDefault(name: String, defaultValue: Double): Double {
return when (val rawValue = opt(name)) { return when (val rawValue = opt(name)) {
null, JSONObject.NULL -> defaultValue null, JSONObject.NULL -> defaultValue
is Double -> rawValue is Double -> rawValue
@ -94,7 +94,7 @@ fun JSONObject.getDoubleOrDefault(name: String, defaultValue: Double): Double {
} }
} }
fun JSONObject.getFloatOrDefault(name: String, defaultValue: Float): Float { public fun JSONObject.getFloatOrDefault(name: String, defaultValue: Float): Float {
return when (val rawValue = opt(name)) { return when (val rawValue = opt(name)) {
null, JSONObject.NULL -> defaultValue null, JSONObject.NULL -> defaultValue
is Float -> rawValue is Float -> rawValue
@ -104,23 +104,21 @@ fun JSONObject.getFloatOrDefault(name: String, defaultValue: Float): Float {
} }
} }
fun JSONArray.JSONIterator(): Iterator<JSONObject> = JSONIterator(this) @Deprecated("")
public fun JSONArray.JSONIterator(): Iterator<JSONObject> = JSONIterator(this)
fun JSONArray.stringIterator(): Iterator<String> = JSONStringIterator(this) @Deprecated("")
public fun JSONArray.stringIterator(): Iterator<String> = JSONStringIterator(this)
fun <T> JSONArray.mapJSONToSet(block: (JSONObject) -> T): Set<T> { public inline fun <T> JSONArray.mapJSONToSet(mapper: (JSONObject) -> T): Set<T> {
val len = length() return mapJSONTo(ArraySet<T>(length()), mapper)
val result = ArraySet<T>(len)
for (i in 0 until len) {
val jo = getJSONObject(i)
result.add(block(jo))
}
return result
} }
fun JSONObject.values(): Iterator<Any> = JSONValuesIterator(this) @Deprecated("")
public fun JSONObject.values(): Iterator<Any> = JSONValuesIterator(this)
fun JSONArray.associateByKey(key: String): Map<String, JSONObject> { @Deprecated("")
public fun JSONArray.associateByKey(key: String): Map<String, JSONObject> {
val destination = LinkedHashMap<String, JSONObject>(length()) val destination = LinkedHashMap<String, JSONObject>(length())
repeat(length()) { i -> repeat(length()) { i ->
val item = getJSONObject(i) val item = getJSONObject(i)
@ -138,10 +136,12 @@ public fun JSONArray?.isNullOrEmpty(): Boolean {
return this == null || this.length() == 0 return this == null || this.length() == 0
} }
fun JSONArray.toJSONList(): List<JSONObject> { @Deprecated("")
public fun JSONArray.toJSONList(): List<JSONObject> {
return List(length()) { i -> getJSONObject(i) } return List(length()) { i -> getJSONObject(i) }
} }
inline fun <reified T> JSONArray.asIterable(): Iterable<T> = Iterable { @Deprecated("")
public inline fun <reified T> JSONArray.asIterable(): Iterable<T> = Iterable {
JSONTypedIterator(this, T::class.java) JSONTypedIterator(this, T::class.java)
} }

Loading…
Cancel
Save