Komga: add search for books (#8102)

* Komga: add search for books

* clean up unused code

* add String.isFromBook() method

* fix lint error
This commit is contained in:
Uranus 2025-03-23 23:03:55 +08:00 committed by Draff
parent 91b33530ac
commit 72c0ecc64f
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
4 changed files with 52 additions and 6 deletions

View File

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

View File

@ -130,6 +130,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
val type = when { val type = when {
collectionId != null -> "collections/$collectionId/series" collectionId != null -> "collections/$collectionId/series"
filters.find { it is TypeSelect }?.state == 1 -> "readlists" filters.find { it is TypeSelect }?.state == 1 -> "readlists"
filters.find { it is TypeSelect }?.state == 2 -> "books"
else -> "series" else -> "series"
} }
@ -171,6 +172,8 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
private fun processSeriesPage(response: Response, baseUrl: String): MangasPage { private fun processSeriesPage(response: Response, baseUrl: String): MangasPage {
val data = if (response.isFromReadList()) { val data = if (response.isFromReadList()) {
response.parseAs<PageWrapperDto<ReadListDto>>() response.parseAs<PageWrapperDto<ReadListDto>>()
} else if (response.isFromBook()) {
response.parseAs<PageWrapperDto<BookDto>>()
} else { } else {
response.parseAs<PageWrapperDto<SeriesDto>>() response.parseAs<PageWrapperDto<SeriesDto>>()
} }
@ -185,6 +188,8 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
return if (response.isFromReadList()) { return if (response.isFromReadList()) {
response.parseAs<ReadListDto>().toSManga(baseUrl) response.parseAs<ReadListDto>().toSManga(baseUrl)
} else if (response.isFromBook()) {
response.parseAs<BookDto>().toSManga(baseUrl)
} else { } else {
response.parseAs<SeriesDto>().toSManga(baseUrl) response.parseAs<SeriesDto>().toSManga(baseUrl)
} }
@ -195,10 +200,30 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book") override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book")
override fun chapterListRequest(manga: SManga): Request = override fun chapterListRequest(manga: SManga): Request = when {
GET("${manga.url}/books?unpaged=true&media_status=READY&deleted=false", headers) manga.url.isFromBook() -> GET("${manga.url}?unpaged=true&media_status=READY&deleted=false", headers)
else -> GET("${manga.url}/books?unpaged=true&media_status=READY&deleted=false", headers)
}
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
if (response.isFromBook()) {
val book = response.parseAs<BookDto>()
return listOf(
SChapter.create().apply {
chapter_number = 1F
url = "$baseUrl/api/v1/books/${book.id}"
name = book.getChapterName(chapterNameTemplate, isFromReadList = true)
scanlator = book.metadata.authors
.filter { it.role == "translator" }
.joinToString { it.name }
date_upload = when {
book.metadata.releaseDate != null -> parseDate(book.metadata.releaseDate)
book.created != null -> parseDateTime(book.created)
else -> parseDateTime(book.fileLastModified)
}
},
)
}
val page = response.parseAs<PageWrapperDto<BookDto>>().content val page = response.parseAs<PageWrapperDto<BookDto>>().content
val isFromReadList = response.isFromReadList() val isFromReadList = response.isFromReadList()
val chapterNameTemplate = chapterNameTemplate val chapterNameTemplate = chapterNameTemplate
@ -464,7 +489,13 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
} }
} }
fun Response.isFromReadList() = request.url.toString().contains("/api/v1/readlists") fun String.isFromReadList() = contains("/api/v1/readlists")
fun String.isFromBook() = contains("/api/v1/books")
fun Response.isFromReadList() = request.url.toString().isFromReadList()
fun Response.isFromBook() = request.url.toString().isFromBook()
private inline fun <reified T> Response.parseAs(): T = private inline fun <reified T> Response.parseAs(): T =
json.decodeFromString(body.string()) json.decodeFromString(body.string())
@ -477,6 +508,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere
internal const val TYPE_SERIES = "Series" internal const val TYPE_SERIES = "Series"
internal const val TYPE_READLISTS = "Read lists" internal const val TYPE_READLISTS = "Read lists"
internal const val TYPE_BOOKS = "Books"
} }
} }

View File

@ -14,13 +14,14 @@ internal class TypeSelect : Filter.Select<String>(
arrayOf( arrayOf(
Komga.TYPE_SERIES, Komga.TYPE_SERIES,
Komga.TYPE_READLISTS, Komga.TYPE_READLISTS,
Komga.TYPE_BOOKS,
), ),
) )
internal class SeriesSort(selection: Selection? = null) : Filter.Sort( internal class SeriesSort(selection: Selection? = null) : Filter.Sort(
"Sort", "Sort",
arrayOf("Relevance", "Alphabetically", "Date added", "Date updated", "Random"), arrayOf("Relevance", "Alphabetically", "Date added", "Date updated", "Random"),
selection ?: Selection(0, false), selection ?: Selection(0, true),
) )
internal class UnreadFilter : Filter.CheckBox("Unread", false), UriFilter { internal class UnreadFilter : Filter.CheckBox("Unread", false), UriFilter {

View File

@ -97,7 +97,7 @@ class BookDto(
val size: String, val size: String,
val media: MediaDto, val media: MediaDto,
val metadata: BookMetadataDto, val metadata: BookMetadataDto,
) { ) : ConvertibleToSManga {
fun getChapterName(template: String, isFromReadList: Boolean): String { fun getChapterName(template: String, isFromReadList: Boolean): String {
val values = hashMapOf( val values = hashMapOf(
"title" to metadata.title, "title" to metadata.title,
@ -119,6 +119,17 @@ class BookDto(
append(sub.replace(template)) append(sub.replace(template))
} }
} }
override fun toSManga(baseUrl: String) = SManga.create().apply {
title = metadata.title
url = "$baseUrl/api/v1/books/$id"
thumbnail_url = "$url/thumbnail"
status = SManga.UNKNOWN
genre = metadata.tags.distinct().joinToString(", ")
description = metadata.summary
author = metadata.authors.joinToString { it.name }
artist = author
}
} }
@Serializable @Serializable
@ -151,6 +162,8 @@ class BookMetadataDto(
val releaseDateLock: Boolean, val releaseDateLock: Boolean,
val authors: List<AuthorDto>, val authors: List<AuthorDto>,
val authorsLock: Boolean, val authorsLock: Boolean,
val tags: Set<String>,
val tagsLock: Boolean,
) )
@Serializable @Serializable