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))
}