[GalleryAdults] Language filter support #357

pull/404/head
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
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
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.network.UserAgents
import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.text.SimpleDateFormat
import java.util.*
@ -60,7 +60,7 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
}
} 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 {
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("popular-comic")

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

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

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

@ -6,6 +6,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@ -25,7 +26,6 @@ internal abstract class GalleryAdultsParser(
override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
@ -37,18 +37,19 @@ internal abstract class GalleryAdultsParser(
}
is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
append("/?")
} else {
val tag = filter.tags.oneOrThrowIfMany()
val lang = filter.locale
if (tag != null && lang != null) {
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
if (tag != null) {
append("/tag/")
append(it.key)
append(tag.key)
append("/?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/?")
}
}
} else {
append("/?")
}
@ -102,39 +103,30 @@ internal abstract class GalleryAdultsParser(
}.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 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> {
val url = "https://$domain$pathTagUrl$page"
val root = webClient.httpGet(url).parseHtml().selectFirstOrThrow(selectTags)
val tagLanguage = ArrayList<MangaTag>(listLanguage.size)
for (language in listLanguage) {
tagLanguage.add(
MangaTag(
key = "languageKey",
title = language,
source = source,
),
)
}
return root.parseTags() + tagLanguage
return root.parseTags()
}
protected open fun Element.parseTags() = select("a").mapToSet {
@ -142,7 +134,7 @@ internal abstract class GalleryAdultsParser(
val name = it.html().substringBefore("<")
MangaTag(
key = key,
title = name,
title = name.toTitleCase(),
source = source,
)
}
@ -204,4 +196,8 @@ internal abstract class GalleryAdultsParser(
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
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.koitharu.kotatsu.parsers.MangaLoaderContext
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.util.mapToSet
import org.koitharu.kotatsu.parsers.util.removeSuffix
import java.util.*
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
internal class AsmHentai(context: MangaLoaderContext) :
@ -17,11 +20,12 @@ internal class AsmHentai(context: MangaLoaderContext) :
override val pathTagUrl = "/tags/?page="
override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag"
override val idImg = "fimg"
override val listLanguage = arrayOf(
"/english",
"/japanese",
"/chinese",
"/turkish",
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("tr"),
)
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.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
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 selectGalleryLink = "a"
override val selectGalleryTitle = ".caption"
@ -19,10 +20,11 @@ internal class DoujinDesuUk(context: MangaLoaderContext) :
override val selectAuthor = "div.tag-container:contains(Artists) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages) a"
override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english",
"/japanese",
"/chinese",
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.JAPANESE,
Locale.CHINESE,
)
override fun parseMangaList(doc: Document): List<Manga> {

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
import java.util.*
@MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI)
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 selectGalleryLink = "a"
@ -21,20 +22,21 @@ internal class Hentai3(context: MangaLoaderContext) :
override val selectLanguageChapter = "div.tag-container:contains(Languages) a"
override val selectUrlChapter = "#main-cover a"
override val idImg = ".js-main-img"
override val listLanguage = arrayOf(
"/english",
"/spanish",
"/french",
"/italian",
"/portuguese",
"/russian",
"/japanese",
)
override val isMultipleTagsSupported = true
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> {
val url = buildString {
append("https://")
@ -49,25 +51,27 @@ internal class Hentai3(context: MangaLoaderContext) :
}
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(buildQuery(filter.tags))
append(buildQuery(filter.tags, filter.locale))
if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular")
}
append("&page=")
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()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tags/")
append(it.key)
}
}
append("/")
append(page.toString())
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")
}
private fun buildQuery(tags: Collection<MangaTag>) =
tags.joinToString(separator = " ") { tag ->
if (tag.key == "languageKey") {
"language:\"${tag.title.removePrefix("/")}\""
} else {
"tag:\"${tag.title}\""
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,5 +1,6 @@
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.MangaSourceParser
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.parseHtml
import org.koitharu.kotatsu.parsers.util.urlEncoded
import java.util.EnumSet
import java.util.*
@MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI)
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 selectGalleryTitle = "div.title"
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 selectLanguageChapter = ".gt_right_tags ul:contains(Languages:) a"
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)
@ -47,12 +37,10 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) {
if (filter.locale != null) {
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
append("/?")
} else {
append("/tag/")
append(it.key)
if (filter.sortOrder == SortOrder.POPULARITY) {
@ -60,7 +48,10 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
}
append("/?")
}
}
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/?")
} else {
append("/?")
}
@ -74,4 +65,16 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
}
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.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
import java.util.*
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
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 selectAuthor = ".galleries_info li:contains(Artists) span.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 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 {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
val name = it.selectFirst(".item_name")?.text() ?: it.text()
@ -52,24 +54,31 @@ internal class HentaiEra(context: MangaLoaderContext) :
}
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=")
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 {
append(buildQuery(filter.tags))
append(buildQuery(filter.tags, filter.locale))
}
append("&")
} else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/")
append(it.key)
}
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
}
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
@ -90,40 +99,20 @@ internal class HentaiEra(context: MangaLoaderContext) :
return parseMangaList(webClient.httpGet(url).parseHtml())
}
private fun buildQuery(tags: Collection<MangaTag>): String {
private fun buildQuery(tags: Collection<MangaTag>, locale: Locale?): String {
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"
var tag = ""
var queryMod = ""
tags.map {
if (it.key == "languageKey" && it.title == "/english") {
queryMod = queryDefault.replace("en=0", "en=1")
}
if (it.key == "languageKey" && it.title == "/japanese") {
queryMod = queryDefault.replace("jp=0", "jp=1")
}
if (it.key == "languageKey" && it.title == "/spanish") {
queryMod = queryDefault.replace("es=0", "es=1")
}
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"
val tag = tags.joinToString(" ", postfix = " ") { it.key }
val queryMod = when (val lp = locale?.toLanguagePath()) {
"english" -> queryDefault.replace("en=0", "en=1")
"japanese" -> queryDefault.replace("jp=0", "jp=1")
"spanish" -> queryDefault.replace("es=0", "es=1")
"french" -> queryDefault.replace("fr=0", "fr=1")
"korean" -> queryDefault.replace("kr=0", "kr=1")
"russian" -> queryDefault.replace("ru=0", "ru=1")
"german" -> queryDefault.replace("de=0", "de=1")
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"
else -> throw IllegalArgumentException("Unsupported locale: $lp")
}
return tag + queryMod
}

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
import java.util.*
@MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI)
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 selectGalleryLink = "a.gallery-thumb"
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 selectLanguageChapter = "div.tag-container:contains(Languages:) a"
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 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 {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found")
@ -56,23 +58,27 @@ internal class HentaiForce(context: MangaLoaderContext) :
}
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(buildQuery(filter.tags))
append(buildQuery(filter.tags, filter.locale))
if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular")
}
append("&page=")
} else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/")
append(it.key)
}
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
}
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
@ -92,12 +98,18 @@ internal class HentaiForce(context: MangaLoaderContext) :
return parseMangaList(webClient.httpGet(url).parseHtml())
}
private fun buildQuery(tags: Collection<MangaTag>) =
tags.joinToString(separator = " ") { tag ->
if (tag.key == "languageKey") {
"language:${tag.title.removePrefix("/")}"
} else {
"tag:${tag.title}"
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,12 +1,13 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
import java.util.*
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
internal class HentaiFox(context: MangaLoaderContext) :
@ -16,21 +17,6 @@ internal class HentaiFox(context: MangaLoaderContext) :
override val selectTags = ".list_tags"
override val selectTag = "ul.tags"
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
@ -51,9 +37,9 @@ internal class HentaiFox(context: MangaLoaderContext) :
}
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(buildQuery(filter.tags))
append(buildQuery(filter.tags, filter.locale))
if (page > 1) {
append("&page=")
append(page.toString())
@ -64,14 +50,22 @@ internal class HentaiFox(context: MangaLoaderContext) :
}
} else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/")
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("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
@ -122,12 +116,14 @@ internal class HentaiFox(context: MangaLoaderContext) :
)
}
private fun buildQuery(tags: Collection<MangaTag>) =
tags.joinToString(separator = " ") { tag ->
if (tag.key == "languageKey") {
tag.title.removePrefix("/")
} else {
tag.key
private fun buildQuery(tags: Collection<MangaTag>, language: Locale?): String {
val joiner = StringUtil.StringJoiner(" ")
tags.forEach { tag ->
joiner.add(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.koitharu.kotatsu.parsers.MangaLoaderContext
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.util.mapToSet
import org.koitharu.kotatsu.parsers.util.removeSuffix
import java.util.*
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
internal class HentaiRox(context: MangaLoaderContext) :
@ -16,14 +19,15 @@ internal class HentaiRox(context: MangaLoaderContext) :
override val selectTag = "li:contains(Tags:)"
override val selectAuthor = "li:contains(Artists:) span.item_name"
override val selectLanguageChapter = "li:contains(Languages:) .item_name"
override val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/spanish",
"/russian",
"/korean",
"/german",
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 {

@ -1,12 +1,13 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
import java.util.*
@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI)
internal class NHentaiParser(context: MangaLoaderContext) :
@ -21,11 +22,6 @@ internal class NHentaiParser(context: MangaLoaderContext) :
override val selectLanguageChapter =
".tag-container:contains(Languages:) span.tags a:not(.tag-17249) span.name" // tag-17249 = translated
override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english",
"/japanese",
"/chinese",
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
@ -45,23 +41,27 @@ internal class NHentaiParser(context: MangaLoaderContext) :
}
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(buildQuery(filter.tags))
append(buildQuery(filter.tags, filter.locale).urlEncoded())
if (filter.sortOrder == SortOrder.POPULARITY) {
append("&sort=popular")
}
append("&")
} else if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
append("/language")
append(it.title)
} else {
append("/tag/")
append(it.key)
}
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
}
append("?")
} else if (filter.locale != null) {
append("/language/")
append(filter.locale.toLanguagePath())
append("/")
if (filter.sortOrder == SortOrder.POPULARITY) {
append("popular/")
@ -101,12 +101,24 @@ internal class NHentaiParser(context: MangaLoaderContext) :
)
}
private fun buildQuery(tags: Collection<MangaTag>) =
tags.joinToString(separator = " ") { tag ->
if (tag.key == "languageKey") {
"language:\"${tag.title.removePrefix("/")}\""
} else {
"tag:\"${tag.key}\""
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.JAPANESE,
Locale.CHINESE,
)
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
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.lang.IllegalArgumentException
import java.util.*
@MangaSourceParser("NHENTAIUK", "NHentai.uk", type = ContentType.HENTAI)
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 selectGalleryLink = "a"
override val selectGalleryTitle = ".caption"
@ -19,18 +20,19 @@ internal class NHentaiUk(context: MangaLoaderContext) :
override val selectAuthor = "div.tag-container:contains(Artists:) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages:) a"
override val idImg = "image-container"
override val listLanguage = arrayOf(
"/english",
"/french",
"/japanese",
"/chinese",
"/spanish",
"/russian",
"/korean",
"/german",
"/italian",
"/portuguese",
"/turkish",
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"),
Locale.ITALIAN,
Locale("tr"),
)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
@ -41,25 +43,34 @@ internal class NHentaiUk(context: MangaLoaderContext) :
when (filter) {
is MangaListFilter.Search -> {
throw IllegalArgumentException("Search is not supported by this source")
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
}
is MangaListFilter.Advanced -> {
if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany()?.let {
if (it.key == "languageKey") {
when {
filter.locale != null && filter.tags.isNotEmpty() -> {
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
filter.locale != null -> {
append("/language")
append(it.title)
} else {
append(filter.locale.toLanguagePath())
append("/?p=")
}
filter.tags.isNotEmpty() -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/tag/")
append(it.key)
}
}
append("/?p=")
} else {
}
else -> {
append("/home?p=")
}
}
}
null -> append("/?")
}

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

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

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.uk
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
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 isMultipleTagsSupported: Boolean = false
private val userHashRegex by lazy {
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.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)
}
val doc = webClient.httpGet(url).parseHtml()

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.util
import okhttp3.HttpUrl
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.exception.ParseException
@ -54,7 +55,7 @@ fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? {
return when {
isNullOrEmpty() -> null
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 {
isNullOrEmpty() -> null
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