Comick: Comick: Added Exclude Tags Filter, Fixed Demographic Type, Added "None" / "Others" Filter, Added Content Rating Filter (#8049)

* Added Exclude Tags Filter

* Added None Demographic & Other Type.

Switched DemographicFilter from TriFilter -> CheckBoxFilter (API Doesn't support it) & switched it.isIncluded() -> it.state

* Added Content Rating Filter

* Added extension setting to hide/show alternative titles.

* Extracted duplicate tags code into a function.
This commit is contained in:
Cleopatra 2025-03-15 02:55:39 -03:00 committed by Draff
parent 7154880810
commit ddf63bf592
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
6 changed files with 73 additions and 11 deletions

View File

@ -1,5 +1,8 @@
ignored_groups_title=Ignored Groups ignored_groups_title=Ignored Groups
ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive) ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)
show_alternative_titles_title=Show Alternative Titles
show_alternative_titles_on=Adds alternative titles to the description
show_alternative_titles_off=Does not show alternative titles to the description
include_tags_title=Include Tags include_tags_title=Include Tags
include_tags_on=More specific, but might contain spoilers! include_tags_on=More specific, but might contain spoilers!
include_tags_off=Only the broader genres include_tags_off=Only the broader genres

View File

@ -1,5 +1,8 @@
ignored_groups_title=Grupos Ignorados ignored_groups_title=Grupos Ignorados
ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha
show_alternative_titles_title=Mostrar Títulos Alternativos
show_alternative_titles_on=Adiciona títulos alternativos à descrição
show_alternative_titles_off=Não mostra títulos alternativos na descrição
include_tags_title=Incluir Tags include_tags_title=Incluir Tags
include_tags_on=Mais detalhadas, mas podem conter spoilers include_tags_on=Mais detalhadas, mas podem conter spoilers
include_tags_off=Apenas os gêneros básicos include_tags_off=Apenas os gêneros básicos

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Comick' extName = 'Comick'
extClass = '.ComickFactory' extClass = '.ComickFactory'
extVersionCode = 53 extVersionCode = 54
isNsfw = true isNsfw = true
} }

View File

