From 66dff6569e0a86bc5b635df16e76c11d520db496 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 31 Jul 2024 19:01:35 +0530 Subject: [PATCH] Radarr and Sonarr integration (#936) * build(backend): add radarr api client * feat(backend): add new lot for integration * feat(database): add migrations for new stuff * feat(backend): adapt to new database schema * chore(frontend): set default value * feat(backend): add new source * feat(frontend): do not allow editing readarr integration * feat(frontend): do not ask for progress for push integrations * feat(database): columns to store destination specifics * feat(backend): allow adding destination specifics to integration * feat(frontend): allow adding radarr integration * feat(backend): sync integrations stuff * chore(frontend): adapt to new gql schema * refactor(backend): change name of function * feat(backend): sync collection ids too * feat(frontend): ask for sync collection ids as well * fix(frontend): no show allowed * feat(frontend): allow editing push integrations * feat(database,backend,frontend): change name for preference to disable integrations * feat(backend): stub functions to add stuff to radarr * feat(backend): allow adding movies to radarr * refactor(backend): move function out of integration * refactor(backend): import with prefix * refactor(backend): inline immediate return * fix(frontend): make collections searchable * feat(config): change name of config param * docs: add instructions for radarr integration * chore(frontend): hide controls for profile id * feat(backend): select more columns * feat(database): add column for tracking system information for collection_to_entity * feat(backend): store cte extra information * refactor(backend): send movies to radarr in a loop * chore(backend): use unused result * fix(backend): do not sync if already done * chore(backend): always return true from function * feat(backend): update collection_to_entity when radarr sync complete * fix(backend): log when movie already synced * feat(database): add new column for destination * Revert "feat(database): add new column for destination" This reverts commit 2b31cfd7d26cc9c45fcf6c2bf2fcc21edc9039ad. * feat(database): merge columns into one * feat(backend): adapt to new database schema * chore(frontend): adapt to new gql schema * feat(backend): respect disable_integrations general preference * feat(backend): store exactly which integration was synced * chore(database): comment * feat(database): account for incorrect finished shows calculations * build(backend): add sonarr deps * docs: add info about sonnar integration * feat(backend): add new integration provider * refactor(frontend): component to create dyanmic arr inputs * feat(frontend): allow creating radarr integration * feat(backend): stub function for sonarr push * feat(backend): store integrations synced to sonarr * refactor(backend): use internal function for pushing data * feat(backend): call function for sonarr * feat(backend): send shows to sonarr * fix(backend): send random title * docs: remove extra text * feat(database): add column to track external ids * feat(backend): update external ids correctly * refactor(backend): change name to match database schema * feat(backend): fetch external identifiers for tmdb movies and shows * fix(backend): use a better function for imports * fix(backend): send tvdb id * fix(backend): allow sending ids * fix(backend): handle arr service push * refactor(backend): extract into variable --- Cargo.lock | 261 +++++++++++++++++- apps/backend/Cargo.toml | 2 + apps/backend/src/background.rs | 23 +- .../src/entities/collection_to_entity.rs | 4 + apps/backend/src/entities/integration.rs | 13 +- apps/backend/src/entities/metadata.rs | 8 +- apps/backend/src/integrations.rs | 73 +++++ apps/backend/src/main.rs | 10 +- apps/backend/src/miscellaneous.rs | 226 ++++++++++++--- apps/backend/src/models.rs | 36 ++- apps/backend/src/providers/tmdb.rs | 30 +- apps/backend/src/users.rs | 4 +- .../_dashboard.settings.integrations.tsx | 192 +++++++++---- .../_dashboard.settings.miscellaneous.tsx | 8 +- .../_dashboard.settings.preferences.tsx | 6 +- docs/content/integrations.md | 21 +- docs/includes/backend-config-schema.yaml | 7 +- libs/config/src/lib.rs | 5 +- libs/database/src/definitions.rs | 7 +- .../migrations/m20230410_create_metadata.rs | 2 + .../m20231016_create_collection_to_entity.rs | 7 + .../m20240607_create_integration.rs | 20 +- ...m20240730_changes_for_push_integrations.rs | 63 +++++ libs/database/src/migrations/mod.rs | 2 + libs/generated/src/graphql/backend/gql.ts | 8 +- libs/generated/src/graphql/backend/graphql.ts | 52 ++-- .../src/backend/queries/UserPreferences.gql | 2 +- libs/graphql/src/backend/queries/combined.gql | 2 +- 28 files changed, 898 insertions(+), 196 deletions(-) create mode 100644 libs/database/src/migrations/m20240730_changes_for_push_integrations.rs diff --git a/Cargo.lock b/Cargo.lock index 4222872041..1a33f08e58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,6 +455,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -646,13 +652,13 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", "hyper 0.14.30", - "hyper-rustls", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", @@ -1997,6 +2003,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -2338,6 +2359,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -2550,7 +2590,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2573,6 +2613,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2597,7 +2638,53 @@ dependencies = [ "rustls 0.21.12", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.11", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.30", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -3071,6 +3158,23 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -3220,19 +3324,57 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_plain", - "serde_with", + "serde_with 3.9.0", "sha2", "subtle", "thiserror", "url", ] +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3766,6 +3908,21 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +[[package]] +name = "radarr-api-rs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c70393d7f8bf52eaada85331584091c498d9e2cbbc10935d1c182a81606e6a" +dependencies = [ + "reqwest 0.11.27", + "serde", + "serde_derive", + "serde_json", + "serde_with 2.3.3", + "url", + "uuid", +] + [[package]] name = "radium" version = "0.7.0" @@ -3963,11 +4120,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", - "hyper-rustls", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -3983,7 +4140,7 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -4006,15 +4163,18 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", - "hyper-rustls", + "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", "mime", + "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4026,7 +4186,8 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-rustls", + "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -4044,26 +4205,35 @@ checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.1", "http-body-util", "hyper 1.4.1", + "hyper-rustls 0.27.2", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration", "tokio", + "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -4388,6 +4558,7 @@ dependencies = [ "nanoid", "openidconnect", "paginate", + "radarr-api-rs", "rand 0.9.0-alpha.1", "regex", "reqwest 0.11.23", @@ -4403,8 +4574,9 @@ dependencies = [ "serde", "serde-xml-rs", "serde_json", - "serde_with", + "serde_with 3.9.0", "slug", + "sonarr-api-rs", "strum", "struson", "tokio", @@ -4840,6 +5012,22 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros 2.3.3", + "time", +] + [[package]] name = "serde_with" version = "3.9.0" @@ -4854,10 +5042,22 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with_macros", + "serde_with_macros 3.9.0", "time", ] +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_with_macros" version = "3.9.0" @@ -5021,6 +5221,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "sonarr-api-rs" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f85e838c6093957978793ece2c0893c838da8dd5bcab0ff6f57fcaa8fc8ee9" +dependencies = [ + "reqwest 0.12.5", + "serde", + "serde_json", + "serde_with 3.9.0", + "url", + "uuid", +] + [[package]] name = "spin" version = "0.9.8" @@ -5615,6 +5829,16 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -5636,6 +5860,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.11", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" diff --git a/apps/backend/Cargo.toml b/apps/backend/Cargo.toml index 0e4f72ad02..b535988553 100644 --- a/apps/backend/Cargo.toml +++ b/apps/backend/Cargo.toml @@ -54,6 +54,7 @@ mime_guess = "=2.0.5" nanoid = { workspace = true } openidconnect = "=3.5.0" paginate = "=1.1.11" +radarr-api-rs = "=3.0.1" rand = "=0.9.0-alpha.1" regex = "=1.10.5" # FIXME: Upgrade once https://github.com/seanmonstar/reqwest/pull/1620 is merged @@ -76,6 +77,7 @@ serde_json = { workspace = true } serde_with = { version = "=3.9.0", features = ["chrono_0_4"] } serde-xml-rs = "=0.6.0" slug = "=0.1.5" +sonarr-api-rs = "=3.0.0" strum = { workspace = true } struson = { version = "=0.5.0", features = ["serde"] } tokio = { version = "=1.38.1", features = ["full"] } diff --git a/apps/backend/src/background.rs b/apps/backend/src/background.rs index 3fe3618483..56dc2754b9 100644 --- a/apps/backend/src/background.rs +++ b/apps/backend/src/background.rs @@ -41,12 +41,17 @@ pub async fn background_jobs( Ok(()) } -pub async fn yank_integrations_data( +pub async fn sync_integrations_data( _information: ScheduledJob, misc_service: Data>, ) -> Result<(), Error> { tracing::trace!("Getting data from yanked integrations for all users"); misc_service.yank_integrations_data().await.unwrap(); + tracing::trace!("Sending data for push integrations for all users"); + misc_service + .send_data_for_push_integrations() + .await + .unwrap(); Ok(()) } @@ -55,7 +60,7 @@ pub async fn yank_integrations_data( // The background jobs which cannot be throttled. #[derive(Debug, Deserialize, Serialize, Display)] pub enum CoreApplicationJob { - YankIntegrationsData(String), + SyncIntegrationsData(String), BulkProgressUpdate(String, Vec), } @@ -71,10 +76,16 @@ pub async fn perform_core_application_job( tracing::trace!("Started job: {:#?}", name); let start = Instant::now(); let status = match information { - CoreApplicationJob::YankIntegrationsData(user_id) => misc_service - .yank_integrations_data_for_user(&user_id) - .await - .is_ok(), + CoreApplicationJob::SyncIntegrationsData(user_id) => { + misc_service + .push_integrations_data_for_user(&user_id) + .await + .ok(); + misc_service + .yank_integrations_data_for_user(&user_id) + .await + .is_ok() + } CoreApplicationJob::BulkProgressUpdate(user_id, input) => misc_service .bulk_progress_update(user_id, input) .await diff --git a/apps/backend/src/entities/collection_to_entity.rs b/apps/backend/src/entities/collection_to_entity.rs index 6cb3834d71..ecf442e9b0 100644 --- a/apps/backend/src/entities/collection_to_entity.rs +++ b/apps/backend/src/entities/collection_to_entity.rs @@ -5,6 +5,8 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::models::CollectionToEntitySystemInformation; + #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "collection_to_entity")] pub struct Model { @@ -21,6 +23,8 @@ pub struct Model { pub exercise_id: Option, pub workout_id: Option, pub information: Option, + #[sea_orm(column_type = "Json")] + pub system_information: CollectionToEntitySystemInformation, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/apps/backend/src/entities/integration.rs b/apps/backend/src/entities/integration.rs index ed9a1ecc54..f9aec10cf2 100644 --- a/apps/backend/src/entities/integration.rs +++ b/apps/backend/src/entities/integration.rs @@ -2,11 +2,11 @@ use async_graphql::{InputObject, SimpleObject}; use async_trait::async_trait; -use database::{IntegrationLot, IntegrationSource}; +use database::{IntegrationLot, IntegrationProvider}; use nanoid::nanoid; use sea_orm::{entity::prelude::*, ActiveValue}; -use crate::models::media::IntegrationSourceSpecifics; +use crate::models::media::IntegrationProviderSpecifics; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, SimpleObject, InputObject)] #[sea_orm(table_name = "integration")] @@ -15,19 +15,20 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] #[graphql(skip_input)] pub id: String, - pub minimum_progress: Decimal, - pub maximum_progress: Decimal, + pub minimum_progress: Option, + pub maximum_progress: Option, #[graphql(skip)] pub user_id: String, pub lot: IntegrationLot, - pub source: IntegrationSource, + pub provider: IntegrationProvider, pub is_disabled: Option, #[graphql(skip_input)] pub created_on: DateTimeUtc, #[graphql(skip_input)] pub last_triggered_on: Option, #[sea_orm(column_type = "Json")] - pub source_specifics: Option, + #[graphql(skip)] + pub provider_specifics: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/apps/backend/src/entities/metadata.rs b/apps/backend/src/entities/metadata.rs index b3a5729641..d0a3a7204a 100644 --- a/apps/backend/src/entities/metadata.rs +++ b/apps/backend/src/entities/metadata.rs @@ -9,9 +9,9 @@ use sea_orm::{entity::prelude::*, ActiveValue}; use serde::{Deserialize, Serialize}; use crate::models::media::{ - AnimeSpecifics, AudioBookSpecifics, BookSpecifics, MangaSpecifics, MetadataFreeCreator, - MetadataImage, MetadataStateChanges, MetadataVideo, MovieSpecifics, PodcastSpecifics, - ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider, + AnimeSpecifics, AudioBookSpecifics, BookSpecifics, ExternalIdentifiers, MangaSpecifics, + MetadataFreeCreator, MetadataImage, MetadataStateChanges, MetadataVideo, MovieSpecifics, + PodcastSpecifics, ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider, }; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, Default)] @@ -41,6 +41,8 @@ pub struct Model { pub free_creators: Option>, #[sea_orm(column_type = "Json")] pub watch_providers: Option>, + #[sea_orm(column_type = "Json")] + pub external_identifiers: Option, pub audio_book_specifics: Option, pub book_specifics: Option, pub movie_specifics: Option, diff --git a/apps/backend/src/integrations.rs b/apps/backend/src/integrations.rs index 5fb2cf3980..41ffb097bf 100644 --- a/apps/backend/src/integrations.rs +++ b/apps/backend/src/integrations.rs @@ -3,6 +3,13 @@ use std::future::Future; use anyhow::{anyhow, bail, Result}; use async_graphql::Result as GqlResult; use database::{MediaLot, MediaSource}; +use radarr_api_rs::{ + apis::{ + configuration::{ApiKey as RadarrApiKey, Configuration as RadarrConfiguration}, + movie_api::api_v3_movie_post as radarr_api_v3_movie_post, + }, + models::{AddMovieOptions as RadarrAddMovieOptions, MovieResource as RadarrMovieResource}, +}; use regex::Regex; use reqwest::header::{HeaderValue, AUTHORIZATION}; use rust_decimal::Decimal; @@ -10,11 +17,19 @@ use rust_decimal_macros::dec; use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; use sea_query::{extension::postgres::PgExpr, Alias, Expr, Func}; use serde::{Deserialize, Serialize}; +use sonarr_api_rs::{ + apis::{ + configuration::{ApiKey as SonarrApiKey, Configuration as SonarrConfiguration}, + series_api::api_v3_series_post as sonarr_api_v3_series_post, + }, + models::{AddSeriesOptions as SonarrAddSeriesOptions, SeriesResource as SonarrSeriesResource}, +}; use crate::{ entities::{metadata, prelude::Metadata}, models::{audiobookshelf_models, media::CommitMediaInput}, providers::google_books::GoogleBooksService, + traits::TraceOk, utils::{get_base_http_client, ilike_sql}, }; @@ -517,4 +532,62 @@ impl IntegrationService { } Ok((media_items, vec![])) } + + pub async fn radarr_push( + &self, + radarr_base_url: String, + radarr_api_key: String, + radarr_profile_id: i32, + radarr_root_folder_path: String, + tmdb_id: String, + ) -> Result<()> { + let mut configuration = RadarrConfiguration::new(); + configuration.base_path = radarr_base_url; + configuration.api_key = Some(RadarrApiKey { + key: radarr_api_key, + prefix: None, + }); + let mut resource = RadarrMovieResource::new(); + resource.tmdb_id = Some(tmdb_id.parse().unwrap()); + resource.quality_profile_id = Some(radarr_profile_id); + resource.root_folder_path = Some(Some(radarr_root_folder_path.clone())); + resource.monitored = Some(true); + let mut options = RadarrAddMovieOptions::new(); + options.search_for_movie = Some(true); + resource.add_options = Some(Box::new(options)); + radarr_api_v3_movie_post(&configuration, Some(resource)) + .await + .trace_ok(); + Ok(()) + } + + pub async fn sonarr_push( + &self, + sonarr_base_url: String, + sonarr_api_key: String, + sonarr_profile_id: i32, + sonarr_root_folder_path: String, + tvdb_id: String, + ) -> Result<()> { + let mut configuration = SonarrConfiguration::new(); + configuration.base_path = sonarr_base_url; + configuration.api_key = Some(SonarrApiKey { + key: sonarr_api_key, + prefix: None, + }); + let mut resource = SonarrSeriesResource::new(); + resource.title = Some(Some(tvdb_id.clone())); + resource.tvdb_id = Some(tvdb_id.parse().unwrap()); + resource.quality_profile_id = Some(sonarr_profile_id); + resource.root_folder_path = Some(Some(sonarr_root_folder_path.clone())); + resource.monitored = Some(true); + resource.season_folder = Some(true); + let mut options = SonarrAddSeriesOptions::new(); + options.search_for_missing_episodes = Some(true); + resource.add_options = Some(Box::new(options)); + sonarr_api_v3_series_post(&configuration, Some(resource)) + .await + .trace_ok(); + Ok(()) + } } diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs index dda502683d..0470753769 100644 --- a/apps/backend/src/main.rs +++ b/apps/backend/src/main.rs @@ -48,7 +48,7 @@ use utils::{COMPILATION_TIMESTAMP, TEMP_DIR}; use crate::{ background::{ background_jobs, perform_application_job, perform_core_application_job, - yank_integrations_data, + sync_integrations_data, }, entities::prelude::Exercise, graphql::get_schema, @@ -102,7 +102,7 @@ async fn main() -> Result<()> { .map(|f| f.parse().unwrap()) .collect_vec(); let rate_limit_count = config.scheduler.rate_limit_num; - let pull_every_minutes = config.integration.pull_every_minutes; + let sync_every_minutes = config.integration.sync_every_minutes; let max_file_size = config.server.max_file_size; let disable_background_jobs = config.server.disable_background_jobs; @@ -273,10 +273,10 @@ async fn main() -> Result<()> { ) .register_with_count( 1, - WorkerBuilder::new("yank_integrations_data") + WorkerBuilder::new("sync_integrations_data") .stream( CronStream::new_with_timezone( - Schedule::from_str(&format!("0 */{} * * * *", pull_every_minutes)) + Schedule::from_str(&format!("0 */{} * * * *", sync_every_minutes)) .unwrap(), tz, ) @@ -284,7 +284,7 @@ async fn main() -> Result<()> { ) .layer(ApalisTraceLayer::new()) .data(media_service_3.clone()) - .build_fn(yank_integrations_data), + .build_fn(sync_integrations_data), ) // application jobs .register_with_count( diff --git a/apps/backend/src/miscellaneous.rs b/apps/backend/src/miscellaneous.rs index 9bf963824f..4167dfcb5f 100644 --- a/apps/backend/src/miscellaneous.rs +++ b/apps/backend/src/miscellaneous.rs @@ -1,12 +1,14 @@ use std::{ collections::{HashMap, HashSet}, fs::File, + future::Future, iter::zip, path::PathBuf, str::FromStr, sync::Arc, }; +use anyhow::Result as AnyhowResult; use apalis::prelude::{MemoryStorage, MessageQueue}; use argon2::{Argon2, PasswordHash, PasswordVerifier}; use async_graphql::{ @@ -17,7 +19,7 @@ use chrono::{Days, Duration as ChronoDuration, NaiveDate, Utc}; use database::{ AliasedCollection, AliasedCollectionToEntity, AliasedExercise, AliasedMetadata, AliasedMetadataGroup, AliasedMetadataToGenre, AliasedPerson, AliasedSeen, AliasedUser, - AliasedUserToCollection, AliasedUserToEntity, EntityLot, IntegrationLot, IntegrationSource, + AliasedUserToCollection, AliasedUserToEntity, EntityLot, IntegrationLot, IntegrationProvider, MediaLot, MediaSource, MetadataToMetadataRelation, NotificationPlatformLot, SeenState, UserLot, UserToMediaReason, Visibility, }; @@ -77,7 +79,7 @@ use crate::{ CreateOrUpdateCollectionInput, EntityWithLot, GenreListItem, ImportOrExportItemRating, ImportOrExportItemReview, ImportOrExportItemReviewComment, ImportOrExportMediaGroupItem, ImportOrExportMediaItem, ImportOrExportMediaItemSeen, - ImportOrExportPersonItem, IntegrationSourceSpecifics, MangaSpecifics, + ImportOrExportPersonItem, IntegrationProviderSpecifics, MangaSpecifics, MediaAssociatedPersonStateChanges, MediaDetails, MetadataFreeCreator, MetadataGroupSearchItem, MetadataImage, MetadataImageForMediaDetails, MetadataPartialDetails, MetadataSearchItemResponse, MetadataVideo, MetadataVideoSource, @@ -89,8 +91,9 @@ use crate::{ ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider, }, BackendError, BackgroundJob, ChangeCollectionToEntityInput, CollectionExtraInformation, - DefaultCollection, IdAndNamedObject, MediaStateChanged, SearchDetails, SearchInput, - SearchResults, StoredUrl, StringIdObject, UserSummaryData, + CollectionToEntitySystemInformation, DefaultCollection, IdAndNamedObject, + MediaStateChanged, SearchDetails, SearchInput, SearchResults, StoredUrl, StringIdObject, + UserSummaryData, }, providers::{ anilist::{ @@ -148,10 +151,10 @@ struct CreateCustomMetadataInput { #[derive(Debug, Serialize, Deserialize, InputObject, Clone)] struct CreateUserIntegrationInput { - source: IntegrationSource, - source_specifics: Option, - minimum_progress: Decimal, - maximum_progress: Decimal, + provider: IntegrationProvider, + provider_specifics: Option, + minimum_progress: Option, + maximum_progress: Option, } #[derive(Debug, Serialize, Deserialize, InputObject, Clone)] @@ -2639,9 +2642,9 @@ impl MiscellaneousService { .await .unwrap(); } - BackgroundJob::YankIntegrationsData => { + BackgroundJob::SyncIntegrationsData => { core_sqlite_storage - .enqueue(CoreApplicationJob::YankIntegrationsData(user_id.to_owned())) + .enqueue(CoreApplicationJob::SyncIntegrationsData(user_id.to_owned())) .await .unwrap(); } @@ -2999,6 +3002,7 @@ impl MiscellaneousService { meta.book_specifics = ActiveValue::Set(input.book_specifics); meta.video_game_specifics = ActiveValue::Set(input.video_game_specifics); meta.visual_novel_specifics = ActiveValue::Set(input.visual_novel_specifics); + meta.external_identifiers = ActiveValue::Set(input.external_identifiers); let metadata = meta.update(&self.db).await.unwrap(); self.change_metadata_associations( @@ -3220,6 +3224,7 @@ impl MiscellaneousService { provider_rating: ActiveValue::Set(details.provider_rating), production_status: ActiveValue::Set(details.production_status), original_language: ActiveValue::Set(details.original_language), + external_identifiers: ActiveValue::Set(details.external_identifiers), is_nsfw: ActiveValue::Set(details.is_nsfw), is_partial: ActiveValue::Set(is_partial), free_creators: ActiveValue::Set(if details.creators.is_empty() { @@ -5421,8 +5426,8 @@ impl MiscellaneousService { } preferences.general.dashboard = value; } - "disable_yank_integrations" => { - preferences.general.disable_yank_integrations = value_bool.unwrap(); + "disable_integrations" => { + preferences.general.disable_integrations = value_bool.unwrap(); } "persist_queries" => { preferences.general.persist_queries = value_bool.unwrap(); @@ -5484,17 +5489,18 @@ impl MiscellaneousService { "Minimum progress cannot be greater than maximum progress", )); } - let lot = match input.source { - IntegrationSource::Audiobookshelf => IntegrationLot::Yank, + let lot = match input.provider { + IntegrationProvider::Audiobookshelf => IntegrationLot::Yank, + IntegrationProvider::Radarr | IntegrationProvider::Sonarr => IntegrationLot::Push, _ => IntegrationLot::Sink, }; let to_insert = integration::ActiveModel { lot: ActiveValue::Set(lot), user_id: ActiveValue::Set(user_id), - source: ActiveValue::Set(input.source), - source_specifics: ActiveValue::Set(input.source_specifics), + provider: ActiveValue::Set(input.provider), minimum_progress: ActiveValue::Set(input.minimum_progress), maximum_progress: ActiveValue::Set(input.maximum_progress), + provider_specifics: ActiveValue::Set(input.provider_specifics), ..Default::default() }; let integration = to_insert.insert(&self.db).await?; @@ -5520,10 +5526,10 @@ impl MiscellaneousService { } let mut db_integration: integration::ActiveModel = db_integration.into(); if let Some(s) = input.minimum_progress { - db_integration.minimum_progress = ActiveValue::Set(s); + db_integration.minimum_progress = ActiveValue::Set(Some(s)); } if let Some(s) = input.maximum_progress { - db_integration.maximum_progress = ActiveValue::Set(s); + db_integration.maximum_progress = ActiveValue::Set(Some(s)); } if let Some(d) = input.is_disabled { db_integration.is_disabled = ActiveValue::Set(Some(d)); @@ -5734,7 +5740,7 @@ impl MiscellaneousService { pub async fn yank_integrations_data_for_user(&self, user_id: &String) -> Result { let preferences = self.user_preferences(user_id).await?; - if preferences.general.disable_yank_integrations { + if preferences.general.disable_integrations { return Ok(false); } let integrations = Integration::find() @@ -5744,15 +5750,16 @@ impl MiscellaneousService { let mut progress_updates = vec![]; let mut collection_updates = vec![]; let mut to_update_integrations = vec![]; + let integration_service = self.get_integration_service(); for integration in integrations.into_iter() { if integration.is_disabled.unwrap_or_default() { tracing::debug!("Integration {} is disabled", integration.id); continue; } - let response = match integration.source { - IntegrationSource::Audiobookshelf => { - let specifics = integration.clone().source_specifics.unwrap(); - self.get_integration_service() + let response = match integration.provider { + IntegrationProvider::Audiobookshelf => { + let specifics = integration.clone().provider_specifics.unwrap(); + integration_service .audiobookshelf_progress( &specifics.audiobookshelf_base_url.unwrap(), &specifics.audiobookshelf_token.unwrap(), @@ -5823,6 +5830,153 @@ impl MiscellaneousService { Ok(()) } + pub async fn send_data_for_push_integrations(&self) -> Result<()> { + let users_with_integrations = Integration::find() + .filter(integration::Column::Lot.eq(IntegrationLot::Push)) + .select_only() + .column(integration::Column::UserId) + .into_tuple::() + .all(&self.db) + .await?; + for user_id in users_with_integrations { + tracing::debug!("Pushing integrations data for user {}", user_id); + self.push_integrations_data_for_user(&user_id).await?; + } + Ok(()) + } + + pub async fn push_integrations_data_for_user(&self, user_id: &String) -> Result { + let preferences = self.user_preferences(user_id).await?; + if preferences.general.disable_integrations { + return Ok(false); + } + let integrations = Integration::find() + .filter(integration::Column::UserId.eq(user_id)) + .all(&self.db) + .await?; + #[allow(clippy::too_many_arguments)] + async fn push_data_to_arr_service( + db: &DatabaseConnection, + integration: integration::Model, + lot: MediaLot, + get_collection_ids: impl Fn(IntegrationProviderSpecifics) -> Vec, + skip_in: impl Fn(CollectionToEntitySystemInformation) -> Option>, + get_identifier: impl Fn(metadata::Model) -> Option, + perform_push: impl Fn(String, IntegrationProviderSpecifics) -> F, + column_name: &str, + ) -> Result<()> + where + F: Future>, + { + let specifics = integration.provider_specifics.unwrap(); + let collection_ids = get_collection_ids(specifics.clone()); + let tmdb_ids_to_add = CollectionToEntity::find() + .find_also_related(Metadata) + .filter(metadata::Column::Lot.eq(lot)) + .filter(metadata::Column::Source.eq(MediaSource::Tmdb)) + .filter(collection_to_entity::Column::CollectionId.is_in(collection_ids)) + .all(db) + .await?; + let mut cte_to_update = vec![]; + for (cte, metadata) in tmdb_ids_to_add { + let metadata = metadata.unwrap(); + if skip_in(cte.system_information) + .unwrap_or_default() + .contains(&integration.id) + { + tracing::debug!("{} {} is already synced", lot, metadata.title); + continue; + } + if let Some(entity_identifier) = get_identifier(metadata) { + perform_push(entity_identifier, specifics.clone()) + .await + .ok(); + cte_to_update.push(cte.id); + } + } + CollectionToEntity::update_many() + .filter(collection_to_entity::Column::Id.is_in(cte_to_update)) + .col_expr( + collection_to_entity::Column::SystemInformation, + Expr::cust( + format!( + r#"JSONB_SET(system_information, '{{{col}}}', COALESCE(system_information->'{col}','[]'::JSONB) || '["{id}"]'::JSONB)"#, + col = column_name, + id = &integration.id + ) + ), + ) + .exec(db) + .await?; + Ok(()) + } + let mut to_update_integrations = vec![]; + let integration_service = self.get_integration_service(); + for integration in integrations.into_iter() { + let id = integration.id.clone(); + match integration.provider { + IntegrationProvider::Radarr => { + push_data_to_arr_service( + &self.db, + integration, + MediaLot::Movie, + |specifics| specifics.radarr_sync_collection_ids.unwrap(), + |info| info.radarr_synced, + |m| Some(m.identifier), + |entity_tmdb_id, specifics| { + integration_service.radarr_push( + specifics.radarr_base_url.unwrap(), + specifics.radarr_api_key.unwrap(), + specifics.radarr_profile_id.unwrap(), + specifics.radarr_root_folder_path.unwrap(), + entity_tmdb_id, + ) + }, + "radarr_synced", + ) + .await + .ok(); + } + IntegrationProvider::Sonarr => { + push_data_to_arr_service( + &self.db, + integration, + MediaLot::Show, + |specifics| specifics.sonarr_sync_collection_ids.unwrap(), + |info| info.sonarr_synced, + |m| { + m.external_identifiers + .and_then(|i| i.tvdb_id.map(|s| s.to_string())) + }, + |entity_tmdb_id, specifics| { + integration_service.sonarr_push( + specifics.sonarr_base_url.unwrap(), + specifics.sonarr_api_key.unwrap(), + specifics.sonarr_profile_id.unwrap(), + specifics.sonarr_root_folder_path.unwrap(), + entity_tmdb_id, + ) + }, + "sonarr_synced", + ) + .await + .ok(); + } + _ => continue, + }; + to_update_integrations.push(id); + } + Integration::update_many() + .filter(integration::Column::Id.is_in(to_update_integrations)) + .col_expr( + integration::Column::LastTriggeredOn, + Expr::value(Utc::now()), + ) + .exec(&self.db) + .await?; + Ok(true) + } + async fn admin_account_guard(&self, user_id: &String) -> Result<()> { let main_user = user_by_id(&self.db, user_id).await?; if main_user.lot != UserLot::Admin { @@ -5877,16 +6031,17 @@ impl MiscellaneousService { .one(&self.db) .await? .ok_or_else(|| Error::new("Integration does not exist".to_owned()))?; - if integration.is_disabled.unwrap_or_default() { + let preferences = self.user_preferences(&integration.user_id).await?; + if integration.is_disabled.unwrap_or_default() || preferences.general.disable_integrations { return Err(Error::new("Integration is disabled".to_owned())); } let service = self.get_integration_service(); - let maybe_progress_update = match integration.source { - IntegrationSource::Kodi => service.kodi_progress(&payload).await, - IntegrationSource::Emby => service.emby_progress(&payload).await, - IntegrationSource::Jellyfin => service.jellyfin_progress(&payload).await, - IntegrationSource::Plex => { - let specifics = integration.clone().source_specifics.unwrap(); + let maybe_progress_update = match integration.provider { + IntegrationProvider::Kodi => service.kodi_progress(&payload).await, + IntegrationProvider::Emby => service.emby_progress(&payload).await, + IntegrationProvider::Jellyfin => service.jellyfin_progress(&payload).await, + IntegrationProvider::Plex => { + let specifics = integration.clone().provider_specifics.unwrap(); service .plex_progress(&payload, specifics.plex_username) .await @@ -5913,10 +6068,10 @@ impl MiscellaneousService { pu: IntegrationMediaSeen, user_id: &String, ) -> Result<()> { - if pu.progress < integration.minimum_progress { + if pu.progress < integration.minimum_progress.unwrap() { return Ok(()); } - let progress = if pu.progress > integration.maximum_progress { + let progress = if pu.progress > integration.maximum_progress.unwrap() { dec!(100) } else { pu.progress @@ -6090,12 +6245,11 @@ impl MiscellaneousService { let min_value = values.iter().min(); let max_value = values.iter().max(); - let is_complete = match (min_value, max_value) { + + match (min_value, max_value) { (Some(min), Some(max)) => min == max && *min != 0, _ => false, - }; - - is_complete + } } else { seen_history.iter().any(|h| h.state == SeenState::Completed) }; diff --git a/apps/backend/src/models.rs b/apps/backend/src/models.rs index 2de0e8d7e0..1af78bcdc2 100644 --- a/apps/backend/src/models.rs +++ b/apps/backend/src/models.rs @@ -108,6 +108,12 @@ meta! { ), "Items that I want to be reminded about."); } +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, FromJsonQueryResult)] +pub struct CollectionToEntitySystemInformation { + pub radarr_synced: Option>, + pub sonarr_synced: Option>, +} + #[derive(Enum, Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq)] pub enum BackgroundJob { CalculateSummary, @@ -115,7 +121,7 @@ pub enum BackgroundJob { UpdateAllMetadata, UpdateAllExercises, RecalculateCalendarEvents, - YankIntegrationsData, + SyncIntegrationsData, PerformBackgroundTasks, } @@ -880,6 +886,21 @@ pub mod media { pub languages: HashSet, } + #[derive( + Clone, + Debug, + PartialEq, + FromJsonQueryResult, + Eq, + Serialize, + Deserialize, + SimpleObject, + Default, + )] + pub struct ExternalIdentifiers { + pub tvdb_id: Option, + } + #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct MediaDetails { pub identifier: String, @@ -904,6 +925,7 @@ pub mod media { pub watch_providers: Vec, pub audio_book_specifics: Option, pub book_specifics: Option, + pub external_identifiers: Option, pub movie_specifics: Option, pub podcast_specifics: Option, pub show_specifics: Option, @@ -1285,10 +1307,20 @@ pub mod media { )] #[graphql(input_name = "IntegrationSourceSpecificsInput")] #[serde(rename_all = "snake_case")] - pub struct IntegrationSourceSpecifics { + pub struct IntegrationProviderSpecifics { pub plex_username: Option, pub audiobookshelf_base_url: Option, pub audiobookshelf_token: Option, + pub radarr_base_url: Option, + pub radarr_api_key: Option, + pub radarr_profile_id: Option, + pub radarr_root_folder_path: Option, + pub radarr_sync_collection_ids: Option>, + pub sonarr_base_url: Option, + pub sonarr_api_key: Option, + pub sonarr_profile_id: Option, + pub sonarr_root_folder_path: Option, + pub sonarr_sync_collection_ids: Option>, } } diff --git a/apps/backend/src/providers/tmdb.rs b/apps/backend/src/providers/tmdb.rs index 7e0e8fe74b..718f2071d6 100644 --- a/apps/backend/src/providers/tmdb.rs +++ b/apps/backend/src/providers/tmdb.rs @@ -26,10 +26,11 @@ use crate::{ entities::metadata_group::MetadataGroupWithoutId, models::{ media::{ - MediaDetails, MetadataGroupSearchItem, MetadataImage, MetadataImageForMediaDetails, - MetadataPerson, MetadataSearchItem, MetadataVideo, MetadataVideoSource, MovieSpecifics, - PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem, - PersonSourceSpecifics, ShowEpisode, ShowSeason, ShowSpecifics, WatchProvider, + ExternalIdentifiers, MediaDetails, MetadataGroupSearchItem, MetadataImage, + MetadataImageForMediaDetails, MetadataPerson, MetadataSearchItem, MetadataVideo, + MetadataVideoSource, MovieSpecifics, PartialMetadataPerson, PartialMetadataWithoutId, + PeopleSearchItem, PersonSourceSpecifics, ShowEpisode, ShowSeason, ShowSpecifics, + WatchProvider, }, IdObject, NamedObject, SearchDetails, SearchResults, StoredUrl, }, @@ -355,6 +356,20 @@ impl TmdbService { .map_err(|e| anyhow!(e))?; Ok(!changes.changes.is_empty()) } + + async fn get_external_identifiers( + &self, + type_: &str, + identifier: &str, + ) -> Result { + let rsp = self + .client + .get(format!("{}/{}/external_ids", type_, identifier)) + .send() + .await + .map_err(|e| anyhow!(e))?; + rsp.json().await.map_err(|e| anyhow!(e)) + } } impl MediaProviderLanguages for TmdbService { @@ -770,6 +785,10 @@ impl MediaProvider for TmdbMovieService { .base .get_all_watch_providers("movie", identifier) .await?; + let external_identifiers = self + .base + .get_external_identifiers("movie", identifier) + .await?; Ok(MediaDetails { identifier: data.id.to_string(), is_nsfw: data.adult, @@ -817,6 +836,7 @@ impl MediaProvider for TmdbMovieService { .map(|c| c.id.to_string()) .collect(), watch_providers, + external_identifiers: Some(external_identifiers), ..Default::default() }) } @@ -1124,6 +1144,7 @@ impl MediaProvider for TmdbShowService { .flat_map(|s| s.episodes.iter()) .count(); let watch_providers = self.base.get_all_watch_providers("tv", identifier).await?; + let external_identifiers = self.base.get_external_identifiers("tv", identifier).await?; Ok(MediaDetails { identifier: show_data.id.to_string(), title: show_data.name.unwrap(), @@ -1210,6 +1231,7 @@ impl MediaProvider for TmdbShowService { }), suggestions, watch_providers, + external_identifiers: Some(external_identifiers), provider_rating: if let Some(av) = show_data.vote_average { if av != dec!(0) { Some(av * dec!(10)) diff --git a/apps/backend/src/users.rs b/apps/backend/src/users.rs index 66e8b47e02..1d7e31916f 100644 --- a/apps/backend/src/users.rs +++ b/apps/backend/src/users.rs @@ -290,7 +290,7 @@ pub struct UserGeneralPreferences { pub watch_providers: Vec, pub review_scale: UserReviewScale, pub disable_watch_providers: bool, - pub disable_yank_integrations: bool, + pub disable_integrations: bool, pub disable_navigation_animation: bool, pub dashboard: Vec, } @@ -318,7 +318,7 @@ impl Default for UserGeneralPreferences { }, ], persist_queries: true, - disable_yank_integrations: false, + disable_integrations: false, disable_navigation_animation: false, disable_videos: false, disable_watch_providers: false, diff --git a/apps/frontend/app/routes/_dashboard.settings.integrations.tsx b/apps/frontend/app/routes/_dashboard.settings.integrations.tsx index 8bf3d25873..81dd33c04c 100644 --- a/apps/frontend/app/routes/_dashboard.settings.integrations.tsx +++ b/apps/frontend/app/routes/_dashboard.settings.integrations.tsx @@ -10,6 +10,7 @@ import { Flex, Group, Modal, + MultiSelect, NumberInput, Paper, Select, @@ -27,12 +28,12 @@ import { CreateUserIntegrationDocument, DeleteUserIntegrationDocument, GenerateAuthTokenDocument, - IntegrationSource, + IntegrationProvider, UpdateUserIntegrationDocument, UserIntegrationsDocument, type UserIntegrationsQuery, } from "@ryot/generated/graphql/backend/graphql"; -import { changeCase, processSubmission } from "@ryot/ts-utils"; +import { changeCase, isString, processSubmission } from "@ryot/ts-utils"; import { IconCheck, IconCopy, @@ -49,10 +50,15 @@ import { z } from "zod"; import { zx } from "zodix"; import { confirmWrapper } from "~/components/confirmation"; import { dayjsLib } from "~/lib/generals"; -import { useConfirmSubmit } from "~/lib/hooks"; +import { useConfirmSubmit, useUserCollections } from "~/lib/hooks"; import { createToastHeaders, serverGqlService } from "~/lib/utilities.server"; -const YANK_INTEGRATIONS = [IntegrationSource.Audiobookshelf]; +const YANK_INTEGRATIONS = [IntegrationProvider.Audiobookshelf]; +const PUSH_INTEGRATIONS = [ + IntegrationProvider.Radarr, + IntegrationProvider.Sonarr, +]; +const NO_SHOW_URL = [...YANK_INTEGRATIONS, ...PUSH_INTEGRATIONS]; export const loader = unstable_defineLoader(async ({ request }) => { const [{ userIntegrations }] = await Promise.all([ @@ -139,15 +145,30 @@ export const action = unstable_defineAction(async ({ request }) => { const MINIMUM_PROGRESS = "2"; const MAXIMUM_PROGRESS = "95"; +const commaDelimitedString = z + .string() + .optional() + .transform((v) => (isString(v) ? v.split(",") : undefined)); + const createSchema = z.object({ - source: z.nativeEnum(IntegrationSource), - minimumProgress: z.string().default(MINIMUM_PROGRESS), - maximumProgress: z.string().default(MAXIMUM_PROGRESS), - sourceSpecifics: z + provider: z.nativeEnum(IntegrationProvider), + minimumProgress: z.string().optional(), + maximumProgress: z.string().optional(), + providerSpecifics: z .object({ plexUsername: z.string().optional(), audiobookshelfBaseUrl: z.string().optional(), audiobookshelfToken: z.string().optional(), + radarrBaseUrl: z.string().optional(), + radarrApiKey: z.string().optional(), + radarrProfileId: z.number().optional(), + radarrRootFolderPath: z.string().optional(), + radarrSyncCollectionIds: commaDelimitedString, + sonarrBaseUrl: z.string().optional(), + sonarrApiKey: z.string().optional(), + sonarrProfileId: z.number().optional(), + sonarrRootFolderPath: z.string().optional(), + sonarrSyncCollectionIds: commaDelimitedString, }) .optional(), }); @@ -284,7 +305,7 @@ const DisplayIntegration = (props: { - {changeCase(props.integration.source)} + {changeCase(props.integration.provider)} {props.integration.isDisabled ? ( (Paused) @@ -301,7 +322,7 @@ const DisplayIntegration = (props: { ) : null} - {!YANK_INTEGRATIONS.includes(props.integration.source) ? ( + {!NO_SHOW_URL.includes(props.integration.provider) ? ( {integrationInputOpened ? : } @@ -357,7 +378,7 @@ const CreateIntegrationModal = (props: { createModalOpened: boolean; closeIntegrationModal: () => void; }) => { - const [source, setSource] = useState(null); + const [provider, setProvider] = useState(null); return ( - - - - - - props.closeIntegrationModal()} + action={withQuery("", { intent: "update" })} + > + - - - + + {!PUSH_INTEGRATIONS.includes( + props.updateIntegrationData.provider, + ) ? ( + + + + + ) : null} + + + + + ) : null} ); }; diff --git a/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx b/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx index 057458f739..4c755c42d9 100644 --- a/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx +++ b/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx @@ -153,12 +153,12 @@ export default function Page() { Synchronize integrations progress - Get data from all configured integrations and update progress - if applicable. The more integrations you have enabled, the - longer this will take. + Get/push data for all configured integrations and update + progress if applicable. The more integrations you have + enabled, the longer this will take. - diff --git a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx index 85a4fdd1be..473266fb2f 100644 --- a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx +++ b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx @@ -303,7 +303,7 @@ export default function Page() { {( [ "displayNsfw", - "disableYankIntegrations", + "disableIntegrations", "disableNavigationAnimation", "disableVideos", "disableReviews", @@ -320,8 +320,8 @@ export default function Page() { () => "Whether NSFW will be displayed", ) .with( - "disableYankIntegrations", - () => "Disable yank integrations", + "disableIntegrations", + () => "Disable all integrations", ) .with( "disableNavigationAnimation", diff --git a/docs/content/integrations.md b/docs/content/integrations.md index 04df0cf932..27d80fe7c9 100644 --- a/docs/content/integrations.md +++ b/docs/content/integrations.md @@ -5,6 +5,7 @@ types: - _Yank_: Progress data is downloaded from an externally running server at a periodic interval. +- _Push_: Ryot sends data to an external service. - _Sink_: An external client publishes progress updates to the Ryot server. ## Yank integrations @@ -14,7 +15,7 @@ to your profile. To do so, go to the "Settings" tab and add a new integration un "Integrations" tab. You can configure the interval at which the data is fetched from the external using the -`integration.pull_every_minutes` configuration key. Defaults to `5` (minutes). +`integration.sync_every_minutes` configuration key. Defaults to `5` (minutes). ### Audiobookshelf @@ -31,6 +32,24 @@ have an Audible ID or ITunes ID or ISBN. 2. Go to your Ryot user settings and add the correct details as described in the [yank](#yank-integrations) section. +## Push integrations + +Follow the same instructions as the [yank](#yank-integrations) integrations to add. + +### Radarr + +Automatically add movies in the selected collections to Radarr. + +1. Obtain your Radarr API key by going to the Radarr general settings page. +2. Fill the inputs in the integration settings page with the correct details. + +### Sonarr + +Automatically add shows in the selected collections to Sonarr. + +1. Obtain your Sonarr API key by going to the Sonarr general settings page. +2. Fill the inputs in the integration settings page with the correct details. + ## Sink integrations All webhook URLs follow this format: diff --git a/docs/includes/backend-config-schema.yaml b/docs/includes/backend-config-schema.yaml index 2b6201ba25..d8ce4a928a 100644 --- a/docs/includes/backend-config-schema.yaml +++ b/docs/includes/backend-config-schema.yaml @@ -112,10 +112,9 @@ frontend: # Settings related to external integrations. integration: - # Sync data from [yank](/docs/guides/integrations.md) based integrations - # every `n` minutes. - # @envvar INTEGRATION_PULL_EVERY_MINUTES - pull_every_minutes: 5 + # Sync data from push and yank based integrations every `n` minutes. + # @envvar INTEGRATION_SYNC_EVERY_MINUTES + sync_every_minutes: 5 # Settings related to media. media: diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index 56fa846c72..390e95f898 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -320,10 +320,9 @@ pub struct FrontendConfig { #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case", env_prefix = "INTEGRATION_")] pub struct IntegrationConfig { - /// Sync data from [yank](/docs/guides/integrations.md) based integrations - /// every `n` minutes. + /// Sync data from push and yank based integrations every `n` minutes. #[setting(default = 5)] - pub pull_every_minutes: i32, + pub sync_every_minutes: i32, } #[derive(Debug, Serialize, Deserialize, Clone, Config)] diff --git a/libs/database/src/definitions.rs b/libs/database/src/definitions.rs index d04e7094a9..d05327c9b9 100644 --- a/libs/database/src/definitions.rs +++ b/libs/database/src/definitions.rs @@ -390,7 +390,7 @@ pub enum MetadataToMetadataRelation { pub enum UserToMediaReason { // There is at-least one element in the seen history Seen, - // User has watched this media completely (most applies to shows, podcasts etc.) + // User has watched this media completely (mostly applies to shows, podcasts etc.) Finished, Reviewed, Collection, @@ -423,6 +423,7 @@ pub enum UserToMediaReason { pub enum IntegrationLot { Yank, Sink, + Push, } #[derive( @@ -445,12 +446,14 @@ pub enum IntegrationLot { rename_all = "snake_case" )] #[serde(rename_all = "snake_case")] -pub enum IntegrationSource { +pub enum IntegrationProvider { Audiobookshelf, Jellyfin, Emby, Plex, Kodi, + Radarr, + Sonarr, } #[derive( diff --git a/libs/database/src/migrations/m20230410_create_metadata.rs b/libs/database/src/migrations/m20230410_create_metadata.rs index 47fb297843..90f55a9406 100644 --- a/libs/database/src/migrations/m20230410_create_metadata.rs +++ b/libs/database/src/migrations/m20230410_create_metadata.rs @@ -53,6 +53,7 @@ pub enum Metadata { VisualNovelSpecifics, WatchProviders, StateChanges, + ExternalIdentifiers, } #[async_trait::async_trait] @@ -101,6 +102,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Metadata::VisualNovelSpecifics).json_binary()) .col(ColumnDef::new(Metadata::WatchProviders).json_binary()) .col(ColumnDef::new(Metadata::StateChanges).json_binary()) + .col(ColumnDef::new(Metadata::ExternalIdentifiers).json_binary()) .to_owned(), ) .await?; diff --git a/libs/database/src/migrations/m20231016_create_collection_to_entity.rs b/libs/database/src/migrations/m20231016_create_collection_to_entity.rs index 495d3adf83..15b552835c 100644 --- a/libs/database/src/migrations/m20231016_create_collection_to_entity.rs +++ b/libs/database/src/migrations/m20231016_create_collection_to_entity.rs @@ -59,6 +59,7 @@ pub enum CollectionToEntity { CreatedOn, LastUpdatedOn, Information, + SystemInformation, EntityId, EntityLot, // the entities that can be added to a collection @@ -119,6 +120,12 @@ impl MigrationTrait for Migration { .not_null() .extra(ENTITY_LOT_SQL), ) + .col( + ColumnDef::new(CollectionToEntity::SystemInformation) + .json_binary() + .not_null() + .default("{}"), + ) .foreign_key( ForeignKey::create() .name("collection_to_entity-fk1") diff --git a/libs/database/src/migrations/m20240607_create_integration.rs b/libs/database/src/migrations/m20240607_create_integration.rs index 368026d914..688a94b0fd 100644 --- a/libs/database/src/migrations/m20240607_create_integration.rs +++ b/libs/database/src/migrations/m20240607_create_integration.rs @@ -10,10 +10,10 @@ pub enum Integration { Table, Id, Lot, - Source, + Provider, CreatedOn, LastTriggeredOn, - SourceSpecifics, + ProviderSpecifics, UserId, MinimumProgress, MaximumProgress, @@ -34,7 +34,7 @@ impl MigrationTrait for Migration { .primary_key(), ) .col(ColumnDef::new(Integration::Lot).text().not_null()) - .col(ColumnDef::new(Integration::Source).text().not_null()) + .col(ColumnDef::new(Integration::Provider).text().not_null()) .col( ColumnDef::new(Integration::CreatedOn) .timestamp_with_time_zone() @@ -42,18 +42,10 @@ impl MigrationTrait for Migration { .default(Expr::current_timestamp()), ) .col(ColumnDef::new(Integration::LastTriggeredOn).timestamp_with_time_zone()) - .col(ColumnDef::new(Integration::SourceSpecifics).json_binary()) + .col(ColumnDef::new(Integration::ProviderSpecifics).json_binary()) .col(ColumnDef::new(Integration::UserId).text().not_null()) - .col( - ColumnDef::new(Integration::MinimumProgress) - .decimal() - .not_null(), - ) - .col( - ColumnDef::new(Integration::MaximumProgress) - .decimal() - .not_null(), - ) + .col(ColumnDef::new(Integration::MinimumProgress).decimal()) + .col(ColumnDef::new(Integration::MaximumProgress).decimal()) .col(ColumnDef::new(Integration::IsDisabled).boolean()) .foreign_key( ForeignKey::create() diff --git a/libs/database/src/migrations/m20240730_changes_for_push_integrations.rs b/libs/database/src/migrations/m20240730_changes_for_push_integrations.rs new file mode 100644 index 0000000000..79498b3d55 --- /dev/null +++ b/libs/database/src/migrations/m20240730_changes_for_push_integrations.rs @@ -0,0 +1,63 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + db.execute_unprepared( + r#" +UPDATE "user" SET preferences = JSONB_SET(preferences, '{general,disable_integrations}', +preferences->'general'->'disable_yank_integrations', true); + "#, + ) + .await?; + db.execute_unprepared( + r#" +ALTER TABLE "integration" ALTER COLUMN "minimum_progress" DROP NOT NULL; +ALTER TABLE "integration" ALTER COLUMN "maximum_progress" DROP NOT NULL; + "#, + ) + .await?; + if manager.has_column("integration", "source").await? { + db.execute_unprepared( + r#"ALTER TABLE "integration" RENAME COLUMN "source" TO "provider""#, + ) + .await?; + } + if manager + .has_column("integration", "source_specifics") + .await? + { + db.execute_unprepared( + r#"ALTER TABLE "integration" RENAME COLUMN "source_specifics" TO "provider_specifics""#, + ) + .await?; + } + db.execute_unprepared( + r#" +ALTER TABLE "metadata" ADD COLUMN IF NOT EXISTS "external_identifiers" jsonb; + "#, + ) + .await?; + db.execute_unprepared( + r#" +ALTER TABLE "collection_to_entity" ADD COLUMN IF NOT EXISTS "system_information" jsonb not null default '{}'; + "#, + ) + .await?; + db.execute_unprepared( + r#" +UPDATE "user_to_entity" SET "needs_to_be_updated" = TRUE WHERE 'finished' = ANY("media_reason"); + "#, + ) + .await?; + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} diff --git a/libs/database/src/migrations/mod.rs b/libs/database/src/migrations/mod.rs index 3e0ce40f3c..0f3d084544 100644 --- a/libs/database/src/migrations/mod.rs +++ b/libs/database/src/migrations/mod.rs @@ -42,6 +42,7 @@ mod m20240722_remove_columns_from_user_table; mod m20240723_remove_integration_columns_from_user_table; mod m20240724_add_new_columns_to_collection_to_entity; mod m20240724_zzz_new_generated_collection_to_entity_columns; +mod m20240730_changes_for_push_integrations; pub use m20230410_create_metadata::Metadata as AliasedMetadata; pub use m20230413_create_person::Person as AliasedPerson; @@ -105,6 +106,7 @@ impl MigratorTrait for Migrator { Box::new(m20240723_remove_integration_columns_from_user_table::Migration), Box::new(m20240724_add_new_columns_to_collection_to_entity::Migration), Box::new(m20240724_zzz_new_generated_collection_to_entity_columns::Migration), + Box::new(m20240730_changes_for_push_integrations::Migration), ] } } diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts index cd0628c66b..f88df859d9 100644 --- a/libs/generated/src/graphql/backend/gql.ts +++ b/libs/generated/src/graphql/backend/gql.ts @@ -36,10 +36,10 @@ const documents = { "query UserMetadataDetails($metadataId: String!) {\n userMetadataDetails(metadataId: $metadataId) {\n mediaReason\n hasInteracted\n collections {\n ...CollectionPart\n }\n inProgress {\n ...SeenPart\n }\n history {\n ...SeenPart\n }\n averageRating\n unitsConsumed\n reviews {\n ...ReviewItemPart\n }\n seenByAllCount\n seenByUserCount\n nextEntry {\n season\n volume\n episode\n chapter\n }\n showProgress {\n timesSeen\n seasonNumber\n episodes {\n episodeNumber\n timesSeen\n }\n }\n podcastProgress {\n episodeNumber\n timesSeen\n }\n }\n}": types.UserMetadataDetailsDocument, "query UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}": types.UserMetadataGroupDetailsDocument, "query UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}": types.UserPersonDetailsDocument, - "query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableWatchProviders\n disableYankIntegrations\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}": types.UserPreferencesDocument, + "query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}": types.UserPreferencesDocument, "query UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n startTime\n endTime\n summary {\n ...WorkoutSummaryPart\n }\n }\n }\n}": types.UserWorkoutsListDocument, "query WorkoutDetails($workoutId: String!) {\n workoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n startTime\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}": types.WorkoutDetailsDocument, - "query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n source\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}": types.GetOidcRedirectUrlDocument, + "query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n provider\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}": types.GetOidcRedirectUrlDocument, "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n episode\n season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n chapter\n volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n date\n metadataId\n metadataLot\n episodeName\n metadataTitle\n metadataImage\n calendarEventId\n showExtraInformation {\n ...SeenShowExtraInformationPart\n }\n podcastExtraInformation {\n ...SeenPodcastExtraInformationPart\n }\n}\n\nfragment SeenPart on Seen {\n id\n progress\n providerWatchedOn\n state\n startedOn\n finishedOn\n lastUpdatedOn\n numTimesUpdated\n showExtraInformation {\n ...SeenShowExtraInformationPart\n }\n podcastExtraInformation {\n ...SeenPodcastExtraInformationPart\n }\n animeExtraInformation {\n ...SeenAnimeExtraInformationPart\n }\n mangaExtraInformation {\n ...SeenMangaExtraInformationPart\n }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n identifier\n title\n image\n publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n personalBestsAchieved\n weight\n reps\n distance\n duration\n restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n images\n videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n duration\n distance\n reps\n weight\n oneRm\n pace\n volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n total {\n ...WorkoutOrExerciseTotalsPart\n }\n exercises {\n numSets\n id\n lot\n bestSet {\n statistic {\n ...WorkoutSetStatisticPart\n }\n lot\n personalBests\n }\n }\n}\n\nfragment CollectionPart on Collection {\n id\n name\n userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n id\n rating\n textOriginal\n textRendered\n isSpoiler\n visibility\n postedOn\n postedBy {\n id\n name\n }\n comments {\n id\n text\n createdOn\n user {\n id\n name\n }\n likedBy\n }\n showExtraInformation {\n ...SeenShowExtraInformationPart\n }\n podcastExtraInformation {\n ...SeenPodcastExtraInformationPart\n }\n animeExtraInformation {\n ...SeenAnimeExtraInformationPart\n }\n mangaExtraInformation {\n ...SeenMangaExtraInformationPart\n }\n}\n\nfragment WorkoutInformationPart on WorkoutInformation {\n comment\n assets {\n ...EntityAssetsPart\n }\n exercises {\n name\n lot\n notes\n restTime\n total {\n ...WorkoutOrExerciseTotalsPart\n }\n supersetWith\n assets {\n ...EntityAssetsPart\n }\n sets {\n statistic {\n ...WorkoutSetStatisticPart\n }\n note\n lot\n personalBests\n confirmedAt\n }\n }\n}": types.SeenPodcastExtraInformationPartFragmentDoc, }; @@ -152,7 +152,7 @@ export function graphql(source: "query UserPersonDetails($personId: String!) {\n /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableWatchProviders\n disableYankIntegrations\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}"): (typeof documents)["query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableWatchProviders\n disableYankIntegrations\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}"]; +export function graphql(source: "query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}"): (typeof documents)["query UserPreferences {\n userPreferences {\n general {\n reviewScale\n displayNsfw\n disableVideos\n persistQueries\n watchProviders\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n section\n hidden\n numElements\n }\n }\n fitness {\n measurements {\n custom {\n name\n dataType\n }\n inbuilt {\n weight\n bodyMassIndex\n totalBodyWater\n muscle\n leanBodyMass\n bodyFat\n boneMass\n visceralFat\n waistCircumference\n waistToHeightRatio\n hipCircumference\n waistToHipRatio\n chestCircumference\n thighCircumference\n bicepsCircumference\n neckCircumference\n bodyFatCaliper\n chestSkinfold\n abdominalSkinfold\n thighSkinfold\n basalMetabolicRate\n totalDailyEnergyExpenditure\n calories\n }\n }\n exercises {\n saveHistory\n unitSystem\n }\n }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n measurements\n }\n media {\n enabled\n anime\n audioBook\n book\n manga\n movie\n podcast\n show\n videoGame\n visualNovel\n people\n groups\n genres\n }\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -164,7 +164,7 @@ export function graphql(source: "query WorkoutDetails($workoutId: String!) {\n /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n source\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n source\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}"]; +export function graphql(source: "query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n provider\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n getOidcToken(code: $code) {\n subject\n email\n }\n}\n\nquery GetPresignedS3Url($key: String!) {\n getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n providersLanguageInformation {\n supported\n default\n source\n }\n}\n\nquery UserExports {\n userExports {\n startedAt\n endedAt\n url\n exported\n }\n}\n\nquery UserCollectionsList($name: String) {\n userCollectionsList(name: $name) {\n id\n name\n count\n isDefault\n description\n creator {\n id\n name\n }\n collaborators {\n id\n name\n }\n informationTemplate {\n lot\n name\n required\n description\n defaultValue\n }\n }\n}\n\nquery UserIntegrations {\n userIntegrations {\n id\n lot\n provider\n createdOn\n isDisabled\n maximumProgress\n minimumProgress\n lastTriggeredOn\n }\n}\n\nquery UserNotificationPlatforms {\n userNotificationPlatforms {\n id\n lot\n createdOn\n isDisabled\n description\n }\n}\n\nquery UsersList($query: String) {\n usersList(query: $query) {\n id\n lot\n name\n isDisabled\n }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n userUpcomingCalendarEvents(input: $input) {\n ...CalendarEventPart\n }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n userCalendarEvents(input: $input) {\n date\n events {\n ...CalendarEventPart\n }\n }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n metadataPartialDetails(metadataId: $metadataId) {\n id\n lot\n title\n image\n publishYear\n }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n metadataGroupsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n peopleList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts index 4ef237c8df..729dee4c26 100644 --- a/libs/generated/src/graphql/backend/graphql.ts +++ b/libs/generated/src/graphql/backend/graphql.ts @@ -81,9 +81,9 @@ export enum BackgroundJob { EvaluateWorkouts = 'EVALUATE_WORKOUTS', PerformBackgroundTasks = 'PERFORM_BACKGROUND_TASKS', RecalculateCalendarEvents = 'RECALCULATE_CALENDAR_EVENTS', + SyncIntegrationsData = 'SYNC_INTEGRATIONS_DATA', UpdateAllExercises = 'UPDATE_ALL_EXERCISES', - UpdateAllMetadata = 'UPDATE_ALL_METADATA', - YankIntegrationsData = 'YANK_INTEGRATIONS_DATA' + UpdateAllMetadata = 'UPDATE_ALL_METADATA' } export type BookSpecifics = { @@ -246,10 +246,10 @@ export type CreateReviewCommentInput = { }; export type CreateUserIntegrationInput = { - maximumProgress: Scalars['Decimal']['input']; - minimumProgress: Scalars['Decimal']['input']; - source: IntegrationSource; - sourceSpecifics?: InputMaybe; + maximumProgress?: InputMaybe; + minimumProgress?: InputMaybe; + provider: IntegrationProvider; + providerSpecifics?: InputMaybe; }; export type CreateUserNotificationPlatformInput = { @@ -700,35 +700,41 @@ export type Integration = { isDisabled?: Maybe; lastTriggeredOn?: Maybe; lot: IntegrationLot; - maximumProgress: Scalars['Decimal']['output']; - minimumProgress: Scalars['Decimal']['output']; - source: IntegrationSource; - sourceSpecifics?: Maybe; + maximumProgress?: Maybe; + minimumProgress?: Maybe; + provider: IntegrationProvider; }; export enum IntegrationLot { + Push = 'PUSH', Sink = 'SINK', Yank = 'YANK' } -export enum IntegrationSource { +export enum IntegrationProvider { Audiobookshelf = 'AUDIOBOOKSHELF', Emby = 'EMBY', Jellyfin = 'JELLYFIN', Kodi = 'KODI', - Plex = 'PLEX' + Plex = 'PLEX', + Radarr = 'RADARR', + Sonarr = 'SONARR' } -export type IntegrationSourceSpecifics = { - audiobookshelfBaseUrl?: Maybe; - audiobookshelfToken?: Maybe; - plexUsername?: Maybe; -}; - export type IntegrationSourceSpecificsInput = { audiobookshelfBaseUrl?: InputMaybe; audiobookshelfToken?: InputMaybe; plexUsername?: InputMaybe; + radarrApiKey?: InputMaybe; + radarrBaseUrl?: InputMaybe; + radarrProfileId?: InputMaybe; + radarrRootFolderPath?: InputMaybe; + radarrSyncCollectionIds?: InputMaybe>; + sonarrApiKey?: InputMaybe; + sonarrBaseUrl?: InputMaybe; + sonarrProfileId?: InputMaybe; + sonarrRootFolderPath?: InputMaybe; + sonarrSyncCollectionIds?: InputMaybe>; }; export type LoginError = { @@ -2012,11 +2018,11 @@ export type UserGeneralDashboardElement = { export type UserGeneralPreferences = { dashboard: Array; + disableIntegrations: Scalars['Boolean']['output']; disableNavigationAnimation: Scalars['Boolean']['output']; disableReviews: Scalars['Boolean']['output']; disableVideos: Scalars['Boolean']['output']; disableWatchProviders: Scalars['Boolean']['output']; - disableYankIntegrations: Scalars['Boolean']['output']; displayNsfw: Scalars['Boolean']['output']; persistQueries: Scalars['Boolean']['output']; reviewScale: UserReviewScale; @@ -2893,7 +2899,7 @@ export type UserPersonDetailsQuery = { userPersonDetails: { collections: Array<{ export type UserPreferencesQueryVariables = Exact<{ [key: string]: never; }>; -export type UserPreferencesQuery = { userPreferences: { general: { reviewScale: UserReviewScale, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, watchProviders: Array, disableReviews: boolean, disableWatchProviders: boolean, disableYankIntegrations: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ section: DashboardElementLot, hidden: boolean, numElements?: number | null }> }, fitness: { measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } }, exercises: { saveHistory: number, unitSystem: UserUnitSystem } }, notifications: { toSend: Array, enabled: boolean }, featuresEnabled: { others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, measurements: boolean }, media: { enabled: boolean, anime: boolean, audioBook: boolean, book: boolean, manga: boolean, movie: boolean, podcast: boolean, show: boolean, videoGame: boolean, visualNovel: boolean, people: boolean, groups: boolean, genres: boolean } } } }; +export type UserPreferencesQuery = { userPreferences: { general: { reviewScale: UserReviewScale, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, watchProviders: Array, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ section: DashboardElementLot, hidden: boolean, numElements?: number | null }> }, fitness: { measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } }, exercises: { saveHistory: number, unitSystem: UserUnitSystem } }, notifications: { toSend: Array, enabled: boolean }, featuresEnabled: { others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, measurements: boolean }, media: { enabled: boolean, anime: boolean, audioBook: boolean, book: boolean, manga: boolean, movie: boolean, podcast: boolean, show: boolean, videoGame: boolean, visualNovel: boolean, people: boolean, groups: boolean, genres: boolean } } } }; export type UserWorkoutsListQueryVariables = Exact<{ input: SearchInput; @@ -2955,7 +2961,7 @@ export type UserCollectionsListQuery = { userCollectionsList: Array<{ id: string export type UserIntegrationsQueryVariables = Exact<{ [key: string]: never; }>; -export type UserIntegrationsQuery = { userIntegrations: Array<{ id: string, lot: IntegrationLot, source: IntegrationSource, createdOn: string, isDisabled?: boolean | null, maximumProgress: string, minimumProgress: string, lastTriggeredOn?: string | null }> }; +export type UserIntegrationsQuery = { userIntegrations: Array<{ id: string, lot: IntegrationLot, provider: IntegrationProvider, createdOn: string, isDisabled?: boolean | null, maximumProgress?: string | null, minimumProgress?: string | null, lastTriggeredOn?: string | null }> }; export type UserNotificationPlatformsQueryVariables = Exact<{ [key: string]: never; }>; @@ -3110,7 +3116,7 @@ export const UserMeasurementsListDocument = {"kind":"Document","definitions":[{" export const UserMetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mediaReason"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"unitsConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextEntry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Seen"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode; export const UserMetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataGroupDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataGroupDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataGroupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}}]} as unknown as DocumentNode; export const UserPersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserPersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPersonDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode; -export const UserPreferencesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableYankIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveHistory"}},{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const UserPreferencesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveHistory"}},{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const UserWorkoutsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserWorkoutsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userWorkoutsList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"startTime"}},{"kind":"Field","name":{"kind":"Name","value":"endTime"}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSummaryPart"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutOrExerciseTotals"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"restTime"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSummaryPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSummary"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"numSets"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}}]}}]}}]}}]} as unknown as DocumentNode; export const WorkoutDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkoutDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workoutId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workoutId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workoutId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"endTime"}},{"kind":"Field","name":{"kind":"Name","value":"startTime"}},{"kind":"Field","name":{"kind":"Name","value":"repeatedFrom"}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSummaryPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"information"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutInformationPart"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutOrExerciseTotals"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"restTime"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EntityAssetsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EntityAssets"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSummaryPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSummary"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"numSets"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EntityAssetsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"restTime"}},{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"supersetWith"}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EntityAssetsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"note"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetOidcRedirectUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOidcRedirectUrl"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getOidcRedirectUrl"}}]}}]} as unknown as DocumentNode; @@ -3120,7 +3126,7 @@ export const GetPresignedS3UrlDocument = {"kind":"Document","definitions":[{"kin export const ProvidersLanguageInformationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProvidersLanguageInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"providersLanguageInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"supported"}},{"kind":"Field","name":{"kind":"Name","value":"default"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}}]} as unknown as DocumentNode; export const UserExportsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserExports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userExports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"exported"}}]}}]}}]} as unknown as DocumentNode; export const UserCollectionsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserCollectionsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userCollectionsList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"isDefault"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"creator"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"informationTemplate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"required"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}}]}}]}}]}}]} as unknown as DocumentNode; -export const UserIntegrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"maximumProgress"}},{"kind":"Field","name":{"kind":"Name","value":"minimumProgress"}},{"kind":"Field","name":{"kind":"Name","value":"lastTriggeredOn"}}]}}]}}]} as unknown as DocumentNode; +export const UserIntegrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"maximumProgress"}},{"kind":"Field","name":{"kind":"Name","value":"minimumProgress"}},{"kind":"Field","name":{"kind":"Name","value":"lastTriggeredOn"}}]}}]}}]} as unknown as DocumentNode; export const UserNotificationPlatformsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserNotificationPlatforms"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userNotificationPlatforms"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]} as unknown as DocumentNode; export const UsersListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UsersList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"usersList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}}]}}]}}]} as unknown as DocumentNode; export const UserUpcomingCalendarEventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserUpcomingCalendarEvents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserUpcomingCalendarEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userUpcomingCalendarEvents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CalendarEventPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"episodeName"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode; diff --git a/libs/graphql/src/backend/queries/UserPreferences.gql b/libs/graphql/src/backend/queries/UserPreferences.gql index 847638dbdb..1a4772e47a 100644 --- a/libs/graphql/src/backend/queries/UserPreferences.gql +++ b/libs/graphql/src/backend/queries/UserPreferences.gql @@ -7,8 +7,8 @@ query UserPreferences { persistQueries watchProviders disableReviews + disableIntegrations disableWatchProviders - disableYankIntegrations disableNavigationAnimation dashboard { section diff --git a/libs/graphql/src/backend/queries/combined.gql b/libs/graphql/src/backend/queries/combined.gql index 7c83080ca9..9c150c7ea8 100644 --- a/libs/graphql/src/backend/queries/combined.gql +++ b/libs/graphql/src/backend/queries/combined.gql @@ -63,7 +63,7 @@ query UserIntegrations { userIntegrations { id lot - source + provider createdOn isDisabled maximumProgress