Migrating 3 sources to kotlinx.serialization (#8463)

* HentaiHand: Migration to kotlinx.serialization

* SimplyHentai: Migration to kotlinx.serialization

* Kangaryu: Migration to kotlinx.serialization
This commit is contained in:
Arraiment 2021-08-08 23:38:14 +08:00 committed by GitHub
parent 5a0d513b94
commit 6470dd5245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 99 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'HentaiHand'
pkgNameSuffix = 'all.hentaihand'
extClass = '.HentaiHandFactory'
extVersionCode = 3
extVersionCode = 4
libVersion = '1.2'
containsNsfw = true
}

View File

@ -1,17 +1,9 @@
package eu.kanade.tachiyomi.extension.all.hentaihand
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.text.InputType
import android.widget.Toast
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.nullObj
import com.github.salomonbrys.kotson.nullString
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
@ -24,20 +16,28 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONException
import org.json.JSONObject
import rx.Observable
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@Nsfw
abstract class HentaiHand(
@ -50,29 +50,37 @@ abstract class HentaiHand(
override val name: String = "HentaiHand$extraName"
override val supportsLatest = true
private val gson = Gson()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor { authIntercept(it) }
.build()
private fun parseGenericResponse(response: Response): MangasPage {
val data = gson.fromJson<JsonObject>(response.body!!.string())
return MangasPage(
data.getAsJsonArray("data").map {
SManga.create().apply {
url = "/en/comic/${it["slug"].asString}"
title = it["title"].asString
thumbnail_url = it["thumb_url"].asString
private val json: Json by injectLazy()
private fun slugToUrl(json: JsonObject) = json["slug"]!!.jsonPrimitive.content.prependIndent("/en/comic/")
private fun jsonArrayToString(arrayKey: String, obj: JsonObject): String? {
val array = obj[arrayKey]!!.jsonArray
if (array.isEmpty()) return null
return array.joinToString(", ") {
it.jsonObject["name"]!!.jsonPrimitive.content
}
},
!data["next_page_url"].isJsonNull
)
}
// Popular
override fun popularMangaParse(response: Response): MangasPage = parseGenericResponse(response)
override fun popularMangaParse(response: Response): MangasPage {
val jsonResponse = json.parseToJsonElement(response.body!!.string())
val mangaList = jsonResponse.jsonObject["data"]!!.jsonArray.map {
val obj = it.jsonObject
SManga.create().apply {
url = slugToUrl(obj)
title = obj["title"]!!.jsonPrimitive.content
thumbnail_url = obj["thumb_url"]!!.jsonPrimitive.content
}
}
val hasNextPage = jsonResponse.jsonObject["next_page_url"]!!.jsonPrimitive.content.isNotEmpty()
return MangasPage(mangaList, hasNextPage)
}
override fun popularMangaRequest(page: Int): Request {
val url = "$baseUrl/api/comics?page=$page&sort=popularity&order=desc&duration=all"
@ -81,7 +89,7 @@ abstract class HentaiHand(
// Latest
override fun latestUpdatesParse(response: Response): MangasPage = parseGenericResponse(response)
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun latestUpdatesRequest(page: Int): Request {
val url = "$baseUrl/api/comics?page=$page&sort=uploaded_at&order=desc&duration=week"
@ -90,17 +98,20 @@ abstract class HentaiHand(
// Search
override fun searchMangaParse(response: Response): MangasPage = parseGenericResponse(response)
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
private fun lookupFilterId(query: String, uri: String): Int? {
// filter query needs to be resolved to an ID
return client.newCall(GET("$baseUrl/api/$uri?q=$query"))
.asObservableSuccess()
.subscribeOn(Schedulers.io())
.map {
val data = gson.fromJson<JsonObject>(it.body!!.string())
// only the first tag will be used
data.getAsJsonArray("data").firstOrNull()?.let { t -> t["id"].asInt }
.map { response ->
// Returns the first matched id, or null if there are no results
val idList = json.parseToJsonElement(response.body!!.string()).jsonObject["data"]!!.jsonArray.map {
it.jsonObject["id"]!!.jsonPrimitive.content
}
if (idList.isEmpty()) return@map null
else idList.first().toInt()
}.toBlocking().first()
}
@ -138,12 +149,6 @@ abstract class HentaiHand(
// Details
private fun tagArrayToString(array: JsonArray, key: String = "name"): String? {
if (array.size() == 0)
return null
return array.joinToString { it[key].asString }
}
private fun mangaDetailsApiRequest(manga: SManga): Request {
val slug = manga.url.removePrefix("/en/comic/")
return GET("$baseUrl/api/comics/$slug")
@ -156,28 +161,26 @@ abstract class HentaiHand(
}
override fun mangaDetailsParse(response: Response): SManga {
val data = gson.fromJson<JsonObject>(response.body!!.string())
val obj = json.parseToJsonElement(response.body!!.string()).jsonObject
return SManga.create().apply {
artist = tagArrayToString(data.getAsJsonArray("artists"))
author = tagArrayToString(data.getAsJsonArray("authors")) ?: artist
genre = listOf("tags", "relationships").map {
data.getAsJsonArray(it).map { t -> t["name"].asString }
}.flatten().distinct().joinToString()
url = slugToUrl(obj)
title = obj["title"]!!.jsonPrimitive.content
thumbnail_url = obj["thumb_url"]!!.jsonPrimitive.content
artist = jsonArrayToString("artists", obj)
author = jsonArrayToString("authors", obj) ?: artist
genre = listOfNotNull(jsonArrayToString("tags", obj), jsonArrayToString("relationships", obj)).joinToString(", ")
status = SManga.COMPLETED
description = listOf(
Pair("Alternative Title", data["alternative_title"].nullString),
Pair("Groups", tagArrayToString(data.getAsJsonArray("groups"))),
Pair("Description", data["description"].nullString),
Pair("Pages", data["pages"].asInt.toString()),
Pair("Category", data["category"].nullObj?.get("name")?.asString),
Pair("Language", data["language"].nullObj?.get("name")?.asString),
Pair("Parodies", tagArrayToString(data.getAsJsonArray("parodies"))),
Pair("Characters", tagArrayToString(data.getAsJsonArray("characters")))
).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}:\n${it.second}" }
Pair("Alternative Title", obj["alternative_title"]!!.jsonPrimitive.content),
Pair("Groups", jsonArrayToString("groups", obj)),
Pair("Description", obj["description"]!!.jsonPrimitive.content),
Pair("Pages", obj["pages"]!!.jsonPrimitive.content),
Pair("Category", obj["category"]!!.jsonObject["name"]!!.jsonPrimitive.content),
Pair("Language", obj["language"]!!.jsonObject["name"]!!.jsonPrimitive.content),
Pair("Parodies", jsonArrayToString("parodies", obj)),
Pair("Characters", jsonArrayToString("characters", obj))
).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" }
}
}
@ -186,12 +189,12 @@ abstract class HentaiHand(
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val data = gson.fromJson<JsonObject>(response.body!!.string())
val obj = json.parseToJsonElement(response.body!!.string()).jsonObject
return listOf(
SChapter.create().apply {
url = "/en/comic/${data["slug"].asString}/reader/1"
url = "/en/comic/${obj["slug"]!!.jsonPrimitive.content}/reader/1"
name = "Chapter"
date_upload = DATE_FORMAT.parse(data["uploaded_at"].asString)?.time ?: 0
date_upload = DATE_FORMAT.parse(obj["uploaded_at"]!!.jsonPrimitive.content)?.time ?: 0
chapter_number = 1f
}
)
@ -204,11 +207,12 @@ abstract class HentaiHand(
return GET("$baseUrl/api/comics/$slug/images")
}
override fun pageListParse(response: Response): List<Page> {
val data = gson.fromJson<JsonObject>(response.body!!.string())
return data.getAsJsonArray("images").mapIndexed { i, it ->
Page(i, "/en/comic/${data["comic"]["slug"].asString}/reader/${it["page"].asInt}", it["source_url"].asString)
}
override fun pageListParse(response: Response): List<Page> =
json.parseToJsonElement(response.body!!.string()).jsonObject["images"]!!.jsonArray.map {
val imgObj = it.jsonObject
val index = imgObj["page"]!!.jsonPrimitive.int
val imgUrl = imgObj["source_url"]!!.jsonPrimitive.content
Page(index, "", imgUrl)
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used")
@ -231,12 +235,12 @@ abstract class HentaiHand(
}
private fun login(chain: Interceptor.Chain, username: String, password: String): String {
val jsonObject = JSONObject().apply {
this.put("username", username)
this.put("password", password)
this.put("remember_me", true)
val jsonObject = buildJsonObject {
put("username", username)
put("password", password)
put("remember_me", true)
}
val body = RequestBody.create(MEDIA_TYPE, jsonObject.toString())
val body = jsonObject.toString().toRequestBody(MEDIA_TYPE)
val response = chain.proceed(POST("$baseUrl/api/login", headers, body))
if (response.code == 401) {
throw Exception("Failed to login, check if username and password are correct")
@ -245,10 +249,9 @@ abstract class HentaiHand(
if (response.body == null)
throw Exception("Login response body is empty")
try {
return JSONObject(response.body!!.string())
.getJSONObject("auth")
.getString("access_token")
} catch (e: JSONException) {
// Returns access token as a string, unless unparseable
return json.parseToJsonElement(response.body!!.string()).jsonObject["auth"]!!.jsonObject["access-token"]!!.jsonPrimitive.content
} catch (e: IllegalArgumentException) {
throw Exception("Cannot parse login response body")
}
}
@ -357,8 +360,7 @@ abstract class HentaiHand(
)
companion object {
@SuppressLint("SimpleDateFormat")
private val DATE_FORMAT = SimpleDateFormat("yyyy-dd-MM")
private val DATE_FORMAT = SimpleDateFormat("yyyy-dd-MM", Locale.US)
private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
private const val USERNAME_TITLE = "Username"
private const val USERNAME_DEFAULT = ""

View File

@ -50,7 +50,7 @@ class HentaiHandFactory : SourceFactory {
)
}
class HentaiHandOther : HentaiHand("other", extraName = " (Unfiltered)")
class HentaiHandOther : HentaiHand("all", extraName = " (Unfiltered)")
class HentaiHandEn : HentaiHand("en", 1)
class HentaiHandZh : HentaiHand("zh", 2)
class HentaiHandJa : HentaiHand("ja", 3)

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Simply Hentai'
pkgNameSuffix = 'all.simplyhentai'
extClass = '.SimplyHentaiFactory'
extVersionCode = 3
extVersionCode = 4
libVersion = '1.2'
containsNsfw = true
}

View File

@ -1,12 +1,6 @@
package eu.kanade.tachiyomi.extension.all.simplyhentai
import android.annotation.SuppressLint
import com.github.salomonbrys.kotson.forEach
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
@ -15,12 +9,16 @@ 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.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
abstract class SimplyHentai(
@ -36,7 +34,7 @@ abstract class SimplyHentai(
override val client: OkHttpClient = network.cloudflareClient
private val gson = Gson()
private val json: Json by injectLazy()
// Popular
@ -158,8 +156,13 @@ abstract class SimplyHentai(
override fun pageListParse(response: Response): List<Page> {
val pages = mutableListOf<Page>()
gson.fromJson<JsonObject>(response.body!!.string()).forEach { _, jsonElement ->
pages.add(Page(pages.size, "", jsonElement["sizes"]["full"].string))
json.parseToJsonElement(response.body!!.string()).jsonObject.forEach {
pages.add(
Page(
pages.size, "",
it.value.jsonObject["sizes"]!!.jsonObject["full"]!!.jsonPrimitive.content
)
)
}
return pages

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Kangaryu'
pkgNameSuffix = 'fr.kangaryu'
extClass = '.Kangaryu'
extVersionCode = 2
extVersionCode = 3
libVersion = '1.2'
}

View File

@ -1,11 +1,5 @@
package eu.kanade.tachiyomi.extension.fr.kangaryu
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -13,11 +7,16 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@ -87,14 +86,15 @@ class Kangaryu : ParsedHttpSource() {
return GET("$baseUrl/search?query=$query", headers)
}
private val gson by lazy { Gson() }
private val json: Json by injectLazy()
override fun searchMangaParse(response: Response): MangasPage {
val mangas = gson.fromJson<JsonObject>(response.body!!.string())["suggestions"].array.map { json ->
val mangas = json.parseToJsonElement(response.body!!.string()).jsonObject["suggestions"]!!.jsonArray.map {
val data = it.jsonObject["data"]!!.jsonPrimitive.content
SManga.create().apply {
url = "/manga/${json["data"].string}"
title = json["value"].string
thumbnail_url = "https://kangaryu-team.fr/uploads/manga/${json["data"].string}/cover/cover_250x350.jpg"
url = "/manga/$data"
title = it.jsonObject["value"]!!.jsonPrimitive.content
thumbnail_url = "https://kangaryu-team.fr/uploads/manga/$data/cover/cover_250x350.jpg"
}
}
return MangasPage(mangas, false)