Doujindesu error 404 HotFix (#11128)
* Sometimes i found another problem - Fixed Author, Group and Series Filters only 1 active (many intentionally or unintentionally enter input in several Filters which makes the results Null) - Fixed all HTML Tags such as &bnsp;, > and < and so on in the description - Fixed descriptions for Manhwa that were cut off due to different logic * Fix DoujinDesu `404` HOTFIX * Remove unused pattern * Remove Log * Revert Description Reverted to May Version Description and slightly make better
This commit is contained in:
parent
d1bed69ada
commit
7a61751a50
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'DoujinDesu'
|
extName = 'DoujinDesu'
|
||||||
extClass = '.DoujinDesu'
|
extClass = '.DoujinDesu'
|
||||||
extVersionCode = 10
|
extVersionCode = 11
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,9 @@ import keiyoushi.utils.firstInstanceOrNull
|
|||||||
import keiyoushi.utils.getPreferencesLazy
|
import keiyoushi.utils.getPreferencesLazy
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -249,9 +249,19 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
Genre("Yuri"),
|
Genre("Yuri"),
|
||||||
)
|
)
|
||||||
|
|
||||||
private class AuthorFilter : Filter.Text("Author")
|
private class AuthorGroupSeriesOption(val display: String, val key: String) {
|
||||||
private class GroupFilter : Filter.Text("Group")
|
override fun toString(): String = display
|
||||||
private class SeriesFilter : Filter.Text("Series")
|
}
|
||||||
|
|
||||||
|
private val authorGroupSeriesOptions = arrayOf(
|
||||||
|
AuthorGroupSeriesOption("None", ""),
|
||||||
|
AuthorGroupSeriesOption("Author", "author"),
|
||||||
|
AuthorGroupSeriesOption("Group", "group"),
|
||||||
|
AuthorGroupSeriesOption("Series", "series"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private class AuthorGroupSeriesFilter(options: Array<AuthorGroupSeriesOption>) : Filter.Select<AuthorGroupSeriesOption>("Filter by Author/Group/Series", options, 0)
|
||||||
|
private class AuthorGroupSeriesValueFilter : Filter.Text("Nama Author/Group/Series")
|
||||||
private class CharacterFilter : Filter.Text("Karakter")
|
private class CharacterFilter : Filter.Text("Karakter")
|
||||||
private class CategoryNames(categories: Array<Category>) : Filter.Select<Category>("Kategori", categories, 0)
|
private class CategoryNames(categories: Array<Category>) : Filter.Select<Category>("Kategori", categories, 0)
|
||||||
private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Urutkan", orders, 0)
|
private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Urutkan", orders, 0)
|
||||||
@ -295,7 +305,8 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
basicInformationFromElement(element)
|
basicInformationFromElement(element)
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/manga/page/$page/?title=&author=&character=&statusx=&typex=&order=popular", headers)
|
// Original url $baseUrl/manga/page/$page/?title=&author=&character=&statusx=&typex=&order=popular
|
||||||
|
return GET("$baseUrl/manhwa/page/$page/", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest
|
// Latest
|
||||||
@ -304,7 +315,8 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
basicInformationFromElement(element)
|
basicInformationFromElement(element)
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/manga/page/$page/?title=&author=&character=&statusx=&typex=&order=update", headers)
|
// Original url $baseUrl/manga/page/$page/?title=&author=&character=&statusx=&typex=&order=update
|
||||||
|
return GET("$baseUrl/doujin/page/$page/", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element Selectors
|
// Element Selectors
|
||||||
@ -321,9 +333,11 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
// Anything else filter handling
|
// Anything else filter handling
|
||||||
val url = "$baseUrl/manga/page/$page/".toHttpUrl().newBuilder()
|
val baseUrlWithPage = if (page == 1) "$baseUrl/" else "$baseUrl/page/$page/"
|
||||||
url.addQueryParameter("title", query.ifBlank { "" })
|
|
||||||
|
|
||||||
|
val finalUrl = if (query.isNotBlank()) "$baseUrlWithPage?s=${query.replace(" ", "+")}" else baseUrlWithPage
|
||||||
|
|
||||||
|
/* Will be used later if DoujinDesu aleardy fix their problem
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is CategoryNames -> {
|
is CategoryNames -> {
|
||||||
@ -351,88 +365,61 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
val author = filters.firstInstanceOrNull<AuthorFilter>()?.state?.trim()
|
val agsFilter = filters.firstInstanceOrNull<AuthorGroupSeriesFilter>()
|
||||||
val group = filters.firstInstanceOrNull<GroupFilter>()?.state?.trim()
|
val agsValueFilter = filters.firstInstanceOrNull<AuthorGroupSeriesValueFilter>()
|
||||||
val series = filters.firstInstanceOrNull<SeriesFilter>()?.state?.trim()
|
val selectedOption = agsFilter?.values?.getOrNull(agsFilter.state)
|
||||||
|
val filterValue = agsValueFilter?.state?.trim() ?: ""
|
||||||
|
|
||||||
// Author filter handling
|
// Author/Group/Series filter handling
|
||||||
if (query.isBlank()) {
|
if (query.isBlank() && selectedOption != null && selectedOption.key.isNotBlank()) {
|
||||||
if (!author.isNullOrBlank()) {
|
val typePath = selectedOption.key
|
||||||
val slug = author.toMultiSlug()
|
val request = if (filterValue.isBlank()) {
|
||||||
if (slug.isNotBlank()) {
|
val url = if (page == 1) {
|
||||||
val authorUrl = if (page == 1) {
|
"$baseUrl/$typePath/"
|
||||||
"$baseUrl/author/$slug/"
|
} else {
|
||||||
} else {
|
"$baseUrl/$typePath/page/$page/"
|
||||||
"$baseUrl/author/$slug/page/$page/"
|
|
||||||
}
|
|
||||||
return GET(authorUrl, headers)
|
|
||||||
}
|
}
|
||||||
}
|
GET(url, headers)
|
||||||
|
} else {
|
||||||
// Group filter handling
|
val url = if (page == 1) {
|
||||||
if (!group.isNullOrBlank()) {
|
"$baseUrl/$typePath/$filterValue/"
|
||||||
val slug = group.toMultiSlug()
|
} else {
|
||||||
if (slug.isNotBlank()) {
|
"$baseUrl/$typePath/$filterValue/page/$page/"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
GET(url, headers)
|
||||||
}
|
}
|
||||||
|
return request
|
||||||
}
|
}
|
||||||
return GET(url.build(), headers)
|
return GET(finalUrl, 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 =
|
override fun searchMangaFromElement(element: Element): SManga =
|
||||||
basicInformationFromElement(element)
|
basicInformationFromElement(element)
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
Filter.Header("NB: Filter bisa digabungkan dengan memakai pencarian teks selain Author, Group dan Series!"),
|
Filter.Header("NB: Fitur Emergency, jadi maklumi aja jika ada bug!"),
|
||||||
Filter.Separator(),
|
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!"),
|
Filter.Header("NB: Tidak bisa digabungkan dengan memakai pencarian teks dan filter lainnya, serta harus memasukkan nama Author, Group dan Series secara lengkap!"),
|
||||||
AuthorFilter(),
|
AuthorGroupSeriesFilter(authorGroupSeriesOptions),
|
||||||
GroupFilter(),
|
AuthorGroupSeriesValueFilter(),
|
||||||
SeriesFilter(),
|
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
|
/* Will be used later if DoujinDesu aleardy fix their problem
|
||||||
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"),
|
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(),
|
CharacterFilter(),
|
||||||
StatusList(statusList),
|
StatusList(statusList),
|
||||||
CategoryNames(categoryNames),
|
CategoryNames(categoryNames),
|
||||||
OrderBy(orderBy),
|
OrderBy(orderBy),
|
||||||
GenreList(genreList()),
|
GenreList(genreList()),
|
||||||
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
// Detail Parse
|
// Detail Parse
|
||||||
|
|
||||||
private val chapterListRegex = Regex("""\d+[-–]?\d*\..+<br>""", RegexOption.IGNORE_CASE)
|
private val chapterListRegex = Regex("""\d+[-–]?\d*\..+<br>""", RegexOption.IGNORE_CASE)
|
||||||
private val htmlTagRegex = Regex("<[^>]*>")
|
private val htmlTagRegex = Regex("<[^>]*>")
|
||||||
|
private val chapterPrefixRegex = Regex("""^\d+(-\d+)?\.\s*.*""")
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val infoElement = document.selectFirst("section.metadata")!!
|
val infoElement = document.selectFirst("section.metadata")!!
|
||||||
@ -483,22 +470,26 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
val paragraphs = element.select("p")
|
val paragraphs = element.select("p")
|
||||||
val firstText = paragraphs.firstOrNull()?.text()?.trim()?.lowercase()
|
val firstText = paragraphs.firstOrNull()?.text()?.trim()?.lowercase()
|
||||||
|
|
||||||
// CASE 1: Gabungan chapter dalam satu paragraf
|
// Fungsi untuk mendekode semua entitas HTML
|
||||||
|
val decodeHtmlEntities = { text: String ->
|
||||||
|
Jsoup.parse(text).text().replace('\u00A0', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
// CASE 1: Gabungan chapter dalam satu paragraf (Manga Style)
|
||||||
val mergedChapterElement = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").firstOrNull {
|
val mergedChapterElement = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").firstOrNull {
|
||||||
chapterListRegex.containsMatchIn(it.html())
|
chapterListRegex.containsMatchIn(it.html())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mergedChapterElement != null) {
|
if (mergedChapterElement != null) {
|
||||||
val chapterList = mergedChapterElement.html()
|
val chapterList = mergedChapterElement.html()
|
||||||
.split("<br>")
|
.split("<br>")
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.map { it.replace(htmlTagRegex, "").trim() }
|
.map { decodeHtmlEntities(it.replace(htmlTagRegex, "").trim()) }
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ")
|
return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 2: Dua paragraf: p[0] = "Sinopsis:", p[1] = daftar chapter
|
// CASE 2: Dua paragraf: p[0] = "Sinopsis:", p[1] = daftar chapter (Manga Style)
|
||||||
if (
|
if (
|
||||||
firstText == "sinopsis:" &&
|
firstText == "sinopsis:" &&
|
||||||
paragraphs.size > 1 &&
|
paragraphs.size > 1 &&
|
||||||
@ -506,39 +497,45 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
) {
|
) {
|
||||||
val chapterList = paragraphs[1].html()
|
val chapterList = paragraphs[1].html()
|
||||||
.split("<br>")
|
.split("<br>")
|
||||||
.map { it.replace(htmlTagRegex, "").trim() }
|
.map { decodeHtmlEntities(it.replace(htmlTagRegex, "").trim()) }
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ")
|
return@let "Daftar Chapter:\n" + chapterList.joinToString(" | ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 3: Sinopsis biasa pakai <strong>Sinopsis:</strong> di p awal
|
// CASE 3 + 5 Hybrid: Tangani Sinopsis dengan <strong> + <br> + <p> campuran (Manhwa Style + Terkompresi)
|
||||||
val sinopsisPara = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))")
|
val sinopsisPara = element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))")
|
||||||
if (sinopsisPara.isNotEmpty()) {
|
if (sinopsisPara.isNotEmpty()) {
|
||||||
val sinopsisStart = sinopsisPara.first()!!
|
val sinopsisStart = sinopsisPara.first()!!
|
||||||
val htmlSplit = sinopsisStart.html().split("<br>")
|
val htmlSplit = sinopsisStart.html().split("<br>")
|
||||||
|
|
||||||
val startText = htmlSplit.getOrNull(1)?.replace(htmlTagRegex, "")?.trim().orEmpty()
|
val startText = htmlSplit
|
||||||
|
.drop(1)
|
||||||
|
.map { decodeHtmlEntities(it.replace(htmlTagRegex, "").trim()) }
|
||||||
|
.filter { it.isNotEmpty() && !it.lowercase().startsWith("download") && !it.lowercase().startsWith("volume") && !it.lowercase().startsWith("chapter") }
|
||||||
|
|
||||||
val sinopsisTexts = buildList {
|
val sinopsisTexts = buildList {
|
||||||
if (startText.isNotEmpty()) add(startText)
|
addAll(startText)
|
||||||
|
|
||||||
val allP = element.select("p")
|
val allP = element.select("p")
|
||||||
val startIndex = allP.indexOf(sinopsisStart)
|
val startIndex = allP.indexOf(sinopsisStart)
|
||||||
|
|
||||||
for (i in startIndex + 1 until allP.size) {
|
for (i in startIndex + 1 until allP.size) {
|
||||||
val content = allP[i].text().trim()
|
val htmlSplitNext = allP[i].html().split("<br>")
|
||||||
if (!content.lowercase().startsWith("download")) {
|
val contents = htmlSplitNext
|
||||||
add(content)
|
.map { decodeHtmlEntities(it.replace(htmlTagRegex, "").trim()) }
|
||||||
} else {
|
.filter { it.isNotEmpty() && !it.lowercase().startsWith("download") && !it.lowercase().startsWith("volume") && !it.lowercase().startsWith("chapter") }
|
||||||
break
|
addAll(contents)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@let "Sinopsis:\n" + sinopsisTexts.joinToString("\n\n")
|
if (sinopsisTexts.isNotEmpty()) {
|
||||||
|
val isChapterList = sinopsisTexts.first().matches(chapterPrefixRegex)
|
||||||
|
val prefix = if (isChapterList) "Daftar Chapter:" else "Sinopsis:"
|
||||||
|
return@let "$prefix\n" + sinopsisTexts.joinToString("\n\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 4: Satu paragraf saja dengan <strong> dan <br>
|
// CASE 4: Satu paragraf saja dengan <strong> dan <br> (Manhwa Style)
|
||||||
if (
|
if (
|
||||||
paragraphs.size == 1 &&
|
paragraphs.size == 1 &&
|
||||||
element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").isNotEmpty()
|
element.select("p:has(strong:matchesOwn(^\\s*Sinopsis\\s*:))").isNotEmpty()
|
||||||
@ -546,16 +543,20 @@ class DoujinDesu : ParsedHttpSource(), ConfigurableSource {
|
|||||||
val para = paragraphs[0]
|
val para = paragraphs[0]
|
||||||
val htmlSplit = para.html().split("<br>")
|
val htmlSplit = para.html().split("<br>")
|
||||||
|
|
||||||
val content = htmlSplit.getOrNull(1)?.replace(htmlTagRegex, "")?.trim().orEmpty()
|
val content = htmlSplit.getOrNull(1)?.let {
|
||||||
|
decodeHtmlEntities(it.replace(htmlTagRegex, "").trim())
|
||||||
|
}.orEmpty()
|
||||||
|
|
||||||
return@let "Sinopsis:\n$content"
|
if (content.isNotBlank()) {
|
||||||
|
return@let "Sinopsis:\n$content"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 5: Fallback
|
// CASE 6: Fallback
|
||||||
if (firstText == "sinopsis:") {
|
if (firstText == "sinopsis:") {
|
||||||
val sinopsisLines = paragraphs.drop(1)
|
val sinopsisLines = paragraphs.drop(1)
|
||||||
.map { it.text().trim() }
|
.map { decodeHtmlEntities(it.text().trim()) }
|
||||||
.filter { !it.lowercase().startsWith("download") }
|
.filter { it.isNotEmpty() && !it.lowercase().startsWith("download") && !it.lowercase().startsWith("volume") && !it.lowercase().startsWith("chapter") }
|
||||||
|
|
||||||
return@let "Sinopsis:\n" + sinopsisLines.joinToString("\n\n")
|
return@let "Sinopsis:\n" + sinopsisLines.joinToString("\n\n")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user