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 { @@ -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(jsonString) - // Transform JSON response into List 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(jsonString) - // Transform JSON response into List - 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 = 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 = 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 = 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 = 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? = emptyList(), @SerialName("link") val url: List? = 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) : Filter.Group(title, genres) +class TypeList(title: String, types: List) : EnhancedSelect(title, types.toTypedArray()) +class LanguageList(title: String, languages: List) : EnhancedSelect(title, languages.toTypedArray()) +class StatusList(title: String, statuses: List) : EnhancedSelect(title, statuses.toTypedArray()) + +open class EnhancedSelect(name: String, values: Array) : Filter.Select(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) + } +}