Merge pull request #23 from dear-clouds/master

1.4.21
This commit is contained in:
Joe Milazzo
2025-07-15 07:51:42 -05:00
committed by GitHub
18 changed files with 1505 additions and 1397 deletions

View File

@@ -3,26 +3,6 @@ description: Report a source issue in Tachiyomi
labels: [Bug] labels: [Bug]
body: body:
- type: input
id: source
attributes:
label: Source information
description: |
You can find the extension name and version in **Browse → Extensions**.
placeholder: |
Example: "Mangahere 1.3.18"
validations:
required: true
- type: input
id: language
attributes:
label: Source language
placeholder: |
Example: "English"
validations:
required: true
- type: textarea - type: textarea
id: reproduce-steps id: reproduce-steps
attributes: attributes:
@@ -59,14 +39,25 @@ body:
- type: input - type: input
id: tachiyomi-version id: tachiyomi-version
attributes: attributes:
label: Tachiyomi version label: Mihon/Komikku version
description: | description: |
You can find your Tachiyomi version in **More → About**. You can find your Mihon/Komikku version in **More → About**.
placeholder: | placeholder: |
Example: "0.14.7" Example: "0.14.7"
validations: validations:
required: true required: true
- type: input
id: kavita-version
attributes:
label: Kavita version
description: |
You can find your Kavita version in **Settings → System**.
placeholder: |
Example: "0.8.7.2"
validations:
required: true
- type: input - type: input
id: android-version id: android-version
attributes: attributes:
@@ -95,13 +86,11 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. - label: I have updated Mihon/Komikku to last version.
required: true required: true
- label: I have updated all installed extensions. - label: I have updated the extension to last version.
required: true required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/). - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/mihonapp/mihon/issues/new/choose).
required: true
- label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose).
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View File

@@ -3,26 +3,6 @@ description: Suggest a feature to improve an existing source
labels: [Feature request] labels: [Feature request]
body: body:
- type: input
id: source
attributes:
label: Source name
description: |
You can find the extension name in **Browse → Extensions**.
placeholder: |
Example: "Mangahere"
validations:
required: true
- type: input
id: language
attributes:
label: Source language
placeholder: |
Example: "English"
validations:
required: true
- type: textarea - type: textarea
id: feature-description id: feature-description
attributes: attributes:
@@ -51,9 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose). - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/mihonapp/mihon/issues/new/choose).
required: true
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View File

@@ -1,59 +0,0 @@
name: 🌐 Source request
description: Suggest a new source for Tachiyomi
labels: [Source request]
body:
- type: input
id: name
attributes:
label: Source name
placeholder: |
Example: "Not Real Scans"
validations:
required: true
- type: input
id: link
attributes:
label: Source link
placeholder: |
Example: "https://notrealscans.org"
validations:
required: true
- type: input
id: language
attributes:
label: Source language
placeholder: |
Example: "English"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
Example:
"18+/NSFW = yes"
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- label: I have checked that the extension does not already exist on the [website extensions list](https://tachiyomi.org/extensions/) or the app.
required: true
- label: I have checked that the extension is not on [the removed sources list](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md).
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/tachiyomiorg/tachiyomi-extensions/) and verified it does not appear in the code base.
required:
- label: I have written a meaningful title with the source name.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View File

@@ -1,57 +0,0 @@
name: 🔗 URL change report
description: Report URL change of an existing source
labels: [Bug,Domain changed]
body:
- type: input
id: source
attributes:
label: Source information
description: |
You can find the extension name and version in **Browse → Extensions**.
placeholder: |
Example: "NotRealScans 1.3.1"
validations:
required: true
- type: input
id: language
attributes:
label: Source language
placeholder: |
Example: "English"
validations:
required: true
- type: input
id: link
attributes:
label: Source new URL
placeholder: |
Example: "https://notrealscans.org"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- label: I have updated all installed extensions.
required: true
- label: I have opened WebView and checked that the source URL is not updated yet.
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View File

@@ -31,11 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose). - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/mihonapp/mihon/issues/new/choose).
required: true
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View File

@@ -1,63 +0,0 @@
name: ❌ Dead source report
description: Source is down and website is closed
labels: [Source is down]
body:
- type: markdown
attributes:
value: |
### Notice
If you have a lot of dead sources to report, please go back and submit a single meta request.
- type: input
id: source
attributes:
label: Source name
description: |
You can find the extension name in **Browse → Extensions**.
placeholder: |
Example: "NotRealScans"
validations:
required: true
- type: input
id: language
attributes:
label: Source language
placeholder: |
Example: "English"
validations:
required: true
- type: input
id: link
attributes:
label: Source link
placeholder: |
Example: "https://notrealscans.org"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- label: I have updated all installed extensions.
required: true
- label: I have opened WebView and checked that the source website is down.
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a meaningful title with the source name.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View File

@@ -1,33 +0,0 @@
name: 🗑 Source removal request
description: Scanlators can request their site to be removed
labels: [Meta request]
body:
- type: input
id: link
attributes:
label: Source link
placeholder: |
Example: "https://notrealscans.org"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: requirements
attributes:
label: Requirements
description: Your request will be denied if you don't meet these requirements.
options:
- label: I've added a `TXT` DNS record for my domain with the value `tachiyomi-verification`
required: true
- label: Site only hosts content scanlated by the group and not stolen from other scanlators or official releases (i.e., not an aggregator site)
required: true
- label: Site is not infested with user-hostile features (e.g., invasive or malicious ads)
required: true

