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.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)
@ -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,15 +80,13 @@ internal abstract class Manga18Parser(
}
}
if (filter.query != null) {
filter.query.let {
if (!filter.query.isNullOrEmpty()) {
append(listUrl)
append(page.toString())
append("?search=")
append(filter.query.urlEncoded())
append("&order_by=latest")
}
}
append("?order_by=")
when (order) {

@ -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,13 +1,11 @@
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.*
@ -20,13 +18,11 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
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
SortOrder.RATING,
)
override val filterCapabilities: MangaListFilterCapabilities
@ -83,7 +79,7 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
author = null,
state = null,
source = source,
isNsfw = isNsfwSource
isNsfw = isNsfwSource,
)
}
}
@ -97,11 +93,12 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
MangaTag(
key = a.attr("href").substringAfterLast('/'),
title = a.text().toTitleCase(sourceLocale),
source = source
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()) {
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
@ -117,22 +114,22 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
branch = null,
scanlator = null,
source = source,
volume = 0
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 ->
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
source = source,
)
}
}
@ -141,12 +138,24 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
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
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
}
}
@ -162,20 +171,14 @@ internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context
}
}
private suspend fun fetchTags(): Set<MangaTag> {
return tagsCache.get() ?: run {
val tags = webClient.httpGet("https://$domain/genre").parseHtml()
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
source = source,
)
}
tagsCache.set(tags)
tags
}
}
}

@ -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
}
fun <T> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
public 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) {
public inline fun Int.ifZero(defaultVale: () -> Int): Int {
contract {
callsInPlace(defaultVale, InvocationKind.AT_MOST_ONCE)
}
@Suppress("KotlinConstantConditions")
return if (this == 0) {
defaultVale()
} else {
} 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 ->
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
} ?: 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)
}

Loading…
Cancel
Save