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:
parent
feb1f23be9
commit
925fb1e3a6
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user