Fix image downloads and json migration for Tsumino (#8867)

* Migration to kotlinx-serialization and refactor to use HttpSource

* Add interceptor to handle binary response
This commit is contained in:
Arraiment 2021-08-27 18:26:43 +08:00 committed by GitHub
parent feb1f23be9
commit 925fb1e3a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 76 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Tsumino'
pkgNameSuffix = 'en.tsumino'
extClass = '.Tsumino'
extVersionCode = 4
extVersionCode = 5
libVersion = '1.2'
containsNsfw = true
}

View File

@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.extension.en.tsumino
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.annotations.Nsfw
import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getArtists
import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getChapter
@ -18,18 +14,27 @@ 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.ParsedHttpSource
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import okhttp3.ResponseBody.Companion.toResponseBody
import rx.Observable
import uy.kohesive.injekt.injectLazy
@Nsfw
class Tsumino : ParsedHttpSource() {
class Tsumino : HttpSource() {
override val name = "Tsumino"
@ -39,46 +44,65 @@ class Tsumino : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
// Based on Pufei ext
private val rewriteOctetStream: Interceptor = Interceptor { chain ->
val originalResponse: Response = chain.proceed(chain.request())
if (originalResponse.headers("Content-Type").contains("application/octet-stream") &&
originalResponse.request.url.pathSegments.any { it == "parts" }
) {
val orgBody = originalResponse.body!!.bytes()
val newBody = orgBody.toResponseBody("image/jpeg".toMediaTypeOrNull())
originalResponse.newBuilder()
.body(newBody)
.build()
} else originalResponse
}
private val gson = Gson()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rewriteOctetStream)
.build()
override fun latestUpdatesSelector() = "Not needed"
private val json: Json by injectLazy()
@Serializable
data class Manga(
val id: Int,
val title: String,
val thumbnailUrl: String
)
// Latest
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/Search/Operate/?PageNumber=$page&Sort=Newest")
override fun latestUpdatesParse(response: Response): MangasPage {
val allManga = mutableListOf<SManga>()
val body = response.body!!.string()
val jsonManga = gson.fromJson<JsonObject>(body)["data"].asJsonArray
for (i in 0 until jsonManga.size()) {
val manga = SManga.create()
manga.url = "/entry/" + jsonManga[i]["entry"]["id"].asString
manga.title = jsonManga[i]["entry"]["title"].asString
manga.thumbnail_url = jsonManga[i]["entry"]["thumbnailUrl"].asString
allManga.add(manga)
val mangaList = mutableListOf<SManga>()
val jsonResponse = json.parseToJsonElement(response.body!!.string()).jsonObject
for (element in jsonResponse["data"]!!.jsonArray) {
val manga = json.decodeFromJsonElement<Manga>(element.jsonObject["entry"]!!)
mangaList.add(
SManga.create().apply {
setUrlWithoutDomain("/entry/${manga.id}")
title = manga.title
thumbnail_url = manga.thumbnailUrl
}
)
}
val currentPage = gson.fromJson<JsonObject>(body)["pageNumber"].asString
val totalPage = gson.fromJson<JsonObject>(body)["pageCount"].asString
val hasNextPage = currentPage.toInt() != totalPage.toInt()
val currentPage = jsonResponse["pageNumber"]!!.jsonPrimitive.int
val totalPage = jsonResponse["pageCount"]!!.jsonPrimitive.int
return MangasPage(allManga, hasNextPage)
return MangasPage(mangaList, currentPage < totalPage)
}
override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun latestUpdatesNextPageSelector() = "Not needed"
// Popular
override fun popularMangaRequest(page: Int) = GET("$baseUrl/Search/Operate/?PageNumber=$page&Sort=Popularity")
override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response)
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularMangaSelector() = latestUpdatesSelector()
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
// Taken from github.com/NerdNumber9/TachiyomiEH
@ -132,62 +156,38 @@ class Tsumino : ParsedHttpSource() {
override fun searchMangaParse(response: Response): MangasPage = latestUpdatesParse(response)
override fun searchMangaSelector() = latestUpdatesSelector()
// Details
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun mangaDetailsRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
}
return super.mangaDetailsRequest(manga)
}
override fun mangaDetailsParse(document: Document): SManga {
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val infoElement = document.select("div.book-page-container")
val manga = SManga.create()
manga.title = infoElement.select("#Title").text()
manga.artist = getArtists(document)
manga.author = manga.artist
manga.status = SManga.COMPLETED
manga.thumbnail_url = infoElement.select("img").attr("src")
manga.description = getDesc(document)
manga.genre = document.select("#Tag a").joinToString { it.text() }
return manga
}
override fun chapterListRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
return SManga.create().apply {
title = infoElement.select("#Title").text()
artist = getArtists(document)
author = artist
status = SManga.COMPLETED
thumbnail_url = infoElement.select("img").attr("src")
description = getDesc(document)
genre = document.select("#Tag a").joinToString { it.text() }
}
return super.chapterListRequest(manga)
}
// Chapters
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val collection = document.select(chapterListSelector())
val collection = document.select(".book-collection-table a")
return if (collection.isNotEmpty()) {
getCollection(document, chapterListSelector())
getCollection(document, ".book-collection-table a")
} else {
getChapter(document, response)
}
}
override fun chapterListSelector() = ".book-collection-table a"
// Page List
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used")
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url.startsWith("http")) {
return GET(chapter.url, headers)
}
return super.pageListRequest(chapter)
}
override fun pageListParse(document: Document): List<Page> {
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val pages = mutableListOf<Page>()
val numPages = document.select("h1").text().split(" ").last()
@ -203,7 +203,7 @@ class Tsumino : ParsedHttpSource() {
return pages
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used")
data class AdvSearchEntry(val type: Int, val text: String, val exclude: Boolean)