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),