From 38f2f4a7672ca90c715de0aab5ad5c2dbf77142a Mon Sep 17 00:00:00 2001
From: Alessandro Jean <alessandrojean@gmail.com>
Date: Thu, 19 Aug 2021 06:57:58 -0300
Subject: [PATCH] Replace org.json in PizzaReader. (#8645)

---
 .../multisrc/pizzareader/PizzaReader.kt       | 148 +++++++++++-------
 .../multisrc/pizzareader/PizzaReaderDto.kt    |  56 +++++++
 .../pizzareader/PizzaReaderExtensions.kt      |  94 -----------
 .../pizzareader/PizzaReaderGenerator.kt       |   2 +-
 4 files changed, 152 insertions(+), 148 deletions(-)
 create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderDto.kt
 delete mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt

diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt
index 035fc4016..57de3377d 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt
@@ -8,23 +8,31 @@ 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 okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.Request
 import okhttp3.Response
-import org.json.JSONObject
 import rx.Observable
+import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.Locale
 
 abstract class PizzaReader(
     override val name: String,
     override val baseUrl: String,
     override val lang: String,
-    private val apiPath: String = "/api"
+    private val apiPath: String = "/api",
+    private val dateParser: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ITALY)
 ) : HttpSource() {
 
     override val supportsLatest = true
 
     open val apiUrl by lazy { "$baseUrl$apiPath" }
 
+    protected open val json: Json by injectLazy()
+
     override fun headersBuilder() = Headers.Builder().apply {
         add("Referer", baseUrl)
     }
@@ -32,72 +40,106 @@ abstract class PizzaReader(
     override fun popularMangaRequest(page: Int) =
         GET("$apiUrl/comics", headers)
 
-    override fun popularMangaParse(response: Response) =
-        MangasPage(
-            JSONObject(response.asString()).run {
-                val arr = getJSONArray("comics")
-                (0 until arr.length()).map {
-                    SManga.create().fromJSON(arr.getJSONObject(it))
-                }
-            },
-            false
-        )
+    override fun popularMangaParse(response: Response): MangasPage {
+        val result = json.decodeFromString<PizzaResultsDto>(response.body!!.string())
 
-    override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
-        GET("$apiUrl/search/$query", headers)
+        val comicList = result.comics
+            .map(::popularMangaFromObject)
 
-    override fun searchMangaParse(response: Response) =
-        MangasPage(
-            JSONObject(response.asString()).run {
-                val arr = getJSONArray("comics")
-                (0 until arr.length()).map {
-                    SManga.create().fromJSON(arr.getJSONObject(it))
-                }
-            },
-            false
-        )
+        return MangasPage(comicList, hasNextPage = false)
+    }
 
-    // TODO
-    override fun latestUpdatesRequest(page: Int): Request =
-        throw UnsupportedOperationException("Not used")
+    protected open fun popularMangaFromObject(comic: PizzaComicDto): SManga = SManga.create().apply {
+        title = comic.title
+        thumbnail_url = comic.thumbnail
+        url = comic.url
+    }
 
-    override fun latestUpdatesParse(response: Response): MangasPage =
-        throw UnsupportedOperationException("Not used")
+    override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page)
+
+    override fun latestUpdatesParse(response: Response): MangasPage {
+        val result = json.decodeFromString<PizzaResultsDto>(response.body!!.string())
+
+        val comicList = result.comics
+            .filter { comic -> comic.lastChapter != null }
+            .sortedByDescending { comic -> comic.lastChapter!!.publishedOn }
+            .map(::popularMangaFromObject)
+            .take(10)
+
+        return MangasPage(comicList, hasNextPage = false)
+    }
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        val searchUrl = "$apiUrl/search/".toHttpUrl().newBuilder()
+            .addPathSegment(query)
+            .toString()
+
+        return GET(searchUrl, headers)
+    }
+
+    override fun searchMangaParse(response: Response) = popularMangaParse(response)
 
     // Workaround to allow "Open in browser" to use the real URL
     override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
         client.newCall(chapterListRequest(manga)).asObservableSuccess()
             .map { mangaDetailsParse(it).apply { initialized = true } }
 