@ -20,6 +20,7 @@ import keiyoushi.utils.getPreferencesLazy
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Builder
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
@ -78,6 +79,20 @@ abstract class Comick(
} }
}.also(screen::addPreference) }.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = SHOW_ALTERNATIVE_TITLES_PREF
title = intl["show_alternative_titles_title"]
summaryOn = intl["show_alternative_titles_on"]
summaryOff = intl["show_alternative_titles_off"]
setDefaultValue(SHOW_ALTERNATIVE_TITLES_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit()
.putBoolean(SHOW_ALTERNATIVE_TITLES_PREF, newValue as Boolean)
.commit()
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply { SwitchPreferenceCompat(screen.context).apply {
key = INCLUDE_MU_TAGS_PREF key = INCLUDE_MU_TAGS_PREF
title = intl["include_tags_title"] title = intl["include_tags_title"]
@ -155,6 +170,9 @@ abstract class Comick(
.orEmpty() .orEmpty()
.toSet() .toSet()
private val SharedPreferences.showAlternativeTitles: Boolean
get() = getBoolean(SHOW_ALTERNATIVE_TITLES_PREF, SHOW_ALTERNATIVE_TITLES_DEFAULT)
private val SharedPreferences.includeMuTags: Boolean private val SharedPreferences.includeMuTags: Boolean
get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT) get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT)
@ -272,6 +290,16 @@ abstract class Comick(
return MangasPage(entries, end < searchResponse.size) return MangasPage(entries, end < searchResponse.size)
} }
private fun addTagQueryParameters(builder: Builder, tags: String, parameterName: String) {
tags.split(",").forEach {
builder.addQueryParameter(
parameterName,
it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
.replace("'-", "-and-039-").replace("'", "-and-039-"),
)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply { val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply {
filters.forEach { it -> filters.forEach { it ->
@ -293,7 +321,7 @@ abstract class Comick(
} }
is DemographicFilter -> { is DemographicFilter -> {
it.state.filter { it.isIncluded() }.forEach { it.state.filter { it.state }.forEach {
addQueryParameter("demographic", it.value) addQueryParameter("demographic", it.value)
} }
} }
@ -314,6 +342,12 @@ abstract class Comick(
} }
} }
is ContentRatingFilter -> {
if (it.state > 0) {
addQueryParameter("content_rating", it.getValue())
}
}
is CreatedAtFilter -> { is CreatedAtFilter -> {
if (it.state > 0) { if (it.state > 0) {
addQueryParameter("time", it.getValue()) addQueryParameter("time", it.getValue())
@ -340,14 +374,14 @@ abstract class Comick(
is TagFilter -> { is TagFilter -> {
if (it.state.isNotEmpty()) { if (it.state.isNotEmpty()) {
it.state.split(",").forEach { addTagQueryParameters(this, it.state, "tags")
addQueryParameter(
"tags",
it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
.replace("'-", "-and-039-").replace("'", "-and-039-"),
)
} }
} }
is ExcludedTagFilter -> {
if (it.state.isNotEmpty()) {
addTagQueryParameters(this, it.state, "excluded-tags")
}
} }
else -> {} else -> {}
@ -400,6 +434,7 @@ abstract class Comick(
return mangaData.toSManga( return mangaData.toSManga(
includeMuTags = preferences.includeMuTags, includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition, scorePosition = preferences.scorePosition,
showAlternativeTitles = preferences.showAlternativeTitles,
covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol }, covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
groupTags = preferences.groupTags, groupTags = preferences.groupTags,
) )
@ -407,6 +442,7 @@ abstract class Comick(
return mangaData.toSManga( return mangaData.toSManga(
includeMuTags = preferences.includeMuTags, includeMuTags = preferences.includeMuTags,
scorePosition = preferences.scorePosition, scorePosition = preferences.scorePosition,
showAlternativeTitles = preferences.showAlternativeTitles,
groupTags = preferences.groupTags, groupTags = preferences.groupTags,
) )
} }
@ -524,6 +560,8 @@ abstract class Comick(
const val SLUG_SEARCH_PREFIX = "id:" const val SLUG_SEARCH_PREFIX = "id:"
private val SPACE_AND_SLASH_REGEX = Regex("[ /]") private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
private const val IGNORED_GROUPS_PREF = "IgnoredGroups" private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
private const val SHOW_ALTERNATIVE_TITLES_PREF = "ShowAlternativeTitles"
const val SHOW_ALTERNATIVE_TITLES_DEFAULT = false
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags" private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
const val INCLUDE_MU_TAGS_DEFAULT = false const val INCLUDE_MU_TAGS_DEFAULT = false
private const val GROUP_TAGS_PREF = "GroupTags" private const val GROUP_TAGS_PREF = "GroupTags"

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.all.comickfun
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SHOW_ALTERNATIVE_TITLES_DEFAULT
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -36,6 +37,7 @@ class Manga(
fun toSManga( fun toSManga(
includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT, includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
scorePosition: String = SCORE_POSITION_DEFAULT, scorePosition: String = SCORE_POSITION_DEFAULT,
showAlternativeTitles: Boolean = SHOW_ALTERNATIVE_TITLES_DEFAULT,
covers: List<MDcovers>? = null, covers: List<MDcovers>? = null,
groupTags: Boolean = GROUP_TAGS_DEFAULT, groupTags: Boolean = GROUP_TAGS_DEFAULT,
) = ) =
@ -54,7 +56,7 @@ class Manga(
if (this.isNotEmpty()) append("\n\n") if (this.isNotEmpty()) append("\n\n")
append(comic.fancyScore) append(comic.fancyScore)
} }
if (comic.altTitles.isNotEmpty()) { if (showAlternativeTitles && comic.altTitles.isNotEmpty()) {
if (this.isNotEmpty()) append("\n\n") if (this.isNotEmpty()) append("\n\n")
append("Alternative Titles:\n") append("Alternative Titles:\n")
append( append(

View File

@ -11,6 +11,7 @@ fun getFilters(): FilterList {
TypeFilter("Type", getTypeList), TypeFilter("Type", getTypeList),
SortFilter("Sort", getSortsList), SortFilter("Sort", getSortsList),
StatusFilter("Status", getStatusList), StatusFilter("Status", getStatusList),
ContentRatingFilter("Content Rating", getContentRatingList),
CompletedFilter("Completely Scanlated?"), CompletedFilter("Completely Scanlated?"),
CreatedAtFilter("Created at", getCreatedAtList), CreatedAtFilter("Created at", getCreatedAtList),
MinimumFilter("Minimum Chapters"), MinimumFilter("Minimum Chapters"),
@ -20,6 +21,7 @@ fun getFilters(): FilterList {
ToYearFilter("To"), ToYearFilter("To"),
Filter.Header("Separate tags with commas"), Filter.Header("Separate tags with commas"),
TagFilter("Tags"), TagFilter("Tags"),
ExcludedTagFilter("Excluded Tags"),
) )
} }
@ -29,8 +31,10 @@ internal class GenreFilter(name: String, genreList: List<Pair<String, String>>)
internal class TagFilter(name: String) : TextFilter(name) internal class TagFilter(name: String) : TextFilter(name)
internal class ExcludedTagFilter(name: String) : TextFilter(name)
internal class DemographicFilter(name: String, demographicList: List<Pair<String, String>>) : internal class DemographicFilter(name: String, demographicList: List<Pair<String, String>>) :
Filter.Group<TriFilter>(name, demographicList.map { TriFilter(it.first, it.second) }) Filter.Group<CheckBoxFilter>(name, demographicList.map { CheckBoxFilter(it.first, it.second) })
internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) : internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) :
Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) }) Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) })
@ -52,6 +56,9 @@ internal class SortFilter(name: String, sortList: List<Pair<String, String>>, st
internal class StatusFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) : internal class StatusFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) :
SelectFilter(name, statusList, state) SelectFilter(name, statusList, state)
internal class ContentRatingFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) :
SelectFilter(name, statusList, state)
/** Generics **/ /** Generics **/
internal open class TriFilter(name: String, val value: String) : Filter.TriState(name) internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
@ -156,12 +163,14 @@ private val getDemographicList: List<Pair<String, String>> = listOf(
Pair("Shoujo", "2"), Pair("Shoujo", "2"),
Pair("Seinen", "3"), Pair("Seinen", "3"),
Pair("Josei", "4"), Pair("Josei", "4"),
Pair("None", "5"),
) )
private val getTypeList: List<Pair<String, String>> = listOf( private val getTypeList: List<Pair<String, String>> = listOf(
Pair("Manga", "jp"), Pair("Manga", "jp"),
Pair("Manhwa", "kr"), Pair("Manhwa", "kr"),
Pair("Manhua", "cn"), Pair("Manhua", "cn"),
Pair("Others", "others"),
) )
private val getCreatedAtList: List<Pair<String, String>> = listOf( private val getCreatedAtList: List<Pair<String, String>> = listOf(
@ -190,3 +199,10 @@ private val getStatusList: List<Pair<String, String>> = listOf(
Pair("Cancelled", "3"), Pair("Cancelled", "3"),
Pair("Hiatus", "4"), Pair("Hiatus", "4"),
) )
private val getContentRatingList: List<Pair<String, String>> = listOf(
Pair("All", ""),
Pair("Safe", "safe"),
Pair("Suggestive", "suggestive"),
Pair("Erotica", "erotica"),
)