diff --git a/src/all/komga/AndroidManifest.xml b/src/all/komga/AndroidManifest.xml index 8072ee00d..395c1e1da 100644 --- a/src/all/komga/AndroidManifest.xml +++ b/src/all/komga/AndroidManifest.xml @@ -1,2 +1,7 @@ +<<<<<<< HEAD +======= + + +>>>>>>> d52b3e572 (Add Komga (#579)) diff --git a/src/all/komga/build.gradle b/src/all/komga/build.gradle index 42e1b089d..b6fdea5c8 100644 --- a/src/all/komga/build.gradle +++ b/src/all/komga/build.gradle @@ -1,3 +1,4 @@ +<<<<<<< HEAD ext { extName = 'Komga' extClass = '.KomgaFactory' @@ -5,3 +6,16 @@ ext { } apply from: "$rootDir/common.gradle" +======= +ext { + extName = 'Komga' + extClass = '.KomgaFactory' + extVersionCode = 51 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation("org.apache.commons:commons-text:1.11.0") +} +>>>>>>> d52b3e572 (Add Komga (#579)) diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt index 4f0adb59f..bfe641081 100644 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt @@ -9,6 +9,10 @@ import android.util.Log import android.widget.Button import android.widget.Toast import androidx.preference.EditTextPreference +<<<<<<< HEAD +======= +import androidx.preference.ListPreference +>>>>>>> d52b3e572 (Add Komga (#579)) import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.AppInfo import eu.kanade.tachiyomi.extension.all.komga.dto.AuthorDto @@ -20,8 +24,11 @@ import eu.kanade.tachiyomi.extension.all.komga.dto.PageWrapperDto import eu.kanade.tachiyomi.extension.all.komga.dto.ReadListDto import eu.kanade.tachiyomi.extension.all.komga.dto.SeriesDto import eu.kanade.tachiyomi.network.GET +<<<<<<< HEAD import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.await +======= +>>>>>>> d52b3e572 (Add Komga (#579)) import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.model.Filter @@ -35,12 +42,20 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.Credentials import okhttp3.Dns +<<<<<<< HEAD import okhttp3.Headers +======= +import okhttp3.HttpUrl.Companion.toHttpUrl +>>>>>>> d52b3e572 (Add Komga (#579)) import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +<<<<<<< HEAD import rx.Observable +======= +import org.apache.commons.text.StringSubstitutor +>>>>>>> d52b3e572 (Add Komga (#579)) import rx.Single import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt @@ -50,19 +65,84 @@ import java.security.MessageDigest import java.util.Locale open class Komga(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() { +<<<<<<< HEAD override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/v1/series?page=${page - 1}&deleted=false&sort=metadata.titleSort,asc", headers) +======= + + override val name by lazy { "Komga${displayName.ifBlank { suffix }.let { if (it.isNotBlank()) " ($it)" else "" }}" } + + override val lang = "all" + + override val baseUrl by lazy { preferences.getString(PREF_ADDRESS, "")!!.removeSuffix("/") } + + override val supportsLatest = true + + // keep the previous ID when lang was "en", so that preferences and manga bindings are not lost + override val id by lazy { + val key = "komga${if (suffix.isNotBlank()) " ($suffix)" else ""}/en/$versionId" + val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) + (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE + } + + private val json: Json by injectLazy() + + internal val preferences: SharedPreferences by lazy { + Injekt.get().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 password by lazy { preferences.getString(PREF_PASSWORD, "")!! } + + override fun headersBuilder() = super.headersBuilder() + .set("User-Agent", "TachiyomiKomga/${AppInfo.getVersionName()}") + + override val client: OkHttpClient = + network.client.newBuilder() + .authenticator { _, response -> + if (response.request.header("Authorization") != null) { + null // Give up, we've already failed to authenticate. + } else { + response.request.newBuilder() + .addHeader("Authorization", Credentials.basic(username, password)) + .build() + } + } + .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing + .build() + + override fun popularMangaRequest(page: Int): Request = + searchMangaRequest( + page, + "", + FilterList(SeriesSort()), + ) +>>>>>>> d52b3e572 (Add Komga (#579)) override fun popularMangaParse(response: Response): MangasPage = processSeriesPage(response) override fun latestUpdatesRequest(page: Int): Request = +<<<<<<< HEAD GET("$baseUrl/api/v1/series/latest?page=${page - 1}&deleted=false", headers) +======= + searchMangaRequest( + page, + "", + FilterList(SeriesSort(Filter.Sort.Selection(2, false))), + ) +>>>>>>> d52b3e572 (Add Komga (#579)) override fun latestUpdatesParse(response: Response): MangasPage = processSeriesPage(response) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { +<<<<<<< HEAD +======= + runCatching { fetchFilterOptions() } + +>>>>>>> d52b3e572 (Add Komga (#579)) val collectionId = (filters.find { it is CollectionSelect } as? CollectionSelect)?.let { it.values[it.state].id } @@ -73,7 +153,11 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere else -> "series" } +<<<<<<< HEAD val url = "$baseUrl/api/v1/$type?search=$query&page=${page - 1}&deleted=false".toHttpUrlOrNull()!!.newBuilder() +======= + val url = "$baseUrl/api/v1/$type?search=$query&page=${page - 1}&deleted=false".toHttpUrl().newBuilder() +>>>>>>> d52b3e572 (Add Komga (#579)) filters.forEach { filter -> when (filter) { @@ -94,45 +178,65 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere } } is LibraryGroup -> { +<<<<<<< HEAD val libraryToInclude = mutableListOf() filter.state.forEach { content -> if (content.state) { libraryToInclude.add(content.id) } } +======= + val libraryToInclude = filter.state.filter { it.state }.map { it.id } + +>>>>>>> d52b3e572 (Add Komga (#579)) if (libraryToInclude.isNotEmpty()) { url.addQueryParameter("library_id", libraryToInclude.joinToString(",")) } } is StatusGroup -> { +<<<<<<< HEAD val statusToInclude = mutableListOf() filter.state.forEach { content -> if (content.state) { statusToInclude.add(content.name.uppercase(Locale.ROOT)) } } +======= + val statusToInclude = filter.state.filter { it.state }.map { it.name.uppercase(Locale.ROOT) } + +>>>>>>> d52b3e572 (Add Komga (#579)) if (statusToInclude.isNotEmpty()) { url.addQueryParameter("status", statusToInclude.joinToString(",")) } } is GenreGroup -> { +<<<<<<< HEAD val genreToInclude = mutableListOf() filter.state.forEach { content -> if (content.state) { genreToInclude.add(content.name) } } +======= + val genreToInclude = filter.state.filter { it.state }.map { it.name } + +>>>>>>> d52b3e572 (Add Komga (#579)) if (genreToInclude.isNotEmpty()) { url.addQueryParameter("genre", genreToInclude.joinToString(",")) } } is TagGroup -> { +<<<<<<< HEAD val tagToInclude = mutableListOf() filter.state.forEach { content -> if (content.state) { tagToInclude.add(content.name) } } +======= + val tagToInclude = filter.state.filter { it.state }.map { it.name } + +>>>>>>> d52b3e572 (Add Komga (#579)) if (tagToInclude.isNotEmpty()) { url.addQueryParameter("tag", tagToInclude.joinToString(",")) } @@ -149,17 +253,23 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere } } is AuthorGroup -> { +<<<<<<< HEAD val authorToInclude = mutableListOf() filter.state.forEach { content -> if (content.state) { authorToInclude.add(content.author) } } +======= + val authorToInclude = filter.state.filter { it.state }.map { it.author } + +>>>>>>> d52b3e572 (Add Komga (#579)) authorToInclude.forEach { url.addQueryParameter("author", "${it.name},${it.role}") } } is Filter.Sort -> { +<<<<<<< HEAD var sortCriteria = when (filter.state?.index) { 0 -> if (type == "series") "metadata.titleSort" else "name" 1 -> "createdDate" @@ -170,17 +280,34 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere sortCriteria += "," + if (filter.state?.ascending!!) "asc" else "desc" url.addQueryParameter("sort", sortCriteria) } +======= + val state = filter.state ?: return@forEach + + val sortCriteria = when (state.index) { + 0 -> if (type == "series") "metadata.titleSort" else "name" + 1 -> "createdDate" + 2 -> "lastModifiedDate" + else -> return@forEach + } + "," + if (state.ascending) "asc" else "desc" + + url.addQueryParameter("sort", sortCriteria) +>>>>>>> d52b3e572 (Add Komga (#579)) } else -> {} } } +<<<<<<< HEAD return GET(url.toString(), headers) +======= + return GET(url.build(), headers) +>>>>>>> d52b3e572 (Add Komga (#579)) } override fun searchMangaParse(response: Response): MangasPage = processSeriesPage(response) +<<<<<<< HEAD override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(GET(manga.url, headers)) .asObservable() @@ -204,21 +331,62 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere } } +======= + override fun getMangaUrl(manga: SManga) = manga.url.replace("/api/v1", "") + + override fun mangaDetailsParse(response: Response): SManga { + return if (response.fromReadList()) { + response.parseAs().toSManga() + } else { + response.parseAs().toSManga() + } + } + + private val chapterNameTemplate by lazy { + preferences.getString(PREF_CHAPTER_NAME_TEMPLATE, PREF_CHAPTER_NAME_TEMPLATE_DEFAULT)!! + } + + override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book") + +>>>>>>> d52b3e572 (Add Komga (#579)) override fun chapterListRequest(manga: SManga): Request = GET("${manga.url}/books?unpaged=true&media_status=READY&deleted=false", headers) override fun chapterListParse(response: Response): List { +<<<<<<< HEAD val responseBody = response.body val page = responseBody.use { json.decodeFromString>(it.string()).content } +======= + val page = response.parseAs>().content +>>>>>>> d52b3e572 (Add Komga (#579)) val r = page.mapIndexed { index, book -> SChapter.create().apply { chapter_number = if (!response.fromReadList()) book.metadata.numberSort else index + 1F +<<<<<<< HEAD name = "${if (!response.fromReadList()) "${book.metadata.number} - " else "${book.seriesTitle} ${book.metadata.number}: "}${book.metadata.title} (${book.size})" +======= +>>>>>>> d52b3e572 (Add Komga (#579)) url = "$baseUrl/api/v1/books/${book.id}" scanlator = book.metadata.authors.groupBy({ it.role }, { it.name })["translator"]?.joinToString() date_upload = book.metadata.releaseDate?.let { parseDate(it) } ?: parseDateTime(book.fileLastModified) +<<<<<<< HEAD +======= + + val values = hashMapOf( + "title" to book.metadata.title, + "seriesTitle" to book.seriesTitle, + "number" to book.metadata.number, + "createdDate" to book.created, + "releaseDate" to book.metadata.releaseDate, + "size" to book.size, + "sizeBytes" to book.sizeBytes.toString(), + ) + val sub = StringSubstitutor(values, "{", "}") + + name = (if (!response.fromReadList()) "" else "${book.seriesTitle} ") + sub.replace(chapterNameTemplate) +>>>>>>> d52b3e572 (Add Komga (#579)) } } return r.sortedByDescending { it.chapter_number } @@ -228,8 +396,13 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere GET("${chapter.url}/pages") override fun pageListParse(response: Response): List { +<<<<<<< HEAD val responseBody = response.body val pages = responseBody.use { json.decodeFromString>(it.string()) } +======= + val pages = response.parseAs>() + +>>>>>>> d52b3e572 (Add Komga (#579)) return pages.map { val url = "${response.request.url}/${it.number}" + if (!supportedImageTypes.contains(it.mediaType)) { @@ -238,12 +411,17 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere "" } Page( +<<<<<<< HEAD index = it.number - 1, +======= + index = it.number, +>>>>>>> d52b3e572 (Add Komga (#579)) imageUrl = url, ) } } +<<<<<<< HEAD override fun getMangaUrl(manga: SManga) = manga.url.replace("/api/v1", "") override fun getChapterUrl(chapter: SChapter) = chapter.url.replace("/api/v1/books", "/book") @@ -348,6 +526,9 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere ) { override fun toString() = name } +======= + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() +>>>>>>> d52b3e572 (Add Komga (#579)) override fun getFilterList(): FilterList { val filters = try { @@ -363,17 +544,113 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere TagGroup(tags.map { TagFilter(it) }), PublisherGroup(publishers.map { PublisherFilter(it) }), ).also { list -> +<<<<<<< HEAD +======= + if (collections.isEmpty() && libraries.isEmpty() && genres.isEmpty() && tags.isEmpty() && publishers.isEmpty()) { + list.add(0, Filter.Header("Press 'Reset' to show filtering options")) + list.add(1, Filter.Separator()) + } + +>>>>>>> d52b3e572 (Add Komga (#579)) list.addAll(authors.map { (role, authors) -> AuthorGroup(role, authors.map { AuthorFilter(it) }) }) list.add(SeriesSort()) } } catch (e: Exception) { +<<<<<<< HEAD Log.e(LOG_TAG, "error while creating filter list", e) +======= + Log.e(logTag, "error while creating filter list", e) +>>>>>>> d52b3e572 (Add Komga (#579)) emptyList() } return FilterList(filters) } +<<<<<<< HEAD +======= + override fun setupPreferenceScreen(screen: PreferenceScreen) { + if (suffix.isBlank()) { + ListPreference(screen.context).apply { + key = PREF_EXTRA_SOURCES_COUNT + title = "Number of extra sources" + summary = "Number of additional sources to create. There will always be at least one Komga source." + entries = PREF_EXTRA_SOURCES_ENTRIES + entryValues = PREF_EXTRA_SOURCES_ENTRIES + + setDefaultValue(PREF_EXTRA_SOURCES_DEFAULT) + setOnPreferenceChangeListener { _, newValue -> + try { + val setting = preferences.edit().putString(PREF_EXTRA_SOURCES_COUNT, newValue as String).commit() + Toast.makeText(screen.context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() + setting + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.also(screen::addPreference) + } + + screen.addEditTextPreference( + title = "Source display name", + default = suffix, + summary = displayName.ifBlank { "Here you can change the source displayed suffix" }, + key = PREF_DISPLAY_NAME, + ) + screen.addEditTextPreference( + title = "Address", + default = "", + summary = baseUrl.ifBlank { "The server address" }, + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI, + validate = { it.toHttpUrlOrNull() != null }, + validationMessage = "The URL is invalid or malformed", + key = PREF_ADDRESS, + ) + screen.addEditTextPreference( + title = "Username", + default = "", + summary = username.ifBlank { "The user account email" }, + key = PREF_USERNAME, + ) + screen.addEditTextPreference( + title = "Password", + default = "", + summary = if (password.isBlank()) "The user account password" else "*".repeat(password.length), + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD, + key = PREF_PASSWORD, + ) + + EditTextPreference(screen.context).apply { + key = PREF_CHAPTER_NAME_TEMPLATE + title = "Chapter title format" + summary = "Customize how chapter names appear. Chapters in read lists will always be prefixed by the series' name." + dialogMessage = """ + |Supported placeholders: + |- {title}: Chapter name + |- {seriesTitle}: Series name + |- {number}: Chapter number + |- {createdDate}: Chapter creation date + |- {releaseDate}: Chapter release date + |- {size}: Chapter file size (formatted) + |- {sizeBytes}: Chapter file size (in bytes) + """.trimMargin() + + setDefaultValue(PREF_CHAPTER_NAME_TEMPLATE_DEFAULT) + setOnPreferenceChangeListener { _, newValue -> + try { + val setting = preferences.edit().putString(PREF_CHAPTER_NAME_TEMPLATE, newValue as String).commit() + Toast.makeText(screen.context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() + setting + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.also(screen::addPreference) + } + +>>>>>>> d52b3e572 (Add Komga (#579)) private var libraries = emptyList() private var collections = emptyList() private var genres = emptySet() @@ -381,6 +658,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere private var publishers = emptySet() private var authors = emptyMap>() // roles to list of authors +<<<<<<< HEAD // keep the previous ID when lang was "en", so that preferences and manga bindings are not lost override val id by lazy { val key = "komga${if (suffix.isNotBlank()) " ($suffix)" else ""}/en/$versionId" @@ -450,6 +728,32 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD, key = PREF_PASSWORD, ) +======= + private class TypeSelect : Filter.Select("Search for", arrayOf(TYPE_SERIES, TYPE_READLISTS)) + private class LibraryFilter(val id: String, name: String) : Filter.CheckBox(name, false) + private class LibraryGroup(libraries: List) : Filter.Group("Libraries", libraries) + private class CollectionSelect(collections: List) : Filter.Select("Collection", collections.toTypedArray()) + private class SeriesSort(selection: Selection? = null) : Filter.Sort("Sort", arrayOf("Alphabetically", "Date added", "Date updated"), selection ?: Selection(0, true)) + private class StatusFilter(name: String) : Filter.CheckBox(name, false) + private class StatusGroup(filters: List) : Filter.Group("Status", filters) + private class UnreadFilter : Filter.CheckBox("Unread", false) + private class InProgressFilter : Filter.CheckBox("In Progress", false) + private class ReadFilter : Filter.CheckBox("Read", false) + private class GenreFilter(genre: String) : Filter.CheckBox(genre, false) + private class GenreGroup(genres: List) : Filter.Group("Genres", genres) + private class TagFilter(tag: String) : Filter.CheckBox(tag, false) + private class TagGroup(tags: List) : Filter.Group("Tags", tags) + private class PublisherFilter(publisher: String) : Filter.CheckBox(publisher, false) + private class PublisherGroup(publishers: List) : Filter.Group("Publishers", publishers) + private class AuthorFilter(val author: AuthorDto) : Filter.CheckBox(author.name, false) + private class AuthorGroup(role: String, authors: List) : Filter.Group(role.replaceFirstChar { it.titlecase() }, authors) + + private data class CollectionFilterEntry( + val name: String, + val id: String? = null, + ) { + override fun toString() = name +>>>>>>> d52b3e572 (Add Komga (#579)) } private fun PreferenceScreen.addEditTextPreference( @@ -511,6 +815,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere addPreference(preference) } +<<<<<<< HEAD private val SharedPreferences.displayName get() = getString(PREF_DISPLAYNAME, "")!! @@ -628,6 +933,126 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere private const val USERNAME_DEFAULT = "" private const val PREF_PASSWORD = "Password" private const val PASSWORD_DEFAULT = "" +======= + private var fetchFiltersFailed = false + + private var fetchFiltersAttempts = 0 + + private fun fetchFilterOptions() { + if (baseUrl.isBlank()) { + return + } + + if (fetchFiltersAttempts > 3 || (fetchFiltersAttempts > 0 && !fetchFiltersFailed)) { + return + } + + Single.fromCallable { + val result = runCatching { + libraries = client.newCall(GET("$baseUrl/api/v1/libraries")).execute().parseAs() + collections = client + .newCall(GET("$baseUrl/api/v1/collections?unpaged=true")) + .execute() + .parseAs>() + .content + genres = client.newCall(GET("$baseUrl/api/v1/genres")).execute().parseAs() + tags = client.newCall(GET("$baseUrl/api/v1/tags")).execute().parseAs() + publishers = client.newCall(GET("$baseUrl/api/v1/publishers")).execute().parseAs() + authors = client + .newCall(GET("$baseUrl/api/v1/authors")) + .execute() + .parseAs>() + .groupBy { it.role } + } + .onFailure { + Log.e(logTag, "Could not fetch filtering options", it) + } + + fetchFiltersFailed = result.isFailure + fetchFiltersAttempts++ + } + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe() + } + + private fun processSeriesPage(response: Response): MangasPage { + return if (response.fromReadList()) { + val data = response.parseAs>() + + MangasPage(data.content.map { it.toSManga() }, !data.last) + } else { + val data = response.parseAs>() + + MangasPage(data.content.map { it.toSManga() }, !data.last) + } + } + + private fun SeriesDto.toSManga(): SManga = + SManga.create().apply { + title = metadata.title + url = "$baseUrl/api/v1/series/$id" + thumbnail_url = "$url/thumbnail" + status = when { + metadata.status == "ENDED" && metadata.totalBookCount != null && booksCount < metadata.totalBookCount -> SManga.PUBLISHING_FINISHED + metadata.status == "ENDED" -> SManga.COMPLETED + metadata.status == "ONGOING" -> SManga.ONGOING + metadata.status == "ABANDONED" -> SManga.CANCELLED + metadata.status == "HIATUS" -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + genre = (metadata.genres + metadata.tags + booksMetadata.tags).distinct().joinToString(", ") + description = metadata.summary.ifBlank { booksMetadata.summary } + booksMetadata.authors.groupBy { it.role }.let { map -> + author = map["writer"]?.map { it.name }?.distinct()?.joinToString() + artist = map["penciller"]?.map { it.name }?.distinct()?.joinToString() + } + } + + private fun ReadListDto.toSManga(): SManga = + SManga.create().apply { + title = name + description = summary + url = "$baseUrl/api/v1/readlists/$id" + thumbnail_url = "$url/thumbnail" + status = SManga.UNKNOWN + } + + private fun Response.fromReadList() = request.url.toString().contains("/api/v1/readlists") + + private fun parseDate(date: String?): Long = runCatching { + KomgaHelper.formatterDate.parse(date!!)!!.time + }.getOrDefault(0L) + + private fun parseDateTime(date: String?) = if (date == null) { + 0L + } else { + runCatching { + KomgaHelper.formatterDateTime.parse(date)!!.time + } + .getOrElse { + KomgaHelper.formatterDateTimeMilli.parse(date)?.time ?: 0L + } + } + + private inline fun Response.parseAs(): T { + return json.decodeFromString(body.string()) + } + + private val logTag = "komga${if (suffix.isNotBlank()) ".$suffix" else ""}" + + companion object { + internal const val PREF_EXTRA_SOURCES_COUNT = "Number of extra sources" + internal const val PREF_EXTRA_SOURCES_DEFAULT = "2" + private val PREF_EXTRA_SOURCES_ENTRIES = (0..10).map { it.toString() }.toTypedArray() + + private const val PREF_DISPLAY_NAME = "Source display name" + private const val PREF_ADDRESS = "Address" + private const val PREF_USERNAME = "Username" + private const val PREF_PASSWORD = "Password" + private const val PREF_CHAPTER_NAME_TEMPLATE = "Chapter name template" + private const val PREF_CHAPTER_NAME_TEMPLATE_DEFAULT = "{number} - {title} ({size})" +>>>>>>> d52b3e572 (Add Komga (#579)) private val supportedImageTypes = listOf("image/jpeg", "image/png", "image/gif", "image/webp", "image/jxl", "image/heif", "image/avif") diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt index 84ec24a84..26428eb85 100644 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory class KomgaFactory : SourceFactory { +<<<<<<< HEAD override fun createSources(): List = listOf( @@ -11,4 +12,13 @@ class KomgaFactory : SourceFactory { Komga("2"), Komga("3"), ) +======= + override fun createSources(): List { + val firstKomga = Komga("") + val komgaCount = firstKomga.preferences.getString(Komga.PREF_EXTRA_SOURCES_COUNT, Komga.PREF_EXTRA_SOURCES_DEFAULT)!!.toInt() + + // Komga(""), Komga("2"), Komga("3"), ... + return listOf(firstKomga) + (0 until komgaCount).map { Komga("${it + 2}") } + } +>>>>>>> d52b3e572 (Add Komga (#579)) }