diff --git a/src/en/tsumino/build.gradle b/src/en/tsumino/build.gradle index 2411757a7..3c566290d 100644 --- a/src/en/tsumino/build.gradle +++ b/src/en/tsumino/build.gradle @@ -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 } diff --git a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt index 3820ba08c..060d95d82 100644 --- a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt +++ b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt @@ -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)