[SoManga] close #1091

[AsuraComic] fix close #1088
[HeanCms] add SearchWithFilters
[fuzzydoodle] add ContentTypes, SearchWithFilters
[DoujinDesu.tv] add SearchWithFilters, ContentTypes
master
devi 2 years ago
parent 1c0df02c56
commit 52329e658f

@ -158,16 +158,16 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
return manga.copy(
description = doc.selectFirst("span.font-medium.text-sm")?.text().orEmpty(),
tags = tags,
author = doc.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.text(),
author = doc.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.text().orEmpty(),
chapters = doc.select("div.scrollbar-thumb-themecolor > div.group").mapChapters(reversed = true) { i, div ->
val a = div.selectLastOrThrow("a")
val urlRelative = "/series/" + a.attrAsRelativeUrl("href")
val url = urlRelative.toAbsoluteUrl(domain)
val date = div.selectFirst("h3:eq(1)")!!.ownText()
val date = div.selectLast("h3")?.text().orEmpty()
val cleanDate = date.replace(regexDate, "$1")
MangaChapter(
id = generateUid(url),
name = div.selectFirstOrThrow("h3:eq(0)").text(),
name = div.selectFirst("h3")?.text() ?: "Chapter : ${i + 1f}",
number = i + 1f,
volume = 0,
url = url,

@ -31,6 +31,24 @@ internal abstract class FuzzyDoodleParser(
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.COMICS,
),
)
@JvmField
protected val ongoing = scatterSetOf(
"en cours",
@ -67,54 +85,53 @@ internal abstract class FuzzyDoodleParser(
protected open val pausedValue = "haitus"
protected open val abandonedValue = "dropped"
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
)
protected open val mangaValue = "manga"
protected open val manhwaValue = "manhwa"
protected open val manhuaValue = "manhua"
protected open val comicsValue = "bande-dessinee"
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/manga?page=")
append(page)
when {
!filter.query.isNullOrEmpty() -> {
append("&title=")
append(filter.query.urlEncoded())
}
append(page.toString())
append("&type=")
filter.types.oneOrThrowIfMany().let {
append(
when (it) {
ContentType.MANGA -> mangaValue
ContentType.MANHWA -> manhwaValue
ContentType.MANHUA -> manhuaValue
ContentType.COMICS -> comicsValue
else -> ""
},
)
}
else -> {
append("&type=")
append("&status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> ongoingValue
MangaState.FINISHED -> finishedValue
MangaState.PAUSED -> pausedValue
MangaState.ABANDONED -> abandonedValue
else -> ""
},
)
}
filter.query?.let {
append("&title=")
append(filter.query.urlEncoded())
}
append("&status=")
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> ongoingValue
MangaState.FINISHED -> finishedValue
MangaState.PAUSED -> pausedValue
MangaState.ABANDONED -> abandonedValue
else -> ""
},
)
}
filter.tags.forEach {
append("&")
append("genre[]".urlEncoded())
append("=")
append(it.key)
}
}
filter.tags.forEach {
append("&")
append("genre[]".urlEncoded())
append("=")
append(it.key)
}
}
@ -270,17 +287,14 @@ internal abstract class FuzzyDoodleParser(
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
d.endsWith(" ago") ||
d.endsWith("مضت") || d.startsWith("منذ") ||
d.startsWith("il y a") -> parseRelativeDate(date)
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) {
it.replace(Regex("""\D"""), "")
} else {
it
}
}.let { dateFormat.tryParse(it.joinToString(" ")) }
WordSet(" ago", "مضت").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("il y a", "منذ").startsWith(d) -> {
parseRelativeDate(d)
}
else -> dateFormat.tryParse(date)
}

@ -16,7 +16,16 @@ internal class HentaiSlayer(context: MangaLoaderContext) :
override val finishedValue = "مكتمل"
override val abandonedValue = "متوقف"
override val mangaValue = "مانجا"
override val manhuaValue = "مانهوا"
override val comicsValue = "كوميكس"
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHUA,
ContentType.COMICS,
),
)
}

@ -2,9 +2,21 @@ package org.koitharu.kotatsu.parsers.site.fuzzydoodle.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.fuzzydoodle.FuzzyDoodleParser
import java.util.EnumSet
@MangaSourceParser("SCYLLACOMICS", "ScyllaComics", "en")
internal class ScyllaComics(context: MangaLoaderContext) :
FuzzyDoodleParser(context, MangaParserSource.SCYLLACOMICS, "scyllacomics.xyz")
FuzzyDoodleParser(context, MangaParserSource.SCYLLACOMICS, "scyllacomics.xyz") {
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
)
}

