[GalleryAdults] Language filter support #357

Koitharu 2 years ago
parent f1e8fbec5d
commit 3ed960254d
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -0,0 +1,12 @@
package org.koitharu.kotatsu.parsers
object ErrorMessages {
const val FILTER_MULTIPLE_STATES_NOT_SUPPORTED = "Multiple states are not supported by this source"
const val FILTER_MULTIPLE_GENRES_NOT_SUPPORTED = "Multiple genres are not supported by this source"
const val FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED =
"Filtering by both genres and locale is not supported by this source"
const val FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED =
"Filtering by both genres and states is not supported by this source"
const val SEARCH_NOT_SUPPORTED = "Search is not supported by this source"
}

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site.en package org.koitharu.kotatsu.parsers.site.en
import okhttp3.Headers import okhttp3.Headers
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -8,7 +9,6 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -60,7 +60,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
} }
} else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { } else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
throw IllegalArgumentException("Source does not support tag + states filters") throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED)
} else { } else {
when (filter.sortOrder) { when (filter.sortOrder) {
SortOrder.POPULARITY -> append("popular-comic") SortOrder.POPULARITY -> append("popular-comic")

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.es
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -10,7 +11,6 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.util.* import java.util.*
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI) @MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
@ -31,7 +31,7 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
append(domain) append(domain)
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -10,7 +11,6 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -46,7 +46,7 @@ internal class FuryoSociety(context: MangaLoaderContext) :
append(domain) append(domain)
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import okhttp3.Headers import okhttp3.Headers
import org.json.JSONArray import org.json.JSONArray
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -10,7 +11,6 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import java.lang.IllegalArgumentException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -49,7 +49,7 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> { override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {

@ -6,6 +6,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
@ -25,7 +26,6 @@ internal abstract class GalleryAdultsParser(
override val isMultipleTagsSupported = false override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> { override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
@ -37,18 +37,19 @@ internal abstract class GalleryAdultsParser(
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) { val tag = filter.tags.oneOrThrowIfMany()
filter.tags.oneOrThrowIfMany()?.let { val lang = filter.locale
if (it.key == "languageKey") { if (tag != null && lang != null) {
append("/language") throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
append(it.title) }
append("/?") if (tag != null) {
} else {
append("/tag/") append("/tag/")
append(it.key) append(tag.key)
append("/?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/?") append("/?")
}
}
} else { } else {
append("/?") append("/?")
} }
@ -102,39 +103,30 @@ internal abstract class GalleryAdultsParser(
}.awaitAll().flattenTo(ArraySet(360)) }.awaitAll().flattenTo(ArraySet(360))
} }
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("id"),
Locale.ITALIAN,
Locale("pt"),
Locale("tr"),
Locale("th"),
Locale("vi"),
)
protected open val pathTagUrl = "/tags/popular/?page=" protected open val pathTagUrl = "/tags/popular/?page="
protected open val selectTags = ".tags_page ul.tags li" protected open val selectTags = ".tags_page ul.tags li"
protected open val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/chinese",
"/spanish",
"/russian",
"/korean",
"/german",
"/indonesian",
"/italian",
"/portuguese",
"/turkish",
"/thai",
"/vietnamese",
) // The "/" is used to move them up in the tag list and therefore also in the url.
private suspend fun getTags(page: Int): Set<MangaTag> { private suspend fun getTags(page: Int): Set<MangaTag> {
val url = "https://$domain$pathTagUrl$page" val url = "https://$domain$pathTagUrl$page"
val root = webClient.httpGet(url).parseHtml().selectFirstOrThrow(selectTags) val root = webClient.httpGet(url).parseHtml().selectFirstOrThrow(selectTags)
val tagLanguage = ArrayList<MangaTag>(listLanguage.size) return root.parseTags()
for (language in listLanguage) {
tagLanguage.add(
MangaTag(
key = "languageKey",
title = language,
source = source,
),
)
}
return root.parseTags() + tagLanguage
} }
protected open fun Element.parseTags() = select("a").mapToSet { protected open fun Element.parseTags() = select("a").mapToSet {
@ -142,7 +134,7 @@ internal abstract class GalleryAdultsParser(
val name = it.html().substringBefore("<") val name = it.html().substringBefore("<")
MangaTag( MangaTag(
key = key, key = key,
title = name, title = name.toTitleCase(),
source = source, source = source,
) )
} }
@ -204,4 +196,8 @@ internal abstract class GalleryAdultsParser(
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
return doc.requireElementById(idImg).src() ?: doc.parseFailed("Image src not found") return doc.requireElementById(idImg).src() ?: doc.parseFailed("Image src not found")
} }
protected open fun Locale.toLanguagePath() = when (language) {
else -> getDisplayLanguage(Locale.ENGLISH).lowercase()
}
} }

