Komga: Filter out EPUB books, fix chapter timestamps, allow setting default libraries, rename None sort to Relevance (#1282)

* Komga: Filter out EPUB books, fix chapter timestamps

* Add default library setting

* Make some settings not require a restart

* Don't use fixed enums

* Rename None sort to Relevance (#1284)

* make popular default to alphabetical sort
This commit is contained in:
beerpsi 2024-02-16 18:52:27 +07:00 committed by GitHub
parent fcd38a3ed6
commit e571f95688
5 changed files with 94 additions and 40 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Komga' extName = 'Komga'
extClass = '.KomgaFactory' extClass = '.KomgaFactory'
extVersionCode = 54 extVersionCode = 55
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -7,6 +7,7 @@ import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.AppInfo import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.extension.all.komga.KomgaUtils.addEditTextPreference import eu.kanade.tachiyomi.extension.all.komga.KomgaUtils.addEditTextPreference
@ -50,6 +51,12 @@ import kotlin.concurrent.write
open class Komga(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() { open class Komga(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() {
internal val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val displayName by lazy { preferences.getString(PREF_DISPLAY_NAME, "")!! }
override val name by lazy { "Komga${displayName.ifBlank { suffix }.let { if (it.isNotBlank()) " ($it)" else "" }}" } override val name by lazy { "Komga${displayName.ifBlank { suffix }.let { if (it.isNotBlank()) " ($it)" else "" }}" }
override val lang = "all" override val lang = "all"
@ -65,14 +72,13 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
} }
internal val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val displayName by lazy { preferences.getString(PREF_DISPLAY_NAME, "")!! }
private val username by lazy { preferences.getString(PREF_USERNAME, "")!! } private val username by lazy { preferences.getString(PREF_USERNAME, "")!! }
private val password by lazy { preferences.getString(PREF_PASSWORD, "")!! } private val password by lazy { preferences.getString(PREF_PASSWORD, "")!! }
private val defaultLibraries
get() = preferences.getStringSet(PREF_DEFAULT_LIBRARIES, emptySet())!!
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.set("User-Agent", "TachiyomiKomga/${AppInfo.getVersionName()}") .set("User-Agent", "TachiyomiKomga/${AppInfo.getVersionName()}")
@ -94,7 +100,9 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
searchMangaRequest( searchMangaRequest(
page, page,
"", "",
FilterList(SeriesSort()), FilterList(
SeriesSort(Filter.Sort.Selection(1, true)),
),
) )
override fun popularMangaParse(response: Response): MangasPage = override fun popularMangaParse(response: Response): MangasPage =
@ -104,7 +112,9 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
searchMangaRequest( searchMangaRequest(
page, page,
"", "",
FilterList(SeriesSort(Filter.Sort.Selection(3, false))), FilterList(
SeriesSort(Filter.Sort.Selection(3, false)),
),
) )
override fun latestUpdatesParse(response: Response): MangasPage = override fun latestUpdatesParse(response: Response): MangasPage =
@ -124,14 +134,20 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
} }
val url = "$baseUrl/api/v1/$type?search=$query&page=${page - 1}&deleted=false".toHttpUrl().newBuilder() val url = "$baseUrl/api/v1/$type?search=$query&page=${page - 1}&deleted=false".toHttpUrl().newBuilder()
val filterList = filters.ifEmpty { getFilterList() }
filters.forEach { filter -> if (filterList.filterIsInstance<LibraryFilter>().isEmpty()) {
url.addQueryParameter("library_id", defaultLibraries.joinToString(","))
}
filterList.forEach { filter ->
when (filter) { when (filter) {
is UriFilter -> filter.addToUri(url) is UriFilter -> filter.addToUri(url)
is Filter.Sort -> { is Filter.Sort -> {
val state = filter.state ?: return@forEach val state = filter.state ?: return@forEach
val sortCriteria = when (state.index) { val sortCriteria = when (state.index) {
0 -> "relevance"
1 -> if (type == "series") "metadata.titleSort" else "name" 1 -> if (type == "series") "metadata.titleSort" else "name"
2 -> "createdDate" 2 -> "createdDate"
3 -> "lastModifiedDate" 3 -> "lastModifiedDate"
@ -162,9 +178,8 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
} }
} }
private val chapterNameTemplate by lazy { private val chapterNameTemplate
preferences.getString(PREF_CHAPTER_NAME_TEMPLATE, PREF_CHAPTER_NAME_TEMPLATE_DEFAULT)!! get() = preferences.getString(PREF_CHAPTER_NAME_TEMPLATE, PREF_CHAPTER_NAME_TEMPLATE_DEFAULT)!!
}
override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book") override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book")
@ -174,18 +189,23 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val page = response.parseAs<PageWrapperDto<BookDto>>().content val page = response.parseAs<PageWrapperDto<BookDto>>().content
val isFromReadList = response.isFromReadList() val isFromReadList = response.isFromReadList()
val r = page.mapIndexed { index, book -> val chapterNameTemplate = chapterNameTemplate
return page
.filter {
it.media.mediaProfile != "EPUB" || it.media.epubDivinaCompatible
}
.mapIndexed { index, book ->
SChapter.create().apply { SChapter.create().apply {
chapter_number = if (!isFromReadList) book.metadata.numberSort else index + 1F chapter_number = if (!isFromReadList) book.metadata.numberSort else index + 1F
url = "$baseUrl/api/v1/books/${book.id}" url = "$baseUrl/api/v1/books/${book.id}"
name = KomgaUtils.formatChapterName(book, chapterNameTemplate, isFromReadList) name = KomgaUtils.formatChapterName(book, chapterNameTemplate, isFromReadList)
scanlator = book.metadata.authors.filter { it.role == "translator" }.joinToString { it.name } scanlator = book.metadata.authors.filter { it.role == "translator" }.joinToString { it.name }
date_upload = book.metadata.releaseDate?.let { KomgaUtils.parseDate(it) } date_upload = book.metadata.releaseDate?.let { KomgaUtils.parseDate(it) }
?: KomgaUtils.parseDateTime(book.fileLastModified) ?: KomgaUtils.parseDateTime(book.lastModified)
} }
} }
.sortedByDescending { it.chapter_number }
return r.sortedByDescending { it.chapter_number }
} }
override fun pageListRequest(chapter: SChapter) = GET("${chapter.url}/pages") override fun pageListRequest(chapter: SChapter) = GET("${chapter.url}/pages")
@ -221,11 +241,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
} }
}, },
), ),
UriMultiSelectFilter( LibraryFilter(libraries, defaultLibraries),
"Libraries",
"library_id",
libraries.map { UriMultiSelectOption(it.name, it.id) },
),
UriMultiSelectFilter( UriMultiSelectFilter(
"Status", "Status",
"status", "status",
@ -283,6 +299,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
default = suffix, default = suffix,
summary = displayName.ifBlank { "Here you can change the source displayed suffix" }, summary = displayName.ifBlank { "Here you can change the source displayed suffix" },
key = PREF_DISPLAY_NAME, key = PREF_DISPLAY_NAME,
restartRequired = true,
) )
screen.addEditTextPreference( screen.addEditTextPreference(
title = "Address", title = "Address",
@ -292,12 +309,14 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
validate = { it.toHttpUrlOrNull() != null }, validate = { it.toHttpUrlOrNull() != null },
validationMessage = "The URL is invalid or malformed", validationMessage = "The URL is invalid or malformed",
key = PREF_ADDRESS, key = PREF_ADDRESS,
restartRequired = true,
) )
screen.addEditTextPreference( screen.addEditTextPreference(
title = "Username", title = "Username",
default = "", default = "",
summary = username.ifBlank { "The user account email" }, summary = username.ifBlank { "The user account email" },
key = PREF_USERNAME, key = PREF_USERNAME,
restartRequired = true,
) )
screen.addEditTextPreference( screen.addEditTextPreference(
title = "Password", title = "Password",
@ -305,8 +324,24 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
summary = if (password.isBlank()) "The user account password" else "*".repeat(password.length), summary = if (password.isBlank()) "The user account password" else "*".repeat(password.length),
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD, inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD,
key = PREF_PASSWORD, key = PREF_PASSWORD,
restartRequired = true,
) )
MultiSelectListPreference(screen.context).apply {
key = PREF_DEFAULT_LIBRARIES
title = "Default libraries"
summary = buildString {
append("Show content from selected libraries by default.")
if (libraries.isEmpty()) {
append(" Browse the source to load available options.")
}
}
entries = libraries.map { it.name }.toTypedArray()
entryValues = libraries.map { it.id }.toTypedArray()
setDefaultValue(emptySet<String>())
}.also(screen::addPreference)
EditTextPreference(screen.context).apply { EditTextPreference(screen.context).apply {
key = PREF_CHAPTER_NAME_TEMPLATE key = PREF_CHAPTER_NAME_TEMPLATE
title = "Chapter title format" title = "Chapter title format"
@ -323,10 +358,6 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
""".trimMargin() """.trimMargin()
setDefaultValue(PREF_CHAPTER_NAME_TEMPLATE_DEFAULT) setDefaultValue(PREF_CHAPTER_NAME_TEMPLATE_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
Toast.makeText(screen.context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show()
true
}
}.also(screen::addPreference) }.also(screen::addPreference)
} }
@ -396,6 +427,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
private const val PREF_ADDRESS = "Address" private const val PREF_ADDRESS = "Address"
private const val PREF_USERNAME = "Username" private const val PREF_USERNAME = "Username"
private const val PREF_PASSWORD = "Password" private const val PREF_PASSWORD = "Password"
private const val PREF_DEFAULT_LIBRARIES = "Default libraries"
private const val PREF_CHAPTER_NAME_TEMPLATE = "Chapter name template" private const val PREF_CHAPTER_NAME_TEMPLATE = "Chapter name template"
private const val PREF_CHAPTER_NAME_TEMPLATE_DEFAULT = "{number} - {title} ({size})" private const val PREF_CHAPTER_NAME_TEMPLATE_DEFAULT = "{number} - {title} ({size})"

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.komga package eu.kanade.tachiyomi.extension.all.komga
import eu.kanade.tachiyomi.extension.all.komga.dto.AuthorDto import eu.kanade.tachiyomi.extension.all.komga.dto.AuthorDto
import eu.kanade.tachiyomi.extension.all.komga.dto.LibraryDto
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -18,8 +19,8 @@ internal class TypeSelect : Filter.Select<String>(
internal class SeriesSort(selection: Selection? = null) : Filter.Sort( internal class SeriesSort(selection: Selection? = null) : Filter.Sort(
"Sort", "Sort",
arrayOf("None", "Alphabetically", "Date added", "Date updated"), arrayOf("Relevance", "Alphabetically", "Date added", "Date updated"),
selection ?: Selection(0, true), selection ?: Selection(0, false),
) )
internal class UnreadFilter : Filter.CheckBox("Unread", false), UriFilter { internal class UnreadFilter : Filter.CheckBox("Unread", false), UriFilter {
@ -53,9 +54,22 @@ internal class ReadFilter : Filter.CheckBox("Read", false), UriFilter {
} }
} }
internal class LibraryFilter(
libraries: List<LibraryDto>,
defaultLibraries: Set<String>,
) : UriMultiSelectFilter(
"Libraries",
"library_id",
libraries.map {
UriMultiSelectOption(it.name, it.id).apply {
state = defaultLibraries.contains(it.id)
}
},
)
internal class UriMultiSelectOption(name: String, val id: String = name) : Filter.CheckBox(name, false) internal class UriMultiSelectOption(name: String, val id: String = name) : Filter.CheckBox(name, false)
internal class UriMultiSelectFilter( internal open class UriMultiSelectFilter(
name: String, name: String,
private val param: String, private val param: String,
genres: List<UriMultiSelectOption>, genres: List<UriMultiSelectOption>,

View File

@ -98,9 +98,9 @@ internal object KomgaUtils {
} }
genre = (metadata.genres + metadata.tags + booksMetadata.tags).distinct().joinToString(", ") genre = (metadata.genres + metadata.tags + booksMetadata.tags).distinct().joinToString(", ")
description = metadata.summary.ifBlank { booksMetadata.summary } description = metadata.summary.ifBlank { booksMetadata.summary }
booksMetadata.authors.groupBy { it.role }.let { map -> booksMetadata.authors.groupBy({ it.role }, { it.name }).let { map ->
author = map["writer"]?.map { it.name }?.distinct()?.joinToString() author = map["writer"]?.distinct()?.joinToString()
artist = map["penciller"]?.map { it.name }?.distinct()?.joinToString() artist = map["penciller"]?.distinct()?.joinToString()
} }
} }
@ -121,6 +121,7 @@ internal object KomgaUtils {
validate: ((String) -> Boolean)? = null, validate: ((String) -> Boolean)? = null,
validationMessage: String? = null, validationMessage: String? = null,
key: String = title, key: String = title,
restartRequired: Boolean = false,
) { ) {
EditTextPreference(context).apply { EditTextPreference(context).apply {
this.key = key this.key = key
@ -159,8 +160,13 @@ internal object KomgaUtils {
setOnPreferenceChangeListener { _, _ -> setOnPreferenceChangeListener { _, _ ->
try { try {
val result = text.isBlank() || validate?.invoke(text) ?: true
if (restartRequired && result) {
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show()
text.isBlank() || validate?.invoke(text) ?: true }
result
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
false false

View File

@ -78,6 +78,8 @@ data class MediaDto(
val status: String, val status: String,
val mediaType: String, val mediaType: String,
val pagesCount: Int, val pagesCount: Int,
val mediaProfile: String = "DIVINA",
val epubDivinaCompatible: Boolean = false,
) )
@Serializable @Serializable