From fd927c254dc6891554aa9a8af32d20ac5ca438f9 Mon Sep 17 00:00:00 2001 From: Alessandro Jean Date: Mon, 1 Nov 2021 08:32:12 -0300 Subject: [PATCH] Replace org.json with kotlinx.serialization in some sources (#9569) * Replace org.json in MeDocTruyenTranh. * Replace org.json in Kuaikanmanhua. * Replace org.json in QiXiManhua. * Replace org.json in Desu. --- src/ru/desu/build.gradle | 3 +- .../tachiyomi/extension/ru/desu/Desu.kt | 184 ++++++++++-------- src/vi/medoctruyentranh/build.gradle | 3 +- .../vi/medoctruyentranh/MeDocTruyenTranh.kt | 138 ++++++------- src/zh/kuaikanmanhua/build.gradle | 3 +- .../zh/kuaikanmanhua/Kuaikanmanhua.kt | 118 ++++++----- src/zh/qiximh/build.gradle | 3 +- .../tachiyomi/extension/zh/qiximh/Qiximh.kt | 118 ++++++----- 8 files changed, 320 insertions(+), 250 deletions(-) diff --git a/src/ru/desu/build.gradle b/src/ru/desu/build.gradle index 7f2a99d8e..705c91953 100644 --- a/src/ru/desu/build.gradle +++ b/src/ru/desu/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Desu' pkgNameSuffix = 'ru.desu' extClass = '.Desu' - extVersionCode = 12 + extVersionCode = 13 } apply from: "$rootDir/common.gradle" diff --git a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt b/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt index c9ec21615..abe054359 100644 --- a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt +++ b/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt @@ -9,12 +9,20 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.float +import kotlinx.serialization.json.floatOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long import okhttp3.Headers import okhttp3.Request import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject import rx.Observable +import uy.kohesive.injekt.injectLazy import java.util.ArrayList class Desu : HttpSource() { @@ -26,31 +34,34 @@ class Desu : HttpSource() { override val supportsLatest = true + private val json: Json by injectLazy() + override fun headersBuilder() = Headers.Builder().apply { add("User-Agent", "Tachiyomi") add("Referer", baseUrl) } - private fun mangaPageFromJSON(json: String, next: Boolean): MangasPage { - val arr = JSONArray(json) - val ret = ArrayList(arr.length()) - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - ret.add( + private fun mangaPageFromJSON(jsonStr: String, next: Boolean): MangasPage { + val mangaList = json.parseToJsonElement(jsonStr).jsonArray + .map { SManga.create().apply { - mangaFromJSON(obj, false) + mangaFromJSON(it.jsonObject, false) } - ) - } - return MangasPage(ret, next) + } + + return MangasPage(mangaList, next) } - private fun SManga.mangaFromJSON(obj: JSONObject, chapter: Boolean) { - val id = obj.getInt("id") + private fun SManga.mangaFromJSON(obj: JsonObject, chapter: Boolean) { + val id = obj["id"]!!.jsonPrimitive.int + url = "/$id" - title = obj.getString("name").split(" / ").first() - thumbnail_url = obj.getJSONObject("image").getString("original") - val ratingValue = obj.getString("score").toFloat() + title = obj["name"]!!.jsonPrimitive.content + .split(" / ") + .first() + thumbnail_url = obj["image"]!!.jsonObject["original"]!!.jsonPrimitive.content + + val ratingValue = obj["score"]!!.jsonPrimitive.floatOrNull ?: 0F val ratingStar = when { ratingValue > 9.5 -> "★★★★★" ratingValue > 8.5 -> "★★★★✬" @@ -64,14 +75,13 @@ class Desu : HttpSource() { ratingValue > 0.5 -> "✬☆☆☆☆" else -> "☆☆☆☆☆" } - val rawAgeValue = obj.getString("adult") - val rawAgeStop = when (rawAgeValue) { - "1" -> "18+" + + val rawAgeStop = when (obj["adult"]!!.jsonPrimitive.int) { + 1 -> "18+" else -> "0+" } - val rawTypeValue = obj.getString("kind") - val rawTypeStr = when (rawTypeValue) { + val rawTypeStr = when (obj["kind"]!!.jsonPrimitive.content) { "manga" -> "Манга" "manhwa" -> "Манхва" "manhua" -> "Маньхуа" @@ -81,21 +91,32 @@ class Desu : HttpSource() { } var altName = "" - if (obj.getString("synonyms").isNotEmpty() && obj.getString("synonyms") != "null") { - altName = "Альтернативные названия:\n" + obj.getString("synonyms").replace("|", " / ") + "\n\n" + + if (obj["synonyms"]?.jsonPrimitive?.content.orEmpty().isNotEmpty()) { + altName = "Альтернативные названия:\n" + + obj["synonyms"]!!.jsonPrimitive.content + .replace("|", " / ") + + "\n\n" } - description = obj.getString("russian") + "\n" + ratingStar + " " + ratingValue + " (голосов: " + obj.getString("score_users") + ")\n" + altName + obj.getString("description") + + description = obj["russian"]!!.jsonPrimitive.content + "\n" + + ratingStar + " " + ratingValue + + " (голосов: " + + obj["score_users"]!!.jsonPrimitive.int + + ")\n" + altName + + obj["description"]!!.jsonPrimitive.content + genre = if (chapter) { - val jsonArray = obj.getJSONArray("genres") - val genreList = mutableListOf() - for (i in 0 until jsonArray.length()) { - genreList.add(jsonArray.getJSONObject(i).getString("russian")) - } - genreList.plusElement(rawTypeStr).plusElement(rawAgeStop).joinToString() + obj["genres"]!!.jsonArray + .map { it.jsonObject["russian"]!!.jsonPrimitive.content } + .plusElement(rawTypeStr) + .plusElement(rawAgeStop) + .joinToString() } else { - obj.getString("genres") + ", " + rawTypeStr + ", " + rawAgeStop + obj["genres"]!!.jsonPrimitive.content + ", " + rawTypeStr + ", " + rawAgeStop } - status = when (obj.getString("status")) { + + status = when (obj["status"]!!.jsonPrimitive.content) { "ongoing" -> SManga.ONGOING "released" -> SManga.COMPLETED "copyright" -> SManga.LICENSED @@ -103,11 +124,13 @@ class Desu : HttpSource() { } } - override fun popularMangaRequest(page: Int) = GET("$baseUrl$API_URL/?limit=50&order=popular&page=$page") + override fun popularMangaRequest(page: Int) = + GET("$baseUrl$API_URL/?limit=50&order=popular&page=$page") override fun popularMangaParse(response: Response) = searchMangaParse(response) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl$API_URL/?limit=50&order=updated&page=$page") + override fun latestUpdatesRequest(page: Int) = + GET("$baseUrl$API_URL/?limit=50&order=updated&page=$page") override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) @@ -136,12 +159,13 @@ class Desu : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val res = response.body!!.string() - val obj = JSONObject(res).getJSONArray("response") - val nav = JSONObject(res).getJSONObject("pageNavParams") - val count = nav.getInt("count") - val limit = nav.getInt("limit") - val page = nav.getInt("page") + val res = json.parseToJsonElement(response.body!!.string()).jsonObject + val obj = res["response"]!!.jsonArray + val nav = res["pageNavParams"]!!.jsonObject + val count = nav["count"]!!.jsonPrimitive.int + val limit = nav["limit"]!!.jsonPrimitive.int + val page = nav["page"]!!.jsonPrimitive.int + return mangaPageFromJSON(obj.toString(), count > page * limit) } @@ -162,53 +186,56 @@ class Desu : HttpSource() { return GET(baseUrl + "/manga" + manga.url, headers) } override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val obj = JSONObject(response.body!!.string()).getJSONObject("response") + val obj = json.parseToJsonElement(response.body!!.string()) + .jsonObject["response"]!! + .jsonObject + mangaFromJSON(obj, true) } override fun chapterListParse(response: Response): List { - val obj = JSONObject(response.body!!.string()).getJSONObject("response") - val ret = ArrayList() + val obj = json.parseToJsonElement(response.body!!.string()) + .jsonObject["response"]!! + .jsonObject - val cid = obj.getInt("id") + val cid = obj["id"]!!.jsonPrimitive.int - val arr = obj.getJSONObject("chapters").getJSONArray("list") - for (i in 0 until arr.length()) { - val obj2 = arr.getJSONObject(i) - ret.add( - SChapter.create().apply { - val ch = obj2.getString("ch") - val fullnumstr = obj2.getString("vol") + ". " + "Глава " + ch - val title = if (obj2.getString("title") == "null") "" else obj2.getString("title") - name = if (title.isEmpty()) { - fullnumstr - } else { - "$fullnumstr: $title" - } - val id = obj2.getString("id") - url = "/$cid/chapter/$id" - chapter_number = ch.toFloat() - date_upload = obj2.getLong("date") * 1000 + return obj["chapters"]!!.jsonObject["list"]!!.jsonArray.map { + val chapterObj = it.jsonObject + val ch = chapterObj["ch"]!!.jsonPrimitive.float + val fullNumStr = "${chapterObj["vol"]!!.jsonPrimitive.int} . Глава $ch" + val title = chapterObj["title"]?.jsonPrimitive?.content.orEmpty() + + SChapter.create().apply { + name = if (title.isEmpty()) { + fullNumStr + } else { + "$fullNumStr: $title" } - ) + url = "/$cid/chapter/${chapterObj["id"]!!.jsonPrimitive.int}" + chapter_number = ch + date_upload = chapterObj["date"]!!.jsonPrimitive.long * 1000L + } } - return ret } + override fun chapterListRequest(manga: SManga): Request { return GET(baseUrl + API_URL + manga.url, headers) } + override fun pageListRequest(chapter: SChapter): Request { return GET(baseUrl + API_URL + chapter.url, headers) } + override fun pageListParse(response: Response): List { - val obj = JSONObject(response.body!!.string()).getJSONObject("response") - val pages = obj.getJSONObject("pages") - val list = pages.getJSONArray("list") - val ret = ArrayList(list.length()) - for (i in 0 until list.length()) { - ret.add(Page(i, "", list.getJSONObject(i).getString("img"))) - } - return ret + val obj = json.parseToJsonElement(response.body!!.string()) + .jsonObject["response"]!! + .jsonObject + + return obj["pages"]!!.jsonObject["list"]!!.jsonArray + .mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonObject["img"]!!.jsonPrimitive.content) + } } override fun imageUrlParse(response: Response) = @@ -236,10 +263,6 @@ class Desu : HttpSource() { } } } - companion object { - const val PREFIX_SLUG_SEARCH = "slug:" - private const val API_URL = "/manga/api" - } private class OrderBy : Filter.Select( "Сортировка", @@ -247,10 +270,13 @@ class Desu : HttpSource() { ) private class GenreList(genres: List) : Filter.Group("Жанр", genres) + private class TypeList(types: List) : Filter.Group("Тип", types) private class Type(name: String, val id: String) : Filter.CheckBox(name) + private class Genre(name: String, val id: String) : Filter.CheckBox(name) + override fun getFilterList() = FilterList( OrderBy(), TypeList(getTypeList()), @@ -312,4 +338,10 @@ class Desu : HttpSource() { Genre("Юри", "Yuri"), Genre("Яой", "Yaoi") ) + + companion object { + const val PREFIX_SLUG_SEARCH = "slug:" + + private const val API_URL = "/manga/api" + } } diff --git a/src/vi/medoctruyentranh/build.gradle b/src/vi/medoctruyentranh/build.gradle index dde362df8..f91e99949 100644 --- a/src/vi/medoctruyentranh/build.gradle +++ b/src/vi/medoctruyentranh/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'MeDocTruyenTranh' pkgNameSuffix = 'vi.medoctruyentranh' extClass = '.MeDocTruyenTranh' - extVersionCode = 4 + extVersionCode = 5 } apply from: "$rootDir/common.gradle" diff --git a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt b/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt index 7490bbf39..528486578 100644 --- a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt +++ b/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt @@ -8,12 +8,15 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Request import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -29,6 +32,8 @@ class MeDocTruyenTranh : ParsedHttpSource() { override val client = network.cloudflareClient + private val json: Json by injectLazy() + override fun popularMangaSelector() = "div.classifyList a" override fun searchMangaSelector() = ".listCon a" @@ -37,37 +42,24 @@ class MeDocTruyenTranh : ParsedHttpSource() { return GET("$baseUrl/tim-truyen/toan-bo" + if (page > 1) "/$page" else "", headers) } - private inline fun JSONArray.mapJSONArray(transform: (Int, T) -> R): List { - val list = mutableListOf() - for (i in 0 until this.length()) { - list.add(transform(i, this[i] as T)) - } - return list - } - - private fun JSONArray.flatten(): JSONArray { - val list = mutableListOf() - for (i in 0 until this.length()) { - val childArray = this[i] as JSONArray - for (j in 0 until childArray.length()) { - list.add(childArray[j]) - } - } - return JSONArray(list) - } - override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() // trying to build URLs from this JSONObject could cause issues but we need it to get thumbnails - val titleCoverMap = JSONObject(document.select("script#__NEXT_DATA__").first().data()) - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("classify") - .getJSONArray("comics") - .mapJSONArray { _, jsonObject: JSONObject -> - Pair(jsonObject.getString("title"), jsonObject.getString("coverimg")) + val nextData = document.select("script#__NEXT_DATA__").first() + .let { json.parseToJsonElement(it.data()).jsonObject } + + val titleCoverMap = nextData["props"]!! + .jsonObject["pageProps"]!! + .jsonObject["initialState"]!! + .jsonObject["classify"]!! + .jsonObject["comics"]!! + .jsonArray + .map { + Pair( + it.jsonObject["title"]!!.jsonPrimitive.content, + it.jsonObject["coverimg"]!!.jsonPrimitive.content + ) } .toMap() @@ -79,6 +71,7 @@ class MeDocTruyenTranh : ParsedHttpSource() { return MangasPage(mangas, document.select(popularMangaNextPageSelector()) != null) } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { return GET("$baseUrl/search/$query", headers) } @@ -108,26 +101,24 @@ class MeDocTruyenTranh : ParsedHttpSource() { override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - val jsonData = JSONObject(document.select("#__NEXT_DATA__").first()!!.data()) - val mangaDetail = jsonData - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("detail") - .getJSONObject("story_item") - manga.title = mangaDetail.getString("title") - manga.author = mangaDetail.getJSONArray("author_list").getString(0) - val genres = mutableListOf() - for (i in 0 until mangaDetail.getJSONArray("category_list").length()) { - genres.add(mangaDetail.getJSONArray("category_list").getString(i)) - } - manga.genre = genres.joinToString(", ") - manga.description = mangaDetail.getString("summary") - manga.status = parseStatus(mangaDetail.getString("is_updating")) - manga.thumbnail_url = mangaDetail.getString("coverimg") - return manga + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + + val nextData = document.select("script#__NEXT_DATA__").first() + .let { json.parseToJsonElement(it.data()).jsonObject } + + val mangaDetail = nextData["props"]!! + .jsonObject["pageProps"]!! + .jsonObject["initialState"]!! + .jsonObject["detail"]!! + .jsonObject["story_item"]!! + .jsonObject + + title = mangaDetail["title"]!!.jsonPrimitive.content + author = mangaDetail["author_list"]!!.jsonArray.joinToString { it.jsonPrimitive.content } + genre = mangaDetail["category_list"]!!.jsonArray.joinToString { it.jsonPrimitive.content } + description = mangaDetail["summary"]!!.jsonPrimitive.content + status = parseStatus(mangaDetail["is_updating"]!!.jsonPrimitive.content) + thumbnail_url = mangaDetail["coverimg"]!!.jsonPrimitive.content } private fun parseStatus(status: String) = when { @@ -139,18 +130,23 @@ class MeDocTruyenTranh : ParsedHttpSource() { override fun chapterListSelector() = "div.chapters a" override fun chapterListParse(response: Response): List { - return JSONObject(response.asJsoup().select("script#__NEXT_DATA__").first().data()) - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("detail") - .getJSONArray("story_chapters") - .flatten() - .mapJSONArray { _, jsonObject: JSONObject -> + val nextData = response.asJsoup().select("script#__NEXT_DATA__").first() + .let { json.parseToJsonElement(it.data()).jsonObject } + + return nextData["props"]!! + .jsonObject["pageProps"]!! + .jsonObject["initialState"]!! + .jsonObject["detail"]!! + .jsonObject["story_chapters"]!! + .jsonArray + .flatMap { it.jsonArray } + .map { + val chapterObj = it.jsonObject + SChapter.create().apply { - name = jsonObject.getString("title") - setUrlWithoutDomain("${response.request.url}/${jsonObject.getString("chapter_index")}") - date_upload = parseChapterDate(jsonObject.getString("time")) + name = chapterObj["title"]!!.jsonPrimitive.content + date_upload = parseChapterDate(chapterObj["time"]!!.jsonPrimitive.content) + setUrlWithoutDomain("${response.request.url}/${chapterObj["chapter_index"]!!.jsonPrimitive.content}") } } .reversed() @@ -166,15 +162,19 @@ class MeDocTruyenTranh : ParsedHttpSource() { } override fun pageListParse(document: Document): List { - return JSONObject(document.select("#__NEXT_DATA__").first()?.data() ?: "{}") - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("read") - .getJSONObject("detail_item") - .getJSONArray("elements") - .mapJSONArray { i, jsonObject: JSONObject -> - Page(i, "", jsonObject.getString("content")) + val nextData = document.select("script#__NEXT_DATA__").firstOrNull() + ?.let { json.parseToJsonElement(it.data()).jsonObject } + ?: return emptyList() + + return nextData["props"]!! + .jsonObject["pageProps"]!! + .jsonObject["initialState"]!! + .jsonObject["read"]!! + .jsonObject["detail_item"]!! + .jsonObject["elements"]!! + .jsonArray + .mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonObject["content"]!!.jsonPrimitive.content) } } diff --git a/src/zh/kuaikanmanhua/build.gradle b/src/zh/kuaikanmanhua/build.gradle index 723bdada2..4030028b7 100644 --- a/src/zh/kuaikanmanhua/build.gradle +++ b/src/zh/kuaikanmanhua/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Kuaikanmanhua' pkgNameSuffix = 'zh.kuaikanmanhua' extClass = '.Kuaikanmanhua' - extVersionCode = 5 + extVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt b/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt index 7faeae37d..fa67f1ab0 100644 --- a/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt +++ b/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt @@ -10,14 +10,20 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Calendar +import java.util.Locale import kotlin.collections.ArrayList class Kuaikanmanhua : HttpSource() { @@ -34,6 +40,8 @@ class Kuaikanmanhua : HttpSource() { private val apiUrl = "https://api.kkmh.com" + private val json: Json by injectLazy() + // Popular override fun popularMangaRequest(page: Int): Request { @@ -42,25 +50,25 @@ class Kuaikanmanhua : HttpSource() { override fun popularMangaParse(response: Response): MangasPage { val body = response.body!!.string() - val jsonList = JSONObject(body).getJSONObject("data").getJSONArray("topics") + val jsonList = json.parseToJsonElement(body).jsonObject["data"]!! + .jsonObject["topics"]!! + .jsonArray return parseMangaJsonArray(jsonList) } - private fun parseMangaJsonArray(jsonList: JSONArray, isSearch: Boolean = false): MangasPage { - val mangaList = mutableListOf() + private fun parseMangaJsonArray(jsonList: JsonArray, isSearch: Boolean = false): MangasPage { + val mangaList = jsonList.map { + val mangaObj = it.jsonObject - for (i in 0 until jsonList.length()) { - val obj = jsonList.getJSONObject(i) - mangaList.add( - SManga.create().apply { - title = obj.getString("title") - thumbnail_url = obj.getString("vertical_image_url") - url = "/web/topic/" + obj.getInt("id") - } - ) + SManga.create().apply { + title = mangaObj["title"]!!.jsonPrimitive.content + thumbnail_url = mangaObj["vertical_image_url"]!!.jsonPrimitive.content + url = "/web/topic/" + mangaObj["id"]!!.jsonPrimitive.int + } } + // KKMH does not have pages when you search - return MangasPage(mangaList, mangaList.size > 9 && !isSearch) + return MangasPage(mangaList, hasNextPage = mangaList.size > 9 && !isSearch) } // Latest @@ -110,12 +118,12 @@ class Kuaikanmanhua : HttpSource() { override fun searchMangaParse(response: Response): MangasPage { val body = response.body!!.string() - val jsonObj = JSONObject(body).getJSONObject("data") - if (jsonObj.has("hit")) { - return parseMangaJsonArray(jsonObj.getJSONArray("hit"), true) + val jsonObj = json.parseToJsonElement(body).jsonObject["data"]!!.jsonObject + if (jsonObj["hit"] != null) { + return parseMangaJsonArray(jsonObj["hit"]!!.jsonArray, true) } - return parseMangaJsonArray(jsonObj.getJSONArray("topics"), false) + return parseMangaJsonArray(jsonObj["topics"]!!.jsonArray, false) } // Details @@ -128,32 +136,37 @@ class Kuaikanmanhua : HttpSource() { return Observable.just(sManga) } - override fun mangaDetailsParse(response: Response): SManga { - val data = JSONObject(response.body!!.string()).getJSONObject("data") - val manga = SManga.create() - manga.title = data.getString("title") - manga.thumbnail_url = data.getString("vertical_image_url") - manga.author = data.getJSONObject("user").getString("nickname") - manga.description = data.getString("description") - manga.status = data.getInt("update_status_code") + override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { + val data = json.parseToJsonElement(response.body!!.string()) + .jsonObject["data"]!! + .jsonObject - return manga + title = data["title"]!!.jsonPrimitive.content + thumbnail_url = data["vertical_image_url"]!!.jsonPrimitive.content + author = data["user"]!!.jsonObject["nickname"]!!.jsonPrimitive.content + description = data["description"]!!.jsonPrimitive.content + status = data["update_status_code"]!!.jsonPrimitive.int } // Chapters & Pages override fun chapterListParse(response: Response): List { val document = response.asJsoup() - val chapters = mutableListOf() val script = document.select("script:containsData(comics)").first().data() - val comics = JSONArray(script.substringAfter("comics:").substringBefore(",first_comic_id")) - val variable = script.substringAfter("(function(").substringBefore("){").split(",") - val value = script.substringAfterLast("}}(").substringBefore("));").split(",") + val comics = script.substringAfter("comics:") + .substringBefore(",first_comic_id") + .let { json.parseToJsonElement(it).jsonArray } + val variable = script.substringAfter("(function(") + .substringBefore("){") + .split(",") + val value = script.substringAfterLast("}}(") + .substringBefore("));") + .split(",") - document.select("div.TopicItem").forEachIndexed { index, element -> - chapters.add( + return document.select("div.TopicItem") + .mapIndexed { index, element -> SChapter.create().apply { - val idVar = comics.getJSONObject(index).getString("id") + val idVar = comics[index].jsonObject["id"]!!.jsonPrimitive.content val id = value[variable.indexOf(idVar)] url = "/web/comic/$id" name = element.select("div.title > a").text() @@ -167,11 +180,10 @@ class Kuaikanmanhua : HttpSource() { } else { "20$dateStr" } - date_upload = SimpleDateFormat("yyyy-MM-dd").parse(dateStr).time + date_upload = runCatching { DATE_FORMAT.parse(dateStr)?.time } + .getOrNull() ?: 0L } - ) } - return chapters } override fun fetchPageList(chapter: SChapter): Observable> { @@ -187,18 +199,26 @@ class Kuaikanmanhua : HttpSource() { } override fun pageListParse(response: Response): List { - val pages = ArrayList() val document = response.asJsoup() val script = document.selectFirst("script:containsData(comicImages)").data() - val images = JSONArray(script.substringAfter("comicImages:").substringBefore("},nextComicInfo")) - val variable = script.substringAfter("(function(").substringBefore("){").split(",") - val value = script.substringAfterLast("}}(").substringBefore("));").split(",") - for (i in 0 until images.length()) { - val urlVar = images.getJSONObject(i).getString("url") - val url = value[variable.indexOf(urlVar)].replace("\\u002F", "/").replace("\"", "") - pages.add(Page(i, "", url)) + val images = script.substringAfter("comicImages:") + .substringBefore("},nextComicInfo") + .let { json.parseToJsonElement(it).jsonArray } + val variable = script.substringAfter("(function(") + .substringBefore("){") + .split(",") + val value = script.substringAfterLast("}}(") + .substringBefore("));") + .split(",") + + return images.mapIndexed { index, jsonEl -> + val urlVar = jsonEl.jsonObject["url"]!!.jsonPrimitive.content + val imageUrl = value[variable.indexOf(urlVar)] + .replace("\\u002F", "/") + .replace("\"", "") + + Page(index, "", imageUrl) } - return pages } // Filters @@ -256,5 +276,9 @@ class Kuaikanmanhua : HttpSource() { companion object { const val TOPIC_ID_SEARCH_PREFIX = "topic:" + + private val DATE_FORMAT by lazy { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + } } } diff --git a/src/zh/qiximh/build.gradle b/src/zh/qiximh/build.gradle index 183806d0b..7f86decef 100644 --- a/src/zh/qiximh/build.gradle +++ b/src/zh/qiximh/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'QiXiManhua' pkgNameSuffix = 'zh.qiximh' extClass = '.Qiximh' - extVersionCode = 2 + extVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt b/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt index c36d9d8b3..639fe2efc 100644 --- a/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt +++ b/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt @@ -11,19 +11,30 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.FormBody import okhttp3.Request import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject +import uy.kohesive.injekt.injectLazy class Qiximh : HttpSource() { + override val lang = "zh" + override val supportsLatest = true + override val name = "七夕漫画" + override val baseUrl = "http://qiximh1.com" + // This is hard limit by API - val maxPage = 5 + private val maxPage = 5 + + private val json: Json by injectLazy() // Used in Rank API private enum class RANKTYPE(val rankVal: Int) { @@ -84,20 +95,19 @@ class Qiximh : HttpSource() { } private fun commonDataProcess(origRequest: Request, responseBody: String): MangasPage { - val jsonData = JSONArray(responseBody) + val jsonData = json.parseToJsonElement(responseBody).jsonArray - val mangaArr = mutableListOf() + val mangaArr = jsonData.map { + val targetObj = it.jsonObject - for (i in 0 until jsonData.length()) { - val targetObj = jsonData.getJSONObject(i) - mangaArr.add( - SManga.create().apply { - title = targetObj.get("name") as String - status = SManga.UNKNOWN - thumbnail_url = targetObj.get("imgurl") as String - url = "$baseUrl/${targetObj.get("id")}/" - } - ) + SManga.create().apply { + title = targetObj["name"]!!.jsonPrimitive.content + status = SManga.UNKNOWN + thumbnail_url = targetObj["imgurl"]!!.jsonPrimitive.content + // Extension is wrongly adding the baseURL to the SManga. + // I kept it as it is to avoid user migrations. + url = "$baseUrl/${targetObj["id"]!!.jsonPrimitive.int}" + } } val requestBody = origRequest.body as FormBody @@ -153,36 +163,37 @@ class Qiximh : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { val responseBody = response.body - val mangaArr = mutableListOf() + ?: return MangasPage(emptyList(), false) - if (responseBody != null) { - val responseString = responseBody.string() - if (!responseString.isNullOrEmpty()) { - if (responseString.startsWith("[")) { - // This is to process filter - return commonDataProcess(response.request, responseString) - } else { - val jsonData = JSONObject(responseString) - if (jsonData.get("msg") == "success") { - val jsonArr = jsonData.getJSONArray("search_data") + val responseString = responseBody.string() - for (i in 0 until jsonArr.length()) { - val targetObj = jsonArr.getJSONObject(i) - mangaArr.add( - SManga.create().apply { - title = targetObj.get("name") as String - thumbnail_url = targetObj.get("imgs") as String - url = "$baseUrl/${targetObj.get("id")}/" - } - ) + if (responseString.isNotEmpty()) { + if (responseString.startsWith("[")) { + // This is to process filter + return commonDataProcess(response.request, responseString) + } else { + val jsonData = json.parseToJsonElement(responseString).jsonObject + + if (jsonData["msg"]!!.jsonPrimitive.content == "success") { + val mangaArr = jsonData["search_data"]!!.jsonArray.map { + val targetObj = it.jsonObject + + SManga.create().apply { + title = targetObj["name"]!!.jsonPrimitive.content + thumbnail_url = targetObj["imgs"]!!.jsonPrimitive.content + // Extension is wrongly adding the baseURL to the SManga. + // I kept it as it is to avoid user migrations. + url = "$baseUrl/${targetObj["id"]!!.jsonPrimitive.int}" } } + + return MangasPage(mangaArr, false) } } } // Search does not have pagination - return MangasPage(mangaArr, false) + return MangasPage(emptyList(), false) } // Filter @@ -247,13 +258,16 @@ class Qiximh : HttpSource() { override fun chapterListParse(response: Response): List { val document = response.asJsoup() - // API does not allow retrieve full chapter list, hence the need to parse the chapters from both HTML and API - val htmlChapters = document.select(".catalog_list.row_catalog_list a").map { - SChapter.create().apply { - name = it.text() - url = "$baseUrl${it.attr("href")}" + // API does not allow retrieve full chapter list, hence the need to parse + // the chapters from both HTML and API + val chapterList = document.select(".catalog_list.row_catalog_list a") + .map { + SChapter.create().apply { + name = it.text() + url = "$baseUrl${it.attr("href")}" + } } - } + .toMutableList() val mangaUrl = response.request.url.toString() @@ -267,22 +281,18 @@ class Qiximh : HttpSource() { ) val inlineResponse = client.newCall(request).execute() - val jsonData = JSONArray(inlineResponse.body!!.string()) + val jsonData = json.parseToJsonElement(inlineResponse.body!!.string()).jsonArray - val chapterArr = mutableListOf() - chapterArr.addAll(htmlChapters) + chapterList += jsonData.map { + val targetObj = it.jsonObject - for (i in 0 until jsonData.length()) { - val targetObj = jsonData.getJSONObject(i) - chapterArr.add( - SChapter.create().apply { - name = targetObj.get("chaptername") as String - url = "$mangaUrl${targetObj.get("chapterid")}.html" - } - ) + SChapter.create().apply { + name = targetObj["chaptername"]!!.jsonPrimitive.content + url = "$mangaUrl${targetObj["chapterid"]!!.jsonPrimitive.int}.html" + } } - return chapterArr + return chapterList } // Page