bapeey 49b3b90db3 Add GistamisHouse (#2195)
* Add GistamisHouse

* is NSFW

* Fix Yokai

* I need a linter

* amazing

* bump
2024-04-06 23:50:56 +01:00

440 lines
16 KiB
Kotlin

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
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import uy.kohesive.injekt.injectLazy
abstract class ZeistManga(
override val name: String,
override val baseUrl: String,
override val lang: String,
) : HttpSource() {
override val supportsLatest = true
protected val json: Json by injectLazy()
private val intl by lazy { ZeistMangaIntl(lang) }
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
protected open val popularMangaSelector = "div.PopularPosts div.grid > figure"
protected open val popularMangaSelectorTitle = "figcaption > a"
protected open val popularMangaSelectorUrl = "figcaption > a"
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(popularMangaSelector).map { element ->
SManga.create().apply {
thumbnail_url = element.selectFirst("img")!!.attr("abs:src")
title = element.selectFirst(popularMangaSelectorTitle)!!.text()
setUrlWithoutDomain(element.selectFirst(popularMangaSelectorUrl)!!.attr("href"))
}
}
return MangasPage(mangas, false)
}
override fun latestUpdatesRequest(page: Int): Request {
val startIndex = maxMangaResults * (page - 1) + 1
val url = apiUrl()
.addQueryParameter("orderby", "published")
.addQueryParameter("max-results", (maxMangaResults + 1).toString())
.addQueryParameter("start-index", startIndex.toString())
.build()
return GET(url, headers)
}
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val startIndex = maxMangaResults * (page - 1) + 1
val url = apiUrl()
.addQueryParameter("max-results", (maxMangaResults + 1).toString())
.addQueryParameter("start-index", startIndex.toString())
if (query.isNotBlank()) {
url.addQueryParameter("q", query)
val searchUrl = url.build().toString().replaceLast("q=", "q=label:$mangaCategory+")
return GET(searchUrl, 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)
}
protected open val excludedCategories: List<String> = listOf("Anime")
override fun searchMangaParse(response: Response): MangasPage {
val jsonString = response.body.string()
val result = json.decodeFromString<ZeistMangaDto>(jsonString)
val mangas = result.feed?.entry.orEmpty()
.filter { it.category.orEmpty().any { category -> category.term == "Series" } } // Default category for all series
.filterNot { it.category.orEmpty().any { category -> excludedCategories.contains(category.term) } }
.map { it.toSManga(baseUrl) }
val mangalist = mangas.toMutableList()
if (mangas.size == maxMangaResults + 1) {
mangalist.removeLast()
return MangasPage(mangalist, true)
}
return MangasPage(mangalist, false)
}
protected open val statusSelectorList = listOf(
"Status",
"Estado",
)
protected open val authorSelectorList = listOf(
"Author",
"Autor",
"الكاتب",
"Yazar",
)
protected open val artisSelectorList = listOf(
"Artist",
"Artista",
"الرسام",
"Çizer",
)
protected open val mangaDetailsSelector = ".grid.gtc-235fr"
protected open val mangaDetailsSelectorDescription = "#synopsis"
protected open val mangaDetailsSelectorGenres = "div.mt-15 > a[rel=tag]"
protected open val mangaDetailsSelectorAuthor = "span#author"
protected open val mangaDetailsSelectorArtist = "span#artist"
protected open val mangaDetailsSelectorAltName = "header > p"
protected open val mangaDetailsSelectorInfo = ".y6x11p"
protected open val mangaDetailsSelectorInfoTitle = "strong"
protected open val mangaDetailsSelectorInfoDescription = "span.dt"
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val profileManga = document.selectFirst(mangaDetailsSelector)!!
return SManga.create().apply {
thumbnail_url = profileManga.selectFirst("img")!!.attr("abs:src")
description = buildString {
append(profileManga.select(mangaDetailsSelectorDescription).text())
append("\n\n")
profileManga.selectFirst(mangaDetailsSelectorAltName)?.text()?.takeIf { it.isNotBlank() }?.let {
append("Alternative name(s): ")
append(it)
}
}.trim()
genre = profileManga.select(mangaDetailsSelectorGenres)
.joinToString { it.text() }
author = profileManga.selectFirst(mangaDetailsSelectorAuthor)?.text()
artist = profileManga.selectFirst(mangaDetailsSelectorArtist)?.text()
val infoElement = profileManga.select(mangaDetailsSelectorInfo)
infoElement.forEach { element ->
val infoText = element.ownText().trim().ifEmpty { element.selectFirst(mangaDetailsSelectorInfoTitle)?.text()?.trim() ?: "" }
val descText = element.select(mangaDetailsSelectorInfoDescription).text().trim()
when {
statusSelectorList.any { infoText.contains(it) } -> {
status = parseStatus(descText)
}
authorSelectorList.any { infoText.contains(it) } -> {
author = descText
}
artisSelectorList.any { infoText.contains(it) } -> {
artist = descText
}
}
}
}
}
protected open val chapterCategory: String = "Chapter"
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val url = getChapterFeedUrl(document)
val res = client.newCall(GET(url, headers)).execute()
val result = json.decodeFromString<ZeistMangaDto>(res.body.string())
return result.feed?.entry?.filter { it.category.orEmpty().any { category -> category.term == chapterCategory } }
?.map { it.toSChapter(baseUrl) }
?: throw Exception("Failed to parse from chapter API")
}
protected open val useNewChapterFeed = false
protected open val useOldChapterFeed = false
private val chapterFeedRegex = """clwd\.run\(["'](.*?)["']\)""".toRegex()
private val scriptSelector = "#clwd > script"
open fun getChapterFeedUrl(doc: Document): String {
if (useNewChapterFeed) return newChapterFeedUrl(doc)
if (useOldChapterFeed) return oldChapterFeedUrl(doc)
val script = doc.selectFirst(scriptSelector)
?: return runCatching { oldChapterFeedUrl(doc) }
.getOrElse { newChapterFeedUrl(doc) }
val feed = chapterFeedRegex
.find(script.html())
?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed")
return apiUrl(chapterCategory)
.addPathSegments(feed)
.addQueryParameter("max-results", maxChapterResults.toString())
.build().toString()
}
private val oldChapterFeedRegex = """([^']+)\?""".toRegex()
private val oldScriptSelector = "#myUL > script"
open fun oldChapterFeedUrl(doc: Document): String {
val script = doc.selectFirst(oldScriptSelector)!!.attr("src")
val feed = oldChapterFeedRegex
.find(script)
?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed")
return "$baseUrl$feed?alt=json&start-index=1&max-results=$maxChapterResults"
}
private val newChapterFeedRegex = """label\s*=\s*'([^']+)'""".toRegex()
private val newScriptSelector = "#latest > script"
private fun newChapterFeedUrl(doc: Document): String {
var chapterRegex = chapterFeedRegex
var script = doc.selectFirst(scriptSelector)
if (script == null) {
script = doc.selectFirst(newScriptSelector)!!
chapterRegex = newChapterFeedRegex
}
val feed = chapterRegex
.find(script.html())
?.groupValues?.get(1)
?: throw Exception("Failed to find chapter feed")
val url = apiUrl(feed)
.addQueryParameter("start-index", "1")
.addQueryParameter("max-results", "999999")
.build()
return url.toString()
}
protected open val pageListSelector = "div.check-box div.separator"
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val images = document.select(pageListSelector)
return images.select("img[src]").mapIndexed { i, img ->
Page(i, "", img.attr("abs:src"))
}
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
protected open val mangaCategory: String = "Series"
open fun apiUrl(feed: String = mangaCategory): HttpUrl.Builder {
return "$baseUrl/feeds/posts/default/-/".toHttpUrl().newBuilder()
.addPathSegment(feed)
.addQueryParameter("alt", "json")
}
protected open val hasFilters = false
protected open val hasStatusFilter = true
protected open val hasTypeFilter = true
protected open val hasLanguageFilter = true
protected open val hasGenreFilter = true
override fun getFilterList(): FilterList {
val filterList = mutableListOf<Filter<*>>()
if (!hasFilters) {
return FilterList(emptyList())
}
filterList.add(Filter.Header(intl.filterWarning))
filterList.add(Filter.Separator())
if (hasStatusFilter) filterList.add(StatusList(intl.statusFilterTitle, getStatusList()))
if (hasTypeFilter) filterList.add(TypeList(intl.typeFilterTitle, getTypeList()))
if (hasLanguageFilter) filterList.add(LanguageList(intl.languageFilterTitle, getLanguageList()))
if (hasGenreFilter) filterList.add(GenreList(intl.genreFilterTitle, getGenreList()))
return FilterList(filterList)
}
protected open fun getStatusList(): List<Status> = listOf(
Status(intl.all, ""),
Status(intl.statusOngoing, "Ongoing"),
Status(intl.statusCompleted, "Completed"),
Status(intl.statusDropped, "Dropped"),
Status(intl.statusUpcoming, "Upcoming"),
Status(intl.statusHiatus, "Hiatus"),
Status(intl.statusCancelled, "Cancelled"),
)
protected open fun getTypeList(): List<Type> = listOf(
Type(intl.all, ""),
Type(intl.typeManga, "Manga"),
Type(intl.typeManhua, "Manhua"),
Type(intl.typeManhwa, "Manhwa"),
Type(intl.typeNovel, "Novel"),
Type(intl.typeWebNovelJP, "Web Novel (JP)"),
Type(intl.typeWebNovelKR, "Web Novel (KR)"),
Type(intl.typeWebNovelCN, "Web Novel (CN)"),
Type(intl.typeDoujinshi, "Doujinshi"),
)
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"),
)
protected open fun getLanguageList(): List<Language> = listOf(
Language(intl.all, ""),
Language("Indonesian", "Indonesian"),
Language("English", "English"),
)
protected open val statusOnGoingList = listOf(
"ongoing",
"en curso",
"en emisión",
"activo",
"ativo",
"lançando",
"مستمر",
)
protected open val statusCompletedList = listOf(
"completed",
"completo",
"finalizado",
)
protected open val statusHiatusList = listOf(
"hiatus",
"pausado",
)
protected open val statusCancelledList = listOf(
"cancelled",
"dropped",
"dropado",
"abandonado",
"cancelado",
)
protected open fun parseStatus(element: String): Int = when (element.lowercase().trim()) {
in statusOnGoingList -> SManga.ONGOING
in statusCompletedList -> SManga.COMPLETED
in statusHiatusList -> SManga.ON_HIATUS
in statusCancelledList -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
private fun String.replaceLast(oldValue: String, newValue: String): String {
val lastIndexOf = lastIndexOf(oldValue)
return if (lastIndexOf == -1) {
this
} else {
substring(0, lastIndexOf) + newValue + substring(lastIndexOf + oldValue.length)
}
}
companion object {
private const val maxMangaResults = 20
private const val maxChapterResults = 999999
}
}