@ -3,10 +3,13 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.removeSuffix import org.koitharu.kotatsu.parsers.util.removeSuffix
import java.util.*
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI) @MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
internal class AsmHentai(context: MangaLoaderContext) : internal class AsmHentai(context: MangaLoaderContext) :
@ -17,11 +20,12 @@ internal class AsmHentai(context: MangaLoaderContext) :
override val pathTagUrl = "/tags/?page=" override val pathTagUrl = "/tags/?page="
override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag" override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag"
override val idImg = "fimg" override val idImg = "fimg"
override val listLanguage = arrayOf(
"/english", override suspend fun getAvailableLocales(): Set<Locale> = setOf(
"/japanese", Locale.ENGLISH,
"/chinese", Locale.JAPANESE,
"/turkish", Locale.CHINESE,
Locale("tr"),
) )
override fun Element.parseTags() = select("a").mapToSet { override fun Element.parseTags() = select("a").mapToSet {

@ -6,10 +6,11 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI) @MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
internal class DoujinDesuUk(context: MangaLoaderContext) : internal class DoujinDesuUk(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.DOUJINDESUUK, "doujindesu.uk", 50) { GalleryAdultsParser(context, MangaSource.DOUJINDESUUK, "doujindesu.uk", 25) {
override val selectGallery = ".gallery" override val selectGallery = ".gallery"
override val selectGalleryLink = "a" override val selectGalleryLink = "a"
override val selectGalleryTitle = ".caption" override val selectGalleryTitle = ".caption"
@ -19,10 +20,11 @@ internal class DoujinDesuUk(context: MangaLoaderContext) :
override val selectAuthor = "div.tag-container:contains(Artists) a" override val selectAuthor = "div.tag-container:contains(Artists) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages) a" override val selectLanguageChapter = "div.tag-container:contains(Languages) a"
override val idImg = "image-container" override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english", override suspend fun getAvailableLocales(): Set<Locale> = setOf(
"/japanese", Locale.ENGLISH,
"/chinese", Locale.JAPANESE,
Locale.CHINESE,
) )
override fun parseMangaList(doc: Document): List<Manga> { override fun parseMangaList(doc: Document): List<Manga> {

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI) @MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI)
internal class Hentai3(context: MangaLoaderContext) : internal class Hentai3(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAI3, "3hentai.net") { GalleryAdultsParser(context, MangaSource.HENTAI3, "3hentai.net", pageSize = 30) {
override val selectGallery = ".doujin " override val selectGallery = ".doujin "
override val selectGalleryLink = "a" override val selectGalleryLink = "a"
@ -21,20 +22,21 @@ internal class Hentai3(context: MangaLoaderContext) :
override val selectLanguageChapter = "div.tag-container:contains(Languages) a" override val selectLanguageChapter = "div.tag-container:contains(Languages) a"
override val selectUrlChapter = "#main-cover a" override val selectUrlChapter = "#main-cover a"
override val idImg = ".js-main-img" override val idImg = ".js-main-img"
override val listLanguage = arrayOf(
"/english",
"/spanish",
"/french",
"/italian",
"/portuguese",
"/russian",
"/japanese",
)
override val isMultipleTagsSupported = true override val isMultipleTagsSupported = true
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale("es"),
Locale("ru"),
Locale.ITALIAN,
Locale("pt"),
)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> { override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -49,25 +51,27 @@ internal class Hentai3(context: MangaLoaderContext) :
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
append("/search?q=") append("/search?q=")
append(buildQuery(filter.tags)) append(buildQuery(filter.tags, filter.locale))
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular") append("&sort=popular")
} }
append("&page=") append("&page=")
append(page.toString()) append(page.toString())
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/")
append(page.toString())
if (filter.sortOrder == SortOrder.POPULARITY) {
append("?sort=popular")
}
} else if (filter.tags.isNotEmpty()) { } else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tags/") append("/tags/")
append(it.key) append(it.key)
} }
}
append("/") append("/")
append(page.toString()) append(page.toString())
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
@ -93,12 +97,18 @@ internal class Hentai3(context: MangaLoaderContext) :
return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found") return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found")
} }
private fun buildQuery(tags: Collection<MangaTag>) = private fun buildQuery(tags: Collection<MangaTag>, language: Locale?): String {
tags.joinToString(separator = " ") { tag -> val joiner = StringUtil.StringJoiner(" ")
if (tag.key == "languageKey") { tags.forEach { tag ->
"language:\"${tag.title.removePrefix("/")}\"" joiner.add("tag:\"")
} else { joiner.append(tag.key)
"tag:\"${tag.title}\"" joiner.append("\"")
}
language?.let { lc ->
joiner.add("language:\"")
joiner.append(lc.toLanguagePath())
joiner.append("\"")
} }
return joiner.complete()
} }
} }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -8,11 +9,11 @@ import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
import org.koitharu.kotatsu.parsers.util.parseHtml import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.urlEncoded import org.koitharu.kotatsu.parsers.util.urlEncoded
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI) @MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI)
internal class HentaiEnvy(context: MangaLoaderContext) : internal class HentaiEnvy(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAIENVY, "hentaienvy.com") { GalleryAdultsParser(context, MangaSource.HENTAIENVY, "hentaienvy.com", pageSize = 24) {
override val selectGalleryLink = "a" override val selectGalleryLink = "a"
override val selectGalleryTitle = "div.title" override val selectGalleryTitle = "div.title"
override val selectTags = ".tags_items" override val selectTags = ".tags_items"
@ -20,17 +21,6 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
override val selectAuthor = ".gt_right_tags ul:contains(Artists:) a" override val selectAuthor = ".gt_right_tags ul:contains(Artists:) a"
override val selectLanguageChapter = ".gt_right_tags ul:contains(Languages:) a" override val selectLanguageChapter = ".gt_right_tags ul:contains(Languages:) a"
override val idImg = "fimg" override val idImg = "fimg"
override val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/chinese",
"/spanish",
"/russian",
"/korean",
"/german",
"/portuguese",
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
@ -47,12 +37,10 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) { if (filter.tags.isNotEmpty()) {
if (filter.locale != null) {
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
append("/?")
} else {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
@ -60,7 +48,10 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
} }
append("/?") append("/?")
} }
} } else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/?")
} else { } else {
append("/?") append("/?")
} }
@ -74,4 +65,16 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
} }
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
} }
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("pt"),
)
} }

