Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support type and Demographic on MangaListFilter #1061

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,30 @@ abstract class MangaParser @InternalParsersApi constructor(
get() = emptySet()


/**
* Supported [ContentRating] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
open val availableContentRating: Set<ContentRating>
get() = emptySet()

/**
* Supported [Type] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
open val availableType: Set<Type>
davvarrr marked this conversation as resolved.
Show resolved Hide resolved
get() = emptySet()

/**
* Supported [Demographic] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
open val availableDemographic: Set<Demographic>
davvarrr marked this conversation as resolved.
Show resolved Hide resolved
get() = emptySet()

/**
* Whether parser supports filtering by more than one tag
*/
Expand Down Expand Up @@ -163,6 +184,8 @@ abstract class MangaParser @InternalParsersApi constructor(
locale = null,
states = emptySet(),
contentRating = emptySet(),
type = emptySet(),
demographic = emptySet(),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.koitharu.kotatsu.parsers.model

enum class Demographic {
SHOUNEN,
SHOUJO,
SEINEN,
JOSEI,
NONE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ sealed interface MangaListFilter {
(tags.size <= 1 || parser.isMultipleTagsSupported) &&
(tagsExclude.isEmpty() || parser.isTagsExclusionSupported) &&
(contentRating.isEmpty() || parser.availableContentRating.containsAll(contentRating)) &&
(states.isEmpty() || parser.availableStates.containsAll(states))
(states.isEmpty() || parser.availableStates.containsAll(states) &&
(type.isEmpty() || parser.availableType.containsAll(type)) &&
(demographic.isEmpty() || parser.availableDemographic.containsAll(demographic)))

is Search -> parser.isSearchSupported
}
Expand All @@ -35,17 +37,21 @@ sealed interface MangaListFilter {
@JvmField val locale: Locale?,
@JvmField val states: Set<MangaState>,
@JvmField val contentRating: Set<ContentRating>,
@JvmField val type: Set<Type>,
@JvmField val demographic: Set<Demographic>,
) : MangaListFilter {

override fun isEmpty(): Boolean =
tags.isEmpty() && tagsExclude.isEmpty() && locale == null && states.isEmpty() && contentRating.isEmpty()
tags.isEmpty() && tagsExclude.isEmpty() && locale == null && states.isEmpty() && contentRating.isEmpty() && type.isEmpty() && demographic.isEmpty()

fun newBuilder() = Builder(sortOrder)
.tags(tags)
.tagsExclude(tagsExclude)
.locale(locale)
.states(states)
.contentRatings(contentRating)
.type(type)
.demographic(demographic)

class Builder(sortOrder: SortOrder) {

Expand All @@ -55,6 +61,8 @@ sealed interface MangaListFilter {
private var _locale: Locale? = null
private var _states: Set<MangaState>? = null
private var _contentRating: Set<ContentRating>? = null
private var _type: Set<Type>? = null
private var _demographic: Set<Demographic>? = null

fun sortOrder(order: SortOrder) = apply {
_sortOrder = order
Expand All @@ -80,13 +88,23 @@ sealed interface MangaListFilter {
_contentRating = rating
}

fun type(type: Set<Type>?) = apply {
_type = type
}

fun demographic(demographic: Set<Demographic>?) = apply {
_demographic = demographic
}

fun build() = Advanced(
sortOrder = _sortOrder,
tags = _tags.orEmpty(),
tagsExclude = _tagsExclude.orEmpty(),
locale = _locale,
states = _states.orEmpty(),
contentRating = _contentRating.orEmpty(),
type = _type.orEmpty(),
demographic = _demographic.orEmpty()
)
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/org/koitharu/kotatsu/parsers/model/Type.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.model

enum class Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename it something more specific, e.g. MangaType?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, because MangaType.Manga is duplicated
and logically manga refers to the type of Japanese work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but Type is a very generic and commonly used name:

изображение

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hum ok, go for MangaType, I can't think of any other name that would be clear to understand

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After that, I was thinking of a "BookType", but I'm not sure that's more relevant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an option we can merge this with a ContentType class and use this name

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's ideal, but you shouldn't add tabs to the App (especially for novel manhua, other ect).

MANGA,
MANHWA,
MANHUA,
COMIC,
NOVEL,
OTHERS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ internal class ComickFunParser(context: MangaLoaderContext) :
SortOrder.NEWEST,
)

override val availableType: Set<Type> = EnumSet.of(
Type.MANGA,
Type.MANHWA,
Type.MANHUA,
Type.OTHERS,
)

override val availableDemographic: Set<Demographic> = EnumSet.allOf(Demographic::class.java)

override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)

Expand All @@ -56,10 +65,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
url.addQueryParameter("q", filter.query)
}

null -> {
url.addQueryParameter("sort", "view")
}

is MangaListFilter.Advanced -> {

filter.tags.forEach {
Expand Down Expand Up @@ -93,6 +98,36 @@ internal class ComickFunParser(context: MangaLoaderContext) :
},
)
}

filter.type.forEach {
url.addQueryParameter(
"country",
when (it) {
Type.MANGA -> "jp"
Type.MANHWA -> "kr"
Type.MANHUA -> "cn"
Type.OTHERS -> "others"
else -> ""
},
)
}

filter.demographic.forEach {
url.addQueryParameter(
"demographic",
when (it) {
Demographic.SHOUNEN -> "1"
Demographic.SHOUJO -> "2"
Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4"
Demographic.NONE -> "5"
},
)
}
}

null -> {
url.addQueryParameter("sort", "uploaded")
}
}
val ja = webClient.httpGet(url.build()).parseJsonArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context

override val availableContentRating: Set<ContentRating> = EnumSet.allOf(ContentRating::class.java)

override val availableDemographic: Set<Demographic> = EnumSet.allOf(Demographic::class.java)

override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)

