From 6d144464c7d19ba78c71be6d56afefb077d19043 Mon Sep 17 00:00:00 2001 From: Alessandro Jean Date: Thu, 3 Jun 2021 12:55:26 -0300 Subject: [PATCH] Add kotlinx.serialization to more sources (#7398) * Replace Gson in Foolslide, Wpmangastream, Gigaviewer and Bilibili. * Replace Gson in Wpmangareader and Cubari. * Replace Gson in MangaAdventure and NaniScans. * Fix missing pages at Wpmangastream (closes #7395). * Replace Gson in Webtoons and WebtoonsTranslate. --- .../silencescan/src/SilenceScan.kt | 64 ++--- .../tachiyomi/multisrc/foolslide/FoolSlide.kt | 24 +- .../multisrc/foolslide/FoolSlideGenerator.kt | 2 +- .../multisrc/gigaviewer/GigaViewer.kt | 39 +-- .../multisrc/gigaviewer/GigaViewerDto.kt | 26 ++ .../gigaviewer/GigaViewerGenerator.kt | 2 +- .../multisrc/mangadventure/MangAdventure.kt | 68 ++--- .../mangadventure/MangAdventureExtensions.kt | 72 ++--- .../mangadventure/MangAdventureGenerator.kt | 2 +- .../tachiyomi/multisrc/webtoons/Webtoons.kt | 21 +- .../multisrc/webtoons/WebtoonsGenerator.kt | 2 +- .../multisrc/webtoons/WebtoonsTranslate.kt | 155 ++++++----- .../webtoons/WebtoonsTranslateGenerator.kt | 2 +- .../multisrc/wpmangareader/WPMangaReader.kt | 19 +- .../wpmangareader/WPMangaReaderGenerator.kt | 2 +- .../multisrc/wpmangastream/WPMangaStream.kt | 37 +-- .../wpmangastream/WPMangaStreamGenerator.kt | 6 +- src/all/cubari/build.gradle | 3 +- .../tachiyomi/extension/all/cubari/Cubari.kt | 246 +++++++++--------- src/en/bilibilicomics/build.gradle | 3 +- .../en/bilibilicomics/BilibiliComics.kt | 153 ++++++----- .../en/bilibilicomics/BilibiliDto.kt | 59 +++++ src/en/naniscans/build.gradle | 3 +- .../extension/en/naniscans/NaniScans.kt | 147 ++++++----- 24 files changed, 626 insertions(+), 531 deletions(-) create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt create mode 100644 src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt diff --git a/multisrc/overrides/wpmangastream/silencescan/src/SilenceScan.kt b/multisrc/overrides/wpmangastream/silencescan/src/SilenceScan.kt index d2c6f6960..be93a749b 100644 --- a/multisrc/overrides/wpmangastream/silencescan/src/SilenceScan.kt +++ b/multisrc/overrides/wpmangastream/silencescan/src/SilenceScan.kt @@ -1,20 +1,17 @@ package eu.kanade.tachiyomi.extension.pt.silencescan +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream -import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import java.util.concurrent.TimeUnit -import okhttp3.OkHttpClient class SilenceScan : WPMangaStream( "Silence Scan", @@ -29,31 +26,33 @@ class SilenceScan : WPMangaStream( .addNetworkInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) .build() - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoEl = document.select("div.bigcontent, div.animefull").first() + private val json: Json by injectLazy() - author = infoEl.select("b:contains(Autor) + span").text() - artist = infoEl.select("b:contains(Artista) + span").text() + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + val infoEl = document.select("div.bigcontent, div.animefull, div.main-info").first() + + author = infoEl.select("div.imptdt:contains(Autor) i").text() + artist = infoEl.select("div.imptdt:contains(Artista) + i").text() status = parseStatus(infoEl.select("div.imptdt:contains(Status) i").text()) - description = infoEl.select("h2:contains(Sinopse) + div p").joinToString("\n") { it.text() } + description = infoEl.select("h2:contains(Sinopse) + div p:not([class])").joinToString("\n") { it.text() } thumbnail_url = infoEl.select("div.thumb img").imgAttr() - val genres = infoEl.select("b:contains(Gêneros) + span a") - .map { element -> element.text().toLowerCase() } + val genres = infoEl.select("span.mgen a[rel]") + .map { element -> element.text() } .toMutableSet() // add series type(manga/manhwa/manhua/other) thinggy to genre document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { if (it.isEmpty().not() && genres.contains(it).not()) { - genres.add(it.toLowerCase()) + genres.add(it) } } - genre = genres.toList().map { it.capitalize() }.joinToString(", ") + genre = genres.toList().joinToString() // add alternative name to manga description document.select(altNameSelector).firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && it !="N/A" && it != "-") { + if (it.isEmpty().not() && it != "N/A" && it != "-") { description += when { description!!.isEmpty() -> altName + it else -> "\n\n$altName" + it @@ -63,7 +62,14 @@ class SilenceScan : WPMangaStream( } override val seriesTypeSelector = ".imptdt:contains(Tipo) a, a[href*=type\\=]" - override val altNameSelector = ".wd-full:contains(Alt) span" + override val altName: String = "Nome alternativo: " + + override fun parseStatus(element: String?): Int = when { + element == null -> SManga.UNKNOWN + element.contains("em andamento", true) -> SManga.ONGOING + element.contains("completo", true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { name = element.select("span.chapternum").text() @@ -73,24 +79,6 @@ class SilenceScan : WPMangaStream( setUrlWithoutDomain(element.select("div.eph-num > a").attr("href")) } - override fun pageListParse(document: Document): List { - val chapterObj = document.select("script:containsData(ts_reader)").first() - .data() - .substringAfter("run(") - .substringBeforeLast(");") - .let { JsonParser.parseString(it) } - .obj - - if (chapterObj["sources"].array.size() == 0) { - return emptyList() - } - - val firstServerAvailable = chapterObj["sources"].array[0].obj - - return firstServerAvailable["images"].array - .mapIndexed { i, pageUrl -> Page(i, "", pageUrl.string) } - } - override fun getGenreList(): List = listOf( Genre("4-koma", "4-koma"), Genre("Ação", "acao"), @@ -126,4 +114,8 @@ class SilenceScan : WPMangaStream( Genre("Violência sexual", "violencia-sexual"), Genre("Yuri", "yuri") ) + + companion object { + private val PORTUGUESE_LOCALE = Locale("pt", "BR") + } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt index 80625f9b1..83816b13b 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt @@ -1,7 +1,5 @@ package eu.kanade.tachiyomi.multisrc.foolslide -import com.github.salomonbrys.kotson.get -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.FilterList @@ -10,11 +8,16 @@ 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.ParsedHttpSource +import kotlinx.serialization.json.Json +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.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Calendar @@ -33,6 +36,8 @@ abstract class FoolSlide( override val supportsLatest = true + private val json: Json by injectLazy() + override fun popularMangaSelector() = "div.group" override fun popularMangaRequest(page: Int): Request { @@ -166,7 +171,7 @@ abstract class FoolSlide( } open fun parseChapterDate(date: String): Long? { - val lcDate = date.toLowerCase() + val lcDate = date.toLowerCase(Locale.ROOT) if (lcDate.endsWith(" ago")) parseRelativeDate(lcDate)?.let { return it } @@ -272,18 +277,17 @@ abstract class FoolSlide( override fun pageListParse(document: Document): List { val doc = document.toString() - val jsonstr = doc.substringAfter("var pages = ").substringBefore(";") - val json = JsonParser().parse(jsonstr).asJsonArray - val pages = mutableListOf() - json.forEach { + val jsonStr = doc.substringAfter("var pages = ").substringBefore(";") + val pages = json.parseToJsonElement(jsonStr).jsonArray + + return pages.mapIndexed { i, jsonEl -> // Create dummy element to resolve relative URL val absUrl = document.createElement("a") - .attr("href", it["url"].asString) + .attr("href", jsonEl.jsonObject["url"]!!.jsonPrimitive.content) .absUrl("href") - pages.add(Page(pages.size, "", absUrl)) + Page(i, "", absUrl) } - return pages } override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt index 97c5ed5a3..1abdc79ab 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt @@ -10,7 +10,7 @@ class FoolSlideGenerator : ThemeSourceGenerator { override val themeClass = "FoolSlide" - override val baseVersionCode: Int = 1 + override val baseVersionCode: Int = 2 override val sources = listOf( SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt index 6b1c8d7df..fddef9c03 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt @@ -4,11 +4,6 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Rect -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonObject -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList @@ -18,6 +13,10 @@ 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.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor @@ -28,6 +27,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.io.ByteArrayOutputStream import java.io.InputStream import java.text.ParseException @@ -57,6 +57,8 @@ abstract class GigaViewer( .add("Origin", baseUrl) .add("Referer", baseUrl) + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/series", headers) override fun popularMangaSelector(): String = "ul.series-list li a" @@ -143,9 +145,12 @@ abstract class GigaViewer( var result = client.newCall(request).execute() while (result.code != 404) { - val json = result.asJsonObject() - readMoreEndpoint = json["nextUrl"].string - val tempDocument = Jsoup.parse(json["html"].string, response.request.url.toString()) + val jsonResult = json.parseToJsonElement(result.body!!.string()).jsonObject + readMoreEndpoint = jsonResult["nextUrl"]!!.jsonPrimitive.content + val tempDocument = Jsoup.parse( + jsonResult["html"]!!.jsonPrimitive.content, + response.request.url.toString() + ) chapters += tempDocument .select("ul.series-episode-list " + chapterListSelector()) @@ -177,16 +182,16 @@ abstract class GigaViewer( } override fun pageListParse(document: Document): List { - val episodeJson = document.select("script#episode-json") + val episode = document.select("script#episode-json") .attr("data-value") - .let { JsonParser.parseString(it).obj } + .let { json.decodeFromString(it) } - return episodeJson["readableProduct"]["pageStructure"]["pages"].asJsonArray - .filter { it["type"].string == "main" } - .mapIndexed { i, pageObj -> - val imageUrl = pageObj["src"].string.toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("width", pageObj["width"].string) - .addQueryParameter("height", pageObj["height"].string) + return episode.readableProduct.pageStructure.pages + .filter { it.type == "main" } + .mapIndexed { i, page -> + val imageUrl = page.src.toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("width", page.width.toString()) + .addQueryParameter("height", page.height.toString()) .toString() Page(i, document.location(), imageUrl) } @@ -280,8 +285,6 @@ abstract class GigaViewer( } } - private fun Response.asJsonObject(): JsonObject = JsonParser.parseString(body!!.string()).obj - companion object { private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt new file mode 100644 index 000000000..2c2d1f7a8 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerDto.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.multisrc.gigaviewer + +import kotlinx.serialization.Serializable + +@Serializable +data class GigaViewerEpisodeDto( + val readableProduct: GigaViewerReadableProduct +) + +@Serializable +data class GigaViewerReadableProduct( + val pageStructure: GigaViewerPageStructure +) + +@Serializable +data class GigaViewerPageStructure( + val pages: List = emptyList() +) + +@Serializable +data class GigaViewerPage( + val height: Int = 0, + val src: String = "", + val type: String = "", + val width: Int = 0 +) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerGenerator.kt index bb3a43d74..fe5c45384 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewerGenerator.kt @@ -9,7 +9,7 @@ class GigaViewerGenerator : ThemeSourceGenerator { override val themeClass = "GigaViewer" - override val baseVersionCode: Int = 1 + override val baseVersionCode: Int = 2 override val sources = listOf( SingleLang("Comic Gardo", "https://comic-gardo.com", "ja"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventure.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventure.kt index 41f0be0c3..0ab52b922 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventure.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventure.kt @@ -12,12 +12,17 @@ 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.JsonPrimitive +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive 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.text.SimpleDateFormat import java.util.Locale @@ -55,6 +60,8 @@ abstract class MangAdventure( add("Referer", baseUrl) } + private val json: Json by injectLazy() + override fun latestUpdatesRequest(page: Int) = GET("$apiUrl/releases/", headers) @@ -100,58 +107,57 @@ abstract class MangAdventure( } override fun latestUpdatesParse(response: Response) = - JSONArray(response.asString()).run { + json.parseToJsonElement(response.asString()).jsonArray.run { MangasPage( - (0 until length()).map { - val obj = getJSONObject(it) + map { + val obj = it.jsonObject SManga.create().apply { - url = obj.getString("url") - title = obj.getString("title") - thumbnail_url = obj.getString("cover") - // A bit of a hack to sort by date + url = obj["url"]!!.jsonPrimitive.content + title = obj["title"]!!.jsonPrimitive.content + thumbnail_url = obj["cover"]!!.jsonPrimitive.content description = httpDateToTimestamp( - obj.getJSONObject("latest_chapter").getString("date") + obj["latest_chapter"]!!.jsonObject["date"]!!.jsonPrimitive.content ).toString() } - }.sortedByDescending(SManga::description), + } + .sortedByDescending(SManga::description), false ) } override fun chapterListParse(response: Response) = - JSONObject(response.asString()).getJSONObject("volumes").run { - keys().asSequence().flatMap { vol -> - val chapters = getJSONObject(vol) - chapters.keys().asSequence().map { ch -> + json.parseToJsonElement(response.asString()).jsonObject["volumes"]!!.jsonObject.entries + .reversed() + .flatMap { vol -> + vol.value.jsonObject.entries.map { ch -> SChapter.create().fromJSON( - chapters.getJSONObject(ch).also { - it.put("volume", vol) - it.put("chapter", ch) + ch.value.jsonObject.toMutableMap().let { + it["volume"] = JsonPrimitive(vol.key) + it["chapter"] = JsonPrimitive(ch.key) + + JsonObject(it) } ) } - }.toList().reversed() - } + } override fun mangaDetailsParse(response: Response) = - SManga.create().fromJSON(JSONObject(response.asString())) + SManga.create().fromJSON(json.parseToJsonElement(response.asString()).jsonObject) override fun pageListParse(response: Response) = - JSONObject(response.asString()).run { - val url = getString("url") - val root = getString("pages_root") - val arr = getJSONArray("pages_list") - (0 until arr.length()).map { - Page(it, "$url${it + 1}", "$root${arr.getString(it)}") + json.parseToJsonElement(response.asString()).jsonObject.run { + val url = get("url")!!.jsonPrimitive.content + val root = get("pages_root")!!.jsonPrimitive.content + + get("pages_list")!!.jsonArray.mapIndexed { i, jsonEl -> + Page(i, "$url${i + 1}", "$root${jsonEl.jsonPrimitive.content}") } } override fun searchMangaParse(response: Response) = - JSONArray(response.asString()).run { + json.parseToJsonElement(response.asString()).jsonArray.run { MangasPage( - (0 until length()).map { - SManga.create().fromJSON(getJSONObject(it)) - }.sortedBy(SManga::title), + map { SManga.create().fromJSON(it.jsonObject) }, false ) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureExtensions.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureExtensions.kt index 6a9ea3c66..0a8667123 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureExtensions.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureExtensions.kt @@ -3,9 +3,15 @@ package eu.kanade.tachiyomi.multisrc.mangadventure import android.net.Uri import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject import java.text.DecimalFormat /** Returns the body of a response as a `String`. */ @@ -22,16 +28,14 @@ fun Number.format(fmt: String): String = DecimalFormat(fmt).format(this) /** * Joins each value of a given [field] of the array using [sep]. * - * @param field The index of a [JSONArray]. - * When its type is [String], it is treated as the key of a [JSONObject]. + * @param field The index of a [JsonArray]. + * When its type is [String], it is treated as the key of a [JsonObject]. * @param sep The separator used to join the array. * @return The joined string, or `null` if the array is empty. */ -fun JSONArray.joinField(field: Int, sep: String = ", ") = - length().takeIf { it != 0 }?.run { - (0 until this).joinToString(sep) { - getJSONArray(it).getString(field) - } +fun JsonArray.joinField(field: Int, sep: String = ", ") = + size.takeIf { it != 0 }?.run { + joinToString(sep) { it.jsonArray[field].jsonPrimitive.content } } /** @@ -41,11 +45,9 @@ fun JSONArray.joinField(field: Int, sep: String = ", ") = * @param sep The separator used to join the array. * @return The joined string, or `null` if the array is empty. */ -fun JSONArray.joinField(field: String, sep: String = ", ") = - length().takeIf { it != 0 }?.run { - (0 until this).joinToString(sep) { - getJSONObject(it).getString(field) - } +fun JsonArray.joinField(field: String, sep: String = ", ") = + size.takeIf { it != 0 }?.run { + joinToString(sep) { it.jsonObject[field]!!.jsonPrimitive.content } } /** The slug of a manga. */ @@ -53,19 +55,19 @@ val SManga.slug: String get() = Uri.parse(url).lastPathSegment!! /** - * Creates a [SManga] by parsing a [JSONObject]. + * Creates a [SManga] by parsing a [JsonObject]. * * @param obj The object containing the manga info. */ -fun SManga.fromJSON(obj: JSONObject) = apply { - url = obj.getString("url") - title = obj.getString("title") - description = obj.getString("description") - thumbnail_url = obj.getString("cover") - author = obj.getJSONArray("authors").joinField(0) - artist = obj.getJSONArray("artists").joinField(0) - genre = obj.getJSONArray("categories").joinField("name") - status = if (obj.getBoolean("completed")) +fun SManga.fromJSON(obj: JsonObject) = apply { + url = obj["url"]!!.jsonPrimitive.content + title = obj["title"]!!.jsonPrimitive.content + description = obj["description"]!!.jsonPrimitive.content + thumbnail_url = obj["cover"]!!.jsonPrimitive.content + author = obj["authors"]!!.jsonArray.joinField(0) + artist = obj["artists"]!!.jsonArray.joinField(0) + genre = obj["categories"]!!.jsonArray.joinField("name") + status = if (obj["completed"]!!.jsonPrimitive.boolean) SManga.COMPLETED else SManga.ONGOING } @@ -78,18 +80,16 @@ val SChapter.path: String * * @param obj The object containing the chapter info. */ -fun SChapter.fromJSON(obj: JSONObject) = apply { - url = obj.getString("url") - chapter_number = obj.optString("chapter", "-1").toFloat() - date_upload = MangAdventure.httpDateToTimestamp(obj.getString("date")) - scanlator = obj.getJSONArray("groups").joinField("name", " & ") - name = obj.optString( - "full_title", - buildString { - obj.optInt("volume").let { if (it != 0) append("Vol. $it, ") } +fun SChapter.fromJSON(obj: JsonObject) = apply { + url = obj["url"]!!.jsonPrimitive.content + chapter_number = obj["chapter"]?.jsonPrimitive?.content?.toFloatOrNull() ?: -1f + date_upload = MangAdventure.httpDateToTimestamp(obj["date"]!!.jsonPrimitive.content) + scanlator = obj["groups"]!!.jsonArray.joinField("name", " & ") + name = obj["full_title"]?.jsonPrimitive?.contentOrNull + ?: buildString { + obj["volume"]?.jsonPrimitive?.intOrNull?.let { if (it != 0) append("Vol. $it, ") } append("Ch. ${chapter_number.format("#.#")}: ") - append(obj.getString("title")) + append(obj["title"]!!.jsonPrimitive.content) } - ) - if (obj.getBoolean("final")) name += " [END]" + if (obj["final"]!!.jsonPrimitive.boolean) name += " [END]" } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureGenerator.kt index be1785fb1..5c4e066a6 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangadventure/MangAdventureGenerator.kt @@ -9,7 +9,7 @@ class MangAdventureGenerator : ThemeSourceGenerator { override val themeClass = "MangAdventure" - override val baseVersionCode = 1 + override val baseVersionCode = 2 override val sources = listOf( SingleLang("Arc-Relight", "https://arc-relight.com", "en", className = "ArcRelight"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt index 9ccaed842..ed4044505 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt @@ -11,6 +11,9 @@ 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.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.Headers @@ -19,10 +22,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import org.json.JSONObject import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -76,6 +79,8 @@ open class Webtoons( } } + private val json: Json by injectLazy() + override fun popularMangaSelector() = "not using" override fun latestUpdatesSelector() = "div#dailyList > $day li > a" @@ -262,14 +267,16 @@ open class Webtoons( val docUrl = docUrlRegex.find(docString)!!.destructured.toList()[0] val motiontoonPath = motiontoonPathRegex.find(docString)!!.destructured.toList()[0] + val motiontoonResponse = client.newCall(GET(docUrl, headers)).execute() - val motiontoonJson = JSONObject(client.newCall(GET(docUrl, headers)).execute().body!!.string()).getJSONObject("assets").getJSONObject("image") + val motiontoonJson = json.parseToJsonElement(motiontoonResponse.body!!.string()).jsonObject + val motiontoonImages = motiontoonJson["assets"]!!.jsonObject["image"]!!.jsonObject - val keys = motiontoonJson.keys().asSequence().toList().filter { it.contains("layer") } - - return keys.mapIndexed { i, key -> - Page(i, "", motiontoonPath + motiontoonJson.getString(key)) - } + return motiontoonImages.entries + .filter { it.key.contains("layer") } + .mapIndexed { i, entry -> + Page(i, "", motiontoonPath + entry.value.jsonPrimitive.content) + } } companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt index aeacccf34..d24123d9e 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt @@ -10,7 +10,7 @@ class WebtoonsGenerator : ThemeSourceGenerator { override val themeClass = "Webtoons" - override val baseVersionCode: Int = 1 + override val baseVersionCode: Int = 2 override val sources = listOf( MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 29), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt index e8d186e12..ae51da5d7 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt @@ -7,22 +7,32 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +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.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Request import okhttp3.Response -import org.json.JSONObject import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import java.util.ArrayList +import uy.kohesive.injekt.injectLazy -open class WebtoonsTranslate ( +open class WebtoonsTranslate( override val name: String, override val baseUrl: String, override val lang: String, private val translateLangCode: String - ) : Webtoons(name, baseUrl, lang) { +) : Webtoons(name, baseUrl, lang) { + // popularMangaRequest already returns manga sorted by latest update override val supportsLatest = false @@ -30,10 +40,9 @@ open class WebtoonsTranslate ( private val mobileBaseUrl = "https://m.webtoons.com".toHttpUrlOrNull()!! private val thumbnailBaseUrl = "https://mwebtoon-phinf.pstatic.net" - private val pageListUrlPattern = "/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json?titleNo=%s&episodeNo=%d&languageCode=%s&teamVersion=%d" - private val pageSize = 24 + private val json: Json by injectLazy() override fun headersBuilder(): Headers.Builder = super.headersBuilder() .removeAll("Referer") @@ -57,44 +66,38 @@ open class WebtoonsTranslate ( override fun popularMangaParse(response: Response): MangasPage { val offset = response.request.url.queryParameter("offset")!!.toInt() - var totalCount: Int - val mangas = mutableListOf() + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + val responseCode = result["code"]!!.jsonPrimitive.content - JSONObject(response.body!!.string()).let { json -> - json.getString("code").let { code -> - if (code != "000") throw Exception("Error getting popular manga: error code $code") - } - - json.getJSONObject("result").let { results -> - totalCount = results.getInt("totalCount") - - results.getJSONArray("titleList").let { array -> - for (i in 0 until array.length()) { - mangas.add(mangaFromJson(array[i] as JSONObject)) - } - } - } + if (responseCode != "000") { + throw Exception("Error getting popular manga: error code $responseCode") } - return MangasPage(mangas, totalCount > pageSize + offset) + val titles = result["result"]!!.jsonObject + val totalCount = titles["totalCount"]!!.jsonPrimitive.int + + val mangaList = titles["titleList"]!!.jsonArray + .map { mangaFromJson(it.jsonObject) } + + return MangasPage(mangaList, hasNextPage = totalCount > pageSize + offset) } - private fun mangaFromJson(json: JSONObject): SManga { - val relativeThumnailURL = json.getString("thumbnailIPadUrl") - ?: json.getString("thumbnailMobileUrl") + private fun mangaFromJson(manga: JsonObject): SManga { + val relativeThumnailURL = manga["thumbnailIPadUrl"]?.jsonPrimitive?.contentOrNull + ?: manga["thumbnailMobileUrl"]?.jsonPrimitive?.contentOrNull return SManga.create().apply { - title = json.getString("representTitle") - author = json.getString("writeAuthorName") - artist = json.getString("pictureAuthorName") ?: author + title = manga["representTitle"]!!.jsonPrimitive.content + author = manga["writeAuthorName"]!!.jsonPrimitive.content + artist = manga["pictureAuthorName"]?.jsonPrimitive?.contentOrNull ?: author thumbnail_url = if (relativeThumnailURL != null) "$thumbnailBaseUrl$relativeThumnailURL" else null status = SManga.UNKNOWN url = mobileBaseUrl .resolve("/translate/episodeList")!! .newBuilder() - .addQueryParameter("titleNo", json.getInt("titleNo").toString()) + .addQueryParameter("titleNo", manga["titleNo"]!!.jsonPrimitive.int.toString()) .addQueryParameter("languageCode", translateLangCode) - .addQueryParameter("teamVersion", json.optInt("teamVersion", 0).toString()) + .addQueryParameter("teamVersion", (manga["teamVersion"]?.jsonPrimitive?.intOrNull ?: 0).toString()) .build() .toString() } @@ -116,24 +119,18 @@ open class WebtoonsTranslate ( override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = mangaRequest(page, 200) private fun searchMangaParse(response: Response, query: String): MangasPage { - val mangas = mutableListOf() + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + val responseCode = result["code"]!!.jsonPrimitive.content - JSONObject(response.body!!.string()).let { json -> - json.getString("code").let { code -> - if (code != "000") throw Exception("Error getting manga: error code $code") - } - - json.getJSONObject("result").getJSONArray("titleList").let { array -> - for (i in 0 until array.length()) { - (array[i] as JSONObject).let { jsonManga -> - if (jsonManga.getString("representTitle").contains(query, ignoreCase = true)) - mangas.add(mangaFromJson(jsonManga)) - } - } - } + if (responseCode != "000") { + throw Exception("Error getting manga: error code $responseCode") } - return MangasPage(mangas, false) + val mangaList = result["result"]!!.jsonObject["titleList"]!!.jsonArray + .map { mangaFromJson(it.jsonObject) } + .filter { it.title.contains(query, ignoreCase = true) } + + return MangasPage(mangaList, false) } override fun mangaDetailsRequest(manga: SManga): Request { @@ -170,7 +167,7 @@ open class WebtoonsTranslate ( override fun chapterListRequest(manga: SManga): Request { val titleNo = manga.url.toHttpUrlOrNull()!! .queryParameter("titleNo") - val chapterUrl = apiBaseUrl + val chapterListUrl = apiBaseUrl .resolve("/lineWebtoon/ctrans/translatedEpisodes_jsonp.json")!! .newBuilder() .addQueryParameter("titleNo", titleNo) @@ -178,38 +175,40 @@ open class WebtoonsTranslate ( .addQueryParameter("offset", "0") .addQueryParameter("limit", "10000") .toString() - return GET(chapterUrl, mobileHeaders) + return GET(chapterListUrl, mobileHeaders) } override fun chapterListParse(response: Response): List { - val chapterData = response.body!!.string() - val chapterJson = JSONObject(chapterData) - val responseCode = chapterJson.getString("code") + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + val responseCode = result["code"]!!.jsonPrimitive.content + if (responseCode != "000") { - val message = chapterJson.optString("message", "error code $responseCode") + val message = result["message"]?.jsonPrimitive?.content ?: "error code $responseCode" throw Exception("Error getting chapter list: $message") } - val results = chapterJson.getJSONObject("result").getJSONArray("episodes") - val ret = ArrayList() - for (i in 0 until results.length()) { - val result = results.getJSONObject(i) - if (result.getBoolean("translateCompleted")) { - ret.add(parseChapterJson(result)) - } - } - ret.reverse() - return ret + + return result["result"]!!.jsonObject["episodes"]!!.jsonArray + .filter { it.jsonObject["translateCompleted"]!!.jsonPrimitive.boolean } + .map { parseChapterJson(it.jsonObject) } + .reversed() } - private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply { - name = obj.getString("title") + " #" + obj.getString("episodeSeq") - chapter_number = obj.getInt("episodeSeq").toFloat() - date_upload = obj.getLong("updateYmdt") - scanlator = obj.getString("teamVersion") - if (scanlator == "0") { - scanlator = "(wiki)" - } - url = String.format(pageListUrlPattern, obj.getInt("titleNo"), obj.getInt("episodeNo"), obj.getString("languageCode"), obj.getInt("teamVersion")) + private fun parseChapterJson(obj: JsonObject): SChapter = SChapter.create().apply { + name = obj["title"]!!.jsonPrimitive.content + " #" + obj["episodeSeq"]!!.jsonPrimitive.int + chapter_number = obj["episodeSeq"]!!.jsonPrimitive.int.toFloat() + date_upload = obj["updateYmdt"]!!.jsonPrimitive.long + scanlator = obj["teamVersion"]!!.jsonPrimitive.int.takeIf { it != 0 }?.toString() ?: "(wiki)" + + val chapterUrl = apiBaseUrl + .resolve("/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json")!! + .newBuilder() + .addQueryParameter("titleNo", obj["titleNo"]!!.jsonPrimitive.int.toString()) + .addQueryParameter("episodeNo", obj["episodeNo"]!!.jsonPrimitive.int.toString()) + .addQueryParameter("languageCode", obj["languageCode"]!!.jsonPrimitive.content) + .addQueryParameter("teamVersion", obj["teamVersion"]!!.jsonPrimitive.int.toString()) + .toString() + + setUrlWithoutDomain(chapterUrl) } override fun pageListRequest(chapter: SChapter): Request { @@ -217,14 +216,12 @@ open class WebtoonsTranslate ( } override fun pageListParse(response: Response): List { - val pageJson = JSONObject(response.body!!.string()) - val results = pageJson.getJSONObject("result").getJSONArray("imageInfo") - val ret = ArrayList() - for (i in 0 until results.length()) { - val result = results.getJSONObject(i) - ret.add(Page(i, "", result.getString("imageUrl"))) - } - return ret + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + return result["result"]!!.jsonObject["imageInfo"]!!.jsonArray + .mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonObject["imageUrl"]!!.jsonPrimitive.content) + } } override fun getFilterList(): FilterList = FilterList() diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslateGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslateGenerator.kt index fa452baa0..c2b3b0e81 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslateGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslateGenerator.kt @@ -9,7 +9,7 @@ class WebtoonsTranslateGenerator : ThemeSourceGenerator { override val themeClass = "WebtoonsTranslation" - override val baseVersionCode: Int = 1 + override val baseVersionCode: Int = 2 override val sources = listOf( MultiLang("Webtoons.com Translations", "https://translate.webtoons.com", listOf("en", "zh-hans", "zh-hant", "th", "id", "fr", "vi", "ru", "ar", "fil", "de", "hi", "it", "ja", "pt-BR", "tr", "ms", "pl", "pt", "bg", "da", "nl", "ro", "mn", "el", "lt", "cs", "sv", "bn", "fa", "uk", "es"), className = "WebtoonsTranslateFactory", pkgName = "webtoonstranslate", overrideVersionCode = 1), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt index c703776a2..c23fa9d93 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.multisrc.wpmangareader -import android.util.Log import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.Filter @@ -11,16 +10,19 @@ 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.jsonPrimitive import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import org.json.JSONArray import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable import rx.Single +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -36,6 +38,8 @@ abstract class WPMangaReader( override val client: OkHttpClient = network.cloudflareClient + private val json: Json by injectLazy() + // popular override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(5))) override fun popularMangaParse(response: Response) = searchMangaParse(response) @@ -65,7 +69,7 @@ abstract class WPMangaReader( * @returns An identifier for a manga, or null if none could be found */ protected open fun mangaIdFromUrl(s: String): Single { - var baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!! + val baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!! return s.toHttpUrlOrNull()?.let { url -> fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false) = url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() || (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty()) val isMangaUrl = listOf( @@ -226,7 +230,7 @@ abstract class WPMangaReader( open val pageSelector = "div#readerarea img" override fun pageListParse(document: Document): List { - var pages = mutableListOf() + val pages = mutableListOf() document.select(pageSelector) .filterNot { it.attr("src").isNullOrEmpty() } .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) } @@ -236,11 +240,12 @@ abstract class WPMangaReader( val docString = document.toString() val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") + val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0] - val imageList = JSONArray(imageListRegex.find(docString)!!.destructured.toList()[0]) + val imageList = json.parseToJsonElement(imageListJson).jsonArray - for (i in 0 until imageList.length()) { - pages.add(Page(i, "", imageList.getString(i))) + pages += imageList.mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonPrimitive.content) } return pages diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt index faa802ee9..a9c8ccbb7 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt @@ -9,7 +9,7 @@ class WPMangaReaderGenerator : ThemeSourceGenerator { override val themeClass = "WPMangaReader" - override val baseVersionCode: Int = 6 + override val baseVersionCode: Int = 7 override val sources = listOf( SingleLang("Kiryuu", "https://kiryuu.id", "id"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt index b90da89c0..ba9133367 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.multisrc.wpmangastream -//import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor // added to override import android.app.Application import android.content.SharedPreferences import eu.kanade.tachiyomi.network.GET @@ -12,17 +11,20 @@ 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.jsonPrimitive import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import org.json.JSONArray import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Elements import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -67,17 +69,16 @@ abstract class WPMangaStream( private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0) - //private val rateLimitInterceptor = RateLimitInterceptor(4) - override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) - //.addNetworkInterceptor(rateLimitInterceptor) .build() protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") protected fun Elements.imgAttr(): String = this.first().imgAttr() + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/manga/?page=$page&order=popular", headers) } @@ -167,7 +168,7 @@ abstract class WPMangaStream( // add alternative name to manga description document.select(altNameSelector).firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && it !="N/A" && it != "-") { + if (it.isEmpty().not() && it != "N/A" && it != "-") { description += when { description!!.isEmpty() -> altName + it else -> "\n\n$altName" + it @@ -182,7 +183,7 @@ abstract class WPMangaStream( open val altNameSelector = ".alternative, .wd-full:contains(Alt) span, .alter, .seriestualt" open val altName = "Alternative Name" + ": " - protected fun parseStatus(element: String?): Int = when { + protected open fun parseStatus(element: String?): Int = when { element == null -> SManga.UNKNOWN listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED @@ -265,24 +266,26 @@ abstract class WPMangaStream( open val pageSelector = "div#readerarea img" override fun pageListParse(document: Document): List { - var pages = mutableListOf() - document.select(pageSelector) + val htmlPages = document.select(pageSelector) .filterNot { it.attr("src").isNullOrEmpty() } - .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) } - - // Some wpmangastream sites now load pages via javascript - if (pages.isNotEmpty()) { return pages } + .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) } + .toMutableList() val docString = document.toString() val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") + val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0] - val imageList = JSONArray(imageListRegex.find(docString)!!.destructured.toList()[0]) + val imageList = json.parseToJsonElement(imageListJson).jsonArray - for (i in 0 until imageList.length()) { - pages.add(Page(i, "", imageList.getString(i))) + val scriptPages = imageList.mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonPrimitive.content) } - return pages + if (htmlPages.size < scriptPages.size) { + htmlPages += scriptPages + } + + return htmlPages } override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt index af412663d..f34af882c 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt @@ -9,10 +9,10 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { override val themeClass = "WPMangaStream" - override val baseVersionCode: Int = 4 + override val baseVersionCode: Int = 5 override val sources = listOf( - SingleLang("Asura Scans", "override url", "en", overrideVersionCode = 1), + SingleLang("Asura Scans", "https://www.asurascans.com", "en", overrideVersionCode = 1), SingleLang("KlanKomik", "https://klankomik.com", "id"), SingleLang("MasterKomik", "https://masterkomik.com", "id"), SingleLang("Kaisar Komik", "https://kaisarkomik.com", "id"), @@ -34,7 +34,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { SingleLang("MangaSwat", "https://mangaswat.com", "ar"), SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1), SingleLang("Manga Pro Z", "https://mangaproz.com", "ar"), - SingleLang("Silence Scan", "https://silencescan.net", "pt-BR", overrideVersionCode = 1), + SingleLang("Silence Scan", "https://silencescan.net", "pt-BR", overrideVersionCode = 2), SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans"), SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"), SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2), diff --git a/src/all/cubari/build.gradle b/src/all/cubari/build.gradle index e8827699e..fdf6d9269 100644 --- a/src/all/cubari/build.gradle +++ b/src/all/cubari/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Cubari' pkgNameSuffix = "all.cubari" extClass = '.CubariFactory' - extVersionCode = 5 + extVersionCode = 6 libVersion = '1.2' } diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt index 76013bde5..e2abb8acf 100644 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt +++ b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt @@ -11,21 +11,32 @@ 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.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +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.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy open class Cubari(override val lang: String) : HttpSource() { final override val name = "Cubari" + final override val baseUrl = "https://cubari.moe" + final override val supportsLatest = true + private val json: Json by injectLazy() + override fun headersBuilder() = Headers.Builder().apply { add( "User-Agent", @@ -50,7 +61,8 @@ open class Cubari(override val lang: String) : HttpSource() { } override fun latestUpdatesParse(response: Response): MangasPage { - return parseMangaList(JSONArray(response.body!!.string()), SortType.UNPINNED) + val result = json.parseToJsonElement(response.body!!.string()).jsonArray + return parseMangaList(result, SortType.UNPINNED) } override fun popularMangaRequest(page: Int): Request { @@ -67,7 +79,8 @@ open class Cubari(override val lang: String) : HttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - return parseMangaList(JSONArray(response.body!!.string()), SortType.PINNED) + val result = json.parseToJsonElement(response.body!!.string()).jsonArray + return parseMangaList(result, SortType.PINNED) } override fun fetchMangaDetails(manga: SManga): Observable { @@ -86,7 +99,8 @@ open class Cubari(override val lang: String) : HttpSource() { } private fun mangaDetailsParse(response: Response, manga: SManga): SManga { - return parseMangaFromApi(JSONObject(response.body!!.string()), manga) + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + return parseManga(result, manga) } override fun fetchChapterList(manga: SManga): Observable> { @@ -139,7 +153,7 @@ open class Cubari(override val lang: String) : HttpSource() { GET("$baseUrl${chapter.url}", headers) } else -> { - var url = chapter.url.split("/") + val url = chapter.url.split("/") val source = url[2] val slug = url[3] @@ -150,55 +164,49 @@ open class Cubari(override val lang: String) : HttpSource() { private fun directPageListParse(response: Response): List { val res = response.body!!.string() - val pages = JSONArray(res) - val pageArray = ArrayList() + val pages = json.parseToJsonElement(res).jsonArray - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") + return pages.mapIndexed { i, jsonEl -> + val page = if (jsonEl is JsonObject) { + jsonEl.jsonObject["src"]!!.jsonPrimitive.content } else { - pages[i] + jsonEl.jsonPrimitive.content } - pageArray.add(Page(i + 1, "", page.toString())) + + Page(i, "", page) } - return pageArray } private fun seriesJsonPageListParse(response: Response, chapter: SChapter): List { - val res = response.body!!.string() - val json = JSONObject(res) - val groups = json.getJSONObject("groups") - val groupIter = groups.keys() - val groupMap = HashMap() + val jsonObj = json.parseToJsonElement(response.body!!.string()).jsonObject + val groups = jsonObj["groups"]!!.jsonObject + val groupMap = groups.entries + .map { Pair(it.value.jsonPrimitive.content, it.key) } + .toMap() - while (groupIter.hasNext()) { - val groupKey = groupIter.next() - groupMap[groups.getString(groupKey)] = groupKey - } + val chapters = jsonObj["chapters"]!!.jsonObject - val chapters = json.getJSONObject("chapters") - - val pages = if (chapters.has(chapter.chapter_number.toString())) { - chapters - .getJSONObject(chapter.chapter_number.toString()) - .getJSONObject("groups") - .getJSONArray(groupMap[chapter.scanlator]) + val pages = if (chapters[chapter.chapter_number.toString()] != null) { + chapters[chapter.chapter_number.toString()]!! + .jsonObject["groups"]!! + .jsonObject[groupMap[chapter.scanlator]]!! + .jsonArray } else { - chapters - .getJSONObject(chapter.chapter_number.toInt().toString()) - .getJSONObject("groups") - .getJSONArray(groupMap[chapter.scanlator]) + chapters[chapter.chapter_number.toInt().toString()]!! + .jsonObject["groups"]!! + .jsonObject[groupMap[chapter.scanlator]]!! + .jsonArray } - val pageArray = ArrayList() - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") + + return pages.mapIndexed { i, jsonEl -> + val page = if (jsonEl is JsonObject) { + jsonEl.jsonObject["src"]!!.jsonPrimitive.content } else { - pages[i] + jsonEl.jsonPrimitive.content } - pageArray.add(Page(i + 1, "", page.toString())) + + Page(i, "", page) } - return pageArray } // Stub @@ -241,125 +249,105 @@ open class Cubari(override val lang: String) : HttpSource() { } private fun searchMangaParse(response: Response, query: String): MangasPage { - return parseSearchList(JSONObject(response.body!!.string()), query) + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + return parseSearchList(result, query) } // ------------- Helpers and whatnot --------------- private fun parseChapterList(payload: String, manga: SManga): List { - val json = JSONObject(payload) - val groups = json.getJSONObject("groups") - val chapters = json.getJSONObject("chapters") - val seriesSlug = json.getString("slug") + val jsonObj = json.parseToJsonElement(payload).jsonObject + val groups = jsonObj["groups"]!!.jsonObject + val chapters = jsonObj["chapters"]!!.jsonObject + val seriesSlug = jsonObj["slug"]!!.jsonPrimitive.content - - val chapterList = ArrayList() - - val iter = chapters.keys() - val seriesPrefs = Injekt.get().getSharedPreferences("source_${id}_updateTime:$seriesSlug", 0) val seriesPrefsEditor = seriesPrefs.edit() - while (iter.hasNext()) { - val chapterNum = iter.next() - val chapterObj = chapters.getJSONObject(chapterNum) - val chapterGroups = chapterObj.getJSONObject("groups") - val groupsIter = chapterGroups.keys() + val chapterList = chapters.entries.flatMap { chapterEntry -> + val chapterNum = chapterEntry.key + val chapterObj = chapterEntry.value.jsonObject + val chapterGroups = chapterObj["groups"]!!.jsonObject - while (groupsIter.hasNext()) { - val groupNum = groupsIter.next() - val chapter = SChapter.create() + chapterGroups.entries.map { groupEntry -> + val groupNum = groupEntry.key - chapter.scanlator = groups.getString(groupNum) - - //Api for gist (and some others maybe) doesn't give a "release_date" so we will use the Manga update time. - //So when new chapter comes the manga will go on top if sortinf is set to "Last Updated" - //Code by ivaniskandar (Implemented on CatManga extension.) - - if (chapterObj.has("release_date")) { - chapter.date_upload = - chapterObj.getJSONObject("release_date").getLong(groupNum) * 1000 - } else { - val currentTimeMillis = System.currentTimeMillis() - if (!seriesPrefs.contains(chapterNum)) { - seriesPrefsEditor.putLong(chapterNum, currentTimeMillis) + SChapter.create().apply { + scanlator = groups[groupNum]!!.jsonPrimitive.content + chapter_number = chapterNum.toFloatOrNull() ?: -1f + + date_upload = if (chapterObj["release_date"] != null) { + chapterObj["release_date"]!!.jsonObject[groupNum]!!.jsonPrimitive.long * 1000 + } else { + val currentTimeMillis = System.currentTimeMillis() + + if (!seriesPrefs.contains(chapterNum)) { + seriesPrefsEditor.putLong(chapterNum, currentTimeMillis) + } + + seriesPrefs.getLong(chapterNum, currentTimeMillis) } - chapter.date_upload = seriesPrefs.getLong(chapterNum, currentTimeMillis) - } - chapter.name = if (chapterObj.getString("volume") != "Uncategorized") { - - "Vol. " + chapterObj.getString("volume") + " Ch. " + chapterNum + " - " + chapterObj.getString("title") - //Output "Vol. 1 Ch. 1 - Chapter Name" - - } else { - - "Ch. " + chapterNum + " - " + chapterObj.getString("title") - //Output "Ch. 1 - Chapter Name" - - } - chapter.chapter_number = chapterNum.toFloat() - chapter.url = - if (chapterGroups.optJSONArray(groupNum) != null) { + + name = if (chapterObj["volume"]!!.jsonPrimitive.content != "Uncategorized") { + // Output "Vol. 1 Ch. 1 - Chapter Name" + "Vol. " + chapterObj["volume"]!!.jsonPrimitive.content + " Ch. " + + chapterNum + " - " + chapterObj["title"]!!.jsonPrimitive.content + } else { + // Output "Ch. 1 - Chapter Name" + "Ch. " + chapterNum + " - " + chapterObj["title"]!!.jsonPrimitive.content + } + + url = if (chapterGroups[groupNum] != null) { "${manga.url}/$chapterNum/$groupNum" } else { - chapterGroups.getString(groupNum) + chapterGroups[groupNum]!!.jsonPrimitive.content } - chapterList.add(chapter) + } } } seriesPrefsEditor.apply() - return chapterList.reversed() + + return chapterList.sortedByDescending { it.chapter_number } } - private fun parseMangaList(payload: JSONArray, sortType: SortType): MangasPage { - val mangas = ArrayList() - - for (i in 0 until payload.length()) { - val json = payload.getJSONObject(i) - val pinned = json.getBoolean("pinned") + private fun parseMangaList(payload: JsonArray, sortType: SortType): MangasPage { + val mangaList = payload.mapNotNull { jsonEl -> + val jsonObj = jsonEl.jsonObject + val pinned = jsonObj["pinned"]!!.jsonPrimitive.boolean if (sortType == SortType.PINNED && pinned) { - mangas.add(parseMangaFromRemoteStorage(json)) + parseManga(jsonObj) } else if (sortType == SortType.UNPINNED && !pinned) { - mangas.add(parseMangaFromRemoteStorage(json)) + parseManga(jsonObj) + } else { + null } } - return MangasPage(mangas, false) + return MangasPage(mangaList, false) } - private fun parseSearchList(payload: JSONObject, query: String): MangasPage { - val mangas = ArrayList() - val tempManga = SManga.create() - tempManga.url = "/read/$query" - mangas.add(parseMangaFromApi(payload, tempManga)) - return MangasPage(mangas, false) + private fun parseSearchList(payload: JsonObject, query: String): MangasPage { + val tempManga = SManga.create().apply { + url = "/read/$query" + } + + val mangaList = listOf(parseManga(payload, tempManga)) + + return MangasPage(mangaList, false) } - private fun parseMangaFromRemoteStorage(json: JSONObject): SManga { - val manga = SManga.create() - manga.title = json.getString("title") - manga.artist = json.optString("artist", ARTIST_FALLBACK) - manga.author = json.optString("author", AUTHOR_FALLBACK) - manga.description = json.optString("description", DESCRIPTION_FALLBACK) - manga.url = json.getString("url") - manga.thumbnail_url = json.getString("coverUrl") - - return manga - } - - private fun parseMangaFromApi(json: JSONObject, mangaReference: SManga): SManga { - val manga = SManga.create() - manga.title = json.getString("title") - manga.artist = json.optString("artist", ARTIST_FALLBACK) - manga.author = json.optString("author", AUTHOR_FALLBACK) - manga.description = json.optString("description", DESCRIPTION_FALLBACK) - manga.url = mangaReference.url - manga.thumbnail_url = json.optString("cover", "") - - return manga - } + private fun parseManga(jsonObj: JsonObject, mangaReference: SManga? = null): SManga = + SManga.create().apply { + title = jsonObj["title"]!!.jsonPrimitive.content + artist = jsonObj["artist"]?.jsonPrimitive?.content ?: ARTIST_FALLBACK + author = jsonObj["author"]?.jsonPrimitive?.content ?: AUTHOR_FALLBACK + description = jsonObj["description"]?.jsonPrimitive?.content ?: DESCRIPTION_FALLBACK + url = mangaReference?.url ?: jsonObj["url"]!!.jsonPrimitive.content + thumbnail_url = jsonObj["coverUrl"]?.jsonPrimitive?.content + ?: jsonObj["cover"]?.jsonPrimitive?.content ?: "" + } // ----------------- Things we aren't supporting ----------------- diff --git a/src/en/bilibilicomics/build.gradle b/src/en/bilibilicomics/build.gradle index 8f975f56c..46bd6f846 100644 --- a/src/en/bilibilicomics/build.gradle +++ b/src/en/bilibilicomics/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Bilibili Comics' pkgNameSuffix = 'en.bilibilicomics' extClass = '.BilibiliComics' - extVersionCode = 2 + extVersionCode = 3 libVersion = '1.2' containsNsfw = true } diff --git a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt index fbd811d9b..2afb7634d 100644 --- a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt +++ b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt @@ -1,16 +1,5 @@ package eu.kanade.tachiyomi.extension.en.bilibilicomics -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.bool -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.jsonArray -import com.github.salomonbrys.kotson.jsonObject -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.POST @@ -21,6 +10,12 @@ 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.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType @@ -30,6 +25,7 @@ import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import org.jsoup.Jsoup import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -55,14 +51,16 @@ class BilibiliComics : HttpSource() { .add("Origin", baseUrl) .add("Referer", "$baseUrl/") + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { - val requestPayload = jsonObject( - "id" to FEATURED_ID, - "isAll" to 0, - "page_num" to 1, - "page_size" to 6 - ) - val requestBody = requestPayload.toString().toRequestBody(JSON_CONTENT_TYPE) + val requestPayload = buildJsonObject { + put("id", FEATURED_ID) + put("isAll", 0) + put("page_num", 1) + put("page_size", 6) + } + val requestBody = requestPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", requestBody.contentLength().toString()) @@ -77,36 +75,36 @@ class BilibiliComics : HttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) - if (jsonResponse["code"].int != 0) { + if (result.code != 0) { return MangasPage(emptyList(), hasNextPage = false) } - val comicList = jsonResponse["data"]["roll_six_comics"].array + val comicList = result.data!!.rollSixComics .map(::popularMangaFromObject) return MangasPage(comicList, hasNextPage = false) } - private fun popularMangaFromObject(obj: JsonElement): SManga = SManga.create().apply { - title = obj["title"].string - thumbnail_url = obj["vertical_cover"].string - url = "/detail/mc" + obj["comic_id"].int + private fun popularMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply { + title = comic.title + thumbnail_url = comic.verticalCover + url = "/detail/mc${comic.comicId}" } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val jsonPayload = jsonObject( - "area_id" to -1, - "is_finish" to -1, - "is_free" to 1, - "key_word" to query, - "order" to 0, - "page_num" to page, - "page_size" to 9, - "style_id" to -1 - ) - val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) + val jsonPayload = buildJsonObject { + put("area_id", -1) + put("is_finish", -1) + put("is_free", 1) + put("key_word", query) + put("order", 0) + put("page_num", page) + put("page_size", 9) + put("style_id", -1) + } + val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val refererUrl = "$baseUrl/search".toHttpUrl().newBuilder() .addQueryParameter("keyword", query) @@ -126,22 +124,22 @@ class BilibiliComics : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) - if (jsonResponse["code"].int != 0) { + if (result.code != 0) { return MangasPage(emptyList(), hasNextPage = false) } - val comicList = jsonResponse["data"]["list"].array + val comicList = result.data!!.list .map(::searchMangaFromObject) return MangasPage(comicList, hasNextPage = false) } - private fun searchMangaFromObject(obj: JsonElement): SManga = SManga.create().apply { - title = Jsoup.parse(obj["title"].string).text() - thumbnail_url = obj["vertical_cover"].string - url = "/detail/mc" + obj["id"].int + private fun searchMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply { + title = Jsoup.parse(comic.title).text() + thumbnail_url = comic.verticalCover + url = "/detail/mc${comic.id}" } // Workaround to allow "Open in browser" use the real URL. @@ -156,8 +154,8 @@ class BilibiliComics : HttpSource() { private fun mangaDetailsApiRequest(manga: SManga): Request { val comicId = manga.url.substringAfterLast("/mc").toInt() - val jsonPayload = jsonObject("comic_id" to comicId) - val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) + val jsonPayload = buildJsonObject { put("comic_id", comicId) } + val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", requestBody.contentLength().toString()) @@ -173,44 +171,45 @@ class BilibiliComics : HttpSource() { } override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) + val comic = result.data!! - title = jsonResponse["data"]["title"].string - author = jsonResponse["data"]["author_name"].array.joinToString { it.string } - status = if (jsonResponse["data"]["is_finish"].int == 1) SManga.COMPLETED else SManga.ONGOING - genre = jsonResponse["data"]["styles"].array.joinToString { it.string } - description = jsonResponse["data"]["classic_lines"].string - thumbnail_url = jsonResponse["data"]["vertical_cover"].string + title = comic.title + author = comic.authorName.joinToString() + status = if (comic.isFinish == 1) SManga.COMPLETED else SManga.ONGOING + genre = comic.styles.joinToString() + description = comic.classicLines + thumbnail_url = comic.verticalCover } // Chapters are available in the same url of the manga details. override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) override fun chapterListParse(response: Response): List { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) - if (jsonResponse["code"].int != 0) + if (result.code != 0) return emptyList() - return jsonResponse["data"]["ep_list"].array - .filter { ep -> ep["is_locked"].bool.not() } - .map { ep -> chapterFromObject(ep, jsonResponse["data"]["id"].int) } + return result.data!!.episodeList + .filter { episode -> episode.isLocked.not() } + .map { ep -> chapterFromObject(ep, result.data.id) } } - private fun chapterFromObject(obj: JsonElement, comicId: Int): SChapter = SChapter.create().apply { - name = "Ep. " + obj["ord"].float.toString().removeSuffix(".0") + - " - " + obj["title"].string - chapter_number = obj["ord"].float + private fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply { + name = "Ep. " + episode.order.toString().removeSuffix(".0") + + " - " + episode.title + chapter_number = episode.order scanlator = this@BilibiliComics.name - date_upload = obj["pub_time"].string.substringBefore("T").toDate() - url = "/mc" + comicId + "/" + obj["id"].int + date_upload = episode.publicationTime.substringBefore("T").toDate() + url = "/mc$comicId/${episode.id}" } override fun pageListRequest(chapter: SChapter): Request { val chapterId = chapter.url.substringAfterLast("/").toInt() - val jsonPayload = jsonObject("ep_id" to chapterId) - val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) + val jsonPayload = buildJsonObject { put("ep_id", chapterId) } + val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", requestBody.contentLength().toString()) @@ -226,19 +225,21 @@ class BilibiliComics : HttpSource() { } override fun pageListParse(response: Response): List { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) - if (jsonResponse["code"].int != 0) { + if (result.code != 0) { return emptyList() } - return jsonResponse["data"]["images"].array - .mapIndexed { i, page -> Page(i, page["path"].string, "") } + return result.data!!.images + .mapIndexed { i, page -> Page(i, page.path, "") } } override fun imageUrlRequest(page: Page): Request { - val jsonPayload = jsonObject("urls" to jsonArray(page.url).toString()) - val requestBody = jsonPayload.toString().toRequestBody(JSON_CONTENT_TYPE) + val jsonPayload = buildJsonObject { + put("urls", buildJsonArray { add(page.url) }.toString()) + } + val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", requestBody.contentLength().toString()) @@ -253,10 +254,10 @@ class BilibiliComics : HttpSource() { } override fun imageUrlParse(response: Response): String { - val jsonResponse = response.asJson().obj + val result = json.decodeFromString>>(response.body!!.string()) + val page = result.data!![0] - return jsonResponse["data"][0]["url"].string - .plus("?token=" + jsonResponse["data"][0]["token"].string) + return "${page.url}?token=${page.token}" } override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") @@ -271,14 +272,12 @@ class BilibiliComics : HttpSource() { } } - private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string()) - companion object { private const val BASE_API_ENDPOINT = "twirp/comic.v1.Comic" private const val ACCEPT_JSON = "application/json, text/plain, */*" - private val JSON_CONTENT_TYPE = "application/json;charset=UTF-8".toMediaType() + private val JSON_MEDIA_TYPE = "application/json;charset=UTF-8".toMediaType() private const val FEATURED_ID = 3 diff --git a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt new file mode 100644 index 000000000..e7dc0175a --- /dev/null +++ b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.extension.en.bilibilicomics + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BilibiliResultDto( + val code: Int = 0, + val data: T? = null, + @SerialName("msg") val message: String = "" +) + +@Serializable +data class BilibiliFeaturedDto( + @SerialName("roll_six_comics") val rollSixComics: List = emptyList() +) + +@Serializable +data class BilibiliSearchDto( + val list: List = emptyList() +) + +@Serializable +data class BilibiliComicDto( + @SerialName("author_name") val authorName: List = emptyList(), + @SerialName("classic_lines") val classicLines: String = "", + @SerialName("comic_id") val comicId: Int = 0, + @SerialName("ep_list") val episodeList: List = emptyList(), + val id: Int = 0, + @SerialName("is_finish") val isFinish: Int = 0, + val styles: List = emptyList(), + val title: String, + @SerialName("vertical_cover") val verticalCover: String = "" +) + +@Serializable +data class BilibiliEpisodeDto( + val id: Int, + @SerialName("is_locked") val isLocked: Boolean, + @SerialName("ord") val order: Float, + @SerialName("pub_time") val publicationTime: String, + val title: String +) + +@Serializable +data class BilibiliReader( + val images: List = emptyList() +) + +@Serializable +data class BilibiliImageDto( + val path: String +) + +@Serializable +data class BilibiliPageDto( + val token: String, + val url: String +) diff --git a/src/en/naniscans/build.gradle b/src/en/naniscans/build.gradle index 87805eb7f..e2f9b97cb 100644 --- a/src/en/naniscans/build.gradle +++ b/src/en/naniscans/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'NANI? Scans' pkgNameSuffix = 'en.naniscans' extClass = '.NaniScans' - extVersionCode = 5 + extVersionCode = 6 libVersion = '1.2' } diff --git a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt index ef1775a15..3ac88918b 100644 --- a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt +++ b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt @@ -8,59 +8,73 @@ 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.int +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 rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale class NaniScans : HttpSource() { - override val baseUrl = "https://naniscans.com" - override val lang = "en" + override val name = "NANI? Scans" + + override val baseUrl = "https://naniscans.com" + + override val lang = "en" + override val supportsLatest = true + override val versionId = 2 private val dateParser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) + private val json: Json by injectLazy() + override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) override fun latestUpdatesParse(response: Response): MangasPage { - val titlesJson = JSONArray(response.body!!.string()) - val mangaMap = mutableMapOf() + val titlesJson = json.parseToJsonElement(response.body!!.string()).jsonArray - for (i in 0 until titlesJson.length()) { - val manga = titlesJson.getJSONObject(i) + val mangaList = titlesJson + .mapNotNull { + val manga = it.jsonObject - if (manga.getString("type") != "Comic") - continue + if (manga["type"]!!.jsonPrimitive.content != "Comic") { + return@mapNotNull null + } - var date = manga.getString("updatedAt") + val date = manga["updatedAt"]!!.jsonPrimitive.content.let { datePrimitive -> + if (datePrimitive == "null") "2018-04-10T17:38:56" else datePrimitive + } - if (date == "null") - date = "2018-04-10T17:38:56" + Pair(dateParser.parse(date)!!.time, getBareSManga(manga)) + } + .sortedByDescending { it.first } + .map { it.second } - mangaMap[dateParser.parse(date)!!.time] = getBareSManga(manga) - } - - return MangasPage(mangaMap.toSortedMap().values.toList().asReversed(), false) + return MangasPage(mangaList, false) } override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles") override fun popularMangaParse(response: Response): MangasPage { - val titlesJson = JSONArray(response.body!!.string()) - val mangaList = mutableListOf() + val titlesJson = json.parseToJsonElement(response.body!!.string()).jsonArray - for (i in 0 until titlesJson.length()) { - val manga = titlesJson.getJSONObject(i) + val mangaList = titlesJson.mapNotNull { + val manga = it.jsonObject - if (manga.getString("type") != "Comic") - continue - - mangaList.add(getBareSManga(manga)) + if (manga["type"]!!.jsonPrimitive.content == "Comic") { + getBareSManga(manga) + } else { + null + } } return MangasPage(mangaList, false) @@ -77,65 +91,54 @@ class NaniScans : HttpSource() { override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}") override fun mangaDetailsParse(response: Response): SManga { - val titleJson = JSONObject(response.body!!.string()) + val titleJson = json.parseToJsonElement(response.body!!.string()).jsonObject - if (titleJson.getString("type") != "Comic") + if (titleJson["type"]!!.jsonPrimitive.content != "Comic") throw UnsupportedOperationException("Tachiyomi only supports Comics.") return SManga.create().apply { - title = titleJson.getString("name") - artist = titleJson.getString("artist") - author = titleJson.getString("author") - description = titleJson.getString("synopsis") - status = getStatus(titleJson.getString("status")) - thumbnail_url = "$baseUrl${titleJson.getString("coverUrl")}" - genre = titleJson.getJSONArray("tags").join(", ").replace("\"", "") - url = titleJson.getString("id") + title = titleJson["name"]!!.jsonPrimitive.content + artist = titleJson["artist"]!!.jsonPrimitive.content + author = titleJson["author"]!!.jsonPrimitive.content + description = titleJson["synopsis"]!!.jsonPrimitive.content + status = getStatus(titleJson["status"]!!.jsonPrimitive.content) + thumbnail_url = "$baseUrl${titleJson["coverUrl"]!!.jsonPrimitive.content}" + genre = titleJson["tags"]!!.jsonArray.joinToString { it.jsonPrimitive.content } + url = titleJson["id"]!!.jsonPrimitive.content } } override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}") override fun chapterListParse(response: Response): List { - val titleJson = JSONObject(response.body!!.string()) + val titleJson = json.parseToJsonElement(response.body!!.string()).jsonObject - if (titleJson.getString("type") != "Comic") + if (titleJson["type"]!!.jsonPrimitive.content != "Comic") throw UnsupportedOperationException("Tachiyomi only supports Comics.") - val chaptersJson = titleJson.getJSONArray("chapters") - val chaptersList = mutableListOf() + val chaptersJson = titleJson["chapters"]!!.jsonArray - for (i in 0 until chaptersJson.length()) { - val chapter = chaptersJson.getJSONObject(i) + return titleJson["chapters"]!!.jsonArray.map { + val chapter = it.jsonObject - chaptersList.add( - SChapter.create().apply { - chapter_number = chapter.get("number").toString().toFloat() - name = getChapterTitle(chapter) - date_upload = dateParser.parse(chapter.getString("releaseDate"))!!.time - url = "${titleJson.getString("id")}_${chapter.getString("id")}" - } - ) + SChapter.create().apply { + chapter_number = chapter["number"]!!.jsonPrimitive.content.toFloatOrNull() ?: -1f + name = getChapterTitle(chapter) + date_upload = dateParser.parse(chapter["releaseDate"]!!.jsonPrimitive.content)!!.time + url = "${titleJson["id"]!!.jsonPrimitive.content} ${chapter["id"]!!.jsonPrimitive.content}" + } } - - return chaptersList } override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api/chapters/${chapter.url.substring(37, 73)}") override fun pageListParse(response: Response): List { - val jsonObject = JSONObject(response.body!!.string()) + val jsonObject = json.parseToJsonElement(response.body!!.string()).jsonObject - val pagesJson = jsonObject.getJSONArray("pages") - val pagesList = mutableListOf() - - for (i in 0 until pagesJson.length()) { - val item = pagesJson.getJSONObject(i) - - pagesList.add(Page(item.getInt("number"), "", "$baseUrl${item.getString("pageUrl")}")) + return jsonObject["pages"]!!.jsonArray.map { + val item = it.jsonObject + Page(item["number"]!!.jsonPrimitive.int, "", "$baseUrl${item["pageUrl"]!!.jsonPrimitive.content}") } - - return pagesList } override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used.") @@ -146,23 +149,23 @@ class NaniScans : HttpSource() { else -> SManga.UNKNOWN } - private fun getChapterTitle(chapter: JSONObject): String { + private fun getChapterTitle(chapter: JsonObject): String { val chapterName = mutableListOf() - if (chapter.getString("volume") != "null") { - chapterName.add("Vol." + chapter.getString("volume")) + if (chapter["volume"]!!.jsonPrimitive.content != "null") { + chapterName.add("Vol." + chapter["volume"]!!.jsonPrimitive.content) } - if (chapter.getString("number") != "null") { - chapterName.add("Ch." + chapter.getString("number")) + if (chapter["number"]!!.jsonPrimitive.content != "null") { + chapterName.add("Ch." + chapter["number"]!!.jsonPrimitive.content) } - if (chapter.getString("name") != "null") { + if (chapter["name"]!!.jsonPrimitive.content != "null") { if (chapterName.isNotEmpty()) { chapterName.add("-") } - chapterName.add(chapter.getString("name")) + chapterName.add(chapter["name"]!!.jsonPrimitive.content) } if (chapterName.isEmpty()) { @@ -172,9 +175,9 @@ class NaniScans : HttpSource() { return chapterName.joinToString(" ") } - private fun getBareSManga(manga: JSONObject): SManga = SManga.create().apply { - title = manga.getString("name") - thumbnail_url = "$baseUrl${manga.getString("coverUrl")}" - url = manga.getString("id") + private fun getBareSManga(manga: JsonObject): SManga = SManga.create().apply { + title = manga["name"]!!.jsonPrimitive.content + thumbnail_url = "$baseUrl${manga["coverUrl"]!!.jsonPrimitive.content}" + url = manga["id"]!!.jsonPrimitive.content } }