-    // Return the real URL for "Open in browser"
-    override fun mangaDetailsRequest(manga: SManga) =
-        GET("$baseUrl${manga.url}", headers)
+    override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
+        val result = json.decodeFromString<PizzaResultDto>(response.body!!.string())
+        val comic = result.comic!!
 
-    override fun mangaDetailsParse(response: Response): SManga =
-        SManga.create().fromJSON(JSONObject(response.asString()).getJSONObject("comic"))
+        title = comic.title
+        author = comic.author
+        artist = comic.artist
+        description = comic.description
+        genre = comic.genres.joinToString(", ") { it.name }
+        status = comic.status.toStatus()
+        thumbnail_url = comic.thumbnail
+    }
 
-    override fun chapterListRequest(manga: SManga) = GET("$apiUrl${manga.url}", headers)
+    override fun chapterListRequest(manga: SManga) = GET(apiUrl + manga.url, headers)
 
-    override fun chapterListParse(response: Response) =
-        JSONObject(response.asString()).getJSONObject("comic").run {
-            val arr = getJSONArray("chapters")
-            (0 until arr.length()).map {
-                SChapter.create().fromJSON(arr.getJSONObject(it))
-            }
-        }
+    override fun chapterListParse(response: Response): List<SChapter> {
+        val result = json.decodeFromString<PizzaResultDto>(response.body!!.string())
+        val comic = result.comic!!
 
-    override fun pageListRequest(chapter: SChapter) =
-        GET("$apiUrl${chapter.url}", headers)
+        return comic.chapters
+            .map(::chapterFromObject)
+    }
 
-    override fun pageListParse(response: Response) =
-        JSONObject(response.asString()).getJSONObject("chapter").run {
-            val arr = getJSONArray("pages")
-            (0 until arr.length()).map {
-                Page(it, "", arr.getString(it))
-            }
-        }
+    protected open fun chapterFromObject(chapter: PizzaChapterDto): SChapter = SChapter.create().apply {
+        name = chapter.fullTitle
+        chapter_number = (chapter.chapter ?: -1).toFloat() +
+            ("0." + (chapter.subchapter?.toString() ?: "0")).toFloat()
+        date_upload = chapter.publishedOn.toDate()
+        scanlator = chapter.teams.filterNotNull()
+            .joinToString(" & ") { it.name }
+        url = chapter.url
+    }
 
-    override fun imageUrlParse(response: Response): String =
-        throw UnsupportedOperationException("Not used")
+    override fun pageListRequest(chapter: SChapter) = GET(apiUrl + chapter.url, headers)
 
