Overhaul 2025 (See PR for changes)
@@ -1,2 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="eu.kanade.tachiyomi.permission.READ_SOURCES" />
|
||||
<uses-permission android:name="eu.kanade.tachiyomi.permission.WRITE_SOURCES" />
|
||||
<application
|
||||
android:usesCleartextTraffic="true" />
|
||||
</manifest>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
## 1.3.13
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed 'null cannot be cast to non-null type' exception
|
||||
|
||||
## 1.3.12
|
||||
|
||||
### Features
|
||||
|
||||
* Migrate filters to v2
|
||||
* Implemented smartFilters
|
||||
* Added localization support
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed publication status not showing
|
||||
|
||||
## 1.3.10
|
||||
|
||||
### Features
|
||||
|
||||
* API Change for Kavita v0.7.2
|
||||
|
||||
## 1.3.9
|
||||
|
||||
### Features
|
||||
|
||||
* Added pdf support
|
||||
|
||||
## 1.3.8
|
||||
|
||||
### Fix
|
||||
|
||||
* Fixed `Expected URL scheme 'http' or 'https` when downloading
|
||||
|
||||
## 1.3.7
|
||||
|
||||
### Features
|
||||
|
||||
* New Sort filter: Time to read
|
||||
* New Filter: Year release filter
|
||||
|
||||
### Fix
|
||||
|
||||
* Filters can now be used together with search
|
||||
* Epub and pdfs no longer show in format filter (currently not supported)
|
||||
|
||||
## 1.3.6
|
||||
|
||||
### Fix
|
||||
|
||||
* Fixed "lateinit property title not initialized"
|
||||
|
||||
## 1.3.5
|
||||
|
||||
### Features
|
||||
|
||||
* Ignore DOH
|
||||
* Added sort option `Item Added`
|
||||
* Latest button now shows latest `Item Added`
|
||||
|
||||
## 1.3.4
|
||||
|
||||
### Features
|
||||
|
||||
* Exclude from bulk update warnings
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Fix
|
||||
|
||||
* Fixed Rating filter
|
||||
* Fixed Chapter list not sorting correctly
|
||||
* Fixed search
|
||||
* Fixed manga details not showing correctly
|
||||
* Fixed filters not populating if account was not admin
|
||||
|
||||
### Features
|
||||
* The extension is now ready to implement tracking.
|
||||
* Min required version for the extension to work properly: `v0.5.1.1`
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Features
|
||||
|
||||
* Add `CHANGELOG.md` & `README.md`
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Features
|
||||
|
||||
* first version
|
||||
@@ -1,37 +1,41 @@
|
||||
# Kavita
|
||||
# Komga
|
||||
|
||||
Table of Content
|
||||
- [FAQ](#FAQ)
|
||||
- [Why do I see no manga?](#why-do-i-see-no-manga)
|
||||
- [Where can I get more information about Kavita?](#where-can-i-get-more-information-about-kavita)
|
||||
- [The Kavita extension stopped working?](#the-kavita-extension-stopped-working)
|
||||
- [Can I add more than one Kavita server or user?](#can-i-add-more-than-one-kavita-server-or-user)
|
||||
- [Can I test the Kavita extension before setting up my own server?](#can-i-test-the-kavita-extension-before-setting-up-my-own-server)
|
||||
- [Why do I see no manga?](#why-do-i-see-no-manga)
|
||||
- [Where can I get more information about Komga?](#where-can-i-get-more-information-about-komga)
|
||||
- [The Komga extension stopped working?](#the-komga-extension-stopped-working)
|
||||
- [Can I add more than one Komga server or user?](#can-i-add-more-than-one-komga-server-or-user)
|
||||
- [Can I test the Komga extension before setting up my own server?](#can-i-test-the-komga-extension-before-setting-up-my-own-server)
|
||||
- [Guides](#Guides)
|
||||
- [How do I add my Kavita server to Tachiyomi?](#how-do-i-add-my-kavita-server-to-tachiyomi)
|
||||
|
||||
Don't find the question you are look for go check out our general FAQs and Guides over at [Extension FAQ](https://tachiyomi.org/help/faq/#extensions) or [Getting Started](https://tachiyomi.org/help/guides/getting-started/#installation)
|
||||
|
||||
Kavita also has a documentation about the Tachiyomi Kavita extension at the [Kavita wiki](https://wiki.kavitareader.com/en/guides/misc/tachiyomi).
|
||||
- [How do I add my Komga server to the app?](#how-do-i-add-my-komga-server-to-the-app)
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why do I see no manga?
|
||||
Kavita is a self-hosted comic/manga media server.
|
||||
Komga is a self-hosted comic/manga media server.
|
||||
|
||||
### Where can I get more information about Kavita?
|
||||
You can visit the [Kavita](https://www.kavitareader.com/) website for for more information.
|
||||
### Where can I get more information about Komga?
|
||||
You can visit the [Komga](https://komga.org/) website for for more information.
|
||||
|
||||
### The Kavita extension stopped working?
|
||||
Make sure that your Kavita server and extension are on the newest version.
|
||||
### The Komga extension stopped working?
|
||||
Make sure that your Komga server and extension are on the newest version.
|
||||
|
||||
### Can I add more than one Kavita server or user?
|
||||
Yes, currently you can add up to 3 different Kavita instances to Tachiyomi.
|
||||
### Can I add more than one Komga server or user?
|
||||
Yes, currently you can add up to 11 different Komga instances to the app. The number of instances
|
||||
available can be customized in extension settings.
|
||||
|
||||
### Can I test the Kavita extension before setting up my own server?
|
||||
Yes, you can try it out with the DEMO servers OPDS url `https://demo.kavitareader.com/api/opds/aca1c50d-7e08-4f37-b356-aecd6bf69b72`.
|
||||
### Can I test the Komga extension before setting up my own server?
|
||||
Yes, you can try it out with the DEMO server `https://demo.komga.org`, username `demo@komga.org` and
|
||||
password `komga-demo`.
|
||||
|
||||
### Why are EPUB chapters/books missing?
|
||||
Mihon/Tachiyomi does not support reading text. EPUB files containing only images will be available
|
||||
if your Komga server version is [1.9.0](https://github.com/gotson/komga/releases/tag/1.9.0)
|
||||
or newer.
|
||||
|
||||
## Guides
|
||||
|
||||
### How do I add my Kavita server to Tachiyomi?
|
||||
Go into the settings of the Kavita extension from the Extension tab in Browse and fill in your OPDS url.
|
||||
### How do I add my Komga server to the app?
|
||||
Go into the settings of the Komga extension (under Browse -> Extensions) and fill in your server
|
||||
address and login details.
|
||||
|
||||
@@ -3,6 +3,20 @@ login_errors_header_token_empty="Error: The JSON Web Token is empty.\nTry openin
|
||||
login_errors_invalid_url=Invalid URL:
|
||||
login_errors_parse_tokendto=There was an error parsing the auth token
|
||||
pref_customsource_title=Displayed name for source
|
||||
pref_group_tags_title=Group Tags (fork must support grouping)
|
||||
pref_group_tags_summary_on=Will prefix tags with their type
|
||||
pref_group_tags_summary_off=List all tags together
|
||||
pref_last_volume_cover_title=Keep cover updated
|
||||
pref_last_volume_cover_summary_off=Keep current cover
|
||||
pref_last_volume_cover_summary_on=Use the last volume cover when updating
|
||||
pref_show_epub_title=Show EPUB (EPUB can't be read in Mihon)
|
||||
pref_show_epub_summary_off=Hide EPUB titles in browsing
|
||||
pref_show_epub_summary_on=Show EPUB titles in browsing
|
||||
pref_rd_date_title=Reading List: Display Release Date
|
||||
pref_rd_date_summary_off=Do not display the release date
|
||||
pref_rd_date_summary_on=Display the release date | !! Once you toggle it on, if you toggle it off again, it will stays as N/A !!
|
||||
pref_reset_title=Reset All Settings
|
||||
pref_reset_summary=Clear all preferences for this Kavita instance (Only affect the selected instance)
|
||||
pref_edit_customsource_summary=Here you can change this source name.\nYou can write a descriptive name to identify this OPDS URL.
|
||||
pref_filters_summary=Show these filters in the filter list
|
||||
pref_filters_title=Default filters shown
|
||||
@@ -10,7 +24,7 @@ pref_opds_badformed_url=Incorrect OPDS address. Please copy it from User setting
|
||||
pref_opds_duplicated_source_url=The URL is configured in a different source ->
|
||||
pref_opds_must_setup_address=You must set up the address to communicate with Kavita
|
||||
pref_opds_summary=The OPDS URL copied from User Settings. This should include address and end with the API key.
|
||||
restartapp_settings=Restart Tachiyomi to apply new setting.
|
||||
restartapp_settings=Restart Mihon to apply new setting.
|
||||
version_exceptions_chapters_parse=Unhandled exception parsing chapters. Send your logs to the Kavita devs.
|
||||
check_version=Ensure you have the newest version of the extension and Kavita. (0.7.8 or newer.)\nIf the issue persists, report it to the Kavita developers with the accompanying logs.
|
||||
version_exceptions_smart_filter=Could not decode SmartFilter. Ensure you are using Kavita version 0.7.11 or later.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
version_exceptions_chapters_parse=Exception non trait\u00E9e durant l'analyse des chapitres. Envoyez les journaux aux d\u00E9velopeurs de Kavita
|
||||
version_exceptions_chapters_parse=Exception non trait\u00E9e durant l'analyse des chapitres. Envoyez les logs aux d\u00E9velopeurs de Kavita.
|
||||
pref_customsource_title=Nom d'affichage pour la source
|
||||
version_exceptions_smart_filter=\u00C9chec du d\u00E9codage de SmartFilter. Assurez-vous que vous utilisez au moins Kavita version 0.7.11
|
||||
pref_opds_summary=L'URL OPDS a \u00E9t\u00E9 copi\u00E9e \u00E0 partir des param\u00E8tres de l'utilisateur. Ceci devrait inclure l'adresse et la cl\u00E9 API.
|
||||
@@ -8,13 +6,13 @@ pref_filters_summary=Afficher ces filtres dans la liste des filtres
|
||||
check_version=Assurez-vous que vous avez l'extension et Kavita mises \u00E0 jour. (version Mini\u202F: 0.7.8)\nSi le probl\u00E8me persiste, signalez-le aux d\u00E9veloppeurs de Kavita en fournissant des journaux
|
||||
pref_filters_title=Filtres par d\u00E9faut affich\u00E9s
|
||||
pref_edit_customsource_summary=Ici vous pouvez changer ce nom source.\nVous pouvez \u00E9crire un nom descriptif pour identifier cette URL opds
|
||||
pref_opds_badformed_url=L'adresse OPDS n'est pas correcte. Veuillez la copiez \u00E0 partir des param\u00E8tres de l'utilisateur - > Applis tierces -> URL OPDS
|
||||
pref_opds_badformed_url=L'adresse OPDS n'est pas correcte. Veuillez la copier \u00E0 partir des param\u00E8tres de l'utilisateur - > Applis tierces -> URL OPDS
|
||||
login_errors_parse_tokendto=Il y a eu une erreur pendant l'analyse du jeton d'authentification
|
||||
restartapp_settings=Red\u00E9marrez Tachiyomi pour appliquer le nouveau r\u00E9glage.
|
||||
restartapp_settings=Red\u00E9marrez Mihon pour appliquer le nouveau r\u00E9glage.
|
||||
pref_opds_duplicated_source_url=L'URL est configur\u00E9e dans une autre source ->
|
||||
pref_opds_must_setup_address=Vous devez configurer l'adresse pour communiquer avec Kavita
|
||||
login_errors_failed_login=\u00C9chec de la connexion. Quelque chose s'est mal pass\u00E9
|
||||
http_errors_500=Quelque chose s'est mal pass\u00E9
|
||||
login_errors_header_token_empty=\u00AB\u00A0Erreur\u202F: le jeton jwt est vide.\nEssayez d'abord d'ouvrir l'extension\u00A0\u00BB
|
||||
login_errors_header_token_empty=\u00AB\u00A0Erreur\u202F: le token jwt est vide.\nEssayez d'abord d'ouvrir l'extension\u00A0\u00BB
|
||||
login_errors_invalid_url=URL invalide\u202F:
|
||||
http_errors_401=Il y a eu une erreur. Essayez de nouveau ou rechargez l'application
|
||||
http_errors_401=Il y a eu une erreur. Essayez de nouveau ou relancez l'application
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
|
||||
|
||||
pref_customsource_title=Vist kildenavn
|
||||
pref_edit_customsource_summary=Her kan du endre dette kildenavnet.\nDu kan skrive et beskrivende navn for \u00E5 identifisere denne OPDS-nettadressen.
|
||||
restartapp_settings=Ny innstilling trer i kraft n\u00E5r du starter Tachiyomi p\u00E5 ny.
|
||||
restartapp_settings=Ny innstilling trer i kraft n\u00E5r du starter Mihon p\u00E5 ny.
|
||||
duplicated_source_url=Nettadressen er satt opp i en annen Kavita-instans
|
||||
pref_filters_summary=Vis disse filterne i filterlisten
|
||||
pref_filters_title=Forvalgte filtre valgt
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
kmkVersionCode = 1
|
||||
extName = 'Kavita'
|
||||
pkgNameSuffix = 'all.kavita'
|
||||
extClass = '.KavitaFactory'
|
||||
extVersionCode = 16
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'info.debatty:java-string-similarity:2.0.0'
|
||||
implementation(project(':lib-i18n'))
|
||||
extVersionCode = 19
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation("org.apache.commons:commons-text:1.11.0")
|
||||
implementation project(':lib:i18n')
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 47 KiB |
@@ -3,18 +3,25 @@ package eu.kanade.tachiyomi.extension.all.kavita
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSelected
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
class UserRating :
|
||||
Filter.Select<String>(
|
||||
"Minimum Rating",
|
||||
arrayOf(
|
||||
"Any",
|
||||
"1 star",
|
||||
"2 stars",
|
||||
"3 stars",
|
||||
"4 stars",
|
||||
"5 stars",
|
||||
),
|
||||
)
|
||||
class UserRating : Filter.Select<Int>(
|
||||
"Minimum Rating",
|
||||
arrayOf(
|
||||
"Any",
|
||||
"1 star",
|
||||
"2 stars",
|
||||
"3 stars",
|
||||
"4 stars",
|
||||
"5 stars",
|
||||
).mapIndexed { index, _ -> index }.toTypedArray(),
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return when (state) {
|
||||
0 -> "Any"
|
||||
else -> "$state star${if (state > 1) "s" else ""}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SmartFiltersFilter(smartFilters: Array<String>) :
|
||||
Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters)
|
||||
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true))
|
||||
@@ -26,15 +33,27 @@ val sortableList = listOf(
|
||||
Pair("Item added", 4),
|
||||
Pair("Time to Read", 5),
|
||||
Pair("Release year", 6),
|
||||
Pair("Read Progress", 7),
|
||||
Pair("Average Rating", 8),
|
||||
Pair("Random", 9),
|
||||
)
|
||||
|
||||
class SpecialListFilter : Filter.Select<String>(
|
||||
"Special Lists",
|
||||
arrayOf("None", "Want to Read", "Reading Lists"),
|
||||
)
|
||||
|
||||
// Use Filter.CheckBox directly for status
|
||||
class StatusFilter(name: String) : Filter.CheckBox(name, false)
|
||||
class StatusFilterGroup(filters: List<StatusFilter>) :
|
||||
Filter.Group<StatusFilter>("Status", filters)
|
||||
|
||||
class ReleaseYearRange(name: String) : Filter.Text(name)
|
||||
// Use Filter.Text directly for release year range
|
||||
class ReleaseYearRange(name: String) : Filter.Text(name, "")
|
||||
class ReleaseYearRangeGroup(filters: List<ReleaseYearRange>) :
|
||||
Filter.Group<ReleaseYearRange>("Release Year", filters)
|
||||
|
||||
// Use Filter.TriState for genres/tags/age ratings/collections/languages/libraries
|
||||
class GenreFilter(name: String) : Filter.TriState(name)
|
||||
class GenreFilterGroup(genres: List<GenreFilter>) :
|
||||
Filter.Group<GenreFilter>("Genres", genres)
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package eu.kanade.tachiyomi.extension.all.kavita
|
||||
|
||||
object KavitaConstants {
|
||||
|
||||
val PERSON_ROLES = listOf(
|
||||
"Writer", "Penciller", "Inker", "Colorist",
|
||||
"Letterer", "CoverArtist", "Editor",
|
||||
"Publisher", "Character", "Translator",
|
||||
)
|
||||
|
||||
// toggle filters
|
||||
const val toggledFiltersPref = "toggledFilters"
|
||||
val filterPrefEntries = arrayOf(
|
||||
"Sort Options",
|
||||
"Special Lists",
|
||||
"Format",
|
||||
"Libraries",
|
||||
"Read Status",
|
||||
@@ -26,9 +34,13 @@ object KavitaConstants {
|
||||
"Character",
|
||||
"Translators",
|
||||
"ReleaseYearRange",
|
||||
"Average Rating",
|
||||
"Random",
|
||||
"Read Progress",
|
||||
)
|
||||
val filterPrefEntriesValue = arrayOf(
|
||||
"Sort Options",
|
||||
"Special Lists",
|
||||
"Format",
|
||||
"Libraries",
|
||||
"Read Status",
|
||||
@@ -50,9 +62,13 @@ object KavitaConstants {
|
||||
"Character",
|
||||
"Translators",
|
||||
"ReleaseYearRange",
|
||||
"Average Rating",
|
||||
"Random",
|
||||
"Read Progress",
|
||||
)
|
||||
val defaultFilterPrefEntries = setOf(
|
||||
"Sort Options",
|
||||
"Special Lists",
|
||||
"Format",
|
||||
"Libraries",
|
||||
"Read Status",
|
||||
@@ -74,6 +90,9 @@ object KavitaConstants {
|
||||
"Character",
|
||||
"Translators",
|
||||
"ReleaseYearRange",
|
||||
"Average Rating",
|
||||
"Random",
|
||||
"Read Progress",
|
||||
)
|
||||
|
||||
const val customSourceNamePref = "customSourceName"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package eu.kanade.tachiyomi.extension.all.kavita
|
||||
|
||||
import android.util.Log
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterDto
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterType
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.LibraryTypeEnum
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.PaginationInfo
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesDto
|
||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.VolumeDto
|
||||
@@ -42,62 +44,138 @@ class KavitaHelper {
|
||||
}
|
||||
|
||||
fun getIdFromUrl(url: String): Int {
|
||||
return url.split("/").last().toInt()
|
||||
return try {
|
||||
val id = url.substringAfterLast("/").toInt()
|
||||
Log.d("KavitaHelper", "Extracted ID $id from URL: $url")
|
||||
id
|
||||
} catch (e: Exception) {
|
||||
Log.e("KavitaHelper", "Failed to extract ID from URL: $url", e)
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiKey: String): SManga =
|
||||
// Rating Providers from Series-Details-Plus
|
||||
private fun getProviderName(provider: Int): String = when (provider) {
|
||||
0 -> "User"
|
||||
1 -> "AniList"
|
||||
2 -> "MyAnimeList"
|
||||
3 -> "MangaUpdates"
|
||||
else -> "Rating"
|
||||
}
|
||||
|
||||
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiUrl: String, apiKey: String): SManga =
|
||||
SManga.create().apply {
|
||||
// url = "$baseUrl/library/${obj.libraryId}/series/${obj.id}"
|
||||
url = "$baseUrl/Series/${obj.id}"
|
||||
title = obj.name
|
||||
// Deprecated: description = obj.summary
|
||||
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
|
||||
}
|
||||
class CompareChapters {
|
||||
companion object : Comparator<SChapter> {
|
||||
override fun compare(a: SChapter, b: SChapter): Int {
|
||||
if (a.chapter_number < 1.0 && b.chapter_number < 1.0) {
|
||||
// Both are volumes, multiply by 100 and do normal sort
|
||||
return if ((a.chapter_number * 100) < (b.chapter_number * 100)) {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
} else {
|
||||
if (a.chapter_number < 1.0 && b.chapter_number >= 1.0) {
|
||||
// A is volume, b is not. A should sort first
|
||||
return 1
|
||||
} else if (a.chapter_number >= 1.0 && b.chapter_number < 1.0) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if (a.chapter_number < b.chapter_number) return 1
|
||||
if (a.chapter_number > b.chapter_number) return -1
|
||||
return 0
|
||||
|
||||
// Set status based on read progress
|
||||
status = when {
|
||||
obj.pagesRead >= obj.pages -> SManga.COMPLETED
|
||||
obj.pagesRead > 0 -> SManga.ONGOING
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun chapterFromVolume(chapter: ChapterDto, volume: VolumeDto): SChapter =
|
||||
// class CompareChapters {
|
||||
// companion object : Comparator<SChapter> {
|
||||
// override fun compare(a: SChapter, b: SChapter): Int {
|
||||
// if (a.chapter_number < 1.0 && b.chapter_number < 1.0) {
|
||||
// // Both are volumes, multiply by 100 and do normal sort
|
||||
// return if ((a.chapter_number * 100) < (b.chapter_number * 100)) {
|
||||
// 1
|
||||
// } else {
|
||||
// -1
|
||||
// }
|
||||
// } else {
|
||||
// if (a.chapter_number < 1.0 && b.chapter_number >= 1.0) {
|
||||
// // A is volume, b is not. A should sort first
|
||||
// return 1
|
||||
// } else if (a.chapter_number >= 1.0 && b.chapter_number < 1.0) {
|
||||
// return -1
|
||||
// }
|
||||
// }
|
||||
// if (a.chapter_number < b.chapter_number) return 1
|
||||
// if (a.chapter_number > b.chapter_number) return -1
|
||||
// return 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun chapterFromVolume(chapter: ChapterDto, volume: VolumeDto, singleFileVolumeNumber: Int? = null, libraryType: LibraryTypeEnum? = null): SChapter =
|
||||
SChapter.create().apply {
|
||||
val type = ChapterType.of(chapter, volume)
|
||||
val type = ChapterType.of(chapter, volume, libraryType)
|
||||
|
||||
name = when (type) {
|
||||
ChapterType.Regular -> "Volume ${volume.number} Chapter ${chapter.number}"
|
||||
ChapterType.SingleFileVolume -> "Volume ${volume.number}"
|
||||
ChapterType.Special -> chapter.range
|
||||
ChapterType.LooseLeaf -> {
|
||||
val cleanedName = chapter.title.replaceFirst("^0+(?!$)".toRegex(), "")
|
||||
"Chapter $cleanedName"
|
||||
ChapterType.Regular -> {
|
||||
val volumeNum = volume.number.toString().toIntOrNull()?.let {
|
||||
it.toString().padStart(2, '0')
|
||||
} ?: volume.number.toString()
|
||||
val chapterNum = chapter.number.toIntOrNull()?.let {
|
||||
it.toString().padStart(2, '0')
|
||||
} ?: chapter.number
|
||||
when {
|
||||
chapter.titleName.isBlank() -> "Volume $volumeNum Chapter $chapterNum"
|
||||
chapter.titleName.trim().matches(Regex("^\\d+$")) -> "Volume $volumeNum Chapter ${chapter.titleName.trim().padStart(2, '0')}"
|
||||
else -> "Volume $volumeNum $chapterNum - ${chapter.titleName}"
|
||||
}
|
||||
}
|
||||
ChapterType.SingleFileVolume -> {
|
||||
when {
|
||||
volume.name.isBlank() -> "Volume ${volume.number}"
|
||||
volume.name.trim().matches(Regex("^\\d+$")) -> "Volume ${volume.name.trim().toIntOrNull()?.toString() ?: volume.name.trim()}"
|
||||
else -> "${volume.number} - ${volume.name}"
|
||||
}
|
||||
}
|
||||
ChapterType.Special -> chapter.titleName.takeIf { it.isNotBlank() } ?: chapter.range
|
||||
ChapterType.Chapter -> {
|
||||
val chapterNum = chapter.number.toIntOrNull()?.let {
|
||||
it.toString().padStart(2, '0')
|
||||
} ?: chapter.number
|
||||
when {
|
||||
chapter.titleName.isBlank() -> "Chapter $chapterNum"
|
||||
chapter.titleName.trim().matches(Regex("^\\d+$")) -> "Chapter ${chapter.titleName.trim().padStart(2, '0')}"
|
||||
else -> "$chapterNum - ${chapter.titleName}"
|
||||
}
|
||||
}
|
||||
ChapterType.Issue -> {
|
||||
val issueNum = chapter.number.toIntOrNull()?.let {
|
||||
it.toString().padStart(3, '0')
|
||||
} ?: chapter.number
|
||||
when {
|
||||
chapter.titleName.isNotBlank() && !chapter.titleName.trim().matches(Regex("^\\d+$")) ->
|
||||
"#$issueNum ${chapter.titleName}"
|
||||
chapter.title.isNotBlank() && !chapter.title.trim().matches(Regex("^\\d+$")) ->
|
||||
"#$issueNum ${chapter.title}"
|
||||
else -> "Issue #$issueNum"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chapter_number = if (type == ChapterType.SingleFileVolume) {
|
||||
volume.number.toFloat() / 10000
|
||||
} else {
|
||||
chapter.number.toFloat()
|
||||
// chapter_number = try {
|
||||
// // if (type == ChapterType.SingleFileVolume) {
|
||||
// // volume.number.toFloat() / 10000
|
||||
// // } else {
|
||||
// // chapter.number.toFloatOrNull() ?: 0f
|
||||
// // }
|
||||
// // } catch (e: NumberFormatException) {
|
||||
// // 0f
|
||||
// // }
|
||||
|
||||
chapter_number = when {
|
||||
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> singleFileVolumeNumber.toFloat()
|
||||
type != ChapterType.SingleFileVolume -> chapter.number.toFloatOrNull() ?: 0f
|
||||
else -> 0f
|
||||
}
|
||||
|
||||
url = chapter.id.toString()
|
||||
url = when {
|
||||
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
||||
"volume_${volume.id}"
|
||||
else -> chapter.id.toString()
|
||||
}
|
||||
|
||||
// url = chapter.id.toString()
|
||||
|
||||
if (chapter.fileCount > 1) {
|
||||
// salt/offset to recognize chapters with new merged part-chapters as new and hence unread
|
||||
@@ -105,8 +183,14 @@ class KavitaHelper {
|
||||
url = "${url}_${chapter.fileCount}"
|
||||
}
|
||||
|
||||
date_upload = parseDate(chapter.created)
|
||||
scanlator = "${chapter.pages} pages"
|
||||
date_upload = parseDateSafe(chapter.created)
|
||||
scanlator = when (type) {
|
||||
ChapterType.SingleFileVolume -> "Volume"
|
||||
ChapterType.Special -> "Special"
|
||||
ChapterType.Issue -> "Issue"
|
||||
ChapterType.Chapter -> "Chapter"
|
||||
ChapterType.Regular -> "Chapter"
|
||||
}
|
||||
}
|
||||
|
||||
val intl = Intl(
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package eu.kanade.tachiyomi.extension.all.kavita
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
val formatterDate = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
val formatterDateTime = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
|
||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
|
||||
fun parseDateSafe(date: String?): Long {
|
||||
return date?.let {
|
||||
try {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
|
||||
sdf.timeZone = TimeZone.getTimeZone("UTC")
|
||||
sdf.parse(it)?.time ?: 0L
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
} ?: 0L
|
||||
}
|
||||
|
||||
fun PreferenceScreen.addEditTextPreference(
|
||||
title: String,
|
||||
default: String,
|
||||
summary: String,
|
||||
dialogMessage: String? = null,
|
||||
inputType: Int? = null,
|
||||
validate: ((String) -> Boolean)? = null,
|
||||
validationMessage: String? = null,
|
||||
key: String = title,
|
||||
restartRequired: Boolean = false,
|
||||
) {
|
||||
EditTextPreference(context).apply {
|
||||
this.key = key
|
||||
this.title = title
|
||||
this.summary = summary
|
||||
this.setDefaultValue(default)
|
||||
dialogTitle = title
|
||||
this.dialogMessage = dialogMessage
|
||||
|
||||
setOnBindEditTextListener { editText ->
|
||||
if (inputType != null) {
|
||||
editText.inputType = inputType
|
||||
}
|
||||
|
||||
if (validate != null) {
|
||||
editText.addTextChangedListener(
|
||||
object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
requireNotNull(editable)
|
||||
|
||||
val text = editable.toString()
|
||||
|
||||
val isValid = text.isBlank() || validate(text)
|
||||
|
||||
editText.error = if (!isValid) validationMessage else null
|
||||
editText.rootView.findViewById<Button>(android.R.id.button1)
|
||||
?.isEnabled = editText.error == null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
val text = newValue as String
|
||||
val result = text.isBlank() || validate?.invoke(text) ?: true
|
||||
|
||||
if (restartRequired && result) {
|
||||
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}.also(::addPreference)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ data class FilterV2Dto(
|
||||
val id: Int? = null,
|
||||
val name: String? = null,
|
||||
val statements: MutableList<FilterStatementDto> = mutableListOf(),
|
||||
val combination: Int = 0, // FilterCombination = FilterCombination.And,
|
||||
val combination: Int = FilterCombination.And.ordinal,
|
||||
val sortOptions: SortOptions = SortOptions(),
|
||||
val limitTo: Int = 0,
|
||||
) {
|
||||
@@ -17,20 +17,22 @@ data class FilterV2Dto(
|
||||
statements.add(FilterStatementDto(comparison.type, field.type, value))
|
||||
}
|
||||
}
|
||||
fun addStatement(comparison: FilterComparison, field: FilterField, values: java.util.ArrayList<out Any>) {
|
||||
|
||||
fun addStatement(comparison: FilterComparison, field: FilterField, values: List<Any>) {
|
||||
if (values.isNotEmpty()) {
|
||||
statements.add(FilterStatementDto(comparison.type, field.type, values.joinToString(",")))
|
||||
}
|
||||
}
|
||||
|
||||
fun addContainsNotTriple(list: List<Triple<FilterField, java.util.ArrayList<out Any>, ArrayList<Int>>>) {
|
||||
list.map {
|
||||
fun addContainsNotTriple(list: List<Triple<FilterField, List<Any>, List<Int>>>) {
|
||||
list.forEach {
|
||||
addStatement(FilterComparison.Contains, it.first, it.second)
|
||||
addStatement(FilterComparison.NotContains, it.first, it.third)
|
||||
}
|
||||
}
|
||||
fun addPeople(list: List<Pair<FilterField, ArrayList<Int>>>) {
|
||||
list.map {
|
||||
|
||||
fun addPeople(list: List<Pair<FilterField, List<Int>>>) {
|
||||
list.forEach {
|
||||
addStatement(FilterComparison.MustContains, it.first, it.second)
|
||||
}
|
||||
}
|
||||
@@ -38,11 +40,15 @@ data class FilterV2Dto(
|
||||
|
||||
@Serializable
|
||||
data class FilterStatementDto(
|
||||
// todo: Create custom serializator for comparison and field and remove .type extension in Kavita.kt
|
||||
val comparison: Int,
|
||||
val field: Int,
|
||||
val value: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SortOptions(
|
||||
var sortField: Int = SortFieldEnum.AverageRating.type,
|
||||
var isAscending: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -53,20 +59,17 @@ enum class SortFieldEnum(val type: Int) {
|
||||
LastChapterAdded(4),
|
||||
TimeToRead(5),
|
||||
ReleaseYear(6),
|
||||
ReadProgress(7),
|
||||
AverageRating(8),
|
||||
Random(9),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val map = SortFieldEnum.values().associateBy(SortFieldEnum::type)
|
||||
private val map = values().associateBy(SortFieldEnum::type)
|
||||
fun fromInt(type: Int) = map[type]
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SortOptions(
|
||||
var sortField: Int = SortFieldEnum.SortName.type,
|
||||
var isAscending: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class FilterCombination {
|
||||
Or,
|
||||
@@ -101,6 +104,18 @@ enum class FilterField(val type: Int) {
|
||||
ReadTime(23),
|
||||
Path(24),
|
||||
FilePath(25),
|
||||
WantToRead(26),
|
||||
ReadingDate(27),
|
||||
AverageRating(28),
|
||||
Imprint(29),
|
||||
Team(30),
|
||||
Location(31),
|
||||
ReadLast(32),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromType(type: Int): FilterField? = values().find { it.type == type }
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -121,4 +136,46 @@ enum class FilterComparison(val type: Int) {
|
||||
IsAfter(13),
|
||||
IsInLast(14),
|
||||
IsNotInLast(15),
|
||||
IsEmpty(16),
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class PersonSearchDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val role: Int? = null,
|
||||
)
|
||||
|
||||
enum class PersonRole(val id: Int) {
|
||||
Writer(0),
|
||||
Penciller(1),
|
||||
Inker(2),
|
||||
Colorist(3),
|
||||
Letterer(4),
|
||||
CoverArtist(5),
|
||||
Editor(6),
|
||||
Publisher(7),
|
||||
Character(8),
|
||||
Translator(9),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val map = values().associateBy(PersonRole::id)
|
||||
fun fromId(id: Int): PersonRole? = map[id]
|
||||
}
|
||||
}
|
||||
|
||||
fun PersonRole.toFilterField(): FilterField? {
|
||||
return when (this) {
|
||||
PersonRole.Writer -> FilterField.Writers
|
||||
PersonRole.Penciller -> FilterField.Penciller
|
||||
PersonRole.Inker -> FilterField.Inker
|
||||
PersonRole.Colorist -> FilterField.Colorist
|
||||
PersonRole.Letterer -> FilterField.Letterer
|
||||
PersonRole.CoverArtist -> FilterField.CoverArtist
|
||||
PersonRole.Editor -> FilterField.Editor
|
||||
PersonRole.Publisher -> FilterField.Publisher
|
||||
PersonRole.Character -> FilterField.Characters
|
||||
PersonRole.Translator -> FilterField.Translators
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package eu.kanade.tachiyomi.extension.all.kavita.dto
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
interface ConvertibleToSManga {
|
||||
fun toSManga(baseUrl: String, apiUrl: String, apiKey: String): SManga
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class MangaFormat(val format: Int) {
|
||||
Image(0),
|
||||
@@ -10,26 +16,32 @@ enum class MangaFormat(val format: Int) {
|
||||
Epub(3),
|
||||
Pdf(4),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val map = PersonRole.values().associateBy(PersonRole::role)
|
||||
fun fromInt(type: Int) = map[type]
|
||||
private val map = values().associateBy(MangaFormat::format)
|
||||
fun fromInt(type: Int): MangaFormat? = map[type]
|
||||
}
|
||||
}
|
||||
enum class PersonRole(val role: Int) {
|
||||
Other(1),
|
||||
Writer(3),
|
||||
Penciller(4),
|
||||
Inker(5),
|
||||
Colorist(6),
|
||||
Letterer(7),
|
||||
CoverArtist(8),
|
||||
Editor(9),
|
||||
Publisher(10),
|
||||
Character(11),
|
||||
Translator(12),
|
||||
|
||||
@Serializable
|
||||
data class LibraryDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val type: Int,
|
||||
)
|
||||
|
||||
@Serializable // https://github.com/Kareadita/Kavita/blob/develop/API/Entities/Enums/LibraryType.cs
|
||||
enum class LibraryTypeEnum(val type: Int) {
|
||||
Manga(0),
|
||||
Comic(1),
|
||||
Book(2),
|
||||
Image(3),
|
||||
LightNovel(4),
|
||||
ComicVine(5),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val map = PersonRole.values().associateBy(PersonRole::role)
|
||||
private val map = values().associateBy(LibraryTypeEnum::type)
|
||||
fun fromInt(type: Int) = map[type]
|
||||
}
|
||||
}
|
||||
@@ -51,28 +63,127 @@ data class SeriesDto(
|
||||
val created: String? = "",
|
||||
val libraryId: Int,
|
||||
val libraryName: String? = "",
|
||||
val ratings: List<RatingDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SeriesMetadataDto(
|
||||
data class SeriesDetailPlusDto(
|
||||
val seriesId: Int? = null,
|
||||
val libraryName: String? = "",
|
||||
val libraryId: Int? = null,
|
||||
val summary: String? = null,
|
||||
val genres: List<MetadataGenres> = emptyList(),
|
||||
val tags: List<MetadataTag> = emptyList(),
|
||||
val writers: List<MetadataPeople> = emptyList(),
|
||||
val coverArtists: List<MetadataPeople> = emptyList(),
|
||||
val publicationStatus: Int? = null,
|
||||
val averageScore: Float = 0f,
|
||||
val ratings: List<RatingDto> = emptyList(),
|
||||
) {
|
||||
// Helper function to get the library name from SeriesDto if needed
|
||||
fun getLibraryName(seriesDto: SeriesDto?): String? {
|
||||
return if (!libraryName.isNullOrEmpty()) {
|
||||
libraryName
|
||||
} else {
|
||||
seriesDto?.libraryName
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get the library ID from SeriesDto if needed
|
||||
fun getLibraryId(seriesDto: SeriesDto?): Int? {
|
||||
return libraryId ?: seriesDto?.libraryId
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SeriesDetailPlusWrapperDto(
|
||||
val series: SeriesPlus? = null,
|
||||
val ratings: List<RatingDto> = emptyList(),
|
||||
val recommendations: RecommendationsDto? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SeriesPlus(
|
||||
val averageScore: Float = 0f,
|
||||
val summary: String = "",
|
||||
val relations: List<RelationDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RecommendationsDto(
|
||||
val ownedSeries: List<SeriesDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RatingDto(
|
||||
val averageScore: Float = 0f,
|
||||
val favoriteCount: Int = 0,
|
||||
val provider: Int = 0,
|
||||
val authority: Int = 0,
|
||||
val providerUrl: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RelationDto(
|
||||
val seriesName: RelationName,
|
||||
val relation: Int,
|
||||
val aniListId: Int? = null,
|
||||
val malId: Int? = null,
|
||||
val provider: Int? = null,
|
||||
val plusMediaFormat: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RelationName(
|
||||
val englishTitle: String? = null,
|
||||
val romajiTitle: String? = null,
|
||||
val nativeTitle: String? = null,
|
||||
val preferredTitle: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RelatedSeriesResponse(
|
||||
val sourceSeriesId: Int,
|
||||
val sequels: List<RelatedSeriesItem> = emptyList(),
|
||||
val prequels: List<RelatedSeriesItem> = emptyList(),
|
||||
val spinOffs: List<RelatedSeriesItem> = emptyList(),
|
||||
val adaptations: List<RelatedSeriesItem> = emptyList(),
|
||||
val sideStories: List<RelatedSeriesItem> = emptyList(),
|
||||
val characters: List<RelatedSeriesItem> = emptyList(),
|
||||
val contains: List<RelatedSeriesItem> = emptyList(),
|
||||
val others: List<RelatedSeriesItem> = emptyList(),
|
||||
val alternativeSettings: List<RelatedSeriesItem> = emptyList(),
|
||||
val alternativeVersions: List<RelatedSeriesItem> = emptyList(),
|
||||
val doujinshis: List<RelatedSeriesItem> = emptyList(),
|
||||
val parent: List<RelatedSeriesItem> = emptyList(),
|
||||
val editions: List<RelatedSeriesItem> = emptyList(),
|
||||
val annuals: List<RelatedSeriesItem> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RelatedSeriesItem(
|
||||
val id: Int,
|
||||
val summary: String? = "",
|
||||
val writers: List<Person> = emptyList(),
|
||||
val coverArtists: List<Person> = emptyList(),
|
||||
val genres: List<Genres> = emptyList(),
|
||||
val seriesId: Int,
|
||||
val ageRating: Int,
|
||||
val publicationStatus: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Genres(
|
||||
val title: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Person(
|
||||
val name: String,
|
||||
val coverImage: String? = null,
|
||||
val libraryId: Int,
|
||||
val libraryName: String? = null,
|
||||
val format: Int = 0,
|
||||
) : ConvertibleToSManga {
|
||||
override fun toSManga(baseUrl: String, apiUrl: String, apiKey: String): SManga = SManga.create().apply {
|
||||
title = name
|
||||
url = "$baseUrl/Series/$id"
|
||||
thumbnail_url = when {
|
||||
!coverImage.isNullOrBlank() && (coverImage.startsWith("http://") || coverImage.startsWith("https://")) -> coverImage
|
||||
else -> "$apiUrl/image/series-cover?seriesId=$id&apiKey=$apiKey"
|
||||
}
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AuthorDto(
|
||||
val name: String,
|
||||
val role: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -85,27 +196,33 @@ data class VolumeDto(
|
||||
val lastModified: String,
|
||||
val created: String,
|
||||
val seriesId: Int,
|
||||
val coverImage: String,
|
||||
val chapters: List<ChapterDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class ChapterType {
|
||||
Regular, // chapter with volume information
|
||||
LooseLeaf, // chapter without volume information
|
||||
Chapter, // manga chapter without volume information
|
||||
SingleFileVolume,
|
||||
Special,
|
||||
Issue, // For comics
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun of(chapter: ChapterDto, volume: VolumeDto): ChapterType =
|
||||
if (volume.number == 100_000) {
|
||||
Special
|
||||
} else if (volume.number == -100_000) {
|
||||
LooseLeaf
|
||||
} else {
|
||||
if (chapter.number == "-100000") {
|
||||
SingleFileVolume
|
||||
} else {
|
||||
Regular
|
||||
fun of(chapter: ChapterDto, volume: VolumeDto, libraryType: LibraryTypeEnum? = null): ChapterType =
|
||||
when {
|
||||
volume.number == 100_000 -> Special
|
||||
volume.number == -100_000 -> when (libraryType) {
|
||||
LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue
|
||||
LibraryTypeEnum.Manga, LibraryTypeEnum.LightNovel, LibraryTypeEnum.Book -> Chapter
|
||||
else -> Chapter // Default to Chapter for other types
|
||||
}
|
||||
chapter.number == "-100000" -> SingleFileVolume
|
||||
else -> when (libraryType) {
|
||||
LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue
|
||||
LibraryTypeEnum.Manga, LibraryTypeEnum.LightNovel, LibraryTypeEnum.Book -> Chapter
|
||||
else -> Regular
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,10 +236,12 @@ data class ChapterDto(
|
||||
val pages: Int,
|
||||
val isSpecial: Boolean,
|
||||
val title: String,
|
||||
val titleName: String,
|
||||
val pagesRead: Int,
|
||||
val coverImageLocked: Boolean,
|
||||
val volumeId: Int,
|
||||
val created: String,
|
||||
val lastModifiedUtc: String,
|
||||
val files: List<FileDto>? = null,
|
||||
) {
|
||||
val fileCount: Int
|
||||
@@ -133,3 +252,37 @@ data class ChapterDto(
|
||||
data class FileDto(
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ReadingListDto(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val coverImage: String? = null,
|
||||
val promoted: Boolean = false,
|
||||
val summary: String?,
|
||||
val itemCount: Int,
|
||||
val startingYear: Int,
|
||||
val startingMonth: Int,
|
||||
val endingYear: Int,
|
||||
val endingMonth: Int,
|
||||
val ownerUserName: String?,
|
||||
@SerialName("items") val items: List<ReadingListItemDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ReadingListItemDto(
|
||||
val id: Int,
|
||||
val order: Int,
|
||||
val chapterId: Int?,
|
||||
val seriesId: Int,
|
||||
val seriesName: String,
|
||||
val chapterNumber: String?,
|
||||
val volumeNumber: String?,
|
||||
val chapterTitleName: String?,
|
||||
val volumeId: Int?,
|
||||
val title: String?,
|
||||
val summary: String?,
|
||||
val releaseDate: String?,
|
||||
val libraryName: String?,
|
||||
@SerialName("readingListId") val readingListId: Int,
|
||||
)
|
||||
|
||||
@@ -1,100 +1,35 @@
|
||||
// Metadata descriptors for filters and categorization
|
||||
package eu.kanade.tachiyomi.extension.all.kavita.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
/**
|
||||
* This file contains all class for filtering
|
||||
* */
|
||||
|
||||
@Serializable
|
||||
data class MetadataGenres(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
)
|
||||
data class MetadataGenres(val id: Int, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class MetadataPeople(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val role: Int,
|
||||
val role: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MetadataPubStatus(
|
||||
val value: Int,
|
||||
val title: String,
|
||||
)
|
||||
data class MetadataPubStatus(val value: Int, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class MetadataTag(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
)
|
||||
data class MetadataTag(val id: Int, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class MetadataAgeRatings(
|
||||
val value: Int,
|
||||
val title: String,
|
||||
)
|
||||
data class MetadataAgeRatings(val value: Int, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class MetadataLanguages(
|
||||
val isoCode: String,
|
||||
val title: String,
|
||||
)
|
||||
data class MetadataLanguages(val isoCode: String, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class MetadataLibrary(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val type: Int,
|
||||
)
|
||||
data class MetadataLibrary(val id: Int, val name: String, val type: Int)
|
||||
|
||||
@Serializable
|
||||
data class MetadataCollections(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
)
|
||||
|
||||
data class MetadataPayload(
|
||||
val forceUseMetadataPayload: Boolean = true,
|
||||
var sorting: Int = 1,
|
||||
var sorting_asc: Boolean = true,
|
||||
var readStatus: ArrayList<String> = arrayListOf<String>(),
|
||||
val readStatusList: List<String> = listOf("notRead", "inProgress", "read"),
|
||||
// _i = included, _e = excluded
|
||||
var genres_i: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var genres_e: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var tags_i: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var tags_e: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var ageRating_i: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var ageRating_e: ArrayList<Int> = arrayListOf<Int>(),
|
||||
|
||||
var formats: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var collections_i: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var collections_e: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var userRating: Int = 0,
|
||||
var people: ArrayList<Int> = arrayListOf<Int>(),
|
||||
// _i = included, _e = excluded
|
||||
var language_i: ArrayList<String> = arrayListOf<String>(),
|
||||
var language_e: ArrayList<String> = arrayListOf<String>(),
|
||||
|
||||
var libraries_i: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var libraries_e: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var pubStatus: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var seriesNameQuery: String = "",
|
||||
var releaseYearRangeMin: Int = 0,
|
||||
var releaseYearRangeMax: Int = 0,
|
||||
|
||||
var peopleWriters: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peoplePenciller: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleInker: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peoplePeoplecolorist: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleLetterer: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleCoverArtist: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleEditor: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peoplePublisher: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleCharacter: ArrayList<Int> = arrayListOf<Int>(),
|
||||
var peopleTranslator: ArrayList<Int> = arrayListOf<Int>(),
|
||||
)
|
||||
data class MetadataCollections(val id: Int, val title: String)
|
||||
|
||||
@Serializable
|
||||
data class SmartFilter(
|
||||
@@ -102,3 +37,40 @@ data class SmartFilter(
|
||||
val name: String,
|
||||
val filter: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MetadataPayload(
|
||||
val forceUseMetadataPayload: Boolean = true,
|
||||
var sorting: Int = 1,
|
||||
var sorting_asc: Boolean = true,
|
||||
var readStatus: ArrayList<String> = arrayListOf("notRead", "inProgress", "read"),
|
||||
var genres_i: ArrayList<Int> = arrayListOf(),
|
||||
var genres_e: ArrayList<Int> = arrayListOf(),
|
||||
var tags_i: ArrayList<Int> = arrayListOf(),
|
||||
var tags_e: ArrayList<Int> = arrayListOf(),
|
||||
var ageRating_i: ArrayList<Int> = arrayListOf(),
|
||||
var ageRating_e: ArrayList<Int> = arrayListOf(),
|
||||
var formats: ArrayList<Int> = arrayListOf(),
|
||||
var collections_i: ArrayList<Int> = arrayListOf(),
|
||||
var collections_e: ArrayList<Int> = arrayListOf(),
|
||||
var userRating: Int = 0,
|
||||
var people: ArrayList<Int> = arrayListOf(),
|
||||
var language_i: ArrayList<String> = arrayListOf(),
|
||||
var language_e: ArrayList<String> = arrayListOf(),
|
||||
var libraries_i: ArrayList<Int> = arrayListOf(),
|
||||
var libraries_e: ArrayList<Int> = arrayListOf(),
|
||||
var pubStatus: ArrayList<Int> = arrayListOf(),
|
||||
var seriesNameQuery: String = "",
|
||||
var releaseYearRangeMin: Int = 0,
|
||||
var releaseYearRangeMax: Int = 0,
|
||||
var peopleWriters: ArrayList<Int> = arrayListOf(),
|
||||
var peoplePenciller: ArrayList<Int> = arrayListOf(),
|
||||
var peopleInker: ArrayList<Int> = arrayListOf(),
|
||||
var peopleColorist: ArrayList<Int> = arrayListOf(),
|
||||
var peopleLetterer: ArrayList<Int> = arrayListOf(),
|
||||
var peopleCoverArtist: ArrayList<Int> = arrayListOf(),
|
||||
var peopleEditor: ArrayList<Int> = arrayListOf(),
|
||||
var peoplePublisher: ArrayList<Int> = arrayListOf(),
|
||||
var peopleCharacter: ArrayList<Int> = arrayListOf(),
|
||||
var peopleTranslator: ArrayList<Int> = arrayListOf(),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Generic response and authentication models
|
||||
package eu.kanade.tachiyomi.extension.all.kavita.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable // Used to process login
|
||||
@Serializable
|
||||
data class AuthenticationDto(
|
||||
val username: String,
|
||||
val token: String,
|
||||
@@ -20,9 +21,9 @@ data class PaginationInfo(
|
||||
@Serializable
|
||||
data class ServerInfoDto(
|
||||
val installId: String,
|
||||
val os: String,
|
||||
// val os: String,
|
||||
val isDocker: Boolean,
|
||||
val dotnetVersion: String,
|
||||
// val dotnetVersion: String,
|
||||
val kavitaVersion: String,
|
||||
val numOfCores: Int,
|
||||
// val numOfCores: Int,
|
||||
)
|
||||
|
||||