@ -116,7 +116,7 @@ internal abstract class GalleryAdultsParser(
publicUrl = href.toAbsoluteUrl(domain),
rating = RATING_UNKNOWN,
isNsfw = isNsfwSource,
coverUrl = div.selectFirstOrThrow(selectGalleryImg).src().orEmpty(),
coverUrl = div.selectFirst(selectGalleryImg)?.src().orEmpty(),
tags = emptySet(),
state = null,
author = null,

@ -19,6 +19,7 @@ internal class AsmHentai(context: MangaLoaderContext) :
override val selectGalleryLink = ".image a"
override val selectGalleryImg = ".image img"
override val pathTagUrl = "/tags/?page="
override val selectTags = ".tags_page ul.tags"
override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag"
override val idImg = "fimg"

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
@ -8,6 +9,7 @@ import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@Broken
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
internal class DoujinDesuUk(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaParserSource.DOUJINDESUUK, "doujindesu.uk", 25) {

@ -77,7 +77,7 @@ internal abstract class GattsuParser(
id = generateUid(href),
url = href,
publicUrl = href,
title = li.selectLastOrThrow(".thumb-titulo, .video-titulo").text(),
title = li.selectLast(".thumb-titulo, .video-titulo")?.text().orEmpty(),
coverUrl = li.selectFirst("img")?.src().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,

@ -14,6 +14,13 @@ internal abstract class GuyaParser(
domain: String,
) : SinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
override val filterCapabilities: MangaListFilterCapabilities
@ -23,13 +30,6 @@ internal abstract class GuyaParser(
override suspend fun getFilterOptions() = MangaListFilterOptions()
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val configKeyDomain = ConfigKey.Domain(domain)
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")

@ -39,16 +39,11 @@ internal abstract class HeanCms(
SortOrder.POPULARITY_ASC,
)
protected open val pathManga = "series"
protected open val apiPath
get() = getDomain("api")
protected open val paramsUpdated = "latest"
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
@ -56,52 +51,59 @@ internal abstract class HeanCms(
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
)
protected open val pathManga = "series"
protected open val apiPath
get() = getDomain("api")
protected open val paramsUpdated = "latest"
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(apiPath)
append("/query?query_string=&series_type=Comic&perPage=$pageSize")
when {
!filter.query.isNullOrEmpty() -> {
append(filter.query.urlEncoded())
}
else -> {
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "Ongoing"
MangaState.FINISHED -> "Completed"
MangaState.ABANDONED -> "Dropped"
MangaState.PAUSED -> "Hiatus"
else -> ""
},
)
}
append("&orderBy=")
when (order) {
SortOrder.POPULARITY -> append("total_views&order=desc")
SortOrder.POPULARITY_ASC -> append("total_views&order=asc")
SortOrder.UPDATED -> append("$paramsUpdated&order=desc")
SortOrder.UPDATED_ASC -> append("$paramsUpdated&order=asc")
SortOrder.NEWEST -> append("created_at&order=desc")
SortOrder.NEWEST_ASC -> append("created_at&order=asc")
SortOrder.ALPHABETICAL -> append("title&order=asc")
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
else -> append("latest&order=desc")
}
append("&tags_ids=")
append("[".urlEncoded())
append(filter.tags.joinToString(",") { it.key })
append("]".urlEncoded())
}
append("/query?query_string=")
filter.query?.let {
append(filter.query.urlEncoded())
}
append("&series_type=Comic&perPage=$pageSize")
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "Ongoing"
MangaState.FINISHED -> "Completed"
MangaState.ABANDONED -> "Dropped"
MangaState.PAUSED -> "Hiatus"
else -> ""
},
)
}
append("&orderBy=")
when (order) {
SortOrder.POPULARITY -> append("total_views&order=desc")
SortOrder.POPULARITY_ASC -> append("total_views&order=asc")
SortOrder.UPDATED -> append("$paramsUpdated&order=desc")
SortOrder.UPDATED_ASC -> append("$paramsUpdated&order=asc")
SortOrder.NEWEST -> append("created_at&order=desc")
SortOrder.NEWEST_ASC -> append("created_at&order=asc")
SortOrder.ALPHABETICAL -> append("title&order=asc")
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
else -> append("latest&order=desc")
}
append("&tags_ids=")
append("[".urlEncoded())
append(filter.tags.joinToString(",") { it.key })
append("]".urlEncoded())
append("&page=")
append(page.toString())
}
return parseMangaList(webClient.httpGet(url).parseJson())
}