+    override fun pageListParse(response: Response): List<Page> {
+        val result = json.decodeFromString<PizzaReaderDto>(response.body!!.string())
+
+        return result.chapter!!.pages.mapIndexed { i, page -> Page(i, "", page) }
+    }
+
+    override fun imageUrlParse(response: Response): String = ""
+
+    protected open fun String.toDate(): Long {
+        return runCatching { dateParser.parse(this)?.time }
+            .getOrNull() ?: 0L
+    }
+
+    protected open fun String.toStatus(): Int = when (substring(0, 7)) {
+        "In cors" -> SManga.ONGOING
+        "On goin" -> SManga.ONGOING
+        "Complet" -> SManga.COMPLETED
+        "Conclus" -> SManga.COMPLETED
+        "Conclud" -> SManga.COMPLETED
+        "Licenzi" -> SManga.LICENSED
+        "License" -> SManga.LICENSED
+        else -> SManga.UNKNOWN
+    }
 }
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderDto.kt
new file mode 100644
index 000000000..144029a4d
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderDto.kt
@@ -0,0 +1,56 @@
+package eu.kanade.tachiyomi.multisrc.pizzareader
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PizzaResultsDto(
+    val comics: List<PizzaComicDto> = emptyList()
+)
+
+@Serializable
+data class PizzaResultDto(
+    val comic: PizzaComicDto? = null
+)
+
+@Serializable
+data class PizzaReaderDto(
+    val chapter: PizzaChapterDto? = null,
+    val comic: PizzaComicDto? = null
+)
+
+@Serializable
+data class PizzaComicDto(
+    val artist: String = "",
+    val author: String = "",
+    val chapters: List<PizzaChapterDto> = emptyList(),
+    val description: String = "",
+    val genres: List<PizzaGenreDto> = emptyList(),
+    @SerialName("last_chapter") val lastChapter: PizzaChapterDto? = null,
+    val status: String = "",
+    val title: String = "",
+    val thumbnail: String = "",
+    val url: String = ""
+)
+
+@Serializable
+data class PizzaGenreDto(
+    val name: String = ""
+)
+
+@Serializable
+data class PizzaChapterDto(
+    val chapter: Int? = null,
+    @SerialName("full_title") val fullTitle: String = "",
+    val pages: List<String> = emptyList(),
+    @SerialName("published_on") val publishedOn: String = "",
+    val subchapter: Int? = null,
+    val teams: List<PizzaTeamDto?> = emptyList(),
+    val url: String = ""
+)
+
+@Serializable
+data class PizzaTeamDto(
+    val name: String = ""
+)
+
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt
deleted file mode 100644
index ff5fd54cc..000000000
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package eu.kanade.tachiyomi.multisrc.pizzareader
-
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import okhttp3.Response
-import org.json.JSONArray
-import org.json.JSONObject
-import java.text.DecimalFormat
-// import java.time.OffsetDateTime
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-/** Returns the body of a response as a `String`. */
-fun Response.asString(): String = body!!.string()
-
-/**
- * Formats the number according to [fmt].
- *
- * @param fmt A [DecimalFormat] string.
- * @return A string representation of the number.
- */
-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 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).mapNotNull {
-            val obj = get(it)
-            if (obj != null && obj.toString() != "null") getJSONArray(it).getString(field)
-            else null
-        }.joinToString(sep)
-    }
-
-/**
- * Joins each value of a given [field] of the array using [sep].
- *
- * @param field 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: String, sep: String = ", ") =
-    length().takeIf { it != 0 }?.run {
-        (0 until this).mapNotNull {
-            val obj = get(it)
-            if (obj != null && obj.toString() != "null") getJSONObject(it).getString(field)
-            else null
-        }.joinToString(sep)
-    }
-
-/**
- * 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("thumbnail")
-    author = obj.getString("author")
-    artist = obj.getString("artist")
-    genre = obj.getJSONArray("genres").joinField("slug")
-    status = when (obj.getString("status").substring(0, 7)) {
-        "In cors" -> SManga.ONGOING
-        "On goin" -> SManga.ONGOING
-        "Complet" -> SManga.COMPLETED
-        "Conclus" -> SManga.COMPLETED
-        "Conclud" -> SManga.COMPLETED
-        "Licenzi" -> SManga.LICENSED
-        "License" -> SManga.LICENSED
-        else -> SManga.UNKNOWN
-    }
-}
-
-/**
- * Creates a [SChapter] by parsing a [JSONObject].
- *
- * @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() + "0.${obj.optInt("subchapter", 0)}".toFloat()
-    // date_upload = OffsetDateTime.parse(obj.getString("published_on")).toEpochSecond()  // API 26
-    date_upload = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ITALY).parse(obj.getString("published_on"))?.time ?: 0L
-    scanlator = obj.getJSONArray("teams").joinField("name", " & ")
-    name = obj.getString("full_title")
-}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt
index 718f4f3ce..87e3fb41e 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt
@@ -10,7 +10,7 @@ class PizzaReaderGenerator : ThemeSourceGenerator {
 
     override val themeClass = "PizzaReader"
 
-    override val baseVersionCode: Int = 0
+    override val baseVersionCode: Int = 1
 
     override val sources = listOf(
         SingleLang("Phoenix Scans", "https://www.phoenixscans.com", "it", className = "PhoenixScans", overrideVersionCode = 4),