@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI) @MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
internal class HentaiEra(context: MangaLoaderContext) : internal class HentaiEra(context: MangaLoaderContext) :
@ -15,20 +15,22 @@ internal class HentaiEra(context: MangaLoaderContext) :
override val selectTag = ".galleries_info li:contains(Tags) div.info_tags" override val selectTag = ".galleries_info li:contains(Tags) div.info_tags"
override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name" override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name"
override val selectLanguageChapter = ".galleries_info li:contains(Languages) div.info_tags .item_name" override val selectLanguageChapter = ".galleries_info li:contains(Languages) div.info_tags .item_name"
override val listLanguage = arrayOf(
"/english",
"/japanese",
"/spanish",
"/french",
"/korean",
"/german",
"/russian",
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override val isMultipleTagsSupported = true override val isMultipleTagsSupported = true
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
)
override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/') val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.selectFirst(".item_name")?.text() ?: it.text() val name = it.selectFirst(".item_name")?.text() ?: it.text()
@ -52,24 +54,31 @@ internal class HentaiEra(context: MangaLoaderContext) :
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty() && filter.tags.size > 1) { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
append("/search/?key=") append("/search/?key=")
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append(buildQuery(filter.tags).replace("&lt=1&dl=0&pp=0&tr=0", "&lt=0&dl=0&pp=1&tr=0")) append(
buildQuery(filter.tags, filter.locale)
.replace("&lt=1&dl=0&pp=0&tr=0", "&lt=0&dl=0&pp=1&tr=0"),
)
} else { } else {
append(buildQuery(filter.tags)) append(buildQuery(filter.tags, filter.locale))
} }
append("&") append("&")
} else if (filter.tags.isNotEmpty()) { } else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
} }
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
} }
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/") append("/")
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
@ -90,40 +99,20 @@ internal class HentaiEra(context: MangaLoaderContext) :
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
} }
private fun buildQuery(tags: Collection<MangaTag>): String { private fun buildQuery(tags: Collection<MangaTag>, locale: Locale?): String {
val queryDefault = val queryDefault =
"&search=&mg=1&dj=1&ws=1&is=1&ac=1&gc=1&en=0&jp=0&es=0&fr=0&kr=0&de=0&ru=0&lt=1&dl=0&pp=0&tr=0" "&search=&mg=1&dj=1&ws=1&is=1&ac=1&gc=1&en=0&jp=0&es=0&fr=0&kr=0&de=0&ru=0&lt=1&dl=0&pp=0&tr=0"
var tag = "" val tag = tags.joinToString(" ", postfix = " ") { it.key }
var queryMod = "" val queryMod = when (val lp = locale?.toLanguagePath()) {
tags.map { "english" -> queryDefault.replace("en=0", "en=1")
if (it.key == "languageKey" && it.title == "/english") { "japanese" -> queryDefault.replace("jp=0", "jp=1")
queryMod = queryDefault.replace("en=0", "en=1") "spanish" -> queryDefault.replace("es=0", "es=1")
} "french" -> queryDefault.replace("fr=0", "fr=1")
if (it.key == "languageKey" && it.title == "/japanese") { "korean" -> queryDefault.replace("kr=0", "kr=1")
queryMod = queryDefault.replace("jp=0", "jp=1") "russian" -> queryDefault.replace("ru=0", "ru=1")
} "german" -> queryDefault.replace("de=0", "de=1")
if (it.key == "languageKey" && it.title == "/spanish") { null -> "&search=&mg=1&dj=1&ws=1&is=1&ac=1&gc=1&en=1&jp=1&es=1&fr=1&kr=1&de=1&ru=1&lt=1&dl=0&pp=0&tr=0"
queryMod = queryDefault.replace("es=0", "es=1") else -> throw IllegalArgumentException("Unsupported locale: $lp")
}
if (it.key == "languageKey" && it.title == "/french") {
queryMod = queryDefault.replace("fr=0", "fr=1")
}
if (it.key == "languageKey" && it.title == "/korean") {
queryMod = queryDefault.replace("kr=0", "kr=1")
}
if (it.key == "languageKey" && it.title == "/russian") {
queryMod = queryDefault.replace("ru=0", "ru=1")
}
if (it.key == "languageKey" && it.title == "/german") {
queryMod = queryDefault.replace("de=0", "de=1")
}
if (it.key != "languageKey") {
tag += it.key + " "
}
}
if (queryMod.isEmpty()) {
queryMod = "&search=&mg=1&dj=1&ws=1&is=1&ac=1&gc=1&en=1&jp=1&es=1&fr=1&kr=1&de=1&ru=1&lt=1&dl=0&pp=0&tr=0"
} }
return tag + queryMod return tag + queryMod
} }

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI) @MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI)
internal class HentaiForce(context: MangaLoaderContext) : internal class HentaiForce(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.HENTAIFORCE, "hentaiforce.net") { GalleryAdultsParser(context, MangaSource.HENTAIFORCE, "hentaiforce.net", pageSize = 30) {
override val selectGallery = ".gallery" override val selectGallery = ".gallery"
override val selectGalleryLink = "a.gallery-thumb" override val selectGalleryLink = "a.gallery-thumb"
override val pathTagUrl = "/tags/popular/" override val pathTagUrl = "/tags/popular/"
@ -19,26 +20,27 @@ internal class HentaiForce(context: MangaLoaderContext) :
override val selectAuthor = "div.tag-container:contains(Artists:) a" override val selectAuthor = "div.tag-container:contains(Artists:) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages:) a" override val selectLanguageChapter = "div.tag-container:contains(Languages:) a"
override val idImg = ".gallery-reader-img-wrapper img" override val idImg = ".gallery-reader-img-wrapper img"
override val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/chinese",
"/spanish",
"/russian",
"/korean",
"/german",
"/indonesian",
"/italian",
"/portuguese",
"/thai",
"/vietnamese",
)
override val isMultipleTagsSupported = true override val isMultipleTagsSupported = true
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("id"),
Locale.ITALIAN,
Locale("pt"),
Locale("th"),
Locale("vi"),
)
override suspend fun getPageUrl(page: MangaPage): String { override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found") return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found")
@ -56,23 +58,27 @@ internal class HentaiForce(context: MangaLoaderContext) :
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty() && filter.tags.size > 1) { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
append("/search?q=") append("/search?q=")
append(buildQuery(filter.tags)) append(buildQuery(filter.tags, filter.locale))
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular") append("&sort=popular")
} }
append("&page=") append("&page=")
} else if (filter.tags.isNotEmpty()) { } else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
} }
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
} }
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/") append("/")
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
@ -92,12 +98,18 @@ internal class HentaiForce(context: MangaLoaderContext) :
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
} }
private fun buildQuery(tags: Collection<MangaTag>) = private fun buildQuery(tags: Collection<MangaTag>, language: Locale?): String {
tags.joinToString(separator = " ") { tag -> val joiner = StringUtil.StringJoiner(" ")
if (tag.key == "languageKey") { tags.forEach { tag ->
"language:${tag.title.removePrefix("/")}" joiner.add("tag:\"")
} else { joiner.append(tag.key)
"tag:${tag.title}" joiner.append("\"")
}
language?.let { lc ->
joiner.add("language:\"")
joiner.append(lc.toLanguagePath())
joiner.append("\"")
} }
return joiner.complete()
} }
} }

