From 0f3be648014f340f08dd70d9afc6780b8d4ffe39 Mon Sep 17 00:00:00 2001 From: Rama Bondan Prakoso Date: Fri, 20 Sep 2019 10:25:36 +0700 Subject: [PATCH] Tsumino: Refactor and add search filters (#1534) Tsumino: Refactor and add search filters --- src/en/tsumino/build.gradle | 2 +- .../tachiyomi/extension/en/tsumino/Tsumino.kt | 138 +++++++++++++----- .../extension/en/tsumino/TsuminoUtils.kt | 50 +++++-- 3 files changed, 143 insertions(+), 47 deletions(-) diff --git a/src/en/tsumino/build.gradle b/src/en/tsumino/build.gradle index db9fda667..f37415025 100644 --- a/src/en/tsumino/build.gradle +++ b/src/en/tsumino/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: Tsumino' pkgNameSuffix = 'en.tsumino' extClass = '.Tsumino' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } 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 e40a959e9..3e058cac3 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 @@ -5,13 +5,15 @@ import com.github.salomonbrys.kotson.get import com.google.gson.Gson import com.google.gson.JsonObject import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getArtists -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getDate +import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getCollection +import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getChapter import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getDesc -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getGroups import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -22,7 +24,7 @@ class Tsumino: ParsedHttpSource() { override val name = "Tsumino" - override val baseUrl = "https://tsumino.com" + override val baseUrl = "https://www.tsumino.com" override val lang = "en" @@ -38,8 +40,8 @@ class Tsumino: ParsedHttpSource() { override fun latestUpdatesParse(response: Response): MangasPage { val allManga = mutableListOf() - val jsonManga = gson.fromJson(response.body()!!.string())["data"].asJsonArray - + val body = response.body()!!.string() + val jsonManga = gson.fromJson(body)["data"].asJsonArray for (i in 0 until jsonManga.size()) { val manga = SManga.create() manga.url = "/entry/" + jsonManga[i]["entry"]["id"].asString @@ -48,7 +50,11 @@ class Tsumino: ParsedHttpSource() { allManga.add(manga) } - return MangasPage(allManga, true) + val currentPage = gson.fromJson(body)["pageNumber"].asString + val totalPage = gson.fromJson(body)["pageCount"].asString + val hasNextPage = currentPage.toInt() != totalPage.toInt() + + return MangasPage(allManga, hasNextPage) } override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") @@ -65,8 +71,38 @@ class Tsumino: ParsedHttpSource() { override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - throw UnsupportedOperationException("Not implemented yet") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + // Taken from github.com/NerdNumber9/TachiyomiEH + val f = filters + getFilterList() + val advSearch = f.filterIsInstance().flatMap { filter -> + val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank) + splitState.map { + AdvSearchEntry(filter.type, it.removePrefix("-"), it.startsWith("-")) + } + } + val body = FormBody.Builder() + .add("PageNumber", page.toString()) + .add("Text", query) + .add("Sort", SortType.values()[f.filterIsInstance().first().state].name) + .add("List", "0") + .add("Length", LengthType.values()[f.filterIsInstance().first().state].id.toString()) + .add("MinimumRating", f.filterIsInstance().first().state.toString()) + .apply { + advSearch.forEachIndexed { index, entry -> + add("Tags[$index][Type]", entry.type.toString()) + add("Tags[$index][Text]", entry.text) + add("Tags[$index][Exclude]", entry.exclude.toString()) + } + + if(f.filterIsInstance().first().state) + add("Exclude[]", "6") + } + .build() + + return POST("$baseUrl/Search/Operate/", headers, body) + } + + override fun searchMangaParse(response: Response): MangasPage = latestUpdatesParse(response) override fun searchMangaSelector() = latestUpdatesSelector() @@ -84,18 +120,11 @@ class Tsumino: ParsedHttpSource() { override fun mangaDetailsParse(document: Document): SManga { val infoElement = document.select("div.book-page-container") val manga = SManga.create() - val genres = mutableListOf() - - infoElement.select("#Tag a").forEach { element -> - val genre = element.text() - genres.add(genre) - } manga.title = infoElement.select("#Title").text() manga.artist = getArtists(document) manga.author = manga.artist manga.status = SManga.COMPLETED - manga.genre = genres.joinToString(", ") manga.thumbnail_url = infoElement.select("img").attr("src") manga.description = getDesc(document) @@ -109,28 +138,12 @@ class Tsumino: ParsedHttpSource() { } override fun chapterListParse(response: Response): List { - val chapterList = mutableListOf() val document = response.asJsoup() val collection = document.select(chapterListSelector()) - if (collection.isNotEmpty()) { - return collection.map { element -> - SChapter.create().apply { - name = element.text() - scanlator = getGroups(document) - setUrlWithoutDomain(element.attr("href"). - replace("entry", "Read/Index")) - } - }.reversed() + return if (collection.isNotEmpty()) { + getCollection(document, chapterListSelector()) } else { - val chapter = SChapter.create().apply { - name = "Chapter" - scanlator = getGroups(document) - chapter_number = 1f - setUrlWithoutDomain(response.request().url().encodedPath(). - replace("entry", "Read/Index")) - } - chapterList.add(chapter) - return chapterList + getChapter(document, response) } } @@ -163,5 +176,60 @@ class Tsumino: ParsedHttpSource() { override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - override fun getFilterList() = FilterList() + data class AdvSearchEntry(val type: Int, val text: String, val exclude: Boolean) + + override fun getFilterList() = FilterList( + Filter.Header("Separate tags with commas (,)"), + Filter.Header("Prepend with dash (-) to exclude"), + TagFilter(), + CategoryFilter(), + CollectionFilter(), + GroupFilter(), + ArtistFilter(), + ParodyFilter(), + CharactersFilter(), + UploaderFilter(), + + Filter.Separator(), + + SortFilter(), + LengthFilter(), + MinimumRatingFilter(), + ExcludeParodiesFilter() + ) + + class TagFilter : AdvSearchEntryFilter("Tags", 1) + class CategoryFilter : AdvSearchEntryFilter("Categories", 2) + class CollectionFilter : AdvSearchEntryFilter("Collections", 3) + class GroupFilter : AdvSearchEntryFilter("Groups", 4) + class ArtistFilter : AdvSearchEntryFilter("Artists", 5) + class ParodyFilter : AdvSearchEntryFilter("Parodies", 6) + class CharactersFilter : AdvSearchEntryFilter("Characters", 7) + class UploaderFilter : AdvSearchEntryFilter("Uploaders", 8) + open class AdvSearchEntryFilter(name: String, val type: Int) : Filter.Text(name) + + class SortFilter : Filter.Select("Sort by", SortType.values()) + class LengthFilter : Filter.Select("Length", LengthType.values()) + class MinimumRatingFilter : Filter.Select("Minimum rating", (0 .. 5).map { "$it stars" }.toTypedArray()) + class ExcludeParodiesFilter : Filter.CheckBox("Exclude parodies") + + enum class SortType { + Newest, + Oldest, + Alphabetical, + Rating, + Pages, + Views, + Random, + Comments, + Popularity + } + + enum class LengthType(val id: Int) { + Any(0), + Short(1), + Medium(2), + Long(3) + } + } diff --git a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt index 11bc1e0b2..eadea07ce 100644 --- a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt +++ b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt @@ -1,9 +1,8 @@ package eu.kanade.tachiyomi.extension.en.tsumino +import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.Response import org.jsoup.nodes.Document -import java.lang.StringBuilder -import java.text.SimpleDateFormat -import java.util.* class TsuminoUtils { companion object { @@ -21,7 +20,7 @@ class TsuminoUtils { return stringBuilder.toString() } - fun getGroups(document: Document): String? { + private fun getGroups(document: Document): String? { val stringBuilder = StringBuilder() val groups = document.select("#Group a") @@ -40,6 +39,7 @@ class TsuminoUtils { val pages = document.select("#Pages").text() val parodies = document.select("#Parody a") val characters = document.select("#Character a") + val tags = document.select("#Tag a") stringBuilder.append("Pages: $pages") @@ -66,17 +66,45 @@ class TsuminoUtils { stringBuilder.append(", ") } } + + if (tags.size > 0) { + stringBuilder.append("\n\n") + stringBuilder.append("Tags: ") + + tags.forEach { + stringBuilder.append(it.text()) + + if (it != tags.last()) + stringBuilder.append(", ") + } + } + return stringBuilder.toString() } - fun getDate(document: Document): Long { - val timeString = document.select("#Uploaded").text() - return try { - SimpleDateFormat("yyyy MMMMM dd", Locale.ENGLISH).parse(timeString).time - }catch (e: Exception) { - 0 - } + fun getCollection(document: Document, selector: String): List { + return document.select(selector).map { element -> + SChapter.create().apply { + val chapterNum = element.select("span")[0].text() + val chapterName = element.select("span")[1].text() + name = "$chapterNum. $chapterName" + scanlator = getGroups(document) + url = element.attr("href").replace("entry", "Read/Index") + } + }.reversed() } + fun getChapter(document: Document, response: Response): List { + val chapterList = mutableListOf() + val chapter = SChapter.create().apply { + name = "Chapter" + scanlator = getGroups(document) + chapter_number = 1f + url = response.request().url().encodedPath(). + replace("entry", "Read/Index") + } + chapterList.add(chapter) + return chapterList + } } }