Akuma: Add Filters, Add Factory for specific language, Improve manga description and tags (#3351)

* 1. Add language for manga description
2. Add a custom preference to choice if show gender as text or icon

* fix code style

* revert extra formatting

* change preference into switch

* remove unused import, increase extVersionCode

* Add filter, split into specific languages

* Apply suggestions from code review

* Change Filter style, Add Filter, Add exclude syntax

* Change Code Style

* Remove RateLimit Preference, Remove Syntax Filter

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
ZIDOUZI 2024-06-03 18:40:52 +08:00 committed by GitHub
parent bfef7faafb
commit c47b9d852d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 194 additions and 17 deletions

View File

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

View File

@ -1,8 +1,14 @@
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
@ -20,16 +26,19 @@ 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
class Akuma : ParsedHttpSource() { class Akuma(
override val lang: String,
private val akumaLang: String,
) : ConfigurableSource, ParsedHttpSource() {
override val name = "Akuma" override val name = "Akuma"
override val baseUrl = "https://akuma.moe" override val baseUrl = "https://akuma.moe"
override val lang = "all"
override val supportsLatest = false override val supportsLatest = false
private var nextHash: String? = null private var nextHash: String? = null
@ -38,6 +47,12 @@ class Akuma : ParsedHttpSource() {
private val ddosGuardIntercept = DDosGuardInterceptor(network.client) private val ddosGuardIntercept = DDosGuardInterceptor(network.client)
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
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)
@ -102,12 +117,19 @@ class Akuma : ParsedHttpSource() {
.add("view", "3") .add("view", "3")
.build() .build()
return if (page == 1) { val url = baseUrl.toHttpUrlOrNull()!!.newBuilder()
if (page == 1) {
nextHash = null nextHash = null
POST(baseUrl, headers, payload)
} else { } else {
POST("$baseUrl/?cursor=$nextHash", headers, payload) url.addQueryParameter("cursor", nextHash)
} }
if (lang != "all") {
// append like `q=language:english$`
url.addQueryParameter("q", "language:$akumaLang$")
}
return POST(url.toString(), headers, payload)
} }
override fun popularMangaSelector() = ".post-loop li" override fun popularMangaSelector() = ".post-loop li"
@ -154,8 +176,39 @@ class Akuma : ParsedHttpSource() {
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 {
append(query)
if (lang != "all") {
append(" language:", akumaLang, "$")
}
filters.filterIsInstance<TextFilter>().forEach { filter ->
if (filter.state.isBlank()) return@forEach
filter.state.split(",").forEach {
// append like `a:"eye-covering bang"$`
if (it.startsWith("-")) {
append(" -", filter.identifier, ":", it.trim().substring(1))
} else {
append(" ", filter.identifier, ":", it.trim())
}
}
}
filters.filterIsInstance<OptionFilter>().firstOrNull()?.let {
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()
.addQueryParameter("q", query.trim()) .setQueryParameter("q", finalQuery)
.build() .build()
return request.newBuilder() return request.newBuilder()
@ -168,14 +221,45 @@ class Akuma : ParsedHttpSource() {
override fun searchMangaParse(response: Response) = popularMangaParse(response) override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun mangaDetailsParse(document: Document) = SManga.create().apply { override fun mangaDetailsParse(document: Document) = with(document) {
title = document.select(".entry-title").text() SManga.create().apply {
thumbnail_url = document.select(".img-thumbnail").attr("abs:src") title = select(".entry-title").text()
author = document.select("li.meta-data > span.artist + span.value").text() thumbnail_url = select(".img-thumbnail").attr("abs:src")
genre = document.select(".info-list a").joinToString { it.text() }
description = document.select(".pages span.value").text() + " Pages" author = select(".group~.value").eachText().joinToString()
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE artist = select(".artist~.value").eachText().joinToString()
status = SManga.COMPLETED
val characters = select(".character~.value").eachText()
val parodies = select(".parody~.value").eachText()
val males = select(".male~.value")
.map { it.text() + if (iconified) "" else " (male)" }
val females = select(".female~.value")
.map { it.text() + if (iconified) "" else " (female)" }
// show all in tags for quickly searching
genre = (characters + parodies + males + females).joinToString()
description = buildString {
append(
"Full English and Japanese title: \n",
select(".entry-title").text(),
"\n",
select(".entry-title+span").text(),
"\n\n",
)
// translated should show up in the description
append("Language: ", select(".language~.value").text(), "\n")
append("Pages: ", select(".pages .value").text(), "\n")
append("Upload Date: ", select(".date .value>time").text(), "\n")
append("Categories: ", selectFirst(".info-list .value")?.text() ?: "Unknown", "\n\n")
// show followings for easy to reference
append("Parodies: ", parodies.joinToString(), "\n")
append("Characters: ", characters.joinToString(), "\n")
}
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
status = SManga.UNKNOWN
}
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
@ -208,8 +292,66 @@ class Akuma : ParsedHttpSource() {
return document.select(".entry-content img").attr("abs:src") return document.select(".entry-content img").attr("abs:src")
} }
override fun getFilterList(): FilterList = FilterList(
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

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.extension.all.akuma
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class AkumaFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
Akuma("all", "all"),
Akuma("en", "english"),
Akuma("id", "indonesian"),
Akuma("jv", "javanese"),
Akuma("ca", "catalan"),
Akuma("ceb", "cebuano"),
Akuma("cs", "czech"),
Akuma("da", "danish"),
Akuma("de", "german"),
Akuma("et", "estonian"),
Akuma("es", "spanish"),
Akuma("eo", "esperanto"),
Akuma("fr", "french"),
Akuma("it", "italian"),
Akuma("hi", "hindi"),
Akuma("hu", "hungarian"),
Akuma("pl", "polish"),
Akuma("pt", "portuguese"),
Akuma("vi", "vietnamese"),
Akuma("tr", "turkish"),
Akuma("ru", "russian"),
Akuma("uk", "ukrainian"),
Akuma("ar", "arabic"),
Akuma("ko", "korean"),
Akuma("zh", "chinese"),
Akuma("ja", "japanese"),
)
}