@ -1,12 +1,13 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI) @MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
internal class HentaiFox(context: MangaLoaderContext) : internal class HentaiFox(context: MangaLoaderContext) :
@ -16,21 +17,6 @@ internal class HentaiFox(context: MangaLoaderContext) :
override val selectTags = ".list_tags" override val selectTags = ".list_tags"
override val selectTag = "ul.tags" override val selectTag = "ul.tags"
override val selectLanguageChapter = "ul.languages a.tag_btn" override val selectLanguageChapter = "ul.languages a.tag_btn"
override val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/chinese",
"/spanish",
"/russian",
"/korean",
"/indonesian",
"/italian",
"/portuguese",
"/turkish",
"/thai",
"/vietnamese",
)
override val isMultipleTagsSupported = true override val isMultipleTagsSupported = true
@ -51,9 +37,9 @@ internal class HentaiFox(context: MangaLoaderContext) :
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty() && filter.tags.size > 1) { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
append("/search/?q=") append("/search/?q=")
append(buildQuery(filter.tags)) append(buildQuery(filter.tags, filter.locale))
if (page > 1) { if (page > 1) {
append("&page=") append("&page=")
append(page.toString()) append(page.toString())
@ -64,14 +50,22 @@ internal class HentaiFox(context: MangaLoaderContext) :
} }
} else if (filter.tags.isNotEmpty()) { } else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
} }
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
} }
if (page > 1) {
append("/pag/")
append(page.toString())
append("/")
}
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/") append("/")
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/") append("popular/")
@ -122,12 +116,14 @@ internal class HentaiFox(context: MangaLoaderContext) :
) )
} }
private fun buildQuery(tags: Collection<MangaTag>) = private fun buildQuery(tags: Collection<MangaTag>, language: Locale?): String {
tags.joinToString(separator = " ") { tag -> val joiner = StringUtil.StringJoiner(" ")
if (tag.key == "languageKey") { tags.forEach { tag ->
tag.title.removePrefix("/") joiner.add(tag.key)
} else { }
tag.key language?.let { lc ->
joiner.add(lc.toLanguagePath())
} }
return joiner.complete()
} }
} }

