Akuma Tags Fix (#3390)

* Fix

- Removed "Pages" filter — They don't work
- Added "Other Tags" filter
- Added Filter limit warning
- Fixed problem on tags with spaces ( e.g. bxg brxxsts )

* Extension version: 2 -> 3

* typo fix

* Change line endings, make prefrence default

- Line endings: CRLF -> LF
- Iconified Tag Preference -> Iconified Tag is default

* Missing space

* Fix1

* Change

* Change2

* Change3

- Removed override for fetchChapterList and provide implementation in chapterListParse
- Filter out empty tags ( For example: user can put 2 commas together )

* Change4

Moved Date Format to Class val

* try catch for date parse

* Update Akuma.kt

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
KenjieDec 2024-06-06 18:52:56 +07:00 committed by Draff
parent 25c9212526
commit 254087d912
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
4 changed files with 110 additions and 112 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Akuma' extName = 'Akuma'
extClass = '.AkumaFactory' extClass = '.AkumaFactory'
extVersionCode = 2 extVersionCode = 3
isNsfw = true isNsfw = true
} }

View File

@ -1,14 +1,8 @@
package eu.kanade.tachiyomi.extension.all.akuma package eu.kanade.tachiyomi.extension.all.akuma
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@ -26,14 +20,16 @@ import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException import java.io.IOException
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class Akuma( class Akuma(
override val lang: String, override val lang: String,
private val akumaLang: String, private val akumaLang: String,
) : ConfigurableSource, ParsedHttpSource() { ) : ParsedHttpSource() {
override val name = "Akuma" override val name = "Akuma"
@ -47,12 +43,9 @@ class Akuma(
private val ddosGuardIntercept = DDosGuardInterceptor(network.client) private val ddosGuardIntercept = DDosGuardInterceptor(network.client)
private val preferences: SharedPreferences by lazy { private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH).apply {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) timeZone = TimeZone.getTimeZone("UTC")
} }
private var iconified = preferences.getBoolean(PREF_TAG_GENDER_ICON, false)
override val client: OkHttpClient = network.client.newBuilder() override val client: OkHttpClient = network.client.newBuilder()
.addInterceptor(ddosGuardIntercept) .addInterceptor(ddosGuardIntercept)
.addInterceptor(::tokenInterceptor) .addInterceptor(::tokenInterceptor)
@ -138,6 +131,10 @@ class Akuma(
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
if (document.text().contains("Max keywords of 3 exceeded.")) {
throw Exception("Login required for more than 3 filters")
} else if (document.text().contains("Max keywords of 8 exceeded.")) throw Exception("Only max of 8 filters are allowed")
val mangas = document.select(popularMangaSelector()).map { element -> val mangas = document.select(popularMangaSelector()).map { element ->
popularMangaFromElement(element) popularMangaFromElement(element)
} }
@ -176,39 +173,39 @@ class Akuma(
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val request = popularMangaRequest(page) val request = popularMangaRequest(page)
val finalQuery = buildString { val finalQuery: MutableList<String> = mutableListOf(query)
append(query)
if (lang != "all") { if (lang != "all") {
append(" language:", akumaLang, "$") finalQuery.add("language: $akumaLang$")
} }
filters.filterIsInstance<TextFilter>().forEach { filter -> filters.forEach { filter ->
if (filter.state.isBlank()) return@forEach when (filter) {
filter.state.split(",").forEach { is TextFilter -> {
// append like `a:"eye-covering bang"$` if (filter.state.isNotEmpty()) {
if (it.startsWith("-")) { finalQuery.addAll(
append(" -", filter.identifier, ":", it.trim().substring(1)) filter.state.split(",").filter { it.isNotBlank() }.map {
} else { (if (it.trim().startsWith("-")) "-" else "") + "${filter.tag}:\"${it.trim().replace("-", "")}\""
append(" ", filter.identifier, ":", it.trim()) },
)
}
}
is OptionFilter -> {
if (filter.state > 0) finalQuery.add("opt:${filter.getValue()}")
}
is CategoryFilter -> {
filter.state.forEach {
when {
it.isIncluded() -> finalQuery.add("category:\"${it.name}\"")
it.isExcluded() -> finalQuery.add("-category:\"${it.name}\"")
} }
} }
} }
filters.filterIsInstance<OptionFilter>().firstOrNull()?.let { else -> {}
val filter = options[it.state].second
if (filter.isNotBlank()) {
append(" opt:", filter)
}
}
filters.filterIsInstance<CategoryFilter>().firstOrNull()?.state?.forEach {
if (it.isIncluded()) {
append(" category:\"", it.name, "\"$")
} else if (it.isExcluded()) {
append(" -category:\"", it.name, "\"$")
}
} }
} }
val url = request.url.newBuilder() val url = request.url.newBuilder()
.setQueryParameter("q", finalQuery) .setQueryParameter("q", finalQuery.joinToString(" "))
.build() .build()
return request.newBuilder() return request.newBuilder()
@ -232,12 +229,14 @@ class Akuma(
val characters = select(".character~.value").eachText() val characters = select(".character~.value").eachText()
val parodies = select(".parody~.value").eachText() val parodies = select(".parody~.value").eachText()
val males = select(".male~.value") val males = select(".male~.value")
.map { it.text() + if (iconified) "" else " (male)" } .map { "${it.text()}" }
val females = select(".female~.value") val females = select(".female~.value")
.map { it.text() + if (iconified) "" else " (female)" } .map { "${it.text()}" }
val others = select(".other~.value")
.map { "${it.text()}" }
// show all in tags for quickly searching // show all in tags for quickly searching
genre = (characters + parodies + males + females).joinToString() genre = (males + females + others).joinToString()
description = buildString { description = buildString {
append( append(
"Full English and Japanese title: \n", "Full English and Japanese title: \n",
@ -248,28 +247,33 @@ class Akuma(
) )
// translated should show up in the description // translated should show up in the description
append("Language: ", select(".language~.value").text(), "\n") append("Language: ", select(".language~.value").eachText().joinToString(), "\n")
append("Pages: ", select(".pages .value").text(), "\n") append("Pages: ", select(".pages .value").text(), "\n")
append("Upload Date: ", select(".date .value>time").text(), "\n") append("Upload Date: ", select(".date .value>time").text().replace(" ", ", ") + " UTC", "\n")
append("Categories: ", selectFirst(".info-list .value")?.text() ?: "Unknown", "\n\n") append("Categories: ", selectFirst(".info-list .value")?.text() ?: "Unknown", "\n\n")
// show followings for easy to reference // show followings for easy to reference
append("Parodies: ", parodies.joinToString(), "\n") parodies.takeIf { it.isNotEmpty() }?.let { append("Parodies: ", parodies.joinToString(), "\n") }
append("Characters: ", characters.joinToString(), "\n") characters.takeIf { it.isNotEmpty() }?.let { append("Characters: ", characters.joinToString(), "\n") }
} }
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
status = SManga.UNKNOWN status = SManga.UNKNOWN
} }
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun chapterListParse(response: Response): List<SChapter> {
return Observable.just( val document = response.asJsoup()
listOf(
return listOf(
SChapter.create().apply { SChapter.create().apply {
url = "${manga.url}/1" url = "${response.request.url}/1"
name = "Chapter" name = "Chapter"
date_upload = try {
dateFormat.parse(document.select(".date .value>time").text())!!.time
} catch (_: ParseException) {
0L
}
}, },
),
) )
} }
@ -292,66 +296,10 @@ class Akuma(
return document.select(".entry-content img").attr("abs:src") return document.select(".entry-content img").attr("abs:src")
} }
override fun getFilterList(): FilterList = FilterList( override fun getFilterList(): FilterList = getFilters()
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
TextFilter("Female Tags", "female"),
TextFilter("Male Tags", "male"),
CategoryFilter(),
TextFilter("Groups", "group"),
TextFilter("Artists", "artist"),
TextFilter("Parody", "parody"),
TextFilter("Characters", "character"),
Filter.Header("Filter by pages, for example: (>20)"),
TextFilter("Pages", "pages"),
Filter.Header("Search in favorites, read, or commented"),
OptionFilter(),
)
private class CategoryFilter : Filter.Group<CategoryFilter.TagTriState>("Categories", values()) {
class TagTriState(name: String) : TriState(name)
private companion object {
fun values() = listOf(
TagTriState("doujinshi"),
TagTriState("manga"),
TagTriState("artist cg"),
TagTriState("game cg"),
TagTriState("west"),
TagTriState("non-h"),
TagTriState("gallery"),
TagTriState("cosplay"),
TagTriState("asian pron"),
TagTriState("misc"),
)
}
}
private class TextFilter(placeholder: String, val identifier: String) : Filter.Text(placeholder)
private class OptionFilter :
Filter.Select<String>("Options", options.map { it.first }.toTypedArray())
override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
key = PREF_TAG_GENDER_ICON
title = "Show gender as text or icon in tags (requires refresh)"
summaryOff = "Show gender as text"
summaryOn = "Show gender as icon"
setOnPreferenceChangeListener { _, newValue ->
iconified = newValue == true
true
}
}.also(screen::addPreference)
}
companion object { companion object {
const val PREFIX_ID = "id:" const val PREFIX_ID = "id:"
private const val PREF_TAG_GENDER_ICON = "pref_tag_gender_icon"
private val options = listOf(
"None" to "",
"Favorited only" to "favorited",
"Read only" to "read",
"Commented only" to "commented",
)
} }
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()

