Replace Gson usage with kotlinx.serialization in some sources (#7372)

* Replace Gson usage with kotlinx.serialization in some sources.

* Add kotlinx.serialization to common-dependencies.

* Add missing dependencies.
This commit is contained in:
Alessandro Jean 2021-06-02 07:40:59 -03:00 committed by GitHub
parent 3d0119f2b8
commit e8b6a225aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 197 additions and 134 deletions

View File

@ -12,6 +12,8 @@ dependencies {
compileOnly 'org.jsoup:jsoup:1.13.1'
compileOnly 'com.google.code.gson:gson:2.8.6'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.2.0'
compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0'
implementation project(":annotations")
compileOnly project(':duktape-stub')

View File

@ -6,12 +6,8 @@ ext {
extName = 'MANGA Plus by SHUEISHA'
pkgNameSuffix = 'all.mangaplus'
extClass = '.MangaPlusFactory'
extVersionCode = 18
extVersionCode = 19
libVersion = '1.2'
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.2.0'
}
apply from: "$rootDir/common.gradle"

View File

@ -18,4 +18,3 @@ class MangaPlusIndonesian : MangaPlus("id", "eng", Language.INDONESIAN)
class MangaPlusPortuguese : MangaPlus("pt-BR", "eng", Language.PORTUGUESE_BR)
class MangaPlusSpanish : MangaPlus("es", "esp", Language.SPANISH)
class MangaPlusThai : MangaPlus("th", "eng", Language.THAI)

View File

@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Bruttal'
pkgNameSuffix = 'pt.bruttal'
extClass = '.Bruttal'
extVersionCode = 2
extVersionCode = 3
libVersion = '1.2'
}
dependencies {
implementation project(':lib-ratelimit')
}

View File

@ -1,12 +1,5 @@
package eu.kanade.tachiyomi.extension.pt.bruttal
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.JsonObject
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
@ -16,11 +9,14 @@ 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.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
class Bruttal : HttpSource() {
@ -41,6 +37,8 @@ class Bruttal : HttpSource() {
.add("Referer", "$baseUrl/bruttal/")
.add("User-Agent", USER_AGENT)
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request {
val newHeaders = headersBuilder()
.add("Accept", "application/json, text/plain, */*")
@ -50,19 +48,17 @@ class Bruttal : HttpSource() {
}
override fun popularMangaParse(response: Response): MangasPage {
val json = response.asJson().obj
val homeDto = json.decodeFromString<BruttalHomeDto>(response.body!!.string())
val titles = json["list"].array.map { jsonEl ->
popularMangaFromObject(jsonEl.obj)
}
val titles = homeDto.list.map(::popularMangaFromObject)
return MangasPage(titles, false)
}
private fun popularMangaFromObject(obj: JsonObject): SManga = SManga.create().apply {
title = obj["title"].string
thumbnail_url = "$baseUrl/bruttal/" + obj["image_mobile"].string.removePrefix("./")
url = "/bruttal" + obj["url"].string
private fun popularMangaFromObject(comicbook: BruttalComicBookDto): SManga = SManga.create().apply {
title = comicbook.title
thumbnail_url = "$baseUrl/bruttal/" + comicbook.imageMobile.removePrefix("./")
url = "/bruttal" + comicbook.url
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
@ -96,20 +92,20 @@ class Bruttal : HttpSource() {
}
override fun mangaDetailsParse(response: Response): SManga {
val json = response.asJson().array
val comicBooks = json.decodeFromString<List<BruttalComicBookDto>>(response.body!!.string())
val titleUrl = response.request.header("Referer")!!.substringAfter("/bruttal")
val titleObj = json.first { it.obj["url"].string == titleUrl }.obj
val soonText = titleObj["soon_text"].string
val comicBookUrl = response.request.header("Referer")!!
.substringAfter("/bruttal")
val currentComicBook = comicBooks.first { it.url == comicBookUrl }
return SManga.create().apply {
title = titleObj["title"].string
thumbnail_url = "$baseUrl/bruttal/" + titleObj["image_mobile"].string.removePrefix("./")
description = titleObj["synopsis"].string +
(if (soonText.isEmpty()) "" else "\n\n$soonText")
artist = titleObj["illustrator"].string
author = titleObj["author"].string
genre = titleObj["keywords"].string.replace("; ", ", ")
title = currentComicBook.title
thumbnail_url = "$baseUrl/bruttal/" + currentComicBook.imageMobile.removePrefix("./")
description = currentComicBook.synopsis +
(if (currentComicBook.soonText.isEmpty()) "" else "\n\n${currentComicBook.soonText}")
artist = currentComicBook.illustrator
author = currentComicBook.author
genre = currentComicBook.keywords.replace("; ", ", ")
status = SManga.ONGOING
}
}
@ -118,21 +114,24 @@ class Bruttal : HttpSource() {
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val json = response.asJson().array
val comicBooks = json.decodeFromString<List<BruttalComicBookDto>>(response.body!!.string())
val titleUrl = response.request.header("Referer")!!.substringAfter("/bruttal")
val title = json.first { it.obj["url"].string == titleUrl }.obj
val comicBookUrl = response.request.header("Referer")!!
.substringAfter("/bruttal")
val currentComicBook = comicBooks.first { it.url == comicBookUrl }
return title["seasons"].array
.flatMap { it.obj["chapters"].array }
.map { jsonEl -> chapterFromObject(jsonEl.obj) }
return currentComicBook.seasons
.flatMap { it.chapters }
.map(::chapterFromObject)
.reversed()
}
private fun chapterFromObject(obj: JsonObject): SChapter = SChapter.create().apply {
name = obj["title"].string
chapter_number = obj["share_title"].string.removePrefix("Capítulo ").toFloatOrNull() ?: -1f
url = "/bruttal" + obj["url"].string
private fun chapterFromObject(chapter: BruttalChapterDto): SChapter = SChapter.create().apply {
name = chapter.title
chapter_number = chapter.shareTitle
.removePrefix("Capítulo ")
.toFloatOrNull() ?: -1f
url = "/bruttal" + chapter.url
}
override fun pageListRequest(chapter: SChapter): Request {
@ -145,22 +144,28 @@ class Bruttal : HttpSource() {
}
override fun pageListParse(response: Response): List<Page> {
val json = response.asJson().array
val comicBooks = json.decodeFromString<List<BruttalComicBookDto>>(response.body!!.string())
val chapterUrl = response.request.header("Referer")!!
val titleSlug = chapterUrl.substringAfter("bruttal/").substringBefore("/")
val season = chapterUrl.substringAfter("temporada-").substringBefore("/").toInt()
val chapter = chapterUrl.substringAfter("capitulo-")
val comicBookSlug = chapterUrl
.substringAfter("bruttal/")
.substringBefore("/")
val seasonNumber = chapterUrl
.substringAfter("temporada-")
.substringBefore("/")
val chapterNumber = chapterUrl.substringAfter("capitulo-")
val titleObj = json.first { it.obj["url"].string == "/$titleSlug" }.obj
val seasonObj = titleObj["seasons"].array[season - 1].obj
val chapterObj = seasonObj["chapters"].array.first {
it.obj["alias"].string.substringAfter("-") == chapter
val currentComicBook = comicBooks.first { it.url == "/$comicBookSlug" }
val currentSeason = currentComicBook.seasons.first {
it.alias.substringAfter("-") == seasonNumber
}
val currentChapter = currentSeason.chapters.first {
it.alias.substringAfter("-") == chapterNumber
}
return chapterObj["images"].array
.mapIndexed { i, jsonEl ->
val imageUrl = "$baseUrl/bruttal/" + jsonEl.obj["image"].string.removePrefix("./")
return currentChapter.images
.mapIndexed { i, bruttalImage ->
val imageUrl = "$baseUrl/bruttal/" + bruttalImage.image.removePrefix("./")
Page(i, chapterUrl, imageUrl)
}
}
@ -184,10 +189,8 @@ class Bruttal : HttpSource() {
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used")
private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string())
companion object {
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
}
}

View File

@ -0,0 +1,42 @@
package eu.kanade.tachiyomi.extension.pt.bruttal
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class BruttalHomeDto(
val list: List<BruttalComicBookDto> = emptyList()
)
@Serializable
data class BruttalComicBookDto(
val author: String,
val illustrator: String,
@SerialName("image_mobile") val imageMobile: String,
val keywords: String,
val seasons: List<BruttalSeasonDto> = emptyList(),
@SerialName("soon_text") val soonText: String = "",
val synopsis: String,
val title: String,
val url: String
)
@Serializable
data class BruttalSeasonDto(
val alias: String,
val chapters: List<BruttalChapterDto> = emptyList()
)
@Serializable
data class BruttalChapterDto(
val alias: String,
val images: List<BruttalImageDto> = emptyList(),
@SerialName("share_title") val shareTitle: String,
val title: String,
val url: String
)
@Serializable
data class BruttalImageDto(
val image: String
)

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'HipercooL'
pkgNameSuffix = 'pt.hipercool'
extClass = '.Hipercool'
extVersionCode = 6
extVersionCode = 7
libVersion = '1.2'
containsNsfw = true
}

View File

@ -1,17 +1,7 @@
package eu.kanade.tachiyomi.extension.pt.hipercool
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.jsonObject
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonArray
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.lib.ratelimit.SpecificHostRateLimitInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
@ -21,7 +11,13 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
@ -29,10 +25,10 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
@Nsfw
class Hipercool : HttpSource() {
@ -49,7 +45,8 @@ class Hipercool : HttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
.addInterceptor(SpecificHostRateLimitInterceptor(baseUrl.toHttpUrl(), 1))
.addInterceptor(SpecificHostRateLimitInterceptor(STATIC_URL.toHttpUrl(), 2))
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
@ -57,31 +54,27 @@ class Hipercool : HttpSource() {
.add("Referer", baseUrl)
.add("X-Requested-With", "XMLHttpRequest")
private fun genericMangaListParse(response: Response): MangasPage {
val result = response.asJson().array
private val json: Json by injectLazy()
if (result.size() == 0)
private fun genericMangaListParse(response: Response): MangasPage {
val chapters = json.decodeFromString<List<HipercoolChapterDto>>(response.body!!.string())
if (chapters.isEmpty())
return MangasPage(emptyList(), false)
val mangaList = result
.map { genericMangaFromObject(it.obj) }
val mangaList = chapters
.map(::genericMangaFromObject)
.distinctBy { it.title }
val hasNextPage = result.size() == DEFAULT_COUNT
val hasNextPage = chapters.size == DEFAULT_COUNT
return MangasPage(mangaList, hasNextPage)
}
private fun genericMangaFromObject(obj: JsonObject): SManga {
val book = obj["_book"].obj
val bookSlug = book["slug"].string
val bookRevision = book["revision"]?.int ?: 1
return SManga.create().apply {
title = book["title"].string
thumbnail_url = bookSlug.toThumbnailUrl(bookRevision)
url = "/books/$bookSlug"
}
private fun genericMangaFromObject(chapter: HipercoolChapterDto): SManga = SManga.create().apply {
title = chapter.book!!.title
thumbnail_url = chapter.book.slug.toThumbnailUrl(chapter.book.revision)
url = "/books/" + chapter.book.slug
}
// The source does not have popular mangas, so use latest instead.
@ -100,12 +93,12 @@ class Hipercool : HttpSource() {
val mediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
// Create json body.
val json = jsonObject(
"start" to (page - 1) * DEFAULT_COUNT,
"count" to DEFAULT_COUNT,
"text" to query,
"type" to "text"
)
val json = buildJsonObject {
put("start", (page - 1) * DEFAULT_COUNT)
put("content", DEFAULT_COUNT)
put("text", query)
put("type", "text")
}
val body = json.toString().toRequestBody(mediaType)
@ -130,27 +123,27 @@ class Hipercool : HttpSource() {
}
override fun mangaDetailsParse(response: Response): SManga {
val result = response.asJson().obj
val book = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
val artists = result["tags"].array
.filter { it["label"].string == "Artista" }
.flatMap { it["values"].array }
.joinToString("; ") { it["label"].string }
val artists = book.tags
.filter { it.label == "Artista" }
.flatMap { it.values }
.joinToString("; ") { it.label }
val authors = result["tags"].array
.filter { it["label"].string == "Autor" }
.flatMap { it["values"].array }
.joinToString("; ") { it["label"].string }
val authors = book.tags
.filter { it.label == "Autor" }
.flatMap { it.values }
.joinToString("; ") { it.label }
val tags = result["tags"].array
.filter { it["label"].string == "Tags" }
.flatMap { it["values"].array }
.joinToString(", ") { it["label"].string }
val tags = book.tags
.filter { it.label == "Tags" }
.flatMap { it.values }
.joinToString { it.label }
return SManga.create().apply {
title = result["title"].string
thumbnail_url = result["slug"].string.toThumbnailUrl(result["revision"].int)
description = result["synopsis"]?.string ?: ""
title = book.title
thumbnail_url = book.slug.toThumbnailUrl(book.revision)
description = book.synopsis.orEmpty()
artist = artists
author = authors
genre = tags
@ -161,31 +154,31 @@ class Hipercool : HttpSource() {
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.asJson().obj
val book = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
if (!result["chapters"]!!.isJsonArray)
if (book.chapters is JsonPrimitive)
return emptyList()
return result["chapters"].array
.map { chapterListItemParse(result, it.obj) }
return json.decodeFromString<List<HipercoolChapterDto>>(book.chapters.toString())
.map { chapterListItemParse(book, it) }
.reversed()
}
private fun chapterListItemParse(book: JsonObject, obj: JsonObject): SChapter = SChapter.create().apply {
name = obj["title"].string
chapter_number = obj["title"].string.toFloatOrNull() ?: -1f
// The property is written wrong.
date_upload = DATE_FORMATTER.tryParseTime(obj["publishied_at"].string)
private fun chapterListItemParse(book: HipercoolBookDto, chapter: HipercoolChapterDto): SChapter =
SChapter.create().apply {
name = "Cap. " + chapter.title
chapter_number = chapter.title.toFloatOrNull() ?: -1f
date_upload = chapter.publishedAt.toDate()
val fullUrl = "$baseUrl/books".toHttpUrlOrNull()!!.newBuilder()
.addPathSegment(book["slug"].string)
.addPathSegment(obj["slug"].string)
.addQueryParameter("images", obj["images"].int.toString())
.addQueryParameter("revision", book["revision"].int.toString())
.toString()
val fullUrl = "$baseUrl/books".toHttpUrlOrNull()!!.newBuilder()
.addPathSegment(book.slug)
.addPathSegment(chapter.slug)
.addQueryParameter("images", chapter.images.toString())
.addQueryParameter("revision", book.revision.toString())
.toString()
setUrlWithoutDomain(fullUrl)
}
setUrlWithoutDomain(fullUrl)
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val chapterUrl = (baseUrl + chapter.url).toHttpUrlOrNull()!!
@ -228,9 +221,9 @@ class Hipercool : HttpSource() {
return GET(page.imageUrl!!, newHeaders)
}
private fun SimpleDateFormat.tryParseTime(date: String): Long {
private fun String.toDate(): Long {
return try {
parse(date.substringBefore("T"))?.time ?: 0L
DATE_FORMATTER.parse(substringBefore("T"))?.time ?: 0L
} catch (e: ParseException) {
0L
}
@ -243,13 +236,11 @@ class Hipercool : HttpSource() {
.addQueryParameter("revision", revision.toString())
.toString()
private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string())
companion object {
private const val STATIC_URL = "https://static.hiper.cool"
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
private const val DEFAULT_COUNT = 40

View File

@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.extension.pt.hipercool
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class HipercoolBookDto(
val chapters: JsonElement,
val revision: Int = 1,
val slug: String,
val synopsis: String? = null,
val tags: List<HipercoolTagDto> = emptyList(),
val title: String
)
@Serializable
data class HipercoolTagDto(
val label: String,
val values: List<HipercoolTagDto> = emptyList()
)
@Serializable
data class HipercoolChapterDto(
@SerialName("_book") val book: HipercoolBookDto? = null,
val images: Int = 0,
@SerialName("publishied_at") val publishedAt: String,
val slug: String,
val title: String
)

View File

@ -6,13 +6,12 @@ ext {
extName = 'Dmzj'
pkgNameSuffix = 'zh.dmzj'
extClass = '.Dmzj'
extVersionCode = 17
extVersionCode = 18
libVersion = '1.2'
}
dependencies {
implementation project(':lib-ratelimit')
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.2.0'
}
apply from: "$rootDir/common.gradle"