mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 02:12:42 +01:00
Hitomi Add "Type" Filter (#3355)
* Add "Type" Filter - Added "Type" Filter - Popular Section: Today -> Year - Fixed issue where manga is skipped if language is null * Change1 * * * Change2 - Use List instead of Set
This commit is contained in:
parent
68690176c5
commit
a1edef99a2
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Hitomi'
|
||||
extClass = '.HitomiFactory'
|
||||
extVersionCode = 28
|
||||
extVersionCode = 29
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -3,79 +3,50 @@ package eu.kanade.tachiyomi.extension.all.hitomi
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
typealias OrderType = Pair<String?, String>
|
||||
typealias ParsedFilter = Pair<String, OrderType>
|
||||
|
||||
private fun parseFilter(query: StringBuilder, area: String, filterState: String) {
|
||||
filterState
|
||||
.trim()
|
||||
.split(',')
|
||||
.filter { it.isNotBlank() }
|
||||
.forEach {
|
||||
val trimmed = it.trim()
|
||||
val negativePrefix = if (trimmed.startsWith("-")) "-" else ""
|
||||
query.append(" $negativePrefix$area:${trimmed.removePrefix("-").replace(" ", "_")}")
|
||||
}
|
||||
fun getFilters(): FilterList {
|
||||
return FilterList(
|
||||
SelectFilter("Sort by", getSortsList),
|
||||
TypeFilter("Types"),
|
||||
Filter.Separator(),
|
||||
Filter.Header("Separate tags with commas (,)"),
|
||||
Filter.Header("Prepend with dash (-) to exclude"),
|
||||
TextFilter("Groups", "group"),
|
||||
TextFilter("Artists", "artist"),
|
||||
TextFilter("Series", "series"),
|
||||
TextFilter("Characters", "character"),
|
||||
TextFilter("Male Tags", "male"),
|
||||
TextFilter("Female Tags", "female"),
|
||||
Filter.Header("Please don't put Female/Male tags here, they won't work!"),
|
||||
TextFilter("Tags", "tag"),
|
||||
)
|
||||
}
|
||||
|
||||
fun parseFilters(filters: FilterList): ParsedFilter {
|
||||
val query = StringBuilder()
|
||||
var order: OrderType = Pair("date", "added")
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is SortFilter -> {
|
||||
order = filter.getOrder
|
||||
}
|
||||
is AreaFilter -> {
|
||||
parseFilter(query, filter.getAreaName, filter.state)
|
||||
}
|
||||
else -> { /* Do Nothing */ }
|
||||
}
|
||||
}
|
||||
return Pair(query.toString(), order)
|
||||
internal open class TextFilter(name: String, val type: String) : Filter.Text(name)
|
||||
internal open class SelectFilter(name: String, val vals: List<Triple<String, String?, String>>, state: Int = 0) :
|
||||
Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
|
||||
fun getArea() = vals[state].second
|
||||
fun getValue() = vals[state].third
|
||||
}
|
||||
internal class TypeFilter(name: String) :
|
||||
Filter.Group<CheckBoxFilter>(
|
||||
name,
|
||||
listOf(
|
||||
Pair("Anime", "anime"),
|
||||
Pair("Artist CG", "artistcg"),
|
||||
Pair("Doujinshi", "doujinshi"),
|
||||
Pair("Game CG", "gamecg"),
|
||||
Pair("Image Set", "imageset"),
|
||||
Pair("Manga", "manga"),
|
||||
).map { CheckBoxFilter(it.first, it.second, true) },
|
||||
)
|
||||
internal open class CheckBoxFilter(name: String, val value: String, state: Boolean) : Filter.CheckBox(name, state)
|
||||
|
||||
private class OrderFilter(val name: String, val order: OrderType) {
|
||||
val getFilterName: String
|
||||
get() = name
|
||||
val getOrder: OrderType
|
||||
get() = order
|
||||
}
|
||||
|
||||
private class SortFilter : UriPartFilter(
|
||||
"Sort By",
|
||||
arrayOf(
|
||||
OrderFilter("Date Added", Pair(null, "index")),
|
||||
OrderFilter("Date Published", Pair("date", "published")),
|
||||
OrderFilter("Popular: Today", Pair("popular", "today")),
|
||||
OrderFilter("Popular: Week", Pair("popular", "week")),
|
||||
OrderFilter("Popular: Month", Pair("popular", "month")),
|
||||
OrderFilter("Popular: Year", Pair("popular", "year")),
|
||||
),
|
||||
)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<OrderFilter>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.getFilterName }.toTypedArray()) {
|
||||
val getOrder: OrderType
|
||||
get() = vals[state].getOrder
|
||||
}
|
||||
|
||||
private class AreaFilter(displayName: String, val areaName: String) :
|
||||
Filter.Text(displayName) {
|
||||
val getAreaName: String
|
||||
get() = areaName
|
||||
}
|
||||
|
||||
fun getFilterListInternal(): FilterList = FilterList(
|
||||
SortFilter(),
|
||||
Filter.Header("Separate tags with commas (,)"),
|
||||
Filter.Header("Prepend with dash (-) to exclude"),
|
||||
AreaFilter("Artist(s)", "artist"),
|
||||
AreaFilter("Character(s)", "character"),
|
||||
AreaFilter("Group(s)", "group"),
|
||||
AreaFilter("Series", "series"),
|
||||
AreaFilter("Female Tag(s)", "female"),
|
||||
AreaFilter("Male Tag(s)", "male"),
|
||||
Filter.Header("Don't put Female/Male tags here, they won't work!"),
|
||||
AreaFilter("Tag(s)", "tag"),
|
||||
private val getSortsList: List<Triple<String, String?, String>> = listOf(
|
||||
Triple("Date Added", null, "index"),
|
||||
Triple("Date Published", "date", "published"),
|
||||
Triple("Popular: Today", "popular", "today"),
|
||||
Triple("Popular: Week", "popular", "week"),
|
||||
Triple("Popular: Month", "popular", "month"),
|
||||
Triple("Popular: Year", "popular", "year"),
|
||||
Triple("Random", "popular", "year"),
|
||||
)
|
||||
|
@ -69,7 +69,7 @@ class Hitomi(
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> = Observable.fromCallable {
|
||||
runBlocking {
|
||||
val entries = getGalleryIDsFromNozomi("popular", "today", nozomiLang, page.nextPageRange())
|
||||
val entries = getGalleryIDsFromNozomi("popular", "year", nozomiLang, page.nextPageRange())
|
||||
.toMangaList()
|
||||
|
||||
MangasPage(entries, entries.size >= 24)
|
||||
@ -88,26 +88,23 @@ class Hitomi(
|
||||
private lateinit var searchResponse: List<Int>
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.fromCallable {
|
||||
val parsedFilter = parseFilters(filters)
|
||||
|
||||
runBlocking {
|
||||
if (page == 1) {
|
||||
searchResponse = hitomiSearch(
|
||||
"$query${parsedFilter.first}".trim(),
|
||||
parsedFilter.second,
|
||||
query.trim(),
|
||||
filters,
|
||||
nozomiLang,
|
||||
).toList()
|
||||
)
|
||||
}
|
||||
|
||||
val end = min(page * 25, searchResponse.size)
|
||||
val entries = searchResponse.subList((page - 1) * 25, end)
|
||||
.toMangaList()
|
||||
|
||||
MangasPage(entries, end != searchResponse.size)
|
||||
MangasPage(entries, end < searchResponse.size)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList = getFilterListInternal()
|
||||
override fun getFilterList() = getFilters()
|
||||
|
||||
private fun Int.nextPageRange(): LongRange {
|
||||
val byteOffset = ((this - 1) * 25) * 4L
|
||||
@ -127,15 +124,49 @@ class Hitomi(
|
||||
|
||||
private suspend fun hitomiSearch(
|
||||
query: String,
|
||||
order: OrderType,
|
||||
filters: FilterList,
|
||||
language: String = "all",
|
||||
): Set<Int> =
|
||||
): List<Int> =
|
||||
coroutineScope {
|
||||
var sortBy: Pair<String?, String> = Pair(null, "index")
|
||||
var random = false
|
||||
|
||||
val terms = query
|
||||
.trim()
|
||||
.replace(Regex("""^\?"""), "")
|
||||
.lowercase()
|
||||
.split(Regex("\\s+"))
|
||||
.map {
|
||||
it.replace('_', ' ')
|
||||
}.toMutableList()
|
||||
|
||||
filters.forEach {
|
||||
when (it) {
|
||||
is SelectFilter -> {
|
||||
sortBy = Pair(it.getArea(), it.getValue())
|
||||
random = (it.vals[it.state].first == "Random")
|
||||
}
|
||||
|
||||
is TypeFilter -> {
|
||||
val (activeFilter, inactiveFilters) = it.state.partition { stIt -> stIt.state }
|
||||
terms += when {
|
||||
inactiveFilters.size < 5 -> inactiveFilters.map { fil -> "-type:${fil.value}" }
|
||||
inactiveFilters.size == 5 -> listOf("type:${activeFilter[0].value}")
|
||||
else -> listOf("type: none")
|
||||
}
|
||||
}
|
||||
|
||||
is TextFilter -> {
|
||||
if (it.state.isNotEmpty()) {
|
||||
terms += it.state.split(",").map { tag ->
|
||||
val trimmed = tag.trim()
|
||||
(if (trimmed.startsWith("-")) "-" else "") + it.type + ":" + trimmed.lowercase().removePrefix("-")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
val positiveTerms = LinkedList<String>()
|
||||
val negativeTerms = LinkedList<String>()
|
||||
@ -151,32 +182,32 @@ class Hitomi(
|
||||
val positiveResults = positiveTerms.map {
|
||||
async {
|
||||
runCatching {
|
||||
getGalleryIDsForQuery(it, language, order)
|
||||
}.getOrDefault(emptySet())
|
||||
getGalleryIDsForQuery(it, language)
|
||||
}.getOrDefault(ArrayList())
|
||||
}
|
||||
}
|
||||
|
||||
val negativeResults = negativeTerms.map {
|
||||
async {
|
||||
runCatching {
|
||||
getGalleryIDsForQuery(it, language, order)
|
||||
}.getOrDefault(emptySet())
|
||||
getGalleryIDsForQuery(it, language)
|
||||
}.getOrDefault(ArrayList())
|
||||
}
|
||||
}
|
||||
|
||||
val results = when {
|
||||
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(order.first, order.second, language)
|
||||
else -> emptySet()
|
||||
}.toMutableSet()
|
||||
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(sortBy.first, sortBy.second, language)
|
||||
else -> ArrayList()
|
||||
}
|
||||
|
||||
fun filterPositive(newResults: Set<Int>) {
|
||||
fun filterPositive(newResults: Collection<Int>) {
|
||||
when {
|
||||
results.isEmpty() -> results.addAll(newResults)
|
||||
else -> results.retainAll(newResults)
|
||||
}
|
||||
}
|
||||
|
||||
fun filterNegative(newResults: Set<Int>) {
|
||||
fun filterNegative(newResults: Collection<Int>) {
|
||||
results.removeAll(newResults)
|
||||
}
|
||||
|
||||
@ -190,15 +221,14 @@ class Hitomi(
|
||||
filterNegative(it.await())
|
||||
}
|
||||
|
||||
results
|
||||
if (random) results.shuffled() else results
|
||||
}
|
||||
|
||||
// search.js
|
||||
private suspend fun getGalleryIDsForQuery(
|
||||
query: String,
|
||||
language: String = "all",
|
||||
order: OrderType,
|
||||
): Set<Int> {
|
||||
): MutableList<Int> {
|
||||
query.replace("_", " ").let {
|
||||
if (it.indexOf(':') > -1) {
|
||||
val sides = it.split(":")
|
||||
@ -220,32 +250,18 @@ class Hitomi(
|
||||
}
|
||||
}
|
||||
|
||||
if (area != null) {
|
||||
if (order.first != null) {
|
||||
area = "$area/${order.first}"
|
||||
if (tag.isBlank()) {
|
||||
tag = order.second
|
||||
} else {
|
||||
area = "$area/${order.second}"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
area = order.first
|
||||
tag = order.second
|
||||
}
|
||||
|
||||
return getGalleryIDsFromNozomi(area, tag, lang)
|
||||
}
|
||||
|
||||
val key = hashTerm(it)
|
||||
val node = getGalleryNodeAtAddress(0)
|
||||
val data = bSearch(key, node) ?: return emptySet()
|
||||
val data = bSearch(key, node) ?: return ArrayList()
|
||||
|
||||
return getGalleryIDsFromData(data)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGalleryIDsFromData(data: Pair<Long, Int>): Set<Int> {
|
||||
private suspend fun getGalleryIDsFromData(data: Pair<Long, Int>): MutableList<Int> {
|
||||
val url = "$ltnUrl/galleriesindex/galleries.$galleriesIndexVersion.data"
|
||||
val (offset, length) = data
|
||||
require(length in 1..100000000) {
|
||||
@ -254,7 +270,7 @@ class Hitomi(
|
||||
|
||||
val inbuf = getRangedResponse(url, offset.until(offset + length))
|
||||
|
||||
val galleryIDs = mutableSetOf<Int>()
|
||||
val galleryIDs = mutableListOf<Int>()
|
||||
|
||||
val buffer =
|
||||
ByteBuffer
|
||||
@ -343,14 +359,14 @@ class Hitomi(
|
||||
tag: String,
|
||||
language: String,
|
||||
range: LongRange? = null,
|
||||
): Set<Int> {
|
||||
): MutableList<Int> {
|
||||
val nozomiAddress = when (area) {
|
||||
null -> "$ltnUrl/$tag-$language.nozomi"
|
||||
else -> "$ltnUrl/$area/$tag-$language.nozomi"
|
||||
}
|
||||
|
||||
val bytes = getRangedResponse(nozomiAddress, range)
|
||||
val nozomi = mutableSetOf<Int>()
|
||||
val nozomi = mutableListOf<Int>()
|
||||
|
||||
val arrayBuffer = ByteBuffer
|
||||
.wrap(bytes)
|
||||
@ -459,14 +475,15 @@ class Hitomi(
|
||||
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
|
||||
}
|
||||
description = buildString {
|
||||
characters?.joinToString { it.formatted }?.let {
|
||||
append("Characters: ", it, "\n")
|
||||
}
|
||||
parodys?.joinToString { it.formatted }?.let {
|
||||
append("Parodies: ", it, "\n")
|
||||
append("Series: ", it, "\n")
|
||||
}
|
||||
characters?.joinToString { it.formatted }?.let {
|
||||
append("Characters: ", it, "\n\n")
|
||||
}
|
||||
append("Type: ", type, "\n")
|
||||
append("Pages: ", files.size, "\n")
|
||||
append("Language: ", language)
|
||||
language?.let { append("Language: ", language) }
|
||||
}
|
||||
status = SManga.COMPLETED
|
||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||
@ -620,7 +637,6 @@ class Hitomi(
|
||||
|
||||
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = PREF_TAG_GENDER_ICON
|
||||
@ -634,13 +650,11 @@ class Hitomi(
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
|
||||
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
companion object {
|
||||
private const val PREF_TAG_GENDER_ICON = "pref_tag_gender_icon"
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ data class Gallery(
|
||||
val galleryurl: String,
|
||||
val title: String,
|
||||
val date: String,
|
||||
val type: String,
|
||||
val language: String,
|
||||
val type: String?,
|
||||
val language: String?,
|
||||
val tags: List<Tag>?,
|
||||
val artists: List<Artist>?,
|
||||
val groups: List<Group>?,
|
||||
|
Loading…
Reference in New Issue
Block a user