ComickLive | Add option to manually input tags (#11448)

* Add option to manually input tags

* Update Comick.kt

* Apply AwkwardPeak's Suggestions

* comma

* transforming serializer on property

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
KenjieDec 2025-11-12 12:36:56 +07:00 committed by Draff
parent f817f7b049
commit 91115ac93f
Signed by: Draff
GPG Key ID: E8A89F3211677653
4 changed files with 104 additions and 28 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Comick (Unoriginal)'
extClass = '.ComickFactory'
extVersionCode = 2
extVersionCode = 3
isNsfw = true
}

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.all.comicklive
import android.util.Log
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.ConfigurableSource
@ -114,38 +115,50 @@ class Comick(
private var nextCursor: String? = null
private val spaceSlashRegex = Regex("[ /]")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (page == 1) {
nextCursor = null
}
val url = "$baseUrl/api/search".toHttpUrl().newBuilder().apply {
addQueryParameter("order_by", filters.firstInstance<SortFilter>().selected)
addQueryParameter("order_direction", "desc")
filters.firstInstance<SortFilter>().let {
addQueryParameter("order_by", it.selected)
addQueryParameter("order_direction", if (it.state!!.ascending) "asc" else "desc")
}
filters.firstInstanceOrNull<GenreFilter>()?.let { genre ->
genre.included.forEach {
addQueryParameter("genres[]", it)
addQueryParameter("genres", it)
}
genre.excluded.forEach {
addQueryParameter("excludes[]", it)
addQueryParameter("excludes", it)
}
}
filters.firstInstanceOrNull<TagFilterText>()?.let { text ->
text.state.split(",").filter(String::isNotBlank).forEach {
val value = it.trim().lowercase().replace(spaceSlashRegex, "-")
addQueryParameter(
if (value.startsWith("-")) "excluded_tags" else "tags",
value.replaceFirst("-", ""),
)
}
}
filters.firstInstanceOrNull<TagFilter>()?.let { tag ->
tag.included.forEach {
addQueryParameter("tags[]", it)
addQueryParameter("tags", it)
}
tag.excluded.forEach {
addQueryParameter("excluded_tags[]", it)
addQueryParameter("excluded_tags", it)
}
}
filters.firstInstance<DemographicFilter>().checked.forEach {
addQueryParameter("demographic[]", it)
addQueryParameter("demographic", it)
}
filters.firstInstance<CreatedAtFilter>().selected?.let {
addQueryParameter("time", it)
}
filters.firstInstance<TypeFilter>().checked.forEach {
addQueryParameter("country[]", it)
addQueryParameter("country", it)
}
filters.firstInstance<MinimumChaptersFilter>().state.let {
if (it.isNotBlank()) {
@ -208,8 +221,8 @@ class Comick(
val filters: MutableList<Filter<*>> = mutableListOf(
SortFilter(),
DemographicFilter(),
CreatedAtFilter(),
TypeFilter(),
CreatedAtFilter(),
MinimumChaptersFilter(),
StatusFilter(),
ContentRatingFilter(),
@ -221,7 +234,16 @@ class Comick(
GET("$baseUrl/api/metadata", headers, CacheControl.FORCE_CACHE),
).await()
// the cache only request fails if it was not cached already
val getTags = preferences.getBoolean(GET_TAGS, true)
val textTags: List<Filter<*>> = listOf(
Filter.Separator(),
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
TagFilterText(),
Filter.Separator(),
)
if (!response.isSuccessful) {
metadataClient.newCall(
GET("$baseUrl/api/metadata", headers, CacheControl.FORCE_NETWORK),
@ -236,10 +258,16 @@ class Comick(
},
)
if (!getTags) {
filters.addAll(
index = 2,
textTags,
)
}
filters.addAll(
index = 0,
listOf(
Filter.Header("Press 'reset' to load genres and tags"),
Filter.Header("Press 'reset' to load genres ${if (getTags) "and tags" else ""}"),
Filter.Separator(),
),
)
@ -251,23 +279,37 @@ class Comick(
} catch (e: Throwable) {
Log.e(name, "Unable to parse filters", e)
if (!getTags) {
filters.addAll(
index = 2,
textTags,
)
}
filters.addAll(
index = 0,
listOf(
Filter.Header("Failed to parse genres and tags"),
Filter.Header("Failed to parse genres ${if (getTags) "and tags" else ""}"),
Filter.Separator(),
),
)
return@runBlocking FilterList(filters)
}
filters.addAll(
index = 1,
listOf(
GenreFilter(data.genres),
TagFilter(data.tags),
),
filters.add(
index = 3,
GenreFilter(data.genres),
)
if (!getTags) {
filters.addAll(
index = 4,
textTags,
)
} else {
filters.add(
index = 4,
TagFilter(data.tags),
)
}
return@runBlocking FilterList(filters)
}
@ -300,7 +342,7 @@ class Comick(
if (data.titles.isNotEmpty()) {
append("\n\n Alternative Titles: \n")
data.titles.forEach {
append(it.title, "\n")
append("- ", it.title.trim(), "\n")
}
}
}.trim()
@ -383,8 +425,17 @@ class Comick(
summary = "%s"
setDefaultValue("0")
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = GET_TAGS
title = "Tags Input Type"
summaryOn = "Tags will be in a form of scrollable list"
summaryOff = "Tags will need to be inputted manually"
setDefaultValue(true)
}.also(screen::addPreference)
}
}
private val domains = arrayOf("https://comick.live", "https://comick.art")
private const val DOMAIN_PREF = "domain_pref"
private const val GET_TAGS = "get_tags"

View File

@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.extension.all.comicklive
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonTransformingSerializer
@Serializable
class Data<T>(
@ -60,6 +65,7 @@ class ComicData(
@SerialName("md_comic_md_genres")
val genres: List<Genres>,
@SerialName("md_titles")
@Serializable(with = TitleTransform::class)
val titles: List<Title>,
) {
@Serializable
@ -79,6 +85,15 @@ class ComicData(
)
}
object TitleTransform : JsonTransformingSerializer<List<ComicData.Title>>(
ListSerializer(ComicData.Title.serializer()),
) {
override fun transformDeserialize(element: JsonElement): JsonElement {
if (element !is JsonObject) return element
return JsonArray(element.values.toList())
}
}
@Serializable
class ChapterList(
val data: List<Chapter>,

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.all.comicklive
import eu.kanade.tachiyomi.source.model.Filter
import java.util.Calendar
import kotlin.collections.filter
abstract class SelectFilter(
name: String,
@ -38,16 +39,21 @@ abstract class TriStateGroupFilter(
val excluded get() = state.filter { it.isExcluded() }.map { it.slug }
}
class SortFilter : SelectFilter(
name = "Sort",
options = listOf(
"Latest" to "created_at",
"Popular" to "user_follow_count",
"Highest Rating" to "rating",
"Last Uploaded" to "uploaded",
),
private val getSortsList = listOf(
"Latest" to "created_at",
"Popular" to "user_follow_count",
"Highest Rating" to "rating",
"Last Uploaded" to "uploaded",
)
class SortFilter : Filter.Sort(
name = "Sort",
values = getSortsList.map { it.first }.toTypedArray(),
state = Selection(0, false),
) {
val selected get() = state?.let { getSortsList[it.index] }?.second.takeIf { it?.isNotEmpty() ?: false }
}
class GenreFilter(genres: List<Metadata.Name>) : TriStateGroupFilter(
name = "Genre",
options = genres.map { it.name to it.slug },
@ -58,6 +64,10 @@ class TagFilter(tags: List<Metadata.Name>) : TriStateGroupFilter(
options = tags.map { it.name to it.slug },
)
class TagFilterText : Filter.Text(
name = "Tags",
)
class DemographicFilter : CheckBoxGroup(
name = "Demographic",
options = listOf(