From 10eb0308954108f9cb5fa7a30a5c35de53bb835a Mon Sep 17 00:00:00 2001 From: Alessandro Jean Date: Wed, 2 Jun 2021 17:28:10 -0300 Subject: [PATCH] Add kotlinx.serialization to more sources. (#7391) --- .../mangasproject/leitornet/src/LeitorNet.kt | 4 +- .../mangalivre/src/MangaLivre.kt | 4 +- .../mangasproject/toonei/src/Toonei.kt | 6 +- .../multisrc/mangasproject/MangasProject.kt | 113 +++-- .../mangasproject/MangasProjectDto.kt | 58 +++ .../mangasproject/MangasProjectGenerator.kt | 4 +- .../java/generator/ThemeSourceGenerator.kt | 1 + src/en/vizshonenjump/build.gradle | 3 +- .../en/vizshonenjump/VizShonenJump.kt | 35 +- src/pt/hqnow/build.gradle | 3 +- .../tachiyomi/extension/pt/hqnow/HQNow.kt | 422 ++++++++++++------ .../tachiyomi/extension/pt/hqnow/HQNowDto.kt | 28 ++ src/pt/mangatube/build.gradle | 3 +- .../extension/pt/mangatube/MangaTube.kt | 90 ++-- .../extension/pt/mangatube/MangaTubeDto.kt | 51 +++ src/pt/muitomanga/build.gradle | 3 +- .../extension/pt/muitomanga/MuitoManga.kt | 27 +- .../extension/pt/muitomanga/MuitoMangaDto.kt | 16 + src/pt/mundomangakun/build.gradle | 3 +- .../pt/mundomangakun/MundoMangaKun.kt | 26 +- src/pt/opex/build.gradle | 3 +- .../tachiyomi/extension/pt/opex/OnePieceEx.kt | 14 +- src/pt/tsukimangas/build.gradle | 3 +- .../extension/pt/tsukimangas/TsukiMangas.kt | 117 +++-- .../pt/tsukimangas/TsukiMangasDto.kt | 66 +++ 25 files changed, 747 insertions(+), 356 deletions(-) create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectDto.kt create mode 100644 src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNowDto.kt create mode 100644 src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTubeDto.kt create mode 100644 src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoMangaDto.kt create mode 100644 src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangasDto.kt diff --git a/multisrc/overrides/mangasproject/leitornet/src/LeitorNet.kt b/multisrc/overrides/mangasproject/leitornet/src/LeitorNet.kt index cd176544f..44543e73a 100644 --- a/multisrc/overrides/mangasproject/leitornet/src/LeitorNet.kt +++ b/multisrc/overrides/mangasproject/leitornet/src/LeitorNet.kt @@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.multisrc.mangasproject.MangasProject import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit class LeitorNet : MangasProject("Leitor.net", "https://leitor.net", "pt-BR") { @@ -18,7 +18,7 @@ class LeitorNet : MangasProject("Leitor.net", "https://leitor.net", "pt-BR") { override val id: Long = 2225174659569980836 override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(RateLimitInterceptor(5, 1, TimeUnit.SECONDS)) + .addInterceptor(RateLimitInterceptor(2, 1, TimeUnit.SECONDS)) .build() /** diff --git a/multisrc/overrides/mangasproject/mangalivre/src/MangaLivre.kt b/multisrc/overrides/mangasproject/mangalivre/src/MangaLivre.kt index 20f086072..6f9390e05 100644 --- a/multisrc/overrides/mangasproject/mangalivre/src/MangaLivre.kt +++ b/multisrc/overrides/mangasproject/mangalivre/src/MangaLivre.kt @@ -6,9 +6,9 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage +import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit class MangaLivre : MangasProject("Mangá Livre", "https://mangalivre.net", "pt-BR") { @@ -17,7 +17,7 @@ class MangaLivre : MangasProject("Mangá Livre", "https://mangalivre.net", "pt-B override val id: Long = 4762777556012432014 override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(RateLimitInterceptor(5, 1, TimeUnit.SECONDS)) + .addInterceptor(RateLimitInterceptor(2, 1, TimeUnit.SECONDS)) .build() override fun popularMangaRequest(page: Int): Request { diff --git a/multisrc/overrides/mangasproject/toonei/src/Toonei.kt b/multisrc/overrides/mangasproject/toonei/src/Toonei.kt index f3e3f0966..67da20df6 100644 --- a/multisrc/overrides/mangasproject/toonei/src/Toonei.kt +++ b/multisrc/overrides/mangasproject/toonei/src/Toonei.kt @@ -2,14 +2,14 @@ package eu.kanade.tachiyomi.extension.pt.toonei import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.multisrc.mangasproject.MangasProject -import org.jsoup.nodes.Document import okhttp3.OkHttpClient +import org.jsoup.nodes.Document import java.util.concurrent.TimeUnit -class Toonei : MangasProject("Toonei", "https://toonei.com", "pt-BR") { +class Toonei : MangasProject("Toonei", "https://toonei.net", "pt-BR") { override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(RateLimitInterceptor(5, 1, TimeUnit.SECONDS)) + .addInterceptor(RateLimitInterceptor(2, 1, TimeUnit.SECONDS)) .build() override fun getReaderToken(document: Document): String? { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProject.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProject.kt index c57fb567d..c934eeb6b 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProject.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProject.kt @@ -1,10 +1,5 @@ package eu.kanade.tachiyomi.multisrc.mangasproject -import com.github.salomonbrys.kotson.array -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.network.POST import eu.kanade.tachiyomi.source.model.FilterList @@ -14,6 +9,11 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement import okhttp3.FormBody import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -23,6 +23,7 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -54,25 +55,26 @@ abstract class MangasProject( protected val sourceHeaders: Headers by lazy { sourceHeadersBuilder().build() } + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/home/most_read?page=$page&type=", sourceHeaders) } override fun popularMangaParse(response: Response): MangasPage { - val result = response.asJsonObject() + val result = json.decodeFromString(response.body!!.string()) - val popularMangas = result["most_read"].array - .map { popularMangaItemParse(it.obj) } + val popularMangas = result.mostRead.map(::popularMangaFromObject) val hasNextPage = response.request.url.queryParameter("page")!!.toInt() < 10 return MangasPage(popularMangas, hasNextPage) } - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["serie_name"].string - thumbnail_url = obj["cover"].string - url = obj["link"].string + private fun popularMangaFromObject(serie: MangasProjectSerieDto) = SManga.create().apply { + title = serie.serieName + thumbnail_url = serie.cover + url = serie.link } override fun latestUpdatesRequest(page: Int): Request { @@ -80,20 +82,19 @@ abstract class MangasProject( } override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asJsonObject() + val result = json.decodeFromString(response.body!!.string()) - val latestMangas = result["releases"].array - .map { latestMangaItemParse(it.obj) } + val latestMangas = result.releases.map(::latestMangaFromObject) val hasNextPage = response.request.url.queryParameter("page")!!.toInt() < 5 return MangasPage(latestMangas, hasNextPage) } - private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].string - thumbnail_url = obj["image"].string - url = obj["link"].string + private fun latestMangaFromObject(serie: MangasProjectSerieDto) = SManga.create().apply { + title = serie.name + thumbnail_url = serie.image + url = serie.link } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { @@ -110,22 +111,22 @@ abstract class MangasProject( } override fun searchMangaParse(response: Response): MangasPage { - val result = response.asJsonObject() + val result = json.decodeFromString(response.body!!.string()) // If "series" have boolean false value, then it doesn't have results. - if (!result["series"]!!.isJsonArray) + if (result.series is JsonPrimitive) return MangasPage(emptyList(), false) - val searchMangas = result["series"].array - .map { searchMangaItemParse(it.obj) } + val searchMangas = json.decodeFromJsonElement>(result.series) + .map(::searchMangaFromObject) return MangasPage(searchMangas, false) } - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].string - thumbnail_url = obj["cover"].string - url = obj["link"].string + private fun searchMangaFromObject(serie: MangasProjectSerieDto) = SManga.create().apply { + title = serie.name + thumbnail_url = serie.cover + url = serie.link } override fun mangaDetailsParse(response: Response): SManga { @@ -202,41 +203,41 @@ abstract class MangasProject( var page = 1 var chapterListRequest = chapterListRequestPaginated(mangaUrl, mangaId, page) - var result = client.newCall(chapterListRequest).execute().asJsonObject() + var result = client.newCall(chapterListRequest).execute().let { + json.decodeFromString(it.body!!.string()) + } - if (!result["chapters"]!!.isJsonArray) + if (result.chapters is JsonPrimitive) return emptyList() val chapters = mutableListOf() - while (result["chapters"]!!.isJsonArray) { - chapters += result["chapters"].array - .flatMap { chapterListItemParse(it.obj) } + while (result.chapters is JsonArray) { + chapters += json.decodeFromJsonElement>(result.chapters) + .flatMap(::chaptersFromObject) .toMutableList() chapterListRequest = chapterListRequestPaginated(mangaUrl, mangaId, ++page) - result = client.newCall(chapterListRequest).execute().asJsonObject() + result = client.newCall(chapterListRequest).execute().let { + json.decodeFromString(it.body!!.string()) + } } return chapters } - private fun chapterListItemParse(obj: JsonObject): List { - val chapterName = obj["chapter_name"]!!.string - - return obj["releases"].obj.entrySet().map { - val release = it.value.obj - + private fun chaptersFromObject(chapter: MangasProjectChapterDto): List { + return chapter.releases.values.map { release -> SChapter.create().apply { - name = "Cap. ${obj["number"].string}" + - (if (chapterName == "") "" else " - $chapterName") - date_upload = obj["date_created"].string.substringBefore("T").toDate() - scanlator = release["scanlators"]!!.array - .mapNotNull { scanObj -> scanObj.obj["name"].string.ifEmpty { null } } + name = "Cap. ${chapter.number}" + + (if (chapter.name.isEmpty()) "" else " - ${chapter.name}") + date_upload = chapter.dateCreated.substringBefore("T").toDate() + scanlator = release.scanlators + .mapNotNull { scan -> scan.name.ifEmpty { null } } .sorted() .joinToString() - url = release["link"].string - chapter_number = obj["number"].string.toFloatOrNull() ?: -1f + url = release.link + chapter_number = chapter.number.toFloatOrNull() ?: -1f } } } @@ -269,11 +270,13 @@ abstract class MangasProject( val chapterUrl = getChapterUrl(response) val apiRequest = pageListApiRequest(chapterUrl, readerToken) - val apiResponse = client.newCall(apiRequest).execute().asJsonObject() + val apiResponse = client.newCall(apiRequest).execute().let { + json.decodeFromString(it.body!!.string()) + } - return apiResponse["images"].array - .filter { it.string.startsWith("http") } - .mapIndexed { i, obj -> Page(i, chapterUrl, obj.string) } + return apiResponse.images + .filter { it.startsWith("http") } + .mapIndexed { i, imageUrl -> Page(i, chapterUrl, imageUrl) } } open fun getChapterUrl(response: Response): String { @@ -299,14 +302,6 @@ abstract class MangasProject( return GET(page.imageUrl!!, newHeaders) } - private fun Response.asJsonObject(): JsonObject { - if (!isSuccessful) { - throw Exception("HTTP error $code") - } - - return JsonParser.parseString(body!!.string()).obj - } - private fun String.toDate(): Long { return try { DATE_FORMATTER.parse(this)?.time ?: 0L @@ -321,7 +316,7 @@ abstract class MangasProject( private const val ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01" private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36" private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectDto.kt new file mode 100644 index 000000000..10ac63a70 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectDto.kt @@ -0,0 +1,58 @@ +package eu.kanade.tachiyomi.multisrc.mangasproject + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +@Serializable +data class MangasProjectMostReadDto( + @SerialName("most_read") val mostRead: List = emptyList() +) + +@Serializable +data class MangasProjectReleasesDto( + val releases: List = emptyList() +) + +@Serializable +data class MangasProjectSearchDto( + val series: JsonElement +) + +@Serializable +data class MangasProjectSerieDto( + val cover: String = "", + val image: String = "", + val link: String, + val name: String = "", + @SerialName("serie_name") val serieName: String = "" +) + +@Serializable +data class MangasProjectChapterListDto( + val chapters: JsonElement +) + +@Serializable +data class MangasProjectChapterDto( + @SerialName("date_created") val dateCreated: String, + @SerialName("chapter_name") val name: String, + val number: String, + val releases: Map = emptyMap() +) + +@Serializable +data class MangasProjectChapterReleaseDto( + val link: String, + val scanlators: List = emptyList() +) + +@Serializable +data class MangasProjectScanlatorDto( + val name: String +) + +@Serializable +data class MangasProjectReaderDto( + val images: List = emptyList() +) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectGenerator.kt index 07f6a72c6..5b7f17c23 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasproject/MangasProjectGenerator.kt @@ -9,12 +9,12 @@ class MangasProjectGenerator : ThemeSourceGenerator { override val themeClass = "MangasProject" - override val baseVersionCode: Int = 2 + override val baseVersionCode: Int = 3 override val sources = listOf( SingleLang("Leitor.net", "https://leitor.net", "pt-BR", className = "LeitorNet", isNsfw = true, overrideVersionCode = 1), SingleLang("Mangá Livre", "https://mangalivre.net", "pt-BR", className = "MangaLivre", isNsfw = true, overrideVersionCode = 1), - SingleLang("Toonei", "https://toonei.com", "pt-BR", isNsfw = true, overrideVersionCode = 1), + SingleLang("Toonei", "https://toonei.net", "pt-BR", isNsfw = true, overrideVersionCode = 1), ) companion object { diff --git a/multisrc/src/main/java/generator/ThemeSourceGenerator.kt b/multisrc/src/main/java/generator/ThemeSourceGenerator.kt index 2f575b7f9..cbd0c725e 100644 --- a/multisrc/src/main/java/generator/ThemeSourceGenerator.kt +++ b/multisrc/src/main/java/generator/ThemeSourceGenerator.kt @@ -63,6 +63,7 @@ interface ThemeSourceGenerator { // THIS FILE IS AUTO-GENERATED; DO NOT EDIT apply plugin: 'com.android.application' apply plugin: 'kotlin-android' + apply plugin: 'kotlinx-serialization' ext { extName = '${source.name}' diff --git a/src/en/vizshonenjump/build.gradle b/src/en/vizshonenjump/build.gradle index 38e264fda..41a300f40 100644 --- a/src/en/vizshonenjump/build.gradle +++ b/src/en/vizshonenjump/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'VIZ Shonen Jump' pkgNameSuffix = 'en.vizshonenjump' extClass = '.VizShonenJump' - extVersionCode = 10 + extVersionCode = 11 libVersion = '1.2' } diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt index 412aae961..8f2953945 100644 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt +++ b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt @@ -1,12 +1,5 @@ package eu.kanade.tachiyomi.extension.en.vizshonenjump -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullInt -import com.github.salomonbrys.kotson.nullObj -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.google.gson.JsonParser import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList @@ -16,6 +9,13 @@ 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.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -26,6 +26,7 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -53,6 +54,8 @@ class VizShonenJump : ParsedHttpSource() { .add("Origin", baseUrl) .add("Referer", "$baseUrl/shonenjump") + private val json: Json by injectLazy() + private var mangaList: List? = null private var loggedIn: Boolean? = null @@ -318,11 +321,14 @@ class VizShonenJump : ParsedHttpSource() { .toString() val authCheckRequest = GET(authCheckUrl, authCheckHeaders) val authCheckResponse = chain.proceed(authCheckRequest) - val authCheckJson = JsonParser.parseString(authCheckResponse.body!!.string()).obj + val authCheckJson = Json.parseToJsonElement(authCheckResponse.body!!.string()).jsonObject authCheckResponse.close() - if (authCheckJson["ok"].int == 1 && authCheckJson["archive_info"]["ok"].int == 1) { + if ( + authCheckJson["ok"]!!.jsonPrimitive.int == 1 && + authCheckJson["archive_info"]!!.jsonObject["ok"]!!.jsonPrimitive.int == 1 + ) { val newChapterUrl = chain.request().url.newBuilder() .removeAllQueryParameters("locked") .build() @@ -334,16 +340,17 @@ class VizShonenJump : ParsedHttpSource() { } if ( - authCheckJson["archive_info"]["err"].isJsonObject && - authCheckJson["archive_info"]["err"]["code"].nullInt == 4 && + authCheckJson["archive_info"]!!.jsonObject["err"] is JsonObject && + authCheckJson["archive_info"]!!.jsonObject["err"]!!.jsonObject["code"]?.jsonPrimitive?.intOrNull == 4 && loggedIn == true ) { throw Exception(SESSION_EXPIRED) } - val errorMessage = authCheckJson["archive_info"]["err"].nullObj?.get("msg")?.nullString + val errorMessage = authCheckJson["archive_info"]!!.jsonObject["err"]?.jsonObject + ?.get("msg")?.jsonPrimitive?.contentOrNull ?: AUTH_CHECK_FAILED - throw Exception(errorMessage ?: AUTH_CHECK_FAILED) + throw Exception(errorMessage) } private fun String.toDate(): Long { @@ -363,7 +370,7 @@ class VizShonenJump : ParsedHttpSource() { SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) } - private const val COUNTRY_NOT_SUPPORTED = "Your country is not supported, try using a VPN." + private const val COUNTRY_NOT_SUPPORTED = "Your country is not supported by the service." private const val SESSION_EXPIRED = "Your session has expired, please log in through WebView again." private const val AUTH_CHECK_FAILED = "Something went wrong in the auth check." diff --git a/src/pt/hqnow/build.gradle b/src/pt/hqnow/build.gradle index 07aaf7cad..bc1253a20 100644 --- a/src/pt/hqnow/build.gradle +++ b/src/pt/hqnow/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'HQ Now!' pkgNameSuffix = 'pt.hqnow' extClass = '.HQNow' - extVersionCode = 3 + extVersionCode = 4 libVersion = '1.2' } diff --git a/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt b/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt index 0379853a1..ab5d6bce5 100644 --- a/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt +++ b/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt @@ -1,200 +1,364 @@ package eu.kanade.tachiyomi.extension.pt.hqnow -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor +import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.FilterList 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 eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import java.util.concurrent.TimeUnit +import rx.Observable +import uy.kohesive.injekt.injectLazy +import java.text.Normalizer +import java.util.Locale class HQNow : HttpSource() { override val name = "HQ Now!" - // Website is http://www.hq-now.com - override val baseUrl = "http://admin.hq-now.com/graphql" + override val baseUrl = "http://www.hq-now.com" override val lang = "pt-BR" override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) + .addInterceptor(SpecificHostRateLimitInterceptor(GRAPHQL_URL.toHttpUrl(), 1)) + .addInterceptor(SpecificHostRateLimitInterceptor(STATIC_URL.toHttpUrl(), 2)) .build() - private val gson = Gson() + private val json: Json by injectLazy() - private val jsonHeaders = headersBuilder().add("content-type", "application/json").build() - - private fun mangaFromResponse(response: Response, selector: String, coversAvailable: Boolean = true): List { - return gson.fromJson(response.body!!.string())["data"][selector].asJsonArray - .map { - SManga.create().apply { - url = it["id"].asString - title = it["name"].asString - if (coversAvailable) thumbnail_url = it["hqCover"].asString - } - } - } - - // Popular + private fun genericComicBookFromObject(comicBook: HqNowComicBookDto): SManga = + SManga.create().apply { + title = comicBook.name + url = "/hq/${comicBook.id}/${comicBook.name.toSlug()}" + thumbnail_url = comicBook.cover + } override fun popularMangaRequest(page: Int): Request { - return POST(baseUrl, jsonHeaders, "{\"operationName\":\"getHqsByFilters\",\"variables\":{\"orderByViews\":true,\"loadCovers\":true,\"limit\":30},\"query\":\"query getHqsByFilters(\$orderByViews: Boolean, \$limit: Int, \$publisherId: Int, \$loadCovers: Boolean) {\\n getHqsByFilters(orderByViews: \$orderByViews, limit: \$limit, publisherId: \$publisherId, loadCovers: \$loadCovers) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n hqCover\\n synopsis\\n updatedAt\\n }\\n}\\n\"}".toRequestBody(null)) + val query = buildQuery { + """ + query getHqsByFilters( + %orderByViews: Boolean, + %limit: Int, + %publisherId: Int, + %loadCovers: Boolean + ) { + getHqsByFilters( + orderByViews: %orderByViews, + limit: %limit, + publisherId: %publisherId, + loadCovers: %loadCovers + ) { + id + name + editoraId + status + publisherName + hqCover + synopsis + updatedAt + } + } + """.trimIndent() + } + + val payload = buildJsonObject { + put("operationName", "getHqsByFilters") + put("query", query) + putJsonObject("variables") { + put("orderByViews", true) + put("loadCovers", true) + put("limit", 300) + } + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(GRAPHQL_URL, newHeaders, body) } override fun popularMangaParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, "getHqsByFilters"), false) + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + val comicList = result["data"]!!.jsonObject["getHqsByFilters"]!! + .let { json.decodeFromJsonElement>(it) } + .map(::genericComicBookFromObject) + + return MangasPage(comicList, hasNextPage = false) } - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return POST(baseUrl, jsonHeaders, "{\"operationName\":\"getRecentlyUpdatedHqs\",\"variables\":{},\"query\":\"query getRecentlyUpdatedHqs {\\n getRecentlyUpdatedHqs {\\n name\\n hqCover\\n synopsis\\n id\\n updatedAt\\n updatedChapters\\n }\\n}\\n\"}".toRequestBody(null)) + val query = buildQuery { + """ + query getRecentlyUpdatedHqs { + getRecentlyUpdatedHqs { + name + hqCover + synopsis + id + updatedAt + updatedChapters + } + } + """.trimIndent() + } + + val payload = buildJsonObject { + put("operationName", "getRecentlyUpdatedHqs") + put("query", query) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(GRAPHQL_URL, newHeaders, body) } override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, "getRecentlyUpdatedHqs"), false) + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + val comicList = result["data"]!!.jsonObject["getRecentlyUpdatedHqs"]!! + .let { json.decodeFromJsonElement>(it) } + .map(::genericComicBookFromObject) + + return MangasPage(comicList, hasNextPage = false) } - // Search - - private var queryIsTitle = true - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - queryIsTitle = true - POST(baseUrl, jsonHeaders, "{\"operationName\":\"getHqsByName\",\"variables\":{\"name\":\"$query\"},\"query\":\"query getHqsByName(\$name: String!) {\\n getHqsByName(name: \$name) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n impressionsCount\\n }\\n}\\n\"}".toRequestBody(null)) - } else { - queryIsTitle = false - var searchLetter = "" - - filters.forEach { filter -> - when (filter) { - is LetterFilter -> { - searchLetter = filter.toUriPart() + val queryStr = buildQuery { + """ + query getHqsByName(%name: String!) { + getHqsByName(name: %name) { + id + name + editoraId + status + publisherName + impressionsCount } } - } - POST(baseUrl, jsonHeaders, "{\"operationName\":\"getHqsByNameStartingLetter\",\"variables\":{\"letter\":\"$searchLetter-$searchLetter\"},\"query\":\"query getHqsByNameStartingLetter(\$letter: String!) {\\n getHqsByNameStartingLetter(letter: \$letter) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n impressionsCount\\n }\\n}\\n\"}".toRequestBody(null)) + """.trimIndent() } + + val payload = buildJsonObject { + put("operationName", "getHqsByName") + put("query", queryStr) + putJsonObject("variables") { + put("name", query) + } + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(GRAPHQL_URL, newHeaders, body) } override fun searchMangaParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, if (queryIsTitle) "getHqsByName" else "getHqsByNameStartingLetter", false), false) + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + val comicList = result["data"]!!.jsonObject["getHqsByName"]!! + .let { json.decodeFromJsonElement>(it) } + .map(::genericComicBookFromObject) + + return MangasPage(comicList, hasNextPage = false) } - // Details - - override fun mangaDetailsRequest(manga: SManga): Request { - return POST(baseUrl, jsonHeaders, "{\"operationName\":\"getHqsById\",\"variables\":{\"id\":${manga.url}},\"query\":\"query getHqsById(\$id: Int!) {\\n getHqsById(id: \$id) {\\n id\\n name\\n synopsis\\n editoraId\\n status\\n publisherName\\n hqCover\\n impressionsCount\\n capitulos {\\n name\\n id\\n number\\n }\\n }\\n}\\n\"}".toRequestBody(null)) - } - - override fun mangaDetailsParse(response: Response): SManga { - return gson.fromJson(response.body!!.string())["data"]["getHqsById"][0] - .let { - SManga.create().apply { - title = it["name"].asString - thumbnail_url = it["hqCover"].asString - description = it["synopsis"].asString - author = it["publisherName"].asString - status = when (it["status"].asString) { - "Concluído" -> SManga.COMPLETED - "Em Andamento" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } + // Workaround to allow "Open in browser" use the real URL. + override fun fetchMangaDetails(manga: SManga): Observable { + return client.newCall(mangaDetailsApiRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response).apply { initialized = true } } } - // Chapters + private fun mangaDetailsApiRequest(manga: SManga): Request { + val comicBookId = manga.url.substringAfter("/hq/").substringBefore("/") - override fun chapterListRequest(manga: SManga): Request { - return mangaDetailsRequest(manga) - } - - override fun chapterListParse(response: Response): List { - return gson.fromJson(response.body!!.string())["data"]["getHqsById"][0]["capitulos"].asJsonArray - .map { - SChapter.create().apply { - url = it["id"].asString - name = it["name"].asString.let { jsonName -> - if (jsonName.isNotEmpty()) jsonName.trim() else "Capitulo: " + it["number"].asString + val query = buildQuery { + """ + query getHqsById(%id: Int!) { + getHqsById(id: %id) { + id + name + synopsis + editoraId + status + publisherName + hqCover + impressionsCount + capitulos { + name + id + number + } } } - }.reversed() + """.trimIndent() + } + + val payload = buildJsonObject { + put("operationName", "getHqsById") + put("query", query) + putJsonObject("variables") { + put("id", comicBookId.toInt()) + } + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(GRAPHQL_URL, newHeaders, body) } + override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + val comicBook = result["data"]!!.jsonObject["getHqsById"]!!.jsonArray[0].jsonObject + .let { json.decodeFromJsonElement(it) } + + title = comicBook.name + thumbnail_url = comicBook.cover + description = comicBook.synopsis.orEmpty() + author = comicBook.publisherName.orEmpty() + status = comicBook.status.orEmpty().toStatus() + } + + override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) + + override fun chapterListParse(response: Response): List { + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + val comicBook = result["data"]!!.jsonObject["getHqsById"]!!.jsonArray[0].jsonObject + .let { json.decodeFromJsonElement(it) } + + return comicBook.chapters + .map { chapter -> chapterFromObject(chapter, comicBook) } + .reversed() + } + + private fun chapterFromObject(chapter: HqNowChapterDto, comicBook: HqNowComicBookDto): SChapter = + SChapter.create().apply { + name = "#" + chapter.number + + (if (chapter.name.isNotEmpty()) " - " + chapter.name else "") + url = "/hq-reader/${comicBook.id}/${comicBook.name.toSlug()}" + + "/chapter/${chapter.id}/page/1" + } + // Pages override fun pageListRequest(chapter: SChapter): Request { - return POST(baseUrl, jsonHeaders, "{\"operationName\":\"getChapterById\",\"variables\":{\"chapterId\":${chapter.url}},\"query\":\"query getChapterById(\$chapterId: Int!) {\\n getChapterById(chapterId: \$chapterId) {\\n name\\n number\\n oneshot\\n pictures {\\n pictureUrl\\n }\\n hq {\\n id\\n name\\n capitulos {\\n id\\n number\\n }\\n }\\n }\\n}\\n\"}".toRequestBody(null)) + val chapterId = chapter.url.substringAfter("/chapter/").substringBefore("/") + + val query = buildQuery { + """ + query getChapterById(%chapterId: Int!) { + getChapterById(chapterId: %chapterId) { + name + number + oneshot + pictures { + pictureUrl + } + } + } + """.trimIndent() + } + + val payload = buildJsonObject { + put("operationName", "getChapterById") + put("query", query) + putJsonObject("variables") { + put("chapterId", chapterId.toInt()) + } + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(GRAPHQL_URL, newHeaders, body) } override fun pageListParse(response: Response): List { - return gson.fromJson(response.body!!.string())["data"]["getChapterById"]["pictures"].asJsonArray - .mapIndexed { i, json -> Page(i, "", json["pictureUrl"].asString) } + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + val chapterDto = result["data"]!!.jsonObject["getChapterById"]!! + .let { json.decodeFromJsonElement(it) } + + return chapterDto.pictures.mapIndexed { i, page -> + Page(i, baseUrl, page.pictureUrl) + } } - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") + override fun imageUrlParse(response: Response): String = "" - // Filters + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .build() - override fun getFilterList() = FilterList( - Filter.Header("NOTA: Ignorado se estiver usando"), - Filter.Header("a pesquisa de texto!"), - Filter.Separator(), - LetterFilter() - ) + return GET(page.imageUrl!!, newHeaders) + } - private class LetterFilter : UriPartFilter( - "Letra", - arrayOf( - Pair("---", ""), - Pair("a", "A"), - Pair("b", "B"), - Pair("c", "C"), - Pair("d", "D"), - Pair("e", "E"), - Pair("f", "F"), - Pair("g", "G"), - Pair("h", "H"), - Pair("i", "I"), - Pair("j", "J"), - Pair("k", "K"), - Pair("l", "L"), - Pair("m", "M"), - Pair("n", "N"), - Pair("o", "O"), - Pair("p", "P"), - Pair("q", "Q"), - Pair("r", "R"), - Pair("s", "S"), - Pair("t", "T"), - Pair("u", "U"), - Pair("v", "V"), - Pair("w", "W"), - Pair("x", "X"), - Pair("y", "Y"), - Pair("z", "Z") - ) - ) + private fun buildQuery(queryAction: () -> String) = queryAction().replace("%", "$") - open class UriPartFilter(displayName: String, private val vals: Array>) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first + private fun String.toSlug(): String { + return Normalizer + .normalize(this, Normalizer.Form.NFD) + .replace("[^\\p{ASCII}]".toRegex(), "") + .replace("[^a-zA-Z0-9\\s]+".toRegex(), "").trim() + .replace("\\s+".toRegex(), "-") + .toLowerCase(Locale("pt", "BR")) + } + + private fun String.toStatus(): Int = when (this) { + "Concluído" -> SManga.COMPLETED + "Em Andamento" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + companion object { + private const val STATIC_URL = "http://static.hq-now.com/" + private const val GRAPHQL_URL = "http://admin.hq-now.com/graphql" + + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() } } diff --git a/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNowDto.kt b/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNowDto.kt new file mode 100644 index 000000000..1167176ac --- /dev/null +++ b/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNowDto.kt @@ -0,0 +1,28 @@ +package eu.kanade.tachiyomi.extension.pt.hqnow + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HqNowComicBookDto( + @SerialName("capitulos") val chapters: List = emptyList(), + @SerialName("hqCover") val cover: String? = "", + val id: Int, + val name: String, + val publisherName: String? = "", + val status: String? = "", + val synopsis: String? = "" +) + +@Serializable +data class HqNowChapterDto( + val id: Int = 0, + val name: String, + val number: String, + val pictures: List = emptyList() +) + +@Serializable +data class HqNowPageDto( + val pictureUrl: String +) diff --git a/src/pt/mangatube/build.gradle b/src/pt/mangatube/build.gradle index 4d47d0b8a..bfdafe4bd 100644 --- a/src/pt/mangatube/build.gradle +++ b/src/pt/mangatube/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'MangaTube' pkgNameSuffix = 'pt.mangatube' extClass = '.MangaTube' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTube.kt b/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTube.kt index 84d807872..c3d1b19f3 100644 --- a/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTube.kt +++ b/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTube.kt @@ -1,13 +1,5 @@ package eu.kanade.tachiyomi.extension.pt.mangatube -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullArray -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.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST @@ -18,6 +10,12 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.floatOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.FormBody import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -27,6 +25,7 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -58,6 +57,8 @@ class MangaTube : HttpSource() { private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() } + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { return GET(baseUrl, headers) } @@ -91,20 +92,20 @@ class MangaTube : HttpSource() { } override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asJson().obj + val result = json.decodeFromString(response.body!!.string()) - val latestMangas = result["releases"].array + val latestMangas = result.releases .map(::latestUpdatesFromObject) - val hasNextPage = result["page"].string.toInt() < result["total_page"].int + val hasNextPage = result.page.toInt() < result.totalPage return MangasPage(latestMangas, hasNextPage) } - private fun latestUpdatesFromObject(obj: JsonElement) = SManga.create().apply { - title = obj["name"].string - thumbnail_url = obj["image"].string - url = obj["link"].string + private fun latestUpdatesFromObject(release: MangaTubeReleaseDto) = SManga.create().apply { + title = release.name + thumbnail_url = release.image + url = release.link } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { @@ -117,18 +118,17 @@ class MangaTube : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val result = response.asJson().obj + val result = json.decodeFromString>(response.body!!.string()) - val searchResults = result.entrySet() - .map { searchMangaFromObject(it.value) } + val searchResults = result.values.map(::searchMangaFromObject) return MangasPage(searchResults, hasNextPage = false) } - private fun searchMangaFromObject(obj: JsonElement) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = obj["img"].string - setUrlWithoutDomain(obj["url"].string) + private fun searchMangaFromObject(manga: MangaTubeTitleDto) = SManga.create().apply { + title = manga.title + thumbnail_url = manga.image + setUrlWithoutDomain(manga.url) } override fun mangaDetailsParse(response: Response): SManga { @@ -154,6 +154,7 @@ class MangaTube : HttpSource() { val url = "$baseUrl/jsons/series/chapters_list.json".toHttpUrlOrNull()!!.newBuilder() .addQueryParameter("page", page.toString()) + .addQueryParameter("order", "desc") .addQueryParameter("id_s", mangaId) .toString() @@ -163,24 +164,26 @@ class MangaTube : HttpSource() { override fun chapterListParse(response: Response): List { val mangaUrl = response.request.header("Referer")!!.substringAfter(baseUrl) - var result = response.asJson().obj + var result = json.decodeFromString(response.body!!.string()) - if (result["chapters"].nullArray == null || result["chapters"].array.size() == 0) { + if (result.chapters.isNullOrEmpty()) { return emptyList() } - val chapters = result["chapters"].array + val chapters = result.chapters!! .map(::chapterFromObject) .toMutableList() - var page = result["pagina"].int + 1 - val lastPage = result["total_pags"].int + var page = result.page + 1 + val lastPage = result.totalPages while (++page <= lastPage) { val nextPageRequest = chapterListPaginatedRequest(mangaUrl, page) - result = client.newCall(nextPageRequest).execute().asJson().obj + result = client.newCall(nextPageRequest).execute().let { + json.decodeFromString(it.body!!.string()) + } - chapters += result["chapters"].array + chapters += result.chapters!! .map(::chapterFromObject) .toMutableList() } @@ -188,12 +191,12 @@ class MangaTube : HttpSource() { return chapters } - private fun chapterFromObject(obj: JsonElement): SChapter = SChapter.create().apply { - name = "Cap. " + (if (obj["number"].string == "false") "0" else obj["number"].string) + - (if (obj["chapter_name"].asJsonPrimitive.isString) " - " + obj["chapter_name"].string else "") - chapter_number = obj["number"].string.toFloatOrNull() ?: -1f - date_upload = obj["date_created"].string.substringBefore("T").toDate() - setUrlWithoutDomain(obj["link"].string) + private fun chapterFromObject(chapter: MangaTubeChapterDto): SChapter = SChapter.create().apply { + name = "Cap. " + (if (chapter.number.booleanOrNull != null) "0" else chapter.number.content) + + (if (chapter.name.isString) " - " + chapter.name.content else "") + chapter_number = chapter.number.floatOrNull ?: -1f + date_upload = chapter.dateCreated.substringBefore("T").toDate() + setUrlWithoutDomain(chapter.link) } private fun pageListApiRequest(chapterUrl: String, serieId: String, token: String): Request { @@ -220,11 +223,13 @@ class MangaTube : HttpSource() { val token = TOKEN_REGEX.find(apiParams)!!.groupValues[1] val apiRequest = pageListApiRequest(chapterUrl, serieId, token) - val apiResponse = client.newCall(apiRequest).execute().asJson().obj + val apiResponse = client.newCall(apiRequest).execute().let { + json.decodeFromString(it.body!!.string()) + } - return apiResponse["images"].array - .filter { it["url"].string.startsWith("http") } - .mapIndexed { i, obj -> Page(i, chapterUrl, obj["url"].string) } + return apiResponse.images + .filter { it.url.startsWith("http") } + .mapIndexed { i, page -> Page(i, chapterUrl, page.url) } } override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!) @@ -248,10 +253,11 @@ class MangaTube : HttpSource() { val apiParams = document.select("script:containsData(pAPI)").first()!!.data() .substringAfter("pAPI = ") .substringBeforeLast(";") - .let { JsonParser.parseString(it) } + .let { json.parseToJsonElement(it) } + .jsonObject val newUrl = chain.request().url.newBuilder() - .addQueryParameter("nonce", apiParams["nonce"].string) + .addQueryParameter("nonce", apiParams["nonce"]!!.jsonPrimitive.content) .build() val newRequest = chain.request().newBuilder() @@ -272,8 +278,6 @@ class MangaTube : HttpSource() { } } - private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string()) - companion object { private const val ACCEPT = "application/json, text/plain, */*" private const val ACCEPT_HTML = "text/html,application/xhtml+xml,application/xml;q=0.9," + diff --git a/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTubeDto.kt b/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTubeDto.kt new file mode 100644 index 000000000..9e9e678d1 --- /dev/null +++ b/src/pt/mangatube/src/eu/kanade/tachiyomi/extension/pt/mangatube/MangaTubeDto.kt @@ -0,0 +1,51 @@ +package eu.kanade.tachiyomi.extension.pt.mangatube + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonPrimitive + +@Serializable +data class MangaTubeLatestDto( + val page: String, + val releases: List = emptyList(), + @SerialName("total_page") val totalPage: Int +) + +@Serializable +data class MangaTubeReleaseDto( + val image: String, + val link: String, + val name: String +) + +@Serializable +data class MangaTubeTitleDto( + @SerialName("img") val image: String, + val title: String, + val url: String +) + +@Serializable +data class MangaTubePaginatedChaptersDto( + val chapters: List? = emptyList(), + @SerialName("pagina") val page: Int, + @SerialName("total_pags") val totalPages: Int +) + +@Serializable +data class MangaTubeChapterDto( + @SerialName("date_created") val dateCreated: String, + val link: String, + @SerialName("chapter_name") val name: JsonPrimitive, + val number: JsonPrimitive +) + +@Serializable +data class MangaTubeReaderDto( + val images: List = emptyList() +) + +@Serializable +data class MangaTubePageDto( + val url: String +) diff --git a/src/pt/muitomanga/build.gradle b/src/pt/muitomanga/build.gradle index ef3c21bea..574aa1922 100644 --- a/src/pt/muitomanga/build.gradle +++ b/src/pt/muitomanga/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Muito Mangá' pkgNameSuffix = 'pt.muitomanga' extClass = '.MuitoManga' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' containsNsfw = true } diff --git a/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoManga.kt b/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoManga.kt index a56faaa0c..4367592d4 100644 --- a/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoManga.kt +++ b/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoManga.kt @@ -1,11 +1,5 @@ package eu.kanade.tachiyomi.extension.pt.muitomanga -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -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.GET @@ -15,6 +9,8 @@ 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.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor @@ -26,6 +22,7 @@ import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody 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.Locale @@ -53,6 +50,8 @@ class MuitoManga : ParsedHttpSource() { .add("Accept-Language", ACCEPT_LANGUAGE) .add("Referer", "$baseUrl/") + private val json: Json by injectLazy() + private val directoryCache: MutableMap = mutableMapOf() override fun popularMangaRequest(page: Int): Request { @@ -67,11 +66,11 @@ class MuitoManga : ParsedHttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - val result = response.asJson().obj - val totalPages = ceil(result["encontrado"].array.size().toDouble() / ITEMS_PER_PAGE) + val directory = json.decodeFromString(response.body!!.string()) + val totalPages = ceil(directory.results.size.toDouble() / ITEMS_PER_PAGE) val currentPage = response.request.header("X-Page")!!.toInt() - val mangaList = result["encontrado"].array + val mangaList = directory.results .drop(ITEMS_PER_PAGE * (currentPage - 1)) .take(ITEMS_PER_PAGE) .map(::popularMangaFromObject) @@ -79,10 +78,10 @@ class MuitoManga : ParsedHttpSource() { return MangasPage(mangaList, hasNextPage = currentPage < totalPages) } - private fun popularMangaFromObject(obj: JsonElement): SManga = SManga.create().apply { - title = obj["titulo"].string - thumbnail_url = obj["imagem"].string - url = "/manga/" + obj["url"].string + private fun popularMangaFromObject(manga: MuitoMangaTitleDto): SManga = SManga.create().apply { + title = manga.title + thumbnail_url = manga.image + url = "/manga/" + manga.url } override fun latestUpdatesRequest(page: Int): Request { @@ -210,8 +209,6 @@ class MuitoManga : ParsedHttpSource() { } } - private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string()) - companion object { private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" diff --git a/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoMangaDto.kt b/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoMangaDto.kt new file mode 100644 index 000000000..c64d3838a --- /dev/null +++ b/src/pt/muitomanga/src/eu/kanade/tachiyomi/extension/pt/muitomanga/MuitoMangaDto.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.pt.muitomanga + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MuitoMangaDirectoryDto( + @SerialName("encontrado") val results: List = emptyList() +) + +@Serializable +data class MuitoMangaTitleDto( + @SerialName("imagem") val image: String, + @SerialName("titulo") val title: String, + val url: String +) diff --git a/src/pt/mundomangakun/build.gradle b/src/pt/mundomangakun/build.gradle index b4215060a..91561e8c1 100644 --- a/src/pt/mundomangakun/build.gradle +++ b/src/pt/mundomangakun/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Mundo Mangá-Kun' pkgNameSuffix = 'pt.mundomangakun' extClass = '.MundoMangaKun' - extVersionCode = 2 + extVersionCode = 3 libVersion = '1.2' } diff --git a/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt b/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt index 360861c44..2259b8b5f 100644 --- a/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt +++ b/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt @@ -1,9 +1,5 @@ package eu.kanade.tachiyomi.extension.pt.mundomangakun -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 eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter @@ -12,6 +8,10 @@ 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.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient @@ -19,6 +19,7 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.util.concurrent.TimeUnit class MundoMangaKun : ParsedHttpSource() { @@ -40,6 +41,8 @@ class MundoMangaKun : ParsedHttpSource() { .add("Origin", baseUrl) .add("Referer", baseUrl) + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { val refererPath = if (page <= 2) "" else "/leitor-online/${page - 1}" val newHeaders = headersBuilder() @@ -124,20 +127,21 @@ class MundoMangaKun : ParsedHttpSource() { val link = element.attr("onclick") .substringAfter("this,") .substringBeforeLast(")") - .let { JsonParser.parseString(it) } - .array - .first { it.obj["tipo"].string == "LEITOR" } + .replace("'", "\"") + .let { json.parseToJsonElement(it) } + .jsonArray + .first { it.jsonObject["tipo"]!!.jsonPrimitive.content == "LEITOR" } - setUrlWithoutDomain(link.obj["link"].string) + setUrlWithoutDomain(link.jsonObject["link"]!!.jsonPrimitive.content) } override fun pageListParse(document: Document): List { return document.select("script:containsData(var paginas)").first().data() .substringAfter("var paginas=") .substringBefore(";var") - .let { JsonParser.parseString(it) } - .array - .mapIndexed { i, page -> Page(i, document.location(), page.string) } + .let { json.parseToJsonElement(it) } + .jsonArray + .mapIndexed { i, page -> Page(i, document.location(), page.jsonPrimitive.content) } } override fun imageUrlParse(document: Document) = "" diff --git a/src/pt/opex/build.gradle b/src/pt/opex/build.gradle index bbcc58090..ff6b5f601 100644 --- a/src/pt/opex/build.gradle +++ b/src/pt/opex/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'One Piece Ex' pkgNameSuffix = 'pt.opex' extClass = '.OnePieceEx' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/pt/opex/src/eu/kanade/tachiyomi/extension/pt/opex/OnePieceEx.kt b/src/pt/opex/src/eu/kanade/tachiyomi/extension/pt/opex/OnePieceEx.kt index 2b4476304..376a4a8e7 100644 --- a/src/pt/opex/src/eu/kanade/tachiyomi/extension/pt/opex/OnePieceEx.kt +++ b/src/pt/opex/src/eu/kanade/tachiyomi/extension/pt/opex/OnePieceEx.kt @@ -1,8 +1,5 @@ package eu.kanade.tachiyomi.extension.pt.opex -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 eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList @@ -12,6 +9,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.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient @@ -20,6 +20,7 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.util.Locale import java.util.concurrent.TimeUnit @@ -42,6 +43,8 @@ class OnePieceEx : ParsedHttpSource() { .add("Accept-Language", ACCEPT_LANGUAGE) .add("Referer", "$baseUrl/mangas") + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/mangas", headers) override fun popularMangaParse(response: Response): MangasPage { @@ -195,10 +198,9 @@ class OnePieceEx : ParsedHttpSource() { .replace("\\\"", "\"") .replace("\\\\\\/", "/") .replace("//", "/") - .let { JsonParser.parseString(it).obj } - .entrySet() + .let { json.parseToJsonElement(it).jsonObject.entries } .mapIndexed { i, entry -> - Page(i, document.location(), "$baseUrl/${entry.value.string}") + Page(i, document.location(), "$baseUrl/${entry.value.jsonPrimitive.content}") } } diff --git a/src/pt/tsukimangas/build.gradle b/src/pt/tsukimangas/build.gradle index cb5a88509..80616fa80 100644 --- a/src/pt/tsukimangas/build.gradle +++ b/src/pt/tsukimangas/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Tsuki Mangás' pkgNameSuffix = 'pt.tsukimangas' extClass = '.TsukiMangas' - extVersionCode = 15 + extVersionCode = 16 libVersion = '1.2' containsNsfw = true } diff --git a/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt b/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt index eb58979d9..15fd3be0a 100644 --- a/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt +++ b/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt @@ -1,14 +1,5 @@ package eu.kanade.tachiyomi.extension.pt.tsukimangas -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.GET @@ -20,12 +11,15 @@ 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.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -52,24 +46,26 @@ class TsukiMangas : HttpSource() { .add("User-Agent", USER_AGENT) .add("Referer", baseUrl) + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/api/v2/mangas?page=$page&title=&filter=0", headers) } override fun popularMangaParse(response: Response): MangasPage { - val result = response.asJson().obj + val result = json.decodeFromString(response.body!!.string()) - val popularMangas = result["data"].array - .map { popularMangaItemParse(it.obj) } + val popularMangas = result.data.map(::popularMangaItemParse) + + val hasNextPage = result.page < result.lastPage - val hasNextPage = result["page"].int < result["lastPage"].int return MangasPage(popularMangas, hasNextPage) } - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" + private fun popularMangaItemParse(manga: TsukiMangaDto) = SManga.create().apply { + title = manga.title + thumbnail_url = baseUrl + "/imgs/" + manga.poster.substringBefore("?") + url = "/obra/${manga.id}/${manga.url}" } override fun latestUpdatesRequest(page: Int): Request { @@ -77,19 +73,19 @@ class TsukiMangas : HttpSource() { } override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asJson().obj + val result = json.decodeFromString(response.body!!.string()) - val latestMangas = result["data"].array - .map { latestMangaItemParse(it.obj) } + val latestMangas = result.data.map(::latestMangaItemParse) + + val hasNextPage = result.page < result.lastPage - val hasNextPage = result["page"].int < result["lastPage"].int return MangasPage(latestMangas, hasNextPage) } - private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" + private fun latestMangaItemParse(manga: TsukiMangaDto) = SManga.create().apply { + title = manga.title + thumbnail_url = baseUrl + "/imgs/" + manga.poster.substringBefore("?") + url = "/obra/${manga.id}/${manga.url}" } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { @@ -149,20 +145,19 @@ class TsukiMangas : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val result = response.asJson().obj + val result = json.decodeFromString(response.body!!.string()) - val searchResults = result["data"].array - .map { searchMangaItemParse(it.obj) } + val searchResults = result.data.map(::searchMangaItemParse) - val hasNextPage = result["page"].int < result["lastPage"].int + val hasNextPage = result.page < result.lastPage return MangasPage(searchResults, hasNextPage) } - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" + private fun searchMangaItemParse(manga: TsukiMangaDto) = SManga.create().apply { + title = manga.title + thumbnail_url = baseUrl + "/imgs/" + manga.poster.substringBefore("?") + url = "/obra/${manga.id}/${manga.url}" } // Workaround to allow "Open in browser" use the real URL. @@ -192,18 +187,16 @@ class TsukiMangas : HttpSource() { return GET(baseUrl + manga.url, newHeaders) } - override fun mangaDetailsParse(response: Response): SManga { - val result = response.asJson().obj + override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { + val mangaDto = json.decodeFromString(response.body!!.string()) - return SManga.create().apply { - title = result["title"].string - thumbnail_url = baseUrl + "/imgs/" + result["poster"].string.substringBefore("?") - description = result["synopsis"].nullString.orEmpty() - status = result["status"].nullString.orEmpty().toStatus() - author = result["author"].nullString.orEmpty() - artist = result["artist"].nullString.orEmpty() - genre = result["genres"].array.joinToString { it.obj["genre"].string } - } + title = mangaDto.title + thumbnail_url = baseUrl + "/imgs/" + mangaDto.poster.substringBefore("?") + description = mangaDto.synopsis.orEmpty() + status = mangaDto.status.orEmpty().toStatus() + author = mangaDto.author.orEmpty() + artist = mangaDto.artist.orEmpty() + genre = mangaDto.genres.joinToString { it.genre } } override fun chapterListRequest(manga: SManga): Request { @@ -219,25 +212,26 @@ class TsukiMangas : HttpSource() { override fun chapterListParse(response: Response): List { val mangaUrl = response.request.header("Referer")!!.substringAfter(baseUrl) - return response.asJson().array - .flatMap { chapterListItemParse(it.obj, mangaUrl) } + return json + .decodeFromString>(response.body!!.string()) + .flatMap { chapterListItemParse(it, mangaUrl) } .reversed() } - private fun chapterListItemParse(obj: JsonObject, mangaUrl: String): List { + private fun chapterListItemParse(chapter: TsukiChapterDto, mangaUrl: String): List { val mangaId = mangaUrl.substringAfter("obra/").substringBefore("/") val mangaSlug = mangaUrl.substringAfterLast("/") - return obj["versions"].array.map { version -> + return chapter.versions.map { version -> SChapter.create().apply { - name = "Cap. " + obj["number"].string + - (if (!obj["title"].nullString.isNullOrEmpty()) " - " + obj["title"].string else "") - chapter_number = obj["number"].string.toFloatOrNull() ?: -1f - scanlator = version.obj["scans"].array - .sortedBy { it.obj["scan"].obj["name"].string } - .joinToString { it.obj["scan"].obj["name"].string } - date_upload = version.obj["created_at"].string.substringBefore(" ").toDate() - url = "/leitor/$mangaId/${version.obj["id"].int}/$mangaSlug/${obj["number"].string}" + name = "Cap. " + chapter.number + + (if (!chapter.title.isNullOrEmpty()) " - " + chapter.title else "") + chapter_number = chapter.number.toFloatOrNull() ?: -1f + scanlator = version.scans + .sortedBy { it.scan.name } + .joinToString { it.scan.name } + date_upload = version.createdAt.substringBefore(" ").toDate() + url = "/leitor/$mangaId/${version.id}/$mangaSlug/${chapter.number}" } } } @@ -258,12 +252,11 @@ class TsukiMangas : HttpSource() { } override fun pageListParse(response: Response): List { - val result = response.asJson().obj + val result = json.decodeFromString(response.body!!.string()) - return result["pages"].array.mapIndexed { i, page -> - val server = page["server"].string - val cdnUrl = "https://cdn$server.tsukimangas.com" - Page(i, "$baseUrl/", cdnUrl + page.obj["url"].string) + return result.pages.mapIndexed { i, page -> + val cdnUrl = "https://cdn${page.server}.tsukimangas.com" + Page(i, "$baseUrl/", cdnUrl + page.url) } } @@ -426,8 +419,6 @@ class TsukiMangas : HttpSource() { else -> SManga.UNKNOWN } - private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string()) - companion object { private const val ACCEPT = "application/json, text/plain, */*" private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" diff --git a/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangasDto.kt b/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangasDto.kt new file mode 100644 index 000000000..f0719a62a --- /dev/null +++ b/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangasDto.kt @@ -0,0 +1,66 @@ +package eu.kanade.tachiyomi.extension.pt.tsukimangas + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TsukiPaginatedDto( + val data: List = emptyList(), + val lastPage: Int, + val page: Int, + val perPage: Int, + val total: Int +) + +@Serializable +data class TsukiMangaDto( + val artist: String? = "", + val author: String? = "", + val genres: List = emptyList(), + val id: Int, + val poster: String, + val status: String? = "", + val synopsis: String? = "", + val title: String, + val url: String +) + +@Serializable +data class TsukiGenreDto( + val genre: String +) + +@Serializable +data class TsukiChapterDto( + val number: String, + val title: String? = "", + val versions: List = emptyList() +) + +@Serializable +data class TsukiChapterVersionDto( + @SerialName("created_at") val createdAt: String, + val id: Int, + val scans: List = emptyList() +) + +@Serializable +data class TsukiScanlatorDto( + val scan: TsukiScanlatorDetailDto +) + +@Serializable +data class TsukiScanlatorDetailDto( + val name: String +) + +@Serializable +data class TsukiReaderDto( + val pages: List = emptyList() +) + +@Serializable +data class TsukiPageDto( + val server: Int, + val url: String +)