@ -3,10 +3,13 @@ package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.removeSuffix import org.koitharu.kotatsu.parsers.util.removeSuffix
import java.util.*
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI) @MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
internal class HentaiRox(context: MangaLoaderContext) : internal class HentaiRox(context: MangaLoaderContext) :
@ -16,14 +19,15 @@ internal class HentaiRox(context: MangaLoaderContext) :
override val selectTag = "li:contains(Tags:)" override val selectTag = "li:contains(Tags:)"
override val selectAuthor = "li:contains(Artists:) span.item_name" override val selectAuthor = "li:contains(Artists:) span.item_name"
override val selectLanguageChapter = "li:contains(Languages:) .item_name" override val selectLanguageChapter = "li:contains(Languages:) .item_name"
override val listLanguage = arrayOf(
"/english", override suspend fun getAvailableLocales(): Set<Locale> = setOf(
"/french", Locale.ENGLISH,
"/japanese", Locale.FRENCH,
"/spanish", Locale.JAPANESE,
"/russian", Locale("es"),
"/korean", Locale("ru"),
"/german", Locale("ko"),
Locale.GERMAN,
) )
override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {

@ -1,12 +1,13 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI) @MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI)
internal class NHentaiParser(context: MangaLoaderContext) : internal class NHentaiParser(context: MangaLoaderContext) :
@ -21,11 +22,6 @@ internal class NHentaiParser(context: MangaLoaderContext) :
override val selectLanguageChapter = override val selectLanguageChapter =
".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated ".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated
override val idImg = "image-container" override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english",
"/japanese",
"/chinese",
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
@ -45,23 +41,27 @@ internal class NHentaiParser(context: MangaLoaderContext) :
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty() && filter.tags.size > 1) { if (filter.tags.size > 1 || (filter.tags.isNotEmpty() && filter.locale != null)) {
append("/search/?q=") append("/search/?q=")
append(buildQuery(filter.tags)) append(buildQuery(filter.tags, filter.locale).urlEncoded())
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular") append("&sort=popular")
} }
append("&") append("&")
} else if (filter.tags.isNotEmpty()) { } else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let { filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
} }
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
} }
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/") append("/")
if (filter.sortOrder == SortOrder.POPULARITY) { if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/") append("popular/")
@ -101,12 +101,24 @@ internal class NHentaiParser(context: MangaLoaderContext) :
) )
} }
private fun buildQuery(tags: Collection<MangaTag>) = override suspend fun getAvailableLocales(): Set<Locale> = setOf(
tags.joinToString(separator = " ") { tag -> Locale.ENGLISH,
if (tag.key == "languageKey") { Locale.JAPANESE,
"language:\"${tag.title.removePrefix("/")}\"" Locale.CHINESE,
} else { )
"tag:\"${tag.key}\""
} private fun buildQuery(tags: Collection<MangaTag>, language: Locale?): String {
val joiner = StringUtil.StringJoiner(" ")
tags.forEach { tag ->
joiner.add("tag:\"")
joiner.append(tag.key)
joiner.append("\"")
}
language?.let { lc ->
joiner.add("language:\"")
joiner.append(lc.toLanguagePath())
joiner.append("\"")
}
return joiner.complete()
} }
} }

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException import java.util.*
@MangaSourceParser("NHENTAIUK", "NHentai.uk", type = ContentType.HENTAI) @MangaSourceParser("NHENTAIUK", "NHentai.uk", type = ContentType.HENTAI)
internal class NHentaiUk(context: MangaLoaderContext) : internal class NHentaiUk(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.NHENTAIUK, "nhentai.uk", 50) { GalleryAdultsParser(context, MangaSource.NHENTAIUK, "nhentai.uk", pageSize = 50) {
override val selectGallery = ".gallery" override val selectGallery = ".gallery"
override val selectGalleryLink = "a" override val selectGalleryLink = "a"
override val selectGalleryTitle = ".caption" override val selectGalleryTitle = ".caption"
@ -19,18 +20,19 @@ internal class NHentaiUk(context: MangaLoaderContext) :
override val selectAuthor = "div.tag-container:contains(Artists:) a" override val selectAuthor = "div.tag-container:contains(Artists:) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages:) a" override val selectLanguageChapter = "div.tag-container:contains(Languages:) a"
override val idImg = "image-container" override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english", override suspend fun getAvailableLocales(): Set<Locale> = setOf(
"/french", Locale.ENGLISH,
"/japanese", Locale.FRENCH,
"/chinese", Locale.JAPANESE,
"/spanish", Locale.CHINESE,
"/russian", Locale("es"),
"/korean", Locale("ru"),
"/german", Locale("ko"),
"/italian", Locale.GERMAN,
"/portuguese", Locale("pt"),
"/turkish", Locale.ITALIAN,
Locale("tr"),
) )
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> { override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
@ -41,25 +43,34 @@ internal class NHentaiUk(context: MangaLoaderContext) :
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) { when {
filter.tags.oneOrThrowIfMany()?.let { filter.locale != null && filter.tags.isNotEmpty() -> {
if (it.key == "languageKey") { throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
filter.locale != null -> {
append("/language") append("/language")
append(it.title) append(filter.locale.toLanguagePath())
} else { append("/?p=")
}
filter.tags.isNotEmpty() -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/tag/") append("/tag/")
append(it.key) append(it.key)
} }
}
append("/?p=") append("/?p=")
} else { }
else -> {
append("/home?p=") append("/home?p=")
} }
} }
}
null -> append("/?") null -> append("/?")
} }

