Update ZeistManga (#15390)

* Update ZeistManga

* Linting

* More Linting

* Update Filters

* Apply requested changes
This commit is contained in:
seew3l 2023-02-18 15:45:27 -05:00 committed by GitHub
parent ee4b27f60c
commit a736f20dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 283 additions and 28 deletions

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.multisrc.zeistmanga package eu.kanade.tachiyomi.multisrc.zeistmanga
import eu.kanade.tachiyomi.network.GET 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@ -25,7 +26,9 @@ abstract class ZeistManga(
) : ParsedHttpSource() { ) : ParsedHttpSource() {
override val supportsLatest = false 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 chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex()
open val scriptSelector = "#clwd > script" open val scriptSelector = "#clwd > script"
open val imgSelector = "img[src]" open val imgSelector = "img[src]"
@ -38,12 +41,10 @@ abstract class ZeistManga(
?.groupValues?.get(1) ?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed") ?: throw Exception("Failed to find chapter feed")
val url = apiUrl(feed) return apiUrl("Chapter")
.addQueryParameter("start-index", "2") // Only get chapters .addPathSegments(feed)
.addQueryParameter("max-results", "999999") // Get all chapters .addQueryParameter("max-results", "999999") // Get all chapters
.build() .build().toString()
return url.toString()
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
@ -51,15 +52,12 @@ abstract class ZeistManga(
val url = getChaptersUrl(document) val url = getChaptersUrl(document)
// Call JSON API
val req = GET(url, headers) val req = GET(url, headers)
val res = client.newCall(req).execute() val res = client.newCall(req).execute()
// Parse JSON API response
val jsonString = res.body.string() val jsonString = res.body.string()
val result = json.decodeFromString<ZeistMangaDto>(jsonString) val result = json.decodeFromString<ZeistMangaDto>(jsonString)
// Transform JSON response into List<SChapter>
return result.feed?.entry?.map { it.toSChapter(baseUrl) } return result.feed?.entry?.map { it.toSChapter(baseUrl) }
?: throw Exception("Failed to parse from chapter API") ?: throw Exception("Failed to parse from chapter API")
} }
@ -108,6 +106,18 @@ abstract class ZeistManga(
throw UnsupportedOperationException("Not used.") 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 { override fun mangaDetailsParse(document: Document): SManga {
val profileManga = document.selectFirst(".grid.gtc-235fr")!! val profileManga = document.selectFirst(".grid.gtc-235fr")!!
return SManga.create().apply { return SManga.create().apply {
@ -115,6 +125,8 @@ abstract class ZeistManga(
thumbnail_url = profileManga.selectFirst("img")!!.attr("src") thumbnail_url = profileManga.selectFirst("img")!!.attr("src")
description = profileManga.select("#synopsis").text() description = profileManga.select("#synopsis").text()
status = SManga.UNKNOWN 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 { override fun popularMangaParse(response: Response): MangasPage {
val jsonString = response.body.string() val jsonString = response.body.string()
val result = json.decodeFromString<ZeistMangaDto>(jsonString) val result = json.decodeFromString<ZeistMangaDto>(jsonString)
// Transform JSON response into List<SManga>
val mangas = result.feed!!.entry?.map { it.toSManga(baseUrl) } val mangas = result.feed?.entry.orEmpty()
val mangalist = mangas!!.toMutableList() .filter { !it.category.orEmpty().any { category -> category.term == "Anime" } } // Skip animes
.map { it.toSManga(baseUrl) }
val mangalist = mangas.toMutableList()
if (mangas.size == maxResults + 1) { if (mangas.size == maxResults + 1) {
mangalist.removeLast() mangalist.removeLast()
return MangasPage(mangalist, true) return MangasPage(mangalist, true)
@ -146,34 +161,140 @@ abstract class ZeistManga(
.addQueryParameter("start-index", startIndex.toString()) .addQueryParameter("start-index", startIndex.toString())
.build() .build()
return GET(url.toString(), headers) return GET(url, headers)
} }
override fun searchMangaSelector(): String = ".grid.gtc-f141a > div" override fun searchMangaParse(response: Response) = popularMangaParse(response)
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 searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrl().newBuilder() val startIndex = maxResults * (page - 1) + 1
.addQueryParameter("q", query) val url = apiUrl()
.build() .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 { open fun apiUrl(feed: String = "Series"): HttpUrl.Builder {
return "$baseUrl/feeds/posts/default/-/".toHttpUrl().newBuilder() return "$baseUrl/feeds/posts/default/-/".toHttpUrl().newBuilder()
.addPathSegment(feed) .addPathSegment(feed)
.addQueryParameter("alt", "json") .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 { companion object {
private const val maxResults = 20 private const val maxResults = 20
} }

View File

@ -31,6 +31,7 @@ data class ZeistMangaFeedDto(
data class ZeistMangaEntryDto( data class ZeistMangaEntryDto(
val title: ZeistMangaEntryTitleDto? = null, val title: ZeistMangaEntryTitleDto? = null,
val published: ZeistMangaEntryPublishedDto? = null, val published: ZeistMangaEntryPublishedDto? = null,
val category: List<ZeistMangaEntryCategory>? = emptyList(),
@SerialName("link") val url: List<ZeistMangaEntryLink>? = emptyList(), @SerialName("link") val url: List<ZeistMangaEntryLink>? = emptyList(),
val content: ZeistMangaEntryContentDto? = null, val content: ZeistMangaEntryContentDto? = null,
) { ) {
@ -77,3 +78,8 @@ data class ZeistMangaEntryLink(
val rel: String, val rel: String,
val href: String, val href: String,
) )
@Serializable
data class ZeistMangaEntryCategory(
val term: String,
)

View File

@ -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
}

View File

@ -9,7 +9,7 @@ class ZeistMangaGenerator : ThemeSourceGenerator {
override val themeClass = "ZeistManga" override val themeClass = "ZeistManga"
override val baseVersionCode: Int = 3 override val baseVersionCode: Int = 4
override val sources = listOf( override val sources = listOf(
SingleLang("DatGarScanlation", "https://datgarscanlation.blogspot.com", "es"), SingleLang("DatGarScanlation", "https://datgarscanlation.blogspot.com", "es"),

View File

@ -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)
}
}