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: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Tsumino' extName = 'Tsumino'
pkgNameSuffix = 'en.tsumino' pkgNameSuffix = 'en.tsumino'
extClass = '.Tsumino' extClass = '.Tsumino'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

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