@ -1,11 +1,11 @@
package org.koitharu.kotatsu.parsers.site.heancmsalt package org.koitharu.kotatsu.parsers.site.heancmsalt
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -41,7 +41,7 @@ internal abstract class HeanCmsAlt(
append(listUrl) append(listUrl)
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {

@ -1,18 +1,14 @@
package org.koitharu.kotatsu.parsers.site.mangareader.en package org.koitharu.kotatsu.parsers.site.mangareader.en
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
import org.koitharu.kotatsu.parsers.util.domain import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
import org.koitharu.kotatsu.parsers.util.parseHtml import org.koitharu.kotatsu.parsers.util.parseHtml
import java.lang.IllegalArgumentException import java.util.*
import java.util.EnumSet
@MangaSourceParser("RIZZCOMIC", "RizzComic", "en") @MangaSourceParser("RIZZCOMIC", "RizzComic", "en")
internal class RizzComic(context: MangaLoaderContext) : internal class RizzComic(context: MangaLoaderContext) :
@ -35,7 +31,7 @@ internal class RizzComic(context: MangaLoaderContext) :
when (filter) { when (filter) {
is MangaListFilter.Search -> { is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source") throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
} }
is MangaListFilter.Advanced -> { is MangaListFilter.Advanced -> {

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.uk package org.koitharu.kotatsu.parsers.site.uk
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -23,6 +24,8 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manga.in.ua") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manga.in.ua")
override val isMultipleTagsSupported: Boolean = false
private val userHashRegex by lazy { private val userHashRegex by lazy {
Regex("site_login_hash\\s*=\\s*\'([^\']+)\'", RegexOption.IGNORE_CASE) Regex("site_login_hash\\s*=\\s*\'([^\']+)\'", RegexOption.IGNORE_CASE)
} }
@ -40,7 +43,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
tags.isNullOrEmpty() -> "/mangas/page/$page".toAbsoluteUrl(domain) tags.isNullOrEmpty() -> "/mangas/page/$page".toAbsoluteUrl(domain)
tags.size == 1 -> "${tags.first().key}/page/$page" tags.size == 1 -> "${tags.first().key}/page/$page"
tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 genre") tags.size > 1 -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED)
else -> "/mangas/page/$page".toAbsoluteUrl(domain) else -> "/mangas/page/$page".toAbsoluteUrl(domain)
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.util
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
@ -54,7 +55,7 @@ fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? {
return when { return when {
isNullOrEmpty() -> null isNullOrEmpty() -> null
size == 1 -> first() size == 1 -> first()
else -> throw IllegalArgumentException("Multiple genres are not supported by this source") else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED)
} }
} }
@ -63,7 +64,7 @@ fun Set<MangaState>?.oneOrThrowIfMany(): MangaState? {
return when { return when {
isNullOrEmpty() -> null isNullOrEmpty() -> null
size == 1 -> first() size == 1 -> first()
else -> throw IllegalArgumentException("Multiple states are not supported by this source") else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED)
} }
} }

Loading…
Cancel
Save