Expand Down Expand Up @@ -96,6 +98,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
},
)

filter.states.forEach {
append("&status[]=")
when (it) {
Expand All @@ -106,6 +109,20 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
else -> append("")
}
}

filter.demographic.forEach {
append("&publicationDemographic[]=")
append(
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
},
)
}

filter.locale?.let {
append("&availableTranslatedLanguage[]=")
append(it.language)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ internal abstract class MangaReaderParser(
override val availableStates: Set<MangaState>
get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)

override val availableType: Set<Type>
get() = EnumSet.of(Type.MANGA, Type.MANHWA, Type.MANHUA, Type.COMIC, Type.NOVEL)


override val isTagsExclusionSupported = true

protected open val listUrl = "/manga"
Expand Down Expand Up @@ -110,6 +114,20 @@ internal abstract class MangaReaderParser(
}
}

filter.type.oneOrThrowIfMany()?.let {
append("&type=")
append(
when (it) {
Type.MANGA -> "manga"
Type.MANHWA -> "manhwa"
Type.MANHUA -> "manhua"
Type.COMIC -> "comic"
Type.NOVEL -> "novel"
else -> ""
},
)
}

append("&page=")
append(page.toString())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ fun Set<MangaState>?.oneOrThrowIfMany(): MangaState? {
}
}

@InternalParsersApi
fun Set<Type>?.oneOrThrowIfMany(): Type? {
return when {
isNullOrEmpty() -> null
size == 1 -> first()
else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED)
}
}

@InternalParsersApi
fun Set<Demographic>?.oneOrThrowIfMany(): Demographic? {
return when {
isNullOrEmpty() -> null
size == 1 -> first()
else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED)
}
}

@InternalParsersApi
fun Set<ContentRating>?.oneOrThrowIfMany(): ContentRating? {
return when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ internal class MangaParserTest {
states = emptySet(),
tagsExclude = emptySet(),
contentRating = emptySet(),
type = emptySet(),
demographic = emptySet(),
),
).minByOrNull {
it.title.length
Expand Down Expand Up @@ -133,6 +135,8 @@ internal class MangaParserTest {
locale = locales.random(),
states = setOf(),
contentRating = setOf(),
type = emptySet(),
demographic = emptySet(),
)
val list = parser.getList(offset = 0, filter)
checkMangaList(list, filter.locale.toString())
Expand Down
Loading