@ -65,8 +65,8 @@ internal abstract class HeanCmsAlt(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(),
title = div.selectFirstOrThrow(selectMangaTitle).text().orEmpty(),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = div.selectFirst(selectMangaTitle)?.text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
tags = emptySet(),
@ -90,14 +90,14 @@ internal abstract class HeanCmsAlt(
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return manga.copy(
altTitle = doc.selectFirst(selectAlt)?.text().orEmpty(),
description = doc.selectFirstOrThrow(selectDesc).html(),
description = doc.selectFirst(selectDesc)?.html(),
chapters = doc.select(selectChapter)
.mapChapters(reversed = true) { i, a ->
val dateText = a.selectFirstOrThrow(selectChapterDate).text()
val dateText = a.selectFirst(selectChapterDate)?.text()
val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain)
MangaChapter(
id = generateUid(url),
name = a.selectFirstOrThrow(selectChapterTitle).text(),
name = a.selectFirst(selectChapterTitle)?.text() ?: "Chapter : ${i + 1f}",
number = i + 1f,
volume = 0,
url = url,
@ -137,8 +137,6 @@ internal abstract class HeanCmsAlt(
}
}
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()

@ -24,6 +24,10 @@ internal abstract class HotComicsParser(
override val configKeyDomain = ConfigKey.Domain(domain)
override fun getRequestHeaders(): Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
@ -31,10 +35,6 @@ internal abstract class HotComicsParser(
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
protected open val mangasUrl = "/genres"
protected open val onePage = false
protected open val isSearchSupported: Boolean = true
final override val filterCapabilities: MangaListFilterCapabilities
@ -46,9 +46,9 @@ internal abstract class HotComicsParser(
availableTags = fetchAvailableTags(),
)
override fun getRequestHeaders(): Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()
protected open val mangasUrl = "/genres"
protected open val onePage = false
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
if (onePage && page > 1) {
@ -161,7 +161,6 @@ internal abstract class HotComicsParser(
)
}
protected open val selectPages = "#viewer-img img"
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {

@ -29,11 +29,17 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.HENTAI,
),
)
override fun getRequestHeaders(): Headers = Headers.Builder()
@ -47,40 +53,58 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
addPathSegment("page")
addPathSegment("$page/")
when {
!filter.query.isNullOrEmpty() -> {
addQueryParameter("title", filter.query)
}
else -> {
addQueryParameter("title", "")
addQueryParameter(
"order",
when (order) {
SortOrder.UPDATED -> "update"
SortOrder.POPULARITY -> "popular"
SortOrder.ALPHABETICAL -> "title"
SortOrder.NEWEST -> "latest"
else -> "latest"
},
)
addQueryParameter(
"title",
filter.query?.let {
filter.query
},
)
addQueryParameter(
"order",
when (order) {
SortOrder.UPDATED -> "update"
SortOrder.POPULARITY -> "popular"
SortOrder.ALPHABETICAL -> "title"
SortOrder.NEWEST -> "latest"
else -> "latest"
},
)
filter.tags.forEach {
addEncodedQueryParameter("genre[]".urlEncoded(), it.key.urlEncoded())
}
filter.states.oneOrThrowIfMany()?.let {
addEncodedQueryParameter(
"statusx",
when (it) {
MangaState.ONGOING -> "Publishing"
MangaState.FINISHED -> "Finished"
else -> ""
},
)
}
}
filter.tags.forEach {
addEncodedQueryParameter("genre[]".urlEncoded(), it.key.urlEncoded())
}
filter.states.oneOrThrowIfMany()?.let {
addEncodedQueryParameter(
"statusx",
when (it) {
MangaState.ONGOING -> "Publishing"
MangaState.FINISHED -> "Finished"
else -> ""
},
)
}
filter.types.oneOrThrowIfMany()?.let {
addQueryParameter(
"typex",
when (it) {
ContentType.MANGA -> "Manga"
ContentType.MANHWA -> "Manhwa"
ContentType.HENTAI -> "Doujinshi"
else -> ""
},
)
}
// Author
// addQueryParameter("author",
// filter.author?.let {
// filter.author
// }
// )
}.build()
return webClient.httpGet(url).parseHtml()

@ -12,10 +12,16 @@ import java.util.*
internal class HentaiCrot(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
)
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
@ -26,11 +32,6 @@ internal class HentaiCrot(context: MangaLoaderContext) :
availableTags = fetchAvailableTags(),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")

@ -12,10 +12,16 @@ import java.util.*
internal class PixHentai(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
override val configKeyDomain = ConfigKey.Domain("pixhentai.com")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
)
override val configKeyDomain = ConfigKey.Domain("pixhentai.com")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
@ -26,11 +32,6 @@ internal class PixHentai(context: MangaLoaderContext) :
availableTags = fetchAvailableTags(),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")

@ -0,0 +1,12 @@
package org.koitharu.kotatsu.parsers.site.mangareader.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("SOMANGA", "SoManga", "id")
internal class SoManga(context: MangaLoaderContext) :
MangaReaderParser(context, MangaParserSource.SOMANGA, "so-manga.com", pageSize = 5, searchPageSize = 25) {
override val datePattern = "MMM d, yyyy"
}
Loading…
Cancel
Save