View File

@ -21,6 +21,7 @@ class AkumaFactory : SourceFactory {
Akuma("it", "italian"), Akuma("it", "italian"),
Akuma("hi", "hindi"), Akuma("hi", "hindi"),
Akuma("hu", "hungarian"), Akuma("hu", "hungarian"),
Akuma("nl", "dutch"),
Akuma("pl", "polish"), Akuma("pl", "polish"),
Akuma("pt", "portuguese"), Akuma("pt", "portuguese"),
Akuma("vi", "vietnamese"), Akuma("vi", "vietnamese"),

View File

@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.extension.all.akuma
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
fun getFilters(): FilterList {
return FilterList(
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
TextFilter("Female Tags", "female"),
TextFilter("Male Tags", "male"),
TextFilter("Other Tags", "other"),
CategoryFilter(),
TextFilter("Groups", "group"),
TextFilter("Artists", "artist"),
TextFilter("Parody", "parody"),
TextFilter("Characters", "character"),
Filter.Separator(),
Filter.Header("Search in favorites, read, or commented"),
OptionFilter(),
)
}
internal class TextFilter(name: String, val tag: String) : Filter.Text(name)
internal class OptionFilter(val value: List<Pair<String, String>> = options) : Filter.Select<String>("Options", options.map { it.first }.toTypedArray()) {
fun getValue() = options[state].second
}
internal open class TagTriState(name: String) : Filter.TriState(name)
internal class CategoryFilter() :
Filter.Group<Filter.TriState>("Categories", categoryList.map { TagTriState(it) })
private val categoryList = listOf(
"Doujinshi",
"Manga",
"Image Set",
"Artist CG",
"Game CG",
"Western",
"Non-H",
"Cosplay",
"Misc",
)
private val options = listOf(
"None" to "",
"Favorited only" to "favorited",
"Read only" to "read",
"Commented only" to "commented",
)