View File

@@ -2,7 +2,7 @@
* Add or remove modules to load as needed for local development here. * Add or remove modules to load as needed for local development here.
*/ */
loadAllIndividualExtensions() loadAllIndividualExtensions()
// loadIndividualExtension("all", "mangadex") loadIndividualExtension("all", "kavita")
/** /**
* ===================================== COMMON CONFIGURATION ====================================== * ===================================== COMMON CONFIGURATION ======================================

View File

@@ -1,32 +1,107 @@
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." # Authentication #
login_errors_invalid_url=Invalid URL: # ================ #
login_errors_parse_tokendto=There was an error parsing the auth token login_errors_failed_login=Authentication failed.\nPlease check your credentials
pref_customsource_title=Displayed name for source login_errors_header_token_empty=Authorization token missing\nPlease open the extension first to generate a token
pref_group_tags_title=Group Tags (fork must support grouping) login_errors_invalid_url=Invalid server URL:
pref_group_tags_summary_on=Will prefix tags with their type login_errors_parse_tokendto=Failed to process authentication token
pref_group_tags_summary_off=List all tags together http_errors_401=Authentication expired\nPlease log in again
pref_last_volume_cover_title=Keep cover updated http_errors_500=Server error occurred
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) # Preferences #
pref_show_epub_summary_off=Hide EPUB titles in browsing # ============== #
pref_show_epub_summary_on=Show EPUB titles in browsing pref_customsource_title=Source Display Name
pref_edit_customsource_summary=Customize how this Kavita instance appears in your library\nExample: "Home Server" or "Cloud Instance"
pref_group_tags_title=Tag Grouping (fork must support grouping)
pref_group_tags_summary_on=Show tags with category prefixes
pref_group_tags_summary_off=Display tags as flat list
pref_last_volume_cover_title=Keep Cover Updated
pref_last_volume_cover_summary_off=Keep series cover
pref_last_volume_cover_summary_on=Always show current Volume/Issue cover on refresh
pref_show_epub_title=EPUB Visibility
pref_show_epub_summary_off=Hide EPUB entries
pref_show_epub_summary_on=Show EPUB entries (unreadable in Mihon)
pref_rd_date_title=Reading List: Display Release Date pref_rd_date_title=Reading List: Display Release Date
pref_rd_date_summary_off=Do not display the release date pref_rd_date_summary_off=Hide publication dates
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_rd_date_summary_on=Show publication dates\nNote: 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_reset_title=Reset Settings
pref_edit_customsource_summary=Here you can change this source name.\nYou can write a descriptive name to identify this OPDS URL. pref_reset_summary=Clear all preferences for this Kavita instance
pref_filters_summary=Show these filters in the filter list
pref_filters_title=Default filters shown pref_filters_title=Default Filters
pref_opds_badformed_url=Incorrect OPDS address. Please copy it from User settings \u2192 3rd party apps \u2192 OPDS URL pref_filters_summary=Select which filters appear by default
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. # OPDS Setup #
restartapp_settings=Restart Mihon to apply new setting. # ============== #
version_exceptions_chapters_parse=Unhandled exception parsing chapters. Send your logs to the Kavita devs. pref_opds_must_setup_address=OPDS URL required for connection
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. pref_opds_badformed_url=Invalid OPDS address\nCopy from: User Settings > API Key / OPDS > OPDS URL (settings#clients)
version_exceptions_smart_filter=Could not decode SmartFilter. Ensure you are using Kavita version 0.7.11 or later. pref_opds_duplicated_source_url=URL already used in another source:
http_errors_500=Something went wrong pref_opds_summary=Complete OPDS URL including API key\nFormat: http://address:port/opds?apiKey=XXX
http_errors_401=There was an error logging in. Try again or reload the app
# ============== #
# System Messages #
# ============== #
restartapp_settings=Restart Mihon to apply changes
check_version=Requires Kavita v0.8.7+\nIf issues persist, contact support with logs
version_exceptions_chapters_parse=Chapter parsing error\nPlease report with logs
version_exceptions_smart_filter=Smart Filter error\nRequires Kavita v0.7.11+
# ============== #
# Filters #
# ============== #
# Filter headers
filters_special=These filters can't be combined with other filters
filters_people=People Filters
filters_special_list=Special Lists
filters_smart_filters=Smart Filters
# @todo
# Basic filters
filters_user_rating=User Rating
filters_status=Status
filters_sort=Sort By
filters_release_year=Release Year
filters_genres=Genres
filters_tags=Tags
filters_age_ratings=Age Ratings
filters_collections=Collections
filters_languages=Languages
filters_libraries=Libraries
filters_pub_status=Publication Status
# People filters
filters_writers=Writers
filters_pencillers=Pencillers
filters_inkers=Inkers
filters_colorists=Colorists
filters_letterers=Letterers
filters_cover_artists=Cover Artists
filters_editors=Editors
filters_publishers=Publishers
filters_characters=Characters
filters_translators=Translators
# Status options
filters_status_any=Any
filters_status_unread=Unread
filters_status_in_progress=In Progress
filters_status_read=Read
# Sort options
filters_sort_title=Title
filters_sort_rating=Rating
filters_sort_date_added=Date Added
filters_sort_release_date=Release Date
filters_sort_read_progress=Read Progress
# Error messages
filters_error_loading=Failed to load filters
filters_empty_list=No filters available

View File

@@ -1,18 +1,107 @@
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 # Authentification #
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. login_errors_failed_login=Échec de l'authentification. Vérifiez vos identifiants
pref_filters_summary=Afficher ces filtres dans la liste des filtres login_errors_header_token_empty=Token d'autorisation manquant\nOuvrez d'abord l'extension pour générer un token
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 login_errors_invalid_url=URL du serveur invalide :
pref_filters_title=Filtres par d\u00E9faut affich\u00E9s login_errors_parse_tokendto=Échec du traitement du token d'authentification
pref_edit_customsource_summary=Ici vous pouvez changer ce nom source.\nVous pouvez \u00E9crire un nom descriptif pour identifier cette URL opds http_errors_401=Authentification expirée. Veuillez vous reconnecter
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 http_errors_500=Erreur du serveur
login_errors_parse_tokendto=Il y a eu une erreur pendant l'analyse du jeton d'authentification
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 -> # Préférences #
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 pref_customsource_title=Nom d'affichage de la source
http_errors_500=Quelque chose s'est mal pass\u00E9 pref_edit_customsource_summary=Personnalisez l'affichage de cette instance Kavita\nExemple : "Serveur Maison" ou "Instance Cloud"
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: pref_group_tags_title=Groupement des tags (fork doit supporter le groupement)
http_errors_401=Il y a eu une erreur. Essayez de nouveau ou relancez l'application pref_group_tags_summary_on=Afficher les tags avec préfixes
pref_group_tags_summary_off=Afficher les tags en liste plate
pref_last_volume_cover_title=Garder la couverture à jour
pref_last_volume_cover_summary_off=Conserver la couverture de la série
pref_last_volume_cover_summary_on=Toujours afficher la dernière couverture
pref_show_epub_title=Visibilité EPUB
pref_show_epub_summary_off=Masquer les EPUB
pref_show_epub_summary_on=Afficher les EPUB (illisibles dans Mihon)
pref_rd_date_title=Listes de Lecture: Affichage date de publication
pref_rd_date_summary_off=Masquer les dates de publication
pref_rd_date_summary_on=Afficher les dates de publication\nNote : Désactiver affichera toujours "N/A" après activation
pref_reset_title=Réinitialisation
pref_reset_summary=Effacer toutes les préférences pour cette instance Kavita
pref_filters_title=Filtres par défaut
pref_filters_summary=Sélectionnez les filtres visibles par défaut
# ============== #
# Configuration OPDS #
# ============== #
pref_opds_must_setup_address=URL OPDS requise pour la connexion
pref_opds_badformed_url=URL OPDS invalide\nCopiez depuis : Paramètres > Clé API / OPDS > URL OPDS
pref_opds_duplicated_source_url=URL déjà utilisée dans une autre source :
pref_opds_summary=URL OPDS complète incluant la clé API\nFormat : http://adresse:port/opds?apiKey=XXX
# ============== #
# Messages système #
# ============== #
restartapp_settings=Redémarrez Mihon pour appliquer les changements
check_version=Nécessite Kavita v0.8.7+\nSi problème persiste, contactez le support avec les logs
version_exceptions_chapters_parse=Erreur d'analyse des chapitres\nMerci de rapporter avec les logs
version_exceptions_smart_filter=Erreur Smart Filter\nNécessite Kavita v0.7.11+
# ============== #
# Filtres #
# ============== #
# Filter headers
filters_special=Ces filtres ne peuvent pas être combinés avec d'autres filtres
filters_people=Filtres Personnalités
# Special filters
filters_special_list=Listes Spéciales
filters_smart_filters=Filtres Intelligents
# Basic filters
filters_user_rating=Note Utilisateur
filters_status=Statut
filters_sort=Trier Par
filters_release_year=Année de Sortie
filters_genres=Genres
filters_tags=Étiquettes
filters_age_ratings=Classifications d'Âge
filters_collections=Collections
filters_languages=Langues
filters_libraries=Bibliothèques
filters_pub_status=Statut de Publication
# People filters
filters_writers=Auteur
filters_pencillers=Dessinateurs
filters_inkers=Encreurs
filters_colorists=Coloristes
filters_letterers=Lettrists
filters_cover_artists=Artistes de Couverture
filters_editors=Éditeurs
filters_publishers=Éditeurs
filters_characters=Personnalités
filters_translators=Traducteurs
# Status options
filters_status_any=Tous
filters_status_unread=Non Lu
filters_status_in_progress=En Cours
filters_status_read=Lu
# Sort options
filters_sort_title=Titre
filters_sort_rating=Note
filters_sort_date_added=Date d'Ajout
filters_sort_release_date=Date de Sortie
filters_sort_read_progress=Progrès de Lecture
# Error messages
filters_error_loading=Échec du chargement des filtres
filters_empty_list=Aucun filtre disponible

View File

@@ -2,7 +2,7 @@ ext {
kmkVersionCode = 1 kmkVersionCode = 1
extName = 'Kavita' extName = 'Kavita'
extClass = '.KavitaFactory' extClass = '.KavitaFactory'
extVersionCode = 19 extVersionCode = 20
isNsfw = false isNsfw = false
} }

View File

@@ -3,25 +3,6 @@ package eu.kanade.tachiyomi.extension.all.kavita
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSelected import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSelected
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
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>) : class SmartFiltersFilter(smartFilters: Array<String>) :
Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters) Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters)
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true)) class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true))
@@ -38,15 +19,22 @@ val sortableList = listOf(
Pair("Random", 9), Pair("Random", 9),
) )
class SpecialListFilter : Filter.Select<String>( class SpecialListFilter(name: String = "Special Lists") : Filter.Select<String>(
"Special Lists", name,
arrayOf("None", "Want to Read", "Reading Lists"), arrayOf("None", "Want to Read", "Reading Lists"),
) )
// Use Filter.CheckBox directly for status class StatusFilter : Filter.Select<String>(
class StatusFilter(name: String) : Filter.CheckBox(name, false) "Status",
class StatusFilterGroup(filters: List<StatusFilter>) : arrayOf("Any", "Unread", "In Progress", "Read"),
Filter.Group<StatusFilter>("Status", filters) )
class StatusSeparator :
Filter.Separator()
class StatusFilterGroup(name: String = "Status") : Filter.Group<StatusFilter>(
name,
listOf(StatusFilter()),
)
// Use Filter.Text directly for release year range // Use Filter.Text directly for release year range
class ReleaseYearRange(name: String) : Filter.Text(name, "") class ReleaseYearRange(name: String) : Filter.Text(name, "")
@@ -59,7 +47,8 @@ class GenreFilterGroup(genres: List<GenreFilter>) :
Filter.Group<GenreFilter>("Genres", genres) Filter.Group<GenreFilter>("Genres", genres)
class TagFilter(name: String) : Filter.TriState(name) class TagFilter(name: String) : Filter.TriState(name)
class TagFilterGroup(tags: List<TagFilter>) : Filter.Group<TagFilter>("Tags", tags) class TagFilterGroup(tags: List<TagFilter>) :
Filter.Group<TagFilter>("Tags", tags)
class AgeRatingFilter(name: String) : Filter.TriState(name) class AgeRatingFilter(name: String) : Filter.TriState(name)
class AgeRatingFilterGroup(ageRatings: List<AgeRatingFilter>) : class AgeRatingFilterGroup(ageRatings: List<AgeRatingFilter>) :
@@ -85,8 +74,29 @@ class PubStatusFilter(name: String) : Filter.CheckBox(name, false)
class PubStatusFilterGroup(status: List<PubStatusFilter>) : class PubStatusFilterGroup(status: List<PubStatusFilter>) :
Filter.Group<PubStatusFilter>("Publication Status", status) Filter.Group<PubStatusFilter>("Publication Status", status)
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 UserRatingSeparator :
Filter.Separator()
class PeopleHeaderFilter(name: String) : class PeopleHeaderFilter(name: String) :
Filter.Header(name) Filter.Header(name.toString())
class PeopleSeparatorFilter : class PeopleSeparatorFilter :
Filter.Separator() Filter.Separator()

View File

@@ -2,6 +2,11 @@ package eu.kanade.tachiyomi.extension.all.kavita
object KavitaConstants { object KavitaConstants {
const val UNNUMBERED_VOLUME = -100000
const val UNNUMBERED_VOLUME_STR = "-100000"
const val SPECIAL_NUMBER = 100_000
const val VOLUME_NUMBER_OFFSET = 10000f
val PERSON_ROLES = listOf( val PERSON_ROLES = listOf(
"Writer", "Penciller", "Inker", "Colorist", "Writer", "Penciller", "Inker", "Colorist",
"Letterer", "CoverArtist", "Editor", "Letterer", "CoverArtist", "Editor",
@@ -23,18 +28,7 @@ object KavitaConstants {
"Publication Status", "Publication Status",
"Rating", "Rating",
"Age Rating", "Age Rating",
"Writers",
"Penciller",
"Inker",
"Colorist",
"Letterer",
"Cover Artist",
"Editor",
"Publisher",
"Character",
"Translators",
"ReleaseYearRange", "ReleaseYearRange",
"Average Rating",
"Random", "Random",
"Read Progress", "Read Progress",
) )
@@ -51,18 +45,7 @@ object KavitaConstants {
"Publication Status", "Publication Status",
"Rating", "Rating",
"Age Rating", "Age Rating",
"Writers",
"Penciller",
"Inker",
"Colorist",
"Letterer",
"CoverArtist",
"Editor",
"Publisher",
"Character",
"Translators",
"ReleaseYearRange", "ReleaseYearRange",
"Average Rating",
"Random", "Random",
"Read Progress", "Read Progress",
) )
@@ -79,18 +62,7 @@ object KavitaConstants {
"Publication Status", "Publication Status",
"Rating", "Rating",
"Age Rating", "Age Rating",
"Writers",
"Penciller",
"Inker",
"Colorist",
"Letterer",
"CoverArtist",
"Editor",
"Publisher",
"Character",
"Translators",
"ReleaseYearRange", "ReleaseYearRange",
"Average Rating",
"Random", "Random",
"Read Progress", "Read Progress",
) )

View File

@@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.extension.all.kavita package eu.kanade.tachiyomi.extension.all.kavita
import android.util.Log import android.annotation.SuppressLint
import eu.kanade.tachiyomi.extension.all.kavita.dto.ChapterDto 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.ChapterType
import eu.kanade.tachiyomi.extension.all.kavita.dto.LibraryTypeEnum import eu.kanade.tachiyomi.extension.all.kavita.dto.LibraryTypeEnum
@@ -13,170 +13,204 @@ import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Response import okhttp3.Response
import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone
class KavitaHelper { class KavitaHelper {
val json = Json { val json = Json {
isLenient = true isLenient = true
ignoreUnknownKeys = true ignoreUnknownKeys = true
coerceInputValues = true
explicitNulls = false
allowSpecialFloatingPointValues = true allowSpecialFloatingPointValues = true
useArrayPolymorphism = true useArrayPolymorphism = true
prettyPrint = true prettyPrint = true
} }
inline fun <reified T : Enum<T>> 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 { fun hasNextPage(response: Response): Boolean {
val paginationHeader = response.header("Pagination") val paginationHeader = response.header("Pagination")
var hasNextPage = false var hasNextPage = false
if (!paginationHeader.isNullOrEmpty()) { if (!paginationHeader.isNullOrEmpty()) {
val paginationInfo = json.decodeFromString<PaginationInfo>(paginationHeader) val paginationInfo = json.decodeFromString<PaginationInfo>(paginationHeader)
hasNextPage = paginationInfo.currentPage + 1 > paginationInfo.totalPages hasNextPage = paginationInfo.currentPage + 1 < paginationInfo.totalPages
} }
return !hasNextPage return hasNextPage
} }
fun getIdFromUrl(url: String): Int { fun getIdFromUrl(url: String): Int {
return try { return try {
val id = url.substringAfterLast("/").toInt() val id = url.substringAfterLast("/").toInt()
Log.d("KavitaHelper", "Extracted ID $id from URL: $url") // Log.d("KavitaHelper", "Extracted ID $id from URL: $url")
id id
} catch (e: Exception) { } catch (e: Exception) {
Log.e("KavitaHelper", "Failed to extract ID from URL: $url", e) // Log.e("KavitaHelper", "Failed to extract ID from URL: $url", e)
-1 -1
} }
} }
// Rating Providers from Series-Details-Plus // @todo Rating Providers from Series-Details-Plus
private fun getProviderName(provider: Int): String = when (provider) { // private fun getProviderName(provider: Int): String = when (provider) {
0 -> "User" // 0 -> "User"
1 -> "AniList" // 1 -> "AniList"
2 -> "MyAnimeList" // 2 -> "MyAnimeList"
3 -> "MangaUpdates" // 3 -> "MangaUpdates"
else -> "Rating" // else -> "Rating"
} // }
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiUrl: String, apiKey: String): SManga = fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiKey: String): SManga =
SManga.create().apply { SManga.create().apply {
// url = "$baseUrl/library/${obj.libraryId}/series/${obj.id}"
url = "$baseUrl/Series/${obj.id}" url = "$baseUrl/Series/${obj.id}"
title = obj.name title = obj.name
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey" thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
// Set status based on read progress
status = when {
obj.pagesRead >= obj.pages -> SManga.COMPLETED
obj.pagesRead > 0 -> SManga.ONGOING
else -> SManga.UNKNOWN
}
} }
// class CompareChapters { fun chapterFromVolume(
// companion object : Comparator<SChapter> { chapter: ChapterDto,
// override fun compare(a: SChapter, b: SChapter): Int { volume: VolumeDto,
// if (a.chapter_number < 1.0 && b.chapter_number < 1.0) { singleFileVolumeNumber: Double? = null,
// // Both are volumes, multiply by 100 and do normal sort libraryType: LibraryTypeEnum? = null,
// return if ((a.chapter_number * 100) < (b.chapter_number * 100)) { isWebtoon: Boolean = false,
// 1 mangaTitle: String = "",
// } else { ): SChapter =
// -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 { SChapter.create().apply {
val type = ChapterType.of(chapter, volume, libraryType) val type = ChapterType.of(chapter, volume, libraryType)
val titleName = chapter.titleName
val title = chapter.title
val range = chapter.range
// For webtoon
val chapterLabel = if (isWebtoon) "Episode" else "Chapter"
val volumeLabel = if (isWebtoon) "Season" else "Volume"
if (titleName != null) {
name = when (type) { name = when (type) {
ChapterType.Regular -> { ChapterType.Regular -> {
val volumeNum = volume.number.toString().toIntOrNull()?.let { val chapterNum = formatChapterNumber(chapter)
it.toString().padStart(2, '0')
} ?: volume.number.toString()
val chapterNum = chapter.number.toIntOrNull()?.let {
it.toString().padStart(2, '0')
} ?: chapter.number
when { when {
chapter.titleName.isBlank() -> "Volume $volumeNum Chapter $chapterNum" titleName.isNotBlank() && titleName.any { it.isLetter() } ->
chapter.titleName.trim().matches(Regex("^\\d+$")) -> "Volume $volumeNum Chapter ${chapter.titleName.trim().padStart(2, '0')}" "$chapterNum - ${cleanChapterTitle(
else -> "Volume $volumeNum $chapterNum - ${chapter.titleName}" titleName,
ChapterTitleContext(
mangaTitle = mangaTitle,
chapterNumber = chapterNum,
volumeName = volume.name.ifBlank { title },
),
)}"
else ->
"$volumeLabel ${formatVolumeNumber(volume)} $chapterLabel $chapterNum"
} }
} }
ChapterType.SingleFileVolume -> { ChapterType.SingleFileVolume -> {
val volumeNumber = formatVolumeNumber(volume)
val cleanVolumeName = volume.name.trim()
// Always use the API's volume number, never parse from title
when { when {
volume.name.isBlank() -> "Volume ${volume.number}" // If name is empty or just numbers (Kavita default)
volume.name.trim().matches(Regex("^\\d+$")) -> "Volume ${volume.name.trim().toIntOrNull()?.toString() ?: volume.name.trim()}" cleanVolumeName.isEmpty() || cleanVolumeName.none { it.isLetter() } -> {
else -> "${volume.number} - ${volume.name}" cleanChapterTitle("Volume $volumeNumber")
}
// If name already contains volume info (Vol. 3, V3, etc.)
cleanVolumeName.contains(Regex("(?i)(vol|volume|v)[.\\s]*\\d+")) -> {
cleanChapterTitle(cleanVolumeName)
}
// Normal case - prefix with volume number
else -> {
cleanChapterTitle("$volumeNumber - $cleanVolumeName")
} }
} }
ChapterType.Special -> chapter.titleName.takeIf { it.isNotBlank() } ?: chapter.range }
ChapterType.Special -> {
cleanChapterTitle(
when {
title.isNotBlank() -> title
range.isNotBlank() -> range
else -> "Special"
},
ChapterTitleContext(
mangaTitle = mangaTitle,
volumeName = volume.name,
),
)
}
ChapterType.Chapter -> { ChapterType.Chapter -> {
val chapterNum = chapter.number.toIntOrNull()?.let { val chapterNum = formatChapterNumber(chapter)
it.toString().padStart(2, '0')
} ?: chapter.number
when { when {
chapter.titleName.isBlank() -> "Chapter $chapterNum" titleName.isNotBlank() && titleName.any { it.isLetter() } ->
chapter.titleName.trim().matches(Regex("^\\d+$")) -> "Chapter ${chapter.titleName.trim().padStart(2, '0')}" "$chapterNum - ${cleanChapterTitle(
else -> "$chapterNum - ${chapter.titleName}" titleName,
ChapterTitleContext(
mangaTitle = mangaTitle,
chapterNumber = chapterNum,
volumeName = volume.name,
),
)}"
else ->
"$chapterLabel $chapterNum"
} }
} }
ChapterType.Issue -> { ChapterType.Issue -> {
val issueNum = chapter.number.toIntOrNull()?.let { val issueNum = chapter.number.toIntOrNull()?.toString()?.padStart(3, '0') ?: chapter.number
it.toString().padStart(3, '0') cleanChapterTitle(
} ?: chapter.number
when { when {
chapter.titleName.isNotBlank() && !chapter.titleName.trim().matches(Regex("^\\d+$")) -> titleName.any { it.isLetter() } -> "$titleName (#$issueNum)"
"#$issueNum ${chapter.titleName}"
chapter.title.isNotBlank() && !chapter.title.trim().matches(Regex("^\\d+$")) ->
"#$issueNum ${chapter.title}"
else -> "Issue #$issueNum" else -> "Issue #$issueNum"
},
ChapterTitleContext(
mangaTitle = mangaTitle,
chapterNumber = issueNum,
volumeName = volume.name,
),
)
} }
} }
} }
// chapter_number = try { // Handle decimal chapter numbers properly
// // if (type == ChapterType.SingleFileVolume) {
// // volume.number.toFloat() / 10000
// // } else {
// // chapter.number.toFloatOrNull() ?: 0f
// // }
// // } catch (e: NumberFormatException) {
// // 0f
// // }
chapter_number = when { chapter_number = when {
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> singleFileVolumeNumber.toFloat() type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
type != ChapterType.SingleFileVolume -> chapter.number.toFloatOrNull() ?: 0f volume.minNumber.toFloat()
else -> 0f
type == ChapterType.SingleFileVolume -> {
// For standalone volumes, use positive numbers
volume.minNumber.toFloat()
}
// For regular chapters
type != ChapterType.SingleFileVolume && type != ChapterType.Special -> {
// Handle decimal chapter numbers
if (chapter.minNumber % 1 != 0.0) {
chapter.minNumber.toFloat()
} else {
chapter.minNumber.toInt().toFloat()
}
}
// For volumes/specials in mixed content, place them below chapter 0
else -> {
val volumeNum = try {
if (volume.minNumber % 1 != 0.0) {
volume.minNumber.toFloat()
} else {
volume.minNumber.toInt().toFloat()
}
} catch (e: NumberFormatException) {
0f
}
volumeNum / KavitaConstants.VOLUME_NUMBER_OFFSET
}
} }
url = when { url = when {
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
"volume_${volume.id}" "volume_${volume.id}"
else -> chapter.id.toString() else -> "chapter_${chapter.id}"
} }
// url = chapter.id.toString()
if (chapter.fileCount > 1) { if (chapter.fileCount > 1) {
// salt/offset to recognize chapters with new merged part-chapters as new and hence unread // salt/offset to recognize chapters with new merged part-chapters as new and hence unread
chapter_number += 0.001f * chapter.fileCount chapter_number += 0.001f * chapter.fileCount
@@ -185,11 +219,86 @@ class KavitaHelper {
date_upload = parseDateSafe(chapter.created) date_upload = parseDateSafe(chapter.created)
scanlator = when (type) { scanlator = when (type) {
ChapterType.SingleFileVolume -> "Volume" ChapterType.SingleFileVolume -> if (isWebtoon) "Season" else "Volume"
ChapterType.Special -> "Special" ChapterType.Special -> "Special"
ChapterType.Issue -> "Issue" ChapterType.Issue -> "Issue"
ChapterType.Chapter -> "Chapter" ChapterType.Chapter -> if (isWebtoon) "Episode" else "Chapter"
ChapterType.Regular -> "Chapter" ChapterType.Regular -> if (isWebtoon) "Episode" else "Chapter"
}
}
private fun formatVolumeNumber(volume: VolumeDto): String {
return when {
volume.maxNumber > volume.minNumber ->
"${removeTrailingZero(volume.minNumber)}-${removeTrailingZero(volume.maxNumber)}"
else -> removeTrailingZero(volume.minNumber)
}
}
private fun formatChapterNumber(chapter: ChapterDto, padLength: Int = 2): String {
val chapterNum = when {
chapter.maxNumber > chapter.minNumber ->
"${removeTrailingZero(chapter.minNumber)}-${removeTrailingZero(chapter.maxNumber)}"
else -> removeTrailingZero(chapter.minNumber)
}
// Only pad if it's a single whole number (not a range and not decimal)
return if (!chapterNum.contains('-') && !chapterNum.contains('.')) {
chapterNum.toIntOrNull()?.toString()?.padStart(padLength, '0') ?: chapterNum
} else {
chapterNum
}
}
// Helper function to remove .0 from whole numbers while preserving actual decimals
@SuppressLint("DefaultLocale")
private fun removeTrailingZero(number: Double): String {
return if (number % 1 == 0.0) {
number.toInt().toString()
} else {
// Use String.format to avoid scientific notation for very small decimals
String.format("%.10f", number).replace(",", ".").trimEnd('0').trimEnd('.')
}
}
private fun cleanChapterTitle(
originalTitle: String,
context: ChapterTitleContext? = null,
): String {
var title = originalTitle.trim()
val mangaTitle = context?.mangaTitle ?: ""
// Remove manga name if present
if (mangaTitle.isNotBlank()) {
title = title.replace(Regex("(?i)\\b${Regex.escape(mangaTitle)}\\b[\\s\\-:]*"), "").trim()
}
// Remove chapter number patterns like c0116, c001, etc.
title = title.replace(Regex("""(?i)\bc\d+\b"""), "").trim()
// Clean up problematic hyphen/space patterns
title = title.replace(Regex("""\s*[-.]\s*[-.]\s*"""), " - ") // Handles -.-, - . -, etc.
.replace(Regex("""\s*-\s*-\s*"""), " - ") // Double hyphens with spaces
.replace(Regex("""^\s*[-.]\s*|\s*[-.]\s*$"""), "") // Leading/trailing hyphens or dots
.trim()
return title.ifBlank { originalTitle }
}
/**
* Data class to hold context information for cleaning chapter titles
*/
data class ChapterTitleContext(
val mangaTitle: String = "",
val chapterNumber: String = "",
val volumeName: String = "",
val isWebtoon: Boolean = false,
)
fun isWebtoonOrLongStrip(tags: List<String>): Boolean {
return tags.any {
val normalized = it.trim().lowercase()
normalized.contains("webtoon") || normalized.contains("long strip")
} }
} }

