diff --git a/src/all/kavita/AndroidManifest.xml b/src/all/kavita/AndroidManifest.xml deleted file mode 100644 index 8072ee00d..000000000 --- a/src/all/kavita/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/kavita/CHANGELOG.md b/src/all/kavita/CHANGELOG.md deleted file mode 100644 index 2843891e9..000000000 --- a/src/all/kavita/CHANGELOG.md +++ /dev/null @@ -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 diff --git a/src/all/kavita/README.md b/src/all/kavita/README.md deleted file mode 100644 index 79f467edd..000000000 --- a/src/all/kavita/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Kavita - -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) -- [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). - -## FAQ - -### Why do I see no manga? -Kavita 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. - -### The Kavita extension stopped working? -Make sure that your Kavita 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 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`. - -## 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. diff --git a/src/all/kavita/assets/i18n/messages_en.properties b/src/all/kavita/assets/i18n/messages_en.properties deleted file mode 100644 index 52f341d65..000000000 --- a/src/all/kavita/assets/i18n/messages_en.properties +++ /dev/null @@ -1,18 +0,0 @@ -login_errors_failed_login=Login failed. Something went wrong -login_errors_header_token_empty="Error: The JSON Web Token is empty.\nTry opening the extension first." -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_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 -pref_opds_badformed_url=Incorrect OPDS address. Please copy it from User settings \u2192 3rd party apps \u2192 OPDS URL -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. -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. -http_errors_500=Something went wrong -http_errors_401=There was an error logging in. Try again or reload the app diff --git a/src/all/kavita/assets/i18n/messages_es_es.properties b/src/all/kavita/assets/i18n/messages_es_es.properties deleted file mode 100644 index 8f6d3058c..000000000 --- a/src/all/kavita/assets/i18n/messages_es_es.properties +++ /dev/null @@ -1,18 +0,0 @@ -pref_customsource_title=Nombre de la instancia -pref_edit_customsource_summary=Aqui puedes cambiar el nombre de la instancia.\nPuedes escribir un nombre descriptivo que identifique esta url/instancia -restartapp_settings=Reinicia la aplicación para aplicar los cambios -version_exceptions_chapters_parse=Algo ha ido mal al procesar los capitulos. Envia los registros de fallo a los desarrolladores de Kavita -check_version=Comprueba que tienes tanto Kavita como la extension actualizada. (Version minima: 0.7.8)\nSi el problema persiste, reportalo a los desarrolladores de Kavita aportando los registros de fallo. -version_exceptions_smart_filter=Fallo al decodificar los filtros inteligentes. Aseg\u00FArate que estas al menos en la version 0.7.11 de Kavita -http_errors_500=Algo ha ido mal -http_errors_401=Ha habido un error al iniciar sesi\u00F3n. Prueba otra vez o reinicia la aplicaci\u00F3n -pref_opds_summary=La url del OPDS copiada de la configuraci\u00F3n del usuario. Debe incluir la direcci\u00F3n y la clave api al final. -pref_filters_summary=Mostrar estos filtros en la lista de filtros -pref_filters_title=Filtros por defecto -pref_opds_badformed_url=La direcci\u00F3n OPDS no es correcta. Por favor, c\u00F3piela desde la Configuraci\u00F3n de usuario-> aplicaciones de terceros -> url de OPDS -login_errors_parse_tokendto=Se ha producido un error al procesar el token de autenticaci\u00F3n -pref_opds_duplicated_source_url=Url est\u00E1 configurado en una fuente diferente -> -pref_opds_must_setup_address=Debe configurar la direcci\u00F3n para comunicarse con Kavita -login_errors_failed_login=Error en el inicio de sesi\u00F3n. Algo ha ido mal -login_errors_header_token_empty="Error: el token jwt est\u00E1 vac\u00EDo.\nIntente abrir primero la extensi\u00F3n" -login_errors_invalid_url=URL no v\u00E1lida: diff --git a/src/all/kavita/assets/i18n/messages_fr_fr.properties b/src/all/kavita/assets/i18n/messages_fr_fr.properties deleted file mode 100644 index bd29b962a..000000000 --- a/src/all/kavita/assets/i18n/messages_fr_fr.properties +++ /dev/null @@ -1,20 +0,0 @@ - - -version_exceptions_chapters_parse=Exception non trait\u00E9e durant l'analyse des chapitres. Envoyez les journaux 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. -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 -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. -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_invalid_url=URL invalide\u202F: -http_errors_401=Il y a eu une erreur. Essayez de nouveau ou rechargez l'application diff --git a/src/all/kavita/assets/i18n/messages_nb_no.properties b/src/all/kavita/assets/i18n/messages_nb_no.properties deleted file mode 100644 index c2e0a530d..000000000 --- a/src/all/kavita/assets/i18n/messages_nb_no.properties +++ /dev/null @@ -1,21 +0,0 @@ - - -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. -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 -login_errors_parse_tokendto=Kunne ikke tolke identifiseringssymbolet -login_errors_failed_login=Innlogging mislyktes. Noe gikk galt. -http_errors_500=Noe gikk galt -login_errors_header_token_empty="Feil: JSON-nettsymbol er tomt.\nPr\u00F8v \u00E5 \u00E5pne utvidelsen f\u00F8rst." -login_errors_invalid_url=Ugyldig nettadresse: -version_exceptions_chapters_parse=Uh\u00E5ndtert unntak i tolking av kapitler. Send loggene dine til Kavita-utviklerne. -version_exceptions_smart_filter=Kunne ikke dekode smartfilter. Forsikre deg om at du bruker Kavita versjon 0.7.11 eller nyere. -pref_opds_summary=OPDS-nettadressen kopiert fra brukerinnstillingene. Denne skal inkludere med adressen og slutte med API-n\u00F8kkelen. -check_version=Forsikre deg om at b\u00E5de utvidelsen og Kavita er av nyeste versjon. (Ihvertfall 0.7.8)\nHvis problemet vedvarer kan du rapportere det til Kavita-utviklerne med tilh\u00F8rende loggf\u00F8ring. -pref_opds_badformed_url=OPDS-adressen er ikke riktig. Kopier den fra brukerinnstillinger -> tredjepartsprogrammer -> OPDS-nettadresse -pref_opds_duplicated_source_url=Nettadressen er satt opp i en annen instans -> -pref_opds_must_setup_address=Du m\u00E5 sette opp adressen som skal kommunisere med Kavita -http_errors_401=Feil med innlogging. Pr\u00F8v \u00E5 laste inn p\u00E5 ny, eller start programmet p\u00E5 ny. diff --git a/src/all/kavita/build.gradle b/src/all/kavita/build.gradle deleted file mode 100644 index bb2376766..000000000 --- a/src/all/kavita/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -ext { - extName = 'Kavita' - extClass = '.KavitaFactory' - extVersionCode = 13 -} - -dependencies { - implementation 'info.debatty:java-string-similarity:2.0.0' - implementation(project(':lib:i18n')) -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/kavita/res/mipmap-hdpi/ic_launcher.png b/src/all/kavita/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 16cee6c3e..000000000 Binary files a/src/all/kavita/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/kavita/res/mipmap-mdpi/ic_launcher.png b/src/all/kavita/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ebcbc75a2..000000000 Binary files a/src/all/kavita/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/kavita/res/mipmap-xhdpi/ic_launcher.png b/src/all/kavita/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 39ca4ccaa..000000000 Binary files a/src/all/kavita/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/kavita/res/mipmap-xxhdpi/ic_launcher.png b/src/all/kavita/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 98f3ea1ff..000000000 Binary files a/src/all/kavita/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/kavita/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/kavita/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dc7d8341f..000000000 Binary files a/src/all/kavita/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Filters.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Filters.kt deleted file mode 100644 index 80d0e4075..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Filters.kt +++ /dev/null @@ -1,112 +0,0 @@ -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( - "Minimum Rating", - arrayOf( - "Any", - "1 star", - "2 stars", - "3 stars", - "4 stars", - "5 stars", - ), - ) -class SmartFiltersFilter(smartFilters: Array) : - Filter.Select("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters) -class SortFilter(sortables: Array) : Filter.Sort("Sort by", sortables, Selection(0, true)) - -val sortableList = listOf( - Pair("Sort name", 1), - Pair("Created", 2), - Pair("Last modified", 3), - Pair("Item added", 4), - Pair("Time to Read", 5), - Pair("Release year", 6), -) - -class StatusFilter(name: String) : Filter.CheckBox(name, false) -class StatusFilterGroup(filters: List) : - Filter.Group("Status", filters) - -class ReleaseYearRange(name: String) : Filter.Text(name) -class ReleaseYearRangeGroup(filters: List) : - Filter.Group("Release Year", filters) -class GenreFilter(name: String) : Filter.TriState(name) -class GenreFilterGroup(genres: List) : - Filter.Group("Genres", genres) - -class TagFilter(name: String) : Filter.TriState(name) -class TagFilterGroup(tags: List) : Filter.Group("Tags", tags) - -class AgeRatingFilter(name: String) : Filter.TriState(name) -class AgeRatingFilterGroup(ageRatings: List) : - Filter.Group("Age Rating", ageRatings) - -class FormatFilter(name: String) : Filter.CheckBox(name, false) -class FormatsFilterGroup(formats: List) : - Filter.Group("Formats", formats) - -class CollectionFilter(name: String) : Filter.TriState(name) -class CollectionFilterGroup(collections: List) : - Filter.Group("Collection", collections) - -class LanguageFilter(name: String) : Filter.TriState(name) -class LanguageFilterGroup(languages: List) : - Filter.Group("Language", languages) - -class LibraryFilter(library: String) : Filter.TriState(library) -class LibrariesFilterGroup(libraries: List) : - Filter.Group("Libraries", libraries) - -class PubStatusFilter(name: String) : Filter.CheckBox(name, false) -class PubStatusFilterGroup(status: List) : - Filter.Group("Publication Status", status) - -class PeopleHeaderFilter(name: String) : - Filter.Header(name) -class PeopleSeparatorFilter : - Filter.Separator() - -class WriterPeopleFilter(name: String) : Filter.CheckBox(name, false) -class WriterPeopleFilterGroup(peoples: List) : - Filter.Group("Writer", peoples) - -class PencillerPeopleFilter(name: String) : Filter.CheckBox(name, false) -class PencillerPeopleFilterGroup(peoples: List) : - Filter.Group("Penciller", peoples) - -class InkerPeopleFilter(name: String) : Filter.CheckBox(name, false) -class InkerPeopleFilterGroup(peoples: List) : - Filter.Group("Inker", peoples) - -class ColoristPeopleFilter(name: String) : Filter.CheckBox(name, false) -class ColoristPeopleFilterGroup(peoples: List) : - Filter.Group("Colorist", peoples) - -class LettererPeopleFilter(name: String) : Filter.CheckBox(name, false) -class LettererPeopleFilterGroup(peoples: List) : - Filter.Group("Letterer", peoples) - -class CoverArtistPeopleFilter(name: String) : Filter.CheckBox(name, false) -class CoverArtistPeopleFilterGroup(peoples: List) : - Filter.Group("Cover Artist", peoples) - -class EditorPeopleFilter(name: String) : Filter.CheckBox(name, false) -class EditorPeopleFilterGroup(peoples: List) : - Filter.Group("Editor", peoples) - -class PublisherPeopleFilter(name: String) : Filter.CheckBox(name, false) -class PublisherPeopleFilterGroup(peoples: List) : - Filter.Group("Publisher", peoples) - -class CharacterPeopleFilter(name: String) : Filter.CheckBox(name, false) -class CharacterPeopleFilterGroup(peoples: List) : - Filter.Group("Character", peoples) - -class TranslatorPeopleFilter(name: String) : Filter.CheckBox(name, false) -class TranslatorPeopleFilterGroup(peoples: List) : - Filter.Group("Translator", peoples) diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt deleted file mode 100644 index 079e457a9..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt +++ /dev/null @@ -1,1264 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita - -import android.app.Application -import android.content.SharedPreferences -import android.text.InputType -import android.util.Log -import android.widget.Toast -import androidx.preference.EditTextPreference -import androidx.preference.MultiSelectListPreference -import eu.kanade.tachiyomi.AppInfo -import eu.kanade.tachiyomi.extension.all.kavita.dto.AuthenticationDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.FilterComparison -import eu.kanade.tachiyomi.extension.all.kavita.dto.FilterField -import eu.kanade.tachiyomi.extension.all.kavita.dto.FilterStatementDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.FilterV2Dto -import eu.kanade.tachiyomi.extension.all.kavita.dto.MangaFormat -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataAgeRatings -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataCollections -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataGenres -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataLanguages -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataLibrary -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataPayload -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataPeople -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataPubStatus -import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataTag -import eu.kanade.tachiyomi.extension.all.kavita.dto.PersonRole -import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesMetadataDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.ServerInfoDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.SmartFilter -import eu.kanade.tachiyomi.extension.all.kavita.dto.SortFieldEnum -import eu.kanade.tachiyomi.extension.all.kavita.dto.SortOptions -import eu.kanade.tachiyomi.extension.all.kavita.dto.VolumeDto -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.UnmeteredSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_EXCLUDE -import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_INCLUDE -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.put -import okhttp3.Dns -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import rx.Observable -import rx.Single -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.io.IOException -import java.net.ConnectException -import java.security.MessageDigest -import java.util.Locale - -class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() { - private val helper = KavitaHelper() - override val client: OkHttpClient = - network.client.newBuilder() - .dns(Dns.SYSTEM) - .build() - override val id by lazy { - val key = "${"kavita_$suffix"}/all/$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 preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - override val name = "${KavitaInt.KAVITA_NAME} (${preferences.getString(KavitaConstants.customSourceNamePref, suffix)})" - override val lang = "all" - override val supportsLatest = true - private val apiUrl by lazy { getPrefApiUrl() } - override val baseUrl by lazy { getPrefBaseUrl() } - private val address by lazy { getPrefAddress() } // Address for the Kavita OPDS url. Should be http(s)://host:(port)/api/opds/api-key - private val apiKey by lazy { getPrefApiKey() } - private var jwtToken = "" // * JWT Token for authentication with the server. Stored in memory. - private val LOG_TAG = """Kavita_${"[$suffix]_" + preferences.getString(KavitaConstants.customSourceNamePref, "[$suffix]")!!.replace(' ', '_')}""" - private var isLogged = false // Used to know if login was correct and not send login requests anymore - private val json: Json by injectLazy() - - private var series = emptyList() // Acts as a cache - - private inline fun Response.parseAs(): T = - use { - if (it.code == 401) { - Log.e(LOG_TAG, "Http error 401 - Not authorized: ${it.request.url}") - Throwable("Http error 401 - Not authorized: ${it.request.url}") - } - - if (it.peekBody(Long.MAX_VALUE).string().isEmpty()) { - Log.e(LOG_TAG, "Empty body String for request url: ${it.request.url}") - throw EmptyRequestBody( - "Body of the response is empty. RequestUrl=${it.request.url}\nPlease check your kavita instance is up to date", - Throwable("Error. Request body is empty"), - ) - } - json.decodeFromString(it.body.string()) - } - - /** - * Custom implementation for fetch popular, latest and search - * Handles and logs errors to provide a more detailed exception to the users. - */ - private fun fetch(request: Request): Observable { - return client.newCall(request) - .asObservableSuccess() - .onErrorResumeNext { throwable -> - // Get Http code - val field = throwable.javaClass.getDeclaredField("code") - field.isAccessible = true // Make the field accessible - try { - var code = field.get(throwable) // Get the value of the code property - Log.e(LOG_TAG, "Error fetching manga: ${throwable.message}", throwable) - if (code as Int !in intArrayOf(401, 201, 500)) { - code = 500 - } - return@onErrorResumeNext Observable.error(IOException("Http Error: $code\n ${helper.intl["http_errors_$code"]}\n${helper.intl["check_version"]}")) - } catch (e: Exception) { - Log.e(LOG_TAG, e.toString(), e) - return@onErrorResumeNext Observable.error(e) - } - } - .map { response -> - popularMangaParse(response) - } - } - - override fun fetchPopularManga(page: Int) = - fetch(popularMangaRequest(page)) - - override fun fetchLatestUpdates(page: Int) = - fetch(latestUpdatesRequest(page)) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = - fetch(searchMangaRequest(page, query, filters)) - - override fun popularMangaParse(response: Response): MangasPage { - try { - val result = response.parseAs>() - series = result - val mangaList = result.map { item -> helper.createSeriesDto(item, apiUrl, apiKey) } - return MangasPage(mangaList, helper.hasNextPage(response)) - } catch (e: Exception) { - Log.e(LOG_TAG, "Unhandled exception", e) - throw IOException(helper.intl["check_version"]) - } - } - - override fun popularMangaRequest(page: Int): Request { - if (!isLogged) { - doLogin() - } - val payload = buildFilterBody(currentFilter) - return POST( - "$apiUrl/series/all-v2?pageNumber=$page&pageSize=20", - headersBuilder().build(), - payload.toRequestBody(JSON_MEDIA_TYPE), - ) - } - - override fun latestUpdatesRequest(page: Int): Request { - if (!isLogged) { - doLogin() - } - // Hardcode exclude epubs - val filter = FilterV2Dto(sortOptions = SortOptions(SortFieldEnum.LastChapterAdded.type, false)) - filter.statements.add(FilterStatementDto(FilterComparison.NotContains.type, FilterField.Formats.type, "3")) - val payload = json.encodeToJsonElement(filter).toString() - return POST( - "$apiUrl/series/all-v2?pageNumber=$page&pageSize=20", - headersBuilder().build(), - payload.toRequestBody(JSON_MEDIA_TYPE), - ) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newFilter = MetadataPayload() // need to reset it or will double - val smartFilterFilter = filters.find { it is SmartFiltersFilter } - // If a SmartFilter selected, apply its filter and return that - if (smartFilterFilter?.state != 0 && smartFilterFilter != null) { - val index = try { - smartFilterFilter?.state as Int - 1 - } catch (e: Exception) { - Log.e(LOG_TAG, e.toString(), e) - 0 - } - - val filter: SmartFilter = smartFilters[index] - val payload = buildJsonObject { - put("EncodedFilter", filter.filter) - } - // Decode selected filters - val request = POST( - "$apiUrl/filter/decode", - headersBuilder().build(), - payload.toString().toRequestBody(JSON_MEDIA_TYPE), - ) - client.newCall(request).execute().use { - if (it.code == 200) { - // Hardcode exclude epub - val decoded_filter = json.decodeFromString(it.body.string()) - decoded_filter.statements.add(FilterStatementDto(FilterComparison.NotContains.type, FilterField.Formats.type, "3")) - - // Make request with selected filters - return POST( - "$apiUrl/series/all-v2?pageNumber=$page&pageSize=20", - headersBuilder().build(), - json.encodeToJsonElement(decoded_filter).toString().toRequestBody(JSON_MEDIA_TYPE), - ) - } else { - Log.e(LOG_TAG, "Failed to decode SmartFilter: ${it.code}\n" + it.message) - throw IOException(helper.intl["version_exceptions_smart_filter"]) - } - } - } - // Else apply user filters - - filters.forEach { filter -> - when (filter) { - is SortFilter -> { - if (filter.state != null) { - newFilter.sorting = filter.state!!.index + 1 - newFilter.sorting_asc = filter.state!!.ascending - } - } - - is StatusFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.readStatus.add(content.name) - } - } - } - - is ReleaseYearRangeGroup -> { - filter.state.forEach { content -> - if (content.state.isNotEmpty()) { - if (content.name == "Min") { - newFilter.releaseYearRangeMin = content.state.toInt() - } - if (content.name == "Max") { - newFilter.releaseYearRangeMax = content.state.toInt() - } - } - } - } - - is GenreFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.genres_i.add(genresListMeta.find { it.title == content.name }!!.id) - } else if (content.state == STATE_EXCLUDE) { - newFilter.genres_e.add(genresListMeta.find { it.title == content.name }!!.id) - } - } - } - - is UserRating -> { - newFilter.userRating = filter.state - } - - is TagFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.tags_i.add(tagsListMeta.find { it.title == content.name }!!.id) - } else if (content.state == STATE_EXCLUDE) { - newFilter.tags_e.add(tagsListMeta.find { it.title == content.name }!!.id) - } - } - } - - is AgeRatingFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.ageRating_i.add(ageRatingsListMeta.find { it.title == content.name }!!.value) - } else if (content.state == STATE_EXCLUDE) { - newFilter.ageRating_e.add(ageRatingsListMeta.find { it.title == content.name }!!.value) - } - } - } - - is FormatsFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.formats.add(MangaFormat.valueOf(content.name).ordinal) - } - } - } - - is CollectionFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.collections_i.add(collectionsListMeta.find { it.title == content.name }!!.id) - } else if (content.state == STATE_EXCLUDE) { - newFilter.collections_e.add(collectionsListMeta.find { it.title == content.name }!!.id) - } - } - } - - is LanguageFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.language_i.add(languagesListMeta.find { it.title == content.name }!!.isoCode) - } else if (content.state == STATE_EXCLUDE) { - newFilter.language_e.add(languagesListMeta.find { it.title == content.name }!!.isoCode) - } - } - } - - is LibrariesFilterGroup -> { - filter.state.forEach { content -> - if (content.state == STATE_INCLUDE) { - newFilter.libraries_i.add(libraryListMeta.find { it.name == content.name }!!.id) - } else if (content.state == STATE_EXCLUDE) { - newFilter.libraries_e.add(libraryListMeta.find { it.name == content.name }!!.id) - } - } - } - - is PubStatusFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.pubStatus.add(pubStatusListMeta.find { it.title == content.name }!!.value) - } - } - } - - is WriterPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleWriters.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is PencillerPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peoplePenciller.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is InkerPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleInker.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is ColoristPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peoplePeoplecolorist.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is LettererPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleLetterer.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is CoverArtistPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleCoverArtist.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is EditorPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleEditor.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is PublisherPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peoplePublisher.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is CharacterPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleCharacter.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - is TranslatorPeopleFilterGroup -> { - filter.state.forEach { content -> - if (content.state) { - newFilter.peopleTranslator.add(peopleListMeta.find { it.name == content.name }!!.id) - } - } - } - - else -> {} - } - } - newFilter.seriesNameQuery = query - currentFilter = newFilter - return popularMangaRequest(page) - } - - /* - * MANGA DETAILS (metadata about series) - * **/ - - override fun fetchMangaDetails(manga: SManga): Observable { - val serieId = helper.getIdFromUrl(manga.url) - return client.newCall(GET("$apiUrl/series/metadata?seriesId=$serieId", headersBuilder().build())) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val serieId = helper.getIdFromUrl(manga.url) - val foundSerie = series.find { dto -> dto.id == serieId } - return GET( - "$baseUrl/library/${foundSerie!!.libraryId}/series/$serieId", - headersBuilder().build(), - ) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.parseAs() - - val existingSeries = series.find { dto -> dto.id == result.seriesId } - if (existingSeries != null) { - val manga = helper.createSeriesDto(existingSeries, apiUrl, apiKey) - manga.url = "$apiUrl/Series/${result.seriesId}" - manga.artist = result.coverArtists.joinToString { it.name } - manga.description = result.summary - manga.author = result.writers.joinToString { it.name } - manga.genre = result.genres.joinToString { it.title } - manga.thumbnail_url = "$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey" - - return manga - } - val serieDto = client.newCall(GET("$apiUrl/Series/${result.seriesId}", headersBuilder().build())) - .execute() - .parseAs() - - return SManga.create().apply { - url = "$apiUrl/Series/${result.seriesId}" - artist = result.coverArtists.joinToString { it.name } - description = result.summary - author = result.writers.joinToString { it.name } - genre = result.genres.joinToString { it.title } - title = serieDto.name - thumbnail_url = "$apiUrl/image/series-cover?seriesId=${result.seriesId}&apiKey=$apiKey" - status = when (result.publicationStatus) { - 4 -> SManga.PUBLISHING_FINISHED - 2 -> SManga.COMPLETED - 0 -> SManga.ONGOING - 3 -> SManga.CANCELLED - 1 -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - } - } - - /* - * CHAPTER LIST - * **/ - override fun chapterListRequest(manga: SManga): Request { - val url = "$apiUrl/Series/volumes?seriesId=${helper.getIdFromUrl(manga.url)}" - return GET(url, headersBuilder().build()) - } - - override fun chapterListParse(response: Response): List { - try { - val volumes = response.parseAs>() - val allChapterList = mutableListOf() - volumes.forEach { volume -> - run { - if (volume.number == 0) { - // Regular chapters - volume.chapters.map { - allChapterList.add(helper.chapterFromObject(it)) - } - } else { - // Volume chapter - volume.chapters.map { - allChapterList.add(helper.chapterFromVolume(it, volume)) - } - } - } - } - - allChapterList.sortWith(KavitaHelper.CompareChapters) - return allChapterList - } catch (e: Exception) { - Log.e(LOG_TAG, "Unhandled exception parsing chapters. Send logs to kavita devs", e) - throw IOException(helper.intl["version_exceptions_chapters_parse"]) - } - } - - /** - * Fetches the "url" of each page from the chapter - * **/ - override fun pageListRequest(chapter: SChapter): Request { - return GET("$apiUrl/${chapter.url}", headersBuilder().build()) - } - - override fun fetchPageList(chapter: SChapter): Observable> { - val chapterId = chapter.url - val numPages = chapter.scanlator?.replace(" pages", "")?.toInt() - val numPages2 = "$numPages".toInt() - 1 - val pages = mutableListOf() - for (i in 0..numPages2) { - pages.add( - Page( - index = i, - imageUrl = "$apiUrl/Reader/image?chapterId=$chapterId&page=$i&extractPdf=true&apiKey=$apiKey", - ), - ) - } - return Observable.just(pages) - } - - override fun latestUpdatesParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - - override fun pageListParse(response: Response): List = - throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String = "" - - /* - * FILTERING - **/ - - private var currentFilter: MetadataPayload = MetadataPayload() - - /** Some variable names already exist. im not good at naming add Meta suffix */ - private var genresListMeta = emptyList() - private var tagsListMeta = emptyList() - private var ageRatingsListMeta = emptyList() - private var peopleListMeta = emptyList() - private var pubStatusListMeta = emptyList() - private var languagesListMeta = emptyList() - private var libraryListMeta = emptyList() - private var collectionsListMeta = emptyList() - private var smartFilters = emptyList() - private val personRoles = listOf( - "Writer", - "Penciller", - "Inker", - "Colorist", - "Letterer", - "CoverArtist", - "Editor", - "Publisher", - "Character", - "Translator", - ) - - /** - * Loads the enabled filters if they are not empty so tachiyomi can show them to the user - */ - override fun getFilterList(): FilterList { - val toggledFilters = getToggledFilters() - - val filters = try { - val peopleInRoles = mutableListOf>() - personRoles.map { role -> - val peoplesWithRole = mutableListOf() - peopleListMeta.map { - if (it.role == helper.safeValueOf(role).role) { - peoplesWithRole.add(it) - } - } - peopleInRoles.add(peoplesWithRole) - } - - val filtersLoaded = mutableListOf>() - - if (sortableList.isNotEmpty() and toggledFilters.contains("Sort Options")) { - filtersLoaded.add( - SortFilter(sortableList.map { it.first }.toTypedArray()), - ) - if (smartFilters.isNotEmpty()) { - filtersLoaded.add( - SmartFiltersFilter(smartFilters.map { it.name }.toTypedArray()), - - ) - } - } - if (toggledFilters.contains("Read Status")) { - filtersLoaded.add( - StatusFilterGroup( - listOf( - "notRead", - "inProgress", - "read", - ).map { StatusFilter(it) }, - ), - ) - } - if (toggledFilters.contains("ReleaseYearRange")) { - filtersLoaded.add( - ReleaseYearRangeGroup( - listOf("Min", "Max").map { ReleaseYearRange(it) }, - ), - ) - } - - if (genresListMeta.isNotEmpty() and toggledFilters.contains("Genres")) { - filtersLoaded.add( - GenreFilterGroup(genresListMeta.map { GenreFilter(it.title) }), - ) - } - if (tagsListMeta.isNotEmpty() and toggledFilters.contains("Tags")) { - filtersLoaded.add( - TagFilterGroup(tagsListMeta.map { TagFilter(it.title) }), - ) - } - if (ageRatingsListMeta.isNotEmpty() and toggledFilters.contains("Age Rating")) { - filtersLoaded.add( - AgeRatingFilterGroup(ageRatingsListMeta.map { AgeRatingFilter(it.title) }), - ) - } - if (toggledFilters.contains("Format")) { - filtersLoaded.add( - FormatsFilterGroup( - listOf( - "Image", - "Archive", - "Pdf", - "Unknown", - ).map { FormatFilter(it) }, - ), - ) - } - if (collectionsListMeta.isNotEmpty() and toggledFilters.contains("Collections")) { - filtersLoaded.add( - CollectionFilterGroup(collectionsListMeta.map { CollectionFilter(it.title) }), - ) - } - if (languagesListMeta.isNotEmpty() and toggledFilters.contains("Languages")) { - filtersLoaded.add( - LanguageFilterGroup(languagesListMeta.map { LanguageFilter(it.title) }), - ) - } - if (libraryListMeta.isNotEmpty() and toggledFilters.contains("Libraries")) { - filtersLoaded.add( - LibrariesFilterGroup(libraryListMeta.map { LibraryFilter(it.name) }), - ) - } - if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Publication Status")) { - filtersLoaded.add( - PubStatusFilterGroup(pubStatusListMeta.map { PubStatusFilter(it.title) }), - ) - } - if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Rating")) { - filtersLoaded.add( - UserRating(), - ) - } - - // People Metadata: - if (personRoles.isNotEmpty() and toggledFilters.any { personRoles.contains(it) }) { - filtersLoaded.addAll( - listOf>( - PeopleHeaderFilter(""), - PeopleSeparatorFilter(), - PeopleHeaderFilter("PEOPLE"), - ), - ) - if (peopleInRoles[0].isNotEmpty() and toggledFilters.contains("Writer")) { - filtersLoaded.add( - WriterPeopleFilterGroup( - peopleInRoles[0].map { WriterPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[1].isNotEmpty() and toggledFilters.contains("Penciller")) { - filtersLoaded.add( - PencillerPeopleFilterGroup( - peopleInRoles[1].map { PencillerPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[2].isNotEmpty() and toggledFilters.contains("Inker")) { - filtersLoaded.add( - InkerPeopleFilterGroup( - peopleInRoles[2].map { InkerPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[3].isNotEmpty() and toggledFilters.contains("Colorist")) { - filtersLoaded.add( - ColoristPeopleFilterGroup( - peopleInRoles[3].map { ColoristPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[4].isNotEmpty() and toggledFilters.contains("Letterer")) { - filtersLoaded.add( - LettererPeopleFilterGroup( - peopleInRoles[4].map { LettererPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[5].isNotEmpty() and toggledFilters.contains("CoverArtist")) { - filtersLoaded.add( - CoverArtistPeopleFilterGroup( - peopleInRoles[5].map { CoverArtistPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[6].isNotEmpty() and toggledFilters.contains("Editor")) { - filtersLoaded.add( - EditorPeopleFilterGroup( - peopleInRoles[6].map { EditorPeopleFilter(it.name) }, - ), - ) - } - - if (peopleInRoles[7].isNotEmpty() and toggledFilters.contains("Publisher")) { - filtersLoaded.add( - PublisherPeopleFilterGroup( - peopleInRoles[7].map { PublisherPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[8].isNotEmpty() and toggledFilters.contains("Character")) { - filtersLoaded.add( - CharacterPeopleFilterGroup( - peopleInRoles[8].map { CharacterPeopleFilter(it.name) }, - ), - ) - } - if (peopleInRoles[9].isNotEmpty() and toggledFilters.contains("Translator")) { - filtersLoaded.add( - TranslatorPeopleFilterGroup( - peopleInRoles[9].map { TranslatorPeopleFilter(it.name) }, - ), - ) - filtersLoaded - } else { - filtersLoaded - } - } else { - filtersLoaded - } - } catch (e: Exception) { - Log.e(LOG_TAG, "[FILTERS] Error while creating filter list", e) - emptyList() - } - return FilterList(filters) - } - - /** - * Returns a FilterV2Dto encoded as a json string with values taken from filter - */ - private fun buildFilterBody(filter: MetadataPayload): String { - val filter_dto = FilterV2Dto() - filter_dto.sortOptions.sortField = filter.sorting - filter_dto.sortOptions.isAscending = filter.sorting_asc - - // Fields that support contains and not contains statements - val containsAndNotTriplets = listOf( - Triple(FilterField.Libraries, filter.libraries_i, filter.libraries_e), - Triple(FilterField.Tags, filter.tags_i, filter.tags_e), - Triple(FilterField.Languages, filter.language_i, filter.genres_e), - Triple(FilterField.AgeRating, filter.ageRating_i, filter.ageRating_e), - Triple(FilterField.Genres, filter.genres_i, filter.genres_e), - Triple(FilterField.CollectionTags, filter.collections_i, filter.collections_e), - ) - filter_dto.addContainsNotTriple(containsAndNotTriplets) - // Fields that have must contains statements - val peoplePairs = listOf( - - Pair(FilterField.Writers, filter.peopleWriters), - Pair(FilterField.Penciller, filter.peoplePenciller), - Pair(FilterField.Inker, filter.peopleInker), - Pair(FilterField.Colorist, filter.peopleCharacter), - Pair(FilterField.Letterer, filter.peopleLetterer), - Pair(FilterField.CoverArtist, filter.peopleCoverArtist), - Pair(FilterField.Editor, filter.peopleEditor), - Pair(FilterField.Publisher, filter.peoplePublisher), - Pair(FilterField.Characters, filter.peopleCharacter), - Pair(FilterField.Translators, filter.peopleTranslator), - - Pair(FilterField.PublicationStatus, filter.pubStatus), - ) - filter_dto.addPeople(peoplePairs) - - // Customized statements - filter_dto.addStatement(FilterComparison.Contains, FilterField.Formats, filter.formats) - filter_dto.addStatement(FilterComparison.Matches, FilterField.SeriesName, filter.seriesNameQuery) - // Hardcoded statement to filter out epubs: - filter_dto.addStatement(FilterComparison.NotContains, FilterField.Formats, "3") - if (filter.readStatus.isNotEmpty()) { - filter.readStatus.forEach { - if (it == "notRead") { - filter_dto.addStatement(FilterComparison.Equal, FilterField.ReadProgress, "0") - } else if (it == "inProgress") { - filter_dto.addStatement(FilterComparison.GreaterThan, FilterField.ReadProgress, "0") - filter_dto.addStatement(FilterComparison.LessThan, FilterField.ReadProgress, "100") - } else if (it == "read") { - filter_dto.addStatement(FilterComparison.Equal, FilterField.ReadProgress, "100") - } - } - } - // todo: check statement - // filter_dto.addStatement(FilterComparison.GreaterThanEqual, FilterField.UserRating, filter.userRating.toString()) - if (filter.releaseYearRangeMin != 0) { - filter_dto.addStatement(FilterComparison.GreaterThan, FilterField.ReleaseYear, filter.releaseYearRangeMin.toString()) - } - - if (filter.releaseYearRangeMax != 0) { - filter_dto.addStatement(FilterComparison.LessThan, FilterField.ReleaseYear, filter.releaseYearRangeMax.toString()) - } - return json.encodeToJsonElement(filter_dto).toString() - } - - class LoginErrorException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { - constructor(cause: Throwable) : this(null, cause) - } - - class OpdsurlExistsInPref(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { - constructor(cause: Throwable) : this(null, cause) - } - - class EmptyRequestBody(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { - constructor(cause: Throwable) : this(null, cause) - } - - class LoadingFilterFailed(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { - constructor(cause: Throwable) : this(null, cause) - } - - override fun headersBuilder(): Headers.Builder { - if (jwtToken.isEmpty()) { - doLogin() - if (jwtToken.isEmpty()) throw LoginErrorException(helper.intl["login_errors_header_token_empty"]) - } - return Headers.Builder() - .add("User-Agent", "Tachiyomi Kavita v${AppInfo.getVersionName()}") - .add("Content-Type", "application/json") - .add("Authorization", "Bearer $jwtToken") - } - - private fun setupLoginHeaders(): Headers.Builder { - return Headers.Builder() - .add("User-Agent", "Tachiyomi Kavita v${AppInfo.getVersionName()}") - .add("Content-Type", "application/json") - .add("Authorization", "Bearer $jwtToken") - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val opdsAddressPref = screen.editTextPreference( - ADDRESS_TITLE, - "OPDS url", - "", - helper.intl["pref_opds_summary"], - ) - val enabledFiltersPref = MultiSelectListPreference(screen.context).apply { - key = KavitaConstants.toggledFiltersPref - title = helper.intl["pref_filters_title"] - summary = helper.intl["pref_filters_summary"] - entries = KavitaConstants.filterPrefEntries - entryValues = KavitaConstants.filterPrefEntriesValue - setDefaultValue(KavitaConstants.defaultFilterPrefEntries) - setOnPreferenceChangeListener { _, newValue -> - @Suppress("UNCHECKED_CAST") - val checkValue = newValue as Set - preferences.edit() - .putStringSet(KavitaConstants.toggledFiltersPref, checkValue) - .commit() - } - } - val customSourceNamePref = EditTextPreference(screen.context).apply { - key = KavitaConstants.customSourceNamePref - title = helper.intl["pref_customsource_title"] - summary = helper.intl["pref_edit_customsource_summary"] - setOnPreferenceChangeListener { _, newValue -> - val res = preferences.edit() - .putString(KavitaConstants.customSourceNamePref, newValue.toString()) - .commit() - Toast.makeText( - screen.context, - helper.intl["restartapp_settings"], - Toast.LENGTH_LONG, - ).show() - Log.v(LOG_TAG, "[Preferences] Successfully modified custom source name: $newValue") - res - } - } - screen.addPreference(customSourceNamePref) - screen.addPreference(opdsAddressPref) - screen.addPreference(enabledFiltersPref) - } - - private fun androidx.preference.PreferenceScreen.editTextPreference( - preKey: String, - title: String, - default: String, - summary: String, - isPassword: Boolean = false, - ): EditTextPreference { - return EditTextPreference(context).apply { - key = preKey - this.title = title - val input = preferences.getString(title, null) - this.summary = if (input == null || input.isEmpty()) summary else input - this.setDefaultValue(default) - dialogTitle = title - - if (isPassword) { - setOnBindEditTextListener { - it.inputType = - InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - setOnPreferenceChangeListener { _, newValue -> - try { - val opdsUrlInPref = opdsUrlInPreferences(newValue.toString()) // We don't allow hot have multiple sources with same ip or domain - if (opdsUrlInPref.isNotEmpty()) { - // TODO("Add option to allow multiple sources with same url at the cost of tracking") - preferences.edit().putString(title, "").apply() - - Toast.makeText( - context, - helper.intl["pref_opds_duplicated_source_url"] + ": " + opdsUrlInPref, - Toast.LENGTH_LONG, - ).show() - throw OpdsurlExistsInPref(helper.intl["pref_opds_duplicated_source_url"] + opdsUrlInPref) - } - - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText( - context, - helper.intl["restartapp_settings"], - Toast.LENGTH_LONG, - ).show() - setupLogin(newValue) - Log.v(LOG_TAG, "[Preferences] Successfully modified OPDS URL") - res - } catch (e: OpdsurlExistsInPref) { - Log.e(LOG_TAG, "Url exists in a different sourcce") - false - } catch (e: Exception) { - Log.e(LOG_TAG, "Unrecognised error", e) - false - } - } - } - } - - private fun getPrefBaseUrl(): String = preferences.getString("BASEURL", "")!! - private fun getPrefApiUrl(): String = preferences.getString("APIURL", "")!! - private fun getPrefKey(): String = preferences.getString("APIKEY", "")!! - private fun getToggledFilters() = preferences.getStringSet(KavitaConstants.toggledFiltersPref, KavitaConstants.defaultFilterPrefEntries)!! - - // We strip the last slash since we will append it above - private fun getPrefAddress(): String { - var path = preferences.getString(ADDRESS_TITLE, "")!! - if (path.isNotEmpty() && path.last() == '/') { - path = path.substring(0, path.length - 1) - } - return path - } - - private fun getPrefApiKey(): String { - // http(s)://host:(port)/api/opds/api-key - val existingKey = preferences.getString("APIKEY", "") - return existingKey!!.ifEmpty { preferences.getString(ADDRESS_TITLE, "")!!.split("/opds/")[1] } - } - - companion object { - private const val ADDRESS_TITLE = "Address" - private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() - } - - /* - * LOGIN - **/ - /** - * Used to check if a url is configured already in any of the sources - * This is a limitation needed for tracking. - * **/ - private fun opdsUrlInPreferences(url: String): String { - fun getCleanedApiUrl(url: String): String = "${url.split("/api/").first()}/api" - - for (sourceId in 1..3) { // There's 3 sources so 3 preferences to check - val sourceSuffixID by lazy { - val key = "${"kavita_$sourceId"}/all/1" // Hardcoded versionID to 1 - 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 - } - val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$sourceSuffixID", 0x0000) - } - val prefApiUrl = preferences.getString("APIURL", "")!! - - if (prefApiUrl.isNotEmpty()) { - if (prefApiUrl == getCleanedApiUrl(url)) { - if (sourceId.toString() != suffix) { - return preferences.getString(KavitaConstants.customSourceNamePref, sourceId.toString())!! - } - } - } - } - return "" - } - - private fun setupLogin(addressFromPreference: String = "") { - Log.v(LOG_TAG, "[Setup Login] Starting setup") - val validAddress = address.ifEmpty { addressFromPreference } - val tokens = validAddress.split("/api/opds/") - val apiKey = tokens[1] - val baseUrlSetup = tokens[0].replace("\n", "\\n") - - if (baseUrlSetup.toHttpUrlOrNull() == null) { - Log.e(LOG_TAG, "Invalid URL $baseUrlSetup") - throw Exception("""${helper.intl["login_errors_invalid_url"]}: $baseUrlSetup""") - } - preferences.edit().putString("BASEURL", baseUrlSetup).apply() - preferences.edit().putString("APIKEY", apiKey).apply() - preferences.edit().putString("APIURL", "$baseUrlSetup/api").apply() - Log.v(LOG_TAG, "[Setup Login] Setup successful") - } - - private fun doLogin() { - if (address.isEmpty()) { - Log.e(LOG_TAG, "OPDS URL is empty or null") - throw IOException(helper.intl["pref_opds_must_setup_address"]) - } - if (address.split("/opds/").size != 2) { - throw IOException(helper.intl["pref_opds_badformed_url"]) - } - if (jwtToken.isEmpty()) setupLogin() - Log.v(LOG_TAG, "[Login] Starting login") - val request = POST( - "$apiUrl/Plugin/authenticate?apiKey=${getPrefKey()}&pluginName=Tachiyomi-Kavita", - setupLoginHeaders().build(), - "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()), - ) - client.newCall(request).execute().use { - val peekbody = it.peekBody(Long.MAX_VALUE).toString() - - if (it.code == 200) { - try { - jwtToken = it.parseAs().token - isLogged = true - } catch (e: Exception) { - Log.e(LOG_TAG, "Possible outdated kavita", e) - throw IOException(helper.intl["login_errors_parse_tokendto"]) - } - } else { - if (it.code == 500) { - Log.e(LOG_TAG, "[LOGIN] login failed. There was some error -> Code: ${it.code}.Response message: ${it.message} Response body: $peekbody.") - throw LoginErrorException(helper.intl["login_errors_failed_login"]) - } else { - Log.e(LOG_TAG, "[LOGIN] login failed. Authentication was not successful -> Code: ${it.code}.Response message: ${it.message} Response body: $peekbody.") - throw LoginErrorException(helper.intl["login_errors_failed_login"]) - } - } - } - Log.v(LOG_TAG, "[Login] Login successful") - } - - init { - if (apiUrl.isNotBlank()) { - Single.fromCallable { - // Login - doLogin() - try { // Get current version - val requestUrl = "$apiUrl/Server/server-info" - val serverInfoDto = client.newCall(GET(requestUrl, headersBuilder().build())) - .execute() - .parseAs() - Log.e( - LOG_TAG, - "Extension version: code=${AppInfo.getVersionCode()} name=${AppInfo.getVersionName()}" + - " - - Kavita version: ${serverInfoDto.kavitaVersion} - - Lang:${Locale.getDefault()}", - ) // this is not a real error. Using this so it gets printed in dump logs if there's any error - } catch (e: EmptyRequestBody) { - Log.e(LOG_TAG, "Extension version: code=${AppInfo.getVersionCode()} - name=${AppInfo.getVersionName()}") - } catch (e: Exception) { - Log.e(LOG_TAG, "Tachiyomi version: code=${AppInfo.getVersionCode()} - name=${AppInfo.getVersionName()}", e) - } - try { // Load Filters - // Genres - Log.v(LOG_TAG, "[Filter] Fetching filters ") - client.newCall(GET("$apiUrl/Metadata/genres", headersBuilder().build())) - .execute().use { response -> - - genresListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e(LOG_TAG, "[Filter] Error decoding JSON for genres filter -> ${response.body}", e) - emptyList() - } - } - // tagsListMeta - client.newCall(GET("$apiUrl/Metadata/tags", headersBuilder().build())) - .execute().use { response -> - tagsListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e(LOG_TAG, "[Filter] Error decoding JSON for tagsList filter", e) - emptyList() - } - } - // age-ratings - client.newCall(GET("$apiUrl/Metadata/age-ratings", headersBuilder().build())) - .execute().use { response -> - ageRatingsListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "[Filter] Error decoding JSON for age-ratings filter", - e, - ) - emptyList() - } - } - // collectionsListMeta - client.newCall(GET("$apiUrl/Collection", headersBuilder().build())) - .execute().use { response -> - collectionsListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "[Filter] Error decoding JSON for collectionsListMeta filter", - e, - ) - emptyList() - } - } - // languagesListMeta - client.newCall(GET("$apiUrl/Metadata/languages", headersBuilder().build())) - .execute().use { response -> - languagesListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "[Filter] Error decoding JSON for languagesListMeta filter", - e, - ) - emptyList() - } - } - // libraries - client.newCall(GET("$apiUrl/Library", headersBuilder().build())) - .execute().use { response -> - libraryListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "[Filter] Error decoding JSON for libraries filter", - e, - ) - emptyList() - } - } - // peopleListMeta - client.newCall(GET("$apiUrl/Metadata/people", headersBuilder().build())) - .execute().use { response -> - peopleListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "error while decoding JSON for peopleListMeta filter", - e, - ) - emptyList() - } - } - client.newCall(GET("$apiUrl/Metadata/publication-status", headersBuilder().build())) - .execute().use { response -> - pubStatusListMeta = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "error while decoding JSON for publicationStatusListMeta filter", - e, - ) - emptyList() - } - } - client.newCall(GET("$apiUrl/filter", headersBuilder().build())) - .execute().use { response -> - smartFilters = try { - response.body.use { json.decodeFromString(it.string()) } - } catch (e: Exception) { - Log.e( - LOG_TAG, - "error while decoding JSON for smartfilters", - e, - ) - emptyList() - } - } - Log.v(LOG_TAG, "[Filter] Successfully loaded metadata tags from server") - } catch (e: Exception) { - throw LoadingFilterFailed("Failed Loading Filters", e.cause) - } - } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe( - {}, - { tr -> - // Avoid polluting logs with traces of exception - if (tr is EmptyRequestBody || tr is LoginErrorException) { - Log.e(LOG_TAG, "error while doing initial calls\n${tr.cause}") - return@subscribe - } - if (tr is ConnectException) { // avoid polluting logs with traces of exception - Log.e(LOG_TAG, "Error while doing initial calls\n${tr.cause}") - return@subscribe - } - Log.e(LOG_TAG, "error while doing initial calls", tr) - }, - ) - } - } -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt deleted file mode 100644 index 24f284410..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt +++ /dev/null @@ -1,81 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita - -object KavitaConstants { - // toggle filters - const val toggledFiltersPref = "toggledFilters" - val filterPrefEntries = arrayOf( - "Sort Options", - "Format", - "Libraries", - "Read Status", - "Genres", - "Tags", - "Collections", - "Languages", - "Publication Status", - "Rating", - "Age Rating", - "Writers", - "Penciller", - "Inker", - "Colorist", - "Letterer", - "Cover Artist", - "Editor", - "Publisher", - "Character", - "Translators", - "ReleaseYearRange", - ) - val filterPrefEntriesValue = arrayOf( - "Sort Options", - "Format", - "Libraries", - "Read Status", - "Genres", - "Tags", - "Collections", - "Languages", - "Publication Status", - "Rating", - "Age Rating", - "Writers", - "Penciller", - "Inker", - "Colorist", - "Letterer", - "CoverArtist", - "Editor", - "Publisher", - "Character", - "Translators", - "ReleaseYearRange", - ) - val defaultFilterPrefEntries = setOf( - "Sort Options", - "Format", - "Libraries", - "Read Status", - "Genres", - "Tags", - "Collections", - "Languages", - "Publication Status", - "Rating", - "Age Rating", - "Writers", - "Penciller", - "Inker", - "Colorist", - "Letterer", - "CoverArtist", - "Editor", - "Publisher", - "Character", - "Translators", - "ReleaseYearRange", - ) - - const val customSourceNamePref = "customSourceName" - const val noSmartFilterSelected = "No smart filter loaded" -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaFactory.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaFactory.kt deleted file mode 100644 index 7639b6e53..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class KavitaFactory : SourceFactory { - override fun createSources(): List = - listOf( - Kavita("1"), - Kavita("2"), - Kavita("3"), - ) -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt deleted file mode 100644 index f588f684b..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt +++ /dev/null @@ -1,141 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita - -import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterDto -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 -import eu.kanade.tachiyomi.lib.i18n.Intl -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Response -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -class KavitaHelper { - val json = Json { - isLenient = true - ignoreUnknownKeys = true - allowSpecialFloatingPointValues = true - useArrayPolymorphism = true - prettyPrint = true - } - inline fun > safeValueOf(type: String): T { - return java.lang.Enum.valueOf(T::class.java, type) - } - val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSS", Locale.US) - .apply { timeZone = TimeZone.getTimeZone("UTC") } - fun parseDate(dateAsString: String): Long = - dateFormatter.parse(dateAsString)?.time ?: 0 - - fun hasNextPage(response: Response): Boolean { - val paginationHeader = response.header("Pagination") - var hasNextPage = false - if (!paginationHeader.isNullOrEmpty()) { - val paginationInfo = json.decodeFromString(paginationHeader) - hasNextPage = paginationInfo.currentPage + 1 > paginationInfo.totalPages - } - return !hasNextPage - } - - fun getIdFromUrl(url: String): Int { - return url.split("/").last().toInt() - } - - fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiKey: String): SManga = - SManga.create().apply { - 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 { - 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 chapterFromObject(obj: ChapterDto): SChapter = SChapter.create().apply { - url = obj.id.toString() - name = if (obj.number == "0" && obj.isSpecial) { - // This is a special. Chapter name is special name - obj.range - } else { - val cleanedName = obj.title.replaceFirst("^0+(?!$)".toRegex(), "") - "Chapter $cleanedName" - } - date_upload = parseDate(obj.created) - chapter_number = obj.number.toFloat() - scanlator = "${obj.pages} pages" - } - - fun chapterFromVolume(obj: ChapterDto, volume: VolumeDto): SChapter = - SChapter.create().apply { - // If there are multiple chapters to this volume, then prefix with Volume number - if (volume.chapters.isNotEmpty() && obj.number != "0") { - // This volume is not volume 0, hence they are not loose chapters - // We just add a nice Volume X to the chapter title - // Chapter-based Volume - name = "Volume ${volume.number} Chapter ${obj.number}" - chapter_number = obj.number.toFloat() - } else if (obj.number == "0") { - // Both specials and volume has chapter number 0 - if (volume.number == 0) { - // Treat as special - // Special is not in a volume - if (obj.range == "") { - // Special does not have any Title - name = "Chapter 0" - chapter_number = obj.number.toFloat() - } else { - // We use it's own special tile - name = obj.range - chapter_number = obj.number.toFloat() - } - } else { - // Is a single-file volume - // We encode the chapter number to support tracking - name = "Volume ${volume.number}" - chapter_number = volume.number.toFloat() / 10000 - } - } else { - name = "Unhandled Else Volume ${volume.number}" - } - url = obj.id.toString() - date_upload = parseDate(obj.created) - - scanlator = "${obj.pages} pages" - } - val intl = Intl( - language = Locale.getDefault().toString(), - baseLanguage = "en", - availableLanguages = KavitaInt.AVAILABLE_LANGS, - classLoader = this::class.java.classLoader!!, - createMessageFileName = { lang -> - when (lang) { - KavitaInt.SPANISH_LATAM -> Intl.createDefaultMessageFileName(KavitaInt.SPANISH) - else -> Intl.createDefaultMessageFileName(lang) - } - }, - ) -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaInt.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaInt.kt deleted file mode 100644 index fbaf93af0..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaInt.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita - -object KavitaInt { - const val ENGLISH = "en" - const val SPANISH = "es_ES" - const val SPANISH_LATAM = "es-419" - const val FRENCH = "fr_FR" - const val NORWEGIAN = "nb_NO" - val AVAILABLE_LANGS = setOf( - ENGLISH, - SPANISH, - SPANISH_LATAM, - NORWEGIAN, - FRENCH, - ) - const val KAVITA_NAME = "Kavita" -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/FilterDto.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/FilterDto.kt deleted file mode 100644 index 77b669e51..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/FilterDto.kt +++ /dev/null @@ -1,124 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita.dto - -import kotlinx.serialization.Serializable -import kotlin.Triple - -@Serializable -data class FilterV2Dto( - val id: Int? = null, - val name: String? = null, - val statements: MutableList = mutableListOf(), - val combination: Int = 0, // FilterCombination = FilterCombination.And, - val sortOptions: SortOptions = SortOptions(), - val limitTo: Int = 0, -) { - fun addStatement(comparison: FilterComparison, field: FilterField, value: String) { - if (value.isNotBlank()) { - statements.add(FilterStatementDto(comparison.type, field.type, value)) - } - } - fun addStatement(comparison: FilterComparison, field: FilterField, values: java.util.ArrayList) { - if (values.isNotEmpty()) { - statements.add(FilterStatementDto(comparison.type, field.type, values.joinToString(","))) - } - } - - fun addContainsNotTriple(list: List, ArrayList>>) { - list.map { - addStatement(FilterComparison.Contains, it.first, it.second) - addStatement(FilterComparison.NotContains, it.first, it.third) - } - } - fun addPeople(list: List>>) { - list.map { - addStatement(FilterComparison.MustContains, it.first, it.second) - } - } -} - -@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 -enum class SortFieldEnum(val type: Int) { - SortName(1), - CreatedDate(2), - LastModifiedDate(3), - LastChapterAdded(4), - TimeToRead(5), - ReleaseYear(6), - ; - - companion object { - private val map = SortFieldEnum.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, - And, -} - -@Serializable -enum class FilterField(val type: Int) { - Summary(0), - SeriesName(1), - PublicationStatus(2), - Languages(3), - AgeRating(4), - UserRating(5), - Tags(6), - CollectionTags(7), - Translators(8), - Characters(9), - Publisher(10), - Editor(11), - CoverArtist(12), - Letterer(13), - Colorist(14), - Inker(15), - Penciller(16), - Writers(17), - Genres(18), - Libraries(19), - ReadProgress(20), - Formats(21), - ReleaseYear(22), - ReadTime(23), - Path(24), - FilePath(25), -} - -@Serializable -enum class FilterComparison(val type: Int) { - Equal(0), - GreaterThan(1), - GreaterThanEqual(2), - LessThan(3), - LessThanEqual(4), - Contains(5), - MustContains(6), - Matches(7), - NotContains(8), - NotEqual(9), - BeginsWith(10), - EndsWith(11), - IsBefore(12), - IsAfter(13), - IsInLast(14), - IsNotInLast(15), -} diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt deleted file mode 100644 index 1c09db11d..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt +++ /dev/null @@ -1,103 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita.dto - -import kotlinx.serialization.Serializable - -@Serializable -enum class MangaFormat(val format: Int) { - Image(0), - Archive(1), - Unknown(2), - Epub(3), - Pdf(4), - ; - companion object { - private val map = PersonRole.values().associateBy(PersonRole::role) - fun fromInt(type: Int) = 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), - ; - companion object { - private val map = PersonRole.values().associateBy(PersonRole::role) - fun fromInt(type: Int) = map[type] - } -} - -@Serializable -data class SeriesDto( - val id: Int, - val name: String, - val originalName: String = "", - val thumbnail_url: String? = "", - val localizedName: String? = "", - val sortName: String? = "", - val pages: Int, - val coverImageLocked: Boolean = true, - val pagesRead: Int, - val userRating: Float, - val userReview: String? = "", - val format: Int, - val created: String? = "", - val libraryId: Int, - val libraryName: String? = "", -) - -@Serializable -data class SeriesMetadataDto( - val id: Int, - val summary: String? = "", - val writers: List = emptyList(), - val coverArtists: List = emptyList(), - val genres: List = emptyList(), - val seriesId: Int, - val ageRating: Int, - val publicationStatus: Int, -) - -@Serializable -data class Genres( - val title: String, -) - -@Serializable -data class Person( - val name: String, -) - -@Serializable -data class VolumeDto( - val id: Int, - val number: Int, - val name: String, - val pages: Int, - val pagesRead: Int, - val lastModified: String, - val created: String, - val seriesId: Int, - val chapters: List = emptyList(), -) - -@Serializable -data class ChapterDto( - val id: Int, - val range: String, - val number: String, - val pages: Int, - val isSpecial: Boolean, - val title: String, - val pagesRead: Int, - val coverImageLocked: Boolean, - val volumeId: Int, - val created: String, -) diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt deleted file mode 100644 index f14256719..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt +++ /dev/null @@ -1,104 +0,0 @@ -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, -) - -@Serializable -data class MetadataPeople( - val id: Int, - val name: String, - val role: Int, -) - -@Serializable -data class MetadataPubStatus( - val value: Int, - val title: String, -) - -@Serializable -data class MetadataTag( - val id: Int, - val title: String, -) - -@Serializable -data class MetadataAgeRatings( - val value: Int, - val title: String, -) - -@Serializable -data class MetadataLanguages( - val isoCode: String, - val title: String, -) - -@Serializable -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 = arrayListOf(), - val readStatusList: List = listOf("notRead", "inProgress", "read"), - // _i = included, _e = excluded - var genres_i: ArrayList = arrayListOf(), - var genres_e: ArrayList = arrayListOf(), - var tags_i: ArrayList = arrayListOf(), - var tags_e: ArrayList = arrayListOf(), - var ageRating_i: ArrayList = arrayListOf(), - var ageRating_e: ArrayList = arrayListOf(), - - var formats: ArrayList = arrayListOf(), - var collections_i: ArrayList = arrayListOf(), - var collections_e: ArrayList = arrayListOf(), - var userRating: Int = 0, - var people: ArrayList = arrayListOf(), - // _i = included, _e = excluded - var language_i: ArrayList = arrayListOf(), - var language_e: ArrayList = arrayListOf(), - - var libraries_i: ArrayList = arrayListOf(), - var libraries_e: ArrayList = arrayListOf(), - var pubStatus: ArrayList = arrayListOf(), - var seriesNameQuery: String = "", - var releaseYearRangeMin: Int = 0, - var releaseYearRangeMax: Int = 0, - - var peopleWriters: ArrayList = arrayListOf(), - var peoplePenciller: ArrayList = arrayListOf(), - var peopleInker: ArrayList = arrayListOf(), - var peoplePeoplecolorist: ArrayList = arrayListOf(), - var peopleLetterer: ArrayList = arrayListOf(), - var peopleCoverArtist: ArrayList = arrayListOf(), - var peopleEditor: ArrayList = arrayListOf(), - var peoplePublisher: ArrayList = arrayListOf(), - var peopleCharacter: ArrayList = arrayListOf(), - var peopleTranslator: ArrayList = arrayListOf(), -) - -@Serializable -data class SmartFilter( - val id: Int, - val name: String, - val filter: String, -) diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/Responses.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/Responses.kt deleted file mode 100644 index 424cc1233..000000000 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/Responses.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.kavita.dto - -import kotlinx.serialization.Serializable - -@Serializable // Used to process login -data class AuthenticationDto( - val username: String, - val token: String, - val apiKey: String, -) - -@Serializable -data class PaginationInfo( - val currentPage: Int, - val itemsPerPage: Int, - val totalItems: Int, - val totalPages: Int, -) - -@Serializable -data class ServerInfoDto( - val installId: String, - val os: String, - val isDocker: Boolean, - val dotnetVersion: String, - val kavitaVersion: String, - val numOfCores: Int, -)