From a736f20dd0d55363e89f40b61e1bbabca1de09fa Mon Sep 17 00:00:00 2001
From: seew3l <90949336+seew3l@users.noreply.github.com>
Date: Sat, 18 Feb 2023 15:45:27 -0500
Subject: [PATCH] Update ZeistManga (#15390)

* Update ZeistManga

* Linting

* More Linting

* Update Filters

* Apply requested changes
---
 .../multisrc/zeistmanga/ZeistManga.kt         | 175 +++++++++++++++---
 .../multisrc/zeistmanga/ZeistMangaDto.kt      |   6 +
 .../multisrc/zeistmanga/ZeistMangaFilters.kt  |  26 +++
 .../zeistmanga/ZeistMangaGenerator.kt         |   2 +-
 .../multisrc/zeistmanga/ZeistMangaIntl.kt     | 102 ++++++++++
 5 files changed, 283 insertions(+), 28 deletions(-)
 create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaFilters.kt
 create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaIntl.kt

diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt
index bd3cd39b1..7588c8796 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt
@@ -1,6 +1,7 @@
 package eu.kanade.tachiyomi.multisrc.zeistmanga
 
 import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.Filter
 import eu.kanade.tachiyomi.source.model.FilterList
 import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.Page
@@ -25,7 +26,9 @@ abstract class ZeistManga(
 ) : ParsedHttpSource() {
 
     override val supportsLatest = false
-    val json: Json by injectLazy()
+    open val hasFilters = false
+    protected val json: Json by injectLazy()
+    protected val intl by lazy { ZeistMangaIntl(lang) }
     open val chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex()
     open val scriptSelector = "#clwd > script"
     open val imgSelector = "img[src]"
@@ -38,12 +41,10 @@ abstract class ZeistManga(
             ?.groupValues?.get(1)
             ?: throw Exception("Failed to find chapter feed")
 
-        val url = apiUrl(feed)
-            .addQueryParameter("start-index", "2") // Only get chapters
+        return apiUrl("Chapter")
+            .addPathSegments(feed)
             .addQueryParameter("max-results", "999999") // Get all chapters
-            .build()
-
-        return url.toString()
+            .build().toString()
     }
 
     override fun chapterListParse(response: Response): List<SChapter> {
@@ -51,15 +52,12 @@ abstract class ZeistManga(
 
         val url = getChaptersUrl(document)
 
-        // Call JSON API
         val req = GET(url, headers)
         val res = client.newCall(req).execute()
 
-        // Parse JSON API response
         val jsonString = res.body.string()
         val result = json.decodeFromString<ZeistMangaDto>(jsonString)
 
-        // Transform JSON response into List<SChapter>
         return result.feed?.entry?.map { it.toSChapter(baseUrl) }
             ?: throw Exception("Failed to parse from chapter API")
     }
@@ -108,6 +106,18 @@ abstract class ZeistManga(
         throw UnsupportedOperationException("Not used.")
     }
 
+    override fun searchMangaFromElement(element: Element): SManga {
+        throw UnsupportedOperationException("Not used.")
+    }
+
+    override fun searchMangaNextPageSelector(): String? {
+        throw UnsupportedOperationException("Not used.")
+    }
+
+    override fun searchMangaSelector(): String {
+        throw UnsupportedOperationException("Not used.")
+    }
+
     override fun mangaDetailsParse(document: Document): SManga {
         val profileManga = document.selectFirst(".grid.gtc-235fr")!!
         return SManga.create().apply {
@@ -115,6 +125,8 @@ abstract class ZeistManga(
             thumbnail_url = profileManga.selectFirst("img")!!.attr("src")
             description = profileManga.select("#synopsis").text()
             status = SManga.UNKNOWN
+            genre = profileManga.select("div.mt-15 > a[rel=tag]")
+                .joinToString { it.text() }
         }
     }
 
@@ -128,9 +140,12 @@ abstract class ZeistManga(
     override fun popularMangaParse(response: Response): MangasPage {
         val jsonString = response.body.string()
         val result = json.decodeFromString<ZeistMangaDto>(jsonString)
-        // Transform JSON response into List<SManga>
-        val mangas = result.feed!!.entry?.map { it.toSManga(baseUrl) }
-        val mangalist = mangas!!.toMutableList()
+
+        val mangas = result.feed?.entry.orEmpty()
+            .filter { !it.category.orEmpty().any { category -> category.term == "Anime" } } // Skip animes
+            .map { it.toSManga(baseUrl) }
+
+        val mangalist = mangas.toMutableList()
         if (mangas.size == maxResults + 1) {
             mangalist.removeLast()
             return MangasPage(mangalist, true)
@@ -146,34 +161,140 @@ abstract class ZeistManga(
             .addQueryParameter("start-index", startIndex.toString())
             .build()
 
-        return GET(url.toString(), headers)
+        return GET(url, headers)
     }
 
-    override fun searchMangaSelector(): String = ".grid.gtc-f141a > div"
-    override fun searchMangaFromElement(element: Element): SManga {
-        return SManga.create().apply {
-            setUrlWithoutDomain(element.select(".block").attr("href"))
-            title = element.selectFirst(".clamp.toe.oh.block")!!.text().trim()
-            thumbnail_url = element.selectFirst("img")!!.attr("src")
-        }
-    }
+    override fun searchMangaParse(response: Response) = popularMangaParse(response)
 
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
-        val url = "$baseUrl/search".toHttpUrl().newBuilder()
-            .addQueryParameter("q", query)
-            .build()
+        val startIndex = maxResults * (page - 1) + 1
+        val url = apiUrl()
+            .addQueryParameter("max-results", (maxResults + 1).toString())
+            .addQueryParameter("start-index", startIndex.toString())
 
-        return GET(url.toString(), headers)
+        if (query.isNotBlank()) {
+            url.addQueryParameter("q", query)
+            return GET(url.build(), headers)
+        }
+
+        filters.forEach { filter ->
+            when (filter) {
+                is StatusList -> {
+                    url.addPathSegment(filter.selected.value)
+                }
+
+                is TypeList -> {
+                    url.addPathSegment(filter.selected.value)
+                }
+
+                is LanguageList -> {
+                    url.addPathSegment(filter.selected.value)
+                }
+
+                is GenreList -> {
+                    filter.state.forEach { genre ->
+                        when (genre.state) {
+                            true -> url.addPathSegment(genre.value)
+                            false -> {}
+                        }
+                    }
+                }
+
+                else -> {}
+            }
+        }
+
+        return GET(url.build(), headers)
     }
 
-    override fun searchMangaNextPageSelector(): String? = null
-
     open fun apiUrl(feed: String = "Series"): HttpUrl.Builder {
         return "$baseUrl/feeds/posts/default/-/".toHttpUrl().newBuilder()
             .addPathSegment(feed)
             .addQueryParameter("alt", "json")
     }
 
+    override fun getFilterList(): FilterList {
+        if (!hasFilters) {
+            return FilterList(emptyList())
+        }
+
+        return FilterList(
+            Filter.Header(intl.filterWarning),
+            Filter.Separator(),
+            StatusList(intl.statusFilterTitle, getStatusList()),
+            TypeList(intl.typeFilterTitle, getTypeList()),
+            LanguageList(intl.languageFilterTitle, getLanguageList()),
+            GenreList(intl.genreFilterTitle, getGenreList()),
+        )
+    }
+
+    // Theme Default Status
+    protected open fun getStatusList(): List<Status> = listOf(
+        Status(intl.statusAll, ""),
+        Status(intl.statusOngoing, "Ongoing"),
+        Status(intl.statusCompleted, "Completed"),
+        Status(intl.statusDropped, "Dropped"),
+        Status(intl.statusUpcoming, "Upcoming"),
+    )
+
+    // Theme Default Types
+    protected open fun getTypeList(): List<Type> = listOf(
+        Type(intl.typeAll, ""),
+        Type(intl.typeManga, "Manga"),
+        Type(intl.typeManhua, "Manhua"),
+        Type(intl.typeManhwa, "Manhwa"),
+        Type(intl.typeNovel, "Novel"),
+    )
+
+    // Theme Default Genres
+    protected open fun getGenreList(): List<Genre> = listOf(
+        Genre("Action", "Action"),
+        Genre("Adventurer", "Adventurer"),
+        Genre("Comedy", "Comedy"),
+        Genre("Dementia", "Dementia"),
+        Genre("Drama", "Drama"),
+        Genre("Ecchi", "Ecchi"),
+        Genre("Fantasy", "Fantasy"),
+        Genre("Game", "Game"),
+        Genre("Harem", "Harem"),
+        Genre("Historical", "Historical"),
+        Genre("Horror", "Horror"),
+        Genre("Josei", "Josei"),
+        Genre("Magic", "Magic"),
+        Genre("Martial Arts", "Martial Arts"),
+        Genre("Mecha", "Mecha"),
+        Genre("Military", "Military"),
+        Genre("Music", "Music"),
+        Genre("Mystery", "Mystery"),
+        Genre("Parody", "Parody"),
+        Genre("Police", "Police"),
+        Genre("Psychological", "Psychological"),
+        Genre("Romance", "Romance"),
+        Genre("Samurai", "Samurai"),
+        Genre("School", "School"),
+        Genre("Sci-fi", "Sci-fi"),
+        Genre("Seinen", "Seinen"),
+        Genre("Shoujo", "Shoujo"),
+        Genre("Shoujo Ai", "Shoujo Ai"),
+        Genre("Shounen", "Shounen"),
+        Genre("Slice of Life", "Slice of Life"),
+        Genre("Space", "Space"),
+        Genre("Sports", "Sports"),
+        Genre("Super Power", "Super Power"),
+        Genre("SuperNatural", "SuperNatural"),
+        Genre("Thriller", "Thriller"),
+        Genre("Vampire", "Vampire"),
+        Genre("Work Life", "Work Life"),
+        Genre("Yuri", "Yuri"),
+    )
+
+    // Theme Default Languages
+    protected open fun getLanguageList(): List<Language> = listOf(
+        Language(intl.languageAll, ""),
+        Language("Indonesian", "Indonesian"),
+        Language("English", "English"),
+    )
+
     companion object {
         private const val maxResults = 20
     }
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaDto.kt
index 2fac1f409..78f5a9af0 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaDto.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaDto.kt
@@ -31,6 +31,7 @@ data class ZeistMangaFeedDto(
 data class ZeistMangaEntryDto(
     val title: ZeistMangaEntryTitleDto? = null,
     val published: ZeistMangaEntryPublishedDto? = null,
+    val category: List<ZeistMangaEntryCategory>? = emptyList(),
     @SerialName("link") val url: List<ZeistMangaEntryLink>? = emptyList(),
     val content: ZeistMangaEntryContentDto? = null,
 ) {
@@ -77,3 +78,8 @@ data class ZeistMangaEntryLink(
     val rel: String,
     val href: String,
 )
+
+@Serializable
+data class ZeistMangaEntryCategory(
+    val term: String,
+)
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaFilters.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaFilters.kt
new file mode 100644
index 000000000..ba432c956
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaFilters.kt
@@ -0,0 +1,26 @@
+package eu.kanade.tachiyomi.multisrc.zeistmanga
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+class Genre(title: String, val value: String) : Filter.CheckBox(title)
+
+class GenreList(title: String, genres: List<Genre>) : Filter.Group<Genre>(title, genres)
+class TypeList(title: String, types: List<Type>) : EnhancedSelect<Type>(title, types.toTypedArray())
+class LanguageList(title: String, languages: List<Language>) : EnhancedSelect<Language>(title, languages.toTypedArray())
+class StatusList(title: String, statuses: List<Status>) : EnhancedSelect<Status>(title, statuses.toTypedArray())
+
+open class EnhancedSelect<T>(name: String, values: Array<T>) : Filter.Select<T>(name, values) {
+    val selected: T
+        get() = values[state]
+}
+
+data class Status(val name: String, val value: String) {
+    override fun toString(): String = name
+}
+
+data class Type(val name: String, val value: String) {
+    override fun toString(): String = name
+}
+data class Language(val name: String, val value: String) {
+    override fun toString(): String = name
+}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt
index 08b900b1d..71cabb57f 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt
@@ -9,7 +9,7 @@ class ZeistMangaGenerator : ThemeSourceGenerator {
 
     override val themeClass = "ZeistManga"
 
-    override val baseVersionCode: Int = 3
+    override val baseVersionCode: Int = 4
 
     override val sources = listOf(
         SingleLang("DatGarScanlation", "https://datgarscanlation.blogspot.com", "es"),
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaIntl.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaIntl.kt
new file mode 100644
index 000000000..2f22991b5
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaIntl.kt
@@ -0,0 +1,102 @@
+package eu.kanade.tachiyomi.multisrc.zeistmanga
+
+class ZeistMangaIntl(lang: String) {
+
+    val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH
+
+    // Status Filter
+
+    val statusFilterTitle: String = when (availableLang) {
+        SPANISH -> "Estado"
+        else -> "Status"
+    }
+
+    val statusAll: String = when (availableLang) {
+        SPANISH -> "Todos"
+        else -> "All"
+    }
+
+    val statusOngoing: String = when (availableLang) {
+        SPANISH -> "En curso"
+        else -> "Ongoing"
+    }
+
+    val statusCompleted: String = when (availableLang) {
+        SPANISH -> "Completado"
+        else -> "Completed"
+    }
+
+    val statusDropped: String = when (availableLang) {
+        SPANISH -> "Abandonada"
+        else -> "Dropped"
+    }
+
+    val statusUpcoming: String = when (availableLang) {
+        SPANISH -> "Próximos"
+        else -> "Upcoming"
+    }
+
+    // Type Filter
+
+    val typeFilterTitle: String = when (availableLang) {
+        SPANISH -> "Tipo"
+        else -> "Type"
+    }
+
+    val typeAll: String = when (availableLang) {
+        SPANISH -> "Todos"
+        else -> "All"
+    }
+
+    val typeManga: String = when (availableLang) {
+        SPANISH -> "Manga"
+        else -> "Manga"
+    }
+
+    val typeManhua: String = when (availableLang) {
+        SPANISH -> "Manhua"
+        else -> "Manhua"
+    }
+
+    val typeManhwa: String = when (availableLang) {
+        SPANISH -> "Manhwa"
+        else -> "Manhwa"
+    }
+
+    val typeNovel: String = when (availableLang) {
+        SPANISH -> "Novela"
+        else -> "Novel"
+    }
+
+    // Language Filter
+
+    val languageFilterTitle: String = when (availableLang) {
+        SPANISH -> "Idioma"
+        else -> "Language"
+    }
+
+    val languageAll: String = when (availableLang) {
+        SPANISH -> "Todos"
+        else -> "All"
+    }
+
+    // Genre Filter
+
+    val genreFilterTitle: String = when (availableLang) {
+        SPANISH -> "Género"
+        else -> "Genre"
+    }
+
+    // Extra
+    val filterWarning: String = when (availableLang) {
+        SPANISH -> "Los filtros serán ignorados si la búsqueda no está vacía."
+        else -> "Filters will be ignored if the search is not empty."
+    }
+
+    companion object {
+        const val ENGLISH = "en"
+        const val SPANISH = "es"
+
+        val AVAILABLE_LANGS = arrayOf(ENGLISH, SPANISH)
+    }
+}