View File

@@ -10,11 +10,6 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone 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 { fun parseDateSafe(date: String?): Long {
return date?.let { return date?.let {
try { try {
@@ -80,7 +75,7 @@ fun PreferenceScreen.addEditTextPreference(
val result = text.isBlank() || validate?.invoke(text) ?: true val result = text.isBlank() || validate?.invoke(text) ?: true
if (restartRequired && result) { if (restartRequired && result) {
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() Toast.makeText(context, "Restart Mihon to apply new setting.", Toast.LENGTH_LONG).show()
} }
result result

View File

@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.extension.all.kavita.dto package eu.kanade.tachiyomi.extension.all.kavita.dto
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -8,7 +9,7 @@ interface ConvertibleToSManga {
fun toSManga(baseUrl: String, apiUrl: String, apiKey: String): SManga fun toSManga(baseUrl: String, apiUrl: String, apiKey: String): SManga
} }
@Serializable @Serializable // https://github.com/Kareadita/Kavita/blob/develop/API/Entities/Enums/MangaFormat.cs
enum class MangaFormat(val format: Int) { enum class MangaFormat(val format: Int) {
Image(0), Image(0),
Archive(1), Archive(1),
@@ -19,7 +20,6 @@ enum class MangaFormat(val format: Int) {
companion object { companion object {
private val map = values().associateBy(MangaFormat::format) private val map = values().associateBy(MangaFormat::format)
fun fromInt(type: Int): MangaFormat? = map[type]
} }
} }
@@ -189,8 +189,9 @@ data class AuthorDto(
@Serializable @Serializable
data class VolumeDto( data class VolumeDto(
val id: Int, val id: Int,
val number: Int, val minNumber: Double,
val name: String, val maxNumber: Double,
val name: String = "",
val pages: Int, val pages: Int,
val pagesRead: Int, val pagesRead: Int,
val lastModified: String, val lastModified: String,
@@ -202,27 +203,34 @@ data class VolumeDto(
@Serializable @Serializable
enum class ChapterType { enum class ChapterType {
Regular, // chapter with volume information Regular, // Chapter with volume information
Chapter, // manga chapter without volume information Chapter, // Chapter without volume information
SingleFileVolume, SingleFileVolume,
Special, Special,
Issue, // For comics Issue, // For comics
; ;
companion object { companion object {
private const val UNNUMBERED_VOLUME_NUMBER = -100_000
fun of(chapter: ChapterDto, volume: VolumeDto, libraryType: LibraryTypeEnum? = null): ChapterType = fun of(chapter: ChapterDto, volume: VolumeDto, libraryType: LibraryTypeEnum? = null): ChapterType =
when { when {
volume.number == 100_000 -> Special // Special
volume.number == -100_000 -> when (libraryType) { volume.minNumber.toInt() == KavitaConstants.SPECIAL_NUMBER ||
chapter.minNumber.toInt() == KavitaConstants.SPECIAL_NUMBER -> Special
// Issue
volume.minNumber.toInt() == UNNUMBERED_VOLUME_NUMBER -> when (libraryType) {
LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue
LibraryTypeEnum.Manga, LibraryTypeEnum.LightNovel, LibraryTypeEnum.Book -> Chapter else -> Chapter
else -> Chapter // Default to Chapter for other types
} }
chapter.number == "-100000" -> SingleFileVolume // SingleFileVolume
chapter.number == KavitaConstants.UNNUMBERED_VOLUME_STR -> SingleFileVolume
// Regular
volume.minNumber > 0 -> Regular
// Everything else depends on library type
else -> when (libraryType) { else -> when (libraryType) {
LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue LibraryTypeEnum.Comic, LibraryTypeEnum.ComicVine -> Issue
LibraryTypeEnum.Manga, LibraryTypeEnum.LightNovel, LibraryTypeEnum.Book -> Chapter else -> Chapter
else -> Regular
} }
} }
} }
@@ -233,12 +241,15 @@ data class ChapterDto(
val id: Int, val id: Int,
val range: String, val range: String,
val number: String, val number: String,
val minNumber: Double,
val maxNumber: Double,
val pages: Int, val pages: Int,
val isSpecial: Boolean, val isSpecial: Boolean,
val title: String, val title: String,
val titleName: String, val titleName: String?,
val pagesRead: Int, val pagesRead: Int,
val coverImageLocked: Boolean, val coverImageLocked: Boolean,
val coverImage: String,
val volumeId: Int, val volumeId: Int,
val created: String, val created: String,
val lastModifiedUtc: String, val lastModifiedUtc: String,
@@ -259,13 +270,13 @@ data class ReadingListDto(
val title: String, val title: String,
val coverImage: String? = null, val coverImage: String? = null,
val promoted: Boolean = false, val promoted: Boolean = false,
val summary: String?, val summary: String? = null,
val itemCount: Int, val itemCount: Int,
val startingYear: Int, val startingYear: Int,
val startingMonth: Int, val startingMonth: Int,
val endingYear: Int, val endingYear: Int,
val endingMonth: Int, val endingMonth: Int,
val ownerUserName: String?, val ownerUserName: String? = null,
@SerialName("items") val items: List<ReadingListItemDto> = emptyList(), @SerialName("items") val items: List<ReadingListItemDto> = emptyList(),
) )
@@ -278,11 +289,11 @@ data class ReadingListItemDto(
val seriesName: String, val seriesName: String,
val chapterNumber: String?, val chapterNumber: String?,
val volumeNumber: String?, val volumeNumber: String?,
val chapterTitleName: String?, val chapterTitleName: String? = null,
val volumeId: Int?, val volumeId: Int?,
val title: String?, val title: String? = null,
val summary: String?, val summary: String? = null,
val releaseDate: String?, val releaseDate: String? = null,
val libraryName: String?, val libraryName: String?,
@SerialName("readingListId") val readingListId: Int, @SerialName("readingListId") val readingListId: Int,
) )

View File

@@ -22,6 +22,9 @@ data class MetadataTag(val id: Int, val title: String)
@Serializable @Serializable
data class MetadataAgeRatings(val value: Int, val title: String) data class MetadataAgeRatings(val value: Int, val title: String)
@Serializable
data class MetadataUserRatings(val value: Int, val title: String)
@Serializable @Serializable
data class MetadataLanguages(val isoCode: String, val title: String) data class MetadataLanguages(val isoCode: String, val title: String)
@@ -37,40 +40,3 @@ data class SmartFilter(
val name: String, val name: String,
val filter: 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(),
)