Refactor json utilities

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

@ -202,7 +202,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT", url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT",
).parseJson().getJSONArray("chapters") ).parseJson().getJSONArray("chapters")
val dateFormat = SimpleDateFormat("yyyy-MM-dd") val dateFormat = SimpleDateFormat("yyyy-MM-dd")
return ja.toJSONList().reversed().mapChapters { _, jo -> return ja.asTypedList<JSONObject>().reversed().mapChapters { _, jo ->
val vol = jo.getIntOrDefault("vol", 0) val vol = jo.getIntOrDefault("vol", 0)
val chap = jo.getFloatOrDefault("chap", 0f) val chap = jo.getFloatOrDefault("chap", 0f)
val locale = Locale.forLanguageTag(jo.getString("lang")) val locale = Locale.forLanguageTag(jo.getString("lang"))
@ -227,7 +227,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
number = chap, number = chap,
volume = vol, volume = vol,
url = jo.getString("hid"), url = jo.getString("hid"),
scanlator = jo.optJSONArray("group_name")?.asIterable<String>()?.joinToString() scanlator = jo.optJSONArray("group_name")?.asTypedList<String>()?.joinToString()
?.takeUnless { it.isBlank() }, ?.takeUnless { it.isBlank() },
uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')), uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')),
branch = branch, branch = branch,
@ -270,7 +270,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
private suspend fun loadTags(): SparseArrayCompat<MangaTag> { private suspend fun loadTags(): SparseArrayCompat<MangaTag> {
val ja = webClient.httpGet("https://api.${domain}/genre").parseJsonArray() val ja = webClient.httpGet("https://api.${domain}/genre").parseJsonArray()
val tags = SparseArrayCompat<MangaTag>(ja.length()) val tags = SparseArrayCompat<MangaTag>(ja.length())
for (jo in ja.JSONIterator()) { for (jo in ja.asTypedList<JSONObject>()) {
tags.append( tags.append(
jo.getInt("id"), jo.getInt("id"),
MangaTag( MangaTag(

@ -86,7 +86,7 @@ internal abstract class LineWebtoonsParser(
val episodes = firstResult val episodes = firstResult
.getJSONObject("episodeList") .getJSONObject("episodeList")
.getJSONArray("episode") .getJSONArray("episode")
.toJSONList() .asTypedList<JSONObject>()
.toMutableList() .toMutableList()
while (episodes.count() < totalEpisodeCount) { while (episodes.count() < totalEpisodeCount) {
@ -94,7 +94,7 @@ internal abstract class LineWebtoonsParser(
url = "/lineWebtoon/webtoon/challengeEpisodeList.json?v=2&titleNo=$titleNo&startIndex=${episodes.count()}&pageSize=30", url = "/lineWebtoon/webtoon/challengeEpisodeList.json?v=2&titleNo=$titleNo&startIndex=${episodes.count()}&pageSize=30",
).getJSONObject("episodeList") ).getJSONObject("episodeList")
.getJSONArray("episode") .getJSONArray("episode")
.toJSONList() .asTypedList<JSONObject>()
episodes.addAll(page) episodes.addAll(page)
} }

@ -320,7 +320,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
) )
} }
private fun JSONObject.firstStringValue() = values().next() as String private fun JSONObject.firstStringValue() = entries<String>().first().value
private fun JSONObject.selectByLocale(): String? { private fun JSONObject.selectByLocale(): String? {
val preferredLocales = context.getPreferredLocales() val preferredLocales = context.getPreferredLocales()
@ -328,7 +328,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
getStringOrNull(locale.language)?.let { return it } getStringOrNull(locale.language)?.let { return it }
getStringOrNull(locale.toLanguageTag())?.let { return it } getStringOrNull(locale.toLanguageTag())?.let { return it }
} }
return getStringOrNull(LOCALE_FALLBACK) ?: values().nextOrNull() as? String return getStringOrNull(LOCALE_FALLBACK) ?: entries<String>().firstOrNull()?.value
} }
private fun JSONArray.flatten(): JSONObject { private fun JSONArray.flatten(): JSONObject {
@ -387,7 +387,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val json = webClient.httpGet(url).parseJson() val json = webClient.httpGet(url).parseJson()
if (json.getString("result") == "ok") { if (json.getString("result") == "ok") {
return Chapters( return Chapters(
data = json.optJSONArray("data")?.toJSONList().orEmpty(), data = json.optJSONArray("data")?.asTypedList<JSONObject>().orEmpty(),
total = json.getInt("total"), total = json.getInt("total"),
) )
} else { } else {
@ -443,6 +443,16 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
return chaptersBuilder.toList() return chaptersBuilder.toList()
} }
private fun JSONArray.associateByKey(key: String): Map<String, JSONObject> {
val destination = LinkedHashMap<String, JSONObject>(length())
repeat(length()) { i ->
val item = getJSONObject(i)
val keyValue = item.getString(key)
destination[keyValue] = item
}
return destination
}
private class Chapters( private class Chapters(
val data: List<JSONObject>, val data: List<JSONObject>,
val total: Int, val total: Int,

@ -14,10 +14,10 @@ import org.koitharu.kotatsu.parsers.SinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey 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.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.util.* import java.util.*
internal abstract class MangaPlusParser( internal abstract class MangaPlusParser(
@ -68,7 +68,7 @@ internal abstract class MangaPlusParser(
return json.getJSONObject("titleRankingView") return json.getJSONObject("titleRankingView")
.getJSONArray("titles") .getJSONArray("titles")
.toJSONList() .asTypedList<JSONObject>()
.toMangaList() .toMangaList()
} }
@ -86,7 +86,7 @@ internal abstract class MangaPlusParser(
apiCall("/title_list/allV2") apiCall("/title_list/allV2")
.getJSONObject("allTitlesViewV2") .getJSONObject("allTitlesViewV2")
.getJSONArray("AllTitlesGroup") .getJSONArray("AllTitlesGroup")
.mapJSON { it.getJSONArray("titles").toJSONList() } .mapJSON { it.getJSONArray("titles").asTypedList<JSONObject>() }
.flatten() .flatten()
} }
@ -169,10 +169,10 @@ internal abstract class MangaPlusParser(
private fun parseChapters(chapterListGroup: JSONArray, language: String): List<MangaChapter> { private fun parseChapters(chapterListGroup: JSONArray, language: String): List<MangaChapter> {
val chapterList = chapterListGroup val chapterList = chapterListGroup
.toJSONList() .asTypedList<JSONObject>()
.flatMap { .flatMap {
it.optJSONArray("firstChapterList")?.toJSONList().orEmpty() + it.optJSONArray("firstChapterList")?.asTypedList<JSONObject>().orEmpty() +
it.optJSONArray("lastChapterList")?.toJSONList().orEmpty() it.optJSONArray("lastChapterList")?.asTypedList<JSONObject>().orEmpty()
} }
return chapterList.mapChapters { _, chapter -> return chapterList.mapChapters { _, chapter ->
@ -254,7 +254,7 @@ internal abstract class MangaPlusParser(
return checkNotNull(success) { return checkNotNull(success) {
val error = response.getJSONObject("error") val error = response.getJSONObject("error")
val reason = error.getJSONArray("popups") val reason = error.getJSONArray("popups")
.toJSONList() .asTypedList<JSONObject>()
.firstOrNull { it.getStringOrNull("language") == null } .firstOrNull { it.getStringOrNull("language") == null }
if (reason?.getStringOrNull("subject") == "Not Found" && url.contains("manga_viewer")) { if (reason?.getStringOrNull("subject") == "Not Found" && url.contains("manga_viewer")) {

@ -11,10 +11,10 @@ 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.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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -364,8 +364,8 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
} }
} }
val pics = pages.getJSONArray("pics").toJSONList() val pics = pages.getJSONArray("pics").asTypedList<JSONObject>()
val picsS = pages.getJSONArray("picsS").toJSONList() val picsS = pages.getJSONArray("picsS").asTypedList<JSONObject>()
return pics.zip(picsS).map { return pics.zip(picsS).map {
val img = it.first.getString("url") val img = it.first.getString("url")

@ -12,10 +12,10 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.domain import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.getDomain import org.koitharu.kotatsu.parsers.util.getDomain
import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.json.stringIterator
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.* import java.util.*
@ -281,7 +281,7 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context,
} }
val result = ArraySet<MangaTag>(length()) val result = ArraySet<MangaTag>(length())
stringIterator().forEach { asTypedList<String>().forEach {
result.add( result.add(
MangaTag( MangaTag(
title = toTitle(it), title = toTitle(it),

@ -3,13 +3,14 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap import androidx.collection.ArrayMap
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.json.JSONObject
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.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.util.json.toJSONList import org.koitharu.kotatsu.parsers.util.json.asTypedList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -135,7 +136,8 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
tagCache?.let { return@withLock it } tagCache?.let { return@withLock it }
val tagMap = ArrayMap<String, MangaTag>() val tagMap = ArrayMap<String, MangaTag>()
val json = val json =
webClient.httpGet("https://gg.$domain/api/series/filters").parseJson().getJSONArray("genres").toJSONList() webClient.httpGet("https://gg.$domain/api/series/filters").parseJson().getJSONArray("genres")
.asTypedList<JSONObject>()
for (el in json) { for (el in json) {
if (el.getString("name").isEmpty()) continue if (el.getString("name").isEmpty()) continue
tagMap[el.getString("name")] = MangaTag( tagMap[el.getString("name")] = MangaTag(

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
@ -11,8 +12,8 @@ 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.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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -167,7 +168,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
val seriesKey = baseUrl.substringAfterLast("/").substringBefore("-") val seriesKey = baseUrl.substringAfterLast("/").substringBefore("-")
val json = JSONArray( val json = JSONArray(
webClient.httpGet("https://api.$domain/api/v1/webtoon/chapters/$key-desc").parseRaw(), webClient.httpGet("https://api.$domain/api/v1/webtoon/chapters/$key-desc").parseRaw(),
).toJSONList().reversed() ).asTypedList<JSONObject>().reversed()
return json.mapChapters { i, j -> return json.mapChapters { i, j ->
val url = "https://$domain/read/webtoon/$seriesKey-${j.getString("id")}-${j.getString("slug")}" val url = "https://$domain/read/webtoon/$seriesKey-${j.getString("id")}-${j.getString("slug")}"
val date = j.getString("createdAt").substringBeforeLast("T") val date = j.getString("createdAt").substringBeforeLast("T")

@ -7,9 +7,9 @@ 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.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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import org.koitharu.kotatsu.parsers.util.json.unescapeJson import org.koitharu.kotatsu.parsers.util.json.unescapeJson
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -151,7 +151,7 @@ internal abstract class HeanCms(
val seriesId = manga.id val seriesId = manga.id
val url = "https://$apiPath/chapter/query?page=1&perPage=9999&series_id=$seriesId" val url = "https://$apiPath/chapter/query?page=1&perPage=9999&series_id=$seriesId"
val response = webClient.httpGet(url).parseJson() val response = webClient.httpGet(url).parseJson()
val data = response.getJSONArray("data").toJSONList() val data = response.getJSONArray("data").asTypedList<JSONObject>()
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH) val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
return manga.copy( return manga.copy(
chapters = data.mapChapters(reversed = true) { i, it -> chapters = data.mapChapters(reversed = true) { i, it ->

@ -6,9 +6,9 @@ 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.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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -132,7 +132,7 @@ internal abstract class IkenParser(
val url = "https://$domain/api/chapters?postId=$seriesId&skip=0&take=1000&order=desc&userid=" val url = "https://$domain/api/chapters?postId=$seriesId&skip=0&take=1000&order=desc&userid="
val json = webClient.httpGet(url).parseJson().getJSONObject("post") val json = webClient.httpGet(url).parseJson().getJSONObject("post")
val slug = json.getString("slug") val slug = json.getString("slug")
val data = json.getJSONArray("chapters").toJSONList() val data = json.getJSONArray("chapters").asTypedList<JSONObject>()
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH) val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
return manga.copy( return manga.copy(
chapters = data.mapChapters(reversed = true) { i, it -> chapters = data.mapChapters(reversed = true) { i, it ->

@ -131,7 +131,7 @@ internal abstract class MangAdventureParser(
"hiatus" -> MangaState.PAUSED "hiatus" -> MangaState.PAUSED
else -> null else -> null
}, },
chapters = chapters?.optJSONArray("results")?.toJSONList()?.mapChapters { _, it -> chapters = chapters?.optJSONArray("results")?.asTypedList<JSONObject>()?.mapChapters { _, it ->
MangaChapter( MangaChapter(
id = generateUid(it.getLong("id")), id = generateUid(it.getLong("id")),
name = it.getString("full_title"), name = it.getString("full_title"),

@ -8,9 +8,9 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -177,7 +177,7 @@ internal abstract class NepnepParser(
doc.selectFirstOrThrow("script:containsData(MainFunction)").data() doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
.substringAfter("vm.Chapters = ") .substringAfter("vm.Chapters = ")
.substringBefore(';'), .substringBefore(';'),
).toJSONList().reversed(), ).asTypedList<JSONObject>().reversed(),
) )
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:SS", sourceLocale) val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:SS", sourceLocale)

@ -8,9 +8,10 @@ import org.koitharu.kotatsu.parsers.SinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey 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.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.toJSONList import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -209,10 +210,10 @@ internal abstract class PizzaReaderParser(
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val json = webClient.httpGet(fullUrl).parseJson().getJSONObject("comic") val json = webClient.httpGet(fullUrl).parseJson().getJSONObject("comic")
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
val chapters = JSONArray(json.getJSONArray("chapters").toJSONList().reversed()) val chapters = JSONArray(json.getJSONArray("chapters").asTypedList<JSONObject>().reversed())
manga.copy( manga.copy(
tags = json.getJSONArray("genres").toJSONList().mapNotNullToSet { tags = json.getJSONArray("genres").mapJSONToSet {
MangaTag( MangaTag(
key = it.getString("slug"), key = it.getString("slug"),
title = it.getString("name"), title = it.getString("name"),

@ -137,13 +137,13 @@ internal class MangaWtfParser(
tags = jo.getJSONArray("labels").mapJSONToSet { it.toMangaTag() }, tags = jo.getJSONArray("labels").mapJSONToSet { it.toMangaTag() },
state = jo.getStringOrNull("status")?.toMangaState(), state = jo.getStringOrNull("status")?.toMangaState(),
author = author =
jo.getJSONArray("relations").toJSONList().firstNotNullOfOrNull { jo.getJSONArray("relations").asTypedList<JSONObject>().firstNotNullOfOrNull {
if (it.getStringOrNull("type") == "AUTHOR") { if (it.getStringOrNull("type") == "AUTHOR") {
it.getJSONObject("publisher").getStringOrNull("name") it.getJSONObject("publisher").getStringOrNull("name")
} else { } else {
null null
} }
}, },
source = source, source = source,
largeCoverUrl = null, largeCoverUrl = null,
description = jo.getString("description").nl2br(), description = jo.getString("description").nl2br(),
@ -209,10 +209,10 @@ internal class MangaWtfParser(
MangaChapter( MangaChapter(
id = generateUid(jo.getString("id")), id = generateUid(jo.getString("id")),
name = name =
jo.getStringOrNull("name") ?: buildString { jo.getStringOrNull("name") ?: buildString {
if (volume > 0) append("Том ").append(volume).append(' ') if (volume > 0) append("Том ").append(volume).append(' ')
if (number > 0) append("Глава ").append(number) else append("Без имени") if (number > 0) append("Глава ").append(number) else append("Без имени")
}, },
number = number, number = number,
volume = volume, volume = volume,
url = jo.getString("id"), url = jo.getString("id"),

@ -13,9 +13,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey 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.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -36,7 +36,7 @@ internal class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(contex
webClient.httpGet("https://$domain/search/objects2.json").parseJson() webClient.httpGet("https://$domain/search/objects2.json").parseJson()
}.recoverCatchingCancellable { }.recoverCatchingCancellable {
webClient.httpGet("https://$domain/search/objects69.json").parseJson() webClient.httpGet("https://$domain/search/objects69.json").parseJson()
}.getOrThrow().getJSONArray("manga").toJSONList() }.getOrThrow().getJSONArray("manga").asTypedList<JSONObject>()
} }
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaiukr.com") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaiukr.com")

@ -7,7 +7,10 @@ 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.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.* import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -93,7 +96,7 @@ internal class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(co
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
return manga.copy( return manga.copy(
chapters = response.getJSONArray("chapters") chapters = response.getJSONArray("chapters")
.toJSONList() .asTypedList<JSONObject>()
.mapChapters(true) { i, jo -> .mapChapters(true) { i, jo ->
val mangaId = jo.getInt("mangaID") val mangaId = jo.getInt("mangaID")
val chapterId = jo.getInt("id") val chapterId = jo.getInt("id")
@ -120,7 +123,7 @@ internal class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(co
.getJSONObject("pageProps") .getJSONObject("pageProps")
.getJSONObject("chapterData") .getJSONObject("chapterData")
.getJSONArray("url") .getJSONArray("url")
.asIterable<String>() .asTypedList<String>()
.map { url -> .map { url ->
MangaPage( MangaPage(
id = generateUid(url), id = generateUid(url),

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.zeistmanga
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
@ -11,8 +12,9 @@ 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.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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.toJSONList import org.koitharu.kotatsu.parsers.util.json.mapJSON
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -150,10 +152,11 @@ internal abstract class ZeistMangaParser(
} }
protected open fun parseMangaList(json: JSONArray): List<Manga> { protected open fun parseMangaList(json: JSONArray): List<Manga> {
return json.toJSONList().map { j -> return json.mapJSON { j ->
val name = j.getJSONObject("title").getString("\$t") val name = j.getJSONObject("title").getString("\$t")
val href = val href =
j.getJSONArray("link").toJSONList().first { it.getString("rel") == "alternate" }.getString("href") j.getJSONArray("link").asTypedList<JSONObject>().first { it.getString("rel") == "alternate" }
.getString("href")
val urlImg = if (j.toString().contains("media\$thumbnail")) { val urlImg = if (j.toString().contains("media\$thumbnail")) {
j.getJSONObject("media\$thumbnail").getStringOrNull("url") j.getJSONObject("media\$thumbnail").getStringOrNull("url")
?.replace("""/s.+?-c/""".toRegex(), "/w600/") ?.replace("""/s.+?-c/""".toRegex(), "/w600/")
@ -274,12 +277,14 @@ internal abstract class ZeistMangaParser(
append("?alt=json&orderby=published&max-results=9999") append("?alt=json&orderby=published&max-results=9999")
} }
val json = val json =
webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").toJSONList().reversed() webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").asTypedList<JSONObject>()
.reversed()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale) val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return json.mapIndexedNotNull { i, j -> return json.mapIndexedNotNull { i, j ->
val name = j.getJSONObject("title").getString("\$t") val name = j.getJSONObject("title").getString("\$t")
val href = val href =
j.getJSONArray("link").toJSONList().first { it.getString("rel") == "alternate" }.getString("href") j.getJSONArray("link").asTypedList<JSONObject>().first { it.getString("rel") == "alternate" }
.getString("href")
val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T") val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T")
val slug = mangaUrl.substringAfterLast('/') val slug = mangaUrl.substringAfterLast('/')
val slugChapter = href.substringAfterLast('/') val slugChapter = href.substringAfterLast('/')

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.zeistmanga.id package org.koitharu.kotatsu.parsers.site.zeistmanga.id
import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
@ -8,7 +9,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.toJSONList import org.koitharu.kotatsu.parsers.util.json.asTypedList
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@MangaSourceParser("KOMIKGES", "KomikGes", "id") @MangaSourceParser("KOMIKGES", "KomikGes", "id")
@ -25,12 +26,14 @@ internal class KomikGes(context: MangaLoaderContext) :
append("?alt=json&orderby=published&max-results=9999") append("?alt=json&orderby=published&max-results=9999")
} }
val json = val json =
webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").toJSONList().reversed() webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").asTypedList<JSONObject>()
.reversed()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale) val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return json.mapIndexedNotNull { i, j -> return json.mapIndexedNotNull { i, j ->
val name = j.getJSONObject("title").getString("\$t") val name = j.getJSONObject("title").getString("\$t")
val href = val href =
j.getJSONArray("link").toJSONList().first { it.getString("rel") == "alternate" }.getString("href") j.getJSONArray("link").asTypedList<JSONObject>().first { it.getString("rel") == "alternate" }
.getString("href")
val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T") val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T")
val slug = mangaUrl.substringAfterLast('/') val slug = mangaUrl.substringAfterLast('/')
val slugChapter = href.substringAfterLast('/') val slugChapter = href.substringAfterLast('/')

@ -0,0 +1,25 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONArray
internal class JSONArrayTypedIterator<T : Any>(
private val array: JSONArray,
private val typeClass: Class<T>,
startIndex: Int,
) : ListIterator<T> {
private val total = array.length()
private var index = startIndex
override fun hasNext() = index < total
override fun next(): T = typeClass.cast(array[index++])
override fun hasPrevious(): Boolean = index > 0
override fun nextIndex(): Int = index + 1
override fun previous(): T = typeClass.cast(array[index--])
override fun previousIndex(): Int = index - 1
}

@ -0,0 +1,53 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONArray
internal class JSONArrayTypedListWrapper<T : Any>(
private val jsonArray: JSONArray,
private val typeClass: Class<T>,
) : List<T> {
override fun contains(element: T): Boolean = indexOf(element) != -1
override fun containsAll(elements: Collection<T>): Boolean = elements.all { contains(it) }
override fun get(index: Int): T = typeClass.cast(jsonArray[index])
override fun indexOf(element: T): Int {
repeat(jsonArray.length()) { i ->
if (jsonArray[i] == element) {
return i
}
}
return -1
}
override fun isEmpty(): Boolean = jsonArray.length() == 0
override fun iterator(): Iterator<T> = listIterator(0)
override fun lastIndexOf(element: T): Int {
val total = jsonArray.length()
repeat(total) { i ->
if (jsonArray[total - i - 1] == element) {
return i
}
}
return -1
}
override fun listIterator(): ListIterator<T> = listIterator(0)
override fun listIterator(index: Int): ListIterator<T> = JSONArrayTypedIterator(jsonArray, typeClass, index)
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
val result = ArrayList<T>(toIndex - fromIndex + 1)
for (i in fromIndex..toIndex) {
result.add(get(i))
}
return result
}
override val size: Int
get() = jsonArray.length()
}

@ -1,14 +0,0 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONArray
import org.json.JSONObject
class JSONIterator(private val array: JSONArray) : Iterator<JSONObject> {
private val total = array.length()
private var index = 0
override fun hasNext() = index < total
override fun next(): JSONObject = array.getJSONObject(index++)
}

@ -0,0 +1,24 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONObject
internal class JSONObjectTypedIterableWrapper<T : Any>(
private val json: JSONObject,
private val typeClass: Class<T>,
) : Iterable<Map.Entry<String, T>> {
override fun iterator(): Iterator<Map.Entry<String, T>> = IteratorImpl()
private inner class IteratorImpl : Iterator<Map.Entry<String, T>> {
private val keyIterator = json.keys().iterator()
override fun hasNext(): Boolean = keyIterator.hasNext()
override fun next(): Map.Entry<String, T> = keyIterator.next().let { key ->
JSONEntry(key, typeClass.cast(json.get(key)))
}
}
private class JSONEntry<T : Any>(override val key: String, override val value: T) : Map.Entry<String, T>
}

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONArray
class JSONStringIterator(private val array: JSONArray) : Iterator<String> {
private val total = array.length()
private var index = 0
override fun hasNext() = index < total
override fun next(): String = array.getString(index++)
}

@ -1,18 +0,0 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONArray
class JSONTypedIterator<T>(
private val array: JSONArray,
private val cls: Class<T>,
) : Iterator<T> {
private val total = array.length()
private var index = 0
override fun hasNext() = index < total
override fun next(): T {
return cls.cast(array.get(index++))
}
}

@ -1,17 +0,0 @@
package org.koitharu.kotatsu.parsers.util.json
import org.json.JSONObject
class JSONValuesIterator(
private val jo: JSONObject,
) : Iterator<Any> {
private val keyIterator = jo.keys()
override fun hasNext(): Boolean = keyIterator.hasNext()
override fun next(): Any {
val key = keyIterator.next()
return jo.get(key)
}
}

@ -38,6 +38,10 @@ public inline fun <T> JSONArray.mapJSONNotNull(block: (JSONObject) -> T?): List<
return mapJSONNotNullTo(ArrayList(length()), block) return mapJSONNotNullTo(ArrayList(length()), block)
} }
public inline fun <T> JSONArray.mapJSONToSet(mapper: (JSONObject) -> T): Set<T> {
return mapJSONTo(ArraySet<T>(length()), mapper)
}
public 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)
@ -104,30 +108,6 @@ public fun JSONObject.getFloatOrDefault(name: String, defaultValue: Float): Floa
} }
} }
@Deprecated("")
public fun JSONArray.JSONIterator(): Iterator<JSONObject> = JSONIterator(this)
@Deprecated("")
public fun JSONArray.stringIterator(): Iterator<String> = JSONStringIterator(this)
public inline fun <T> JSONArray.mapJSONToSet(mapper: (JSONObject) -> T): Set<T> {
return mapJSONTo(ArraySet<T>(length()), mapper)
}
@Deprecated("")
public fun JSONObject.values(): Iterator<Any> = JSONValuesIterator(this)
@Deprecated("")
public fun JSONArray.associateByKey(key: String): Map<String, JSONObject> {
val destination = LinkedHashMap<String, JSONObject>(length())
repeat(length()) { i ->
val item = getJSONObject(i)
val keyValue = item.getString(key)
destination[keyValue] = item
}
return destination
}
public fun JSONArray?.isNullOrEmpty(): Boolean { public fun JSONArray?.isNullOrEmpty(): Boolean {
contract { contract {
returns(false) implies (this@isNullOrEmpty != null) returns(false) implies (this@isNullOrEmpty != null)
@ -136,12 +116,14 @@ public fun JSONArray?.isNullOrEmpty(): Boolean {
return this == null || this.length() == 0 return this == null || this.length() == 0
} }
@Deprecated("") public fun <T : Any> JSONArray.asTypedList(typeClass: Class<T>): List<T> {
public fun JSONArray.toJSONList(): List<JSONObject> { return JSONArrayTypedListWrapper(this, typeClass)
return List(length()) { i -> getJSONObject(i) }
} }
@Deprecated("") public inline fun <reified T : Any> JSONArray.asTypedList(): List<T> = asTypedList(T::class.java)
public inline fun <reified T> JSONArray.asIterable(): Iterable<T> = Iterable {
JSONTypedIterator(this, T::class.java) public fun <T : Any> JSONObject.entries(typeClass: Class<T>): Iterable<Map.Entry<String, T>> {
return JSONObjectTypedIterableWrapper(this, typeClass)
} }
public inline fun <reified T : Any> JSONObject.entries(): Iterable<Map.Entry<String, T>> = entries(T::class.java)

Loading…
Cancel
Save