Refactoring
This commit is contained in:
@@ -10,7 +10,6 @@ import com.google.devtools.ksp.validate
|
||||
import java.io.File
|
||||
import java.io.Writer
|
||||
import java.util.*
|
||||
import kotlin.math.log
|
||||
|
||||
class ParserProcessor(
|
||||
private val codeGenerator: CodeGenerator,
|
||||
@@ -76,7 +75,6 @@ class ParserProcessor(
|
||||
|
||||
""".trimIndent(),
|
||||
)
|
||||
//language=kotlin
|
||||
sourcesWriter?.write(
|
||||
"""
|
||||
package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
@@ -3,11 +3,11 @@ package org.koitharu.kotatsu.parsers.exception
|
||||
import okio.IOException
|
||||
import org.json.JSONArray
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -410,9 +410,7 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
|
||||
for (i in 0.until(numberOfKeys)) {
|
||||
val keySize = buffer.int
|
||||
|
||||
if (keySize == 0 || keySize > 32) {
|
||||
throw Exception("fatal: !keySize || keySize > 32")
|
||||
}
|
||||
check(keySize in 1..32) { "Invalid key size $keySize" }
|
||||
|
||||
keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize)))
|
||||
buffer.position(buffer.position() + keySize)
|
||||
@@ -482,14 +480,14 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
|
||||
title = doc.selectFirstOrThrow("h1").text(),
|
||||
url = id.toString(),
|
||||
coverUrl =
|
||||
"https:" +
|
||||
doc.selectFirstOrThrow("picture > source")
|
||||
.attr("data-srcset")
|
||||
.substringBefore(" "),
|
||||
"https:" +
|
||||
doc.selectFirstOrThrow("picture > source")
|
||||
.attr("data-srcset")
|
||||
.substringBefore(" "),
|
||||
publicUrl =
|
||||
doc.selectFirstOrThrow("h1 > a")
|
||||
.attrAsRelativeUrl("href")
|
||||
.toAbsoluteUrl(domain),
|
||||
doc.selectFirstOrThrow("h1 > a")
|
||||
.attrAsRelativeUrl("href")
|
||||
.toAbsoluteUrl(domain),
|
||||
author = null,
|
||||
tags = emptySet(),
|
||||
isNsfw = true,
|
||||
@@ -512,37 +510,37 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
|
||||
return manga.copy(
|
||||
title = json.getString("title"),
|
||||
largeCoverUrl =
|
||||
json.getJSONArray("files").getJSONObject(0).let {
|
||||
val hash = it.getString("hash")
|
||||
val commonId = commonImageId()
|
||||
val imageId = imageIdFromHash(hash)
|
||||
val subDomain = 'a' + subdomainOffset(imageId)
|
||||
json.getJSONArray("files").getJSONObject(0).let {
|
||||
val hash = it.getString("hash")
|
||||
val commonId = commonImageId()
|
||||
val imageId = imageIdFromHash(hash)
|
||||
val subDomain = 'a' + subdomainOffset(imageId)
|
||||
|
||||
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
|
||||
},
|
||||
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
|
||||
},
|
||||
author =
|
||||
json.optJSONArray("artists")
|
||||
?.mapJSON { it.getString("artist").toCamelCase() }
|
||||
?.joinToString(),
|
||||
json.optJSONArray("artists")
|
||||
?.mapJSON { it.getString("artist").toCamelCase() }
|
||||
?.joinToString(),
|
||||
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
|
||||
tags =
|
||||
buildSet {
|
||||
json.optJSONArray("characters")
|
||||
?.mapToTags("character")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("tags")
|
||||
?.mapToTags("tag")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("artists")
|
||||
?.mapToTags("artist")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("parodys")
|
||||
?.mapToTags("parody")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("groups")
|
||||
?.mapToTags("group")
|
||||
?.let(::addAll)
|
||||
},
|
||||
buildSet {
|
||||
json.optJSONArray("characters")
|
||||
?.mapToTags("character")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("tags")
|
||||
?.mapToTags("tag")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("artists")
|
||||
?.mapToTags("artist")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("parodys")
|
||||
?.mapToTags("parody")
|
||||
?.let(::addAll)
|
||||
json.optJSONArray("groups")
|
||||
?.mapToTags("group")
|
||||
?.let(::addAll)
|
||||
},
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = generateUid(manga.url),
|
||||
@@ -566,15 +564,15 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
|
||||
mapJSON {
|
||||
MangaTag(
|
||||
title =
|
||||
it.getString(key).toCamelCase().let { title ->
|
||||
if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
|
||||
"$title ♀"
|
||||
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
|
||||
"$title ♂"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
},
|
||||
it.getString(key).toCamelCase().let { title ->
|
||||
if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
|
||||
"$title ♀"
|
||||
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
|
||||
"$title ♂"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
},
|
||||
key = it.getString("url").tagUrlToTag(),
|
||||
source = source,
|
||||
).let(tags::add)
|
||||
@@ -681,11 +679,12 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
|
||||
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"
|
||||
|
||||
if (!base.isNullOrBlank())
|
||||
if (!base.isNullOrBlank()) {
|
||||
retval = base
|
||||
}
|
||||
|
||||
val regex = Regex("""/[0-9a-f]{61}([0-9a-f]{2})([0-9a-f])""")
|
||||
val hashMatch = regex.find(url) ?: return "a"
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
|
||||
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")
|
||||
|
||||
@@ -126,7 +126,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
): List<Manga> {
|
||||
val query = """
|
||||
queryPopularChapters(
|
||||
size: $size
|
||||
size: $PAGE_SIZE
|
||||
language: "${locale.getSiteLang()}"
|
||||
dateRange: 1
|
||||
page: $page
|
||||
@@ -167,7 +167,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
}
|
||||
val query = """
|
||||
queryChapters(
|
||||
limit: $size
|
||||
limit: $PAGE_SIZE
|
||||
search: {$searchPayload}
|
||||
page: $page
|
||||
) {
|
||||
@@ -322,7 +322,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
_id: "${seed.url}"
|
||||
search: {sortBy:Popular}
|
||||
page: 1
|
||||
size: $size
|
||||
size: $PAGE_SIZE
|
||||
) {
|
||||
chapters {
|
||||
_id
|
||||
@@ -384,7 +384,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val size = 20
|
||||
private const val PAGE_SIZE = 20
|
||||
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
|
||||
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.PagedMangaParser
|
||||
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.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
|
||||
@@ -61,9 +62,10 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
return emptyList()
|
||||
}
|
||||
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 ->
|
||||
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 img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg"
|
||||
Manga(
|
||||
|
||||
@@ -80,14 +80,12 @@ internal abstract class Manga18Parser(
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.query != null) {
|
||||
filter.query.let {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&order_by=latest")
|
||||
}
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&order_by=latest")
|
||||
}
|
||||
|
||||
append("?order_by=")
|
||||
|
||||
@@ -136,7 +136,7 @@ internal class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val docs = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
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(
|
||||
id = generateUid(pageUrl),
|
||||
url = pageUrl,
|
||||
|
||||
@@ -1,181 +1,184 @@
|
||||
package org.koitharu.kotatsu.parsers.site.vi
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
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 org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("SAYHENTAI", "SayHentai", "vi", ContentType.HENTAI)
|
||||
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<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
private val tagsCache = AtomicReference<Set<MangaTag>?>(null)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.RATING
|
||||
)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions {
|
||||
return MangaListFilterOptions(
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
availableTags = fetchTags(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions {
|
||||
return MangaListFilterOptions(
|
||||
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||
availableTags = fetchTags(),
|
||||
)
|
||||
}
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genre/")
|
||||
append(filter.tags.first().key)
|
||||
append("/")
|
||||
} else {
|
||||
append("/")
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
val sortQuery = getSortOrderQuery(order, filter.tags.isNotEmpty())
|
||||
if (sortQuery.isNotEmpty()) {
|
||||
append("&")
|
||||
append(sortQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genre/")
|
||||
append(filter.tags.first().key)
|
||||
append("/")
|
||||
} else {
|
||||
append("/")
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
val sortQuery = getSortOrderQuery(order, filter.tags.isNotEmpty())
|
||||
if (sortQuery.isNotEmpty()) {
|
||||
append("&")
|
||||
append(sortQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select(".page-item-detail").mapNotNull { element ->
|
||||
val href = element.selectFirst(".item-summary a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
title = element.selectFirst(".item-summary a")?.text().orEmpty(),
|
||||
coverUrl = element.selectFirst(".item-thumb img")?.src().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource
|
||||
)
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select(".page-item-detail").mapNotNull { element ->
|
||||
val href = element.selectFirst(".item-summary a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
title = element.selectFirst(".item-summary a")?.text().orEmpty(),
|
||||
coverUrl = element.selectFirst(".item-thumb img")?.src().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return manga.copy(
|
||||
altTitle = doc.selectFirst("h2.other-name")?.text(),
|
||||
author = doc.selectFirst("div.summary-heading:contains(Tác giả) + div.summary-content")?.text(),
|
||||
tags = doc.select("div.genres-content a[rel=tag]").mapToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
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()?.lowercase()) {
|
||||
"đang ra" -> MangaState.ONGOING
|
||||
"hoàn thành" -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
chapters = doc.select("li.wp-manga-chapter").mapChapters(reversed = true) { i, element ->
|
||||
val a = element.selectFirst("a") ?: return@mapChapters null
|
||||
MangaChapter(
|
||||
id = generateUid(a.attrAsRelativeUrl("href")),
|
||||
name = a.text(),
|
||||
number = i + 1f,
|
||||
url = a.attrAsRelativeUrl("href"),
|
||||
uploadDate = parseChapterDate(element.selectFirst("span.chapter-release-date")?.text()),
|
||||
branch = null,
|
||||
scanlator = null,
|
||||
source = source,
|
||||
volume = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return manga.copy(
|
||||
altTitle = doc.selectFirst("h2.other-name")?.text(),
|
||||
author = doc.selectFirst("div.summary-heading:contains(Tác giả) + div.summary-content")?.text(),
|
||||
tags = doc.select("div.genres-content a[rel=tag]").mapToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
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()
|
||||
?.lowercase()) {
|
||||
"đang ra" -> MangaState.ONGOING
|
||||
"hoàn thành" -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
chapters = doc.select("li.wp-manga-chapter").mapChapters(reversed = true) { i, element ->
|
||||
val a = element.selectFirst("a") ?: return@mapChapters null
|
||||
MangaChapter(
|
||||
id = generateUid(a.attrAsRelativeUrl("href")),
|
||||
name = a.text(),
|
||||
number = i + 1f,
|
||||
url = a.attrAsRelativeUrl("href"),
|
||||
uploadDate = parseChapterDate(element.selectFirst("span.chapter-release-date")?.text()),
|
||||
branch = null,
|
||||
scanlator = null,
|
||||
source = source,
|
||||
volume = 0,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
return doc.select("div.page-break img").mapIndexed { i, img ->
|
||||
val url = img.src().orEmpty()
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source
|
||||
)
|
||||
}
|
||||
}
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
return doc.selectOrThrow("div.page-break img").mapIndexed { i, img ->
|
||||
val url = img.src().orEmpty()
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String?): Long {
|
||||
if (date == null) return 0
|
||||
return when {
|
||||
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").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("tuần trước") -> System.currentTimeMillis() - date.removeSuffix(" tuần trước").toLong() * 7 * 24 * 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
|
||||
else -> SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L
|
||||
}
|
||||
}
|
||||
private fun parseChapterDate(date: String?): Long {
|
||||
if (date == null) return 0
|
||||
return when {
|
||||
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")
|
||||
.toLong() * 60 * 1000
|
||||
|
||||
private fun getSortOrderQuery(order: SortOrder, hasTags: Boolean): String {
|
||||
if (!hasTags) return ""
|
||||
return when (order) {
|
||||
SortOrder.UPDATED -> "m_orderby=latest"
|
||||
SortOrder.POPULARITY -> "m_orderby=views"
|
||||
SortOrder.ALPHABETICAL -> "m_orderby=alphabet"
|
||||
SortOrder.RATING -> "m_orderby=rating"
|
||||
else -> "m_orderby=latest"
|
||||
}
|
||||
}
|
||||
date.contains("giờ trước") -> System.currentTimeMillis() - date.removeSuffix(" giờ trước")
|
||||
.toLong() * 60 * 60 * 1000
|
||||
|
||||
private suspend fun fetchTags(): Set<MangaTag> {
|
||||
return tagsCache.get() ?: run {
|
||||
val tags = webClient.httpGet("https://$domain/genre").parseHtml()
|
||||
.select("ul.page-genres li a")
|
||||
.mapToSet { a ->
|
||||
val title = a.ownText().toTitleCase(sourceLocale)
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/"),
|
||||
title = title,
|
||||
source = source
|
||||
)
|
||||
}
|
||||
tagsCache.set(tags)
|
||||
tags
|
||||
}
|
||||
}
|
||||
date.contains("ngày trước") -> System.currentTimeMillis() - date.removeSuffix(" ngày trước")
|
||||
.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
|
||||
|
||||
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
|
||||
|
||||
else -> SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSortOrderQuery(order: SortOrder, hasTags: Boolean): String {
|
||||
if (!hasTags) return ""
|
||||
return when (order) {
|
||||
SortOrder.UPDATED -> "m_orderby=latest"
|
||||
SortOrder.POPULARITY -> "m_orderby=views"
|
||||
SortOrder.ALPHABETICAL -> "m_orderby=alphabet"
|
||||
SortOrder.RATING -> "m_orderby=rating"
|
||||
else -> "m_orderby=latest"
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchTags(): Set<MangaTag> = webClient.httpGet("https://$domain/genre").parseHtml()
|
||||
.select("ul.page-genres li a")
|
||||
.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
|
||||
.find(script.html())
|
||||
?.groupValues?.get(1)
|
||||
?: throw Exception("Failed to find chapter feed")
|
||||
?: doc.parseFailed("Failed to find chapter feed")
|
||||
|
||||
} else if (doc.selectFirst("#clwd > script") != null) {
|
||||
val chapterRegex = """clwd\.run\('([^']+)'""".toRegex()
|
||||
@@ -257,7 +257,7 @@ internal abstract class ZeistMangaParser(
|
||||
chapterRegex
|
||||
.find(script.html())
|
||||
?.groupValues?.get(1)
|
||||
?: throw Exception("Failed to find chapter feed")
|
||||
?: doc.parseFailed("Failed to find chapter feed")
|
||||
|
||||
} else if (doc.selectFirst("#chapterlist") != null) {
|
||||
doc.selectFirstOrThrow("#chapterlist").attr("data-post-title")
|
||||
|
||||
@@ -11,14 +11,14 @@ public fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
|
||||
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) {
|
||||
destination.addAll(element)
|
||||
}
|
||||
return destination
|
||||
}
|
||||
|
||||
fun <T> List<T>.medianOrNull(): T? = when {
|
||||
public fun <T> List<T>.medianOrNull(): T? = when {
|
||||
isEmpty() -> null
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
for (item in this) {
|
||||
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))
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
public fun <T> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
|
||||
|
||||
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)
|
||||
for (item in this) {
|
||||
val (k, v) = transform(item)
|
||||
@@ -74,7 +60,7 @@ inline fun <T, K, V> Collection<T>.associateGrouping(transform: (T) -> Pair<K, V
|
||||
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
|
||||
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.MangaLoaderContext
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.Throws
|
||||
|
||||
private const val HASH_CIPHER = "AES/CBC/PKCS7PADDING"
|
||||
private const val AES = "AES"
|
||||
@@ -32,8 +30,8 @@ public class CryptoAES(
|
||||
@Throws(Exception::class)
|
||||
public fun decrypt(cipherText: String, password: String): String {
|
||||
val ctBytes = context.decodeBase64(cipherText)
|
||||
val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16)
|
||||
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size)
|
||||
val saltBytes = ctBytes.copyOfRange(8, 16)
|
||||
val cipherTextBytes = ctBytes.copyOfRange(16, ctBytes.size)
|
||||
val md5: MessageDigest = MessageDigest.getInstance(KDF_DIGEST)
|
||||
val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5)
|
||||
return decryptAES(
|
||||
|
||||
@@ -78,19 +78,9 @@ public class FaviconParser(
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.resolveLink(): String {
|
||||
return when {
|
||||
startsWith("http:") || startsWith("https:") -> {
|
||||
this
|
||||
}
|
||||
|
||||
startsWith('/') -> {
|
||||
"https://$domain$this"
|
||||
}
|
||||
|
||||
else -> {
|
||||
"https://$domain/$this"
|
||||
}
|
||||
}
|
||||
private fun String.resolveLink(): String = when {
|
||||
startsWith("http:") || startsWith("https:") -> 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.exception.ParseException
|
||||
|
||||
val Element.host: String?
|
||||
public val Element.host: String?
|
||||
get() {
|
||||
val uri = baseUri()
|
||||
return if (uri.isEmpty()) {
|
||||
@@ -24,14 +24,14 @@ val Element.host: String?
|
||||
* Return an attribute value or null if it is missing or empty
|
||||
* @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
|
||||
* @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)
|
||||
} else {
|
||||
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 attrAsAbsoluteUrl
|
||||
*/
|
||||
fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? {
|
||||
public fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? {
|
||||
val attr = attrOrNull(attributeKey) ?: return null
|
||||
if (attr.isEmpty() || attr.startsWith("data:")) {
|
||||
return null
|
||||
@@ -62,7 +62,7 @@ fun Element.attrAsRelativeUrlOrNull(attributeKey: String): String? {
|
||||
* @see attrAsAbsoluteUrlOrNull
|
||||
* @see attrAsAbsoluteUrl
|
||||
*/
|
||||
fun Element.attrAsRelativeUrl(attributeKey: String): String {
|
||||
public fun Element.attrAsRelativeUrl(attributeKey: String): String {
|
||||
return requireNotNull(attrAsRelativeUrlOrNull(attributeKey)) {
|
||||
"Cannot get relative url for $attributeKey: \"${attr(attributeKey)}\""
|
||||
}
|
||||
@@ -74,7 +74,7 @@ fun Element.attrAsRelativeUrl(attributeKey: String): String {
|
||||
* @see attrAsRelativeUrl
|
||||
* @see attrAsRelativeUrlOrNull
|
||||
*/
|
||||
fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? {
|
||||
public fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? {
|
||||
val attr = attrOrNull(attributeKey) ?: return null
|
||||
if (attr.isEmpty() || attr.startsWith("data:")) {
|
||||
return null
|
||||
@@ -89,7 +89,7 @@ fun Element.attrAsAbsoluteUrlOrNull(attributeKey: String): String? {
|
||||
* @see attrAsRelativeUrl
|
||||
* @see attrAsRelativeUrlOrNull
|
||||
*/
|
||||
fun Element.attrAsAbsoluteUrl(attributeKey: String): String {
|
||||
public fun Element.attrAsAbsoluteUrl(attributeKey: String): String {
|
||||
return requireNotNull(attrAsAbsoluteUrlOrNull(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
|
||||
*/
|
||||
fun Element.styleValueOrNull(property: String): String? {
|
||||
public fun Element.styleValueOrNull(property: String): String? {
|
||||
val regex = Regex("${Regex.escape(property)}\\s*:\\s*[^;]+")
|
||||
val css = attr("style").find(regex) ?: return null
|
||||
return css.substringAfter(':').removeSuffix(';').trim()
|
||||
@@ -107,33 +107,33 @@ fun Element.styleValueOrNull(property: String): String? {
|
||||
/**
|
||||
* 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())
|
||||
}
|
||||
|
||||
fun Element.selectOrThrow(cssQuery: String): Elements {
|
||||
public fun Element.selectOrThrow(cssQuery: String): Elements {
|
||||
return Selector.select(cssQuery, this).ifEmpty {
|
||||
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())
|
||||
}
|
||||
|
||||
fun Element.selectLast(cssQuery: String): Element? {
|
||||
public fun Element.selectLast(cssQuery: String): Element? {
|
||||
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())
|
||||
}
|
||||
|
||||
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 parents = parents()
|
||||
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
|
||||
*/
|
||||
fun Element.attrOrNull(vararg names: String): String? {
|
||||
public fun Element.attrOrNull(vararg names: String): String? {
|
||||
for (name in names) {
|
||||
val value = attr(name)
|
||||
if (value.isNotEmpty()) {
|
||||
|
||||
@@ -5,6 +5,8 @@ package org.koitharu.kotatsu.parsers.util
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
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) {
|
||||
defaultVale()
|
||||
} else {
|
||||
this
|
||||
public inline fun Int.ifZero(defaultVale: () -> Int): Int {
|
||||
contract {
|
||||
callsInPlace(defaultVale, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
@Suppress("KotlinConstantConditions")
|
||||
return if (this == 0) {
|
||||
defaultVale()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import okhttp3.Call
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
public suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
|
||||
val callback = ContinuationCallCallback(this, continuation)
|
||||
@@ -39,8 +41,13 @@ public fun Response.Builder.setHeader(name: String, value: String?): Response.Bu
|
||||
header(name, value)
|
||||
}
|
||||
|
||||
public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response = body?.use { responseBody ->
|
||||
newBuilder()
|
||||
.body(mapper(responseBody))
|
||||
.build()
|
||||
} ?: this
|
||||
public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response {
|
||||
contract {
|
||||
callsInPlace(mapper, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return body?.use { responseBody ->
|
||||
newBuilder()
|
||||
.body(mapper(responseBody))
|
||||
.build()
|
||||
} ?: this
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.koitharu.kotatsu.parsers.util
|
||||
|
||||
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> {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return try {
|
||||
Result.success(block())
|
||||
} 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> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when (val exception = exceptionOrNull()) {
|
||||
null -> this
|
||||
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> {
|
||||
contract {
|
||||
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when (val exception = exceptionOrNull()) {
|
||||
null -> this
|
||||
else -> transform(exception)?.let(Result.Companion::success) ?: this
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.json.JSONObject
|
||||
import java.util.*
|
||||
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,
|
||||
block: (JSONObject) -> R,
|
||||
): C {
|
||||
@@ -18,7 +18,7 @@ inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONTo(
|
||||
return destination
|
||||
}
|
||||
|
||||
inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo(
|
||||
public inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo(
|
||||
destination: C,
|
||||
block: (JSONObject) -> R?,
|
||||
): C {
|
||||
@@ -30,15 +30,15 @@ inline fun <R, C : MutableCollection<in R>> JSONArray.mapJSONNotNullTo(
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 result = ArrayList<T>(len)
|
||||
for (i in 0 until len) {
|
||||
@@ -48,13 +48,13 @@ fun <T> JSONArray.mapJSONIndexed(block: (Int, JSONObject) -> T): List<T> {
|
||||
return result
|
||||
}
|
||||
|
||||
fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.takeUnless {
|
||||
public fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.takeUnless {
|
||||
it === JSONObject.NULL
|
||||
}?.toString()?.takeUnless {
|
||||
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)) {
|
||||
null, JSONObject.NULL -> defaultValue
|
||||
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)) {
|
||||
null, JSONObject.NULL -> defaultValue
|
||||
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)) {
|
||||
null, JSONObject.NULL -> defaultValue
|
||||
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)) {
|
||||
null, JSONObject.NULL -> defaultValue
|
||||
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)) {
|
||||
null, JSONObject.NULL -> defaultValue
|
||||
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> {
|
||||
val len = length()
|
||||
val result = ArraySet<T>(len)
|
||||
for (i in 0 until len) {
|
||||
val jo = getJSONObject(i)
|
||||
result.add(block(jo))
|
||||
}
|
||||
return result
|
||||
public inline fun <T> JSONArray.mapJSONToSet(mapper: (JSONObject) -> T): Set<T> {
|
||||
return mapJSONTo(ArraySet<T>(length()), mapper)
|
||||
}
|
||||
|
||||
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())
|
||||
repeat(length()) { i ->
|
||||
val item = getJSONObject(i)
|
||||
@@ -138,10 +136,12 @@ public fun JSONArray?.isNullOrEmpty(): Boolean {
|
||||
return this == null || this.length() == 0
|
||||
}
|
||||
|
||||
fun JSONArray.toJSONList(): List<JSONObject> {
|
||||
@Deprecated("")
|
||||
public fun JSONArray.toJSONList(): List<JSONObject> {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user