diff --git a/src/id/doujindesu/build.gradle b/src/id/doujindesu/build.gradle index 9861e6443..f89d00c2c 100644 --- a/src/id/doujindesu/build.gradle +++ b/src/id/doujindesu/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'DoujinDesu' extClass = '.DoujinDesu' - extVersionCode = 9 + extVersionCode = 10 isNsfw = true } diff --git a/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt b/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt index f6a31b364..12419419e 100644 --- a/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt +++ b/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt @@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import keiyoushi.utils.firstInstanceOrNull import keiyoushi.utils.getPreferencesLazy import okhttp3.FormBody import okhttp3.Headers @@ -249,6 +250,8 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { ) private class AuthorFilter : Filter.Text("Author") + private class GroupFilter : Filter.Text("Group") + private class SeriesFilter : Filter.Text("Series") private class CharacterFilter : Filter.Text("Karakter") private class CategoryNames(categories: Array) : Filter.Select("Kategori", categories, 0) private class OrderBy(orders: Array) : Filter.Select("Urutkan", orders, 0) @@ -317,8 +320,10 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { // Search & FIlter override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + // Anything else filter handling val url = "$baseUrl/manga/page/$page/".toHttpUrl().newBuilder() - .addQueryParameter("title", query) + url.addQueryParameter("title", query.ifBlank { "" }) + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> when (filter) { is CategoryNames -> { @@ -329,19 +334,14 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { val order = filter.values[filter.state] url.addQueryParameter("order", order.key) } - is AuthorFilter -> { - url.addQueryParameter("author", filter.state) - } is CharacterFilter -> { url.addQueryParameter("character", filter.state) } is GenreList -> { filter.state .filter { it.state } - .let { list -> - if (list.isNotEmpty()) { - list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } - } + .forEach { genre -> + url.addQueryParameter("genre[]", genre.id) } } is StatusList -> { @@ -352,16 +352,76 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { } } + val author = filters.firstInstanceOrNull()?.state?.trim() + val group = filters.firstInstanceOrNull()?.state?.trim() + val series = filters.firstInstanceOrNull()?.state?.trim() + + // Author filter handling + if (query.isBlank()) { + if (!author.isNullOrBlank()) { + val slug = author.toMultiSlug() + if (slug.isNotBlank()) { + val authorUrl = if (page == 1) { + "$baseUrl/author/$slug/" + } else { + "$baseUrl/author/$slug/page/$page/" + } + return GET(authorUrl, headers) + } + } + + // Group filter handling + if (!group.isNullOrBlank()) { + val slug = group.toMultiSlug() + if (slug.isNotBlank()) { + val groupUrl = if (page == 1) { + "$baseUrl/group/$slug/" + } else { + "$baseUrl/group/$slug/page/$page/" + } + return GET(groupUrl, headers) + } + } + + // Series filter handling + if (!series.isNullOrBlank()) { + val slug = series.toMultiSlug() + if (slug.isNotBlank()) { + val seriesUrl = if (page == 1) { + "$baseUrl/series/$slug/" + } else { + "$baseUrl/series/$slug/page/$page/" + } + return GET(seriesUrl, headers) + } + } + } return GET(url.build(), headers) } + private val nonAlphaNumSpaceDashRegex = Regex("[^a-z0-9\\s-]") + private val multiSpaceRegex = Regex("\\s+") + + private fun String.toMultiSlug(): String { + return this + .trim() + .lowercase() + .replace(nonAlphaNumSpaceDashRegex, "") + .replace(multiSpaceRegex, "-") + } + override fun searchMangaFromElement(element: Element): SManga = basicInformationFromElement(element) override fun getFilterList() = FilterList( - Filter.Header("NB: Filter bisa digabungkan dengan memakai pencarian teks!"), + Filter.Header("NB: Filter bisa digabungkan dengan memakai pencarian teks selain Author, Group dan Series!"), Filter.Separator(), + Filter.Header("NB: Gunakan ini untuk filter per Author, Group dan Series saja, tidak bisa digabungkan dengan memakai pencarian teks dan filter lainnya!"), AuthorFilter(), + GroupFilter(), + SeriesFilter(), + Filter.Separator(), + Filter.Header("NB: Untuk Character Filter akan mengambil hasil apapun jika diinput, misal 'alice', maka hasil akan memunculkan semua Karakter yang memiliki nama 'Alice', bisa digabungkan dengan filter lainnya"), CharacterFilter(), StatusList(statusList), CategoryNames(categoryNames), @@ -371,6 +431,9 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { // Detail Parse + private val chapterListRegex = Regex("""\d+[-–]?\d*\..+
""", RegexOption.IGNORE_CASE) + private val htmlTagRegex = Regex("<[^>]*>") + override fun mangaDetailsParse(document: Document): SManga { val infoElement = document.selectFirst("section.metadata")!! val authorName = if (infoElement.select("td:contains(Author) ~ td").isEmpty()) { @@ -414,13 +477,96 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource { Seri : $seriesParser """.trimIndent() } else { - val showDescription = infoElement.selectFirst("div.pb-2 > p:nth-child(1)")!!.text() - """ - $showDescription + val pb2Element = infoElement.selectFirst("div.pb-2") - Judul Alternatif : $alternativeTitle - Seri : $seriesParser - """.trimIndent() + val showDescription = pb2Element?.let { element -> + val paragraphs = element.select("p") + val firstText = paragraphs.firstOrNull()?.text()?.trim()?.lowercase() + + // CASE 1: Gabungan chapter dalam satu paragraf + val mergedChapterElement = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").firstOrNull { + chapterListRegex.containsMatchIn(it.html()) + } + + if (mergedChapterElement != null) { + val chapterList = mergedChapterElement.html() + .split("
") + .drop(1) + .map { it.replace(htmlTagRegex, "").trim() } + .filter { it.isNotEmpty() } + + return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ") + } + + // CASE 2: Dua paragraf: p[0] = "Sinopsis:", p[1] = daftar chapter + if ( + firstText == "sinopsis:" && + paragraphs.size > 1 && + chapterListRegex.containsMatchIn(paragraphs[1].html()) + ) { + val chapterList = paragraphs[1].html() + .split("
") + .map { it.replace(htmlTagRegex, "").trim() } + .filter { it.isNotEmpty() } + + return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ") + } + + // CASE 3: Sinopsis biasa pakai Sinopsis: di p awal + val sinopsisPara = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))") + if (sinopsisPara.isNotEmpty()) { + val sinopsisStart = sinopsisPara.first()!! + val htmlSplit = sinopsisStart.html().split("
") + + val startText = htmlSplit.getOrNull(1)?.replace(htmlTagRegex, "")?.trim().orEmpty() + + val sinopsisTexts = buildList { + if (startText.isNotEmpty()) add(startText) + + val allP = element.select("p") + val startIndex = allP.indexOf(sinopsisStart) + + for (i in startIndex + 1 until allP.size) { + val content = allP[i].text().trim() + if (!content.lowercase().startsWith("download")) { + add(content) + } else { + break + } + } + } + return@let "Sinopsis:\n" + sinopsisTexts.joinToString("\n\n") + } + + // CASE 4: Satu paragraf saja dengan dan
+ if ( + paragraphs.size == 1 && + element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").isNotEmpty() + ) { + val para = paragraphs[0] + val htmlSplit = para.html().split("
") + + val content = htmlSplit.getOrNull(1)?.replace(htmlTagRegex, "")?.trim().orEmpty() + + return@let "Sinopsis:\n$content" + } + + // CASE 5: Fallback + if (firstText == "sinopsis:") { + val sinopsisLines = paragraphs.drop(1) + .map { it.text().trim() } + .filter { !it.lowercase().startsWith("download") } + + return@let "Sinopsis:\n" + sinopsisLines.joinToString("\n\n") + } + return@let "" + } ?: "" + """ + |$showDescription + | + |Judul Alternatif : $alternativeTitle + |Seri : $seriesParser + """.trimMargin().replace(Regex(" +"), " ") } val genres = mutableListOf() infoElement.select("div.tags > a").forEach { element ->