From 0118b8f50fb0f1f79e354f6dc67fb99a39b33685 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 15 Dec 2024 17:07:42 +0530 Subject: [PATCH] Add music tracking using YoutubeMusic (#1133) * build(backend): add rustypipe deps * feat(backend): start adding new source and lot * feat(migrations): start adding new columns for youtube music * feat(backend): add music fields * fix(services/integration): return result correctly * feat(backend): start working on new provider * chore(frontend): start adding for youtube music * feat(backend): allow changing music preferences * fix(utils/common): add lot mapping for music * chore(frontend): add new lot * feat(providers): get youtube music search working * feat(providers): allow getting music details * feat(providers): get recommendations for music * chore(services/miscellaneous): get correct service for metadata details * feat(backend): save music specifics * feat(frontend): display music details * feat(backend): store youtube music album details * feat(frontend): display overview of metadata group * fix(providers): get correct album id * feat: allow searching from youtube music albums * refactor(backend): change name of struct * feat(backend): store metadata group extra information * feat(backend): remove stuff for metadata group * feat(backend): store source url in the metadata itself * feat(providers): select biggest images * feat(backend): add source url to metadata group table * chore(frontend): adapt to new gql schema * fix(providers): get correct youtube music album id * feat(backend): start storing person source url in database * chore(frontend): adapt to new gql schema * chore(frontend): add ytmusic as people search source * feat(providers): support artist search for youtube music * feat(providers): support artist details * chore(backend): apply clippy lints * feat(providers): get all related tracks to a yt-music artist * refactor(backend): make the images field nullable for metadata group * feat(backend): return more music specifics * feat(frontend): display more music specifics * refactor(models/media): add skip serializing none to models * chore(backend): remove useless column in table * chore(backend): changes for big merge * refactor(backend): extract cache service to variable * chore(models/dependent): change the order of declarations * refactor(backend): use types wherever possible * refactor(backend): move server key validation into core details * ci: Run CI * refactor(backend): change return signature of function * fix(backend): parse responses correctly * ci: double quote stuff * ci: Run CI * ci: install needed deps * ci: Run CI * ci: fix install correct dep * ci: Run CI * ci: remove another double quote * ci: Run CI * ci: bring back old command * ci: Run CI --- Cargo.lock | 202 +++++++- Cargo.toml | 1 + apps/backend/src/job.rs | 8 - apps/backend/src/main.rs | 3 - apps/frontend/app/lib/generals.ts | 9 + .../_dashboard.media.groups.$action.tsx | 1 + ...dashboard.media.groups.item.$id._index.tsx | 23 +- .../_dashboard.media.item.$id._index.tsx | 18 + .../_dashboard.media.people.$action.tsx | 1 + ...dashboard.media.people.item.$id._index.tsx | 2 +- .../public/provider-logos/youtube-music.png | Bin 0 -> 96462 bytes ci/cross-pre-build.sh | 3 + crates/background/src/lib.rs | 1 - crates/config/src/lib.rs | 31 +- crates/enums/src/lib.rs | 28 +- crates/migrations/src/lib.rs | 2 + .../src/m20230410_create_metadata.rs | 6 +- .../migrations/src/m20230413_create_person.rs | 2 + .../src/m20230501_create_metadata_group.rs | 8 +- .../m20240827_create_daily_user_activity.rs | 4 + .../src/m20241220_changes_for_issue_49.rs | 49 ++ crates/models/common/src/lib.rs | 1 - .../database/src/daily_user_activity.rs | 2 + crates/models/database/src/metadata.rs | 9 +- crates/models/database/src/metadata_group.rs | 4 +- crates/models/database/src/person.rs | 1 + crates/models/dependent/src/lib.rs | 15 +- crates/models/media/src/lib.rs | 180 ++++--- crates/models/user/src/lib.rs | 2 + crates/providers/Cargo.toml | 2 +- crates/providers/src/anilist/mod.rs | 27 +- crates/providers/src/audible.rs | 59 +-- crates/providers/src/google_books.rs | 22 +- crates/providers/src/igdb.rs | 62 ++- crates/providers/src/itunes.rs | 32 +- crates/providers/src/lib.rs | 1 + crates/providers/src/listennotes.rs | 29 +- crates/providers/src/mal.rs | 22 +- crates/providers/src/manga_updates.rs | 33 +- crates/providers/src/openlibrary.rs | 63 ++- crates/providers/src/tmdb.rs | 118 ++--- crates/providers/src/vndb.rs | 37 +- crates/providers/src/youtube_music.rs | 279 +++++++++++ crates/services/cache/src/lib.rs | 14 +- crates/services/exporter/src/lib.rs | 2 +- crates/services/importer/src/hevy.rs | 2 +- crates/services/importer/src/lib.rs | 2 +- .../integration/src/yank/audiobookshelf.rs | 2 +- crates/services/miscellaneous/Cargo.toml | 4 - crates/services/miscellaneous/src/lib.rs | 447 +++--------------- crates/services/statistics/src/lib.rs | 35 +- crates/services/supporting/Cargo.toml | 18 +- crates/services/supporting/src/lib.rs | 198 +++++++- crates/services/user/src/lib.rs | 4 + crates/traits/src/lib.rs | 21 +- crates/utils/common/src/lib.rs | 8 +- crates/utils/database/src/lib.rs | 33 +- crates/utils/dependent/src/lib.rs | 37 +- docs/includes/backend-config-schema.yaml | 3 + docs/includes/export-schema.ts | 20 +- libs/generated/src/graphql/backend/gql.ts | 16 +- libs/generated/src/graphql/backend/graphql.ts | 44 +- .../src/graphql/backend/types.generated.ts | 30 +- .../src/backend/queries/MetadataDetails.gql | 5 + .../src/backend/queries/PersonDetails.gql | 2 +- .../src/backend/queries/UserDetails.gql | 13 +- libs/graphql/src/backend/queries/combined.gql | 11 +- 67 files changed, 1432 insertions(+), 941 deletions(-) create mode 100644 apps/frontend/public/provider-logos/youtube-music.png create mode 100644 crates/migrations/src/m20241220_changes_for_issue_49.rs create mode 100644 crates/providers/src/youtube_music.rs diff --git a/Cargo.lock b/Cargo.lock index 86649e2615..68775f6ef9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.20" @@ -325,6 +340,20 @@ dependencies = [ "nom", ] +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-graphql" version = "7.0.11" @@ -982,6 +1011,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -1070,6 +1114,27 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1423,6 +1488,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2257,6 +2331,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "fast_chemail" version = "0.9.6" @@ -3580,6 +3665,16 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libquickjs-dtp-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b869d7bd9cfc01d9ee1b29a7556990897268da33765d750be3f51379e850fb95" +dependencies = [ + "cc", + "copy_dir", +] + [[package]] name = "libredox" version = "0.1.3" @@ -3829,7 +3924,6 @@ dependencies = [ "dependent-models", "dependent-utils", "enums", - "env-utils", "futures", "itertools 0.13.0", "markdown", @@ -3844,13 +3938,10 @@ dependencies = [ "sea-query", "serde", "serde_json", - "serde_with 3.11.0", - "slug", "supporting-service", "tokio", "tracing", "traits", - "unkey", "user-models", "uuid", ] @@ -4594,7 +4685,6 @@ dependencies = [ "enums", "graphql_client", "hashbag", - "isolang", "itertools 0.13.0", "media-models", "paginate", @@ -4603,6 +4693,7 @@ dependencies = [ "rust_decimal", "rust_decimal_macros", "rust_iso3166", + "rustypipe", "scraper", "sea-orm", "serde", @@ -4643,6 +4734,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-js-dtp" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d07297b897136123d4bb9c63249b587aafbd1a74dcc7f86848c41e506655ab" +dependencies = [ + "libquickjs-dtp-sys", + "once_cell", +] + [[package]] name = "quote" version = "1.0.37" @@ -4893,6 +4994,7 @@ version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ + "async-compression", "base64 0.22.1", "bytes", "encoding_rs", @@ -4934,6 +5036,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "ress" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d77111a94ef694bb2fe9867a93e21007e2151943c9a48b2ed72734bde41875a" +dependencies = [ + "log", + "unicode-xid", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -5189,12 +5301,48 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +[[package]] +name = "rustypipe" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dba9b1e6f02758c5dff06157082cdd55e8c6c96ef9bac3bea0b191cc864da7" +dependencies = [ + "base64 0.22.1", + "fancy-regex", + "once_cell", + "phf", + "quick-js-dtp", + "rand 0.8.5", + "regex", + "reqwest 0.12.9", + "ress", + "serde", + "serde_json", + "serde_plain", + "serde_with 3.11.0", + "thiserror 2.0.3", + "time", + "tokio", + "tracing", + "url", + "urlencoding", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.27" @@ -6255,13 +6403,26 @@ dependencies = [ "async-graphql", "background", "cache-service", + "chrono", "chrono-tz", "common-models", + "common-utils", "config", + "database-models", "dependent-models", + "enums", + "env-utils", "file-storage-service", + "isolang", + "itertools 0.13.0", "openidconnect", + "rustypipe", "sea-orm", + "serde", + "serde_json", + "serde_with 3.11.0", + "tracing", + "unkey", ] [[package]] @@ -6890,6 +7051,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -6941,6 +7108,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "user-models" version = "0.1.0" @@ -7064,6 +7237,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -7227,6 +7410,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 60dfb076a5..1d1f086bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ regex = "=1.11.1" rust_decimal = "=1.36.0" rust_decimal_macros = "=1.36.0" rust_iso3166 = "=0.1.13" +rustypipe = "0.7.2" schematic = { version = "=0.17.7", features = [ "config", "env", diff --git a/apps/backend/src/job.rs b/apps/backend/src/job.rs index 528b7dd3c0..2becad99a9 100644 --- a/apps/backend/src/job.rs +++ b/apps/backend/src/job.rs @@ -24,13 +24,8 @@ pub async fn run_background_jobs( pub async fn run_frequent_jobs( _information: ScheduledJob, fitness_service: Data>, - misc_service: Data>, integration_service: Data>, ) -> Result<(), Error> { - misc_service - .perform_server_key_validation() - .await - .trace_ok(); integration_service .yank_integrations_data() .await @@ -153,9 +148,6 @@ pub async fn perform_application_job( .await .is_ok() } - ApplicationJob::PerformServerKeyValidation => { - misc_service.perform_server_key_validation().await.is_ok() - } ApplicationJob::HandleEntityAddedToCollectionEvent(collection_to_entity_id) => { integration_service .handle_entity_added_to_collection_event(collection_to_entity_id) diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs index be68b012ac..2802d2c735 100644 --- a/apps/backend/src/main.rs +++ b/apps/backend/src/main.rs @@ -121,7 +121,6 @@ async fn main() -> Result<()> { join_all( [ - ApplicationJob::PerformServerKeyValidation, ApplicationJob::SyncIntegrationsData, ApplicationJob::UpdateExerciseLibrary, ] @@ -190,7 +189,6 @@ async fn main() -> Result<()> { let miscellaneous_service_1 = app_services.miscellaneous_service.clone(); let miscellaneous_service_2 = app_services.miscellaneous_service.clone(); let miscellaneous_service_3 = app_services.miscellaneous_service.clone(); - let miscellaneous_service_4 = app_services.miscellaneous_service.clone(); let monitor = Monitor::::new() .register_with_count( @@ -218,7 +216,6 @@ async fn main() -> Result<()> { .layer(ApalisTraceLayer::new()) .data(integration_service_1.clone()) .data(fitness_service_2.clone()) - .data(miscellaneous_service_4.clone()) .build_fn(run_frequent_jobs), ) // application jobs diff --git a/apps/frontend/app/lib/generals.ts b/apps/frontend/app/lib/generals.ts index 703de1a364..91904f1dda 100644 --- a/apps/frontend/app/lib/generals.ts +++ b/apps/frontend/app/lib/generals.ts @@ -24,6 +24,7 @@ import { IconDeviceTvOld, IconHeadphones, IconMicrophone, + IconMusic, } from "@tabler/icons-react"; import { QueryClient, queryOptions, skipToken } from "@tanstack/react-query"; import dayjs from "dayjs"; @@ -179,6 +180,7 @@ export const getLot = (lot: unknown) => { .with("books", "book", () => MediaLot.Book) .with("movies", "movie", () => MediaLot.Movie) .with("tv", "show", "shows", () => MediaLot.Show) + .with("music", () => MediaLot.Music) .with( "visual_novel", "visualnovel", @@ -232,6 +234,10 @@ export const getLotGradient = (lot: MediaLot) => from: "green", to: "yellow", })) + .with(MediaLot.Music, () => ({ + from: "orange", + to: "pink", + })) .exhaustive(); /** @@ -269,6 +275,7 @@ export const getVerb = (verb: Verb, lot: MediaLot) => ) .with( MediaLot.AudioBook, + MediaLot.Music, MediaLot.VideoGame, MediaLot.Podcast, () => "play", @@ -308,6 +315,7 @@ export const getMetadataIcon = (lot: MediaLot) => match(lot) .with(MediaLot.Book, () => IconBook) .with(MediaLot.Manga, () => IconBooks) + .with(MediaLot.Music, () => IconMusic) .with(MediaLot.Movie, () => IconDeviceTv) .with(MediaLot.Anime, () => IconDeviceTvOld) .with(MediaLot.VisualNovel, () => IconBook2) @@ -487,6 +495,7 @@ type EntityColor = Record; export const MediaColors: EntityColor = { ANIME: "blue", + MUSIC: "indigo.2", AUDIO_BOOK: "orange", BOOK: "lime", MANGA: "purple", diff --git a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx index d2555d11bc..87fd6e7583 100644 --- a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx @@ -75,6 +75,7 @@ enum Action { const SEARCH_SOURCES_ALLOWED: Partial> = { [MediaSource.Tmdb]: MediaLot.Movie, [MediaSource.Igdb]: MediaLot.VideoGame, + [MediaSource.YoutubeMusic]: MediaLot.Music, }; export const loader = async ({ request, params }: LoaderFunctionArgs) => { diff --git a/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx index 72b5468c85..27c7c1d5c5 100644 --- a/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx +++ b/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx @@ -20,6 +20,7 @@ import { } from "@ryot/generated/graphql/backend/graphql"; import { IconDeviceTv, + IconInfoCircle, IconMessageCircle2, IconUser, } from "@tabler/icons-react"; @@ -86,9 +87,9 @@ export default function Page() { @@ -128,6 +129,12 @@ export default function Page() { Reviews </Tabs.Tab> ) : null} + <Tabs.Tab + value="overview" + leftSection={<IconInfoCircle size={16} />} + > + Overview + </Tabs.Tab> </Tabs.List> <Tabs.Panel value="media"> <MediaScrollArea> @@ -208,6 +215,18 @@ export default function Page() { </MediaScrollArea> </Tabs.Panel> ) : null} + <Tabs.Panel value="overview"> + {loaderData.metadataGroupDetails.details.description ? ( + <div + // biome-ignore lint/security/noDangerouslySetInnerHtml: generated by the backend securely + dangerouslySetInnerHTML={{ + __html: loaderData.metadataGroupDetails.details.description, + }} + /> + ) : ( + <Text>No description</Text> + )} + </Tabs.Panel> </Tabs> </MediaDetailsLayout> </Container> diff --git a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx index 71061f0612..290ebb358d 100644 --- a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx +++ b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx @@ -59,6 +59,7 @@ import { import { changeCase, formatDateToNaiveDate, + formatQuantityWithCompactNotation, getActionIntent, humanizeDuration, isInteger, @@ -404,6 +405,21 @@ export default function Page() { ) .asMilliseconds(), ), + loaderData.metadataDetails.musicSpecifics?.duration && + humanizeDuration( + dayjsLib + .duration( + loaderData.metadataDetails.musicSpecifics.duration, + "second", + ) + .asMilliseconds(), + ), + loaderData.metadataDetails.musicSpecifics?.viewCount && + formatQuantityWithCompactNotation( + loaderData.metadataDetails.musicSpecifics.viewCount, + ), + loaderData.metadataDetails.musicSpecifics?.byVariousArtists && + "Various Artists", ] .filter(Boolean) .join(" • ")} @@ -439,6 +455,7 @@ export default function Page() { .with(MediaSource.Openlibrary, () => "openlibrary.svg") .with(MediaSource.Tmdb, () => "tmdb.svg") .with(MediaSource.Vndb, () => "vndb.ico") + .with(MediaSource.YoutubeMusic, () => "youtube-music.png") .with(MediaSource.Custom, () => undefined) .exhaustive()}`} /> @@ -467,6 +484,7 @@ export default function Page() { MediaSource.Custom, MediaSource.Itunes, MediaSource.Openlibrary, + MediaSource.YoutubeMusic, () => undefined, ) .exhaustive()} diff --git a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx index 46ae4c82fe..ce15266b90 100644 --- a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx @@ -80,6 +80,7 @@ const SEARCH_SOURCES_ALLOWED = [ MediaSource.Audible, MediaSource.MangaUpdates, MediaSource.Igdb, + MediaSource.YoutubeMusic, ] as const; export const loader = async ({ request, params }: LoaderFunctionArgs) => { diff --git a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx index 89e01c5651..76e4df0f30 100644 --- a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx +++ b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx @@ -89,7 +89,7 @@ export default function Page() { images={loaderData.personDetails.details.displayImages} externalLink={{ source: loaderData.personDetails.details.source, - href: loaderData.personDetails.sourceUrl, + href: loaderData.personDetails.details.sourceUrl, }} > <Title id="creator-title"> diff --git a/apps/frontend/public/provider-logos/youtube-music.png b/apps/frontend/public/provider-logos/youtube-music.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d4392573d68f8c4dbd1bb66519c81f5b0f4673 GIT binary patch literal 96462 zcmYIw3p`Z$`~Mk|P0Dw-a!L5@wy{bjiV(JJ*>VkYk=tlWAruWYVy125+vc*Zt(1^T zm>Fg!xrZqmE6TRaC~_&H&V-`lGK~2@XGY(D-`8vV=A6&x^E~h8{yd-Kf~ULlGL6+5 z2!brz`}0p;2=W#D->;Cb7Q<in3fj%#uSG|8y6r>|LHg3UU^V#n|IvQ-azhY?F@msu zMGz_ci1iLZ=<qH|MG)Ie1kpKuqk8`ic(C}Oi}O#29RJ_V+JX%DiTcUCZoAdrsW1M< z&SIa(>2<Ie+56K@pHp2QWO4WJ9zH!C#T?Be6V$$b9O&uu(}#8YAFek4;Z|cwbDM6) z(ec#-)^|fw_Rv=Eq5Y%#VVT`ew+}k6TxIt{ur&53PpNGFlct`nckJmWC%725_8Q@; zM76a0bZ_UjMR4@7-v#Bx<wYf45#)&4N!e450ef4xygIBPtT2{q#VO~NQ)b(uYA45D z29G#)@1D2bbn<myUl%j6X~Rr?1u;X;lnk_@H#W?~yYrp}&CXYe^mJxwgGj>xjbqtF z8Dm5~c`aN%({A$KHe#eNrmL&I?jv(RCtQ9lC}4Wt{In6!K=C+-lVbLZAU4usNBy8= zy<5^}9a-6kc)n}5x=be__j1hh$E^12W;*`U^LlRDOE;a79UPH-cBxB}R{aS}TkV#@ zf05RdGLPP|=nI|CbWY3JS3gPY=x;rqmg>4k-P``L6gwrGdo-2(6TB$o^6~eDY&{c? zj<E-m^whBQ4bu|)>zWh6`PrgF-}XQ1)M9&=^z%FnP?eYPDkW4KUqSI27+fyxVTYF_ z)Hs*Tc6DxJb9l1afULP%^QdPfi9QL;k!~X&N3MIo?7UpoYd#ZCKoII)3ci?Hn^Ja{ z-Y{#=4(BDr7nenCUNag*+(?OXjJT(JJS-!hHMY(7o9WT6ioU4mTf!e!!5VU-3cgfl z`BWUQr27z``82G}_?&EMUtew4u8ouv!&X%X`)|1rbn4{UsGrF63XY_LIX=?Z+@IKK z?D0NG{teHbE%E6#RkM`bFfLHP@$aE6{eu@DxEIygbnp(r37Js}_J)MpXXYc*H`%|c z@9pXwb;}u7*KSvH(<ao39$!^;FL5_Cc2R``TDL3kwmis<sQJv2-#Ry6OjNP@*EgW? z;E&i-RNqN<;5$&__eX$vg!&uTp3Q6%FPoeyi7cO#Nrp@lsr0!Awnesri<`f?amWUL ziJ^dq5*(f`sgI>+pnF8Aopq5u0R>(D>!^EfbPgUk%OeH_%vOJtjcIP%2W;}A&0ry7 zmT_^%Q|9x6Y1rL&<mP|Oho2SI^;UbUZrZU^ScDVM@)wSw(0H{;!(5h?-uPJAB)#2O zmFY2|>i_Dro3@HOx6Ui!{i9CLBYy*T{7;H~xdmk&pZhACZ!*U&KIq{bxcwiYv0Z*y z!6F}%!M|WX*0O{6=KU5aWzJ<mI!2Ry54!dluvXCnvFuIJ=YPz2%i63(ufr`~qTo%t zt7~93>eZpY>-)Moi`gR`gv_vEpP;CFn#W&<5_0v>f`8%Ni%)Q3`R1Djisog}XB>qO zZ1OK|R=d$Lxccn=)c{nlFFZ$vo^98A*LR2R@p;T|#Czn<a^WuNX{-vhEcRPIje6?* z42-m{z@;Q~)ekQ<_V^U^xoF;wY{yX*uG)UP*-N-Y=XAp9!ijufBIGje!>A!#gUP=8 zbdSlt>S|(Hwk=|I){@g^<-2(LBvxdGzhbFC(!8v(>o&b1(PL7Tzj_nhOXz8W6YaWI zah#8CcoTZ1SpRchSyzA_a`&H5H+S{vqoCdC%dizdJWCXU14lVQiwGR??kTEHpx2;D zb<PB8tYaBghOQ8O5}gnYC);}9?5>-64S(@Jh#h*EHRvX{oR9V4^i_wEOxh7rZbbAh zp+KabEERyrgwgk4Tg0qLV8xjX>)AgaR}_iPY9a6bG;TqR0%j*IodW(3NaXv>A<Gfu z#rL#`?m=C7jg{VHUtZTvJ*3jBfNT_g?>`@3qbopz!6K+M1=v=9uS=V#kI8r_Cp`f@ zvg1kpCGKBM3q2h#0V<Rr+;&(Swu%jl?ViY=iRwhF4<ppwH(phrXVH?HgFqDi#tLj6 zr_g0+q|WJ)A`QefHHvJ+cJNF%S{Stt;Pd|*7ra^J#T~DiKhoP!1{)E!ZU==^9sCs@ zP)^~eP#@|}s|x*V-t7{`Xa5I&aPcv|s<lc`5;#&nNl!<sVcDbU5NsYz5X~k@FMW?7 z=JmKj3vcL!*Pv+tpySQzGM<dUDszt<p%X;$Nz(K9TaI)8fgM9$QL@Oh_apn|`l2gO zzyZCPLraVOzMB5q^GFMvRf_)yX}u|!EE(;oJ}Zx9BS_Y}6tqDU&xzB**6jr%%W-{Y z>6wcsMLir<&a6)-Ne!WdI3BzApW`R#pFJHj79q&pEL@{9chMq9J#2cDR31`x-U1P> zuNtHij8>!mfIPKYv61N(_Z+l-s;hHe<PN8Kqr>2ldfNx9Sz|bGoHm>y?jIL-yk&Z3 z+?GX+gr7vHd$L~*vRSvIZSiNT9sL&}tW{gZB6dwh<h%OP^#F&>Z7DV5Fza@*^enK? zw@`p&p&veG+%SKRZAS|CAt2g+sfmtU_tyckUzyKr1CsO<B(1Xo-fFWn=fL{zY9K!n z9l5<P2T!u5H0gKo%}y#d`$pHqX@1rxnKZ>M_fkXpHL{5Y-Ghf%)>@c7j#Im0uhf$l zZp+37CE*CmE!rz1gOFC@VB7J-5(v(^jyHjBY^!)kWN>}QTRAJtgNPtk9>=le5%)Ad z=HxxY{e8&|9LY)*T7=(7-=TL*Il3NT;uZ!G$9QSnUaOAxe}MQ8m%>ZPRdewsx3oz| zesxe;_Id#GH5YwhY@+%gxX^nY0b$W`!hhT*E(58l6Q@ebJ=G8@J3A=hfi0Uc;Td!l z=-G|Cb=LW9VmFSuj}~{*dl5ptQh#6KE%IYmEXP&>Ym}dGZKeD`8woJz8a%~xVj`@W zlV0>gSYz<G1E~2T+#XtMFKWuPiX-oSg$Q?59oOqG%ya);6=9vm9R#cD<fT0MxvVvq z7J`_NoypG2>D)E2NMEruRWKRalQC~i0)jStd<!hBvFXqm1XK-iRH=7U={@MTqT3U9 z91%o+_))k@pmhfse@?<}nmUwHb_5d|dr13yzXnMEIp2ct))<o^#}l^V@l)ukD`AVG zi0RzPsWJd%L;tTRQKX+_O9bC>H{0mC3WEE=s^*OOdhed%E?0=wq=z%2S#~Em$^hL8 zAY|^rLC9QBiy-!Jnm^4a`*gEa5aCLj8b(w`xJud@v=anlz7dDqd{tu?kKu#y(Xij( zU#jk$G<KkGZB*g@Z(!dHT)?EK6zgI`FZ)~7XTJj&_+zA~;xt{fG#d16HNFS`R%3M- z7=RJ62j^+%pS^DGYNsB17Ca#!!hHgGLwHX=JPYkNc9O@FvWkEfmF@M6I52_=E&3JE zTQd_JTeB>|WVpp==BW%5IRT;Wm)&AG!#h*Uwt=|`y>LAe{;^teU9^_nM8pMvY<jyv zB+2ss`lFi-NPRud_KQQbhya6*!BF|^(0`fmQzIQ8qwwhU!ti8UC*aBIJkFC<7bUJJ zvBLIF*G{1L#nA$C6nj3tFde73Q6YxKdf`*(w{DB&6`pX=l~-i;TJ}{f%n3h8;dm>( zXvIaN`|ay&E4y}q5Gb+3WTV1BuUdNH#P<ZG|2JG6`lC}p+<a7vE%^a=8AE@fmqjkg z(j-tQe{Gy;|5l10aVl#3zI?te1azt>W><zcA<(kp8sK~UTgO&4<R)7%$u{(s1=5MT zL4#1h#R<EW=df8FL2#C}GYe#ZSET=`^+Hav^fK&yv03XBc#(f7)<uSXvPk+ie4*ln zrODD?@E36ZE&S!<eb7>~H3jewGhN5eYDi{Gc<!WJhCUZ<0(}rZn%Rr}hOTo;0PSPV z90v2CE>A7HTkVVuq7`R)fB-J`9Oa|MPT3~vi15s(b5w+7V<8rCntYg^0nPw!fAri< z1`&IdSEqOb2iII3SGqIU-(mVd7YjS-j!hbOs;*fJTO7m1Y<|13tD12fD-ad0gfp$4 zU**WTfmR)QfJv(%4x42NSD(oAv9GTF4U|&&ja@NgH(*j{3|o9qoUyjC+6B9Vo)v`v zBS_1KUr?E7Df@QvYH&B1kvML5Ur@TqTHzMceSSbE_41iwhA(~E+~!-L$@CD;t41pQ z94hyl?b?mMu<u4-ZJo3;ug(k(IVr&D+`VQ3yhQ6)oe*nXzr_jT9OFiCoJk%fk!&d6 z%xUi2<(xqK4&GbP5PSSoQZt3jo4+Y-P*@ydIoc#*lI0E^Q}`RF6>q$t%=&m78}E=? z;iSh5-9VR#CX{m9(1goPL)RoIZlrH8Q{FdD*98mwKZgVMJX=QLeh|~!x9V-c@KJ6t zJK2XBI8qFB@Nd?UY6}_fJ?sBaMX0B7)ub$@jgVv~uwRvQ_+SI*W6{#&=2=h$1t%^? zxe{xeA^8+LVikZ<>3W15#m%woxQY|H8YlFMkWx`He}_RSy9Po)ByWE~vmrz};6k{I z<HNty*xczgYkm5S{6~;9V%6--874YwZ38>wzs7yXv73T&uHgaM>9PuZ3l}t8bdlYV zZ0iDyV7R}h+NYM$uwRWm=Em}X5#c>)AQmNZNs{J+7L%XhXk6Bngo%uZ9_(1$Cs221 z2i882Cg-_dRz>>9ai?3khBgu?+lw`c%5gxsY)j5nqI_#d9B@*(6_*->CoF5tgnT+I z9H;Z{lPZyfGpvan26GoHI1%GADKaEQK+r&hJ~cNPN3b}j1TXN)_y(!;t7xbwYDNlA zsm2NS8NHs)dkKh{g=7C>L|feTP;SYViQvV|rP>hg&f}4WvYlq&Y0s%X2Z<&U^PVPh z^ie`%rU6?DcY%<n)=*@0x*txH_M>4=J+Jo=Xd-11pwFtZ5?@CvuCKwE3YxY5)!_`2 zd3xv>8;E4Msb;!_tILyp+PDf;L_~W<6YS3A^qe(_@Bz+K>t%rz&(K$Pn68Q+P^lSI zoRhr*sI#HrtC#A4*v2^e8*q#nklMXM>$8EqR#luj*P6fk<-Q^}+&-ymZ@d}|V_mfY z+A<I0v|T%S>7l%j7;lQN{bt%LV+Fx~87%!?vDDXYT3&2?PVT38Y_Ywl`2vEYat*I+ zGM5YtO0KAKj*w>8<Eu5c3#3h`2y$q3#C!zT8Sf~r>h&F`PlRuSe^Atdxbu;PYx6;B z*VKTPFkBQbMNx&YmvH6w5{A^%=noZTP-GQZiVfKHyh$?HE6enxD>m$uutx)-Ugutd zleK9*XSmi@r*A#_2>k(8DE&wJBTCd7$sTY!?T;Wa60-HfdFXplL+MRi3y#@T*xq6c zc{)P!KykzQCR&Q=oFhKzg;tQ9Kq@-PLHjt&HRuu8f%6d9;lF`ciFKgOA~CLt=FZOw z1AXj|6t=$w&+rFM|KpUh0L)nw<U`(%Z(%c;f#$Lsl5mTluEP!YN)N@)C%rhwcccV} zK$Z^LiAp(Fm(lA$u8LB`EbGL0S>rkOu#dG5zJ=OkJ{rZbUWQwpnK~|kd%6-9dRkPB zD<n%kaRGhrRO<mS6yer#rGrutYB61(jH~7Gs$M_YV^5QxVd*M-=^wfffct#v$;CK% zh<LdBeXozKKF|G%D)Pby$H2`(94qqXIP%IBZ&<ys5iv<jyN*!6Kp)TI`THB2z30=9 zM!%Ae;vfi(XMM4BQT;M{Ik1p)4}NrvFIZkUUsaut)4(6B^On&uc5pPbx`Yf|b-5LS z$gaV?zp5%YI6T#i7C9yS3>+ljf~>Tl1<<%xNkM<(azsc^A~;%U*cCzK_v*;;!#ChZ z{w2_Uny+Lq%5Hy$pAhz}IIVC#x-j=|f-)`(^HVZ-UmN<^*H@?EZ<udO6b~}$m<H$? zFt3X^jS#uxc=J_VWKe7*^FUD1JVT*|oAA(47{=8nGlLA+c=N6`g~l$>#7>(kd<DP1 zu`6FXfqHN@;(nENEoLWn9q5;NE=MT-fFN~eYDTa;*T;ibatoJm>x1^|H%6z%oU$Q6 zn&IJ(&uZ+dXY8a~;ub(1JyR@|FQeaIiSQ}npCPu>xb{tq!x^)LWspZ9REM5I+c54_ zTAf^-b^Z-Vje^i3yzL`K!WE8#8hpdAwo)p|bHA#J4B^#LW(*B7s7kqn4a<?G5))A$ z9--8tA;ROF$KcE*6Q}lr6(JPKujpPU0~0l*HFw)L@P=M^lVCnydN4^2Plb<1$Od-e zuW2Kj)R9&l6ZpvuU6b5SBVYT?eL{Rad9)Q2d{~p7wgxeu1^X7R(GBkdv$5mgrysk? z^I`7jh|fn@FNZXMwqV+bg*aGbVmSxd2V(WijXjk@@1Vqka+kJ%jhdfpZ2qQi?e#V7 z6&zf0!f#G&1PsjqHgj5Ibp-YZjSYjJLwlwSf7>5Ufre6e3ibq@*2DpW2zmex+KG$e zNov_k3bX6b#QY_jhxmaM8+B698p&Q-1VlMkYt=<a=0VzspX^A^H2#>>cZAbtgs$*{ z3?UkH-8D~8t}m(jLhjv$Vs?5eeVSqi<hD2A<Q{IcEo21KBXCi&OtEw5<|Ib`O2phA zhvHA&@N`s7^sRzm%Qx@P^dxCH$XSY04XMV{CPp;&R!Q~CXe#|*iidu<r42%*+rSH? z=agAeGP#0?<D7XSmy^gW8~`9iuW=<%qeWPYV>lim(~!a-t;q@OfkxXiT!Xg=>+&%E zLNLw1r?#-m8`p2Dd$t!?d7!Bp!d-)F;I78%5ITkaU)(^2zih9T-p;GrsD_w1lHuP_ z%}xk%{1Z$YnD|=38cOz7v1Mb|F3|8mci{8zR&g<>fzypUY3Mz)5j}L#0l;khIACUW zPEcauo;EB0Nufdran{GkK=M%#`==^$C1(X}cp;U395WJa>wwjenDp^-ws+XwL0I_% zINjgTMs~n*X1H=WdGNZZyNNMCA!oq)Bz*mn6#7x@Ru~z_9I^U4on5dT+h?jQj9G5x zVo{dnyp}?{sTpemWb@$RRS>JR4X|`=aRMs8s5g&8YIa_gv!aRd00eRZ_bU2Fxjh^m zvbsWX`DRd=Z)J@^79eQ@LGuY>2aKt-3Z&(IqDZHSG50Qb4?>A@2vrd>bS1VT7k0d& zbI8Bd8(^PUI0jyq+zM-tt2M3`_u5UJS4bKmHPmu8r(*Y?`>P@)zXL=4(JAy=G+#8Y z&=c45G)|uZ$^ks{U;e4ea;f~=Tb|pd@P*XtukxiIy4n#d0AtfLeDAMmJvXI1G*Y>? zY~cEh#UBp?7<j<*Of3r;fyFJ##oDimq$%xyf&myZ|6^lUjdX0mnp*D+aGI9U|5yn* z5B`wfxVn)MP2Z)ILFtD{(UNcvRr6Vd8Vst*I=>YH<yCTh5$+#_{x&6y4$r>dRam=e zOP90G-<vFwH<1_IH`TDGNcy=emp~BiIsxjjlm;kYC8;Z%6aPQG{`SFzxjz$x-{HEm zG??qe*{oD){?E&c>b8q_dLjI;z?jTC8@uRSYho0)1W(POxT#`bX@Wo-@nke&-Ub#W ze6?Cq!D;AB%)m_<3E~)T>uAb{${we<%2I4aGa%J&ROmD!uG&xkKxs1wcd<b-twE=$ zB`MHP6ouDc!&QUTGI1XL+9@GW0~tCG63Sdki*OLPvuR4V)&HdDmTe-pAa!UR(z*oj zGM{Ly4g?RhNg)OP*S%!>*ut@W1ff~!^Tp6gmF#y*3stVA(nf+3g1JpD@Gu5gwhZM4 z03P+lIFyLXJ2%maBx%t~q;)meY3mKaC|MRqrz=#Qve>cE(7K}rc)C)l0-<M%m;^i- zWWRRA*R~u<M+0aER$>wWvH>(2Z$=o4B<utc{+OzkP%X{Qt6Q&zwBwrNS}QOlCahQD zLFz5$w54H(Op(l39F4HlGAHZ}`dYz{xmp_6(6J-b3o%c{C2_g2+AU+PJgQJZviG2$ zs3A$3xe{^J2k&7{1Z6iYn0&5O_DqM)Vuu}K%RMpL$lVki%GFS~)IO-@;jRa1WzpEq z%jmzsVmvQ(JujH#1rh_CaHoZYfN3{rVw+46N<`XS0+pCrHbZGE?9EW}TY97dWj5L# z<8FM5P`BYiCR)0Wy2MA&ofU9ps^+p2j}-C%=`dWmI0w1v#N1|uEwE-zWTC$b3^%DM z6T1yz+=xb7uI;_9y<g);54EO25zKf?>24ti_eDcg-4MdfBJ+5YB!zyceY|W;RyhO7 zC+QbpiV{m(MzgPKHaMy@7_;B9e2nxZ&9{>+2o!?DON7sbtbwtcepA7<89&Y^T@M`w z)%FKK`NtZo>GVVN1O?eQ$9%L5(Nn<m@)D4QZwgk3zEFbMzpJyv;r?W`D?*9JrL;G- z%nbtK35Aq|Zy9dbtW!4lco)1q-~t5uM6n}F6jJgp+Dz73js67NYT(=MO~F{kaaWa| zP$+%Lkxo;zE~~KesemKofVg7S<}1o`AaG|nFVI^2nigTIj$}Pr1#$hpV3ONQ>Dj3e z)Qdy8?BxciAMmpU*SkG>l0?xFrM|QBqq9(NT92~^VwD6qTK!GQO2typ!Z4lPIMUR; z0cDI~+!P`3z4#AKTWxAtIEJEuO1AFJ{)pK@dZmGglVQEM1$sZdo+Eh*wz!#NL@hmZ zyD4Oiu(V$zbz2HFH)IQZ?W-YSd4e1(27d)>!|N4l$klMJ0Y}bWu864!si&LMq)ETN zQkgaS-xWj>4Sah%6-;HW1`o2eV>ln_{{#0f`6*bYaoleUJD;rDj|mj|a}^RsmfTW2 zM6-u_qHB*re=_%VlX!*@^~!%&Wi71&V`bx&LsA%1A!BODnM&J}g+_Y`EMRGMcrIEj z!W7|<b#0;tB{*gOpPKMsj2gsdeaW|?6AOoOyGm_ixz76t{1>1$W`zPPJUNkEf2F4Y zqqU@%Lr%e72_QO9x4@xPLTZ@{%pYo%TUSlg<Ca>rQqeyW{-Ouop_~2XA%>y-dG3Fz zP)~qwp`J~ldtw)qYDewu;p6IoKZ6nm@=s9q;^qvw9bV$$UV9dGquIx8U7<qx3ZwuD z=!j?SJzI(bM+!ymZGWaP=x2grqR=W3M0N}o$+Hxp5?MON5j}KD*sH;s8LZS{*=!Tb z*dB)zISu0asuGT^->9a@Tg{K)G3fx^r!3<;{8ZuY50qLucR0=cx5b%u4t&+tOM-G< zTA|?`1sKQIfzz_<xaNhl;zd);L9V_}6lcL2_&E+i20J>e^O4L1oM2U2Pm%2=r7NU1 zyt*eHq|}?L2|tvAKO1Od9L4IwtQCS|r5+cdJv94$8myPl1fcH0^(_UZE9*g+X;3-n zD)7PAjR>vWohq!CmhOw^<pHuZh20CkiTB3d<t46D6CM-5){xNd!6wmig`y)ZgDXT) zT6FeGq_hD#rkZ+^E290PG7-)!a^>X|XI2XfHAO<s+4&<Oe^I829sW={LcT?bZ^BXT ztP8<hAM~P?1<9L1z=+a4^M5Jl^;(GA?;x3pII9O?er7<j->;Aj^|FkN{gwCZvI^A} zipNYZ+K7K`QMICWg!Fa$T~Jbdr2{0>ccC45G<s03G)u&LC|tDDDPgw;>;1hwDy;Vw z;%rX(;RV4J=o6b#>DH#m-HU3F7F&o1*i+;hMaV~71p(~1bnGZR1bb$gnv2cFmh7uU zKczCYvNm$Arqf>>9a?0*YVi_;)Jf?k@=BH~{4SEYKdJ=6I`59M9S13LCpV2fsWfNE zk-4Le9YtOUcR7fhbBLQo9AlR#BdfVd9kC~_aI*`6KmMvog|E?AO~dk(3eK8|?m;UB zqwCd#PeA1SM~#dhu{tzNDVLa|!)Ptdo}$6J41F1@S}OD<-9%Ok507!sGMX%JC4%?% zptTpSE721LJ4`Aq<6tRz9@Ifv^@H#?-R~iNrbYNdd&@wnOlsI&^paCHsKYp@13#s) zD|kewe98+CF$m@O7|(Ef$7W#F5a7SkOo{iAd-v>92#&?Bqx3}KDF**D?h4MqV6E3T zz)$(`;>=EZA9R@2g#R8`OA!9MS`sM|D18Xxf5n~+<+Q#RVzv=Lf;OQOJC10lw8rCA zu_e8hVyAqBVvC>oH^rl*mlB~wP<XkE%^0CDH^H{R%Zsf8gS7WC6T8>r)CwCJJFx<2 z^QZ|MaR`?*HYcGiG5CA~9^PfdVE@f~_J;~}0367RpJ^jLwZ&351q#TOztlPE3NNe= z4ME`^ylBc6IVkZpD_X&sU4|K$B33D&Ez7ow14Z#{d!<oOwEK!iw{?&KB4#+C%cPBv z6SgbOnDzGL2--j!*`Owr;soV1N>ibST(YojP0w{R!UsS|iX(5>Xv>%Og(>p}gw#4I zlBdx5D-|6|yg?ZZGpY!M6NLe^AJI7tSKpFMAZUZRcm;<{^iz~V(220(rRhpM?jV(C z0KZL56c0||AZ?uj1Rq^C(?C+?h4{~_??W~nym98(nM7=*5`??|){{L9!@$KFfK+t0 zh(koLMPo{fF&70A?IQ~(z!0G~&KFbsNW7v2Hz#yG6_5RcLn~B)fg&w}3{m^4!rLI0 zZ&kseU@oSL*&F2)7`M!0Avr=q{x!jYT>%flkx`C9kF~Z&*|CA7bmR^i=#&toLER5- zL0e4e<n($k#6?!s$6_?cDIq|E`gQRonAyR!F5&wH&`*OQt-b9XbXE;a1v;cX<x)@j zf}Dq*jf&Kpsxwv!Zvp0Je+ov42{)_RN?K#WhrzFoY*yp{4EhX3!YnC@SMIH_1nOu+ zP;CtE-|*HdbXFxoh?n98Nv3S%tZibjRtmoZa9V2x%Zq!xBJve6K=|n8Jw^j1`DX%t z)ko-$$I@`l#sxuA{I>e4oVv48xC8W~wOC+Cs;%zA`<#&fpRe$x|3yi^qLNA0hT>tX zc#Oz<qR1R!*tyAH<<u=$2F;Y#Y=L2r!6<DFz6!Y-To|}rY~(^{)o<7W6$Pd)VoIl# zq^#0qH#ABC?$ZZ<T-=%}nCD8<%YrKL3K|iv(!*6_b3RlXY6x{x3U(iz*@r(t_-E%6 zx%HO_nd&)zsD<=)y_UyZRy?4t*b!AIH&HcjfsCv5zF>YKd?8jn?Tw5aO+|HKUfRfr z#ZD^p525Um@94;FMq0lEEL!zUjB4h~826O$o1K$$p$h*&&D;gxw>&)ZQLZL3QaS~p z1tS#uHE+}JDw(SQ45&J;mjxZScUK^Vkc2JlCO;J<1gG3MrOcQ96ZW%GrztymT&P!O zyrAHKxhE!en30bwJuX5qfM=5L5o#CcP-e)$q5bb2EfhT=go^E8OL@<aEz68{_?h4x z4z!#XjVY>f81%;<!~S@_q+7TWgyvd0O!W9u0U^uuQ;n#;8FAeJ%=xT_w8(n_rzbl? zxy;K1e5{H$kV`oQrb^KNlx7I#_b#S(flIpkgwn>%DS`e2{P7+&rOAplg<JK$NA5nR zFiG}7P%puskiUbLWn@GUNZG*a`V<&&)&HQ}OH+C=FK2@)zYEagL~>1t$qR>Q&Q3fu ztq`sUYPoDdB9Cz)-C40$Yo!31F7r+r)H2;l0%zv(KLhNmq;REfQkSQQj5O($D}`a8 z)Lb$7$MlkUJEe^wLoI3|S`y>(O5r|y*Gka%K)(oOnvcZ1n?s*F)$Y-tt^>;@bx@Lh zc*@=ZOaNmOvHu2-Tb^L83I^-kLNoBM6?E-V%28WbCCYQE1*^u})Fj9+ih4;4b_8AO z8|d$OX;)MzFnVJhwieg2;}()M;XzxD?eW5?y##y=EWEc`GAY_3np7GmUzkYj2`^0F zM=;aDAL>c8Me3XYC6g?7&ZtAjk$mLJIe^nQ1%`EN7n(wNTl+Rz0)tBpMOXXED+qo~ zq)w$jfWh$~y2&Z~J2n2F&@na37nB^RwO&A{az`hx*Rpz-4&@+_y3SHuF0x}UB%28N z-H@oJu`3XE1tzcj1)emjXjGXnBHXLp#3-Zi4W^1B2%kigYVTH8C=G<ab98@gBKK)N zVg=Wps2M5Se3Hpp%1Dh^J$Wc<5zn_8P;Ue1R-y?`-(`hfz{u=2yWx;xY)!qLidBfb z|5QkyweC|B%|2i;m1eVN(Tg8w_5#MGZxm1XgYzc~nHhu^o<ROGD{%vdJhbv_#Usjc z8djMXxJixwEvT8kbXC(%h)YjJ${i2KL}IMGd1no30uUkYro?f2i3_0{F{=q8PwAlz zUWf#8OMYNuSAmpTma)LwL3iHhmX0M}ihF~{^Bb%8W0z1=DMI0IvnMcprvzUO#Z0{9 zA4)u_iP*WoIq7K}+in?_V9IJK-J{Y!o<hHjzFRPHYFR)HV~S$uLhy&KxA5@l@D}u` zXyXF8Mf^BSEGYj&rP2%-b<s2FJU16tR<ESUoS0b3czpDky8mJ$T~^EZW>LdoC6`5? ze@Ck<kgHpNM*KS{9!2wem8O}=VA^(o{p0tcJ~A7d8~ej!Z1Fx3ILBX>vXeD2V^bD5 zZ0<JqQB6i%+2w_UET6F<gK)ls0HUd7aT%L6W0jdE67p;_yJk72W=a+K!W70@Y$$4C zhb!F>VkwCiWhY5XR`M%fO)Oh_464dFW!nnwI*lF>*)?_T)xgIAFbya>j17euDP1CU z)2jyz3B_+O;bOB2Vs`UHw5*9qR99vXNX*+vQHwMAY{M>~^y2$mw(}DO2QXgD6U7{2 zJ*G{lYqwH7xPf<+o~pIhL<>{V!^<+3nuRz|B#DtKnqP1&b}8E_nQ?I?U)Kz(NL`6F zJC5wPK>gBzPEl==ElDGj2^11i=%vQFFX9&X)_&%}se#$4DYXvxwy_p(;|%<d@1>F~ z;M<m{kr^~fNVc&OTZ#OXbv|71iK|853Cb_43|)nr2;KvOZByZ#Ze<FC_{<V{Yo2}; z968glYy!OVLklHmIwyX{T=l39%f1oL=}ESTzE*}k2&G&@`wq?>)(lJ)hsG=?12|J8 zKV?8d{L^AM@yoECrqo1W94;(ioB<B!%fkz~q&Qtv;w3yoSxab57Q`0yUVg3UkRklU zk^=_Y#P0A6{Avu9yOC{8vR+{P1rkq;S`!{)N}UA8XFiUJ<`;<6f1M*@K0!+1${4uq zoCC7DZxqxEji-AAIcBP@<vUd}186-whI839rD6$<t8X%d=GW{IOVG&HLg*|c-02Eb z`daFRxqH<T&)f`^kaU1*CoSN%ZJn}H5ABZ&3H8-7^V3UPf8X|v@R%N4y@^=J-I107 zycymFg4+m!OG?S`wwtf1&R#gu0<L8|e)rRfOjCGRzN3IqsHr4V<CG^3a_oy2XZ5y- zZ*#70P<kisO#$PxC+`oHySYF{Y8yAMD6Ob`!L%vC!#-1n=s7RpNeDCEkx)N3k;%$R z3!$uYEQxf40-E){Y>!GNlr=$LTB|U<7&%ZI!5z87dJjq9-Hz=xc>{+<3vLRzE8NH- zr@8AT7vT>(gNRyJl*D9yr^U@NR&_P$wlP27SRIEsib9kw2x`q=(D{Z#R|u|OFU3bL z)qCk@=&O_##WJNc=*f9?->bQ5kzr)47oLo6{DN0|L=(F4_O->XF#I%M*Vvrf)Jd{l z2qKw;cp^{t>6+lM>4vY`2gcZ{N*4oBK8noTsOp-$Q-zX1Gk7)lj>$Au(8%(&BtdV^ z%}a<{9Qze&gj4P4fZOPk1qOE4b4Ry#T!5#L-*S#}vpF6x!&RiPFuw^Z2Ean$A!sEx z?Zo)-*tRcZPP}du={I({YETS7a@Y=DHE%(3YpTe&<mk|nAw8sMg^zz$Dzfl_9;Z(a zUFIcx0<L0%TrVmYtqk*9fW(F#LT}2e`#&{&*u(z??>t>Vr3+xjjQWWplBE|`@+}~E zZlH0Wa@H=GFrwW+=XCP2FCiPCn&94G7L9IFlF$Efk~hC|F-5NgE<~D(Y3#MiIEH9< zG$b>MHTe&~%UI|`$D)ZHq4e<Zh{u80;A={q0V{=>(Q=UqCu_mgnN{+L6?*7NQ|f7$ z`Ce(-?#n-YTG>2B5TZ3ZaJ{OT5fFc7p3AtY1Ot%|Kcw03T}&|qA`Y924LDj~>MrE? zmSIwy5qjH8_%Gn$a*W%<_8@mIVBc?%z^leQ9j~ZdfguoeNot0pH}9D;n}?fT{v1XR zC2KFi1qyhx;v>W<Wk!kg?;8n{UDo8EgzlfqA?_^E`LIBd%fp;Hz*8pnly6WwW(zbf zYOIc>@1k#0q7NO;HnuEDdSxX)bVre9)=_XWokx_75$KP`76m$ruXrhL*kq0J(X<60 zbL@UMS^k6SvDKi&#<~)4<qHT?zDd_bw^;}qL8<gZp{JY6jrCMYg}V2~{+`If&3g&@ z&sHJ)uN%8c7%pExbP1hBQ^SvJ5iSNq^oJjdo*q)VTuQVx58yopxAFOB8{sC?r3IgB zmer>x`D7tX()*r;S&Jh$={{dPE}#3XWON6l2#R`H63WfaI`d0aj?(cU8{Tw_5}jl| z8kNx?8qD1N%HhCk3oci<ui72E_x#*bejuob^mH`+i#RuoR&Y$7K#hG@)Bl1Xa(}|D zt^-Q{$ReJI1)Fx*pyuze6d#{;l}LTc7OGl=1GkKd;X0s3<uN#I!{n`nd;_`rFqI9N z{-)d&_(%=wN0%)y=6X3<3p=3K|1VH*SXZ)GRKPJ<P}0ox^@WoLXqK1oF*Hm!1%t52 zUxa0zTH;k3JmRWe`4vzR+Ez6Be@fhi9WaP?)$4x)ngOMHEyRAC1<mLR@a%~%q}^dH z0d&Qb8e%Rd7Qe0nf2hWD{A!VB!9GGxbP~I<*`k&Czmr@_0u8n>KI*N9gYpVU_3IW6 zYOV6XOe|P#x<|R)K1Rjm3mQ!|&i3n}!!($B$rqvKrIsat{?QinFQluJBTKRk(9piT z0im3f|4$M-DuW?epd8XhC={A~IivC$r9#9EJg3?3TC8~e=Yd12AKFFB7o0`wWlMIq zCbr6yveg}-zD_A4VvS$m9(q103UBQ4($G#-L$seel4hSM_cO1U<m7Brz1X-)QRR~j zM(Pt3PgqsfBnT<BUi=?4clzE*|5!uH<^2zyZ|81SKZaZ0zwGYUK(w1LesM~a@8f+o zUpl)xn7}%3E*{`GeKCyf$tjqp9%WGpT%y3Q`dR*uY_mJ<?1Us~+R9c-6UF540fYX< zE-oG`Ko8$mzjF;HQkikgfS#R7(Xr0Q{fIeRn719PG_QaO7u~<?{DOY_9Kn6nPo|m9 zu*3y=ED6=sf`w=Pe-&`-H0dQPTVeWT&RedWwL%NQrG}1?EzHwzz+h4j29p)aiVzXL z-Oj6S@oaK=xI+Lv2|c*|ro--8P{)3aGq*uUj{N-a!ErTYV(3dvjKIk8CRFvUD}W#C zM#fLrdSy`$pFz>+{hnig{&Jd1V4a@%`+;-DZF!tx`VB-ZJHC&8F)wkwS}8b))(0o< z{XHJ^CF<~Bm+Y_gSv+)B4H<g4g8fB$!aWQjMeFKW-(d|TqzgJf4&;%WN{EoGejU-q zWE|J^Ku2j~GqZm4LUjpKqqxhc=(gT^%U0%t)_abeM5l#SSN;y*m^2HyFh+xEXDw$1 zySPKCDbyv|#2yAXu!mfj4O<==5p3A39MA#n`4x2N!DPWRz{yNnin=ejO1^7kkiGBX zkkk!GrYWtbQhKZ`bwL_tw}XfktI=>Td;ltJrNn=hGZ&iDa6O^BoH29s?i0;VMjJ{8 z_lg1*0zUOT48~RUGFO2j8RMh)i7)jl1kY#ONj+5G%X|d(>(Dp3xc>hWc)20~dJzqx zR=7jh`io=X^52z-0E-x%j!qq-cD?ipW~b7#(WU=WO#W}~yhw+RxP0mX8v?7SY*P1f z!xufLVk0jO#j1J3C9C!iah&)TB}T$OE5|75f1C|{tAX787wX_3?w1mUWjcN_Y%H&C zvs!!O62z4wSKgQc?B2&*Mju<?<63jN*f49nd%+W$=PzF^$+Iwz@Fxg;@H^6fh3PD~ zjm(3Nq0y^^>366Hwi8&P6aRConTcdh;Qy(%!&f}sG;XqwmXD&h4fo?d{r>#SMbkGx z*Q4z(f43EMxhefOq)<g}B`VX`R&uRCPc{Z-E(8Ol^vDz%beOstP9R!~cZ%#5Y?+z_ z<CyNl)NSC-h^bIS-U-wCLRWBKaU@(G_TnD^qQm0dzZi0Bojs>M<E&_K5q`<NqSyg$ zcP?mLO#d7j>y&Ung9NPw(qs5GCAV;4NC5T1coDk1;gB9e`V;))M&|V|#7zuaiHx2o zhQ%8qx#46JQuji^BwQmsOfQ8y&}ti+mmu6EoiDYXaIP!Q)^#!IIvmqM(PA(6TENC; zN+?M5TTJ=`j@UvQp=A36C~o3_Otpn^S^jFrlU8yUnIQZ>au&w-_|302RL_F%1)BDo zuB04Nsi$A;<m`<g2=pE<52H(=jiPVjB=;cAE|gY66{!zBIryF?(gGfr`>W&sEa)*k z3%y{$e+3&tU#3fR;s5KkFwwW?SmD^-htXhoEhBwjl#pe>W@emydx*d~Y$^U$B)hCs zLKb_u-qSY99<UJKl#FF}GuYvHxy4^p-d9)t;#O=|QIg9UyZVOZWivxM^0JNPnRAv` z-=<iiuVOaOhYx3y>*wCCtE<wN+;2B#j_Dnlj)Q&qTkE_>?Ej3qPl?WQ5n9a5;*o`Q zUKSZY(R_3M7B4%l?lUi<j5tzBi>ReXls<i&Jm+_8i{6Kz>G<~k&iazlXY6*5Tr*Ao zlQ#Xg>f<I~X7ts(lMFl7e}=U=auOKAZkPf@aSHZ5^n%M4E|GW?<yqksK<3h#4_@SY z#z=K|z`*LV?#PVJ(Hf85(R(eA)n~6x4chC(HR|aEeKq~E4Rv;8d%G7I$tx458*H%- z`k4H~k?X+z#G41Wn(fiJ0(U>JbZpm?GH+Fvfh!VrbVpQl#k5t6%TU|SBKyfqO)Rd5 zK0f&<PkzNOY}>MDHpBm3BoyQv=#wWV4BKHnW8$egYhQe@nVASvtnJV><@=Z+{5y?O zIZ@7qa-WZuzYH0_KacSo0fBLue&4C+zZ@opKa`rd5DIR1IZp4)J~RHf&v9n3`7+)f z;EbgwGx9X?i=SrK1hKq2FU3fZ|6OeFX^W2f!SJzK6Q{(=9ggyu>ccFTy1JRCtDRE; zv&+XBWu2Y{KR@*1!;MiriAdCdtHG6yoOtoFsO_hF`WNdkYh;sB{^w`gZ`aLZ%@X}S z%G6|dP?rA|T(0-w1CtXL{FOle_l?yt*w=D5KubR&-5w3G&e~DSEX{Ju8SFcf+eIfw zWEvLaI`}TyJ~L?h{*LXZznZ%@Clqa}{Lu(9CRz&KWvN_wY<<IlD{t2R8zL%TD^gPb zBA4aTsot8%(=A=W>Jga%M{@0L*7ZL+k6eV4gh?|e>D_r}R392c=rGooXmNDb!_WO& zNq(ZIQGv6gHKX(vKXGh#Xv3TB11ZwEVM%Fjaod@Yap@x+^hV5mczM%%xjtHdi0Vzj z+kIs&m<VM<RwPvE4o+@9A=7bqUf-9P(JA%|SG{_&_cSKriI<pg?M(YC2hajX5qKi) zZVyhDW<~{nKLD|}(e?r30c2eKA7ft$BICQ8CE|*l9WR;Z^gH_fTwhE_@tdU5&d%&_ zJ~n%}2NvX(fG;(>!ZbqnTJQyPCzDdG;L@FkkD`?s6IS{(j8`kRf6g4X^H@9|E<UQd z;q_pK<hD`v4ejF}1R0z~&m4nvK%0Vw$uQ=zF@2${jlB3_V6Z-0ix`Jj@k4*R+le9~ zz2mA{;Dfwpb`q8LH(;<axqcP!Eu&Va7PAm0QLR_uHkxkOpT8_SOkky2EL46hz48#Z zk=i)fP?Y;(SzM)4z*Kd$kUKMEEg3M%y|MCRo|fo>iF?UP(1wtg4M~h_&0{|-#qR~j z7S?$wuJ>eqt7A9})OQlpJ9@?f$u-wEnMWngwAjv1+YV(mdkH@^byf|d;7&{5&WZw^ zvcFZ+f24xwALBBM=6#^)ml>S}lKfz4^GyCIuYO=I_|uIOjnyku((KC}#xL2<9t!{k zlG?`=*8hHX8RU-3EW}UQZ6w8{&O2n{sbsQ(C!6B+O{Z257=(96&<UGHTP-CtBaRlc zvUjRZ`j-j%VB#crr1vrI^p@ag3u`VkvEkGA`HJca5TyCK5~i#<L#9)=<F(eY!lgPY zX*MC_wyzht!NH6NN6fQms-Qcysn|{QXUNv5t48=vJtL9y{<Cymyok=@EaBgGw>f<8 ztK%K}Gghc1PFmtC={?NC+lQzL_|lXzH@X)+03sn(Eu!x9NNHQO$=q0MMKh1U{dMW4 zLeSWr_lVKV&)Es(n?SYhxtB~DqGqO*7Lem_rrC3(8}NhG{71s*^|SC{cKuXsc$2|4 z!rca5B5{l<hmQ|$r<WMb{kQ@K&U#Gyj=mP*Ww4pct0hth7x;`#Xz?<BtXU|(c@9mB z<8BMzO1Rs%*QD5Q@$@_A@HN{2pSPbRXoypSi$=+R)sT`7%2=Ty3|{%is=Fh5Y=_>o zV?Uv6IOjT2Is%?Fu09?<maUij)z9S{E1bm|^r6M^-Db#fX!{(&3~`E=_)lEVI4eJ^ zF18=t;tTixopl0N9sm41<2*uE1Dd?5rl_$^tH3|K#oNjgA~%SXtkjd)l8%S!oq5T& zT}BjtlWZSnLtox$w}g>!1}ri@Ll5W)D~!qSzemBp?ShN^M&Q_3%TCrxM6xkQJ!#*2 z_ifYSrTudMO3jZ+)*V;t?m#>BSf!&LYOYslmZ<R<y4amie-iQIF#c4plE}o__XNI! zyKQLnZsBw1PC*k24n#6MEK0@i^7%t_*wjwh%+4tUzwCduySIF~&7tw?*UK>E#mXRA zSnb=5*j;PU5|ekZjQl~(39{Qw&>y~PA$DVL_ED(p)#E)9v6G=!dc1$nbRz>XOO9gJ z1j?nhZ~iNNLjDZoxY+TabTHZXaJMhg@6~9_w)F%vp-8TGa@FmRM*k(x9^<MD_u*c4 z{BOogi^Q+*U+@FsncRo2=`H{zd}myNszRp7{|EtnlqqliNA56H^whd@5p`R@@tp}D zXz)Q_=YzY6<10W@Z;o1a)Ew^LrHxEG!pC$D`$r1E=`9VF6iafZZons+Hh0Q<NGn*O zCnsNGy;JU22)%PdwiB+fl;UGToYl<OK!7%r1nyO#UxCmm+vBQS>?Z{owE4JK`?XlU z;}?TmwFzn4Z>8C}!zE|!=PQzI{TDw`LBjESZ<oV7z%D3Xbyn`NpMHqd)caLbo;O4$ z`Zoj>b=D`shpr=061cZ@Yx)HJc3$GQYTEd<{`#VvA`n7MM9W4=?^KDi!#%@~tgbUL zZphGcHJy&xz91e$1Xd9wKL92%)Z@4u+p5P$exwD|N)MFb-}<!X*G}bV%8aHeA$r+E zp&D8sg7BAY6qq}I-w>X2V%S+)8P|-KwyGeh*uUu1pP<ks{umqknA}M-@~f!6Y>2pO zBfwbT#C7RR8wSsN;XBlGhbhOj5$#u$3a&$dBA1N`u9u;o(s|B1ZUK~9$fl7Gr4oa& z!mAJNq}l8NOoo~RlB9*27h6>Dn_@kpTKufL;Adpd#yX7V&cdgKFU*6a{|+)(S8KV? zwmZR^ke(*!%Wc~y$TUOD`1l3Wbns@a+}cTY(~gkwzwlT<5H`-f-7EngC7({0z8VJo zqFynu=<qv40m`+Zr#mh$RrGYJcaN13d*t&Od2)jKJGg9O_xjgn4B;ihH|u1uI`eS% zl1SSQ8!!CRhoxa$6Q9i_{O;-rh<ht?%;6)YFmk&p>+MuVZpYWo9nAS7*=O$IF#h3a zh@Q7ltUAgyVDm^dkc<mEYFYx&y&i0J2fkIes}@4)RZ4C1shtK1CsRwjVK|4%?o_|_ ztpEL>fP0Dht!y`5Fu0a(tl&D^hefMTI6HVH8-`752~pqDkm!+CyWah|Gp&QGuSb+W zhZ`NCv1Mo|oNI!ZWeQ4&3He>5WiTLqCgM5RyphAv_LKjpvfg92joeDqPrY6$iSKKN zua84dlIR(Eb!#8L;Uj!%BZH0|2c6DbKRfE@aq1CVcq^_DtFm66cxHo5R&>_Sxg^XD z;P6BQ+H~ALOj!e{{!OVUN^28=h+NM-wc?!9Dlu`a8fB2}DgKV~)3P@@^wBE1^gSKS zi4!EccEo-0Me^r(9lF>TlLQ6Lb#AJpj<*GM5KAm<p3U7Ve>wDTi?v^*Z+8T_JJQcD zf@n8Nf%JK1c=Jp=xoTfT-OS*~GrQTiCvT+xB%}NWW3t)}W*tE9dTuG+z~rvH_z-TD z=t(len7DZoyN`LNCmgeXYf6_%BoEI_V?0T+BiGv_d(G0suF#CmHfG(ZtR?>G?d;g< z&ye(Qp6!h7DJwaV+7)3P$F_8YOi^0EWRo8pymtc}4@c7D)Z?7=m`b4TOv!NU7=zl0 zMJ#Q1$Nn;L&zbIvu(r=k9M19Ulg|$V+9J~e*WUT*ChVM%Yj5DR<j9S@POle<v-B3E zX7oV(hb^aTx+5itvICvh=cL+x?;d})!#d#8w9Sd1@;wH?KVGH_J;ySF=Y8P@<G^6w z1f-I<M*UVJ6-l(_sqi6bsEK{1AwUgz^dXk~oRPm&7RBuTy0`t<aJEUoe?JJC4%zho zF%~pzo59&bT|GB`mHyY)*xYdER9ykY3C`;b(Ma;@eEip<XhRQ$6L=e3%?8xix~RaR z%y8!K=7iV$f1m9rt^1VPy#JL>9sDgFvNrL+a%0l=OdM<3Xi}i&KUGa{(032Cu6$TM zRnbFQOX_`jq;o@%jqjrAu1?$z^9^h}vJT;g484MH$%5FCH^J|2coSQ}DX=eI?ocV_ zK0o&sYYB2sI(l`@I8!FcFqwHh4%9=Me{s`Suiot`YgjJjKiE`x6Ff4bcLM!&g<|6~ zWz{pD!~3?tZ+?K^4FMlmodw5WRs&g5{aj1+Y)={WeRd)v;k^s?cymcSI0%ol_sRLO z5Om$54p`UEJnlQ7NwrxM<7z+q5__l>2I%gamgNP0*D!YvF>h+@s*^GlciAacQxA^F zM*74MlGL(BKetQ%T4BPwxO}dDHXwqM&{;Q`ucI0nYk84imI~Qe&P*#qK3fL7+hlw7 z94`#oyCoM$6bR<a8A&*a7i(VS&ySAn_|(>DH*&xEru^3x;@Y~;`OVK?n7d5AxINh& z)g!U0DC&vpJf9l#>IC*`SiIjk6zGed!7pw!oB{d@8mkXrsftTr6fZm!Nqp%QHK7ls zc5nZrJI$}~;AXgAe{lYES(9N#J7KanzUQD2_&#U!7_O5}3Z%(QJ`9|N(c6ogO%cb- z2j{0tV%$^d6zqH4dMu`Df@DwoI!2;+YUB?ljz#Ir2WgIp<I;`-bl9;|E%xQ%Xj|b( zRQ)KujPw4{s@WG!#$?q8vpokO9{-u+*pb^}&IG3O1SRBzqAvUzpw&yrolA2ADr&N| z{ku*Ni^%l{nhgDnWbX!-$TQs8Cps33n+)Ue9Ly%*?IVe5`S{3=tFNKb8QxLQ``OqC zJ`|;B)R#o==$N{bxf%57AIfH)VKFFe)*uAOdiiX#xyGi?vN4avQ185!zxU|$J@Q_~ z<#1L6Dy}H8dlfuh-JWgiqH;WMAe~n)<=77CZiM@NKG1Goq&IX?jb&pgPUWoN;KM)a z?N{@Y877)zC$u&edb?lS(Zg)rlJT&<U^e(|z^Q#gg1W1_M&-+=TZ6~9%xs>&<uLv= zz87VfWNiQqxK^#DNK6RVnv@KcoN#f?dpDCfO~-dTw@s6RgK>i`Jo!NYX@=OkzG#VS z*2O^m<4_v8YkoWtf5ITfRrWGhH0)5I24(1g$jRO-Y36lJ15A!LLP>yMx}nwvj4|M< zbc0b~_!-lx<8|Yg8GW;8gHefALZQ1oaYg2~_z(N*Kj~-S1xsq=o#tCM@;L^^kwnSM z>tqr3K<a;Vd}f!!cY;IbfN=})b5Rtue1(jvE|IZ^=6rK*2g&cf;D^NV45?7v*dwKG z*d8m63RRg1nd*_0#ATbQt~u!my@=~(zs0l_uT3jFvV#_V9%?XdZiOcOznT-^jc@2m zVnt0dJNUIA`yk5Of4YEuF6d0NL${1Qvx|y0`@E&80}{{^O(Hd&cPbYTKE2)**Vk-; zgR8A-mhiORvW=@#$IqF4WjnGJXohbs@j=VCk3U$N<>WeE9^;JWf%vb6bBfV)dFA|@ z&0$vqK4%^YntL<&etxPhF8aQlmzclAT+kDXW!3wF8TUv6>7`kr7Hx^rcE+<9_^mm^ zH*SMBBo8u^VU7tybNrJ)IjRd*Q0cF3;3Jpv=;!p0Zb^S;$4hgpOS5J7PL@LsyOkhr zqx0;b74Yt6vlpwKFw`Db=gpLLVr;T#J7m<tw^iNnHE?jbFMea&fZVem^4m>s%PWeR z^TW-T+FGyX&fV$7N+50R87pb`=v?$-D@4B8u^@HB?Wacs$;J~y(*qJykD~NMAHa1} zi@<5-+kaC^@k`$D8DBEE68~0ca)3>xpZaN8-G^L#lkBZ}(?$s=4dVO*j-NFaj#EnS zPWDtR-%bT<%^n?`XnvsE#Awe#Lp?ZAiQW*qH@)w5s&!6!`PQz&cRsOL?2}tE3GH|m z8*=KL`Ol1hGu903(0b-r?=bh8F_9$!hcK4lv4TqfP<6!M%yIjVXEVGHLvEAd8wY_s zN=}l0)N;QBzmnpX1F<LahdAe_P)S=JgAb*w!4iIiA{a^n@Kw;lVzl0yXyf6IWyihV zk4d(ej~nZKw1^{)l93D;M?q3$Z?9Z$y_Y+GavW>880a&cjmIrwz5VvHXF>qg{o%Ru za#y+wB-YxmcB+dea)Yd^E@+pc+)~tj?Ang^%#QuoL~giBYf?aL(QIG+a+^D&r#%YV zTJM3sahxq>bk^L5&oC@SiSC3<_@+7pnxe&l1&g3<Qu8U*#CZw|>jSN~KnvsQV&IpB z4BJVr)^{w*jIbNsZll-#yOVKl7G$~^z9-?J0_K6f=_I&EdvS!|RO^=Xl188|E>4ij zV-%Su<wp=MNFI~V=bCIh@^ognQ{BOn#~?yEMQ_x*j!cAtpAxD{_NTMs`)bNx?8@+s z9<9RO=ybwdS8W@@dW<~|%Y}=L2jN!_!7U7XE+Z9GVCg$3b<c0+h6njw^o@SB1TtGm zAlXrR$xwLk<>}$(VIEWslYRAz9n-GsLI2KP_CBL6_;z~`{wq7ab*kN+^zw~eiJql6 z#LX&l5^hsa&weN)*Y>Mr%wE2>Oi`5D=w08wM~`~2&0ca=>xP-?hl*Uo(l_UfxF+^R zbsyEh?`gMO5N*s>G4}(PpD8f(t@T<0S9}B$&#*Wq+2ho%h`K7n_EEn+c4uAe_#L@U zeWAK=Rn_B-Xs}k+gB!;zWw<-gcy5`qzkV93BIM%0KY6|1H^48~Fn1C=$e0J|D?rFY zvJF`9_{l+#bIr)zrmc1ot}R-~Xm&vp#$tYgN$`QvyQ3!ynulwX9Uqi;yJv(>w&#jV zbBOZ!G5T5chd?LG8wp8KW9eBe+$%93#?8a=L1et&#=9?@97xV<_D7v^%1*2{L|k64 zCl@6<+&7$kQ7s$6GDD2y(}Ta>YD;im3Kwpse$VUuq2U-%xE80-OEJ(K>L~9P_szLM z)|ob&OM;GR)EQdshr`%Rn}oo>G1<j2oXBK+z8yZ*gJtf4Mzj2r5q6d73)vz38jCsG zqU!TNAUI^W_(J2zq4c1-8=UGN4D6BkJ6{ZqnV-SVYPqIOGrPN?SG|ou*_#zXf)<7S z{3rUyhh>koue)ZAG3#?a-(j@)fof+4Y3ZYM3s*<5LF!1m9I`LCJ}A6VB`!0LduMot zuiZYR-<PQ=OW%GgagJsWL+QvG(SV_~@8Zf{>~p476vNworWumhqXfj9S?AO9$`ok_ zAE~J;xg^?f8BSOmF76nU1=`DJ$_COHt{0)tGgZ&Dm}a*48GFc8&9ifz$4;a-e<xHm z>_=~eMh{)XxJN{evW&Ld(2^d0=wx8p09SU*-vWyA$6=dsIsa80NrVa&&lK9<ba+cv z@e1X0ZBlW?XC!kS1h(`uhh(pf#5wWkmW>V**QByOo=2h=E|n7p=`IEEsq|9(3Q+Qp z!?J#PTW@w#0P)J~fc!)9{vZEy;=d11?^?OL;ldiPX5W@SU6*F<-;m#WE0OLc>An!M z%|)>P{*toWw?hB8=Cr=3=!fstKbtUH|C{FQRFgxFnMu<BBkR56n#{Jg;f+{EMLCX& z0|?BF0W1{h(wXrngwRxaRSALwi1c1&9CbznG@(cfsED+H0s-k*00UB_N>>TJNicNC zyKc|(e82a+-+%rvx$nK#UVD{mU2A1iqt%*JGhEOU*d-TA^9|28n_<k8>{L)!TdoD0 zQy|%JwBt<c!<Oac8c9jo!<1l;!pnbXpvF8x0<oY<$oa#3IqBje!{V9E%hRiq#?*pP z>A<bPK!i-+Y%y52a)%NmA8pH3nez6gd~QeRc$wys%e}beYgfmE=gL?KHEsr19%|Z% zA~QuF7q6AiOkM4yZ_|=7Eq?P=Q6oZ`hA>4*XLax)E0vKUbN6aHGP%rQZQ@%VY)3l( zaHbPNmMfUgv_=izwkSIrv5V4_l(*7C%|#JC-8hYP|1707RHiq;G0>-^sKr(Bn#N`^ z5DgpM)2zK7{dAY9a(@TRnjy$v1hXT~=&_G#9{c4i@k64$$Rf8vTOv|`$(w$d!4R*Q zq1+Qoo2e?ztZe!A?ill#_TZVYT_~b3$(~Sh_^e!D--)cCLSsrc1vdC7&RoBGF||I+ zMy+9El^J`2xm=wv;1#KXRuZC=UEOrT*;IUiJ$pjRIW_L6=fByVmkQx)&jwxpQK0S7 z<JI}Ci&`ToC=LdP((9bY>byjKb<^pCplE2ZoG*aPRLQj1U5M<_Wk!t&*I1o#Hk_W6 ziqVV@8B<`Y8r5!x737kb6>m*6gwUQW+6;|#6^~HtJJ(!s?JZCCAN=ngD-48qkGOMB zT%zpQ%i^h}Ob=<Ue%eKKjK<&QPtbK<*_@{yi|MS~g-o>6dnh&kJhD_ohfFlfer4{* zcKDB+BL!Ujg2XQ()AQlvqop@q?Z8*b@T2(FrGCkS5{eRQZ7*nn607V4YKIYafPou~ z_?eTpuYN!#r#ZxD6F~7fe#gxG`n`4SPsqwAqb`!V*h+TWa0sSrAeU<Ys%eKPq~wt1 zn^n}JAARQ6z^M~Js*PoJcuSXA^VNOcoq2gG>I1ieunnbu@*CD%9hIg2c9Zzwf`F9E zy&q_2<jQ8115w*nw?mCw($@1g6j4pqhYO;?QmdWMDA|{Q`eqVxiNp-MdVK#rwBw@j zdEDx=a)b_j+;d7%IXy<BUHA`?(Yhm4hmBuXx0b29MDOoHBIITzs%*$Ft8XPKLsg?6 zF0`CEw)*WE^@#m_*r8_^wX!IF;P~CEU!$B}Z1sDZQ+UPUH6~Bai&G^fr|FWB(O=&y zF#nO1z)s;?2OFg?(hK#U?7p$FlHs5o=<}EQyg_}mVWu(AH+4KWOX5TH`0CPm(tn;} zp2vF-+9md(h(9O$lvewCgt$FVBQ^T^2g;Zyo+(O5YpmyKpyRZ|7H*Y^T33E=uc?9m ze0___A9g=vbDDXo048Ln2MCDwzKkC@aku5!_lL?-LkvLgcPO}7I&RFw+cZ9|(&U(W zv6;->k{?%gr609--@ekGK#8-6*9}gVdm^{*ay=iM!lBj-Yp<6-sip0V*p?-Emhv9o zZYPA$xz!9?f)*#6rK_zU#G&X02l<Y0;or1iBT*D<oTkNe)_QUv{aEFOm)C$Nb|<9l zrT9_AmxpP@L2LH&<7x@tA1EoIs*f#8j;JRe=vBDbQs`oAtkwI|crdJNuk1VC2=;Wp ziYogZcunJ-eD`&Ola!@bCv>TILTmVt$<M<D;tq$-j<=&W<`gpmw=6y>4T9SiFdq!3 z`8r`~c9JP6OrG8R_Dp$;Co2<VrnoKS^InN<#m^*ZdXPx6Uhl>9gHKk%DmPw3{BBE! z#GgjXs~<OZp@|U9ARHLMjaKf*4RWb&?GfsHh@@5cCaYqYvC6OGtH}*v4|7xV80PFz zSxN8l#thB8a#wj@*AWHV7*RHYCsa{G&hw=<ZI3WAHD1eHSmNrp!=L|s_KpN3-||=g zlInYNssLBB<?A)`g83w-kvF2dsPQCIt^qgFAgLML_(b!4N@B>@n@oBsK(XoGnyK}N z5IM}}$Ad_%=ktqIe$-aBUsJJcD=3*edu8DZE3b$WrDC*S{r#9WqEEx-sYrR?wCZdz z@x!l)iv1GFIPQw>6M(xTT8h0W#&;hrK16N!DSQmhUs4bUvNLKcC3`iVVE%IF-DAy- zQQ0W(80Yt(^Bd3LxzXBMs$Xj}#-__M)z@cOm(uiUq2>@XrjESAO?OjvJe~-UKt447 z26(&9_(#`VRvNc>by#$Zop8Q-J}{;^90e#8T@x2u(J7oQVLnZ0eA2sL<8;|%Cb9E4 zXq!*;FZlgGo*i#PZM&zUn0s}xEB{1$vqY(Wert1{83bM4i5``5f|}(o7Q#0<Y-l=I zhzVER+87A+kMeFV>9%G%C5Sg1bp&bYjIUfAR9rLq<(rX-@e;v{hJz{3Z6|Ya&a~c@ zKU2p7Cmyj#4g?8fBLUfXm==>OV@HVzQ-r!2^$*_B4+i0Dm*oB2;PMv2ky!?JQnVu< zIbKH-+c~U8TtBw5oA-_;rudo{F7*|<#2;}mFw#<8?JlK^rzPw{ii84t<^x>`s5lgU zxyyEm-zDmDwBp7r1AE$;gl@`*s}q*dXyP)5IQ?DQ&WzM6o!{!;AV-6JRA|vt{+RBW z@+U~lB|mVH0-7hNrue_-_(q6?3tzX94+ADBQftZQR;cLjlIKak*+tcs=ymuJp;@=O z!_>4*Db<OetrVM{CP+A}{O+~bT!<o~<=rgVoAZ*vb0;FNt)_AD*exg<hsNr*tuP0| zeu1+MA5hyLQ_S1=YjrHZXqS}0S1<K4C6eDg7WAmmTwUuCZa5By?)Ys2yZ_3ArUK2U zgZ;cd{qa9)8Y-<{ctJYx*xl$W$I2Z;e;b4K%$ya|&#xI83s=xscVm~eiodzjzp*Q# zWhtQsXXEx^rKmS~x{3TGiW0u@L=IBTh^0<(=J?w-PXjdZGl$5)ElXJa29{?wT{i|U zpkcI{zChFFX9g`L@S4|qIp#=A6EC`Ztu~_Ght?XVe0?#W>7_n$j|J`^!5(TtI_nP; zh;(aq#__&1$-kn#iEc2Lq&UX68=qg@2dgqjqq$Z2dQCi1=JKRx(SlA2YHRg|t+Q?m zvtCq!rIxADup&u=juyw*<F%x3M_<{dvnxvl#@ABveuaA@(SQ9}eZZQTMO+;_U@DuH zJZuoQF+yCp8i;hPiW%Z!TQ}HM^WwR5iI~w%jc>2`m7{H-tkWxM?6{}4>9NbW?)C#d zf5*JVZq8zqaL>x0-DBKB2(#eK>s4P$(p(nBP~)|QFGM!0K5bMowR5E?)k)#9j@LpR z^RL~YRW8+C6!0Rmt=l8~%*jLz1!jFa{FOq}q+T+@Ypq?dl8R{p6j4LIY*fGs`WM}* z105f??pJ?zHE?6jpDC%G_&yohiXz4m6Nv%R*^!SoU66}2hnR-L=8m@E&fdm_Z#Wiq znHO4`8*8VAjGkh)hQ)Vkl6iE*qXelNEJ+D*=@?<M!P2mH*}N9WO0jj0!TM3ISWFic z*R)2$e(@#4THmczQ;xkc5E|ruT`BH>qtJ;<_3f^S-f`n?Yhmxz$a?42RR4H5wZ)I> z?#@D}?}DZv?d?Ih)C%{66E-*n%&FX_t5yS^y7ECjaA-`baU(CXex=Zg8P54If3JRb zwxvLB9>vxRFMs7vxF$)CF+qiiKlR1zj9BtDI0`iT=OIDg^uo3liR~E~HY$Nq^aOi- zuyDO@Mdx{Upthj=N_j!KU$M9*!6Ck(eX36>@r+!ehU#SRB}np`=JgH~izRo=X3+Qy zyvUR}5Vqy{n>)qFQ>bngbgB3{utd5?AH1hQ>u>s%Dh?%WOR?8o#Y!qNx4;OjULflL zh)Akp72lmB9`n!RRRiRro%Rph_z0)c)iI`0&&Lf1ff%i%5Y{oKOa*2nwgyvFnR6AA zU_aR_BWz1+h&Os1(kd5I;ruQAG$}`AZZCmis%J@7Q3eu-hp)p6Zf-7vq>oJw+6WLh zzZKH}Nyi*atJW!g^odTd^kP2|6Tc&RGV&?s^<L&3tym~bfV|?HN3I+2+&FJWO+;;4 z`=h?EOCc--Oo1{tp*wV0X=rSnCFu^=h%h$!O!(5xr2&O9ZdjmHlvXIkyVVd1iQpQV zt}$?8Kb~ELhY&gEGZm<AQgB<IwlPTT5i!X-$y*E`F`a2pr0#%e?&|m`3{Ok04yBUA z6d>58D$nyt$2n}ttA6>L6=wl9>bcJ~2|;_p)MFRXeaO(<*w5JO1=#iY*NvxChSr7y zovoyzg6Hs9Xwpsq&#u_8UmaJoa<1^z&O%Ee3%j^V?k4k~F|>(Xo}9TzkU4mE(PiAd z`q(*8heO>C+GT65$y~w{dt)PY>0UR5WG>S2whMHUzxr=>>O3y;NRuPpy(V--VYclN zCUQ01f6~j~+#UxNhEaMySi%B{py8NVHMcq^KyYnIVNufzww6@jsAox?S@i+yQOcA$ zO3>OQtCpAARQYx%dj6MyWN&)>fb`w5=NATT)8bCRBrYKpnbm0r_bu7%bCHz!P+38( z0Wb}1a{1M2y<SbMJKBo-F5}_wu<PHTEd$q9yd$3VgPdZ_Ze)1T*xZ1vLkR<w49GQ3 zXVmNU%7$GKAWY3wP-8+eJWHgIj`K&)2G+L@Dx)X9j<U8?N-YRuc!oe7mOR>vJL}4f zd~6vuWKO2V)WdsgZ6Iq<T{HmMjNDTY8voh{+NjoX*aDYKrlt5LNyT?sR8tIS11o%d z@L{VHOiMY%JFpTHGAqD_@(5eV!BhDUF>kp(R|qtF?V&RgpDP#PNDnp9f+gUH<Zq-) z#lLmnCSgb9-3l?vr{-q;LcFw`)^$}-pQFK;ajZ=iKJ$E``(ZOz-2211sM0M=0kv{t zhh-`hGgbEqLceW<N~tHfSIaOVlr*Au0HMl%uDqd;3fx2R?`@LRRa9r5Lo!Rz6@36X zk8O-Lt#Uq;sjEfN;8to3wPYqMYaJw)B*GOKO400*xUs#~D@RfoSEiYC0wH^H#Vq6$ zR32X9f?s}h`NEDhJAYW}@dQSXk&(7$ucxk6WvVmnx}uS-=Ph`KeF2T=B3*X&P*Y1L zhrOP6lm`vf?$Ww%>zCNfEnRFp#Z0J<oq2u}u2*`>Tve&u*jBECp@2+mgUuw+%NEn9 zy!#>is-5nl9R20?DE|W|h$L}=r}5fww=3&c7MT3u4~r+MA%x|j<fC4CUd>9Nm^oK+ z>6GD387s1;Rb^a|X5#5zR)>~B*k9*qhVmKlmB;TojR^OSE`G+$%1VN@^y({q%0Z*& zt6V>*nR)Ka!`7ppUm+7Xu1?k7_)UPkT((*<?7rd9tPVwzf)^JlH|w4s6;nF*d}fN- zj8}$v;W`wEpgN2QT3ep*(j)eI)n9L&2VS6=9~fxkFv_>dW09qRpfDS>##Uf{Z+KZ* z{Jnp@Df|G_7^ax}@%i_$XNYQ2hBxZ1GRfm<T!Z~uXUazQ!`-1@fzV)>t0gG+y8wA~ zW087uzrKB#6&RR(Ph9``kl0jf+wHV`9S$2eIjEkf)-}ep$?l<2jr7k)6+)O?9N$|+ zbuV=f5y<HoUAVT?FIzV8m{@lhOhnsdSwF!<^qg;ssSZXnqe2ly#CO+(v!7`zRd<d? zX)gTRF=P7H5eO+=+$(0|4QMHO)#Xw{?>RjXL?vphW=Nq3^JRUCk}GrMv89>>STN%{ z?6OYn<TuJMZ-k|+_778<RZ3wY_i+iflzT^eSNNF1!m9FRGlj&qpRo<*2UB7quS{M? zmin9w62+5(kaeHhG%mq`#wRQhsj|_wQge*!-!Bb*BM^>$>eVi4sy~F}Lk|%7T8AOE zhO226j<uuG=l%|AKgUPV<*jY+mu3D7XoAuts-Y0sonA_l8hQtb<U=_oXwIKI*a)o{ z?>!>XAd-Mx0)^IhBT;!(eqN(-Q80lD(Mip?;D4@6x**G-G#ESDR#-ArzA#qvAcH(p zFHDs#(Q8o!nm!_rZ!8L@rRG-{36NPL^Daksopz1Iq;ieycLH{_heLdVtIz`h;wxsm zSGIZ8BCE;=my8Wd`en-2Jz&jJ+3RxzzrGWY+dD0sUt2cb+88JH*un7>=L}B$b0LH9 z8|+3^18>NObeej-TUnKh6#FXTLYjTh2`ouJ7Ogkd3UPOBVHj=goh~JCk+qPv_6;vn zdAgUZjY%a-nt4V}N;SEckVX4uz&4@8J%y*oRxNwnE1v&wR)BkbX@>azAuHYni-q}w z$tfwJe)4V)rM5aCN82S5n-ZLl?P-xn`GxVeUxiS+JwjYX0^-F8b8m=!xf?OfTorjI zeR;18%voMbQp)wrqZrQhV2H~bG0JicOvBd6wGwBpzkN<rL{h3x#8E`ISKDvQ`LG0^ zX^F_5oF3{(jIq|rE7q+eELeq`_qeih8Sr_rK(f+&c`oXFCx5sl_LvtI6@MM&dA@y$ zS&zTgxEHwy?h;D@-Sn9~8QGJht|F+etjZ06jr;snwWJ@mm`r>cM_jOu6cA5a`M1~F zBv=XSbDGRHYp*-Uhmv-K6aR|Y0_!?Stp_QAnay0TPlE`vq#v{N6MB#g6>_?n?xoBx zjB+15a0gi<Vt`$=opi-(iQg9Ad$-iY*j$f&`MXq2hhBODSb=k2`!~1w))5J@H52xL zH@tCz?^OQBhbElrWq8gMhn4k&LN$gsuWb)aU;btM`y+M9i_LTW!Ur+Od+N-_R{+G^ zw`$C*Xd&b3%&_%dY%vC{+G&`NNT^8xR3XNq%aw_j6d!2-F2BnTUdoK@?b6l1kN;U} zZoEY!#^O+fzWUk(YnS%@H>Bp&<fB0@UPFPfmg5xO{d!@W(;3EMR+TyS<d975&)Y#> zX3M44g+)INrR<C-T^mSoOL@!Vh;$etzNTJZ{_zT+tIxUIOGs5BfZm@gp3G#HjZ0vF zkA>=fh_*$NJ7+#upIEdT?t2C?g=wFp)@SKQT%Rs8X2p;bgz)L_JgSs>gQi)9O~i#9 zix}QBUw-)h(%lCt>l%JPQ28RL@OR1`Y91ux=@cwC8`n1O`}WsXn#0E9g<F$<?!cCQ zb8e7|r$V}FDo_AA^M)Vd6{rB|m}$llZ%8M~-tAd*pHEgljVWi2+BcJAZh;wqyJBe} zY%2T>H9|Bc6h%*4w=E2Kp+Tv~MPMny#uaQON<V{sew7g0b?$5Z6mys|gWHYLy&zy+ z9h|4++c`bJ$yT<lczyfsyiKmH&!?q$o^P)xm7)+oJU%}%PY#E%5b1X_yC{QizXl1R zAstOC6{a1V2b~;;>h{*xU9g%UNWQD)4Lx3BqeIP}L%6#-E=kHi5t;E}I}loxXh6=( zaq^`2l9-{7SNyM4SHCdE0ud9X;JMy_^@g$VH-;+W{T?Jtr1~D!s=Xc+thH%aoL#wM z=C4rWfvcKxoxqlptE3suaZQZNGe4Y=*AqfIPBYcPj&DO_L<{vOMXg4_UnLG{z{X84 z%nwCH&2KIzoTF5-^}T9&m^+R@pqNG3Ma{D#2>kQK!2ktRX*$9yvnYdpgci-7F>Y=( zhtA>^dk&`E#7VaL;n9H0kmoUf#h3dVXBy!B2F>7D;`s1icw55T(AS`}e*xSi*zb{? z;%(Gk^h`=XT3>5(5(6Tpd;?(-gJ0^fh~Q`-air*SuFaKOEO2(SrEt|u#FzZIH;*Cv zC$u~o>kr&u1`moI`udqUycM5UStvEuOaQ|{oHB*I6hB_Bl3kr83nzb~&y|f+{A<+9 zCMB@b{i+Yvi?#cL;zNi9w1lvGb~vxp-is-&%%!p*j&JxGGXpMm<Fw?NN^kaG^S3$* z9N6R9lENNP3r*isE~)x&KJL|RwWLm}75=t9#6FByH>sxz47#m!1(pY>P+FA_h6oe6 zC^p)McouH06~0`bBi<3A+!2GTZsJ$kP5FG)<uuQy@Gh#zRgmWK3P6V|SILXOz$zzJ z%wnneS6S~dMdd0oD9Chve}Lalr*%M(z3$e=B|o0Mxicbs_2CaF;-Zab9qVx<<;okP zoV#`5bUxED1_QUkmnWVuD~3}yCXEX}5~=6+pUTG;Kcgk{=gxfy@UD5>E^Os=xJRG^ z=c8lm3c^!os{Gj-i*Ua_MOA(MojtTHp}n3cRWhWex;&8KYz*Ra^1GpF^&6iMviH^b z(LEpOsQS((86;9>N|L3vhPec|uf$i|;DcYg1!N5Nt4k%GxgY=qg^e*4e1Z%-xiQCq zviUZh=aZS(0Z;^ehPC|lizBm7&CLrW9X77A9k`=3AV^GV>4rgiy|NG$KHNA2oGgnP z{k{+<J`@|TIgDe(bMK|`;l0Q|&eEY?vhisb^Mh`w-%fDkX=|pASlhBxu5p;WD#w)k zDjaGif9;;zh0^Z{h+9>}YkX|{#bw;!KSwVZy4Io0bqEw!UAYXZfTU{DxEiXTB3Ppd zHtKhvH$V<nnI2M(Q_bi)dz$CxGEL_BEf{+r+Xx{nfCh9;+G5=iVMl%0vbUQE(sfW7 zu&&Izr%F6_MjnyGyk=<0Iurns`(2q5^rJci71T-f7cXlJ2RQg*FXSMt;YLAAZi9SK zLjdwKD~U7!g?+_ga~pBY=`^0CC%<Zvm7EVW_KGVo=80(UEswYD$%!O$jv=itTf30H z)5qtm0dJe&MeYM#N~#|g?&++9Ce=H)0QYc^FKEd?=H<_jK*0)Q4GHr+r8C<jg4z|B z$MELtVc6fHo%>wff(pv6O<%jwsu@*!f9$;{$Pg+D_Uehs(g68(H-Ca~Yqo^6aSF~{ zBb8_(oj80ql03Q_fh_{FB(S+Ae#Bv*FPW?0Eb-(#=BLs}27<|sKi1}1!4B^Kf*H6| z`4eHF16N0J>m^fPY|40QSHe;H(Q;oxi&`#2mpl~W4H!58!R2BY=E$#|0-Pk~JwbSY zfDLXuHR0RDXY2uwotFQ!Ddb59<UzhKlph_kr^j!6ITH0^nLY(^*@q`oN!kr;2dbjs zl^?d>81#iFw5UtgteK4n?m+1)OIeb+rC(m#_-?Cxg)e_O&lB-_y~UsM!qLWmv*Vd0 zXe@-a@VjUcEgUFg0OHW&FkCKpG~@;w>i_Wf<2Up1T5Wgtl9_(ZL0)Gm(u2!L=Mk_h zqe{6pUBg@&6yfFl^RXv<ndtc*Ygf!#tK59EpJ1fkTm^N7!Ee@u?zz(Cqe2LaU_88Q zc+strk!WV|b^P)iy=)5%%F>WJR2P?_EupTX!ryCaHR`$<-R0Gv#28IV%~sd<$Fy0? zSHuYn+|@<*eABQ;d^istxnypPJDQ*}w1I`(Iv1)|t`^Q$Gaa_tlcwjprB$c%h&T5Q z>_s{sL<U^<dSvfLocf@7qr#G45p+Bw&cZ0as>HWOuVUp33p|#65EEXdv%s17@bQOX z|KV<1?o4mglYh|y>#-$trQn1+DM7hXFS{hr%ukp_z2G}f+gOO9SFDDAzQtT0at0$8 z!G5T%EvO)|+L31My(#Pt7sJmHdXRMF=n2GRbL$(qN+{%==6-ajJ0?73ruY*4fL&iP zfCR!0>41ABzE4Y=sR^G}=9wuZcui%`L6PU3wQI~vr)ZQVIY`x@5S^CKAd@o=TVCv0 za_Lwfmz_YY*{s9_P)2|LyVt87QTrCmeE=)v5BsEr(A|lDa5qWJKg})OPAQ?I_gQYs zD)9aF`Qe|Ed%L@XNZj>ds^*fOeZZ?wSmr8cs_RVO)Zn~F6+_lO1q;9e$IIXn|Jqj? zZ`-zKulrMSVib25y6YS%O;KoZX>R5M(6uzs0rvxS$&u{~TeX;nEsBTw`uivftp?}s z-2g2=*C`)dCoKoM{LR>LBb<L(8JYY9C>oS0<O6O@(>MD_)Iopau@+zWu!$yIvyM!0 z8~3gtQuwDoh|_ect{C<|=hzo1T_onBnS)DirK{g%%!Qn5r`xdaXcv!-aidfug6G|_ zQ<w^ZLbT>QKEI}#(_LLJ#1!K`{Pq=oQ_1A`DO{k#DXC&JA*3}y+lls!KGQaYmOD;& z=wfq}?C)t|eVQN#MpfVTudU%FLw%+FGGJksVOfgOFPv>xkAWtuQ#2lCL<S3?<A1e= zNT{x|x^0%FOL6M+DH@#EV1Tu|Urgm-9-B`XB&@yhmDrBMo^WU{g)dJ$ZtKs&!xAXu zXDn`e9MDri5!-ss>DF{M0@MVH+gvn_H9olfRW%abZL^N4$;&73(BpSK@cXmYezkeT z^Xcbhd1UefDZls3-lE{;ZtLZ<{7UE3wAg=tfBCnS5hkE5{MrN7({tU@iSput@^4{R zht7@0@ML{EfH=fv+;ZlQXf=fP<U6;r8c$Il9J3(}8fztjN&>1n2Z7l~pAHtgi5h3& zLHFe@rZJ%oEL+7Ag;GTS7aXaZawn&}Hpdv9IfHHE*E6HB-TfQkTdVapM%W(GKX=;* zr#)~8c*N+g8jWXW;HSWX{boavGjq5cC&=YTKGjg1)>`|}ZM}$PUZM|e!>iVKQ7vh6 zM2s0coD5Y(5<Ph4BkSF<E{Gof)EDD5=`9_Z#!9FD+-)e;6)F=D73AC5I5$JWgLxtE zXzU5eflwJNYW1Ky-@3Jz3Nf5ag#rA`T1&}q(q*}K_vW&<N8B7NMLN%{ol{x-;VyL9 zZvwI&2I^RNhsd*#a~g-9%<h55+Epmi9&}rt#lCIc>xlXeLA);VZQ=n2aj_fej5*jt zi^e7F=Z5ebYdy>hx{U553p<9~YI?9g2hg)iP9Sn3ua0J~2J`f?G-|tUq;>+}78_DC zq~jYCx~}j}!U%8x$f3E6?n+3%mf`m}0J-bSv;VNk$}vJK5y1jzLh;T~%01y8Ytq(g zE2NJWaMG3nT#f@%HoVuGqy@#(H)7_&C|$wK>v0JEZe)&R5rxmUbsG}#0N7x%gKA%8 z?c99C{viedyuJmeftqq`O0f)EbJ;CbspjkNh0V&4`(*>BA$YBuVwhW!)u@Cv=2y7M z0|iLu&UA<5YJ)aZ9z9Z(9Q^Gt9~LQ_1J_=vz`lw?kTonBEb;Bu2=;^G0dui67kkd_ zE+N?ji}QzL&`%>;JCIsk_OlWwc>utzwT?xg5!Vt`WPAOGSuN@yZp>c#CH3*5)X~}; z>8!i^(5{yd4D4443t>OvXS4*R1w58Z`nJ%}1!|atRejSKV!?U7-5am?CGLpjV`9r1 zXc~=E+Xi0KSpH4u9^{kdUV_hm`Rn9Rl~T7l*e3`d89h009f}j^CHgWIQj>!f@IT>U zg_}!EPEW2TYWr}~mrn=m{7;yg=7FaO<F0r2Za6&uS@jdbBB-_@0L@e<(utp6cUzKU zM477NINqIE-@mGKrP>u1Uarj(5xtJ<ir-i&W%iLu>w~5_Se+_T7ETFG`~W8rverQ_ z8dB;p!17b`OK^C{%v@v2<oCe^?Z63Fz!UZ_K9v=(m>*J`-mF)lIBe<<!qqyqIpfR> z=lW0Q`KN_aTEp!J;Z?<fnu6AGb*7ndg|wq-tm!#plztW(NxZtu=n8Axydt3y-DzJ2 zh&<XnSG;D`(Ch0f`ir3*dnqD*dYQ(cPw7uW81bf$rwf4ztw81TCT~Nt`3^NKh&6n> zuSaq0!$S)@jF^>yn98O)-8u~JS*h2rzuR&i&NJRd!h0OV+IGMrN8HBr$}e$ewxtjK zv`GegJn@tE=Jjc}tzih2wm$>d1Wnk=R>gv+^Wtv8A~@XyV0QV!b!q$?F!sX&?dA{u zJo<^Y9jUzqpaK9-QXXe@oVvM05DhlKMDT-XsA4av07{zHj#YO=lkSP+&LJJ$-kLp; zS#QC>v2NpR$|ZRs{1~rxGWV(APUTx}GGwe{G3DQJBugEPVKbP>OV1?i%1y)RvX%Ft z%KM&1hld1LWX9h+x&EuZHn^+2xVtG0@L@|h<Jc4Q7prNwwDlBh2gh?FW%R38>bfmS zK!hNNQGMp~v2XX6(D+WQ|C$4c@>cdc960dWu&+0XZ~yMLoCaH-5>lANi*z1rPUtd? z!~29t(5p-(hubvsL@b#T3LOp$TyW*E*f{laF<5;O5SU6olcd|cS0FcApP@?GX3c>k z@v$q%0!2t=I6c+{lJlYDkqIEgE-fZlvuX0O>7Bz@d#Gj<4N69C@B!p<1d4hpgRy)+ znQStK!h*4TK2SCVNQTpkc75izN#<=~^Kd$#^$*`Ky*mc~QJioKzq6Yn*;F1;D8}=( z8oR1}v($?=z~3ObZD}vtva6S|@He6`aH+R@RjQ`Xm01uzDAdsl(PX<4)85Hmr&ky) z`SXJkjRCF0Z>Vf-+P3n{>eoBe-l8s;?%C+lokyJcSns@^1FhXGg|rLaC|xryh`+p; zPT^!2QzR+heBkSbDsGlun;hnzQ*clYtNQYuk1+}MiUhj>>!izJ9P)-^x)KV`IjX2l z)Ly^PRXXs+y(11|yHPL}wYP~v<ZYx~rKHT|t~9XJhv7Xa>rZmXF^CYS3W}Ut>pk5E z%d@d>o9h+oTFF+w--qhCyl8f0Wki@vH66Ss$b581f>VyY_i*PllwxQDK_pI3IXE_p znw&8>_7k)AJ||)zNYkWf&@u{w`H@S`g(s9#`FiC(*-L6^VQK&e5nGNbfy{EO!0(80 z^~|lF_D$QqJPj3S;+hZqPtt5JF1L%a!>QtHp%MD1ih0&+(}U`(oPyk%)f$%zUhjVS z?p$-`%APGGhJSeMT2MslVX4&M*S=xm>owOeyd)@FnYRzEmye@}%=Hg3lzqxsTWT>{ zYEWkA5(dD<4FXQ?Iya}L1@4`MktrF(9_wXX?xTx3g?mCAC`tp~NT&mo3s^1}bIpRG z(vb)kYzXY$9#;l<OuhPhu%~Gbu~kz^jTbfhIY?Vo_=xckmaSxZ<(d2DR8abLb1U(( z(RcugvzFAzfQI??v+9mu2XB22E(|E$^DABU<8SySlilMXYy#Of7h6e}JvBIDTYn5K zd8hFpL*J?Fu3;>yfCw2Y0qNuhe!j&@znR08I_+Y@rlw`70DStiKUv0+x3+pt0bUEm z1W0YC3Ksu9Mgc{G4O@SpmrA5<5IH@8ZNEj;J45aJ#Nd!mHWpIfq-xy%QPd7@IK~-) ztS*;sKI==n@jH99HCF|EL^(iaz`gAwg|L}Ksv)I8%mU;kC?F|lOAwWA+L7SCtu`0~ zi(KNNS#d3y3yIZ1i7a4<-aiUhVzW}0{Rli+d*hKcY_L6^m{w?aBN1~fiVb1w;*RyE z!BNKAgFpST+IVUTI#6(X1#n^;mq(;mPp-G!p=>T=ud~u;=-MTfPEO;~aK^aFNm2OS zmZk_Epv&bJ*bgen*rz}y2Ivj8;;-}PV~k{~&rG&IEhmMr9@Jkztr|~;U{*`e3Fb)# z(Oyud#NZ%W`WvhL2pXwnLpF<ti-hS_gbGH~7~b+@^qjeInSE0X_R+?73fIe!I%1Q# z8`T)G4=f*npIBHLRDoU%*Mf!CaF5E(nt&HU(0xV*{%C#CoBRs%;8eix9{OR$_b_up zecb2Y2|yDh8=MR{!<?FvMwb(T<_R}Qo~Y;BvjWlTSh8e{Rt_jwt&L46hn1O3@vnRW z%b$2egy6+F&5R5`(g(@U4e9J7o4+@CZLc-QXi-uH2aU3p#<+l{-O+Y~Lx83rj5ayr zh_3><>=#WjNXhDktqVOQF3Q`wSP7veA7dWW<OHGPvBTPi6TmUaG`vItFotOt;t4f! zv*4(adU0Se!ErT_3gm5xR?+CC3{!6ze$>Xm+*U48Rl&+JYASyLQWg<^0|6-$G6v=N zKrRT}rFVP6cLXmFG6Arw421KP%B|_4%tnSixRr(gIwur9UEdAAz&pHgjiJI#3zku3 z{R@jHY6}BL<5<v!H`YR*6Y8;30s2js5R57B@22vct9d66H2{l7Z&Lu16z4;n*}bji z!BNn2;MJ-Lu?El!RJ|c)&FLd;ygouXaI6!)YHmlFvHun+h5b%=t$axPbpo`>WDpWy z<<8oKuyq>-w7A6&1DAF3nT3C-U4;ZyLWy>m2gMtHUd|_-t9}^-cKjgT+)EaQwat2` z5yHN~0NMGHehR(B>o&BWv}sfY17wiKCt^LQB<lHZ$X8)8QjyQ7dFBLx!<=oAL+JS> zN=;b|Pyv)(OdM}kI@Gx4&yOZRjn2@c+KLVuHzuT|fkl-x&D^r;GH)vI&qTv4=`bus zE+^jhQfK6;wY*~^U<aKRscEMEi`sYzgYR*Aag2=zzIur<m0v&}hDy1qGYnEW9K>HS zaS(YJ7XB0b1F|fSaR?SaVsAOf*&LN+dQ^yW%Hdei>=U{)AU?fLAPmppQ0iV86B4?7 zebACnA~jWaxCHb2i7c&Ue)x&%AdddEmYi=v49Myb{@=fu8g$!NBuxh|^%9GkDZ51B zn<zbWpCIM^!w28y9hy0V{tksq2`n;Jf^jTU_}iV7-)WMjEHG&|<{{Ld<!;tYd2C5C z59nfE6{*$V<d+y}fFmH$&1RqM2QPk+P-yc&Hvj$<Qx!inzXeC~@mjLD%D{b}S?gxb zCd89>v@%*COKn&KTJO#MIb(f*L!OT|0GX!pL_A&l%apKR=F6?a7<lA^XaAeBH75d7 zFoB@dW1kE$p;FKCr<+kb?>N&gV9R&h_tX6WFM|Ay%St`|oGvWW`z|QLR5s?0>dsX> ze=N%L+#O4|2_py7vgXFZp;j|>>ATHnS?Ih3h4qVr``F;{UO09M!mnZSQYwr7K4RXv zGh%R10f-LMQ049}&8et{TCGgAWhw?bDY#n$2eHd;*uN(E+w`**RNH=>MJoZdOy7V$ zPGXl?Zdr*{5v3sHTom^C<vLl3B4-P4_uK;7Mo)OQ9LRT1*)zLSeLK2@pK#Ok+UqoV zQ6;@unc(sAm&rax5Xt8G&rmX`UxT3;cP|5sP{XY%Z@ql%Jg#&w6W&L$d`OJ5X4^iH z$@%*r8p7&PwG<eb;u^LP3udqci}TVgw-#bp!Na*@edWHSqpyI;K`fW(Eps_y2CNQD zGsV5%{Q@wwT{ELODQ;sqJQqr_CK^zNd7om{%z$Y)P-JlBgW~7wS@$CgKSZok^FG@H zLEvJ6eUu?vsV8sf`|(9VA>IfR%+<bY$zZkZPM&$s`C}Kx^t2DT0OwljO_sKLrgy|N z3y>@#71N&hw>>rX0KsI)8GQ#}Oo-rqwAU^u^^^eU0kanBu}xOc+)Og|p6moX)XRq= z_R<(dlooHT0gq2YGsu+eUOG&3dvT7<Zkpl)M`#0~t~NK3PuRSQ(VUjXCq^3xe5S?= z<=-r8_Q+#f1DjW`17p^CQGksk%@SS?Oh)6d_AzarVyF=K*VsT@Ng!=))GNGye6Gs| z9v3y~2Ht_}^FSu^G~_lJ!IarSF);PeNCy;+oU#(J%`DH`5^XQAkO4~NeZ}Bup)mSf zF~1B$S6uUFp^D@;Y!?MWDDu{vG>?ru@g9RQ8gK~}+Tgaj7$o35b{&-sglgvB2bcue zYz3d<W))+P^6Z8ABT{-{oLsE>LJRm^8CLh$qM*L0Z*AIw><&Cth(92#26eCzG$Z7n zczx^8<PQ;cgGb;GFbcxeW2;7OZ5Xy!?J-F49D$&My8B>G;J^<^r>Hr(Cm=JsC%3c& zPDyKFx6)2DWOQYzJhBs=?hEBXmDyP5z!nE~02%(N0#nE1l&wwbKL@FT3KTg<*yC3} z;2f%QI^h#5Bh<qdrj9_@azO#~pu(#Z`T#5<d2Px<c-+UL`B;o;=?k8m%Ww8kf#y$; zbwAnx9el~_KwY~dnln^x?ojnY8HAydL;EHJyKvf8YlJT^lZ8JOx2OS4)!f@1>r)%> zcI7))+9ZSJMZja{-36q&Pf_n-{^ZtTmu>$CT}cxCDJV|zB#pTKtF7JzA>`+43@13H zKAb(KI>24j2JkgUHujv24RB|@##5P|4dSQrem)38#(lc2n4Q^OHWsZkW@+`)Y7pXy zw={qH4@WK-d*BPRgSVl8R51U*aLVq=VdRge8Q<PV5DhVjdQ=4SSiCw63EWoAmCNjA zvIO*vL*Wv*H1>?KB<A}>e4195wM7-pt;o=8&=;;$<pW_7Gd?x+K&lpIvuVVG%TQBN zV^{<A)dC7l_^s(0x2?0iV4e+<VuAb#6dVm;>-Yk!sZxER*|`&1kjE5GLXeXT`I7qj zuXiDBn>a-L5ghGljDMH!Z$`EQ9|vjM+V5tNBieeNbL^Zgp9Y!zj0cYh*>P@Z{%i?h zXjzN10}bifP*Njr97uJy07Uz%+F*snVn`j3yWZ@Fs)C6$q;T((x<+L3;qO72H3+ zp3+(m;jZIjM8EIHXn0-bci!3-0DeFDMys8)B3Dh(r!%OO@U2Usk8;ZW$s6-MkZrFl z;Cu4rd60FFZ;v>Ps;cs(S9o~6JutNwgkq<*zD>>dTbQPmBy$jZ(r{k!MaM<A(YRbj z{w`5H5V(E7Z&8o*l!rBdmA$mu3l<<`Arh;E3L+Q7&~=H@!FWDVTe>f_4?H<=dCnFT zd#l5m!P;tB8%@>Z`7{XlQDw`K8q2C?E)F<+?ptSbfx4WirUZ^vP|H>n=}af8m&yrd zyYA!ziqH;BHt(WSSDVx;crHtFfaTZ>-77FmrpnjO3lFIO)LuFOdi4upR0;9i0_Z$R zXF%~3h;LmsJs)(}-FCsoj0SmC0GT`<?-*lfZu`QT6f_#=s;q<v&qzJQftRfO3d%?{ zA!sRfH)>M@=_P8{Sd*pzwqhrA!Auq7`MLKFL)S5QWe`UKoa<{i)P7c3P$kI+@*QxH zI6H*azFk*TAN14(zA2tAaS)}i%~dL_F2ulRjKkHeoJC+K_N2|A!fVc(?3NVt)q{a; zMx`dvXWpK!gsJW<Iam5?5Gn6LN>~tN0L-ilY)HH2g@^S-!(PIIfGE<}(}^ue-LG4A z?)}b))J_o?`I79B1W*U3n{!JRCtICWU7Os0Et!>*OF3~#h-w0lm;ww0Y)@%^Ob8BI zps<zXb}YSyWCPWGg(ih!h8fAw!XD`A@qOFK1rU1Q!(VGLkT5b>TZ-F_ObjvDXLYp8 z`ci^S)Fe1sE_<$JtuB=<9js|3%e02fABTrZDD)_~(yxDh4M{nE%9pbv-WFU{>6dHG z`t?zykVNRo5a$Fz5UNZ|%wa|F3Q<$xC%3u_BuRetvU7PoGeh(-RLe~~^I~C%6qn+2 z(W^<?b+1(NZ#OAGXPC|>49za=Mp+MS$nGKC*%Y9qjj6!-1kgp)M-pHXW5fbfSKf#Q z1E6jT5?y+@5ToM_9?GlN2?|FTMV%LAMMKjw>mSJ+M&@#*WUtc^1_-0;MDQ005Ju{= z9I~Fl!}}V`DQnxB6@WZE5*4K~YHnu+dbz5(Xu$3&Felq(!biF7C&<;l0RYfioni_> z#7>;A0x&$6kqH!MnC{~K$JlhYw4jPiqGrVhm(C}53{ZsEdYvp>$RcZ!kWvmtWB=Kz zfW|j9;m{{|5cxqw_%q&-<=7Q6=gs_5=eoBz?Y~WaY!Bosat)?ry9Tj>5>%Tz)b*j} zxq=WXv2aE|2hYX<MV-<84Z77VY#){AG<>}AhS(SjOjqiGW8g7jJAiTwK`mX$z0_{x z2TADW2v8k@7V~s(=nU$zZvArykY@7obsGAe5`fuGVN$VH2l!&ET!SJlJLl~o`0S%T z>>7;X&O*3mJpfP`j3S{crG|KcId|Rb6J1h*VKdzQ(4}4YdJkBd#*w9dX#L9VPC6gu zs_426(LIWS3NPwzy^f}#=V#TNry%OVK_djCdl9OMVEglR#sRxfUHZt1RtLu^1VJ2f zx&e(s-mRdf(1%>U4Y}h>rY9UeJ}9wpL@u5c^R&uoyDsYvmFfoo3G{8N!&-)}Uz86G zD!h5=BZ~MjfX!spa4X>EL)j$N-`oa3&gFONj!->pYlLjTl|Bya*GonhEYcrBKn%Dq zm?SHIJ219&ey?{kQ&h31*JMLc|IXY4e@i&`$vEO(&W;FyCE7Bs-@@(ZHlR%}j^|37 zxP3R)aVY^;+Y8b(AT9%IGbJLpobNBLKATa3+WJcJB{kKj5`0<PBiyMz77obe3Lx@u z1WG-)aawM^jw+;u00WHB%NItdz^d5>tyW@N_aRe+WxQy}IfaT#MDgeSI%TKd?Tna( zOJMyG^5^NJQ$d=lYZLV~+hUoJ=P#6(4zxdn;LJo33V)35B7okb)xUpx|GF3a=qXT! z|Fii4Zz`$4%p$$=_I|@mBgkzD(EbMm*rPy-whg$#=r(0M!I;@ySN0)B^olqqgFtyk z{bpu^Tz<20kJ}g#>#}Nnd=5*<0Z@}ReFgte8SLy6l(Kz@QObL2Xc|io!HG|Vyt$*I z^@PDNb2bF@O=Tb*1d;@_)AV>Lk9mjA3u!cEj7{!h2ap}3+q+&+nfr$;^t!=SDx1z{ zy&Qp*59HyK4{030>#p?4Ncf3w`1~nx!M+1ESx<z}`Ro&@I4eq7e`tMZDsMc_f&%jZ z?EY{Ci~c`ab(Y`IRVfiJ{5Z*hq;b=%?vGs(!fxsLsI9zY`frDJ^QU#tclPb6tQm<o z0#r5sVS2s}gZ|<?P~(7lk9+dX8;T7qS}+B8S8CIf?c%CyF1|Y=y1RY}9ur}!>1viK zFxOo7An9vgKh<9gT%PN)Y~3r1n=Y6Samz|-Rw1XzHYIDcbz$WuMW4ao0??Car5l&w z7UAcI;KR@^3iQqxm^KBy2G_bMS7tW}eVe|OL52DhPUk^n-8Qc6kcv6cSNxD)(VzXl zM!U^ZG!TdRpCTHNW+4Uy85y3PW;S9E3f%^UC0S<R)NUl52~d6Yx)9fT2bA%E>;skg zzTdkZ0-0Vk?2PKxO8uFkbCF;_ifbSy9ea;Aw=WX{AFX2L-3%}~xFhRUdj{+A(#8%E z&!6Z0rhY)u6Yq}Y76Ih_Jr#<;2`U~>R6LO=cdXZuA1>7Fe6KTgKC}Sj2Hkrzr!l#8 z&ION)$nksv06Rw<72iK>t~XaEX%)(G%^L@9#Xe;{;Pbjax(mOzsTICTfEFR8Xc+P0 zB3l)tl!+=wL_lN!2~OuHzfS|(*k-+TkucOjt4X($1V)Jm{(R0cRQwR;F{lA*HS(SW z85`LRz?^zv;Y&RKvG3~-RSK*xp`H8m_iLKk@uYLyE*X~>kITCBU<80Mt}nJT07Aej z36bSZU!CO_pDrliK{@ZYM~nrIB?3Z>H#+mG-wlW^oT)hIIr9~2nSXh#Hvnt&VZbH} z1Emu`*lSnPQ)R={IlK`9Mcl2f-VNi*P5$SKYs(O#jR9i(P!7C)Qm*SItT(UMSqu|T zM}F|c>>zp`wZ@|pDX3koMhav-U>&J3xfJn~@%c8C7@%EF;@Ej`a4s~gA2)AI5UR`t z?){5*AX7(4&P~$56Dw#5s@|MsJx%C5eA#Lim;!J1F<DGz?H};Q_+h}qwKuLXv}9qT zLHRZvQg69tctC{`$S;nX_bd#x{jSAboyr>p9!(n8){rNDJ-bL+P+hrfv88?ig|5BZ z4u=B;8}>)XE~^-z!?(FA=S!Yq^zf76cbjb_v$Mre@!x;&Qu#yD)ut2AeCX$*=FSR# z`76K6lg#BkY@a{^eA()KasmXlU`~FygPRYy11p)%5)X6^-u$}c0b8-IJD`h$@Yqor znqM|%oL%>{iQeYJOBm{B8XNn4{7n=<G1bvn)<Q$>i+IrdH~TMLLN3y?B?&{ceaI$T z7}g-FDMa=*zg5cR=^K9c0B8p|DCkV*L(9wM$;y?hj7$V2Hhe2>Y7e468C+_|0;Q(; zH)2MRDhZJSzkOg8cXS>~e=<{KJYpGf(AZ#&1HW|qoCR}NrVMy2-5CHicOF944JEUj zoBdC*93z$J>kB9D*?FPMstG6-v*8iZ-BrOOtrURZ{@2J?R$+aDXCA3d3u@aN@}&Fn z<w@S`AK6vfvmKGSygIoboe}^(%6SnFJp?I)eLCYmolaj6-;yp>lmKI1nVPnNgSv`q zo)eHPQvEK%#zkyO8~D3m{ZeTss~+8e##yMap3rd?{;#0BHaS_n)H-SaA{(7k*AsR| zSO;&k1+hzU)`xl0r@Cz*#*NOP(>F*^b%i3tREpScsj}~BGJgL#Mt~<fc^HQg)IK_b zCfSoe)v~~w(lncD;vt2B%R5)53pZ+Q%bMWlzQpgoR?6Yjw+>vaLg_n>h6O-{g$l|H z@SMrN-QAfVUoh?b#T!H{qGXqNBF2J1)4z4VVM71Fe-3k>O;`Vwn(E1eK&i;Ydt8u3 zSuf$FAww~@V{DMDUOr#AU01c7bE^2)gQ#&7un?F;pgM9d!ya5(+w-|{=1ec7L8A^$ zRAUJ!ae>!9g-nMf7_5?OTeV98PUH+V9-X}_wYze#!d=aJYIAr*p`~L~y>M8ME%ly| zIAj^0VTax=+4a@{J_Lfk!g0S*Pu}G1Z@lpU>F|iawz@AAz>Rtov@Nz-xgQ6f6u5wS zRV85RS^dNLEq|rwXn-3bJ2ze9>Hr!Ge0;Z}sY+icRh$p!jWwgk-xH)lhp$1(!*8>v z4k8yPmEjbFWb-J-%uC0@$xLuBT^2K7oWK`1ckV_f=l(vHQxrcRqN~Z%m~T-)eEk;@ zN@W8tGYCf#p#+lCt}t}vPPQa$Re&Qg-`kV4`sP0Zltfw24_g?B24yW2IAPNr`pE^E zZBxp_1GxJ+WwRx$y$*VSz6qeVyKu>X<-ai<Fjq$W`U$Jd96~wnF4BpEd1Bo&$54qW z@LylTq$VLSv{)rTQs?kK;doo03y#~reviPd4b5nrH4b6FmZc`>L{bo+4xpC%(eZT8 zk(?LN!SjYNFIST%=Rf8dBQitV<StZv*SiG|<tri5>U+q)QBFa&Y%gZp;s>%WFa>N( z9i^ciTgo~k6PfkUZJq?W17ZL5Um3dmu=g%$>)4baSEX)f)dH3QCOLEXJ$O*UKHE@~ z{u(MZLel|IF0!b(XdGeQ4nsaXugW`~m%nyT1Eo(vyI0`n9j#xGj-Z<r`Ac;u{FVWq zVY@DT_3=)=td`r%g0MJ98j`jZKi;Qe0L)&Qs^W$7B9J+?Y9&K9r_{{`rokyXEY8F( zl$zHA!g~K51x#;*;X@hDt?U9vXwc4}b{<Uc`ftk|aT1WldWFlq;8c!!{T;MThvD9v zC)R2HHgX%<Gx0=>Ua!`ot(~X<$FA4c2S_SaectUTe_MlzI?9TLQ36}vp1J0YI*MH~ zMp-2QWVt<(K_(Y$+%BZbF(p&dX=tNjs0tE93V^2niMJ!1Ykw6~m4_ZT&i06@ljaca zW{WRO@}iualVl;ld?6d0+FdIwERVxHyOw6}m0i%vde}DTI)<lNHtA|TromPXgL|v3 z3Y7BVGHS|^&JG~H0ZR~sf$0Sxd#rb?yQi>*Dc>GA;n+dEq?w}#%+MVX6hT>!c{oH9 zg)oyM2<QO0H0=a#PhO$^YR|$)N9KHZ0S!r&g)Ut`y7$JHfABS7Y^W)(&624NVzOK{ zzz&v%Y(Txr&nu4HQOxDJ?9D+DcXJw7xX|{Ti0PLlU@VflB605%sW8w3`XUYj4K=~R z2$~l_SWwUbVdZDelwm41H1O#almX(xMSw4)%`FSs_Iw6jVRYsfs8rSyAuX~sQhmDM zX6Xl67~jz>*t($*0RMi_+XT$sQHKU-b66gH21~Aabe0MmzTQFk{04F>Cr^Nsz?*LQ z`phPUOq_*)tjqM?P9UJFFx6nt)?uHCM_rZ;C!S@Aqh=*YNIMKMj}85;yr}V0=cU{i z(J&oGnkPL;8M->-m+vS#ZmYe9cg4P1w7C>jdbDW{3>87<f-y=epnrGQ|5p@!&JP%f zRm<_=B0!;WD~bZ;;`rbTC)r!~uPLH?CVNp<dQ51Z(DXya6IPXQUtP3A*KiG@$<SNt z7-VD^qk#^!Lsr@vK9>mO$if3n>%2+Ou~nvqbn3p%Bp$t?1Lp;8Yz5(T8uGcP)U5|? zaAjjj(9sY2asVc2Mmg6Ns!ycDJ;hYbYOcy3GlkQ$r4H={4y!$$fD4Yq<VqqDQnEWG zH(wtvSGPS3i7~-}|A!GYt34PO!9EzXYsTZsP>LS^m)qn0$V6_Jm~E7Aiq~E1ZEdQq z3~1gVj=d*-d7D9Y0E=)TB`C+)AEjq0=SnvDmVENse1o+imFDsyQ$6k$^H@x5c<iV5 ziOkI89LcXs-w`hf)wb&YiHSdc{vH#j&UV{S4fVG?`Y{r6fG%L9*a;STNS8TVaX*i0 zZmTVwg0_TRM@^!<w*zCDu|53^M3@k*Tzjv#^zp1HYU>SKDq<Hra&9GZH@kjBInHHE zpRj;~J9qzVW6f=7JI0D`+tUL8ev>Ibo~TUUAd76%Nlf)TFCQKT;(f&rA+xFRGi9_= zvJK@B+tP^*B26{XU@u_+gC@`Me4zXalidt<So)fQ)OCZQy|i*MXCd*6$h--tdHiVK zO$;AfNjSS7U+!~)s}{yN1#esd0z`cj;aaPagh5vQ0S)x2_l2YHBqih%uPf}bT@@TK z%!UwtCx%Y4KHY4pxedk}x>@0?8qlN#@DXgX{vI?^`ot1s@S*{=MH-Nbo}I?`IBniZ zaNbtiCe<Lj6Wv{UaMh)7G#>5=@a(1R^JsuM+!mI&l!;5QPrCQhFF=YQ17DmfnGh}l z%@J;&>@-o<{n4NU;ekTv9(Py7NmbT;kRjGIepZ_2c&VKq-s9z?7r)5eNC*3^`CZMJ zpk_Zawnw;Hja3DJj_JJFrER&Tgqf`6LD$_nt1#Wiu|dLrsP~L$y0IAONbt*X8gLvz zl>DK3aL23_;aHlWc``>%<|}v#0?K*nKmY%^+4GJN^4ivHIIZ@a_Hij(9m9n52;^$H zf0rn{Nn_a-hMvKjl>Qr6PETynQcqOxDbfU`7U_q2?;6gEFo#SI%!h}s_I!odYH=)E z9Cays5xwdT15D2Rjw2_1hOsm!E;ZIf%Q<#};y6|X6}~RqxY?w}bD4@$D&y7J0ZshS zVewtA^mtSp{onR-)a=%=QRTklg31Q4JE3yG2CcSUIl05A$1!+2DD9TgKv*z_neWyi zO+t_FR2H!%g$Pq>NEV3@XP2){0Rc~Q1lY9lK2}?gN}@NMAoel)-1Nur#(5DQ7dzga zILQ>ONa!dJReiTGZgA@gu$X*Ycq0J+GT9;QVHvd$5x#QM8kFG?wlx6izJ3N{{*=={ z93Q^VOm&PO>8Y*!fS~y}eNX+p6I>zSUkf=3@uXV;W5J{R1T@`Sg+hBwEv0;O31*5& zan**nVc1DeJj5)iCbOKqGh)x_Eisgn{r@w8gvWa>p`#Ezo4xo=bF#eCVYgJ1Y@__f zuPFU8puB=|j|WJWFlb>~3ZAIa@yr+-#v<a61M*m>t2`8z*cHR#kpgkS`v3>YW5io; z(^-}7h+J)IN6%m6@uY9tukm~%8DUB5M@P^IvU8LYrNh`5N>qYM`l9ITH83e<8*HzF zN9Kg3VF&Ca;)BRxz&W_xD2Ps`?>WYkdphX=QfvR;AhP}8&h4mjXg+K_5eTk#_g_b+ zLCYFF0BYE&lh~Y`0KJGH&-R=ys1;EX6|lNdbtr%)1u(Ffxv@HG{{}*)+CN}5qotj) z<H?}bbeEdVM(r&w^b?JzSrC8+P!%M8Pyz>aF5orTl|ynzVL{%my_r2gav>3xdUau? z8-4zOBxH;CnzSGjFSC^&v6cmw=>Gt=P^!{gf@w*nqlw=0Rb2;ABYA>5_d)i>)3xMj zrZs*pP65SQ!XOlg!cXOy3@O3vkHWPnm{F7^`F!JlEN*QsjE7-)r;(0A_b%NP$b$`{ z1i<hsNdXIW#5glbf`hp+I$jEGOOM1ilM|MHh=^kCMdB(!K4mIgYW+$$r21`PX@@;3 zU&tN4++{Y(qeBXsd<vs|nyPL?*aKi70UfV-+V{kv?>`aQOwQ?TUsJq051_tFr5;RZ zR-EaDCK)KT$O9^1`nk7gk1m3rFk$G{6z@aA?~WjSATGfCy8%)Fu#6}KxQuGQgAaN8 z4|HdI&*#$_&}$SWx8_tbUmZ0MElBb<IhsM3M#s68IRTxetmm|E&f;F4^e65wuRxcm z*W^>EN5WtzB}j)iR$Jl7V5-ZTJGtU0)}}%kJ%l53GQag99QbBB<G@JxzW`eujw(R( z6Ctx5U`om*tq9e~<ZAG|-Q0AVifq}+Rp0FqEuG!*_e7Eh0-Z+!BD6w@+jM-*uI#bv zt=S7x=;@Mn2`eu`uZP`;|FVl0rb6AgEbm1u>$&ma+^BZ=0hIIj|1o-|&5Ny)9hq<i zz&PiXB*7I3^ffLVqii)37JaH8x$&gGg>H@PuEwgJQ1}K<_FwtBsc~=GIbi*3E}7`U zNs15UIwb9k*vHy~#Lbr;gfEUu9<k4gMkc0!7h2eU_$jV(t{fzz)}k&OIQoCcy7G9c zv-f{cnx@5frYV!c%!HJ!EG;5;rm04Vii9j>jZl{C+?i%-row~>;g%Lz3zzJgin*3Z zmh38ICu{cK`!IdKncpAZ*LSM>x%Yg|d7kGy%lmmhtD9j*B2N7=)}=sW*N!G-(El{p zT;C=Yx=R-l+A&&t@2q4#xVEMzI3SDy00YgHML8GHHjt_Knw}jft57Q><_gx@756kV zY6c2bkSz+@JJttSwy0w}NJHw+09#Pxy_}Kf&DmBTgMSU#0ROJWwalZ)Yx*+(I^eR* z<w3WCRPlT;&d`hS@qSxd=>qWCJF7n0QY67K$>3(F_NEzRB+UW^1V|X(H*U@$T(^H> zxiE@XMeiOK2B4R2OaONcBQ!xrGgF4s{OI;7QmuA4P=W-NGr0hn4fa^Uf1HIqSsc9| zdDfu$u5%?aaJud)!n3o;)NAcCM6fC1^EO}`iI+G+#P1urXw=o=&F>$9F2>&aw<Tok z49ekRp&<4yg6gWxAEP~VORKJVJ#C&I+nC-Ii{DfDulq;qT%|4Flh6Br^Ial5EQp3= zy|6m+z)y*bbsKEs4K={uS${@!D5RkC-o}rS^QAZ=P3i0h4L0RehAohRPt8WqRK3Yf zI0^-va)Gp3v5c-*lrGGA+0fRl*QUS5_+|0gu|-JF(qD+gFZ^26R67eslD3dIWcDuU zOQCrN5G!!7bk^&Nfi?p9u%UK{Wb}S~An1rAI*C-JICX=Im|t%8;K>&ap<zEB4Fd-K zW9S2l0;s~)?BT<MHF?-$KcFg-^>2lYhL~~Zw}>dCp-c27xJmk4LOfQO3nzSrtja=L z7pW6|V%VzGnP?sK>f%`2k8PJy<;l-X#;*dMaoad=(1@bW_VVA~K>kFR$A{xv(3!{) zgFHKGM1)m#x8=;B>LZn|4bixl-k4Q`P10d^C${Cz_{_+;4Igq|L}n-q?SGj|C%=or z2kmk*rhM*l6Z6U-UZ!*FXGAph<v~0I&wmeT8TrJM`^kOy&!wQSt4nn-o<%j_TM`rq z9V!pNXMn_Suo0gO*@Pasu4Wa=XtQ<0TDg5f#MQ!3pxz#F5|x782yR%Sm<Lr6r|Xt9 z3pd0j@z8$K()%ytWqRDre6T_d@3h-%Dg(DoYE%963N_q}Y3sRIW<|S;&=0KEUIsld z;Vg`LSzb9{hl9B`IuEysb*0)pEAO?`IvnDdNdKQ3i0kkDS`=$r{Er;}F9%tcXul8F zdC-d%_rc(IAF>-4F%^={j=<>N>ZlN1s}MUcd6NGBih0XVha5lL*WG+#iO)XV;Z7Q| z9SJiR(kEYfNv-8(a8O4?K!Ufuc`=~g?g&;q{?UzVdeurB`nPUCQWoc1B(X8vZf=|G zGcF=QOk(f$Y?vyV;YE9^(wq&usvZ#*pOpxHLqB)rjNuB0q{kHdvyX1;ouKL0jkNJE z=VNwJa_p%HQF^lNGOW6Iv_f;2N+lJF!*QbZ_Qp-!Nq^O^B8-PLwszK*wLB>6?ODN& zL!$g#$ngr-V5zu>bJm_A*pNTyDa&qalwZ4rm0jb5ND_h71~f7*2xX5^QXyb0-I9FR zRRO-+4nq+6;VzfUf;AaBU26`Vc8Z_3S{rU$q-7#C9HYe8$Sy3f$f6dS<~w`(_bI~q z5fso6vemjV-=f=apTC{@hOmE}wFj-zdD#s$#_iFIxL69Xb4|m`qte63mMy9-<6MkC zPNy5XCFK}tCT1R3fhL-l^Y@jrE)b_k%*t}L^ctSs`!+N!9vsChxLC)5z%B~kU6=bz z#cE201aSZou(|fZzaLeiNcRJR_==i;yKXI|An$OP?1gxa)M$c{M-boB<RsUUAV8{v z)`2H8<`6U}*<XDTqOy-(+uf-sHW^T#v_ME1Zd>^uQe+;A&;N<f|5Phno%HAjtoM!g z^UP07*hu6lYD#ODyQR$1Z|5yuL6qWMe#hYt9#B|BF2()3dNVtio_B3>V(zKzPNQ7x zY|Jl6fV$+S;OHiM(a8)8qUHIClcuoyB3J$fQ1vzHG<7$_*d%D+g_MF%ap(UhgNvbh zI$1TN`3H$1y*~)UMqoinMyt=V-g`RW=AJ6N&J#FVTBkth+}ABLNk7~zuuYQkq12`B zJ0eCB;(E%`%xCR@At|m9<kGUA8u{6QhZ!I~spkdxWhSpvR?K)`P@v7EX*-5QW>2z% zu}^KD0lM2{A4OI+b*Q+eEG7LvRp{6etuGDi4V!GD<Tko>e2a}JN-{`C!qV)2sk>B~ z*6qtm%C13M2?;{D8JGRSXTV|(Y0uCb?ydh?+2E+Ial!>O-wpRiAz2DU<(0J?_?Slz zw_7%7Y`679BSz4(Keta@rux+boWdy^iT)AIs6;%Yu+U^#EjITqu(eTZ8b7G8h&gyH zmY4bMAU8AM>z&CBHB^^lGUsB;ZxM8?-Ju1p+H<(1a%U3K4(}o2{@Xq8H!p&gW=W+> z_OtVb4<j1}b<sBdh<NgS%QZ24btz+YqpK-^a}YhLG%Y;I2MyCwHMb*=W+u=hj;+w& z-`@nUlhd2N@omAiN%4)us2{Wp8pIsLB&5g;F!ZlQWQh3B;KOpRk?J1_@sj@zF`0nU zj!^mgXV+JNx{Zf*plB*S>oM=$N>JoRN;Y@MV2l(~cHP$R;EZ{onKurKS@*Hv&2I3G zAlh&B(i14V72e;$wQ$|c*<5)aIl!9Cg-h?@msuy!JA_2xKUEJ#=rymp2x_7wd2?SF zdm7Tp_*FAeH!kMI!E+-6PdMlKaK+@uf$@MMYveY9I<K=%M~s*|HGBD5HYHb3H()CO zr?X{wANH*xykhq`%ZO4?dR$!6Q3}$N{nW40+HD@`O4m_zE7*)ZJ9XK<l9*79{~qwe zf^{Kd6ST|J)dnP*sOg{^^vT6yo_sm`LbN3oXbTY5a-;NgdXPm^jHUzw*mL1o8~{^v z>w@GOYk5%vDbMi$!;1hnW9Razvb^#%5ua5=i-9>VO*Y2aJ2Kwy!!aE@{7J<kWVmP3 zD&}{S?^FWabFyptzlJC*qh7vj(bN|28bexp!CqASze4m^FF?D~=z`T#jn}inAr*-a zJ^7$Ad*2%=2h)$nXFg=ytKH6Vp|UI)9K)4Q)zt}$7<dh73b%tCGA)4noyY$KfWT`B z<7!X_mq|*gdMFpufrg5kPb9EGTwClhEr!jP*Zfzdf%%=dBM+lFm(%hX!u!k;il_b8 zgA)*QS)ZlcaKt~cI&+~ROYU0ExL2SCtl_18XOBso$%9yoy4>TM<<|0ZD`z9@3M*&1 zKoHP$(9WKR7nO)&v8lhP|8D&zSeas!Wdx4m{0HnQ&eJ$R$bp;iz3xTyhO+o%iR+8K z$O|;RSv0Ub`?QZA8hUHuBB$!z9?$ON%10Hk<=bp8+8SE^5O<6xZ#a^bRk|u9^5>90 z>79Am$D{lum9isoqI)g|&uubs%KikF86d>V;CAKpUKf74;=@W%KPXatw6;kMjem5~ zEvXrM3~-RoEYgQ&1y{^UzIG$2>9X6&aCgXZf!h~gi0N@u7AgJGfB#;<5@Br|!t=BR zg;`ckgQF($gE%COZScv&chCKVx%Y?2okymDGd2q`gy;Vg7)N<m1}$NvRCROW_3&xg zyk8Od?x8M?eyDIR1cZF0`7e9Iw0sTb3ZlhpUrQc@d6N7mE?M9`9^pZ|*}R)mZ?6dq zmOL-iz3eZ#*yZXFGMXZ|xd(E9zaf|G2U@^(Xf`nnI*wMZ2iZmNvr7v9g90pd)$b^{ zo|*H2jIk7k+dQTboj%Dl8as9rw(HobJ7z5m*o~8Pw`pOzEobyz<N+Zs?G*q5S$s4c zk}h(a8J3h`HRBzu5US|d!P9i<mpsw-R&4-l-`9(y9|YuWVa`eNzI)D#pLCAWH|RL( z(K|cjLnTHxV09}Z)uZjaOj&=WMKDtN%+_C51sK?!Qs)H}uk5M$j5IgV-xK9t@wRIA z9UErmZ!~cymKcsv70@nRkbH_6lzT|iQ@j#+6ZUmY9WvZRC^P&yEIYGnEv`2((hjzo zja0R94IoVpM1vYVor=uv;ITRl{5j_C91Q9cTw%V?X2&DhR2X^aIVmDn4C2?e>`}`| z)%op-jR#BlE7WfKegbFj{VFr=f!HOqu|w(|d#pPoJ~LK;oAGeOXQY{0hh%Zb!f8R5 z7-7`)P?)4teKv`z`+NhIFaj#OeN{J^vzy$h@><Ya^I+&Y*gH{l4HR1}%%Wo-(fxN} z1(|NcJEqk?yRowi%6MrmUtUmKvudN5*JRNmD?@YdZPG((z()$uSXn4-7u76gb#<KX zf6Mn3cP^_ltB<r>no^V3K|gorEkGK`Od%x=!Gh-aE4Ol`2Ft3fqdBL&)Q%sxSJF62 z#m_YXJbdVl1omiK?T<nXn@2;>2aU4mp3Q0FW_0&YPlgQDy?d@9<Z~Pn<(jh~v0a58 zMD$Mt57p@!Ds;Zw54Z5qj6Kiy^lTmr*aVH$<xKMdSKRpzE^ilj?>b-X5o0Jo)#i-v z#pP5n6Sv>MFyP~a#i~9IZz~atWQxA{v?R#1-aI7d5NdpoFxR-=avy0<6;;i7pVU4^ zm=}B6sqp7lEJWXFQ|F!KHJq@K-D<pwOtkx;6TRizto!XHhg49*Y?96rL8AuZ_~q-x z`B(x)Q&*0+cm7JWybJ?}56o1Na!oPt-J9>WEQ1u1V4Y75z}F^AgBrkyIwyS0TixMP zllTxTrl`T(r#}bhnLkKr@3wrKF+B?J3VCYATC|(EB^hFCzB0^vicgDQOx{K$^@EfK zf1f}kzyAD^jR4f5D_~d8H+(ElA<r%MYl_`^VSAakCewN6vavDN>;nD>b4r8%%VG!{ z!_jvR=={+r6q(LU0f&Ld#9%RA?1U&DI03GD00Z25`IK0=+ESyuMpc0iJ*c#`(=uOF z>&vlb<LBLdRT68rFs2FX(VP%zwXgJm*1+U~kSE%Yf{+UQH8am$TiT+Rk#ikiySE*3 zCjOgj)I;6sbO@7p6c-N&3xBf9xU+RF$xm?@I)#OQ=ZF_|ePm~e<)B%60n`oO6k0SD zz)iHHHel~S`m(v7t@43IHv^H&r+MaQhhSzNpT~5#OT~2OenZb03>KAi#Uu0Qf~y|T zc2IOL8i>zUlGo-!<8J*Tf|qD{ojFcThSs75nG_&~Swlf?+)OI&$v)wve7_L=?EUsK zO*9|ieh~LtSUih(`(l5fuF<A<?4-0Xz=F<>9He4GD`Q$n;k&l{d>f-uVAU-bXgs4( zKYIsEuY*w*wQy!$Us8K?nysJQ6$^88Q5Y^4xiqck@!xV;(kfZ5vJ(5bSEVa!p<8_m z&Fz`a67Se5qt~%T3j&cjm%z%$U&RO~Su59BqluHePEYbT-S+1b$=;zDmh-O&q&Kca zW9TlV<PcDK^m8dAxStUBe=K@Q)taqxBaxxAM?&-B2-Dg|9=Ig^0HZ-d_^LFhOANR6 z)5xPGbR1kA(1HmO-=##N)i@B}$5&NK3o^{)kJ#LgHh^hqu^+#FBFdyoH9d{CSw5rR zNfeD2<Z*+W-};BltJ!zmjlr|Z$d`(<WL>Igm6&A>z!pd|5YU@F{mhLdjF+5-Y<{ID zOwavhrkB$DU+G?HfIz3=%pSBvgVa=sc}KT>UY-7S--VA~I-SpSFdADXBR_+7`PkU` zG~Il9!}A%h<Zozp3o+V^W6w^WJ9f+iP+N&Zmq^fl-n|V)4(8DpbD%1Own$$47El`W z4{zJD`(~!AR+7)BenmIw;+=%?{WUfhE8h4dtkrzf@ZUndeNueskoj~Xy)nzukoN)@ zvMR}VXVT;!8!ORiRxQ7v5*5>wk}@Z^t0CEx-o~iO@y94{eMqg9?SH|rZtFaL9tB|# z7J`sP+OQJ(!=d^UFY8jNG8vDpVSJKu-DsFqwCF<!tWu@5FBF#bJN@z#&|{qb*IT~X z`TAFoF$x62v6$&;$jifCY^6&67mX)Km8_E$%#f_DzO3gXC05n7@!E`%W<A7+^b7c2 z_Ojwlms3iFD?$+sPDgMSs5}fZkJ3Z8%3WO-F#IPPm9E<X6@ar#_@^}3cx}DQ@rswb z6w4i`4wq8T7#~_fuP+m{CI_xZZ#g=N(Dcm?Q)lS}xw#w7&cFaatEB$hSJ$vhfMhK| z_%n~1;9JT)@rulgo#(XL(-AeW<ZRKa0$M--`|50pd1B*B%AU_j^HJ)G?X60;cT1WE z`Vt}%JP#CVIA;LNN=DQQ=5}4Quuo3_ZTA&T>}eg#@dz2p<d-@S>0^W=F>wRm&w@*v zb)fyZ#u^!EI2v7dyor{&a(#ePAP;(*KbHs^%0#>A{Tez8Gt7L!J+AZ6^=$M~)|L4~ zr?;Na-SA?#<6wkdptg+F*#kf9Ov;Lvks^$dxkK7)!;=p}2zVsDr#?+)YFU_jUD$?0 zxO4y;hP_P=F*>{pn`YmbNefXamgtfeQFi}(k<uQQ_0)8|+PIji*NIFpEn^G)UVeH} z=gT=2&TSYy*U#hP-*wXCXZoctaLtwWq$eeNTXK;q>X3Y1Ouha5in;|wmB^d*U@S)L z(^wbabZt&H1gLDV9RF1|W?FW%QxV)-RR!T5*wT2MN(bv}_c2t_e*#1P+U<!iz9z00 z?B+anZF-~rU~Qx3J}mx@P0a}N<XRfSe^f?%Ut9WLpQ|R^#mOk)yYkOF1zsDO`hn&m zu5JhfQvd+l)rPu4Eo<FtuZ=%jOk%dtjrqx3=nj1!a>@3i<2{E{bh<06`NDBI#PLvi znusCQ?IZ2d?l6B~v09KttkV4z-??d|2SKQo5`FQj5DN8>FK6FRq3I8YXTJ5**gW!P zh&I2$GQ`NXwyM?+(6bjBDQ9j{#jag07)EL2k(A(8+9R`;E)ZBh)w)(l&8={nutm*( zF~3Oo>44*IcO+RGr1Z6$Y-sj-ulAg1-3pH@F*d>)<J<-wQ+8~xF^#@FWBS${DpKD$ zV7tFM3|L>zIea^=By_}Yj0ifNeb(Xm&Ozq$H@LT08FiTj^jQAWs6KZruR!At`f#&$ zbv<mfxo&$1KVfCk(CCLbJ0f5IQgh=)!Qh_Mz*3)q<vCOIpT3KwmlT#w-G1_wTF%^< zf#DZ(JoA@8BLYu|Iy*`~Zr2OV%f=J2p%|LdH`^6w&b8N!s{3&qJKY}Yo`cZ{GaSI| znpRiTt^&ajHq45J@`9@yr%0E{G<SZy7(-J68+`I4=RIqA-O~B@m=`Zcr4J@_#6s>Q z7+K58do`)KIb>i!b%O6j>1`?piq+`wP~9j>xoaK$l&Ef;p?h{HT!mhWgcbk4&A=Z| zsU;sn$Ih001Z%W!>%12TeztRUoOYEUz0jze{L<Pg7&P<DFtCz}P&EAp*9{}?l)@RU zfSS8E@rm!Eb<$GyBO9oSCnoY?%cvd1l!Uc%0&i!Ma`o3}tu|EiJdi}Mk6Z2wmK$c% zl--Kn#)LF*_UX_-f|W5tlYm+xh&9EB>T&mK$@3rolTJID9xLXK{d;cbrR=D$O-~pm z6|S$xM{&Ve79Hv*vNG1tix#I7f)653zEDtN<?t$2iJ2Sl@E#|}IfG&D*mez#*C2W* z;@02$-?tZn1;A$l=RR|1X!s@P#6|RYm<<W*`{HqCyFPUQTz|KdrV{zU44k$la)HtQ z2O~WX3zc7@E|EN40~Tv$?hQUX5ZPQ=>w9)q*H~(ql{t={*&h4pT3sE7+C|YJe42f& z)Q4t%7%5VHVNatf5KCmbxsT#Va0s95iTJEsDt<ux)-zkxHAP(HbBmd&K_ibOBdWbg zBQ57S=zN&$15s+sbGhMSnK;N@*)M4#%UW@!a`r}UNnXFLrngLh+2daTPo!PNV09MC z@4l9S<Uq3_Vf+^LLnhVVugcV9(B5@8?pl8xO;Rwx6a8_|Vw&WJ-#lxJH2REG0f#_b z|Fn~(2B*9z(p!--uHTD56GN5P1@95qFP0V6vcJN%%XHSJc-ACfUzkU=lQx1*WWATu zU~AwYiJe|<i>%*QRdlV8KioWXHYC9ULFQy{;2u7lc51==%g6pUr)@7wb*fQf2gH1L zim;C-Wt*zfE#|R`u(YD)XwDM<1!#bSQ93k_r2eu@`X@|aYn_=i=SppobJEL8LHuuE zg7trO4abW)O6XAlE2UYp7f41DCYRgJ{S!UVEsbVWabs?QCkd_V+v+93?1#{IbbduW zftRUxJ#liJx8$yJ=)S`MFj>dDlBBHs4|-(MRJfiv1tvVYRR^ze1%_&@KX(jFN@$1~ zeQ{3{-WY)VE6Jr(-`9vF{mn&`)VFKod8;M_S9a52Y$u}HP)o7>!VzF=RAP?4s16?P zHF$1AkzSXq!KgOwfMH1P@1Hpsxr9jH#cH*T<Sax`<Xi6^Q|iO=8tJXK;uha9w>L8B zqHcQdG8tvN4UqjjC5qG%>o`bd=|~Jr^683e<4Vn&!OZDiu!c|{LdM0HzQx^lkrMsv zq}0P;Y?@ou@iX5KhL(L;zRW2e3ACW`UBDQoh0p`Pg>ErI>S~n7L_GQ<da{xr;weKF z;zKTLh?edA5%w}5X=HDj$@dNwsvNKz_)G*U&G?M=DIJfC?GgSkwv+WnsZA-E=jMeI z7=rL@tK98DXX~d$0qX-QikeO{EhN+)s;fFOUfyu{=7z$avqyh8doXA{uucQtyd!1( z{t@=px0YMFj(YEo>V8vn{>-bfv8Do}GJOxJ{8w%#s8JWF>WNgfDCQypTA}<T$Zg#o zc<HuiB5gSjtg;!`*Slmk!(m}WCu5~wtGjUH52V}*xM{?jyes9$`S1x$WFV|VrCn?B z|M@1F(R%m{BRZxgaDK*F=r#=@=0nH*)I7__>Pq2X&v*KzzqvHA1U_>3j)$-Me5PFW zh;QLaJXc7=TKQp<Oe2|h?0Tv!eU){9&4E|Keo%PJxjrix4Vk=nA5wn32Vb?q26HFS z6x9|i(M+QV5Ji+LqvI24r%>6wYZ#^y-Q3Kza+mhvyElZH!%$e6nKvG`AE_FF@1LlE zxW!6K*7X6$hqb=?SoDn=!}bk8u1px&s>&h~DQ6XB;w}+B34z+(+spa;swz^+uO1GP zqUiX`@9;9reWTW8A_)*bUi$6idLG*2&+{D=(+F5>($k<*G@-Yc$Y0de4=4%ZA@VVu zFN@t8i)ErDQ?Agpx|MN|-RD^nHnpT^mz|M&Ct_Ja?B~^-ih$PD2yD)~w}&EgWQecs zuo`j45SR)CMAq8x6&Pz1GAv-zy0oWxm<0+iv3=!#7iAL@Uxi^l_=R#neVZ_V$C0%S zQ(`(u4BZ;HjH*&0R>wg~L(X>{JV`}V@FrUUvtKZQ0c{#W422-KlH=_mtC43k>I^kI z8sMg?kA5lMD!~HC3t3YGK7B+4NACX39TP0uPfYL~^08_96-|fLx>WVs<~|x*B5Z-h z<k0l*iUYiS+s65{kA(7<QtK~JCBgA&nuyv293+D-gcX{aPPwblaBW&x#1kV;WlXUD znn>sPgbZEhN>jSdWv2fVOYJCZ5!26-o~A39s8iY3hU%zbnS77_c6evY*INw=TM3y9 z%9Vv(%btQ%FihQ_a1$!a8^9tCJx0u#lZF!*7%j^L?gT6@(fd{#^T=OL_6wz{HQ&<~ zC+YWlnk(~~V5E({24aB^_Q)Xu28ce3mv=clR?<i{JXb`jK~`7?x4}DH$Xg!u{fLr6 zTw<&bAyfNpY=F|I5Pa`!C1!gn&?AS{u-INK*0+bgW50|A8l42RLv?GC@^ZIl!pxvk z0@UrY=9eUy?P*Q!@B!!6?Xh#bY$cH?<{`@ycO4I<g>#lEO|l6DEf+dHD9q|HHJ|_B zrtvavW(baLMHgoytk@d-ntames9EdZx9ywFdXBk~Y>Vj;{EMd2O=!|1qk}V!i<ngI z4qY3U+5|-=VK0FddrT8&9B$SDd~*i2&g02Sf-hr4Pt@cU{qwycpjAWx{bwGH86NGk z@yFZC#HsKcm`UMb(tDvL^DVUq<?G=vU}hVp7hw41lKlEjLthY_KXc!Fx(gt36yolL zNH?<$@Nq4#RU6mTPx}kYEMso=zXslBG%Zj$AGxrBP{sGfRHaDQTQ_QQpDO~*=KdMc z+MAF7oSEwEjMyyKn#wQLL~P;@J+~PfRUz`3+a<{Z*f9CzWFemN6wBoHFqj`L1=9i% zq^_P^+-PfyyuX&Yk+^aDhR?ZJJF{=Rg48Wf-c`@Lt^<i_osS#cqoKvz`w%$PoO9If zy{Mi0SXO?)x^8@|mi?(Ln;f!DR&`5igxFg-$%v|!`S`qd2yhPwJ5KVy$=YL^dKCf~ zRQt(BhZg>Q&YO$5Z`-}#RI%A6E>>`fn(Ps2V)AF>Ye~Gd_`Oed`|AzVKnm2)_I_v{ za#k7tA)w8ihw#=47SW)u?J%5-xk;Q<|H+lB%jR}_+cew9$}KKx5q-HUi|Gfq{$nV9 z=lC$6Yqbj!B<W@e>nuICJ7^pk{A(FRTt`>qsF5BY?h3t6g_<Zn0XzZJg3oPy`ZC`1 ze-0U%dvNIQ4cMbBe207@keis4^!iC)+BP@h6s)mrQ$IUoTy80`T1myJb80ARem{vo z89D-3-=kXh+?jXFHgB+gLfj_vq<)>dIC`J!w#%(VpW@vdW%P8Q=W^{XOhqjy=#MXv z5k;u-AunSlGHH^--^xvR>m&Zc)-wyX*c}JaBj{I?$(C(KXXf^S8jmfn`hK8SafcCd z!ynBsKH*A#^ojq3(d5NQ`;kDxe0=U=ozszN9@4QAK><+waG$9GVC|YeVC;8!$Bcbq zGi<Qd8q{j#EP{}^H&%A5$r?tvP(tIz_2pmxxqR(OsA(3=HpsrQrToUjW1kWE-Bgf* zFb6fers<wPM8fH}>S~ZB-bRgX%GHg?|5CaQen&mhVMY0Pp)DPKC(UOd&MZklh}Rgc z6pl<~V=;$-kcPC4&Z5{OlRe5u-y-xF-Y?&PR`}(`X)G?g7u)qzkY2W0yt6rk6`Xqa z1ExpC$|hyX4AtYWePb8wt#53}vU!c`YtxsVWXrhcb{g*vG2>aw#W+v>0+NJw=A9o< zZWGZXU$1fzqDCm%qi@7GHM=oy5fLbP!iPL#jlP7Q`D$fUnU8<6+v4Y|bTxsj88pg+ zES7gb$e9)HXmH2GM2~5&i4K&QZ^2@tID*>Wq3BCVTrkPIzw8H2>t0m#9%6NxzUC?T zhdg{iV-v+w7|_gc3EQ!C?UrxWqN4}i2Qfd~-aFeX&qK`Vb%n~~l4i&NIkw}Y!2p%W zAQ9hya{vwag`G2z_G2<!OkSti>3QQzBp*S9(P({F{pw<%1l%M&o4sH_d|{ALS~WNF z%6qm3e;zpa$(5(I*u060o|pJ}OXJz!@OmZ#jn38B2GbST23Ls%{zS|#&NUiG4H8gk z$1NhxLpSlhMkpw?4mEZA%ubH{2ScLJz<_2m<fAN}qpGSi+^ySW;{*Kf{d<ryfhhch zmNf*FE*s@-9BcWDkG|S)uqb2oHMiwG{i3MjQ;8cc@?6fO2H@GP=bvnEOr&i8FUTJO zW|CUt^I~YkxH2H5o6{qr##u@PuG0tF6waQc3iaWAGV(UE{y!H_)xx*&*R@vd?)JIW zCguG?4n^<t0|!&y7l0wZ>s#am^$kGX0OB896Y}TLeT}J)z`@Ypd0e@D_Q&F1C`$b? ze9&BdDZn22&@Cxzp$-tMtfy;5wHP1MqCClXE3Y(0ZlK`1#)>Qdr2|R~V_?oJ32n>q zF2ur0Pok8GydG-5MPEU20>|?5vTJ54)R4`<BII*t!|Kx^vO$M(B2mFcF&xLk>Z+>; z+r{Mw{{laNeB|n^;|7f;Rb~!(j>2{+?#DrxH#h?uNbNYQsK8eu(&fSmiLpctBH#aN ztS@T>P}kR?+h>2J*PTgrw{4nUMO=swHxjpA?lgA`%<-uJB@(8WWQ2xztd+ikB#oG_ zZ9VY=T)00(F*B)V4`B&(jhGF0oV;aODQ+OfqqUyvWs0R+QeH_EJVD2^aE-MK5K-9L z;(Tq>Uf_W#G^POZ1`sm6RmpFB-Vd^Cc4I_kvlp(!wg-%5^LXI$>fi<+pMPGs<l|Wc zzJCi)s&hd$CTgXocLP27gd09xk|JbQHj1O$p8hSO_H7@JA;ZOtn^tzX7@;aQ_y<`T zPKx~uXI7tRsnrE!e<MS-i1%wF>xBX@UcYVYKi>5j_%cesvBV_=pH4kAzX<Wmr`WA| zX}zaH%Vf?iM|2V7lBh3L>uv+p5HR-e07|<e-Cp(_CIGv7m^Z(FP8I3$eVAjZWkZ*t zoa=+i!gT?-7z+L!d-L^n>CPINwc8{^cVc9rwnsczjzJ7a_h6vJV7hf`4N=-e5k}}B z0BcEhjqzyucZh()b}NIjXYGzHq4KIiprbrCcJrd7D-Uzf^-Y3z1^vw|S^LuT(rv10 zm+?xz(MFmmpdtUc&v$b!FLQBKMF1G@n!3}c0o(b5Dxj*l&<%u$=xN1|%U?jd0?K#a z?g=>c5+kYq0)MpLiQfRM{B<c^+xuY0i6H|r@M|_<iP+`kP!M6t7hFthD_f0ySHzoM zt%p-iBr|Q(rN%~@&xH#CjZPht&7Ghi%y0zQ0oIQ+o+`Qi#<ujI`?WmVv;lRMU47*g zi$(2-XzBH<bEluzV-Im|lV5Ny9bbcM6B+m>kHAnln3*Eeo9Bq@wm;1KahIhx9B#7d zuivhBN)!i^xo_@Z-Y=g!myR8nI(a%x54fZDaQfieDv`3hsyx@?#RMFi0rx4C?$Jm! zax`jcvL;OIUxYn6c#ZSSUTv}8!Cn5Icco;(BXgbhvNeP?uB4~dWj`X*)OYIXR_x;% zSkG@%(F6}m@LX<Ak+yD?qk6O@%}c_3_B0m>EB1Uh*lfp@A2|0*{ka(C@z0h-pnvL< zgCeH&v!ib=bN29cLdXlhlbZ7egPvu(0h0!#lFGo(X5(!mUtnM@{LaGV6b&0L#)r|v zmn6g}NUA$;r20(?(m9gtld^f#Q{G(UdDIvLUupHQEDt^`RH+&ibvj?5l4?KcgI3JF z`oskOhIpW?4Ec%n=Sb!*aEVORvsgtlq1;41YOf-<$)yIsO^b5%sdX|rr16i7e?$HK zSzc0*y6ldTrd^f>9{`?0QO$W)dO^*Lh&;<ON^19%bX-FE!C)hOVJ_z4nx;tLT84YW zuLyz+AhS@mSj4~<a7rIwRmR`pU5m`Gt|96gvIggf)_3Mji%ss|37E4gCXX?Cl){15 z1()1Fpq5#7u3l748oMwj<{CC&j?DAf**tg5P3>^USzL`yM>3L{ad7#&&_er(PJLZ0 zow&}v60#VQ-pI)_KkF@rL3HS7YpBV4c@RpF&2~qhgCeT>z%ze951cuFbMtKCMWQ8r zq^ZwX9|EpJ`EP9X0prC?!nWX-`@z-r(eP~=OIb;(Ql<vmc0af4s%*V^5#;_w&u}r9 z%Xf`9*f*xP27t}68wk@mrTPiod<Fgh`ubYliwF=67VjUKo$fzZ#l;+ah@T2OP@CRY zZ@V4RP8*K?=eC+GuU~tbwnmsi|KwLwUqLo}fm<b?#^>{>Vg63L3R6(7jUtJW=fmt8 zcn4^6>)Jr%!xuzh<sYgIx7RwoggpQk@NLxzn1qyL%Shx;vYLPqedqWN^KXELsNPSk z#85E;YUIfUQ6z48m=3SJ#sEjg0FsiS_pm)BH$*h^8f9GTaVWQYBXV)~v(A5zrL__H ziywoAoS#2qKD5zIp~mXPD)z}Q@;%v-`P<#p{fNnbn(v&em%3kt-HSzR^@;K@Wlx>j zG~4G)9j&#>Gk*S?mJMhKA!`^^g5I&D?88mn#-_ezm!;^VRzQ{IGtK_~)2vS34V?p6 zGU$XMb>nSje=YI?4t4z9u4!(1175<tYhbs@Wv(^B`V_Tz|2>?7_{L5!;AS{dIkg<z zHT_Gl-v=?m;K_V_0k3^gB_8szdO`SxrWP>mQSbL?|A)a`s-v;!u7jAp_2W1ccVfM@ zAA<|jBGIKFaP-*C9nbHUBxdXr=3?ET7qUh-nqc{jAQUoe?c`!<oQQel0!4~7A8QO0 z4Uz00QJM90s4=8xR{8BNqIAtHSQ3o@06EQWCFgnR@0C6}i*9a^Ji|r7@3|N4KAJOH zhap^aA3_hQMS9#b9`;*CtW}jcri19KKocU~4op<+u-)8@wI8z?*VV&B_|WioM|Aw} z{Y8d*YE>j~{jwRm#?qZyw<CQ}0(|o11YGP|YYBY!KFnD9B&osBK?OFsK%g!0b)5~} z(9Wo3gO{nRz1gGnD&SuZH4olJ;zfL|7oMJ%dGic<1d0GnB=R!2O33{Js5rxP;bE0V z_n1~D_kZ;nDYgCi=zALe>FACwCLj+)dW3%O$&%~0HKWWv3;@WDYt|p6+Y_TwE-TDu zuR!I(;W#nBvYaf9FhC1H6`H5Y_md90z>WO%LVVya4*iaggq1~ltii8@$!*Md%}e*M zYXD<NWEV5PbVsAw^IgT*ma=1un6ftZn*01Dn_Sn^$WT5h*<ZsxL)5KP6wf$>g}1(6 zJpS9VR2uh)xgAa<T8GV>-`J-&V#FfEH?wZxiMdJTI3Y{DdmSN}#inetpP#?B(fi$g z;?Lt;`QPLjNM@ZLSFz$^$}Ti{4=`Rqwm2y3f=%KJbQ~mI<R9jzW~{WOJshN$MTSqB zJYbKU(iR#%am;5ySW|BY`zD^WI5N%{-Zo$HYH|R;33L%T)C~NgDWdGrC5jk&fnPzA z^iKW(uh*gG40>vB>~u$aO4X)aga&`+qiFw`oB)gryaw0<zv_STsZm#iW%!6|3Px*! zguOxG%GzjuqGjiYXPR1*d^FliCFWJrPvN5j_|Wt}L@VcEM?(g+uZ&RVB5o!}wdadP z#C)VDg<hpu7iPz!Zr|A}!)0=I?&8uuZK*`PKND|^px!<6-l1j-23brE%`Gt)r!oC( z+0;^Z+i&x>l5%EIZd-Ghjaa>1oIiOGGv}A1+cWT?DH4nQbg){Lt_-9`<;(vN*VNz= z!*b*72TA3$MX)ITbITd}A8QsW9n)g%`J=B>kb$vVrm&yJ8}gJLqEIdOX4&pZ5q6jn zcm6L6y}$=7YxbZ$i1Odg%q&5do$Cqjvuc*!j=1Fvz1=dBe2@<0^JEBfwM2jizvBgb zde9p_t0DQn)Ss(2YjiF)d~zoQ);`Gip7gw<VYvGF3^m2sISbFNRB<%^FH3ACKIp2I zi@sBk6Q`yHXYs;70;-V)9wd?J$&DEK$V1iu95Gck3%SxYLQDAfGU!<qI(8jfu#C)H zGrM~Qt%(+;o7Zp!)ry7hz*aP!UZ8dlcF?JLPgY=gI=Z4sxv?QQZkdg4?kBvT{2)bQ zL`UdDMM<9<5bv%dP+L_B>!IoKc?UlKdWxc{4{4e4DSILFAb9Wip4R0=)CGiu*={f` zN1_hs>kog)Y_D2iFFVAQJ;Ww^b^8R`OK}Vs*n9I`J58Kh41+USDjlzbfx^5iE7j(5 zj{n$8^^GH3jEjIRnE5Ic&Bmjl)Che{$`2mo-Mk|)gOm!Fq3elpn={zcQVW25JIYUf z(QsRJkPy?-F28Xu=2Hl0K6h5ft=mSB7yx%9*+E9!T3(-|(8p(2E@AGgiqjqZLo;YL z<Mo@%Bj`N8iE)=oH*OnN<1oOZj>s>==9pU>(4KfV>+l7Mh4N532JhKw>?nAIXD34F zBH5y$*$?tg-Q*{gmpHTg+g6a(Er7vT9bb@bAf_elYrA=f>L3QJr98;HZ~n#XgC(u_ z@R`Sp+mdd6Zf^<pSqz&85(o6dG{HkM|2iO{V;YiiS-L`v#9Wx<Z`;mxY7^!q^4HNd zhmpz+3d3F3Jg45eqc8ts!gAhxb{v@70__Z&N1)6&wC)2pW3N-^d`4V{9((4(m@EHP zvRmN?3?f={>Y(=_e$uefp2YmV*>eZAZFM%?qKG2wjsrq1d-@m(eM*G23v_GAZ~fYm zp3o~8xH+DDFV%L=da}6%)N)INC0$lP5Td5zaSGBOGeF>5r|<QWYl(=I6l4f7$dc1d z%gE}j+d1EPoyq%2Q=Z*=O~k)gkZgV<<Fm<U1!g2wTI2oR#w|z+G5e0h!6z$vKKJAP zl>vH}2iNgis=ifrSi-#YsO6Wzng^I*3TIuE_}VVG<ro`Yow#WB*0<RBB8}ffFDvoq z?N~<ouI(b)Pa|B!_>{G06nD?M<t$=tU8s-(9R6Ib8G*jMyd7`&NX(_ZyCojn*)^jD z>5OnZIBxR_3A|8qh)uzQOa`$MDVCyhOUAo*bW7$?7Khf5_p~JNRqVit$C&Z)_WU~N z$%0%4V?&+K?8z?}p;g^g$56!1HcFQyuHUF;^>DGi13Ml_tp;lj{g$u}j1dcbOy*Xz zcbVhuCtrUHhbbjcDLE$3?J8t5AWej?7-Sc>Keu1r0}iGa`>?BH3*+eWzqi$j)Y|Pb zbwj4R!C0+bvdi91uSBJR256%1ZUmM@81xBX9EM}mOEr>N4JQ`Z^ZcLT$_~Ok{_q$7 zrLAV6rJ;d0d~%^0Y{}~NQ*Wfnz<pk#%>fw2XVL(BFpv+zl<7sRAtdoWv8If-Hm6i~ zPu33BoCSZZTv-Q5QHoENKBh@;*J$+aPbtJ_*}+G{y5B8K_-ToG^jw8Jj(0zRtE+t| zy>G5irCkb84uBdnin)WUah#5+3+(HawONI6Nn-rQ@i2_ajhUn+N>zUZ!8XFmz+xF0 zA3*?#yW^vy&@#9nG(aUT&-)<{qZTZ_TQ4jjv8`<){xZ+EAB(3El3jquFebv@{k)KX zMaHvJ4eKquP9ukyV<y|^-N-6gsVI(camx7><#p%~X5Y@;(agHDLHY+_!a9j^rjh&o zDQ6V6Bp?3j&HbQb;l*fPlVUmen@muiA109}KKen0i{-w?qmQPmTJmxxtU)Z?mSl=9 z8RfA`&Hr~lZhmExgy!U8<3f!0O|k5z)H8b|$#oXSyncT#;;8TZW|_QJk231{j`q*f z7@Tz=R(4-}(RD#M5KAvluCT`cx;PH1Mh6Nd#ZZ(0h=oq%$*@5k7>V19!qu8B3AUgI zIPuGR@tNm4y5n0hn}p^>hz?B>f`E{%pFyd4YELek26;|sxY=*|6`4Dl%^q&-wc(VA z_ygskUB1>bDDMKuatA|3zGS9v|6k>T#Ki7dxoNi5=M9=IViK3|y9}yyK1v^*y(vuJ zBPy~a^a#%V!?WAEGSiD%@)l}xtaaxdjGq_x5k!l;WmG~GkgbBU@6Z@Xw)h~QuWO_? zqj(f9l%+5p_Qi={;PwSR&AK}J0%^_OoXv6CZ^zA1i-kHTLzO$Yb!zno{@erY|7$_i z!L&Ft@VF}G?eI%_+r-^O2wo235mPLUa3FEY`E-^oW)n>;EiYMk%B3f>dSkZ=d5-Fp zZ2^Q}mExOlr@y(G`m&;&A$M-F8Y~FlWvGfVf3;<Lk=4<;XmPa<|6BlXC8o1>S)`lA zL)I67aah`@Uc52%vf^icXa8Kn*c$;dv94F(q0bo^Ud0Ms7<5=|0}mx8t0-9S98#wT zGqHcaSG3=8awdd6+vunX#+kOHXv?y$8<U^q*Z<2bw>y4#&G}LdvRQ8@QP7S;n)9pU zS+KMDzwGQywXWO>a+#jwiKSZR3o5H5tP1IhB|=u}E0G_L5FGK<7kGaQHFhVEexQ>m z$rNlEWuE&~xME{ItzVP!9`HJzTz>3aQg#5CL%d;h6k1mjKGD#d82NYz_s+WrU0;2q z(5)4e7so6N@fj(Y^pNDu7GQ&mY4FWucTlU191hsOt1I0372RqX&Z229=I6UQZ(CPB zthE4*RwpRo7T&b1ffxQ_U<7@L5tY0y)dBANy;6exa(B=NvoH%NF)Q{^@q-h1Ws3y` z0MgW9V;r|n(4{$|;p|1aaOaMTeNw)jXA$*+V_Cqt=i&Z`I`X7VZtmiJc-^L5-3@5A zRE(6C&eDz!Zk|2})t^ut7WJT^+Ol^&DS;uz(msOhFhlrt^KOz^OFjxU^ts;OH(c6k zT#JdJVDgy&y-!U!qS!y5cJpJtGo}DpM(FtJV>itb*u!73UVJpp&ko9u0943KPXnL6 zrHX;C{&OWca91cVW?Fmf<ZKWeU$M)Hiy;*n(keW$h^*GVCiPi}{^p%iXCRhl3s2eA zWB>ZAk{M<S)|eVU3?97xKA*COF4uz(KKiK;UU3yVJHQfF71Q?To`7pdD~>Sty<8Bz zv_vFiI}EnT=tc*l-K;N1R}&l1*VSSF>hOijNF<kog)Q0-1gt9+e^TKR0DlSi^U+<X zY7nkOn2Q}dZgK{T*EymxRPoxeo9#DqVZ>}mBv_0aEPHHiaF+l{-ImlAY8e1l2O}>X z<C|;$F=7Ibq18ah0kgo3N>Yyd>as6w67^>E(ei_&PN=ehF_y9|PISzrb$ibhLDHu= zm~6zwcvc!2fhpn&))=5eugxROzKVbRWt%}^a!eldO1_*~k)3T{1%g0gz0rX|ll?v~ zR~jS7-u?{tZuxo)PYm6jB*69@3a6Ft&&#V8)P=}{0n7pR+i^>Pjd=!F1n%6!Gj_9M z4el4MJ8xgk(a0mLrAiZ4pk;xzz&NMbabe}f9h(>WWXGH^?mYXQGWPz><;&5_;jfo( za_ov=la_PS+)S~vQ}3(pw}B>>>QRUKN4NU@IfER4y-eP(ce}k615Wov$lkL<>eJ-? zRIfc+fbn&hGkdOfuO^pbF*s$w0D^(Rsi)w$uW1(Y_^NQJ&SOKD)rA|lNa-!5U2s!2 zsYX`I5vwmZ>_2)hINHBj^WPCH>+MwkEdC8FO%Q)KC8SlluGlwJ{K4N#-R?DDDg%06 z!5R~o-zZsQ*33d7@Jp$;Lw*nvbT8`Q^Ie*dAh(7(z2;Xm`TCxomZyVr*Z9oX$XEY_ zw18X8JJqbWUi}`}g{P~eg29_xamukXB7?MS^WtHOIlTFUgPz~$V1(C5Vrz1(+3VqP zR$l!d_z+PngwDR}oy8;#l_~r)E5oVoxVxnciRl(3@_$%HY0ON$>#8*6C(PfrFYvU9 zY1(2m_zsrH!9JCm8U^gy)gA6q2m3U5GT<VLQDJ{SEX00n&e7m5ul-p6QwJak&2Sm9 zX*TssH+F0SSaZ^vQyei;YM?m&Mi4paQc3~6&MxMlvW}wWmn^x;?q}<4KH_4Ktk|40 z?SsKMPye7l+cx@qsy!lyw5$OrL(Gv1gL!&m4>#GOvQWi41a|cu>rpgYzJdhRltzq~ z8WefHr+K{Liq@<9V~>=%<i*`1Ni_@9nq@QHvIH9ow$oyn8Zf`^zGFlN{6%PaBJ6Lp z7bkh^M7V=&^34j}f2C=OVfP;4&FA2a8&7=~x?K}mn2~)0GaIIO6rHJA3U6KMwFpZt zSW9Pg+%J>{?sQ=umofI?g@5c3N~j7~0NUeeH$JxB{obk0U%u;Ex|Ec*SLwz%`++od z<F4M@NsOUWG~<Nv0vfb{NXos=)hFn`O*Wpb^E*JwZ`k$qsIcyQz87G%j;E^v;a(n8 z##E2Pah($7O8?Vb#o865V;@_e-86$y$`~+L4wCDG*Yt>1jT*6%mC3ttMF1~NClfeo zdM%E%pNo3`Yt9?{eP%Y+T7-914uQi<&Z0vC#RT_&dc{e;`_6|%`NV3qA!MBRhx<U? zrJ7_^74r_kmQ=cWd+AGqm;n5TnEIc4fc|zqiV+&~8Y!>`edth9i*TLjNL^!%rs+Pj zF72Gf%yzl^!KYu;`uwEEzgo@+TDKJQ1OS$<x=$w}YkD0p{<#Z$D|03Ir>!ca{MhcX zj4pKyeA;yH`qUDn2tY+-2vcGKk3!5DgPI#Ms_wrlR5=)Y{IK!Q^VP4pT(Cj<3PiV} zA?mqfIbD@<E_UF|n1KAnskN6-M2KQdw7(d?p>~8j`jvJLFuWEQ*)K?XFy4+A=?08t z-%}i+mwK5`7d9qII#e#n`I+!xnKc6Hn#s3vxf!e6XD0)*`VLsLtt%<`xnLc?{d4ZN zbWAt=b!q95{*Tta3)4Lx1skSamqgCHt1=b9+eVjeDI$N5KOkOTtt(=Bi8E*AT>Z;n zWHkw^1nmL4=IFghcJ+bW2_3BO&Oh*q8oDtb=wKX;8Gk`){KM)?4Y!U*IW>MkR2`z} zq9!{v<8;c>^CIGtFXeK#0m$-6$jvNB8!Q48MLX^;SU0Jl`*z2w#N4^G7s#)UiEcXS zNpS>YPaG-_O?)n&_OS{8XeYeZpeJLwx{};6muwO@tpSd|G`?7fH?!xPCii5g0RBM9 z`+21N_PN?SA6lX}*VP38g7W;%q9U7Osmsv^VsC?;P%+Nc(XLKjnHPmWjG6AslF_ri z-&HAp#)Q4*DGym@b1vYTH7CyS{YB?mx*<mcvWEFd*emQt_a1*7srTS2uf+oYy#;{U zLUEu?CQf18r_q`I$so%bPJG=4@+F)Bf{{+_u3G!N);+=%x~$2C4!n32=IMa8OkZJo z;35RXsdK%Xt_*o}$Ol5N)7$d|F+)|-rP;+D(jSWk{DpxiI~zFXbth>en`Y0&ERrGD z?-i2rR`02|)>T7bxt;8ZPGZ#j%RIPwO}ylstJjIU`^ARg)#Q43YjENePu85%6WQwR z#mf|PLA#Uh>iVxcuA9a>9~3Qrwa}LjG5}K?xK0zo>@qoMGGYX%$BW81w+X+hSh3gd zr6plG2=`@AHp0PxkyyMT_R}%~DeCVw)&zV)Ujf$--c_V=%l0P0WyG%1l6<3k{8086 zheVsF?J&2eQ>uLD!V5ra1&Z+C)?r&`cHV8B=pI=^ehM?=VtV<bOQh-?*RfNg_HINl zX+Z%9##DG_i{9C1)>XsP{oN8|AWYh4Sz@)IPIX{mz-d-9Vc2De&KaMVO-463)A>B^ z%gi6nsgAFkEiQy5!Z)r6CuyQ%ijFWDYXt30)%{M8ZS|(t7+_S0P@-t~xe?*I3XNxV z2oA)xZQ}|>gF#CBz46b_KDvAW03)))ToQ%xs8_33qGdYIxe(8&KjVhHW5|b1Qo>P< ztiG@wc1RQVWs8M&KmjbQNaa_>c4DA+-n0hJyT++l?R}&3qWr8trPy~kg;va^Yei*U zKS5DJBlNI(3p=e#Mm}@jPHvOIRyw%&q3OsXK)lU^skqm&ez^Cvy8GFy(L^fQuBDmm zR!-u~o-Fevh!u8n-n!FKGJTIWWPEsIT}6kDVdzGB{w^DPnY6s#`Z@hy@3%WsKM=zw zo7||x*LXbIB{myqSBKJf7pGHoWw{4-2Z&z>kPa%&o&^Q=Q0e7#_n!-@B6bl}X+c4Z zczic*?-S9!mmV6!u-`w`*MMRixMs?YNsEF{jOY?<Q$vOC-ey)mM$*uhqn?yFotG7A z8w-Wv((`@7|0&p(Q;T%mZ&xB+(O{tpK*^*jzP|mhEXO6xk+<-sT1Fsu$3A;ddAKUp z#$OquUCWKFv<)+O7@yo6$r$2Vd{N~F2(#GM?ju!ne5KE{nh##jAU(^EbfgIqYfg}e zf2T604+;4wVZ2#am4buW$QlK*%>JL`+<)NyGKTaVI44hQE96Z^aFBT}73xdm%-;6! zCqD(`bz*iAugc6>AQaiwb!LIBamG~!R!0|RS;Jml-7l8op&NG>zz^f8A8_j1vvPOM z4)ht3Gv{Z(SU%9;$a!C=o0Qn^qk_ps)4y5BeJpoS*b!mvOh`8xjOf=+N;IC{>^7=5 zjVVs?2JiB-Jh<|mC-}%z2>pA|!&RkD4Wd=LQ%e3a#?63B4Wr;T9f7HpPe=Q9utB1} zf<}1oRQq;!45+%+ZU~S5u1IK)5EAt*cR~U%I@R5|H$^iUrMP7})lekc`hO5GWcT|& zTHgvM85zO|f@)_1Ur_r5($<&F4Cejpu{7do5GmOs&yr|lm^W!S(&WkY`Nx9s>UWQU zEXXnsNoY41<lB9HYG#an!8Mz#NpAgLQ4qPEme%Sso9^z6G!~|Yb<1ee(X{`|<UzDC zwfXd;6C+&{)#;Zaq6nEWNH6oM$(-cw;&iFr-S!uodN=((nrY3s`U*(+VSEqNd}5ie z35GQ+UA5-KmLs?^B+#DrX8AF^@P{kTg@V)y#CC%<?OdHIq$3^iwE+9Jfr@z7D3y>D z)jntR0NHo0YZe!)jRmoID+b63DG&1(eXLVbAtCnF6^F05#cmr{u{YYfZ!v&IE-quO zJ{4c@Cn6k$z9pnZs_~ow8t1Cw;*divKEpabht{|>d!dj50rCyTyL1C>)9xD?qALf3 z{${wzkfZH}<3H{>on@@aE4ub=B$f<H3^DAUWQsbw%N^BhjK16$RcYFBaGCp}{Qtt@ zm!r}Jj9<p=4C(@LIyLcnv^<nIbElBxPxTn7C3f{)wh9s?c0X10&3)@CO%a4PNr?zj z{gjXT+<fs7?*T|RX^B9iVO98)W`OZb1+;VETwOpz<!AgA|3T3hNwM@y8NwW&s4WYC zr)p$4*>9{a$?bbDC+C=F<wx0RqNAFT!5QgtpXj8R|NN;&Z8f7)zb-p<ilvq4Y)r_| z$Y3#|;;MMt${4e@Ya?F^pxOG9$~AG_2!wC;wDI2N=|syPuq^qK<LNNepiU9E*5F}3 z(xvaw+j`w}m|x)WLeV*<g%+>)l!GH(NYU<myPRqdN5R+OTi;aD(k>#`u#V(=mPUko zb{ka1_o^8mIM=A-e>3Z2VeQ$;Xm>*-6qj{v5LR8egB8XWJp1F;HH*<_5faewLY)7I z&26(Tj&Y!8r7}0>ULuP&+K}SztUTk2>D;1n@qhd(%|@bPqI=`^X31p;rr3Xd&QTuw zs_cl4BlyC1Pj+ktY*RW>)I*`@S*rZ89lCUwqRTKl2^BK^oXl;nz8SAA8|Kff3)H0c z@R&A8krg)1T9zKjeZK;4v*xLSuhUlFG}5wp3<yxanej4EpTz0aKVHIb>5sTdJ~QIn zf!wJ!>*~>zC{cgqyO4xm9wlQCWVKaiWnf`oeE)7t!mNfl24O_Dg0TVy5~3SI&az7m z;r{htJu&#wwN}wxRol!D01#PG7h6VQHpej)<|qEj3g)-8?&)d?dfVoSqY5@2V_AX1 zGg|&2d@L9liqdr)RKfV>28hoCR-#qvGV7;;WK_c;(ql0>_6>TqJU=|CibI3q8*wIp zI;Ox)JVX+5>(NuE?i<<a8XJ~w6$2gS?n7$Qr*7ln#YAGGo3tw5*fP8=m~AQZNNtKc zTX}#n_%omD5KxL%>x-qsQ8@z$t|~4eAz_DK@8cySn2Guy$AIj(;Oxqu_^ZyS$=h(2 zsoJ^Z;dH^M(uzRP!WKE>hY2`uM)U`Gf;%y>qEOHhig4`fx9e;NRZis#l7QQh%QXji z#+H)@3;;M>4ZIWBvAPC1k!XZC?wzq4Ti)1>`6ifqh3SgA^_BOj1{TY7%i;a~5=B*P zQ!iUr?g>kDqQBgO+<vrwqJ=G+4q`<bw}WoF*HNRLbRgwHge+J0({o>QbkFls+q<}# z^#STGW%R)-a-ih7Z((bbWY}nmY9rHO$9OBMzc_lJa?q731Si;3KOk$}`@aMn=0i`# zu@&S&K3dI?@IPgrWZ{L8-0!hqRvZv#Jz4e&Ha0dGCh=&8_X=Y2*ic8UUE$4dZ22=* ze$d-B&w>5KD3Qhq%!)Fb9>Io^4#n<7gWj{%GTv7kT%z#q%i>PFOI|mcf7d<PC}0yE zd3zHU3+}_*bk+$lHDmgGP6U!0PPxIs=yd~WaH55mzfu5@VkKdBP{sZ$W~1J{V5P=C z{m=M@!Ee?L@MusW`9A>ku~IoTbA(QFeRX02rB|n16vH#A`||g<)P31|RyP`3=IgbH z5inIX%|j$pi)A#WAF(n=rl;O<FhCYN2Z~?Mq1Hl`L_06bD8n8*G?NYpxUbBr{+nM{ z^W0Ak=jqg9E6`N|vY1?Ppenu}c?rPUkbX;oNc*uX52I`>0zPqtZjaZ$0awx*YB`K) z&tD10i>7budZsY3l57_Z?E~I$O&wUc5$7~cG>zr9TB^XcCakxD9;cl*VoXa+v^qS1 z%hvh<_3OsDZG0n5ly`3D%=C$3)7YsZ+ncUK4-~qQ($@6+E@Sv`4%AGs0Nn`~KF8Kq z=*}9cHUl8dZD{V-qir~U?#s;&{_@uy)n1E;B>o;_dqwAtd0x0sH$^k)gAdhu8!^G9 z>)=dTXy9@|YbxIBSE#rcURX>*HopInmq>;ICICm*pc%at$X``NUo?W0Tu!_rx^t+6 z4h1%NiwSMBALw6Swzdn1TQ~DCkrw5ZLRwdjts)QpMsY&OBR+Fv#rk76PM`(!K}_ys zBj|APo#0he8UF*aa@o{V$}RVWO!6I(6N-4ZV@^o>(gJIDRl?b)zrR_p2C4o{!Gu9a ziKf%WZW*slp$ckAT+C;OF733T7`XN`jn16=_l~CLMk)t7mT7=`lAhAybPO`kv(%Y9 z_7LQH2(RcfI+u2K>#vngHj>nKmF0x>Nf48(;BTP9>E5lAUVt9e(MH|5#~u4JI5U&@ z(L!NO9GzLga%}#Z!d0r(Nun$AN>6V>hDUTL;CNpy4!IZu3+RM3n+D4$Oe?}^z#3j$ zf4G-3>#mGa5g_M8@O6^;^(oCFM~LR}cKTF3X7hwv9?dEM@c`f)^k>KEh~|-`I72!o z4LpoB7%=cCRgzy{qWHJ!K=VL#wTfbxe92%8n8DbCYQ1}z3cF`qAAZ&XKqW9juF;^9 z!Kh5Pu`Ha>5lvBp(^s<xe(}aucV83fj}=V{etl9cg@=*w*{D7pBL517y@~x?ryqQe zVQip9Xr_)_r8Vc&Jspohl}&!zw_w7%)0Ma)+3~2L?$4MM8OzuYdf=U74c0ZF^rs;O zhz0U}XfyG9f8DGCxiWR>j32Sxeurasb{)?7@~CF@!NDJE6W0+dt++(}^@|@A2;hAC z8d`Cs0{WH*>X6#r)FTcfRY)h}U%Dz0WCV7;#2a;dHWUzVs1~`YcyWj|qMAYNfL#ri zm>Wpv%M3h;Gc-kpNY7Ffkr$R;UQ&HrZs%l=wI<Bw&5P7*Dpp9Dhhf`Y7BSu+eqe2g za4|wXvMTYcufiq13HQ-Tdt4X$)FcmG@u=31t8<QYJm~OZ@5Bn?bi1NdBjhYs{+&KK z3^-gN7^d|8M;!6l!q)vN6*lxKWYN&!$hPrBzsQ3pIQ9;!JK^Dp*S5B~BmBD1x1F$_ zplAaAV==bAvrT+Wpudk&M#+T(SgQPJokYgKUtRvr=bDu>CakA<vDRp|S#+|YlbY%i zp|?0R#mcsjjT<Ahdj)LRxExEjy`+ofMNSlTQ3#jF+MU!XD_<+U>FwSS=Os+OjTsfn zm=F;|1H{~2Kz3T!jH53dWcM;Tx8I>?t|JjoCkWscXLozH{A?DNfVQ{c^;r3?nt<-& zb1%#rxmxU1Jx^i-;;XA$Uq&6QtB*6Z#0?fTdt<ocCjFAVQc|3#8SQD#Eaq+TtelgP z3;Y2M$BOnpmNjycI;AFC?S*AL!n_>snW3(ljLAOESY8s2NNX>Rn5?u$y4P?04y=0g zwV#-?ZwJ|5x;8p+&pp{bV5q?Z2i#pNN2%eu6Y%4hb3PczaTB_a1C*WLx7$bK&PBL1 zTm`l5h|3v3IC}46RQ>USWimtD%(eifaI;fm!le8)rWR7>IAC^SwmMoQ_oiV&OV>_x zU>G71{^30*x%g<-bHJI>>NbNl6?><+$0nf4h9hB~{UJE{u92FBSVqA#-sKXgQ!(!i z@6zBv?Qg$(NF?aULlcq@Kwhe%;39AEM?*4kx+_tn2yJoY%FmmAMjs7B^p%*KIpJ*g z=meU#Xw3xIwRvUqeiXhq`_}2?Rb$T3p=f#x>USc~OHXn$)dEY-@&T`Y%SaO?FmU_D z8h0b#3)BJq)wP0->dEzXRY{Hj9A7q;EC3T}M6@!;xcE5ZKE+nN3%ueejK|x-pvbe* z%8&1Q?npvwv2nE{MS++3<i7ecuM2Jf`Zgln_LTwQ?qn$CcC%!0sy1sWz3UZgAbN$u z&Fk4SU>Z5NtCQ~8V+^I=7*UD88w@Eu`{O!N-grP|?6`ynrVB-g%*F!5#o#fpzK@|6 z5G<@1RQ6!8SDJc|i$zDfmwCWrQALx{1iR_%{=u7kqPDiUE1mATsa7Y5YKCO|UIEO% zTBN$e#YsTnwLwsWA`m)|ru6|Iq*PFd4Fpl$KjEOoAjBn8^~dkEUu=#OF)iFy*gA^= zGnTteahr8~{<e7cc$D_re+-tcE5@D;JPD7k9?RW6UThp78dB*3O%hR)yn4gM2<Z*S z%E@a<B7DlX`w2rg=IISboi*fmt2a6ho`P-NGNNidd*%A;H(YPg-QNJZBoIu7MOVK( zsP=rf0k$vE;q2;5F*<kFjhLMb7|W31VjfI~oB+KOZdbQ{#Cy7%2Ii=WI;Got%Z)#i z!NdE7Aa$`j{Z0Ts`&f{D+h_IXr|xSVkv?P0O$<-Amq7RIBIU&WmA8$*DjddZXTHSM zU*=uCp8Y&Bq9;8H83~qzjzwa|0pRa?a<SMF{NR4!#`Wy9)UOJKhI`}sVQB;*+v7t~ z2wcOWo9w+LGA8YyoS7BB>1?`v^iE=Bphw{8z8O5-t{O|Hq<IcUmmcazSXgfxGSzMD z&PRx$`S}$A;r0h%A$NA=v<6^ee1N-QKYedqsd0wRrBnBhIX-vs!NTuCU?*#V>1~g3 zX>_KHI_TQ$hl!WKyeUk&DwP`tu#v-`hYo4dVSP=}I@cOKLhvq}5&$0=ZO+>foxB!Z z9hf?eG6DVdi2J%pZ)`v8_QLv61(<o~6M)M>2v4s;-=O{=BjEp-dh<Z4w)cO0OT(?i zyNOh|_eLsH2t}x?k_eUIm`@TFQkiG>cH@?$C}gZubR2V%SqUja#X-psC+e6o95d(p z+R^9t+kZOt+G{=QS<iaL*E9V&k=PeWb}V_lB_^p&$n9|L?k#7}zW0WL$^&8b?L7j0 zH77<8FLiHyG>$!v(bpDEYe=+N62l~~h2B|k`CU6jy`t&<Nc30*R}yhwno4xh@xYU% zI^D-?I04T4#(O8mEX^?Wo>$gED5WX*sF~YGc<in@4#$wa6iX&U+Q1>2Xr>Hr&jU|Z z5xU-77SS;^Bj?NOJ6H79=X3+D`H|N}o_!+Y62!Z&imgv~uc2O9kHsM!S56eoQHquk z0p&RWOG}t**jwp{>KV8*hT+tNbP2keWAm1_-jE?)weoX1u`kyH<rMcWM<=zJrZ&@@ zBP~YZ&*85`4wRtXt~#baWam&YsT&U%R3&~j+qIIocX@WQuIj2d7OO3NH`d=TtDK)C zbMOPFb~M}wdlYdlJ~~uII<M5*9OKiNWrtH~tu*oRtsg)8t|VN7;TWA8%Z26{ozx9P z(r4{zfYOJc3s{o#{t(&X!_;0uuLhMneID-`AH$30o>vgzU|9Z=R+!L`H(*BGYeC6< z!ki0ADn=R#$39gV8Y?s{1FU++^SXD&R;}iB@9Or3_hpBY9A04PCY|?YB~hz&+<kBn z(b_#!8D2EDgCC{xA&N*gMPy=*n<#BD0bpuhGqZK@e0+3EOZe=#9)Mwfu3PVtrD1SN z9~q#m6Lrt`=L$IQ+7*KC$P9A@r_6&x`@4~yua4x{JGeIIrkZTtO2woMl<|>Xd2#?Z zXDLrQtvtt|(32F6`R#xsOp5=6mw_ubRE~=JpKrH?X(JMJ8#j_O!xzy#Ephz$-EXx| zE?vyo0n?)K=&%Q^<Q*4ku@S9z%EzAKAMt+i^j7EY=gYu`nxV{C=WBLMTR>ey=mMdu z0Aw9dl&SK{VLqYpQ@&pvG95k;7W26hJLAn}FxMs=j`yPFvF2WhUu)$%r!bB{MNO)a zwys*<<W^3SuGBezAD|4Zm$r8@VOM3$5V?YlW%YVWJ<F^#0@qdk4uwvB`m0PhH=jcq z2p25-4z-Gy__iJD?w@%;cRlMm6XKpM8oT-Q+t(P6e+d$ApN<F3-^HR};>O;rNtW6~ z2YM*5MTj%&9JS_-v;UMRL4rGM0p)QN+w4Tb07`2nS#w|b!qHKY0}@DU3g1O%QVcif zXS{hCpmcYqC5zRkSlVcZ@2dZq;}0ks=Age*alZdBX)2A1K-vERw@As9;Rr+3&j(?` zDJ(7eag=<Tg2pYj%_C7g@$X*&@4m4DIo!GJ@kq~eW>}<)qapUtq5q+Hk%776QuA?D zpYuSH6O#Hs_JBjfN5T}4;zbkqw2qAU3xv-wd=d7$joxx_q284TH<(I6=y&ebCt+x~ z`E=l;o@B+#Zh>SbO}LP6owiW!qkDb}5Z%*4z8i}iZ?Ir@tz%lgeUA0AfXlp>aK`kd z5;#62dPa2%0-xHXr0hI^J$v_Gv~Ak+yZpqALe<ECBRXQDqm0jVOO3HRa<1pb%Y)!2 z)n5YP6np8&hYufoazrj3Kmx@81nX+mNG8j6`?It02kf=X!d8B6C-!MfG17sRbFMhw z%#>`9q{iM(a>mzZb!t@~?Zj2Rc3Jf&#Yb=UFNBCZW6;wOwx{N}!-;+kL%pS<f<3~^ zh>afrU|?*1tFO>`gwfXaQv*9FkcTyFBVk5s_lU#kGI_29U8@pBj&@gH6hY<oxfs(+ zX4&gn9LSOqVXa}|@jb-5n`r7u;*_@*OrA?n$=xl(re}W!x8JVxn_gd+p)L8|CrACq zK2kRemdWSCey|UR9ktggGFfq+R+c>1+N_?3D>hKU*K263+PO}_s$bP>T_xA%3jx!l z*JGj~>6#-a=Hf7CvIqM`8e1XfUF46>i64biti9GPfty&>4}j02ab4TG^TnUMM-R_U zRH6^(1<d(fGkQvSjFbrG&N?~C+)(vkt{zA2(zn{P&;E%y53h;m?^Qwru4vXCvMJK% zt3-_niU}{Vp1{7qT!}L}Xa-JhXx}FX7TbELtbMLq4JWL%CWraORwDnAR*a(nPWDio zpVx49f`VN?O!1e?`Ed&0dGM-}p@;5R=#C<@+XPiV`GP9@Pt-1Niq8DzmE{#C9zN>Q zj^)fgHIjJtiZjy_lRbx~<ioF-N+lO%FvRSQy>f^vM(54}>?V+Bdwf0c5n5tJdmF;+ zVp4xRHh(-dc*v^182f2i6u;R;A2?DqCjQ{5sV4X(dMq(DZy{F$XT3QqKTjlaB05eS z85#X>9Zr=IVXm8^cF%FmA3x)yH`XF^cjtXi`H3%lQt;l~n+7P(%uG*>87I!2{}ej! zeqIT*_1HH0m8R&NA7qD~6Iw=Y9B$%KMx!aIY6osobXtlAW;~m^Q@22j9>Oe!Bg-2( zHRq6N3_r$`6Rmt00s(P1v_QRHuGEjPEt4VIlLsmM!DH&X4a#JC;_cgbil-PPN^`s_ zUO@M9^s7Z{*gSDgg1;KtVv~WJkFl+EEWufX>n539zAXmY)>1xQQ?nSQ%P5A0rSHYQ z1eyPCPlUM~j9F$WH+}&ENT{!*=a2p4+=Ra3QPpLi8%RHQjh66_cAUQ*xoy#;CGt71 zCgug4+Bl<<C56*JokD$Cq*XB8S6?WItS6tb?DEw%IB;@LB0hht=6d>knVjijAWfLN znZS2d6ZZnr9i<j1RQ~yTCIaaUV{wtW3(h@1*y-W|7EC{+%zYcR<-6C|$JG$-8n!q7 zVGu_c=mgLn(StoZPafvC4Qj!X65OzmjBjYLMz;DHsfkeuLSxR!TKCT&r(lg%mY>vF z7T<~(Khl7<3G1DOD7gIr?U<5cKYSJ}9Q3U^sstdrK75h-o{ajgQi_^U@4C+XmiTi+ zEI#&yb5L8%89yVj2uKkCQjxIAR88VT2`t6HciL1xTC^u2VY3HSwgRLUcP_pNC&$Bh z$vA6;*VjG(sQ7G^7}{oPsSV%it|c4o>G?bW93Fv^nxwdE<xeG28$RzbcRq^9Q&r!` zf#pCvdMs{I5V32PI|Tk#B-3k2-&{=Sn*y1?&0Aclt@-mFGOYlZXyWSM;}I6@?=Nyk zO>h+l_Qlwzw=jzJ-TW0NtulxXb=|c<{5qGKT5hJYcIz&8UTvv6NL0o3|CM|*6%BPs z8z9Blx#vQiDvAA3eV3d<pYHnoFIE%jC-eriKWMH28bWHZ7aD8Br<}E6kKE)`Q@H1d z4d58jJEn4TIFjMACeW*(KUXi_&y*h=*m^>H>e_wkvH+zlII8sh(7l#)1~i<=+CB4w zIWto9aS2+@oM_n7gR29f938io8gk<6{r~sEznQDUl?-jgAI|!B7dE}cI+*NAW@vP) zynW6n)_$rp7+Jn&4oRfPn)_VLD&x(9N950->49)G##!R1)%ZlL{=l&e?ov_`gkQe} z>Mb8{GwWO2v9}H#EyH8ZA8E9*LPcebZP}_|yZzxDw2BiInT{e=t|QWNlbl1ijB-yP zYsjF&F;^^M;((legrs5%eWD{by{i;ea}`ErVqKNu=vDq8N8zJ$9g5@vSUYp4w~{YP zgw1I40Jy;1+Y#O=hVkjlf&B76144au@J++qO5rMYlem4VSTMld+kJx*Gf|k+l_}bE z^-BoK3c^k0l&bEAA@%($EhVgI?1*h$MERw+Vo<L(yWW<>|DJ2kAe)e0S#JX<HZ=Fa z=1T$^a!+bR_1dyci~~Q~SyhOtuURRIzkak&XV&@aMo|9Z*CL(lXolY35Ar4d1D|16 zs7(COws`09R;4`V8WYS;UZ%?XTvgWt4*+s>Ct5lPWQJpuHvp$&es&J#(}dIN@q|7~ z<xYdKY{?zCus>_0HtR6TMZ@d-o_UNdBa-7K?5i|i{b(=zqUN7;4c~gWQbq>|c%9t) z!_YkJe1X(QmlOEfZN{NZAY;soe+}}=xR#Lum{x5*;M2@SRby3s(2Cc|$^r)*QSJy} zhWhK6(w83%+spCERC&2d={1g{j@sybri?$#>ZANxz}x|j3|c^v#E6ctX*HEITgRzM zDjGSaZ7U`y1-{OLX#}5SFyC)lNGONIus(^1$=4rOWJH@R?1niC0PvXirqfhfnVcCD zKvd}->0EmeSf1py`J+r~kw*=5UF5WHcGiJwcrWfdL-^s46t}ldI`84$u+F=4NLC2f z4FfZNPl^{cQYl`gNfeF<?UJq{v;aFX1{sIWw+(xR=Mp5+b=25Tm{$aFnBE;mDd6rF zzuZ=vp=hrnkH=8d$SrZVkotI#XT)w<fV*4PZ`h3K8YdFvth*+>+@5rx0gOFZiX31Y z>N?}!>&$U9Vfpmila`O|$$>E;kMiEQ_=NCBVS8^eEg+M23?kO2tG4HCrtT;Qjlq59 zMyjp|AeZ)@N`HaVrB7$!8hX>IknT9`-+2|h68++-Xm4ZK#|KgB34O(v{oD81X)Pko ztVoIt^BHhP&105F4LB5X5SJqVAzVvA&>30DU_8Z1j+b-KR@j5&;krBXp2=CXS1q_B zjuAe-HBZly*jGQn6el5O;#E%%d7*D=!lVw;zwy;?E8wq-^|Xa+^=&5`$il@xu5{iN zLQ+6O_-L9KUvOu?=asWu4Pnc|u7{(g+pbzwrODBx@G&=&!ZuCyXaE^!eY7P9a)>z} z{rOUG6-OyXUVKjnL0*#Cd5uiYKbeU1gscJS3~JR&nwQ^8J<6Y+TeX$EvMbfLB4k;c z)nrL_>BLFzWFr-eRR7e>AmXxRjSB)C&XxqQs*Y=+0#M;B3YbqLEd!kgyx3von}q+@ zTeVzVw0$BlZVh}&M*vsgUJ?VnU#mtl(rKr+TNWx8fV2ZbD%Odbltg%(ILigb=VF9? z)apwCa9sl8H%LCPX_Pc6{KAzPfe!bVP>o}1kFqW5MMz|_i7THUb(m9wnEJf-SHs#B z&RJO-KKfU(c+6JMedmdNCrAL|&fxH`5#i-As<SS`PxQo;KL3uz9n2i5g`Ityo*5|F z=1_6HbK(h-bFeu1=t!Gz)8XW8{79bW|4YhqTNNV=eDEQsiC?5zH)d<Fl9_$#p9w&{ z;16v^Kn{H?k=CD9_<_kWL)&>=&7>+?({~Aznfy=cpDQQy)Z&@eVE?1`h~1<8a-)eR zztd@mvDY^{%TS=(?Nq)#96qJ7Pulz>NGx{CdjOUE@2ZzF!ZNKX7x)Ruhh87HRBh2x zybY7-DRK_;rdX&QJ3VBCUe03>Vf~6YFNJqfpB+ITbI&?&n;|1$0->^gvPT{AYFBn; zxPyUq_ciMm9}`^un4MlaeHp^-@fiAm^QwrBw@C!&qRTB*0MTVaqM(nG3iBpVpu#yL z!1K#$b;E%o!rJf7Sb4Y+l*MeD98u1ZtlJP!kC6;m9x>kHC>}gX`q;=&jhY1r>1=kf zoJ^dU9gi%ZK?~e02>+3~x7c&H>~OlVcKq7%mzmt9e@={txZNk`GLBY+WcrTl9m#Gx zc$x|Zb(iwo1Q`mSeSGY(Rc=z<zsVx9FDn?wDd1(^R*pc(5+Zouz-odJ_56Cr=0^wf zpOrSU>b>a+=Kj7Ud=Dw30=nOS2m_)7l9r(33`ZqEApts|Z8b*T_htM~%B~v(g@?_o z6*a@b{=NLwn3nkZmR5QHCaVPUgoo>aO_QGiB?%pdQOaym7>7^?@7|qg7LPmaN`ZYh zRRBkmzpm;}Aob<qF1XBl#BXRN{xQyN$FmZYs$gpv?4eM=Zb!}d!<l<_c(N`A0^cLC z{TdZeZdBQZ@Y9e`Hm7lt3Txr6LiD*{I@dRfF77{}F%0WB+ZLRk9Ee7Xr*3FL4}RF4 zSUDN3qO=D(#et;j$U&$ICU5`L2b5jdAs$ZgQ0?u-DCL|P%dPU8MpD6!;7K)|#%n@r z(eBrC&>NM_x7)j8(38?s$024Wp#<1pgGD6k7UtCJ%*hC^L}t8Gc=cL1Sz^pxj%Bva zI@eWo_5=KB^YRM~$FzbgQJMU&rCGB_izCn}hdoq~$zL!SZ+_`2ZtA&~LeY}7W!7r( z!PFr}xUAMvP`5*hM3SETTFx0BBO}5u1;WJRu@CyZ*t1wSQ}Q<TK#OW|l+o>-xQAid zK8fWp*IHBdTFSCEi2nyoRSiXlAN{7`-M1Oib!%0~8*l4er3~vacd)oz_s~{O`^Q&T z%jGjkCdoZjo5S7Ab&tCtc(b_Jcfha)Oj6G0p>g-M+;|T=QhD{cfTC~+%8hT2NQjlp zIs#Yqwe@~dDIF(+_h47*+*UD9E2$^J)<fh_Gt-A=QbjSd3izEX)gorT@MTf{H$7n1 zw{+)gtrPHse{{_46fB2?MX(h{vH~12KWYy}&kSzhCtPCd!*v8mzf}~vcq#9i{H}L8 z^LK==^7^vKW;>4}Bgjl{<X-F<dWF8(;jWFhDpj5|A+cg7Nv<(BPqLa@AS_lY@Wx55 zeao=p+exePha1%MZ;9bDezIrL7F9*<Ok-FXuS9KA>v<KtV&MdEjF<m*dOV0;d$9dI zn(?CaRefUrP~S?VbWCbUc^>2uDqHKlj3&NmtEZ?I9~e<GfX{aud88Re;pK}|FEj>k zSqHbz?&EMi$OE82_Udeilc=iC=bU}<Fu<TD1#ZGsoI&m0`tf0e4*$W*3^*Mb;o4z7 zI=BW_o<XLd<G}Rjt@3}+!Eo|JWLR$+)Yf-<;7LT%gH8BRo6LJ?<P4n>Xe#7L9ny5M z>HqF`?Ot@REAID22nzqG^XFILimJA}FzDw_-UwCV;wX{iLv)>vi0;f#Rr{Rnu>VPN z1KEwF(Bf}9yxK)lg>d=DOHQ_GS`@?nkE~Nsk=`?^Zz6defnFrZARz%#QZgPLZ4ZU~ zRFPM9-98bf!K?MD1wUk57)ZT)q8|``-!_-5=R#5f@E}M>l|716YwuM4^ND(yY!)_B ztBpXkFmf3n{H*Qnzm-v%EZIrk-?RPJvX4ogJb01*n{vDmp}b$!WJgt(RwJ4KK?pWi zhGp03MwsdHuk5-iP6qHNd#%VRMIm-LIXf7h(->C%G77GzhNQjj_<O!xxP!j7<@$q_ zS={@?lf|;bV<nekco%d%c57MR(vsbQdLV>s!C9!VV{#GI?WXo($TTze$*kgBVKc+4 zZm9fBE!ry{H8Z#wYZGPLu-6q3qhaKfOdWHq?Wcv@Z<mvR&dg=%hyA9_G<OgL2Lcu4 zq*XHhcj+1b?B#ES8Hp-yowyz3y{aTpHI-u%y-oScEvv<GiAD})7jNLW@RvE)i52um zY<9jb$=?Wcqe`JjTnZmbN+6<{sLeTFyl2m(O*kjm8o6LSaik!UEQU9S#PxG|Ksw&9 zURp|gBbEu>j=k_#7Bei1LP61_czl%4ctN>m4lW!M+q=x^cD>@E5ES-$O43o|0=RYE zJqIDDgVW<}Mocf2Y*P-p`n|T~a{7_17)(pK6c9rK<{h!04w3``V#-ftFuJ_sMft6L zz3DQ+2ZsO`0P8dN^PgDWjiDyftfURfp<4*$cBcbyB<x2Uow;_-2v^?&Q@c28uJY57 zcNaqoH$!4t;YgUa410X;LH?v|9js2Fy3c3Dvx$2n7PpvcaCW-fgdg4Qv+N$Wfr%td zbzBG8y@G0cP0nriB=+5r%si0URmaUIIz1o}dQid31vST2^?DFV_qslZGnHjs0D-J* zlDfOHb2qdLmwQ+*ZeK{1vrdtf4=9LGVABAvJ^4)&(%vuwucImeUTmD0(1Wsey2JfW z;=q*sdAHm<B{V&lcS8Bt(ZzA;@sf9#L!$PFf%SNK)dqbB`;tJ-)N)jlUqV?WvnmVS zUyvAv#Fmi?PG_{D6qz1B)eClgHxz#~YrmAI;kvlDyo3dCM0GD^k#v3$qO^5(S|!Hy zVt<|;V3p8M`hzyWCEid!o(>n`?9Lmv#NPJ;duP0)9p>=9o;@#^{!JO&`vjhdr#x<6 zLN$R%a)IR*%vTz!e1t!^7smuJ@U?qS@DuNn;p9OrjN)f~{$TrtWaQ+cPcuZwyFHd# zLt~tg2j6_a-+yw7@B99<0$F92D(S9JF(NI)Dmy9etxP0U^`>b*YyL|Zwg_O>ki><= z8_dWaSm>NtAkC5t>5`_7lcJ1~{$ev#wS<p+&_K>d+!-Vq!?V;ju4W>cHa5n5S|2v( zD}$6>?4rAgD}!W8-aQYSC;uyV=FB4ZPjtLh9L!X!AYN6GW1ZxZ4-DLVeJRgfRR^JJ zezW0Iz3qS)1M5i^#T>Cp`0r3avK4T#MPBIklK%l#SXm&7Y(@N@S8;Ca1U7R?2`O9B zhU~&Q79`>6@iQf&x!PcrU>?D+SeN5jqaU)q{l=Lx9&R1v7m01^MWKA{@s#yW$s*1= zXvX|DaZBOUxr;Up=rx76dL0b42lzDvc*PF9W3#H*T5eD#_6dA%O5eSvLv`@hK~0!m zh`NPe>;jdA9MK=?ROnH)(LYRFZf`#khQ1Pj3d6Cp=%JZr6>P|l*a9Rl8F&@mT|t7v zq(4Un9g+Ek9{F=f_l7#>quEvB-7&>a$&{xaZ>6?!R6d}&G&;{ASa`4Z?%MvPxH>PJ z$|+W<Lx8`#V(IY*T=3U&8(Tgx-~Yk)EXSr~Hf(xO&u{WAV#TKy5y94X46iB84I*3K z+sV`AHjc*0GSZ`9>2pjN)9OVE%6g3^WaTJ55nm>{#{w5rD-~H0NF_q&G=8D871x=l zno5{?IAI4rlT?8N{CDMfqY|m2p*Kd6Talz@*tZxer2B+{)E;9V3qT*AKdu*Xc=&J_ z`w`~2D#A()F50lmykQ45y#Y}xRnc`rqOFjdXgdSkMH!zz$dhg_<>~Si79cJfU~6Ky zaSzF=tvNO~w&llzWLi>Z_2iC|b0ZtS0c>QYx7kZKI>SgC`4zY%uPxyx`?w%C@sZd^ z_x8?-*l<0?$#%gy_k>voIU6ABB8<eg59hQ3c(pY-UJ^J&qA5pktT&-&>u{2~7|NrA zYcGH+`TTCepK(0>Eq6fo)84f50S#be;ipQW@(51oUQ+lCuzkW-ovV^~@J$#;;1E<y zJ(4Swy!$dqj2yshdX6yPzekBW+REpBjX<!R$+gd!EXPukMy^XZ=O;Yg4Y)4;AQPxV zz9?<w6hwmey{{iHoaIo$zCHxyh_U7^duB(`8}Rgd6Zl+qk4!v{u>oFWe}=};c&n~T z)q`xEI8%pllm%wN8O9?|zWmR&{7{T!lG?nqYaPVluK!>{wsGcgE!(^|Ar-};iq5hr zE^bUI2f!1TX4yX-o`X84D*5XiNL68D)m6*~Am1DJshTKNsfSB$hsJmaa7|+H_7C@3 ziNBSW2(5KN*JqMi-l>wnOXA|bR#J!LVg9(YjdU`dy?Ly}64~ARx}xHktvSrI$6SD5 zwF@rT17YuRy3hQMn$>d#A9B-ESt>0-Y6hEEUO-f`;{|}a%$M?Pn-kuoRLk_YE9igL znab%3Rf78X&SUP(aEUC`&;1SIAz^!?9tNRd&_*5D)hIJSZhqFQHN)Q=(8L~|nM)r} zX8}cd)rOn!)b|x;ODea-?y1VG+}b{E`Jqi3-nt5Tr7Caxsx1$``5}^i(`&d#bQ;?E zz5R~Jp6|YkwAC!6QdEc`We5F}FqkY-!!*^xkn5Fsc~#ut+;lj6=_&)H1=XdT65*rE zvr5ik_5QA8akH&?W>%P5^9OB#o@qZ+3J0&v$$zCjcZ-ZXAbF^e>o>w8^^q7*C_K1} z01fJL1-Wu%A|yb!r6*J;V$}>Y;-QmNp7FYn7YCfXW(9ESg-OC)fch{q6beMUHxE*D z;>}|xRn8f}JGqzm<^$($Uy1)v1?O?ho5U2f1}7C1D-NlVRJdzNP5!%KBT!aWJ&t!s zU=CuiZGRgYY3c6GjM{Lo7rZ37cmgUGZi8iL>i+c(c^na>G4XZvba~`lOQQM%1{_x0 z?XqV%On%v2yerYVKsE1=DqeD|tB4k&K}Dmb8NUT7Hz5D5=Hxs(mwS>)my|6eNHP4D zkM1zv---9!O6rX;RN+JvHf{>z3%;L80R$DRDorab0>|kpJ^$R0(UhIqmf$o+AgZDl zjFTVPwPr6t@rZG<j8))#tlO$By*)Hd5#_kB4eOIgk!|jlEY|hv9YqrPc5G&as7M$F zt3p->sU!EMNdymE0OKNk6NPB({JqFz2kz<V$WBMj{>#_Wznki*TWrp`FjSs}6(!i) zmuuJT`lPS9IOIvZ&#o#fF--R_iL|JhPJyb(5(l~a1$$>M45Q=|j<CXEfQd|m!Zw{9 z(?-HkXN`Y;<zx?XBd_HGR{D)SRcf+e!^Tj(Et@E+c~V;bIG(o~kk*uVNQ_jm2GHLg zX$zw843d~@0k+Q&IeK@$e9uTq+K$kH;fb#DKdVyW{FW2?)=i8si&yWWut+MKxmIiL zPw2dB%oY^C$r?a_3AdhZD<o3_demiZ!13wn)4^S?$U(;Q&r>4J_8JaShi~eT+^Img zFyA;<nqIoRVIwS&kiF77rkvfwq#(cbFR2Wg`+S>?AAkLzpw-L%;@(oM2v#&-zz#3R z7!E+0pS76^xAx!C3`1ae?tBo12v{*;CiPK^C*&;vf{pdaTz9BGJE*|;K*z<oaev_V z-PUyXhBghiG}INcHhSNBG!>V9kEFb+f;O?xfyTCY1b(aBi^G4C6?i;Jro&3IyP)fB zG*>;VH5@8xM(SmMl9|PDuQ=x3IhgnyMRjQN6D%XWRRS%v5f}^g+XdGEByd=CXu-zr z!N&U<Qb5g4Za!ftCHQ+_h3x@@MQo<LRxQaTcj8~!SM{hxl4q2b@+R>Z4iLZs$o3$o zLV@soE49dN$o)GSF!ZL1haMUNqUnz>q=#>$8(xWfrfz9sO<-fx3@>6o)C|+o^3uki ztx=CM34bjdKnJ(nRe>$?xcSMKHxfsw#I9@P6n#X=8d=q`ZN-l-#`?NlV%|bzp7d>d zRp4A)y$?h`{WWZx!(sBc^!1Oa^c%$9YH5kC3mkz;j@+InNn82UCHVpdNQk8*w060$ zFY8O-@R?nqYSEi79nL(1RvWZWIT7X(ffaV>-rkJLk$cM}i(F8cTE&@jf)O!%=74+K zT@;g$p&=41@mpVs!1!nRJ>TwucGtR`&q((7moKUq^brTv1Pp0|$Xt&0tJhBsu&NMW zA<t`h(V%a+;3(+;JEU;x&?R)465&LlAs+71#rL*AbRvDuYQ{PXF?jfvA0C^X9V9y( z%<F)xmedIIErDS?DF^h0Ic-Zi+C&G@;%*-!p~1fVRVBpsPm-eo1BFE84?-%1(Zt{Z z#V(GSop7{x^bwqZU*Eq*2cXHZ+~BT@k^$Vv6jf?k<MibAe@h54_#We!4&-%s6@@;2 zKQ5D+F#;mOAr45yo$3wl;*!qa29(0PL}47zvB%m}NI$ZWyN;=;&4<_U`gYyWMp>%0 z_-6P(%Lj6pJ7jM|l44-fFrCB0YEa5|-8!mt6g_N!5ovR5DU$b$t>Tbgbm_~L+Jj3y zj-urBe1RAoBycKaU)Oolk>4AJBpb3(32e5!M-dJZftNDjN@7*bY1Q5VMc1PLI#BjM zUeGYPU5<uGk(Q>COP|jS+thBpOVm7qs&l>o7#7-UlU3I4o8El+{}}>A&G#)i%p<b5 zgE7a^K?`C)_k%HX^rL5f9>N`dqb#vdIaJMAYX(5=iaCGDO-|ntQdMV41l-ddJ-jU( z(O}f@Pfpf_8(X2SexGxGPK1v>&(~96Ax5}wc&s>N&epN`anX`FSJFfO;5tC`DLfZY z*W;LL!UOz0ZEd8PlsC?K4?5;XXXXERNvg_~B9Y{Zr_V^`nJ<hM+}0XrqfOE<51xk` zs?R*?!!Du8MOewqqbL_>EWqr%OMHEosF^i8R{k%?(p#*Tz9l@1tZC}}kNL?SCRb^k z))e+z;x?{hF(Wbs`U)<k-;w@iryc`y+cDzp05T+yYC2M<hwfx>4=sO30V?9NRz*^0 ztb|kjVKv}Gu+7ZelB4ivAWK6_E;-`*dW}6eg4QHk-hg|Qn+OmEZi#sNoZB!0IodK( zp;@Tm%wvWz9|T`hlC!G`TmDWp!czI8Yg6Y@4npcCo~Q5Lwy?L)y!;N8B0n*K2uo*s zsmXgA-atr(rHG4M9Z^C8dp7*5D79y5TkUXLc5epVY~iJZz@Yr^N4kI2;&0|D>G3|y zEmZ*H<P^*d%RTwWOEs_KAEsIR5n*o~XbpuQ&*v~V%H9Tv4L9V9HHm%HT7yO?k$gV) zJPMpT_L&&e_>PN3S{O<Gwn1GD01aD9JI|ignqo_|jf(8u4UU3GHSq|vRuUj8#wh}_ z!s)cR)W`iiUv1YJBh$54G#5}{#7GX1=wL&~I7tWZfa#kzH)SnbS}q_iQzE&R`T|8S zFw@J;=DY{)(u<zy(7t^MBD<B<s*_RI!nnV4n1j~al-5?PKc**rRE~m4RyV<(L)m~1 zQP^uSoJ@nQf?hr;>C%2WQTh(!JU3^0q&Tf^5`H`z^k-)GDX5N{4=0L<p)cRlTv*Tn zxg$S)5^rFET!RKCI;pv4S>Ymt?;_0L<9Y|3RLSJxK~IBL`vPwG#mMk^SLoZt(KRIE zrIS_P(k>}Z_fBV9@-|nWHo;{)i~xCDL%7eqh|f~jhT*U<<(!Vc!;}YKQU8W|GF;;X z15^ywXrc)aT{1UxMB5{<At$@o@F@}^p0)6=okKmeLyqC`k=6Pq&e&_~s;dIZB-}3k z7I<qVYsNGyVFT^OxAebQRe@T<I@W_~a1nh*wH$nsmrA*iD?NN>D{zWxlmt{3&p=?J z@+LL)(%)I0jxYUTYoN3Zppp;m^zwFv?{U6%l>_4gu4HoXQ|H6R8DY~0kFDY-40KfY z$X2}|=Y5|&U=h2vuuiRQoud&PN1O>wK>SpNDDf98My-VTz4wy2=CW1ls5-gNx4vas zCg-+!H1PUi6v1Q$8Xu5w3I1}y9Oj61GH(LQ(D8F5{SNb%=v&;z0SC#dKPcE)gT+~A z+z|ESQ=3RAClEsbp0~iabLhMTi#{`s1K^oPGjVOnKO$}2;l2dK0t!Fj=%yI-Yqgna z3$H)uN&#>fo7tmv16MN<d>UxcutJ-SHip?iulew>vpE3S4(E#$JUjmG?D_i!ym6!7 zPv}fI6*vw(1|oXP-7B2uLltla2Pc|g;X|<$ZQk`7IU7`s^i@s7VAjw@S79kFm<pdi zMe?@*r5M>QcXwxcaI;Yv4?LrIUnA*^dq(E6-`h(jIG^}@e#h~!xnszD>0bavBkEMx zTWP3E=;5RQTa~%}m;t2idPBY9aHcp&jpB!V`{BzX>%t9wxC#3`Zx-sv5kWD2w%yw$ zUhE?X)IR&c=~xz=JEPP_kuEI47lAnsZ#LX(BiW|135Ux;f@5r(kD{TXCMO`hU6O%i zkARzn+pQ%lZP9rPmTwpLKtX!56bWs6w%$g=6$L%AA31;XiN-h@IdpM2b5>am$Fokp z@aPhOr(@)=un_wymtzK@n5mH-ulA*XpqhPK$Gvo&%A&sf6iLc1=)aOnJ^CvjYE>nM zzPU1N;@!{D8TN<r7z#A*=uivFVx?AtnOU#?oGdId+b26><_HhNM%45q+vch$pQvXj zjIwXLxR;?dV&C&bNs3RMY9xwO(HFyM#;Rb+yWfnU9$E)mU-CzpJW|p<+o!|ai?kIb zi|x~;o=u4;=|V)fJQfdkFx56acntm%19MfuFeVS~?|0wbn*Z1{^^I9360t<c>C;n= z<%XSSj}P}{XWj2g%{(-u$gy9G#wF{lV!kCM0yJyAu_yvfm%2iRF<FVt6z#cffh^c^ ztghD&#i}f{o*|M*SUj*QL5?LE+VvW}xh$?17X=_a$y(A|nr#zhX<UVIa5gBm?S{J# z4*F}HR*$aE1Y?tfYA$Ig0>O=PW6##fN_8d@VvId^R`HXr3mQcGR((CSTTB^}9kd;v z9veoa04b2@PqFu#mrT(p&PKY`?>~?cenM~k+6x9vWZR_lI20twW^z8+aO^n+o!8_Z z^wvb*L0o7!#Y!w(oRoEG&y1z=C_D^Xu6^Oj;$7hm63dZN_8H}BLBi1p5@!v+8NPQB zt#XIICBj!KuyT>G?`VjX`WQvdDL!^1<Bf#C+8O)QJ3yE!4{h=<nwvr~_@xK3g)^5> z301y{qo*o{bDm%3sHH~HW<5w&TuDS9Q2qEv)hco+EeJB8vj%QIfweC0zS-94&au<2 z_m|4oi2|(;+r4+%VaPdsb_C^r&?Z^Gy_GVa;gqpb1cHMa%}3NZGoR|xbBwauypTOy z6<je6pEHa*=%3Y5e~{m;m(GD>*C1gDar9xld7O<gx%H_LraJ`X9_|*V)*t=4U!gjh zU2%m=rg)-4M5b^jCn1GwByME-Jw&~G+<DNe$$#s3q~)ig0}6Y8?3?rVb{9_TRc-d3 z7_r&+-+!LM8x`t*5BztXn5`_|K?U2?<1U@M9rf6wn;FWf=M2B^t2uCI>$ZP*tq&hO zT=Pdolzd%TTis;W$CkD~%7hz#@Q(Xh4CXYpsSH>49I;MiPZyUYsn6DXm2F}bj*LRs z6cxEAFMFM_nTLpYO_^4(7|aaD`K1?FxII~>njAk8o8P0#rh3Nt43E}!jMOD8<?2aL zZ{2ob?4xV#p85of3ok~S9OtrYL~@^<EOV)uxKg}&v%i;3#(0~;%&SmtAg#EyvR{_u z_QdB*54e^~=o){wntW4VDPfr@?Vk5N!*9eYo1Ng<u<UK$Wf6L05xs3hIP`z$S83!x zKd;_k_mIN7l+d=ND;b0uBc5~@@|s<3*SI#mL#ULfj&!a|%`Hl`ocf?VMo~pmJ%w~Z zR+XG2A9sM5d?TACW9&y;L6r98RVc8u^k{afDQWJiszj7UJIAkREQdWD+2mfFh)XE@ zTOZ3-!&m#-*ONrXzZFyAtlT@Gj;%$v9Q$TagVW5R{*n@5qWXrOf4=498?V;h$CWN9 zr|X+ayzi$M_|=+x>r=th5m!T0Zbo?m$GFZ@S9L72UcQ2hn(FwOGU%x{TW-~`TJsw; zz`YYE+1VO#TgxxW5|rboRCA}C#qioD^$~gYb-RY}1asq(d|`rKEHZP?awOV|k;1aK zs>gBi9In1^xxH-o=W7;^ryN|@lHZev5#jqWn$%P@V0hWdo}ir66tPJ6b1j-<yN-SI z0yd>nu|cbC^2j5Kn&0tfuPVF@*ox!_&6EmERKIJnpC&O3(;h4%R0f{bv!tA!7#BVO z#sK)c`=dtSlz?DFI9F-drrs?K3z8~n76X@Df4pFS?rF3bz*{ZnM+#=!Cn}UjbjUq$ z=g2m+o~Br0-_Fx#Q$nZEBq-oYX1iZ~7nd++BLC(3QKBOWz_&tf6p|#jG+7|YgAFMK zylmO+5s?~J8+(NqWw-IA`vAXH9k^J(WzoQeWvWDS;tg6;;p4WMZe_LsK1JE(B*@B? zlT<=wPHzRHhxV36hWhf$rC2vLSXcGv_Le930#3s$W{>4INm!WxIs>YwB+Z#-#=~~q z9ub2mO9<PJG4k_cs8^nSDWenmo5wc|`|(x<F3N#TiVZ>P<|HXc;#}t@qsjbD%e@UN zPnW_{#fB@V<wxVZNM5nGND&^1`o5ZLdLU&P!h@Xxy*cS|mhGHk@`J%521<ggUE6mO z9hDVRIjnT@d*Ay?IJ*{mj3(L-7O{yaaq$FmCtv%bZUxtdW7xBd>+#AB?Qi!95*c?g z-BQ&oT1}w^M0_|cV>X_rr^-6U{t?u)KJDJ}Qc2~WSmn<Dlt<rrO}F&Yvv=Y6nD{>a z<dnSXxc*XN=HJW#t0GOv-H5iYGFE&()Y)N~Cf`A`g2A-qrCy&h?gMTe98GR~{pa&5 zko({pI2FL-U`v$h3HBB-W-;4<qiL7!crmvm$(C9S9KY=Jh-j>Sui4aVrEW38DH{Ba zh_+dweR9y7(#DyFYYB3VQnXVktqpiF;lkxj4Q0YcP?&QI;)IEmFCz&FY2*G~n|hyj zjgtT1=I7dbzgTjKnLN?)X9)Wm>+TU8EioXCezy(K-234C#g(drTSbqWF=ted+(|fX zLDdUYa}LSvxBpJCAxaN!?A^h*cvJ+Ork!1<MsjDerl$^3`s~w;E+@ChaSG;eGrzc@ zlHlXe3xD|iSzf*|iSbSw>>SYVQnsm4L*yI}ap9Pv$W+PqqwLeUcNP()ydGov{$kt$ z<$Cv@Q%+etR(_U$7n_}NIn968Yoe@oKOfEebD~?z(Y4e(htCv|X^N0rvr7@jxP%5` z_dTyO%-zKD+5UXnQo{T}=72?!75+@&YAPPXws&y%-n-0NVf=0}MaM4Q1T7GXrdwSn zzOai1&mwJGqAOm*m$FnEWgwB~=w&I(c7VbX-!4U_aP!3m5`5m2*3rkO_9_T=#* zC*>cU(q*hzXKa1I9d^WhVF~WYSD=MLV@OQe+B`Zr?IWm>;#Kp=|D$8TJ-yYvX>pX} zCZ$H%N}XkMK|)3eQwAZ4()c&!TH5(N4N4C?!Zr{YM}LXJ0EG)VDGYAmovJ%JipNtW zEVR~A6=N^GiEy7R8833}yc&%y`{eOOUnb&j?NdalqG0behCoVhGNx^RrpcoBS#F&0 z`U~&&yXF=!PD>}Q%urNxNQN}MvD?~fw$i`Bv*$aOZQq;1NDUf}m6E>0MYK53HE2!b z!h651Fiz&?ZI=73*-8Cqz#Z3_Av4|?G~@Yhfv`lWm)AS}!HB;dPvX0qlKd^zj9N`_ zR-#rn6}t&}P=$PcjJ^&?F+aGLIvwj_?fXrm{%_87yOk9ue=B683a<!@D%fnalyHd5 z980-Db32AAH{0s80$rs=pL;Fq6BiA5HRX4%cjc1qgM56wC8>8NboT=!Lw<S=$a5k} zwZ|}5koER>Mk)`{eEw9?W3LBmFmRV!YtDJUk+Z*%%dYcoG)$+hl#Hp@OV{(UR_;?D zeyz+(Rvz4I0a-9Mn{eOHc<HkstlE)oZ)imuJB~?oSF26yoC@IPql3}X{C$s7-_LX` ztN(+esU{~hCE|SDeFCW|Gm}QH!S`!#|I-fw@Cx>NG7`3fXwC!txy{4R1611c&U>9W zN=+-?FM=b->Jmxdo#BkA_Xb-vz$oZ$KNaW|!{Z>sO}M?L6c{GZ#|-gl{@0e?jJSba zt^dnt_j4`f65d2)BbUFo`BYNb+?LQ--Z>;XkyFo{v2z{2e`tF%1b$yeCNF)iOkERv z)G_wa?(Of?l3CUbclulNLjn{>NYlt-15FXtBDLYwO_P{<XTd#)sZ)=rn!BH$PP)zx zWDSvLe+^Pf&Mv-CsnXbgUa9E(rSMOu1stiGj-Pj1eD$xEt?x}vWQ8Zt<!j$TCysb) z#ltoiY(9qX-_A@k@qLmXM)LbB0KA-c%g$${KKs1x!gNcMu=g2mb@Y(L0%`{P)Th2p zjV1|=VXf-P=M@{aAI|`KY4h1rvxnFmNIXIsnSn|BOZ(3o=N9#kUTpWZPcM3U(lgfU zoRD{4g!>rcu)cF6_2|M!bi9n0Yfyh(TDy~=CWYjfxw^2O&*ATptT)E-fn2$TUn4@* z$DLh6?9Y_VD6<=;-WWeFo2~Ob|F$ySH?`uJ2!{Bu*`oDqa>(%vc^-nY`jil5Hpu@m z#FLQihSI$k0R2qR3qu$dfm8pk>yr%1p+{x5QB-vyQK$zkra!rc$28Q<IkZkx2+Vmr zlK5WsM&;}6DVA~4<+)UKVRv)i@q#H#NTPo*$cx59U(8JyRa2U1#gxhAkc=6MX>K_y z`4%abocHO<pJK4TbOIhvK&_*FYD8rAFs4UMjWud@NFLOBaRh5T7{ZQaoyJZPy#ra1 zslArDY0h!q`TzDEyK5Qa?~A!udVhcVU#)7c?(UObxpVHtCn`nMixfFi;J>H!E;#A7 zFXV%~9`VrEVI~cf0t1oM0XjBw=SZ3tBY%#a)U?WJ_bTo*-~R7o0ruvn-u6Tm#2RIH z#5zBAAMhV9U|$w-n4D2*f8yll?BzS%0tsXao*(H*W(C|=Jo4g*EI};~$#G&=ol}B_ z6mTV@$x=X-=lK*PsV?2;>nHC)DUoz$)tMIN?Gw^7%9G6R`_L>XmUlwQzj*Rqd%+k4 zY5B$du9f%Iq?Slj5dns>jS0qehL|~}cJNsch~+mtQj`AH+;(cV)h$KB@?!p3DnNVc z+*|rz`YW!~RWFi$PE}4W>G<b+lU;&pk-UGey2@Z-f?9J)vQ=oJzY%72nk}xOYPHm8 zMsv-YjExoff3ZKZ4v~yQ4^SP8TDr*i`O96Kn?K>ok1UzF;_uXCkrcC1`pwxSM&K%e zxuWb^nsZ&zk3nE1#Cdi{X!zkxqnAo4W~*ES*U@)@vNaUG<hE3}CCF!{`Ero5&QhZ? z=`|UqfnIuSY3P9DbB~t0XU0w;k1N1Xp^=?ZXJ~AIQ-LQy%+1vG?S0*w|1dh25=IRD zcG_GdL>u`?=5fCKNMLm1@2!*qqlDzK6Te;>wPJ8-IN~MT%+VW?$~bzvLa;gHLp3qi z2b-UINX_wFOxRl|C#D#0oBypeY|=T1Ltn2%WY|{ZTeBxwUL+~-WGpT=X+4)L|Bx#6 zoGU;<zTrE=nLMF*s9Jm9eo?o_1}P{ssL<R=!H1{x9~GD6G8?KC`H}PVUM1BPR6Xzk z`B3&H8Rl+gE#6MiK2K0<R<ToA{{Q#dR01!@gb#nWxSnJ0YT)Yq>!sg0hC(|VmTsrm zFD2+PnFIR1g=cx@2P8%Y{$c5A2yI!?`(g`0QPS|unzCOYE#-eQ&J%TBm$-@EbMzuw zopBQ>T=4|#84F<<X~b<`(p$cYpr_o|O^Lt1uu;wf#+&D|+aL$rHHr$n!WSrB{iKP9 zoi3Nrws%p$-JOkw*r=vo8?|S+^>GSEFXA6IHpKR&zIgYsg~?SXSYZZqoLXBRb{ean zJhgasinr}*P=8xW|M44P`-Wm{@3pQ}0s0#GPy=U^zq2?ZhhNYE*&u&9YEENv@4|wj zE|#!<B$#yJD<3F>$-<8p<}ClsD$wgcp6ZR<<PS~fG29)-ui;{wUD%xQN7P1-#f1FX z<Uym7C%<0n-owkeYY2ir4q?Z$=6=nNyG^y>vJKTpkl0yYk$;~3@to7IozMh`j4-R^ z_|6oP4vG!S87qG=L)>g&nZIq^CRkuyqjle>Y2GOd{GdFc@A^(Zab2|>H_`o)qC}f3 z_;dm~0{Jw)yUVCs<yyF|S~--UO7pPUtSbxi$f)pZ`Jz7Zaurb>a&aTw=Xrh%t`_p4 z|LvbL@SWw~eT9o~K247?N=O^iU)Wkh7o(g(eXCplcz#(;RNuK@Y(87Ou!r<4&i5Y) zo*-X$?PE=@8BJH?U+Y>TlFqyN+~sv-25<KVN-q6InsgFGBKb(bJ{{BWJ+`7dM3dMV z893#`{_7Wqovd@8s4E1AIosDK5^DObzt&|`x*D4N#z$0PP_d~Z-_q5=_4LBmnwKXT zO!KmjZjUk+<|;Mc#A^Bfj+D4+J=R>iije#_v&~f16n6~yxRt5|di~2&t%43iFL$dh z>S%tf32{h1rO&2xU1RR+hW+E&vl_46*G-E*9`ft-hE%Yg;8d*xUeWdYZB|7`76yFw z-f9)wQjoF^Gx$recbFl|IJ&?x9cz3tp%`R8q#nbGzmoM`{at8!t?>eVgD>u99|+<| zg4g(K^%Llp4;Hvdpv#|+y<Cv8iyU#IeJ<-YOZV3-?j)vgFI75{9e`s+In`P?WsnoJ zu$Q|mCjF=H7mF?levR`>(eTZj68XgdRZb9%w;&p?DC%;qmn7vUEWD)M&&}`+0(FnU zLi+(81$EaiP&eYkc7|i%)U$Pxa^R>ht6cwL_bv=5nQC=$qD~ISaXSyYf|dH~&v8mT zwO1)?F!2u?><7Ub=2r9U8Sv=BU-b-*xb_P<97gR^QStr5PtPr!d+HZQIxjgMuDd5Z z?Be-L89O3bs4m31ESI5!GxA8jae(1(<Zd)iKI$L7S^2&CJz6iwx3-6{vl;IhmJ4K( zye&_g4V=naC%FkT-@b}{hxPH-6l2MmXUP%Nv#Z$otX04MeBFNoD?~1XPJT(TG5%;> zKptr(k&kn~1fxGl*<wY+olOMgT-hj3RHbpxlle2bGq*NXxc^7OX*;6y!zR|Z|2uTm zsqKQS4T2OmVJ?EPd$!<*4CC^GWQvj)OcmbUux-iPT7rO$!D86a>=(}XP66HI^C}IY z4~bABKx7juh|xt`wXm#es7`<Ia!#(~CYs;qbBu}%=6NvS?zn%@)IFYzE1EENH-KZ* zY23fQrKM@OjMuezT?UT0`M;TCPt*)8v=%ny?xiEH1^0`kmzYQ4&4U|Q6Rfif1gVa- za-HB4sDTDqp&w+#xi`<8rPUq`eWzPo($RQ9Y(wt~hQDF%g2bR&_!j4P>Dy^>3Dm42 zE-VjJ&h~O~eYQZw-tJh|MPAOAFhQd9(FRsFW8}^}c_~VDn^^L4rD7w$5y+w%9eksb zvTT7G8IRj!8@AX4XxkA)%Ncr6N@9xd!onzP+mqp2(>h-Ih<x#oM%;1LLS?gsl^MF9 zXa5$&$>;|{rHX^svue^gLOvANx=oK;?*8q_T#aUB6<&_6|1Ai$lukzt)k900)s~V! zd`g@;<7&2W5{w!yGK2z^kFNOn<K>EcEofvizkYnR2MVq7p%u-31&0Y0+o(o6y%X~y zg<3unUFP46My45S1HH`HmlmX*+ozbosbzxHN?h!H4>R4;)y!KB7iKOroQG>TPHaiv zZf@Awrz}dQ3|ti!zV+<w_oG1^tJ{m*Zh-zPHn7YXGDe#gDAFvghb@yRgZlJZUxYq) zc!4_{$LutUjxMDLf)gdq#2!)@OdCj=7s;h!Qxw<G`?cYVi~_j`dPZj2aUqM=!wXXh z9-SrSwdM-S9`aR(<Zzl*HR->>)A7JuU-i=$b^2b+a*2lTgDIhf#reG%37X;)q<+QX z=npd6^i?evCPHg7RCP!`wu0gbf?TWkl&wt5zcSArrD4;Tpr-0#B0*T$j*YDAtoIC) z1;*FzyUzLo#&-qp?AQd}sbAoohz5%4>Et>su9Ua90Za9r=yM74+gZBCYe?TIoXe>F zcP@GeTu;0y1L^aN&Jb<0Ql-MJR0_z+0EJzRhO|W5rUj0ZeBMP<S><|dU54V`MYvS; z#SNDkCoLRy$qac~(4Lu~oYOG!9g<}X^yr7?cP3!TE+f5-Ea&5;pX7)D*PRXO#{TsA zBa;y@cOg0VEn)9;lW45mz3Kv}us~tz{uymZ#Z8X_a=_rdXXp|}BI)PRFN%D2_Q2ig znSA0=0Q)3<bfCu=CM2glT=+3bH+@WmHFCUk>f^yh6sxkQfzw~juguHF>ZA}x*XFsh zDJ)NLr%qkjWc$LYcK$XVsBFHJ;*b55w~n3j^|5DfTUf!Kp*qGET(kjf%thXYu9^~R zn7bftqb^z{<8!;VQx!K5rPl>}w=#|~+7@<4p2Y;m`W{>nC4>WzeLA^|rfK|(DQ(|X zu!gX8V|b5eBa53cxS*w0-|OMSy-6K|HdUQ`acX>|hH+|vlWkwc1bP+n?2N(b4GhWA zWG68E7CzS7SI@8rQr2D(b#y756BSc;S!)>##!re7jaB0*Dtzo!+o^8-e{xknyI*Xi zN>?_X=UA#x><QKCFY485+(Z<&hLDNS|6T6(XXtQKRz3{)h^0GQC+8Y*NKYdk>Z;in z`cAjGy32m&st&z4<vzBG(X}v3qmTC)r<N+MEBm|?im-g+R~n!3^a2~v!#K17*#?v2 zr8DG@yBc$7dIm}hf|7F5irl>E<E1{>yeM@I-}HQcSK7i!SfO0xl~Lv&rHAz;kvdZI zxy!$PYSvdhpUjC^)E$n(ip=|MX0y*0*nNdZt$xs3V#NL?d?A$d<oE^MG~@EzrDUr} zu5NgVB?B_uQrb?mE(l4gN>Ms|P6CIyMrz<Hc}4Ly)HM3F<IRT>(g&2`Pbi%M7cG%% ztTPZvPrE<QeHXU-8T6|+7(WUq&j?<eu{&_el>Hyee1Va)FX*zVki96nr@r7}-(r1V z;68y<3uG?0CJS?tn+!R|^JMP$v+zu^(i<Y!?k2%szk7Ciwt|(pu;VeCO;v59&ZDO0 zI+l=;IhG7*NqWZcP`nc818O9ht#Ry@C^hCp$A59&GCvXOS>H!c5!7!k;%|~(Q)<15 z<;^G>@&ESo%?!Gqf$F<2>XP6F%A&HTES2ZV3o0jNhn+E>Fg#(Uomje<Y{N0esg#8~ zxb!%=1yRE5c_g{;;-4BqMzhiLTad9XUcN!)bO3@ci1j2J8~zdil26q)v)0R%UJ?`y z_$sf<87O(Id|+YzyPfFqJD~|kqB;vPlzvMg%T~>W-!_QQ2GkookC!4)5+J77xIV={ z{rrO9t}aSU@#cYnoDmBuSB1FtvraKq%^!n+Ja!ICTEkaw1;r8v@~tCGmGN}l<MYCy z`@_|vO>-(UL1N(bT@3=XWd?Q&;?4PMAL|bdU#%6Ts31R=tY;9Ksz|y%@i?D6#n#pk z+P(y?_)??5sYsT|{HB(gdkp9g-sEboCWz-Z@6SE<dNNOro%IMYSiK<}9B&d6T3w^< zf&TRPd-MEL9jjy<Dst}jqULBW;=-+pPeoqtt_!9S)fV;%N5AI?*NiAP(P13|EayRn z<HFKh$J-gLfyxJ0bUXl(R^%CaFL?IXxoWIwBEv62+XI5LgBO>I3Bn#Iq75#ruD5d; zV|Mc#cVq=-YM=q$aB|I>KUf)$bsd#wIAQBbAE7?xS(SdJ5g)yFK|5`HWx{wt+Qk`C zJVbz_VnZb@$gl@78uCeD&9H81%qFcXeI|!D-|CAb3mZEwoO7e{3f6LsBI6ZQZ%nqu zG1=Xq?)bcBVDy<X%hGqcvxsy!!rjSoqHz{ZEInC(hkZ^kKuGp5(RV#_?%|Z<{LM;D zD)LAcctiAULp5^J^?H9zOGDM8^UTtGGCehZuTCi91AkyZ-A(pQtfP#?1*-Lr)iDwS zKmERhbjt;-mu);vb2l1aFhrhxT+O%~sEk?K3BpFRrN1>EOyONvyfmXS1wWoxK^4Oi zB+k(#3^_)vM)MziPT^Rz`X+}zB8;w~TCKAp{}5Y*Q8O<d0}_VsuzG@~;@6cP0cniN zDN4y|$1N7@fr-quH143OeEisQa1m93hh4*(V_cm-^7{<cX#-t9L?}{N3QdsZ%;2Wg z&o6~q6=&J%pIoQSMdt?<3|F}tuz#>NEBxFH%5fZAL00&2BBhGd^fZKysfup-`4bUQ z=Lr%_WvnYb2gu{bSNela`O7;NL>6UDk74L$R_k%X{smr?*C@*MGI#xZo;f6+uVSnX zRMuHRO$7<;&tVrsYe;`7+#lsRlRr3)c#Zs?vqn*o*UV#?fS<onZ#mH8cQ?c!#sntM zkXB3;PRReh@{gZCQWl?Lr)U)IUqMyHn7y{iO-6d{qf1azjHfy~b1LV)rPPuPOYD}b zLPNW8-Lao+6R=0os7{!5=Qz<ze$ZWG_B4CJr4e;PW&?x_(OUyoKA4U^B{k1AQSvgI zS$Z~v?cZF)PWiwoe@5`_pP!-X&dedu!4<Xs^5RmuU~lOEaiDzPBaPW-Jf)I247MM3 zOKr7U(EjDW)dfwh5)3dog;~-sdfA4tMSqT!&6ElBO2Zr2Y1_u#vr4RVn%(>nDdm|c zV13sJIZkB1#?;Qn93uQJX0OfDmY&#TpeJ-Laa}2$e6#KG>e3Y2g0@wvJW<K2;^q8p z0zp-M1uER!{iy}ZpQ6Db6~9#&^t{0G0MCr?D^tW3Re66-hG=}*u23}6{zZMK00@{T z3nejSN_&AvPN&o2zkKqi#)I(Ar|W;~*u!``&#}}yg;`UEL7dV;th>8bze~R^WiE7K zq8X9A?CPUVhq<F9z?7divfNVy<~5np6`oc4B~siw)0bi<N{u5a>PGXs758J=Wu-VS zXIOcZ-@2Z@o>@D;CCywVN(~}a1nGpJp1jW9#L%A2{duGU9CsO&>}g2Q=8{|#WK;UU z_tBUH!)khP)6a!PTGQiI8s0u4_^)uwQCF8(dz-SA7}E2H=ECX<)<=z^zgAFhVP(Nn zcUrn=O@;*vn_#49ptp&oaGVH+^n>SM)|x%_)2EDq&nVCM5AQ7cn}_-ZZ?1HtYZ^Nx zyUj}n>J!IgNGI%G@Eg47aY{AW%iGmto|X|GJQ=5gyn@!1{)sac@e*#@xhMIt^Aywm zTF82@QKY?s`W|9M`xJdzAys93UNTa*$6BbmB#&d6+#tb|>#pgnI7a;ZLL{Horv&k^ zPi_x5eG)E!ICv?Ga(@P>G1ryfnJg|1U-K2zyO^u?S-KhA9xkf$^WlHvo&tAJ`*Gqe zxk}Sht;vYLkhukT_+K{By!qIM81^YHhpdgPUPddeZvKATIj&|b^bHcn7MpvVR1{Qc zq4T8MnR|l<6~p}@5pfsicr;M?QNA8qZGO=1+8~<uD%S_=O0z(LWYXfj;F>&70Ozha z26v#c#tH%{53uA5S(%LXg)v9wGQAqeJ8maf4YvFynw^0^?dRoKTJ7b@et2K{<{~QW z{D6d=4Z9l#4P+Jw_xL(vWuRB>PG8<qqovCU=^=?Kt0$JXUaTXRSnAcHuj-s^tj%@d zJ-oWo<&+XiQQ9YqdDe2hXvzNfe%sb1rRA7w^U_nQ_mrRJ7tT-Co<(P^)|gFMM?~Tx zr)~;ik1$R$?#v$y%Gx9YzKyKI+X?A7E~3<JLvJHvn)Wj)3b!_`*s9ejSx=iQ!x0~S zR!^BW5IKW>+T?`ZOdoO`yniEn5w!&hdFhxSJPf6D-Jf@-QJKH-GUb_N&3;(73KdF< zdi&^Z@-^HR6+yHF5x7SQ2XzoZXjg+E<8q49{7s%wZ)t@);KU=MS(beDWbz2jie@=) z8I|&Sd$M00Rd-%OUAAfsLBF0EZ&4)KwSycrz%l2(SFL_JNZSLRq~FUF{xo_xKy`kV z&kyt%>1|-?Zzo7wxOBxPdk!mub#9(1%^!x-z?7@;;|=_{V?!@L!#icu{B~aW!zx91 zXM?~af?^VXVpYb9z^T9Lj}i&<8dpc<ja0w3FY2YNonT5aQrY*$uRou+1OGiP<E(v1 zP=v6Olrzclv}MNr^EzI}tGUeYpE&jSQI-7BA$cV&$yjE7P|9C2PvUnqOdcobY5Nxi z<j6Okpp`svn-5-4E`Xm_)z=;1qHyC)dk1=uffG%7-u%tDS4;Q1m|TX*s^G_7DqY1m z$k2HHxKnnD8$tC=#IRDJyQ^GdNXod85*bRy-2Yc;JT3ygn4lL~h!PYL4d0x6GuPWc zIjD5e1jRL=%s+sQyCLKM)%C1lO<da!QcI6uI6d`+2;A1wT16#_Kn(&~uA-%(2ze<& zq9~MCpkSb;0;yIw6_B=8q@Y4nFeD)fS_Gm%tQM>y=z&2BhG4BC6H%&+KqSdsd*Zp@ z{d4*HwM}Mbt+m(tvG?Rio+(B&K`?F1%Xo5|hzT~YpJlt)na4P1sv<VxGD=D4A#KF_ zcx)aciY6g_He>Wi{?edoXw0moY)s+;t(+d|;D623B?ALXSH-%8H($`^*{~Ke>coBF z)}=FbPte5;v7?c09DM|(@q)Jf7a3p5)S-6tsLbzSJnbafU!O|~UxR&aA=c+xu?rAM z1)FAz#N>B`+NKF{I)wsyX7V|35z>c8I^JjMn60DLz%E!@n=h+%74pKRDey(cF6Apu zYVGT~T(hZD=I-BfKLSHlrey8v-rDk(+bvQXvCCd%ipIh#N@fTGpMW(?qb``6yx8V~ zl&6ciS!t8bbUs+oqmJ+(IZZw&v!R?tt9`n*X97@qA(WrXG89W#Uh+*F5kZo<bdFbz zyLZp;=~zbvC_|I=@bn!#N%2~*GjBKHk@@0-bFdfCBoWvZiq?}|xDSq=1#>Ny=R1=X zK(HSWJabiV*P0@Xnw^7>*woH)b|qZlVk;83Q*d@|f2;VUZ8g4MhKedd;J<>G)~wHC z#LI%^!yMN+lyR^8C6AbiKgeT*(j>pa`~Y)IM0`@I(0CN?|7{D39IbP(f_9+kYt+6C z;=ZmuO9pef&Thn)OR;%JLm&cZq%9F{;pU?hT1^j4LEAg&tmy+b&AS&K&niw7SKqa< z0wM_>S~fV9Teb)*xCNpsF?yOESgFdFwm!zZ!4cfcnG9Qsl^g^s{=vmq&E$_6KBm-# z$Kwmdcdhfm-rS)*qS>7{;%N1cC*gH!5Kjp`eiDj@<sQJL_t$*N%?-FL-cY|4RtARg zRxJ4gaV7ZVG6&uBzd)%CZ!g->EbR}a;Fd<kv%q!G0O@hyO_AsYOdRfgYP;souW@Je z3<FLf;9L()du=G0Lusf$HJv0{0vh@wJXP)_iXbJ`@!u33Mi_OUx|ZjZOjJt4O6(=i z0pm)Oe||e#ty0-4td0}j@^en4?@|m^SlIjoWjQxhEv%xL-%;qrEe73-z{ZY3k4sVj zRiMJGXt3#__x)&UrD<OPgBy9lCs*~vD&kqgOR1d3_{RZ|>(x94n})4~9wgdaqYV~) zF0$3!f?LxX@sn_mAK{(iFdagrR7LVayJ>|@b{_;r=zH%6VFNdiGxqKI7Pl``xeioP zen45q<xVUj{39?;Eh5Wpb*~o-G{)8rQ<lPtXe0+#1Qiq7qoFd47hSibJd7^vRWd1) zwtP4f^Zy0J-nTP2TcM32e@J9eJ-Ez1qr&WFvDbLt1Ku58V0;(MqnWY*NlIoIc|dCU zcovpf0<41ir<|3l&IzOB)5zirwa=}*r5NvTARpaf7ow5q#0|DUbo_CJ!hh|wgW!FL zCYsa81kSPZS@AYj%}*DS_oVKDJvHf=<_EMQws-HSJXrC%)tG%jJRV;0-3$pn1PkKa z3U6m+DF<yL3VrH^x6rJ1JSJRH%B5lMgmj-R)xJ4$qT18yv8rb&fGtB{eY|^DC>mG| zl$VygKX!<y3(np&3+p`qX(JRf(3Q_1(<z<erK#0VA<vQ2{|SJ5f8{x=Eh??83SRzQ zw>;jdn^}r!szLNTzgT^xIP&#mkB5c$z<lZQ98Bo|MW#TH3O0AVKt7w&NBX(8>h>6S z1ervT;~x5-<;w=cqiwy6N7pA5!Fl2ec-jxn-O%63Go{t1iW(`c==gSm+W?Jg9ZYwr z@$MPo3Z9|JfES*?=S?RE#=VuVW|IMHvC_Fn2A?i8zQoHF<y7p|IZZ{7ebOV}&yb*L zE|e?b6>?!z^A1XUao3Cr|BpaGoF>=_@snTgVnu7M%@;^SY1_#T=^&`gg$~fhCVFp? z+>h-*8G&x0X?OQAD<46HQ6N+w(v}!~t$6EegXjF-H~!^~OOU(>Vj2PRzUfZmAf6;Q z+Gtuf$$<<m5J11U26S|sl)^kyF0QARtJDN52GEo4h^8jr-ZOG8hfB@VB`4E9A)la7 zhrK`+(ggi1d8@+R76tr=?D3=dFpk4Iypaf=&L*3QA;Ja!i4uXPs8baauI~c#D$oJe z<v+r#Cx-iauCVe-E*p}YP%y3!kqvx|GBJ1N&CwSWn}?R{2yZ^CRb0)*<`p5Wf!Sis zh_9raQuOuR<smuyWQ(gX!P|)FLq5<F8!elsccmVNM4KdtVepQc>j?Jw{rOqX2p?QT z85nPwL2=3#g+$F`bTwiDG=XaseiWlMYiPB?QP3Kc>ch;C_)&T)JV49Xg8ZuFkSLKX zl)4A>d<&|+9I1L0YNWQR-77)Vu!t!E@VVvvnUL?J6<6&PzZ^ISskF@k=@Xqbz>#*~ z$juw*dUKud&R$oY&1ZB}6>~mwAqs%54n!9W?;P>3nGJR)q{toV?tOF#_T^<|Ogdvi zpvBQ8se(vVd__r^-hD)QYF*FqDopY%a7*AB?8bNN0nj6qC^}AD=zp%t&9`D2*aw2? zeqCV91HkvFzN_S?F$>hg*-#fuCLqWKt9tzMprm+A{F933y|-$Ze!hc9kUQ3)$9)$V z`*A+knbHvf6M7SE;f5b(U@Lz@xLut{z0|!IkQY;-mnS;6#%(%C2ahMaqQ^5A8q07` zd5DeS%V_GHJu=8RB`L$e#6D=;=gLBS^>2;^Q`k8j&hp?qMjv_{Qg5tpL$KR+z4Ep4 z`j6fwdV`z8W<lx=wupHOlnxxVT&1AokvSDZCIvUc%KFx#mCdF1z%RoL`vN;*gKFJW zv=v;MU?x}$#_6jbz+$%9+Ch?n@gjnoXW}l!1P}lBsxSsLay_L89+xV``$VpYq0d29 zkSHs&6<oG$7R*a;XLT(gT471}eV~K4F*v&#qG%zp2CYc){@yt90C5DN7J>F+z1Pvk zj^(;{sAR9?+I`$R-&8UO)1cpY5)>7YmC(GfW%kQu@5uQcgj)pc3Po|97kl;R{jGb* z)5LxX^vv`P<a6Q?0zFEBzQcRP$&DPPGHv>BAUhyd_Sq+PnDHv8A4-Zp72O4cO`x<S z<8cqsM1@+NDIGZ7@vk~nbn{v90puohCqB9?eng46w*rXnD6Mnnnc225D{(=ic#6@b zZb%H?ak4CKgFJ?H@*L7#dhkkFOpN{>$gsm5seqHKaahh}+lCX$3!kxpU(hAfB~y4c z(j&D`w+CEeuo1IiOcG(<E(!r=(+3c<A;VR0TKNH<iv!VRvIuvT8{COk>2PoW3Q5V- ztIn@l2``0h;>`K&4t^ouR5}NK8~gHCe}_z#o^79Vm-vowpjPeq_$*pA1Y_Ow7iiUW zbuU#htbhysObWY0bhtT~{6!k}BkUi6DF+%_fF@klHc)KIG))k9^BJ63SZOL+xuZbl zMH5v(#PFkh(ZwrSW$vpc9g#>@Ayt~!!8@+bIVavA+D3`2A}xo!L1e)=p9a4}1?r-y zY%~2%?7{6RZ{@r8^oZ8#wb_nJOd-MKIk0oL>8WtS+s>~!<%c|(?vMVBGcaEA)t{dj z8pJ|T-h03q4XYC*pl<`NFAqdq|A&iFix()CP=@FDY>e1OSv@)nf(}|}LqK<Y6X8kS zFA&5)p8Q!DZzWn)=;yLr#mgj6(k+N-x{p7q6K9H&kp7=QB7q|bp4_g~h4wo*h-sz% z!(4SuPL|B$h8=bjl6>gi!(N_TR=_8qcW@#jgw^E+vq=mC%(_FU+hS$EqCA<m(GgFj zkugg#$t|dnu)cnn91Hnw5!rkS@>1xkwLS9%c1kKxU`PG<crprqp}0w9ImSnqp>=KW zAXsa`lkEa*JLC{|EREu`YS?fIJ1$#SJgdd63kYL6^y{xVkJ&-u*XyP@&dH*RRgS)0 z0ot?}h#U9?9)O+4Zmedd_81bk<`39(BqU$%)B;lk<<RlL{C6w$iY4q^UUVcPurFZ= zCwIGfDt!1iI}GjqMV)bq!e^|nK13<&z^D?GbH1}kz6jGDfi5G)xyHL&o6{=(q+WsK zmR94-v9ipa;r!#UuVNcw(cy*0i?|1Og7Ua4ds{hP3kYwZ(DPTgpEoyFUnSlmszely z#fGr0u`=f^Fg;&`u(OLq;cyls1vwYNx7l?gclM>iqywfqvk_#uYtnJzZM=fz7Xg{6 z<o)!I$$UJ>N%S=kmxze#Mf!RwI-pg19uc=(bF?gP%*%sF2L9Fy;2UMr7tpWEA&s~@ z(hbe}PPyg0>47xtd%zKjaA;kOV*DBVGkGQ#;aHm)Lr&lxvq?7qK~f+t)d_jcn*T7M z<NG2!hz(t0&vfVqKsk(+y2R>Fi?@=S`RCvh{gt8Z<x?V;CQ<^G%f}9iFu|ijQF(QU z{W?m);%>hUT`9))S(u;%$;dlNX)m`%nki4#Az7?2xDkf4keq!FVx+%l)25OkxEsYG zxBaI_hOBRa`fZDYCp(JjX!4j7@529LNe{f4e%;O4vaK{s&<hzhmJ#3m_n~5inR;^W zeyyo>?W6<t2*!f2o2+fbSPHfpu8%=_!O^vs(=5(eW6oH|Vk8$;iq9KgZWZ|>C(vi- zp@8AkPLSve$o-&xi<)g`(P(QC$CD2hb8?>y!LQW8Rxgw&fxuSb&o~XJl#mp_X3>H? zQ{^0ZRoyDMd)~jR-__YGtn5+B^Wxv*p_k$4CyzxvJ3!pQTDf{uB}%IPL@;Ne@Bh8P zSV`pCX2qI0N0gT13X2_;0-<&%>^-m49!JXM_}i5rO!FgzB}r9%!dqk0)geFwMz<bO z=Hya3x13trNRj9jRAq~^;fMGzda<FGd`ej17!t^P&m#wL80Cc`BatO<=UhTYOYipO zJgcz?WM>}22LL#^6gs8i81m;tXT+Yk8~gAmMbJXxCg?-*PWUEm6&mey5Nq*4q;pDz zw>(#7*)4nLPk1;1mV`lTx>>%2?IlxHK?Ks3nYO|Sn1{h9|LFkf4@3#*CB#NcN9wRH zg%Fi{I=M*<W@2y_l`Mj%p+gS)bc*UjNgXjE_g@B7YEB1s?;W#NLnjnk!~;nFPrd=> zoHDF=Ypi}QfH>lR9?Wnu{0u-C=yHZsm7|0B+qx~_A{9&|Dd+28e{gF);}du#$H`cL zpgGW{-AZ02mf~|!vedXao|CGKVf1_fD^)syo=DjAUR)k86jSa+B5&a~pEem`Pax2+ z2hjjKT2@9v_dE(uK@duM>X;5zHD#X|2M#`9x`i%A9WjO5XQP(h^7e)e*&0Y*Ch6vd z0R2ygE~+6{&gzn%+8&K`RdD4*RCBEuD&?bqWE2^Y@@SnZCZGv?el!R~vUcJiaSVrT zhKtvNgE)ndFkFr2aW8gPjqnU`lO7)!Y2HXy?-Wl2feBv&5Q4lykpP^;Rk<F~8Q0TB z1OTNTixPi)0~46hQaUQ@RFMJAVvdN7u(*7YNEYK2&ZG#i#1$YckT;~`XB37T58#u8 zeDRP_D_UKNPtgr1HtSCG5r1LM>P$8$9&eAU!kO)*re)oN-_Jy;0aofAYfc8e*<#Zh z&nJIES-^)TJOFq^G3!MC_?MD{iea_`a@J4Ieg?-&1|OHf3&f^&0{{)y)~^<e$!{pl zl%(txl3utXpYbsWbSe(ru)b~Ndn}*TRg<#`csaD|M7TMcJS@%m5EO40Qf}V**wG06 zg%OS<pL<CTNJ3L%NhyKj{xA^z8{CA<n3oo-Zv+dwMl1vc*Zq4chb$vf^BFOK=hr<5 z&p+F=pOW8IuhKQ|UjZC<eD=6H>s6~zmH{Tf+n@8;kqYTs$RCN{a2!$*$)PXOMW2(; zrGZO;l5`5mF5ygI<Dt4Kz2&vWlpOUKn}LPnJinxcGZsZeqYSKcUywW|$~+p*F9C&w zf8Y#<@+QYwmD8lntye4qaeMo6JmfKaH$ra#3%v!mND6#<MGC(B)K+uhdqA!*465(u zM41}GqHZI?J`@4wCF-h<vUA@yDxC#Nrp-{~CWsrXUr~&J;EdpmuI?QKk@IVW$e}44 zySQqFWha$cP$O<kkY6M^2NL!2osI0pEhC+LMEC?}N6=t%?Khwln12e8J5d9L+|T{d zV5p5OPz;~wzpO8%=)FIZlRM!i6l43Da7uBPC<?T1=-hs(II4TD)c~PVa%>m6L+)ba zFdJs9Ytm|ANz3aZ%V=hPP)`VOT;PMuL__!nyf-uixn&M6l*8vYI@OkX=p0YNzEC*& zMg>#>vq*G|_#XG-X8q-}6eRcfF+t){d8QU{8_Kys$upswQ7F1f%rAF;J1r7uDUCJA z1|!Vo@OH#JF;XJI2Zi<g?pmz4MpXlB$rw6Ij543S3`&=6{RfyNvIW>-u8dajKVN9S zPT3y`%Z|Ig7T?Ltw^$iHQ$K*vJi(7+rQ2eo>%^aT$If^fJ*omJ7>6zs*-SXDeKS0M zGti7CsIYyzQ1lRL`>nWHu4lnP$hj%XK#tq?<^-9?CvcMz8JFN`p{Sm?&n*61V}Uve zejH;%g*t}YER;QiEKs-ZuY(a_Wc@I8hvLv%7Y#ZoaM<56a?+BzfQs~VH6n<&AXa}K zH2*r$dl(%3-;UG{#<($h=05}kZ&OVE4(+kJ(gYquvck8u+gcl(y#ZuBk2(zD*ai2% zkT7sOw;;=84>djobQuaLo4Xebt!(+=O^mm>+}Uh^dKpP>+vLtAV1!9ZSFW?#ouhB+ z-#8aq9Y5EZc+i&i&oh<%6NHW88YXMgZil94x!u#q=uL3bPZy=8FT4HC&>Z#$j%uDD zTtiC`uPTY5>H)i#ED+`gC!(`845fG&cK*BpEl4CkNhrN{uKbnHY&&<}Xe}J3E@Lz? zE{}+<6SGAnfRCD&p|KYnmsk%4pcKCaZ0F78s|O7bgj!&SB!EKGFK|+obuSadpX$z( z<=I`5&A>FlzMS?oZj&CqJ?Kq)x|c{eQ2Rn0aag0LrL_`=K{t1tzMxoKPWun-x=v)1 zlDWRU?)U~~gZ;Wnt!f$k^g#9{*a^cUr8^fQRG!!1Ul`|iUPGeRWvG&Pia!NVp}T^~ z5#Q)hes-d4>pT!43N)A|uTA@jNPY3%-_ugq(8<r(1zzr7M09XA{~vIVD|Js;TYr@{ zI;sr=TVTNUvOvQ)&AM2g>q^Yn0<syMx(}9y3eiY?gZ8uT4~G%~pEr@u%r`$%4Eps< zz$>w+{ekf9<0V3ek7O_aa@hluqVrPI-fpS&J|2?a{~@%@J79YTsJlHxeMV)1)7TI( z4?roNOjg?iLT4w@VPHFb07+0lD-v~Ax6fR}(x2%Mor?*UoZN1XBEPOqGPlBu*07HG z6sU~Y(e3(+D~cT_r-<`6AZdVBw0FwB<JMMZQOj}w)=B}pQYe}tR;IYTaZlC1(FLn? z<QqYLVMu=SP{_fSH`b{*`U~2-)x|-IS=jQT2Xbe&hca+Y&$mc?f2@Xk(82rqGWU1V z!bJj6C>y5qi}-Havd@mVSqw<2N^FqU9)u+((WZeZ9!ToQQybZ>;rcV$C^)VFXAK(( zgCbX9Ns+w+oWp!=H{IdgRX%0u>qqh={_IPh_LxE#OjcEAO?dpicgV`z2KWnN5jb>| zQ~46=ykz&8JHlL`+L_O)NVs{-65=E}4@<UrWGwT>M?hDdX{vgIE?VAv%uW(2N+<7B zU$qpotUf(W=K+*Ia17Moi`da_@CG@|Gm-)Jr4Q|OKlrzd=*=O1pugTdn-FTs`9gH4 zQ}IRzN<!YeSEW6?8VV8bGSv&~W{`16J_D(pzz0?c$&3)(UKCi?%T)0<x?35ckW?qc zl81;^d2xz3yYmwI$)S1_tFaE)ey$V_jQ2@y71R1cT(G#?S>>v~4AYEs+0os=pI+Fc z2!g!OrVWdF)9%&7J5W_M-{MZC20pP~4@a+v#X8<Y${-LH!f|@x;F>oZ&DF&XwJI46 z^Bfu{N{Ptwm<nafpUZOLrWoYEu#9K#31{`SYRgt*I}UU!VlR!cE&Q}TXMp8R!Mb1A zJ;4YXD2bXO<c>x~N4rU?#t&8}O&2FH9$Q%?F5Q#>Y}trn5@<`EYKLC#XqcCFA(1PB zT?KiBD?U8%TxCIrZ`OcJ;8X<smGtwD*9iYzvO%|^T<Fv&XJFo=<HXJANsYA>PPu|L z^5Cp4{LSB~G{V6$VLjeH!2L>Quc_`-A5jF}-P_kX^<aKp|A#=RZXR_j?10zHUH9JK zDrtWUQ=WKsairgJanZ}%Oj$-1Fs^Ygb(ep_3E|AfjWA}~sk`NCHhsV>UVsS-UX-bx zTK}lNJM5P|{Y&`pby&LQ0kRm3^Bc$S%_rT5QfD_Z>yLj7r_O3e2+M<k=R(Va7f0YY zo`g+lqlN~jZ@QUwe!ml<ZX!NC30gdzNsOK#)kw6t9b}yV1p&~(i=yc8j)R5i=V`h) zYEX|iGudzP91JNp{U`le0}Xp`=YG8oa9_BBC=i4dcHhGM<idN0+U?5uU&-HiQrUfJ z?7pQ}t#$^74?s)5pny8=QYQVp$4NtO9?HW-3O(yUm1qMwO1MT}d)EpMAA+;MLh0r5 zZ|hX|4y8IZhKWX*6^5U+r$0ewHNrk;+_nnIlPmXMw=%gTZ~^TAg+DoJlg{>SRyB-> zP7)@j-&;Exw8{w0BVUiF72(+zM$)VU<uQh2#=Qv|I-*Fit}nCP-CMJ7j)q(>c#$&W zZr}Kfi?^J8n_IfaJIxjAhO9@fX2ZP~kHHGSohK^w1FPpW#))c~75p=B)HG>L(M;90 z=$KXWZg0aU%4p-cx8cEC`O^l2sVk4cUkOj4y>;+sj8g{4JyS_-W;bu>kDTlkhMVVU zoi$;D<M46-bAg$pqv}uMGo1ec+zo~F%Bbi)-NqYH*_NZNmfwOdMbsA@2u%qVC3fGR z-V)Uu)>7J)J8hJ99s>oF>=Pgy(40@|N>nq|o++lr2~I_-`4#ux_|!wWXR@jJrjI!Q zTtfBZvc^hAGvmxi^yE2qywEh=!_3X>ukQM6fOrv^^L!$2B-a68Iij=_4VZbC8umD) zH8DLVl1*k$*72H^hP|;@+{-OxH-!K-*_7mK4d^E(d<WnMvAc((tfKl9`)`^X?tf(| zV&?yOeD@2x>A3ixAO1wTmeKC|`Cnk9b`PIRH5r6(cC>78V{T^meRETJ&a=zR+*?!S zmO;3d)F}(|sBT4cXo@UB$y+P(xBk985k9`ZcO!4_?wx!0Fk;j9z<;pS^wn!t(EV1> zeOaqMV)(3O_|QG+bOxOsx=slZ>;F8ElDaz~Zr}g^0AuGIaEtH~yNzN0t@>DS;C}#) CA$@89 literal 0 HcmV?d00001 diff --git a/ci/cross-pre-build.sh b/ci/cross-pre-build.sh index 3b15d0e343..b611d9aad2 100644 --- a/ci/cross-pre-build.sh +++ b/ci/cross-pre-build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euxo pipefail +apt-get update +apt-get --assume-yes install patch + if [[ "$CROSS_TARGET" == "aarch64-unknown-linux-gnu" ]]; then dpkg --add-architecture $CROSS_DEB_ARCH apt-get update && apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH diff --git a/crates/background/src/lib.rs b/crates/background/src/lib.rs index 01fd0824e5..e50b86a756 100644 --- a/crates/background/src/lib.rs +++ b/crates/background/src/lib.rs @@ -31,7 +31,6 @@ pub enum ApplicationJob { PerformBackgroundTasks, RecalculateCalendarEvents, ReviseUserWorkouts(String), - PerformServerKeyValidation, UpdateMetadataGroup(String), UpdateMetadata(String, bool), HandleOnSeenComplete(String), diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 23a3651b07..e5ec0deebe 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use anyhow::Result; use async_graphql::SimpleObject; -use common_utils::{IsFeatureEnabled, PROJECT_NAME}; +use common_utils::PROJECT_NAME; use env_utils::{DEFAULT_MAL_CLIENT_ID, DEFAULT_TMDB_ACCESS_TOKEN}; use schematic::{derive_enum, validate::not_empty, Config, ConfigEnum, ConfigLoader, HandlerError}; use serde::{Deserialize, Serialize}; @@ -61,7 +61,9 @@ pub struct AnimeAndMangaConfig { pub manga_updates: MangaUpdatesConfig, } -impl IsFeatureEnabled for AnimeAndMangaConfig {} +#[derive(Debug, Serialize, Deserialize, Clone, Config)] +#[config(rename_all = "snake_case")] +pub struct MusicConfig {} #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case", env_prefix = "AUDIO_BOOKS_AUDIBLE_")] @@ -79,8 +81,6 @@ pub struct AudioBookConfig { pub audible: AudibleConfig, } -impl IsFeatureEnabled for AudioBookConfig {} - derive_enum!( #[derive(ConfigEnum, Default)] pub enum OpenlibraryCoverImageSize { @@ -121,8 +121,6 @@ pub struct BookConfig { pub google_books: GoogleBooksConfig, } -impl IsFeatureEnabled for BookConfig {} - #[derive(Debug, Serialize, Deserialize, Clone, Config, PartialEq, Eq)] #[config(rename_all = "snake_case", env_prefix = "DATABASE_")] pub struct DatabaseConfig { @@ -162,8 +160,6 @@ pub struct MovieAndShowConfig { pub tmdb: TmdbConfig, } -impl IsFeatureEnabled for MovieAndShowConfig {} - #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case", env_prefix = "PODCASTS_LISTENNOTES_")] pub struct ListenNotesConfig { @@ -190,8 +186,6 @@ pub struct PodcastConfig { pub itunes: ITunesConfig, } -impl IsFeatureEnabled for PodcastConfig {} - #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case", env_prefix = "VIDEO_GAMES_TWITCH_")] pub struct TwitchConfig { @@ -230,8 +224,8 @@ pub struct VideoGameConfig { pub twitch: TwitchConfig, } -impl IsFeatureEnabled for VideoGameConfig { - fn is_enabled(&self) -> bool { +impl VideoGameConfig { + pub fn is_enabled(&self) -> bool { let mut enabled = false; if !self.twitch.client_id.is_empty() && !self.twitch.client_secret.is_empty() { enabled = true; @@ -244,8 +238,6 @@ impl IsFeatureEnabled for VideoGameConfig { #[config(rename_all = "snake_case", env_prefix = "VISUAL_NOVEL_")] pub struct VisualNovelConfig {} -impl IsFeatureEnabled for VisualNovelConfig {} - #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case", env_prefix = "FILE_STORAGE_")] pub struct FileStorageConfig { @@ -264,8 +256,8 @@ pub struct FileStorageConfig { pub s3_url: String, } -impl IsFeatureEnabled for FileStorageConfig { - fn is_enabled(&self) -> bool { +impl FileStorageConfig { + pub fn is_enabled(&self) -> bool { let mut enabled = false; if !self.s3_access_key_id.is_empty() && !self.s3_bucket_name.is_empty() @@ -331,8 +323,8 @@ pub struct SmtpConfig { pub mailbox: String, } -impl IsFeatureEnabled for SmtpConfig { - fn is_enabled(&self) -> bool { +impl SmtpConfig { + pub fn is_enabled(&self) -> bool { let mut enabled = false; if !self.server.is_empty() && !self.user.is_empty() && !self.password.is_empty() { enabled = true; @@ -408,6 +400,9 @@ pub struct UsersConfig { #[derive(Debug, Serialize, Deserialize, Clone, Config)] #[config(rename_all = "snake_case")] pub struct AppConfig { + /// Settings related to music. + #[setting(nested)] + pub music: MusicConfig, /// Settings related to anime and manga. #[setting(nested)] pub anime_and_manga: AnimeAndMangaConfig, diff --git a/crates/enums/src/lib.rs b/crates/enums/src/lib.rs index c4ef6e6a31..cea1fff02b 100644 --- a/crates/enums/src/lib.rs +++ b/crates/enums/src/lib.rs @@ -28,14 +28,15 @@ use strum::Display; )] #[serde(rename_all = "snake_case")] pub enum MediaLot { - AudioBook, - Anime, #[default] Book, - Podcast, - Manga, - Movie, Show, + Movie, + Anime, + Manga, + Music, + Podcast, + AudioBook, VideoGame, VisualNovel, } @@ -63,19 +64,20 @@ pub enum MediaLot { )] #[serde(rename_all = "snake_case")] pub enum MediaSource { - Anilist, + Mal, + Igdb, + Tmdb, + Vndb, #[default] - Audible, Custom, - GoogleBooks, - Igdb, Itunes, + Anilist, + Audible, Listennotes, - MangaUpdates, - Mal, + GoogleBooks, Openlibrary, - Tmdb, - Vndb, + MangaUpdates, + YoutubeMusic, } #[derive( diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 764923cc0f..281693bb80 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -54,6 +54,7 @@ mod m20241126_changes_for_issue_1113; mod m20241129_changes_for_issue_1114; mod m20241214_create_user_notification; mod m20241215_changes_for_issue_1131; +mod m20241220_changes_for_issue_49; pub use m20230410_create_metadata::Metadata as AliasedMetadata; pub use m20230413_create_person::Person as AliasedPerson; @@ -129,6 +130,7 @@ impl MigratorTrait for Migrator { Box::new(m20241126_changes_for_issue_1113::Migration), Box::new(m20241214_create_user_notification::Migration), Box::new(m20241215_changes_for_issue_1131::Migration), + Box::new(m20241220_changes_for_issue_49::Migration), ] } } diff --git a/crates/migrations/src/m20230410_create_metadata.rs b/crates/migrations/src/m20230410_create_metadata.rs index 65f9d7d748..7a3346bd61 100644 --- a/crates/migrations/src/m20230410_create_metadata.rs +++ b/crates/migrations/src/m20230410_create_metadata.rs @@ -16,6 +16,7 @@ pub enum Metadata { // updated using jobs LastUpdatedOn, Title, + SourceUrl, Description, // the year this media item was released PublishYear, @@ -53,8 +54,8 @@ pub enum Metadata { ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, + MusicSpecifics, WatchProviders, - StateChanges, ExternalIdentifiers, } @@ -83,6 +84,7 @@ impl MigrationTrait for Migration { .default(Expr::current_timestamp()), ) .col(ColumnDef::new(Metadata::Title).text().not_null()) + .col(ColumnDef::new(Metadata::SourceUrl).text()) .col(ColumnDef::new(Metadata::Description).text()) .col(ColumnDef::new(Metadata::PublishYear).integer()) .col(ColumnDef::new(Metadata::PublishDate).date()) @@ -103,9 +105,9 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Metadata::VideoGameSpecifics).json_binary()) .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::IsRecommendation).boolean()) .col(ColumnDef::new(Metadata::ExternalIdentifiers).json_binary()) + .col(ColumnDef::new(Metadata::MusicSpecifics).json_binary()) .to_owned(), ) .await?; diff --git a/crates/migrations/src/m20230413_create_person.rs b/crates/migrations/src/m20230413_create_person.rs index 0c4aa9bcd4..f1d5adf3f2 100644 --- a/crates/migrations/src/m20230413_create_person.rs +++ b/crates/migrations/src/m20230413_create_person.rs @@ -28,6 +28,7 @@ pub enum Person { IsPartial, SourceSpecifics, StateChanges, + SourceUrl, } #[derive(Iden)] @@ -74,6 +75,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Person::IsPartial).boolean()) .col(ColumnDef::new(Person::SourceSpecifics).json_binary()) .col(ColumnDef::new(Person::StateChanges).json_binary()) + .col(ColumnDef::new(Person::SourceUrl).text()) .to_owned(), ) .await?; diff --git a/crates/migrations/src/m20230501_create_metadata_group.rs b/crates/migrations/src/m20230501_create_metadata_group.rs index 8b9f44e845..c03fce9df9 100644 --- a/crates/migrations/src/m20230501_create_metadata_group.rs +++ b/crates/migrations/src/m20230501_create_metadata_group.rs @@ -15,6 +15,7 @@ pub enum MetadataGroup { Lot, Source, IsPartial, + SourceUrl, } #[async_trait::async_trait] @@ -36,12 +37,9 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(MetadataGroup::Description).text()) .col(ColumnDef::new(MetadataGroup::Lot).text().not_null()) .col(ColumnDef::new(MetadataGroup::Source).text().not_null()) - .col( - ColumnDef::new(MetadataGroup::Images) - .json_binary() - .not_null(), - ) + .col(ColumnDef::new(MetadataGroup::Images).json_binary()) .col(ColumnDef::new(MetadataGroup::IsPartial).boolean()) + .col(ColumnDef::new(MetadataGroup::SourceUrl).text()) .to_owned(), ) .await?; diff --git a/crates/migrations/src/m20240827_create_daily_user_activity.rs b/crates/migrations/src/m20240827_create_daily_user_activity.rs index 172d0f4876..5a511e4040 100644 --- a/crates/migrations/src/m20240827_create_daily_user_activity.rs +++ b/crates/migrations/src/m20240827_create_daily_user_activity.rs @@ -37,6 +37,8 @@ pub enum DailyUserActivity { MangaCount, MovieCount, MovieDuration, + MusicCount, + MusicDuration, ShowCount, ShowDuration, VideoGameCount, @@ -99,6 +101,8 @@ pub async fn create_daily_user_activity_table(manager: &SchemaManager<'_>) -> Re .col(integer_not_null(DailyUserActivity::MangaCount)) .col(integer_not_null(DailyUserActivity::MovieCount)) .col(integer_not_null(DailyUserActivity::MovieDuration)) + .col(integer_not_null(DailyUserActivity::MusicCount)) + .col(integer_not_null(DailyUserActivity::MusicDuration)) .col(integer_not_null(DailyUserActivity::ShowCount)) .col(integer_not_null(DailyUserActivity::ShowDuration)) .col(integer_not_null(DailyUserActivity::VideoGameCount)) diff --git a/crates/migrations/src/m20241220_changes_for_issue_49.rs b/crates/migrations/src/m20241220_changes_for_issue_49.rs new file mode 100644 index 0000000000..cdc74bcd39 --- /dev/null +++ b/crates/migrations/src/m20241220_changes_for_issue_49.rs @@ -0,0 +1,49 @@ +use sea_orm_migration::prelude::*; + +use crate::m20240827_create_daily_user_activity::create_daily_user_activity_table; + +#[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(); + if !manager.has_column("metadata", "music_specifics").await? { + db.execute_unprepared(r#"ALTER TABLE "metadata" ADD COLUMN "music_specifics" JSONB"#) + .await?; + } + if manager.has_column("metadata", "state_changes").await? { + db.execute_unprepared(r#"ALTER TABLE "metadata" DROP COLUMN "state_changes""#) + .await?; + } + let tables = vec!["metadata", "metadata_group", "person"]; + for table in tables { + if !manager.has_column(table, "source_url").await? { + db.execute_unprepared(&format!( + r#"ALTER TABLE "{}" ADD COLUMN "source_url" TEXT"#, + table + )) + .await?; + } + } + if !manager + .has_column("daily_user_activity", "music_count") + .await? + { + db.execute_unprepared(r#"DROP TABLE "daily_user_activity""#) + .await?; + create_daily_user_activity_table(manager).await?; + } + db.execute_unprepared(r#" +UPDATE "user" SET preferences = jsonb_set(preferences, '{features_enabled,media,music}', 'true', true); +ALTER TABLE "metadata_group" ALTER COLUMN "images" DROP NOT NULL; +"#) + .await?; + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} diff --git a/crates/models/common/src/lib.rs b/crates/models/common/src/lib.rs index 83350a9260..e303a8e54d 100644 --- a/crates/models/common/src/lib.rs +++ b/crates/models/common/src/lib.rs @@ -323,7 +323,6 @@ pub enum ApplicationCacheKey { CoreDetails, IgdbSettings, TmdbSettings, - ServerKeyValidated, ListennotesSettings, UserAnalyticsParameters(UserLevelCacheKey<()>), PeopleSearch(UserLevelCacheKey<PeopleSearchInput>), diff --git a/crates/models/database/src/daily_user_activity.rs b/crates/models/database/src/daily_user_activity.rs index 513c21881a..6654fbd67f 100644 --- a/crates/models/database/src/daily_user_activity.rs +++ b/crates/models/database/src/daily_user_activity.rs @@ -31,6 +31,8 @@ pub struct Model { pub manga_count: i32, pub movie_count: i32, pub movie_duration: i32, + pub music_count: i32, + pub music_duration: i32, pub show_count: i32, pub show_duration: i32, pub video_game_count: i32, diff --git a/crates/models/database/src/metadata.rs b/crates/models/database/src/metadata.rs index 417fafd98a..6708bbcb52 100644 --- a/crates/models/database/src/metadata.rs +++ b/crates/models/database/src/metadata.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use chrono::NaiveDate; use enums::{MediaLot, MediaSource}; use media_models::{ - AnimeSpecifics, AudioBookSpecifics, BookSpecifics, ExternalIdentifiers, MangaSpecifics, - MetadataFreeCreator, MetadataImage, MetadataStateChanges, MetadataVideo, MovieSpecifics, + AnimeSpecifics, AudioBookSpecifics, BookSpecifics, MangaSpecifics, MetadataExternalIdentifiers, + MetadataFreeCreator, MetadataImage, MetadataVideo, MovieSpecifics, MusicSpecifics, PodcastSpecifics, ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider, }; use nanoid::nanoid; @@ -26,6 +26,7 @@ pub struct Model { pub source: MediaSource, pub is_nsfw: Option<bool>, pub is_partial: Option<bool>, + pub source_url: Option<String>, pub is_recommendation: Option<bool>, pub description: Option<String>, pub original_language: Option<String>, @@ -42,7 +43,7 @@ pub struct Model { #[sea_orm(column_type = "Json")] pub watch_providers: Option<Vec<WatchProvider>>, #[sea_orm(column_type = "Json")] - pub external_identifiers: Option<ExternalIdentifiers>, + pub external_identifiers: Option<MetadataExternalIdentifiers>, pub audio_book_specifics: Option<AudioBookSpecifics>, pub book_specifics: Option<BookSpecifics>, pub movie_specifics: Option<MovieSpecifics>, @@ -52,7 +53,7 @@ pub struct Model { pub visual_novel_specifics: Option<VisualNovelSpecifics>, pub anime_specifics: Option<AnimeSpecifics>, pub manga_specifics: Option<MangaSpecifics>, - pub state_changes: Option<MetadataStateChanges>, + pub music_specifics: Option<MusicSpecifics>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/models/database/src/metadata_group.rs b/crates/models/database/src/metadata_group.rs index aa27b3dbe5..dfbd9b8f06 100644 --- a/crates/models/database/src/metadata_group.rs +++ b/crates/models/database/src/metadata_group.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "metadata_group")] #[graphql(name = "MetadataGroup")] #[boilermates("MetadataGroupWithoutId")] +#[boilermates(attr_for("MetadataGroupWithoutId", "#[derive(Clone, Debug)]"))] pub struct Model { #[boilermates(not_in("MetadataGroupWithoutId"))] #[sea_orm(primary_key, auto_increment = false)] @@ -24,12 +25,13 @@ pub struct Model { pub title: String, #[sea_orm(column_type = "Json")] #[graphql(skip)] - pub images: Vec<MetadataImage>, + pub images: Option<Vec<MetadataImage>>, #[sea_orm(ignore)] pub display_images: Vec<String>, #[boilermates(not_in("MetadataGroupWithoutId"))] pub is_partial: Option<bool>, pub description: Option<String>, + pub source_url: Option<String>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/models/database/src/person.rs b/crates/models/database/src/person.rs index 4df7a9f7fa..268253ab8b 100644 --- a/crates/models/database/src/person.rs +++ b/crates/models/database/src/person.rs @@ -33,6 +33,7 @@ pub struct Model { pub death_date: Option<NaiveDate>, pub place: Option<String>, pub website: Option<String>, + pub source_url: Option<String>, #[graphql(skip)] pub source_specifics: Option<PersonSourceSpecifics>, #[graphql(skip)] diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs index 8aa47ff775..d1a26a1d73 100644 --- a/crates/models/dependent/src/lib.rs +++ b/crates/models/dependent/src/lib.rs @@ -134,13 +134,11 @@ pub struct CollectionContents { pub struct PersonDetails { pub details: person::Model, pub contents: Vec<PersonDetailsGroupedByRole>, - pub source_url: Option<String>, } #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)] pub struct MetadataGroupDetails { pub details: metadata_group::Model, - pub source_url: Option<String>, pub contents: Vec<String>, } @@ -198,6 +196,7 @@ pub struct MetadataLotSourceMappings { pub sources: Vec<MediaSource>, } +#[skip_serializing_none] #[derive(PartialEq, Eq, Clone, Debug, SimpleObject, Serialize, Deserialize)] pub struct CoreDetails { pub page_size: i32, @@ -290,7 +289,6 @@ pub struct UserWorkoutTemplateDetails { pub collections: Vec<collection::Model>, } -#[skip_serializing_none] #[derive( Debug, Default, SimpleObject, Serialize, Deserialize, Clone, FromQueryResult, PartialEq, Eq, )] @@ -330,7 +328,6 @@ pub struct DailyUserActivityItem { pub total_duration: i64, } -#[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone, PartialEq, Eq)] pub struct DailyUserActivitiesResponse { pub total_count: i64, @@ -340,7 +337,6 @@ pub struct DailyUserActivitiesResponse { pub grouped_by: DailyUserActivitiesResponseGroupedBy, } -#[skip_serializing_none] #[derive( Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq, )] @@ -349,7 +345,6 @@ pub struct FitnessAnalyticsExercise { pub exercise: String, } -#[skip_serializing_none] #[derive( Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq, )] @@ -358,7 +353,6 @@ pub struct FitnessAnalyticsMuscle { pub muscle: ExerciseMuscle, } -#[skip_serializing_none] #[derive( Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq, )] @@ -367,7 +361,6 @@ pub struct FitnessAnalyticsEquipment { pub equipment: ExerciseEquipment, } -#[skip_serializing_none] #[derive( Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq, )] @@ -385,7 +378,6 @@ pub struct UserFitnessAnalytics { pub workout_equipments: Vec<FitnessAnalyticsEquipment>, } -#[skip_serializing_none] #[derive( Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq, )] @@ -417,17 +409,18 @@ pub struct IgdbSettings { pub access_token: String, } +#[skip_serializing_none] #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone)] pub struct EmptyCacheValue { pub _empty: (), } -pub type MetadataSearchResponse = SearchResults<MetadataSearchItemResponse>; pub type PeopleSearchResponse = SearchResults<PeopleSearchItem>; +pub type MetadataSearchResponse = SearchResults<MetadataSearchItemResponse>; pub type MetadataGroupSearchResponse = SearchResults<MetadataGroupSearchItem>; -#[skip_serializing_none] #[derive(Clone, Debug, PartialEq, FromJsonQueryResult, Serialize, Deserialize, Eq)] +#[serde(untagged)] pub enum ApplicationCacheValue { Empty(EmptyCacheValue), CoreDetails(CoreDetails), diff --git a/crates/models/media/src/lib.rs b/crates/models/media/src/lib.rs index 6aa3b5c40f..e9f29efcf6 100644 --- a/crates/models/media/src/lib.rs +++ b/crates/models/media/src/lib.rs @@ -48,16 +48,17 @@ pub struct GenreListItem { pub num_items: Option<i64>, } +#[skip_serializing_none] #[derive( + Eq, + Clone, Debug, + Default, + PartialEq, Serialize, + InputObject, Deserialize, SimpleObject, - Clone, - InputObject, - PartialEq, - Eq, - Default, FromJsonQueryResult, )] #[graphql(input_name = "AudioBookSpecificsInput")] @@ -65,16 +66,17 @@ pub struct AudioBookSpecifics { pub runtime: Option<i32>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, + PartialEq, Serialize, Deserialize, - SimpleObject, - Clone, InputObject, - PartialEq, - Eq, - Default, + SimpleObject, FromJsonQueryResult, )] #[graphql(input_name = "BookSpecificsInput")] @@ -82,16 +84,17 @@ pub struct BookSpecifics { pub pages: Option<i32>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, + PartialEq, Serialize, Deserialize, - SimpleObject, - Clone, InputObject, - Eq, - PartialEq, - Default, + SimpleObject, FromJsonQueryResult, )] #[graphql(input_name = "MovieSpecificsInput")] @@ -99,17 +102,18 @@ pub struct MovieSpecifics { pub runtime: Option<i32>, } +#[skip_serializing_none] #[derive( + Eq, Debug, - Serialize, - Deserialize, - SimpleObject, Clone, Default, - FromJsonQueryResult, - Eq, PartialEq, + Serialize, + Deserialize, InputObject, + SimpleObject, + FromJsonQueryResult, )] #[graphql(input_name = "PodcastSpecificsInput")] pub struct PodcastSpecifics { @@ -117,17 +121,18 @@ pub struct PodcastSpecifics { pub total_episodes: usize, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "PodcastEpisodeInput")] #[serde(default)] @@ -144,17 +149,18 @@ pub struct PodcastEpisode { pub thumbnail: Option<String>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "ShowSpecificsInput")] pub struct ShowSpecifics { @@ -164,18 +170,19 @@ pub struct ShowSpecifics { pub total_episodes: Option<usize>, } +#[skip_serializing_none] #[derive( + Eq, + Hash, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - Hash, - InputObject, )] #[graphql(input_name = "ShowSeasonSpecificsInput")] pub struct ShowSeason { @@ -189,18 +196,19 @@ pub struct ShowSeason { pub backdrop_images: Vec<String>, } +#[skip_serializing_none] #[derive( + Eq, + Hash, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - Hash, - InputObject, )] #[graphql(input_name = "ShowEpisodeSpecificsInput")] pub struct ShowEpisode { @@ -213,51 +221,54 @@ pub struct ShowEpisode { pub runtime: Option<i32>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "VideoGameSpecificsInput")] pub struct VideoGameSpecifics { pub platforms: Vec<String>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "VisualNovelSpecificsInput")] pub struct VisualNovelSpecifics { pub length: Option<i32>, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "AnimeAiringScheduleSpecificsInput")] pub struct AnimeAiringScheduleSpecifics { @@ -265,17 +276,18 @@ pub struct AnimeAiringScheduleSpecifics { pub airing_at: NaiveDateTime, } +#[skip_serializing_none] #[derive( + Eq, Debug, + Clone, + Default, PartialEq, - Eq, Serialize, Deserialize, + InputObject, SimpleObject, - Clone, - Default, FromJsonQueryResult, - InputObject, )] #[graphql(input_name = "AnimeSpecificsInput")] pub struct AnimeSpecifics { @@ -283,17 +295,38 @@ pub struct AnimeSpecifics { pub airing_schedule: Option<Vec<AnimeAiringScheduleSpecifics>>, } +#[skip_serializing_none] #[derive( - Debug, - PartialEq, Eq, + Debug, + Clone, + Default, Serialize, + PartialEq, + InputObject, Deserialize, SimpleObject, + FromJsonQueryResult, +)] +#[graphql(input_name = "MusicSpecificsInput")] +pub struct MusicSpecifics { + pub duration: Option<i32>, + pub view_count: Option<i32>, + pub by_various_artists: Option<bool>, +} + +#[skip_serializing_none] +#[derive( + Eq, + Debug, Clone, Default, - FromJsonQueryResult, + PartialEq, + Serialize, + Deserialize, InputObject, + SimpleObject, + FromJsonQueryResult, )] #[graphql(input_name = "MangaSpecificsInput")] pub struct MangaSpecifics { @@ -398,6 +431,7 @@ pub struct MetadataPerson { pub gender: Option<String>, pub place: Option<String>, pub website: Option<String>, + pub source_url: Option<String>, pub description: Option<String>, pub images: Option<Vec<String>>, pub death_date: Option<NaiveDate>, @@ -423,7 +457,7 @@ pub struct WatchProvider { #[derive( Clone, Debug, PartialEq, FromJsonQueryResult, Eq, Serialize, Deserialize, SimpleObject, Default, )] -pub struct ExternalIdentifiers { +pub struct MetadataExternalIdentifiers { pub tvdb_id: Option<i32>, } @@ -433,6 +467,7 @@ pub struct MetadataDetails { pub is_nsfw: Option<bool>, pub title: String, pub source: MediaSource, + pub source_url: Option<String>, pub description: Option<String>, pub original_language: Option<String>, pub lot: MediaLot, @@ -451,7 +486,7 @@ pub struct MetadataDetails { pub watch_providers: Vec<WatchProvider>, pub audio_book_specifics: Option<AudioBookSpecifics>, pub book_specifics: Option<BookSpecifics>, - pub external_identifiers: Option<ExternalIdentifiers>, + pub external_identifiers: Option<MetadataExternalIdentifiers>, pub movie_specifics: Option<MovieSpecifics>, pub podcast_specifics: Option<PodcastSpecifics>, pub show_specifics: Option<ShowSpecifics>, @@ -459,6 +494,7 @@ pub struct MetadataDetails { pub visual_novel_specifics: Option<VisualNovelSpecifics>, pub anime_specifics: Option<AnimeSpecifics>, pub manga_specifics: Option<MangaSpecifics>, + pub music_specifics: Option<MusicSpecifics>, } /// A specific instance when an entity was seen. @@ -792,14 +828,11 @@ pub struct MetadataGroupSearchItem { )] pub struct CommitMediaInput { pub lot: MediaLot, - pub source: MediaSource, pub identifier: String, + pub source: MediaSource, pub force_update: Option<bool>, } -#[derive(Debug, Serialize, Deserialize, Clone, FromJsonQueryResult, Eq, PartialEq, Default)] -pub struct MetadataStateChanges {} - #[derive( Debug, Serialize, Deserialize, Clone, FromJsonQueryResult, Eq, PartialEq, Default, Hash, )] @@ -959,21 +992,22 @@ pub struct DeployImportJobInput { pub struct CreateCustomMetadataInput { pub title: String, pub lot: MediaLot, + pub is_nsfw: Option<bool>, + pub publish_year: Option<i32>, pub description: Option<String>, - pub creators: Option<Vec<String>>, pub genres: Option<Vec<String>>, pub images: Option<Vec<String>>, pub videos: Option<Vec<String>>, - pub is_nsfw: Option<bool>, - pub publish_year: Option<i32>, - pub audio_book_specifics: Option<AudioBookSpecifics>, + pub creators: Option<Vec<String>>, + pub show_specifics: Option<ShowSpecifics>, pub book_specifics: Option<BookSpecifics>, + pub music_specifics: Option<MusicSpecifics>, pub movie_specifics: Option<MovieSpecifics>, - pub podcast_specifics: Option<PodcastSpecifics>, - pub show_specifics: Option<ShowSpecifics>, - pub video_game_specifics: Option<VideoGameSpecifics>, pub manga_specifics: Option<MangaSpecifics>, pub anime_specifics: Option<AnimeSpecifics>, + pub podcast_specifics: Option<PodcastSpecifics>, + pub audio_book_specifics: Option<AudioBookSpecifics>, + pub video_game_specifics: Option<VideoGameSpecifics>, pub visual_novel_specifics: Option<VisualNovelSpecifics>, } @@ -1210,13 +1244,15 @@ pub struct GraphqlMetadataDetails { pub show_specifics: Option<ShowSpecifics>, pub book_specifics: Option<BookSpecifics>, pub movie_specifics: Option<MovieSpecifics>, - pub podcast_specifics: Option<PodcastSpecifics>, + pub music_specifics: Option<MusicSpecifics>, pub manga_specifics: Option<MangaSpecifics>, pub anime_specifics: Option<AnimeSpecifics>, + pub podcast_specifics: Option<PodcastSpecifics>, pub creators: Vec<MetadataCreatorGroupedByRole>, pub audio_book_specifics: Option<AudioBookSpecifics>, pub video_game_specifics: Option<VideoGameSpecifics>, - pub external_identifiers: Option<ExternalIdentifiers>, + #[graphql(skip)] + pub external_identifiers: Option<MetadataExternalIdentifiers>, pub visual_novel_specifics: Option<VisualNovelSpecifics>, } diff --git a/crates/models/user/src/lib.rs b/crates/models/user/src/lib.rs index 54415accba..673c1aa1cc 100644 --- a/crates/models/user/src/lib.rs +++ b/crates/models/user/src/lib.rs @@ -46,6 +46,8 @@ pub struct UserMediaFeaturesEnabledPreferences { #[educe(Default = true)] pub manga: bool, #[educe(Default = true)] + pub music: bool, + #[educe(Default = true)] pub movie: bool, #[educe(Default = true)] pub podcast: bool, diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index 8ce8c69875..131511b041 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -19,7 +19,6 @@ educe = { workspace = true } enums = { path = "../enums" } graphql_client = { workspace = true } hashbag = { workspace = true } -isolang = { workspace = true } itertools = { workspace = true } media-models = { path = "../models/media" } paginate = { workspace = true } @@ -28,6 +27,7 @@ reqwest = { workspace = true } rust_decimal = { workspace = true } rust_decimal_macros = { workspace = true } rust_iso3166 = { workspace = true } +rustypipe = { workspace = true } scraper = { workspace = true } sea-orm = { workspace = true } serde = { workspace = true } diff --git a/crates/providers/src/anilist/mod.rs b/crates/providers/src/anilist/mod.rs index 298d920fdf..cb7eeb9dd5 100644 --- a/crates/providers/src/anilist/mod.rs +++ b/crates/providers/src/anilist/mod.rs @@ -5,7 +5,7 @@ use chrono::NaiveDate; use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl}; use common_utils::PAGE_SIZE; use config::AnilistPreferredLanguage; -use dependent_models::SearchResults; +use dependent_models::{PeopleSearchResponse, SearchResults}; use enums::{MediaLot, MediaSource}; use graphql_client::{GraphQLQuery, Response}; use itertools::Itertools; @@ -18,7 +18,7 @@ use media_models::{ use reqwest::Client; use rust_decimal::Decimal; use sea_orm::prelude::DateTimeUtc; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; static URL: &str = "https://graphql.anilist.co"; static STUDIO_ROLE: &str = "Production Studio"; @@ -83,16 +83,6 @@ pub struct AnilistService { preferred_language: AnilistPreferredLanguage, } -impl MediaProviderLanguages for AnilistService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl AnilistService { async fn new(config: &config::AnilistConfig) -> Self { let client = get_base_http_client(None); @@ -137,7 +127,7 @@ impl MediaProvider for NonMediaAnilistService { page: Option<i32>, source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let is_studio = matches!( source_specifics, Some(PersonSourceSpecifics { @@ -307,6 +297,7 @@ impl MediaProvider for NonMediaAnilistService { website: details.site_url, description: None, gender: None, + source_url: None, place: None, images: None, death_date: None, @@ -442,6 +433,7 @@ impl MediaProvider for NonMediaAnilistService { related, source_specifics: source_specifics.to_owned(), website: None, + source_url: None, } }; Ok(data) @@ -703,9 +695,10 @@ async fn media_details( title.romaji, preferred_language, ); + let identifier = media.id.to_string(); Ok(MetadataDetails { - title, - identifier: media.id.to_string(), + title: title.clone(), + identifier: identifier.clone(), is_nsfw: media.is_adult, source: MediaSource::Anilist, description: media.description, @@ -713,6 +706,10 @@ async fn media_details( people, creators: vec![], url_images: images, + source_url: Some(format!( + "https://anilist.co/{}/{}/{}", + lot, identifier, title + )), videos, genres: genres.into_iter().unique().collect(), publish_year: year, diff --git a/crates/providers/src/audible.rs b/crates/providers/src/audible.rs index 660c0ceecd..3e4ebde88d 100644 --- a/crates/providers/src/audible.rs +++ b/crates/providers/src/audible.rs @@ -5,7 +5,7 @@ use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails}; use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE}; use convert_case::{Case, Casing}; use database_models::metadata_group::MetadataGroupWithoutId; -use dependent_models::SearchResults; +use dependent_models::{PeopleSearchResponse, SearchResults}; use educe::Educe; use enums::{MediaLot, MediaSource}; use itertools::Itertools; @@ -20,9 +20,8 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_json::json; use strum::{Display, EnumIter, IntoEnumIterator}; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; -static LOCALES: [&str; 10] = ["au", "ca", "de", "es", "fr", "in", "it", "jp", "gb", "us"]; static AUDNEX_URL: &str = "https://api.audnex.us"; #[derive(EnumIter, Display)] @@ -134,16 +133,6 @@ pub struct AudibleService { locale: String, } -impl MediaProviderLanguages for AudibleService { - fn supported_languages() -> Vec<String> { - LOCALES.into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl AudibleService { fn url_from_locale(locale: &str) -> String { let suffix = match locale { @@ -181,7 +170,7 @@ impl MediaProvider for AudibleService { page: Option<i32>, _source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let internal_page: usize = page.unwrap_or(1).try_into().unwrap(); let req_internal_page = internal_page - 1; let client = Client::new(); @@ -236,19 +225,24 @@ impl MediaProvider for AudibleService { .json() .await .map_err(|e| anyhow!(e))?; + let name = data.name; Ok(MetadataPerson { place: None, gender: None, website: None, - name: data.name, related: vec![], death_date: None, birth_date: None, + name: name.clone(), identifier: data.asin, source_specifics: None, - description: data.description, source: MediaSource::Audible, + description: data.description, images: Some(Vec::from_iter(data.image)), + source_url: Some(format!( + "https://www.audible.com/author/{}/{}", + name, identity + )), }) } @@ -293,16 +287,21 @@ impl MediaProvider for AudibleService { is_recommendation: None, }) } + let title = data.product.title; Ok(( MetadataGroupWithoutId { - display_images: vec![], - parts: collection_contents.len().try_into().unwrap(), - identifier: identifier.to_owned(), - title: data.product.title, + images: None, description: None, - images: vec![], + title: title.clone(), + display_images: vec![], lot: MediaLot::AudioBook, source: MediaSource::Audible, + identifier: identifier.to_owned(), + parts: collection_contents.len().try_into().unwrap(), + source_url: Some(format!( + "https://www.audible.com/series/{}/{}", + identifier, title + )), }, collection_contents, )) @@ -452,14 +451,20 @@ impl AudibleService { None }; MetadataDetails { - identifier: item.asin, + people, + creators, + description, + url_images: images, + provider_rating: rating, lot: MediaLot::AudioBook, + title: item.title.clone(), source: MediaSource::Audible, + identifier: item.asin.clone(), is_nsfw: item.is_adult_product, - title: item.title, - description, - people, - creators, + source_url: Some(format!( + "https://www.audible.com/pd/{}/{}", + item.title, item.asin + )), genres: item .category_ladders .unwrap_or_default() @@ -477,8 +482,6 @@ impl AudibleService { audio_book_specifics: Some(AudioBookSpecifics { runtime: item.runtime_length_min, }), - url_images: images, - provider_rating: rating, ..Default::default() } } diff --git a/crates/providers/src/google_books.rs b/crates/providers/src/google_books.rs index 00846ae875..8d016ffdfb 100644 --- a/crates/providers/src/google_books.rs +++ b/crates/providers/src/google_books.rs @@ -17,7 +17,7 @@ use reqwest::{ }; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::{MediaProvider, }; static URL: &str = "https://www.googleapis.com/books/v1/volumes"; @@ -27,16 +27,6 @@ pub struct GoogleBooksService { pass_raw_query: bool, } -impl MediaProviderLanguages for GoogleBooksService { - fn supported_languages() -> Vec<String> { - vec!["us".to_owned()] - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl GoogleBooksService { pub async fn new(config: &config::GoogleBooksConfig) -> Self { let client = get_base_http_client(Some(vec![( @@ -221,11 +211,11 @@ impl GoogleBooksService { genres.push(g); } MetadataDetails { - identifier: id, lot: MediaLot::Book, - source: MediaSource::GoogleBooks, - title: item.title, + identifier: id.clone(), + title: item.title.clone(), description: item.description, + source: MediaSource::GoogleBooks, creators: creators.into_iter().unique().collect(), genres: genres.into_iter().unique().collect(), publish_year: item.published_date.and_then(|d| convert_date_to_year(&d)), @@ -235,6 +225,10 @@ impl GoogleBooksService { }), url_images: images.unique().collect(), provider_rating: item.average_rating, + source_url: Some(format!( + "https://www.google.co.in/books/edition/{}/{}", + item.title, id + )), ..Default::default() } } diff --git a/crates/providers/src/igdb.rs b/crates/providers/src/igdb.rs index 8f9a20400f..543313b4a5 100644 --- a/crates/providers/src/igdb.rs +++ b/crates/providers/src/igdb.rs @@ -9,7 +9,10 @@ use common_models::{ }; use common_utils::{ryot_log, PAGE_SIZE}; use database_models::metadata_group::MetadataGroupWithoutId; -use dependent_models::{ApplicationCacheValue, IgdbSettings, SearchResults}; +use dependent_models::{ + ApplicationCacheValue, IgdbSettings, MetadataGroupSearchResponse, PeopleSearchResponse, + SearchResults, +}; use enums::{MediaLot, MediaSource}; use itertools::Itertools; use media_models::{ @@ -28,7 +31,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use serde_with::{formats::Flexible, serde_as, TimestampSeconds}; use supporting_service::SupportingService; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; static URL: &str = "https://api.igdb.com/v4"; static IMAGE_URL: &str = "https://images.igdb.com/igdb/image/upload"; @@ -160,16 +163,6 @@ pub struct IgdbService { supporting_service: Arc<SupportingService>, } -impl MediaProviderLanguages for IgdbService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl IgdbService { pub async fn new(ss: Arc<SupportingService>) -> Self { let config = ss.config.video_games.clone(); @@ -189,7 +182,7 @@ impl MediaProvider for IgdbService { query: &str, page: Option<i32>, _display_nsfw: bool, - ) -> Result<SearchResults<MetadataGroupSearchItem>> { + ) -> Result<MetadataGroupSearchResponse> { let client = self.get_client_config().await?; let req_body = format!( r#" @@ -271,16 +264,18 @@ where id = {id}; } }) .collect_vec(); + let title = details.name.unwrap_or_default(); Ok(( MetadataGroupWithoutId { - display_images: vec![], - parts: items.len().try_into().unwrap(), - identifier: details.id.to_string(), - title: details.name.unwrap_or_default(), + images: None, description: None, - images: vec![], + title: title.clone(), + display_images: vec![], lot: MediaLot::VideoGame, source: MediaSource::Igdb, + identifier: details.id.to_string(), + parts: items.len().try_into().unwrap(), + source_url: Some(format!("https://www.igdb.com/collection/{}", title)), }, items, )) @@ -292,7 +287,7 @@ where id = {id}; page: Option<i32>, _source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let client = self.get_client_config().await?; let req_body = format!( r#" @@ -394,14 +389,21 @@ where id = {id}; }, } })); + let name = detail.name; Ok(MetadataPerson { + related, + gender: None, + birth_date: None, + death_date: None, + name: name.clone(), + source_specifics: None, + source: MediaSource::Igdb, + description: detail.description, identifier: detail.id.to_string(), - name: detail.name, + source_url: Some(format!("https://www.igdb.com/companies/{}", name)), images: Some(Vec::from_iter( detail.logo.map(|l| self.get_cover_image_url(l.image_id)), )), - source: MediaSource::Igdb, - description: detail.description, place: detail .country .and_then(from_numeric) @@ -411,11 +413,6 @@ where id = {id}; .unwrap_or_default() .first() .map(|i| i.url.clone()), - related, - birth_date: None, - death_date: None, - gender: None, - source_specifics: None, }) } @@ -539,9 +536,8 @@ impl IgdbService { let cc = &self.supporting_service.cache_service; let maybe_settings = cc .get_value::<IgdbSettings>(ApplicationCacheKey::IgdbSettings) - .await - .ok(); - let access_token = if let Some(value) = maybe_settings.flatten() { + .await; + let access_token = if let Some(value) = maybe_settings { value.access_token } else { let access_token = self.get_access_token().await; @@ -613,17 +609,19 @@ impl IgdbService { source: MetadataVideoSource::Youtube, }) .collect_vec(); + let title = item.name.unwrap(); MetadataDetails { - identifier: item.id.to_string(), + title: title.clone(), lot: MediaLot::VideoGame, source: MediaSource::Igdb, - title: item.name.unwrap(), description: item.summary, + identifier: item.id.to_string(), people, url_images: images, videos, publish_date: item.first_release_date.map(|d| d.date_naive()), publish_year: item.first_release_date.map(|d| d.year()), + source_url: Some(format!("https://www.igdb.com/games/{}", title)), genres: item .genres .unwrap_or_default() diff --git a/crates/providers/src/itunes.rs b/crates/providers/src/itunes.rs index bc186c16b7..2b9115dc42 100644 --- a/crates/providers/src/itunes.rs +++ b/crates/providers/src/itunes.rs @@ -14,7 +14,7 @@ use media_models::{ use reqwest::Client; use sea_orm::prelude::ChronoDateTimeUtc; use serde::{Deserialize, Serialize}; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::{MediaProvider, }; static URL: &str = "https://itunes.apple.com"; @@ -24,16 +24,6 @@ pub struct ITunesService { language: String, } -impl MediaProviderLanguages for ITunesService { - fn supported_languages() -> Vec<String> { - ["en_us", "ja_jp"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "en_us".to_owned() - } -} - impl ITunesService { pub async fn new(config: &config::ITunesConfig) -> Self { let client = get_base_http_client(None); @@ -154,16 +144,20 @@ impl MediaProvider for ITunesService { .collect_vec(); episodes.reverse(); Ok(MetadataDetails { - identifier: details.identifier, - title: details.title, + genres, + creators, + url_images, + description, publish_date, - publish_year: publish_date.map(|d| d.year()), - source: MediaSource::Itunes, lot: MediaLot::Podcast, - description, - url_images, - creators, - genres, + source: MediaSource::Itunes, + title: details.title.clone(), + identifier: details.identifier, + publish_year: publish_date.map(|d| d.year()), + source_url: Some(format!( + "https://podcasts.apple.com/us/podcast/{}/id{}", + details.title, identifier + )), podcast_specifics: Some(PodcastSpecifics { total_episodes: episodes.len(), episodes, diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index 113854ee55..a8f9282b89 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -9,3 +9,4 @@ pub mod manga_updates; pub mod openlibrary; pub mod tmdb; pub mod vndb; +pub mod youtube_music; diff --git a/crates/providers/src/listennotes.rs b/crates/providers/src/listennotes.rs index 3ea99486fe..62a7e6cc5a 100644 --- a/crates/providers/src/listennotes.rs +++ b/crates/providers/src/listennotes.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use serde_with::{formats::Flexible, serde_as, TimestampMilliSeconds}; use supporting_service::SupportingService; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; static URL: &str = "https://listen-api.listennotes.com/api/v2"; @@ -33,16 +33,6 @@ pub struct ListennotesService { supporting_service: Arc<SupportingService>, } -impl MediaProviderLanguages for ListennotesService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl ListennotesService { pub async fn new(ss: Arc<SupportingService>) -> Self { let url = env::var("LISTENNOTES_API_URL") @@ -188,9 +178,8 @@ impl ListennotesService { let cc = &self.supporting_service.cache_service; let maybe_settings = cc .get_value::<ListennotesSettings>(ApplicationCacheKey::ListennotesSettings) - .await - .ok(); - let genres = if let Some(value) = maybe_settings.flatten() { + .await; + let genres = if let Some(value) = maybe_settings { value.genres } else { #[derive(Debug, Serialize, Deserialize, Default)] @@ -266,12 +255,16 @@ impl ListennotesService { let podcast_data: Podcast = resp.json().await.map_err(|e| anyhow!(e))?; let genres = self.get_genres().await?; Ok(MetadataDetails { - identifier: podcast_data.id, - title: podcast_data.title, - is_nsfw: podcast_data.explicit_content, - description: podcast_data.description, lot: MediaLot::Podcast, + identifier: podcast_data.id, source: MediaSource::Listennotes, + title: podcast_data.title.clone(), + description: podcast_data.description, + is_nsfw: podcast_data.explicit_content, + source_url: Some(format!( + "https://www.listennotes.com/podcasts/{}-{}", + podcast_data.title, identifier + )), creators: Vec::from_iter(podcast_data.publisher.map(|p| MetadataFreeCreator { name: p, role: "Publishing".to_owned(), diff --git a/crates/providers/src/mal.rs b/crates/providers/src/mal.rs index 05cde95045..76e31523db 100644 --- a/crates/providers/src/mal.rs +++ b/crates/providers/src/mal.rs @@ -18,7 +18,7 @@ use reqwest::{ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_json::json; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::{MediaProvider, }; static URL: &str = "https://api.myanimelist.net/v2"; @@ -27,16 +27,6 @@ pub struct MalService { client: Client, } -impl MediaProviderLanguages for MalService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - #[derive(Debug, Clone)] pub struct NonMediaMalService {} @@ -256,13 +246,19 @@ async fn details(client: &Client, media_type: &str, id: &str) -> Result<Metadata } suggestions.shuffle(&mut rng()); let is_nsfw = details.nsfw.map(|n| !matches!(n.as_str(), "white")); + let identifier = details.id.to_string(); + let title = details.title; let data = MetadataDetails { - identifier: details.id.to_string(), - title: details.title, + identifier: identifier.clone(), + title: title.clone(), source: MediaSource::Mal, description: details.synopsis, lot, is_nsfw, + source_url: Some(format!( + "https://myanimelist.net/{}/{}/{}", + media_type, identifier, title + )), production_status: details.status.map(|s| s.to_case(Case::Title)), genres: details .genres diff --git a/crates/providers/src/manga_updates.rs b/crates/providers/src/manga_updates.rs index 7b037a8e44..11ef20c704 100644 --- a/crates/providers/src/manga_updates.rs +++ b/crates/providers/src/manga_updates.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use chrono::NaiveDate; use common_models::{PersonSourceSpecifics, SearchDetails}; use common_utils::PAGE_SIZE; -use dependent_models::SearchResults; +use dependent_models::{PeopleSearchResponse, SearchResults}; use enums::{MediaLot, MediaSource}; use itertools::Itertools; use media_models::{ @@ -15,7 +15,7 @@ use media_models::{ use reqwest::Client; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::{MediaProvider, }; static URL: &str = "https://api.mangaupdates.com/v1"; @@ -24,16 +24,6 @@ pub struct MangaUpdatesService { client: Client, } -impl MediaProviderLanguages for MangaUpdatesService { - fn supported_languages() -> Vec<String> { - vec!["us".to_owned()] - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl MangaUpdatesService { pub async fn new(_config: &config::MangaUpdatesConfig) -> Self { let client = get_base_http_client(None); @@ -172,7 +162,7 @@ impl MediaProvider for MangaUpdatesService { page: Option<i32>, _source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let data: MetadataSearchResponse<PersonItemResponse> = self .client .post(format!("{}/authors/search", URL)) @@ -251,11 +241,17 @@ impl MediaProvider for MangaUpdatesService { }) .collect_vec(); let resp = MetadataPerson { - identifier: identity.to_owned(), - source: MediaSource::MangaUpdates, - name: data.name.unwrap(), + related, + website: None, + death_date: None, + source_url: None, + description: None, gender: data.gender, place: data.birthplace, + source_specifics: None, + name: data.name.unwrap(), + identifier: identity.to_owned(), + source: MediaSource::MangaUpdates, images: Some(Vec::from_iter(data.image.and_then(|i| i.url.original))), birth_date: data.birthday.and_then(|b| { if let (Some(y), Some(m), Some(d)) = (b.year, b.month, b.day) { @@ -264,11 +260,6 @@ impl MediaProvider for MangaUpdatesService { None } }), - related, - death_date: None, - description: None, - website: None, - source_specifics: None, }; Ok(resp) } diff --git a/crates/providers/src/openlibrary.rs b/crates/providers/src/openlibrary.rs index 51de37df31..231b1e4c7c 100644 --- a/crates/providers/src/openlibrary.rs +++ b/crates/providers/src/openlibrary.rs @@ -5,7 +5,7 @@ use chrono::{Datelike, NaiveDate}; use common_models::{PersonSourceSpecifics, SearchDetails}; use common_utils::{ryot_log, PAGE_SIZE}; use convert_case::{Case, Casing}; -use dependent_models::SearchResults; +use dependent_models::{PeopleSearchResponse, SearchResults}; use enums::{MediaLot, MediaSource}; use itertools::Itertools; use media_models::{ @@ -17,7 +17,7 @@ use reqwest::Client; use scraper::{Html, Selector}; use serde::{Deserialize, Serialize}; use serde_json::json; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::{MediaProvider, }; static URL: &str = "https://openlibrary.org"; static IMAGE_BASE_URL: &str = "https://covers.openlibrary.org"; @@ -91,16 +91,6 @@ struct OpenAuthorLibrarySearchResponse { docs: Vec<PeopleSearchOpenlibraryAuthor>, } -impl MediaProviderLanguages for OpenlibraryService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl OpenlibraryService { pub async fn new(config: &config::OpenlibraryConfig) -> Self { let client = get_base_http_client(None); @@ -175,7 +165,7 @@ impl MediaProvider for OpenlibraryService { page: Option<i32>, _source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let page = page.unwrap_or(1); let rsp = self .client @@ -215,12 +205,12 @@ impl MediaProvider for OpenlibraryService { async fn person_details( &self, - identity: &str, + identifier: &str, _source_specifics: &Option<PersonSourceSpecifics>, ) -> Result<MetadataPerson> { let rsp = self .client - .get(format!("{}/authors/{}.json", URL, identity)) + .get(format!("{}/authors/{}.json", URL, identifier)) .send() .await .map_err(|e| anyhow!(e))?; @@ -240,7 +230,7 @@ impl MediaProvider for OpenlibraryService { .collect(); let author_works: OpenlibraryEditionsResponse = self .client - .get(format!("{}/authors/{}/works.json", URL, identity)) + .get(format!("{}/authors/{}/works.json", URL, identifier)) .query(&serde_json::json!({ "limit": 600 })) .send() .await @@ -275,21 +265,26 @@ impl MediaProvider for OpenlibraryService { } } ryot_log!(debug, "Found {} related works.", related.len()); + let name = data.name; Ok(MetadataPerson { - identifier, - source: MediaSource::Openlibrary, - name: data.name, + related, + place: None, description, + gender: None, + name: name.clone(), images: Some(images), + source_specifics: None, + identifier: identifier.clone(), + source: MediaSource::Openlibrary, + birth_date: data.birth_date.and_then(|b| parse_date(&b)), + death_date: data.death_date.and_then(|b| parse_date(&b)), + source_url: Some(format!( + "https://openlibrary.org/authors/{}/{}", + identifier, name + )), website: data .links .and_then(|l| l.first().and_then(|a| a.url.clone())), - birth_date: data.birth_date.and_then(|b| parse_date(&b)), - death_date: data.death_date.and_then(|b| parse_date(&b)), - related, - gender: None, - place: None, - source_specifics: None, }) } @@ -447,21 +442,25 @@ impl MediaProvider for OpenlibraryService { }); } } - + let identifier = get_key(&data.key); Ok(MetadataDetails { - identifier: get_key(&data.key), - title: data.title, - description, - lot: MediaLot::Book, - source: MediaSource::Openlibrary, people, genres, + description, + suggestions, url_images: images, + lot: MediaLot::Book, + title: data.title.clone(), + identifier: identifier.clone(), + source: MediaSource::Openlibrary, publish_year: first_release_date.map(|d| d.year()), + source_url: Some(format!( + "https://openlibrary.org/works/{}/{}", + identifier, data.title + )), book_specifics: Some(BookSpecifics { pages: Some(num_pages), }), - suggestions, ..Default::default() }) } diff --git a/crates/providers/src/tmdb.rs b/crates/providers/src/tmdb.rs index 20d7d42ffb..f15f5b6a9c 100644 --- a/crates/providers/src/tmdb.rs +++ b/crates/providers/src/tmdb.rs @@ -12,12 +12,15 @@ use common_models::{ }; use common_utils::{convert_date_to_year, convert_string_to_date, SHOW_SPECIAL_SEASON_NAMES}; use database_models::metadata_group::MetadataGroupWithoutId; -use dependent_models::{ApplicationCacheValue, SearchResults, TmdbLanguage, TmdbSettings}; +use dependent_models::{ + ApplicationCacheValue, MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults, + TmdbLanguage, TmdbSettings, +}; use enums::{MediaLot, MediaSource}; use hashbag::HashBag; use itertools::Itertools; use media_models::{ - ExternalIdentifiers, MetadataDetails, MetadataGroupSearchItem, MetadataImage, + MetadataDetails, MetadataExternalIdentifiers, MetadataGroupSearchItem, MetadataImage, MetadataImageForMediaDetails, MetadataPerson, MetadataPersonRelated, MetadataSearchItem, MetadataVideo, MetadataVideoSource, MovieSpecifics, PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem, ShowEpisode, ShowSeason, ShowSpecifics, @@ -33,7 +36,7 @@ use sea_orm::prelude::DateTimeUtc; use serde::{Deserialize, Serialize}; use serde_json::json; use supporting_service::SupportingService; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; static URL: &str = "https://api.themoviedb.org/3"; @@ -375,7 +378,7 @@ impl TmdbService { &self, type_: &str, identifier: &str, - ) -> Result<ExternalIdentifiers> { + ) -> Result<MetadataExternalIdentifiers> { let rsp = self .client .get(format!("{}/{}/{}/external_ids", URL, type_, identifier)) @@ -386,18 +389,6 @@ impl TmdbService { } } -impl MediaProviderLanguages for TmdbService { - fn supported_languages() -> Vec<String> { - isolang::languages() - .filter_map(|l| l.to_639_1().map(String::from)) - .collect() - } - - fn default_language() -> String { - "en".to_owned() - } -} - pub struct NonMediaTmdbService { base: TmdbService, } @@ -418,7 +409,7 @@ impl MediaProvider for NonMediaTmdbService { page: Option<i32>, source_specifics: &Option<PersonSourceSpecifics>, display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let language = &self .base .supporting_service @@ -474,7 +465,7 @@ impl MediaProvider for NonMediaTmdbService { async fn person_details( &self, - identity: &str, + identifier: &str, source_specifics: &Option<PersonSourceSpecifics>, ) -> Result<MetadataPerson> { let type_ = match source_specifics { @@ -487,7 +478,7 @@ impl MediaProvider for NonMediaTmdbService { let details: TmdbNonMediaEntity = self .base .client - .get(format!("{}/{}/{}", URL, type_, identity)) + .get(format!("{}/{}/{}", URL, type_, identifier)) .query(&json!({ "language": self.base.language })) .send() .await @@ -497,7 +488,7 @@ impl MediaProvider for NonMediaTmdbService { .map_err(|e| anyhow!(e))?; let mut images = vec![]; self.base - .save_all_images(type_, identity, &mut images) + .save_all_images(type_, identifier, &mut images) .await?; let images = images .into_iter() @@ -510,7 +501,7 @@ impl MediaProvider for NonMediaTmdbService { let cred_det: TmdbCreditsResponse = self .base .client - .get(format!("{}/{}/{}/combined_credits", URL, type_, identity)) + .get(format!("{}/{}/{}/combined_credits", URL, type_, identifier)) .query(&json!({ "language": self.base.language })) .send() .await @@ -545,7 +536,7 @@ impl MediaProvider for NonMediaTmdbService { .client .get(format!("{}/discover/{}", URL, m_typ)) .query( - &json!({ "with_companies": identity, "page": i, "language": self.base.language }), + &json!({ "with_companies": identifier, "page": i, "language": self.base.language }), ) .send() .await @@ -575,24 +566,29 @@ impl MediaProvider for NonMediaTmdbService { } } } + let name = details.name; let resp = MetadataPerson { - name: details.name, + related, + name: name.clone(), images: Some(images), - identifier: details.id.to_string(), - description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }), source: MediaSource::Tmdb, - place: details.origin_country.or(details.place_of_birth), website: details.homepage, birth_date: details.birthday, death_date: details.deathday, + identifier: details.id.to_string(), + source_specifics: source_specifics.to_owned(), + place: details.origin_country.or(details.place_of_birth), + source_url: Some(format!( + "https://www.themoviedb.org/person/{}-{}", + identifier, name + )), + description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }), gender: details.gender.and_then(|g| match g { 1 => Some("Female".to_owned()), 2 => Some("Male".to_owned()), 3 => Some("Non-Binary".to_owned()), _ => None, }), - source_specifics: source_specifics.to_owned(), - related, }; Ok(resp) } @@ -790,21 +786,22 @@ impl MediaProvider for TmdbMovieService { .base .get_external_identifiers("movie", identifier) .await?; + let title = data.title.unwrap(); Ok(MetadataDetails { + people, + title: title.clone(), identifier: data.id.to_string(), is_nsfw: data.adult, - original_language: self.base.get_language_name(data.original_language), lot: MediaLot::Movie, source: MediaSource::Tmdb, production_status: data.status, - title: data.title.unwrap(), genres: data .genres .unwrap_or_default() .into_iter() .map(|g| g.name) .collect(), - people, + original_language: self.base.get_language_name(data.original_language), url_images: image_ids .into_iter() .unique() @@ -823,6 +820,10 @@ impl MediaProvider for TmdbMovieService { runtime: data.runtime, }), suggestions, + source_url: Some(format!( + "https://www.themoviedb.org/movie/{}-{}", + data.id, title + )), provider_rating: if let Some(av) = data.vote_average { if av != dec!(0) { Some(av * dec!(10)) @@ -853,7 +854,7 @@ impl MediaProvider for TmdbMovieService { query: &str, page: Option<i32>, display_nsfw: bool, - ) -> Result<SearchResults<MetadataGroupSearchItem>> { + ) -> Result<MetadataGroupSearchResponse> { let page = page.unwrap_or(1); let rsp = self .base @@ -939,22 +940,29 @@ impl MediaProvider for TmdbMovieService { is_recommendation: None, }) .collect_vec(); + let title = replace_from_end(data.name, " Collection", ""); Ok(( MetadataGroupWithoutId { - display_images: vec![], - parts: parts.len().try_into().unwrap(), - identifier: identifier.to_owned(), - title: replace_from_end(data.name, " Collection", ""), - description: data.overview, - images: images - .into_iter() - .unique() - .map(|p| MetadataImage { - url: StoredUrl::Url(self.base.get_image_url(p)), - }) - .collect(), lot: MediaLot::Movie, + title: title.clone(), + display_images: vec![], source: MediaSource::Tmdb, + description: data.overview, + identifier: identifier.to_owned(), + parts: parts.len().try_into().unwrap(), + source_url: Some(format!( + "https://www.themoviedb.org/collections/{}-{}", + identifier, title + )), + images: Some( + images + .into_iter() + .unique() + .map(|p| MetadataImage { + url: StoredUrl::Url(self.base.get_image_url(p)), + }) + .collect(), + ), }, parts, )) @@ -1144,16 +1152,17 @@ impl MediaProvider for TmdbShowService { .count(); let watch_providers = self.base.get_all_watch_providers("tv", identifier).await?; let external_identifiers = self.base.get_external_identifiers("tv", identifier).await?; + let title = show_data.name.unwrap(); Ok(MetadataDetails { - identifier: show_data.id.to_string(), - title: show_data.name.unwrap(), - is_nsfw: show_data.adult, - original_language: self.base.get_language_name(show_data.original_language), + people, lot: MediaLot::Show, - production_status: show_data.status, + title: title.clone(), + is_nsfw: show_data.adult, source: MediaSource::Tmdb, description: show_data.overview, - people, + production_status: show_data.status, + identifier: show_data.id.to_string(), + original_language: self.base.get_language_name(show_data.original_language), genres: show_data .genres .unwrap_or_default() @@ -1164,6 +1173,10 @@ impl MediaProvider for TmdbShowService { publish_date: convert_string_to_date( &show_data.first_air_date.clone().unwrap_or_default(), ), + source_url: Some(format!( + "https://www.themoviedb.org/tv/{}-{}", + show_data.id, title + )), url_images: image_ids .into_iter() .unique() @@ -1320,9 +1333,8 @@ async fn get_settings( let cc = &supporting_service.cache_service; let maybe_settings = cc .get_value::<TmdbSettings>(ApplicationCacheKey::TmdbSettings) - .await - .ok(); - let tmdb_settings = if let Some(setting) = maybe_settings.flatten() { + .await; + let tmdb_settings = if let Some(setting) = maybe_settings { setting } else { #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/providers/src/vndb.rs b/crates/providers/src/vndb.rs index 42dce07244..23cb9ba537 100644 --- a/crates/providers/src/vndb.rs +++ b/crates/providers/src/vndb.rs @@ -3,7 +3,7 @@ use application_utils::get_base_http_client; use async_trait::async_trait; use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails}; use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE}; -use dependent_models::SearchResults; +use dependent_models::{PeopleSearchResponse, SearchResults}; use enums::{MediaLot, MediaSource}; use itertools::Itertools; use media_models::{ @@ -13,7 +13,7 @@ use media_models::{ use reqwest::Client; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -use traits::{MediaProvider, MediaProviderLanguages}; +use traits::MediaProvider; static URL: &str = "https://api.vndb.org/kana"; const METADATA_FIELDS_SMALL: &str = "title,image.url,released,screenshots.url,developers.name"; @@ -28,16 +28,6 @@ pub struct VndbService { client: Client, } -impl MediaProviderLanguages for VndbService { - fn supported_languages() -> Vec<String> { - vec!["us".to_owned()] - } - - fn default_language() -> String { - "us".to_owned() - } -} - impl VndbService { pub async fn new(_config: &config::VisualNovelConfig) -> Self { let client = get_base_http_client(None); @@ -87,7 +77,7 @@ impl MediaProvider for VndbService { page: Option<i32>, _source_specifics: &Option<PersonSourceSpecifics>, _display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { let data = self .client .post(format!("{}/producer", URL)) @@ -148,18 +138,19 @@ impl MediaProvider for VndbService { let data: SearchResponse = rsp.json().await.map_err(|e| anyhow!(e))?; let item = data.results.unwrap_or_default().pop().unwrap(); Ok(MetadataPerson { - identifier: item.id, - source: MediaSource::Vndb, - name: item.title.unwrap(), - description: item.description, - related: vec![], + place: None, gender: None, images: None, + website: None, + related: vec![], death_date: None, birth_date: None, - place: None, - website: None, + source_url: None, + identifier: item.id, source_specifics: None, + name: item.title.unwrap(), + source: MediaSource::Vndb, + description: item.description, }) } @@ -265,16 +256,18 @@ impl VndbService { .into_iter() .map(|t| t.name) .collect_vec(); + let identifier = item.id; MetadataDetails { - identifier: item.id, - lot: MediaLot::VisualNovel, source: MediaSource::Vndb, + lot: MediaLot::VisualNovel, + identifier: identifier.clone(), production_status: item.devstatus.map(|s| match s { 0 => "Finished".to_owned(), 1 => "In development".to_owned(), 2 => "Cancelled".to_owned(), _ => unreachable!(), }), + source_url: Some(format!("https://vndb.org/{}", identifier)), title: item.title.unwrap(), description: item.description, people: people.into_iter().unique().collect(), diff --git a/crates/providers/src/youtube_music.rs b/crates/providers/src/youtube_music.rs new file mode 100644 index 0000000000..48a003a9c3 --- /dev/null +++ b/crates/providers/src/youtube_music.rs @@ -0,0 +1,279 @@ +use anyhow::Result; +use async_trait::async_trait; +use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl}; +use common_utils::TEMP_DIR; +use database_models::metadata_group::MetadataGroupWithoutId; +use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults}; +use enums::{MediaLot, MediaSource}; +use itertools::Itertools; +use media_models::{ + MetadataDetails, MetadataGroupSearchItem, MetadataImage, MetadataImageForMediaDetails, + MetadataPerson, MetadataPersonRelated, MetadataSearchItem, MusicSpecifics, + PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem, +}; +use rustypipe::{ + client::{RustyPipe, RustyPipeQuery}, + model::{richtext::ToHtml, Thumbnail}, +}; +use traits::MediaProvider; + +pub struct YoutubeMusicService { + client: RustyPipeQuery, +} + +impl YoutubeMusicService { + pub async fn new() -> Self { + let client = RustyPipe::builder().storage_dir(TEMP_DIR).build().unwrap(); + Self { + client: client.query(), + } + } +} + +impl YoutubeMusicService { + fn order_images_by_size(&self, images: &[Thumbnail]) -> Vec<Thumbnail> { + images + .iter() + .cloned() + .sorted_by_key(|i| i.width * i.height) + .rev() + .collect() + } + + fn largest_image(&self, images: &[Thumbnail]) -> Option<Thumbnail> { + self.order_images_by_size(images).first().cloned() + } +} + +#[async_trait] +impl MediaProvider for YoutubeMusicService { + async fn metadata_details(&self, identifier: &str) -> Result<MetadataDetails> { + let details = self.client.music_details(identifier).await?; + let suggestions = if let Some(related_id) = details.related_id { + let related = self.client.music_related(related_id).await?; + related + .tracks + .into_iter() + .map(|t| PartialMetadataWithoutId { + title: t.name, + identifier: t.id, + lot: MediaLot::Music, + is_recommendation: None, + source: MediaSource::YoutubeMusic, + image: self.largest_image(&t.cover).map(|c| c.url.to_owned()), + }) + .collect() + } else { + vec![] + }; + let identifier = details.track.id; + Ok(MetadataDetails { + suggestions, + lot: MediaLot::Music, + title: details.track.name, + identifier: identifier.clone(), + source: MediaSource::YoutubeMusic, + group_identifiers: details.track.album.into_iter().map(|a| a.id).collect(), + source_url: Some(format!("https://music.youtube.com/watch?v={}", identifier)), + music_specifics: Some(MusicSpecifics { + by_various_artists: Some(details.track.by_va), + duration: details.track.duration.map(|d| d.try_into().unwrap()), + view_count: details.track.view_count.map(|v| v.try_into().unwrap()), + }), + url_images: self + .order_images_by_size(&details.track.cover) + .into_iter() + .map(|t| MetadataImageForMediaDetails { image: t.url }) + .collect(), + people: details + .track + .artists + .into_iter() + .filter_map(|a| { + a.id.map(|id| PartialMetadataPerson { + name: a.name, + identifier: id, + character: None, + source_specifics: None, + role: "Artist".to_string(), + source: MediaSource::YoutubeMusic, + }) + }) + .collect(), + ..Default::default() + }) + } + + async fn metadata_search( + &self, + query: &str, + _page: Option<i32>, + _display_nsfw: bool, + ) -> Result<SearchResults<MetadataSearchItem>> { + let results = self.client.music_search_tracks(query).await?; + let data = SearchResults { + details: SearchDetails { + total: 1, + next_page: None, + }, + items: results + .items + .items + .into_iter() + .map(|i| MetadataSearchItem { + title: i.name, + identifier: i.id, + publish_year: None, + image: self.largest_image(&i.cover).map(|t| t.url.to_owned()), + }) + .collect(), + }; + Ok(data) + } + + async fn metadata_group_details( + &self, + identifier: &str, + ) -> Result<(MetadataGroupWithoutId, Vec<PartialMetadataWithoutId>)> { + let album = self.client.music_album(identifier).await?; + let title = album.name; + Ok(( + MetadataGroupWithoutId { + title: title.clone(), + lot: MediaLot::Music, + identifier: album.id, + display_images: vec![], + source: MediaSource::YoutubeMusic, + parts: album.tracks.len().try_into().unwrap(), + description: album.description.map(|d| d.to_html()), + source_url: album + .playlist_id + .map(|id| format!("https://music.youtube.com/playlist?list={}", id)), + images: Some( + self.largest_image(&album.cover) + .into_iter() + .map(|c| MetadataImage { + url: StoredUrl::Url(c.url), + }) + .collect(), + ), + }, + album + .tracks + .into_iter() + .map(|t| PartialMetadataWithoutId { + title: t.name, + identifier: t.id, + lot: MediaLot::Music, + is_recommendation: None, + source: MediaSource::YoutubeMusic, + image: self.largest_image(&t.cover).map(|t| t.url.to_owned()), + }) + .collect(), + )) + } + + async fn metadata_group_search( + &self, + query: &str, + _page: Option<i32>, + _display_nsfw: bool, + ) -> Result<MetadataGroupSearchResponse> { + let data = self.client.music_search_albums(query).await?; + Ok(SearchResults { + details: SearchDetails { + total: 1, + next_page: None, + }, + items: data + .items + .items + .into_iter() + .map(|t| MetadataGroupSearchItem { + parts: None, + name: t.name, + identifier: t.id, + image: self.largest_image(&t.cover).map(|t| t.url.to_owned()), + }) + .collect(), + }) + } + + async fn person_details( + &self, + identifier: &str, + _source_specifics: &Option<PersonSourceSpecifics>, + ) -> Result<MetadataPerson> { + let data = self.client.music_artist(identifier, false).await?; + let related = if let Some(playlist_id) = data.tracks_playlist_id { + let items = self.client.music_playlist(playlist_id).await?; + items + .tracks + .items + .into_iter() + .map(|t| MetadataPersonRelated { + character: None, + role: "Artist".to_string(), + metadata: PartialMetadataWithoutId { + title: t.name, + identifier: t.id, + lot: MediaLot::Music, + is_recommendation: None, + source: MediaSource::YoutubeMusic, + image: self.largest_image(&t.cover).map(|t| t.url.to_owned()), + }, + }) + .collect() + } else { + vec![] + }; + let identifier = data.id; + Ok(MetadataPerson { + related, + place: None, + gender: None, + website: None, + name: data.name, + birth_date: None, + death_date: None, + source_specifics: None, + description: data.description, + identifier: identifier.clone(), + source: MediaSource::YoutubeMusic, + source_url: Some(format!("https://music.youtube.com/channel/{}", identifier)), + images: Some( + self.largest_image(&data.header_image) + .into_iter() + .map(|t| t.url.to_owned()) + .collect(), + ), + }) + } + + async fn people_search( + &self, + query: &str, + _page: Option<i32>, + _source_specifics: &Option<PersonSourceSpecifics>, + _display_nsfw: bool, + ) -> Result<PeopleSearchResponse> { + let data = self.client.music_search_artists(query).await?; + Ok(SearchResults { + details: SearchDetails { + total: 1, + next_page: None, + }, + items: data + .items + .items + .into_iter() + .map(|t| PeopleSearchItem { + name: t.name, + identifier: t.id, + birth_year: None, + image: self.largest_image(&t.avatar).map(|t| t.url.to_owned()), + }) + .collect(), + }) + } +} diff --git a/crates/services/cache/src/lib.rs b/crates/services/cache/src/lib.rs index 18ea61ea69..78be3e8ab1 100644 --- a/crates/services/cache/src/lib.rs +++ b/crates/services/cache/src/lib.rs @@ -30,7 +30,6 @@ impl CacheService { match key { ApplicationCacheKey::IgdbSettings | ApplicationCacheKey::ListennotesSettings - | ApplicationCacheKey::ServerKeyValidated | ApplicationCacheKey::TmdbSettings => None, ApplicationCacheKey::CoreDetails @@ -80,14 +79,12 @@ impl CacheService { Ok(insert_id) } - pub async fn get_value<T: DeserializeOwned>( - &self, - key: ApplicationCacheKey, - ) -> Result<Option<T>> { + pub async fn get_value<T: DeserializeOwned>(&self, key: ApplicationCacheKey) -> Option<T> { let cache = ApplicationCache::find() .filter(application_cache::Column::Key.eq(key.clone())) .one(&self.db) - .await?; + .await + .ok()?; let Some(db_value) = cache .filter(|cache| { cache @@ -96,10 +93,9 @@ impl CacheService { }) .map(|m| m.value) else { - return Ok(None); + return None; }; - let parsed = serde_json::from_value::<T>(db_value); - Ok(parsed.ok()) + serde_json::from_value::<T>(db_value).ok() } pub async fn expire_key(&self, key: ApplicationCacheKey) -> Result<bool> { diff --git a/crates/services/exporter/src/lib.rs b/crates/services/exporter/src/lib.rs index 142c9ad834..eeeed08dd1 100644 --- a/crates/services/exporter/src/lib.rs +++ b/crates/services/exporter/src/lib.rs @@ -4,7 +4,7 @@ use async_graphql::{Error, Result}; use background::ApplicationJob; use chrono::{DateTime, Utc}; use common_models::ExportJob; -use common_utils::{IsFeatureEnabled, TEMP_DIR}; +use common_utils::TEMP_DIR; use database_models::{ exercise, prelude::{ diff --git a/crates/services/importer/src/hevy.rs b/crates/services/importer/src/hevy.rs index 91a2a7ac54..706274f9f7 100644 --- a/crates/services/importer/src/hevy.rs +++ b/crates/services/importer/src/hevy.rs @@ -174,5 +174,5 @@ pub async fn import( } fn parse_date_string(input: &str) -> NaiveDateTime { - NaiveDateTime::parse_from_str(&input, "%d %b %Y, %H:%M").unwrap() + NaiveDateTime::parse_from_str(input, "%d %b %Y, %H:%M").unwrap() } diff --git a/crates/services/importer/src/lib.rs b/crates/services/importer/src/lib.rs index 87972744a9..b47c3e8e72 100644 --- a/crates/services/importer/src/lib.rs +++ b/crates/services/importer/src/lib.rs @@ -217,7 +217,7 @@ pub mod utils { .filter(exercise::Column::Name.eq(exercise_name)) .one(&ss.db) .await?; - let generated_id = generate_exercise_id(&exercise_name, exercise_lot, user_id); + let generated_id = generate_exercise_id(exercise_name, exercise_lot, user_id); let exercise_id = match existing_exercise { Some(db_ex) if db_ex.source == ExerciseSource::Github || db_ex.id == generated_id => { db_ex.id diff --git a/crates/services/integration/src/yank/audiobookshelf.rs b/crates/services/integration/src/yank/audiobookshelf.rs index 0fa7531bd3..8d6979d838 100644 --- a/crates/services/integration/src/yank/audiobookshelf.rs +++ b/crates/services/integration/src/yank/audiobookshelf.rs @@ -222,5 +222,5 @@ pub async fn sync_to_owned_collection( })); } } - todo!() + Ok(result) } diff --git a/crates/services/miscellaneous/Cargo.toml b/crates/services/miscellaneous/Cargo.toml index e7671178ed..c6b39224cd 100644 --- a/crates/services/miscellaneous/Cargo.toml +++ b/crates/services/miscellaneous/Cargo.toml @@ -15,7 +15,6 @@ database-utils = { path = "../../utils/database" } dependent-models = { path = "../../models/dependent" } dependent-utils = { path = "../../utils/dependent" } enums = { path = "../../enums" } -env-utils = { path = "../../utils/env" } futures = { workspace = true } itertools = { workspace = true } markdown = { workspace = true } @@ -30,13 +29,10 @@ sea-orm = { workspace = true } sea-query = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -serde_with = { workspace = true } -slug = { workspace = true } supporting-service = { path = "../supporting" } tokio = { workspace = true } tracing = { workspace = true } traits = { path = "../../traits" } -unkey = { workspace = true } user-models = { path = "../../models/user" } uuid = { workspace = true } diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs index 56da9c7a5e..e9a0f5011a 100644 --- a/crates/services/miscellaneous/src/lib.rs +++ b/crates/services/miscellaneous/src/lib.rs @@ -10,17 +10,15 @@ use application_utils::{ }; use async_graphql::{Error, Result}; use background::{ApplicationJob, CoreApplicationJob}; -use chrono::{Days, Duration, NaiveDate, TimeZone, Utc}; +use chrono::{Days, Duration, NaiveDate, Utc}; use common_models::{ - ApplicationCacheKey, BackendError, BackgroundJob, ChangeCollectionToEntityInput, - DefaultCollection, IdAndNamedObject, MediaStateChanged, MetadataGroupSearchInput, - MetadataSearchInput, PeopleSearchInput, ProgressUpdateCacheInput, SearchDetails, SearchInput, - StoredUrl, StringIdObject, UserLevelCacheKey, + ApplicationCacheKey, BackgroundJob, ChangeCollectionToEntityInput, DefaultCollection, + IdAndNamedObject, MediaStateChanged, MetadataGroupSearchInput, MetadataSearchInput, + PeopleSearchInput, ProgressUpdateCacheInput, SearchDetails, SearchInput, StoredUrl, + StringIdObject, UserLevelCacheKey, }; use common_utils::{ - convert_naive_to_utc, get_first_and_last_day_of_month, ryot_log, IsFeatureEnabled, - COMPILATION_TIMESTAMP, EXERCISE_LOT_MAPPINGS, MEDIA_LOT_MAPPINGS, PAGE_SIZE, - SHOW_SPECIAL_SEASON_NAMES, + get_first_and_last_day_of_month, ryot_log, PAGE_SIZE, SHOW_SPECIAL_SEASON_NAMES, }; use database_models::{ access_link, application_cache, calendar_event, collection, collection_to_entity, @@ -29,8 +27,8 @@ use database_models::{ metadata_to_metadata_group, metadata_to_person, monitored_entity, notification_platform, person, prelude::{ - AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Exercise, - Genre, ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata, + AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Genre, + ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata, MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity, NotificationPlatform, Person, Review, Seen, User, UserNotification, UserToEntity, }, @@ -42,11 +40,9 @@ use database_utils::{ remove_entity_from_collection, revoke_access_link, user_by_id, }; use dependent_models::{ - ApplicationCacheValue, CoreDetails, EmptyCacheValue, ExerciseFilters, ExerciseParameters, - ExerciseParametersLotMapping, GenreDetails, MetadataBaseData, MetadataGroupDetails, - MetadataGroupSearchResponse, MetadataLotSourceMappings, MetadataSearchResponse, - PeopleSearchResponse, PersonDetails, ProviderLanguageInformation, SearchResults, - UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails, + ApplicationCacheValue, CoreDetails, GenreDetails, MetadataBaseData, MetadataGroupDetails, + MetadataGroupSearchResponse, MetadataSearchResponse, PeopleSearchResponse, PersonDetails, + SearchResults, UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails, }; use dependent_utils::{ add_entity_to_collection, commit_metadata, commit_metadata_group_internal, @@ -60,11 +56,9 @@ use dependent_utils::{ update_metadata_and_notify_users, }; use enums::{ - EntityLot, ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, - ExerciseMuscle, MediaLot, MediaSource, MetadataToMetadataRelation, SeenState, - UserNotificationLot, UserToMediaReason, + EntityLot, MediaLot, MediaSource, MetadataToMetadataRelation, SeenState, UserNotificationLot, + UserToMediaReason, }; -use env_utils::{APP_VERSION, UNKEY_API_ID}; use futures::TryStreamExt; use itertools::Itertools; use markdown::{to_html_with_options as markdown_to_html_opts, CompileOptions, Options}; @@ -91,54 +85,32 @@ use migrations::{ use nanoid::nanoid; use notification_service::send_notification; use providers::{ - anilist::{AnilistService, NonMediaAnilistService}, - audible::AudibleService, - google_books::GoogleBooksService, - igdb::IgdbService, - itunes::ITunesService, - listennotes::ListennotesService, - mal::{MalService, NonMediaMalService}, - manga_updates::MangaUpdatesService, - openlibrary::OpenlibraryService, - tmdb::TmdbService, - vndb::VndbService, + anilist::NonMediaAnilistService, audible::AudibleService, google_books::GoogleBooksService, + igdb::IgdbService, itunes::ITunesService, listennotes::ListennotesService, + mal::NonMediaMalService, manga_updates::MangaUpdatesService, vndb::VndbService, + youtube_music::YoutubeMusicService, }; use rust_decimal::Decimal; use rust_decimal_macros::dec; use sea_orm::{ prelude::DateTimeUtc, query::UpdateMany, sea_query::NullOrdering, ActiveModelTrait, ActiveValue, ColumnTrait, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait, - FromQueryResult, ItemsAndPagesNumber, Iterable, JoinType, ModelTrait, Order, PaginatorTrait, - QueryFilter, QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait, + FromQueryResult, ItemsAndPagesNumber, JoinType, ModelTrait, Order, PaginatorTrait, QueryFilter, + QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait, }; use sea_query::{ extension::postgres::PgExpr, Alias, Asterisk, Cond, Condition, Expr, Func, PgFunc, PostgresQueryBuilder, Query, SelectStatement, }; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; use supporting_service::SupportingService; use tokio::time::{sleep, Duration as TokioDuration}; -use traits::{MediaProvider, MediaProviderLanguages, TraceOk}; -use unkey::{models::VerifyKeyRequest, Client}; +use traits::{MediaProvider, TraceOk}; use user_models::{DashboardElementLot, UserReviewScale}; use uuid::Uuid; type Provider = Box<(dyn MediaProvider + Send + Sync)>; -#[derive(Debug, Clone)] -struct CustomService {} - -impl MediaProviderLanguages for CustomService { - fn supported_languages() -> Vec<String> { - ["us"].into_iter().map(String::from).collect() - } - - fn default_language() -> String { - "us".to_owned() - } -} - pub struct MiscellaneousService(pub Arc<SupportingService>); impl MiscellaneousService { @@ -252,128 +224,7 @@ ORDER BY RANDOM() LIMIT 10; } pub async fn core_details(&self) -> Result<CoreDetails> { - if let Some(cached) = self - .0 - .cache_service - .get_value(ApplicationCacheKey::CoreDetails) - .await? - { - return Ok(cached); - } - let mut files_enabled = self.0.config.file_storage.is_enabled(); - if files_enabled && !self.0.file_storage_service.is_enabled().await { - files_enabled = false; - } - let download_required = Exercise::find().count(&self.0.db).await? == 0; - let core_details = CoreDetails { - page_size: PAGE_SIZE, - version: APP_VERSION.to_owned(), - file_storage_enabled: files_enabled, - frontend: self.0.config.frontend.clone(), - website_url: "https://ryot.io".to_owned(), - oidc_enabled: self.0.oidc_client.is_some(), - docs_link: "https://docs.ryot.io".to_owned(), - backend_errors: BackendError::iter().collect(), - disable_telemetry: self.0.config.disable_telemetry, - smtp_enabled: self.0.config.server.smtp.is_enabled(), - signup_allowed: self.0.config.users.allow_registration, - local_auth_disabled: self.0.config.users.disable_local_auth, - repository_link: "https://github.com/ignisda/ryot".to_owned(), - token_valid_for_days: self.0.config.users.token_valid_for_days, - is_server_key_validated: self.0.is_server_key_validated().await?, - metadata_lot_source_mappings: MEDIA_LOT_MAPPINGS - .iter() - .map(|(lot, sources)| MetadataLotSourceMappings { - lot: *lot, - sources: sources.to_vec(), - }) - .collect(), - exercise_parameters: ExerciseParameters { - filters: ExerciseFilters { - lot: ExerciseLot::iter().collect_vec(), - level: ExerciseLevel::iter().collect_vec(), - force: ExerciseForce::iter().collect_vec(), - mechanic: ExerciseMechanic::iter().collect_vec(), - equipment: ExerciseEquipment::iter().collect_vec(), - muscle: ExerciseMuscle::iter().collect_vec(), - }, - download_required, - lot_mapping: EXERCISE_LOT_MAPPINGS - .iter() - .map(|(lot, pbs)| ExerciseParametersLotMapping { - lot: *lot, - bests: pbs.to_vec(), - }) - .collect(), - }, - metadata_provider_languages: MediaSource::iter() - .map(|source| { - let (supported, default) = match source { - MediaSource::Itunes => ( - ITunesService::supported_languages(), - ITunesService::default_language(), - ), - MediaSource::Audible => ( - AudibleService::supported_languages(), - AudibleService::default_language(), - ), - MediaSource::Openlibrary => ( - OpenlibraryService::supported_languages(), - OpenlibraryService::default_language(), - ), - MediaSource::Tmdb => ( - TmdbService::supported_languages(), - TmdbService::default_language(), - ), - MediaSource::Listennotes => ( - ListennotesService::supported_languages(), - ListennotesService::default_language(), - ), - MediaSource::GoogleBooks => ( - GoogleBooksService::supported_languages(), - GoogleBooksService::default_language(), - ), - MediaSource::Igdb => ( - IgdbService::supported_languages(), - IgdbService::default_language(), - ), - MediaSource::MangaUpdates => ( - MangaUpdatesService::supported_languages(), - MangaUpdatesService::default_language(), - ), - MediaSource::Anilist => ( - AnilistService::supported_languages(), - AnilistService::default_language(), - ), - MediaSource::Mal => ( - MalService::supported_languages(), - MalService::default_language(), - ), - MediaSource::Custom => ( - CustomService::supported_languages(), - CustomService::default_language(), - ), - MediaSource::Vndb => ( - VndbService::supported_languages(), - VndbService::default_language(), - ), - }; - ProviderLanguageInformation { - source, - default, - supported, - } - }) - .collect(), - }; - self.0 - .cache_service - .set_key( - ApplicationCacheKey::CoreDetails, - ApplicationCacheValue::CoreDetails(core_details.clone()), - ) - .await?; - Ok(core_details) + self.0.core_details().await } async fn metadata_assets(&self, meta: &metadata::Model) -> Result<GraphqlMediaAssets> { @@ -546,55 +397,6 @@ ORDER BY RANDOM() LIMIT 10; genres, suggestions, } = self.generic_metadata(metadata_id).await?; - let slug = slug::slugify(&model.title); - let identifier = &model.identifier; - let source_url = match model.source { - MediaSource::Custom => None, - // DEV: This is updated by the specifics - MediaSource::MangaUpdates => None, - MediaSource::Itunes => Some(format!( - "https://podcasts.apple.com/us/podcast/{slug}/id{identifier}" - )), - MediaSource::GoogleBooks => Some(format!( - "https://www.google.co.in/books/edition/{slug}/{identifier}" - )), - MediaSource::Audible => Some(format!("https://www.audible.com/pd/{slug}/{identifier}")), - MediaSource::Openlibrary => { - Some(format!("https://openlibrary.org/works/{identifier}/{slug}")) - } - MediaSource::Tmdb => { - let bw = match model.lot { - MediaLot::Movie => "movie", - MediaLot::Show => "tv", - _ => unreachable!(), - }; - Some(format!( - "https://www.themoviedb.org/{bw}/{identifier}-{slug}" - )) - } - MediaSource::Listennotes => Some(format!( - "https://www.listennotes.com/podcasts/{slug}-{identifier}" - )), - MediaSource::Igdb => Some(format!("https://www.igdb.com/games/{slug}")), - MediaSource::Anilist => { - let bw = match model.lot { - MediaLot::Anime => "anime", - MediaLot::Manga => "manga", - _ => unreachable!(), - }; - Some(format!("https://anilist.co/{bw}/{identifier}/{slug}")) - } - MediaSource::Mal => { - let bw = match model.lot { - MediaLot::Anime => "anime", - MediaLot::Manga => "manga", - _ => unreachable!(), - }; - Some(format!("https://myanimelist.net/{bw}/{identifier}/{slug}")) - } - MediaSource::Vndb => Some(format!("https://vndb.org/{identifier}")), - }; - let group = { let association = MetadataToMetadataGroup::find() .filter(metadata_to_metadata_group::Column::MetadataId.eq(metadata_id)) @@ -623,7 +425,6 @@ ORDER BY RANDOM() LIMIT 10; assets, genres, creators, - source_url, suggestions, id: model.id, lot: model.lot, @@ -631,6 +432,7 @@ ORDER BY RANDOM() LIMIT 10; title: model.title, source: model.source, is_nsfw: model.is_nsfw, + source_url: model.source_url, is_partial: model.is_partial, identifier: model.identifier, description: model.description, @@ -639,6 +441,7 @@ ORDER BY RANDOM() LIMIT 10; book_specifics: model.book_specifics, show_specifics: model.show_specifics, movie_specifics: model.movie_specifics, + music_specifics: model.music_specifics, manga_specifics: model.manga_specifics, anime_specifics: model.anime_specifics, provider_rating: model.provider_rating, @@ -1677,11 +1480,12 @@ ORDER BY RANDOM() LIMIT 10; user_id: &String, input: MetadataSearchInput, ) -> Result<MetadataSearchResponse> { + let cc = &self.0.cache_service; let cache_key = ApplicationCacheKey::MetadataSearch(UserLevelCacheKey { input: input.clone(), user_id: user_id.to_owned(), }); - if let Some(cached) = self.0.cache_service.get_value(cache_key.clone()).await? { + if let Some(cached) = cc.get_value(cache_key.clone()).await { return Ok(cached); } let query = input.search.query.unwrap_or_default(); @@ -1752,13 +1556,11 @@ ORDER BY RANDOM() LIMIT 10; details: results.details, items: data, }; - self.0 - .cache_service - .set_key( - cache_key, - ApplicationCacheValue::MetadataSearch(results.clone()), - ) - .await?; + cc.set_key( + cache_key, + ApplicationCacheValue::MetadataSearch(results.clone()), + ) + .await?; Ok(results) } @@ -1767,11 +1569,12 @@ ORDER BY RANDOM() LIMIT 10; user_id: &String, input: PeopleSearchInput, ) -> Result<PeopleSearchResponse> { + let cc = &self.0.cache_service; let cache_key = ApplicationCacheKey::PeopleSearch(UserLevelCacheKey { input: input.clone(), user_id: user_id.clone(), }); - if let Some(results) = self.0.cache_service.get_value(cache_key.clone()).await? { + if let Some(results) = cc.get_value(cache_key.clone()).await { return Ok(results); } let query = input.search.query.unwrap_or_default(); @@ -1794,13 +1597,11 @@ ORDER BY RANDOM() LIMIT 10; preferences.general.display_nsfw, ) .await?; - self.0 - .cache_service - .set_key( - cache_key, - ApplicationCacheValue::PeopleSearch(results.clone()), - ) - .await?; + cc.set_key( + cache_key, + ApplicationCacheValue::PeopleSearch(results.clone()), + ) + .await?; Ok(results) } @@ -1809,11 +1610,12 @@ ORDER BY RANDOM() LIMIT 10; user_id: &String, input: MetadataGroupSearchInput, ) -> Result<MetadataGroupSearchResponse> { + let cc = &self.0.cache_service; let cache_key = ApplicationCacheKey::MetadataGroupSearch(UserLevelCacheKey { input: input.clone(), user_id: user_id.clone(), }); - if let Some(results) = self.0.cache_service.get_value(cache_key.clone()).await? { + if let Some(results) = cc.get_value(cache_key.clone()).await { return Ok(results); } let query = input.search.query.unwrap_or_default(); @@ -1831,19 +1633,18 @@ ORDER BY RANDOM() LIMIT 10; let results = provider .metadata_group_search(&query, input.search.page, preferences.general.display_nsfw) .await?; - self.0 - .cache_service - .set_key( - cache_key, - ApplicationCacheValue::MetadataGroupSearch(results.clone()), - ) - .await?; + cc.set_key( + cache_key, + ApplicationCacheValue::MetadataGroupSearch(results.clone()), + ) + .await?; Ok(results) } async fn get_non_metadata_provider(&self, source: MediaSource) -> Result<Provider> { let err = || Err(Error::new("This source is not supported".to_owned())); let service: Provider = match source { + MediaSource::YoutubeMusic => Box::new(YoutubeMusicService::new().await), MediaSource::Vndb => Box::new(VndbService::new(&self.0.config.visual_novels).await), MediaSource::Openlibrary => Box::new(get_openlibrary_service(&self.0.config).await?), MediaSource::Itunes => { @@ -2015,34 +1816,36 @@ ORDER BY RANDOM() LIMIT 10; }) .collect(); let is_partial = match input.lot { - MediaLot::Anime => input.anime_specifics.is_none(), - MediaLot::AudioBook => input.audio_book_specifics.is_none(), + MediaLot::Show => input.show_specifics.is_none(), MediaLot::Book => input.book_specifics.is_none(), + MediaLot::Music => input.music_specifics.is_none(), + MediaLot::Anime => input.anime_specifics.is_none(), MediaLot::Manga => input.manga_specifics.is_none(), MediaLot::Movie => input.movie_specifics.is_none(), MediaLot::Podcast => input.podcast_specifics.is_none(), - MediaLot::Show => input.show_specifics.is_none(), + MediaLot::AudioBook => input.audio_book_specifics.is_none(), MediaLot::VideoGame => input.video_game_specifics.is_none(), MediaLot::VisualNovel => input.visual_novel_specifics.is_none(), }; let details = MetadataDetails { + videos, + creators, identifier, - title: input.title, - description: input.description, lot: input.lot, - source: MediaSource::Custom, - creators, - genres: input.genres.unwrap_or_default(), + title: input.title, s3_images: images, - videos, + source: MediaSource::Custom, + description: input.description, publish_year: input.publish_year, - anime_specifics: input.anime_specifics, - audio_book_specifics: input.audio_book_specifics, + show_specifics: input.show_specifics, book_specifics: input.book_specifics, manga_specifics: input.manga_specifics, + anime_specifics: input.anime_specifics, movie_specifics: input.movie_specifics, + music_specifics: input.music_specifics, + genres: input.genres.unwrap_or_default(), podcast_specifics: input.podcast_specifics, - show_specifics: input.show_specifics, + audio_book_specifics: input.audio_book_specifics, video_game_specifics: input.video_game_specifics, visual_novel_specifics: input.visual_novel_specifics, ..Default::default() @@ -2350,33 +2153,7 @@ ORDER BY RANDOM() LIMIT 10; }) .sorted_by_key(|f| Reverse(f.count)) .collect_vec(); - let slug = slug::slugify(&details.name); - let identifier = &details.identifier; - let source_url = match details.source { - MediaSource::Custom - | MediaSource::Anilist - | MediaSource::Listennotes - | MediaSource::Itunes - | MediaSource::MangaUpdates - | MediaSource::Mal - | MediaSource::Vndb - | MediaSource::GoogleBooks => None, - MediaSource::Audible => Some(format!( - "https://www.audible.com/author/{slug}/{identifier}" - )), - MediaSource::Openlibrary => Some(format!( - "https://openlibrary.org/authors/{identifier}/{slug}" - )), - MediaSource::Tmdb => Some(format!( - "https://www.themoviedb.org/person/{identifier}-{slug}" - )), - MediaSource::Igdb => Some(format!("https://www.igdb.com/companies/{slug}")), - }; - Ok(PersonDetails { - details, - contents, - source_url, - }) + Ok(PersonDetails { details, contents }) } pub async fn genre_details(&self, input: GenreDetailsInput) -> Result<GenreDetails> { @@ -2425,7 +2202,7 @@ ORDER BY RANDOM() LIMIT 10; .await? .unwrap(); let mut images = vec![]; - for image in group.images.iter() { + for image in group.images.clone().unwrap_or_default().iter() { images.push( self.0 .file_storage_service @@ -2434,28 +2211,6 @@ ORDER BY RANDOM() LIMIT 10; ); } group.display_images = images; - let slug = slug::slugify(&group.title); - let identifier = &group.identifier; - - let source_url = match group.source { - MediaSource::Custom - | MediaSource::Anilist - | MediaSource::Listennotes - | MediaSource::Itunes - | MediaSource::MangaUpdates - | MediaSource::Mal - | MediaSource::Openlibrary - | MediaSource::Vndb - | MediaSource::GoogleBooks => None, - MediaSource::Audible => Some(format!( - "https://www.audible.com/series/{slug}/{identifier}" - )), - MediaSource::Tmdb => Some(format!( - "https://www.themoviedb.org/collections/{identifier}-{slug}" - )), - MediaSource::Igdb => Some(format!("https://www.igdb.com/collection/{slug}")), - }; - let contents = MetadataToMetadataGroup::find() .select_only() .column(metadata_to_metadata_group::Column::MetadataId) @@ -2466,7 +2221,6 @@ ORDER BY RANDOM() LIMIT 10; .await?; Ok(MetadataGroupDetails { details: group, - source_url, contents, }) } @@ -2766,14 +2520,17 @@ ORDER BY RANDOM() LIMIT 10; .person_details(&person.identifier, &person.source_specifics) .await?; ryot_log!(debug, "Updating person for {:?}", person_id); - let images = provider_person.images.map(|images| { - images - .into_iter() - .map(|i| MetadataImage { - url: StoredUrl::Url(i), - }) - .collect() - }); + let images = provider_person + .images + .map(|images| { + images + .into_iter() + .map(|i| MetadataImage { + url: StoredUrl::Url(i), + }) + .collect() + }) + .filter(|i: &Vec<MetadataImage>| !i.is_empty()); let mut default_state_changes = person.clone().state_changes.unwrap_or_default(); let mut to_update_person: person::ActiveModel = person.clone().into(); to_update_person.last_updated_on = ActiveValue::Set(Utc::now()); @@ -2783,6 +2540,7 @@ ORDER BY RANDOM() LIMIT 10; to_update_person.death_date = ActiveValue::Set(provider_person.death_date); to_update_person.place = ActiveValue::Set(provider_person.place); to_update_person.website = ActiveValue::Set(provider_person.website); + to_update_person.source_url = ActiveValue::Set(provider_person.source_url); to_update_person.images = ActiveValue::Set(images); to_update_person.is_partial = ActiveValue::Set(Some(false)); to_update_person.name = ActiveValue::Set(provider_person.name); @@ -3158,65 +2916,6 @@ ORDER BY RANDOM() LIMIT 10; Ok(()) } - async fn get_is_server_key_validated(&self) -> bool { - let pro_key = &self.0.config.server.pro_key; - if pro_key.is_empty() { - return false; - } - ryot_log!(debug, "Verifying pro key for API ID: {:#?}", UNKEY_API_ID); - let compile_timestamp = Utc.timestamp_opt(COMPILATION_TIMESTAMP, 0).unwrap(); - #[skip_serializing_none] - #[derive(Debug, Serialize, Clone, Deserialize)] - struct Meta { - expiry: Option<NaiveDate>, - } - let unkey_client = Client::new("public"); - let verify_request = VerifyKeyRequest::new(pro_key, &UNKEY_API_ID.to_string()); - let validated_key = match unkey_client.verify_key(verify_request).await { - Ok(verify_response) => { - if !verify_response.valid { - ryot_log!(debug, "Pro key is no longer valid."); - return false; - } - verify_response - } - Err(verify_error) => { - ryot_log!(debug, "Pro key verification error: {:?}", verify_error); - return false; - } - }; - let key_meta = validated_key - .meta - .map(|meta| serde_json::from_value::<Meta>(meta).unwrap()); - ryot_log!(debug, "Expiry: {:?}", key_meta.clone().map(|m| m.expiry)); - if let Some(meta) = key_meta { - if let Some(expiry) = meta.expiry { - if compile_timestamp > convert_naive_to_utc(expiry) { - ryot_log!(warn, "Pro key has expired. Please renew your subscription."); - return false; - } - } - } - ryot_log!(debug, "Pro key verified successfully"); - true - } - - pub async fn perform_server_key_validation(&self) -> Result<()> { - let is_server_key_validated = self.get_is_server_key_validated().await; - let cs = &self.0.cache_service; - if is_server_key_validated { - cs.set_key( - ApplicationCacheKey::ServerKeyValidated, - ApplicationCacheValue::Empty(EmptyCacheValue::default()), - ) - .await?; - } else { - cs.expire_key(ApplicationCacheKey::ServerKeyValidated) - .await?; - } - Ok(()) - } - pub async fn sync_integrations_data_to_owned_collection(&self) -> Result<()> { self.0 .perform_application_job(ApplicationJob::SyncIntegrationsData) diff --git a/crates/services/statistics/src/lib.rs b/crates/services/statistics/src/lib.rs index 007588debf..d1a941ad66 100644 --- a/crates/services/statistics/src/lib.rs +++ b/crates/services/statistics/src/lib.rs @@ -38,11 +38,12 @@ impl StatisticsService { &self, user_id: &String, ) -> Result<ApplicationDateRange> { + let cc = &self.0.cache_service; let cache_key = ApplicationCacheKey::UserAnalyticsParameters(UserLevelCacheKey { input: (), user_id: user_id.to_owned(), }); - if let Some(cached) = self.0.cache_service.get_value(cache_key.clone()).await? { + if let Some(cached) = cc.get_value(cache_key.clone()).await { return Ok(cached); } let get_date = |ordering: Order| { @@ -64,13 +65,11 @@ impl StatisticsService { end_date, start_date, }; - self.0 - .cache_service - .set_key( - cache_key, - ApplicationCacheValue::UserAnalyticsParameters(response.clone()), - ) - .await?; + cc.set_key( + cache_key, + ApplicationCacheValue::UserAnalyticsParameters(response.clone()), + ) + .await?; Ok(response) } @@ -274,12 +273,8 @@ impl StatisticsService { input: input.clone(), user_id: user_id.to_owned(), }); - if let Some(cached) = self - .0 - .cache_service - .get_value::<UserAnalytics>(cache_key.clone()) - .await? - { + let cc = &self.0.cache_service; + if let Some(cached) = cc.get_value::<UserAnalytics>(cache_key.clone()).await { return Ok(cached); } #[derive(Debug, DerivePartialModel, FromQueryResult)] @@ -379,13 +374,11 @@ impl StatisticsService { workout_personal_bests, }, }; - self.0 - .cache_service - .set_key( - cache_key, - ApplicationCacheValue::UserAnalytics(response.clone()), - ) - .await?; + cc.set_key( + cache_key, + ApplicationCacheValue::UserAnalytics(response.clone()), + ) + .await?; Ok(response) } } diff --git a/crates/services/supporting/Cargo.toml b/crates/services/supporting/Cargo.toml index b6b6fa185c..84a4e09e6f 100644 --- a/crates/services/supporting/Cargo.toml +++ b/crates/services/supporting/Cargo.toml @@ -8,10 +8,26 @@ async-graphql = { workspace = true } apalis = { workspace = true } background = { path = "../../background" } cache-service = { path = "../../services/cache" } -common-models = { path = "../../models/common" } +chrono = { workspace = true } chrono-tz = { workspace = true } +common-models = { path = "../../models/common" } +common-utils = { path = "../../utils/common" } config = { path = "../../config" } +database-models = { path = "../../models/database" } dependent-models = { path = "../../models/dependent" } +enums = { path = "../../enums" } +env-utils = { path = "../../utils/env" } file-storage-service = { path = "../../services/file-storage" } +isolang = { workspace = true } +itertools = { workspace = true } openidconnect = { workspace = true } +rustypipe = { workspace = true } sea-orm = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_with = { workspace = true } +tracing = { workspace = true } +unkey = { workspace = true } + +[package.metadata.cargo-machete] +ignored = ["tracing"] diff --git a/crates/services/supporting/src/lib.rs b/crates/services/supporting/src/lib.rs index 5d176e5038..8b9d01be85 100644 --- a/crates/services/supporting/src/lib.rs +++ b/crates/services/supporting/src/lib.rs @@ -4,11 +4,30 @@ use apalis::prelude::{MemoryStorage, MessageQueue}; use async_graphql::Result; use background::{ApplicationJob, CoreApplicationJob}; use cache_service::CacheService; -use common_models::ApplicationCacheKey; -use dependent_models::EmptyCacheValue; +use chrono::{NaiveDate, TimeZone, Utc}; +use common_models::{ApplicationCacheKey, BackendError}; +use common_utils::{ + convert_naive_to_utc, ryot_log, COMPILATION_TIMESTAMP, EXERCISE_LOT_MAPPINGS, + MEDIA_LOT_MAPPINGS, PAGE_SIZE, +}; +use database_models::prelude::Exercise; +use dependent_models::{ + ApplicationCacheValue, CoreDetails, ExerciseFilters, ExerciseParameters, + ExerciseParametersLotMapping, MetadataLotSourceMappings, ProviderLanguageInformation, +}; +use enums::{ + ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, ExerciseMuscle, + MediaSource, +}; +use env_utils::{APP_VERSION, UNKEY_API_ID}; use file_storage_service::FileStorageService; +use itertools::Itertools; use openidconnect::core::CoreClient; -use sea_orm::DatabaseConnection; +use rustypipe::param::{Language, LANGUAGES}; +use sea_orm::{DatabaseConnection, EntityTrait, Iterable, PaginatorTrait}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use unkey::{models::VerifyKeyRequest, Client}; pub struct SupportingService { pub db: DatabaseConnection, @@ -64,10 +83,175 @@ impl SupportingService { Ok(()) } + async fn get_is_server_key_validated(&self) -> bool { + let pro_key = &self.config.server.pro_key; + if pro_key.is_empty() { + return false; + } + ryot_log!(debug, "Verifying pro key for API ID: {:#?}", UNKEY_API_ID); + let compile_timestamp = Utc.timestamp_opt(COMPILATION_TIMESTAMP, 0).unwrap(); + #[skip_serializing_none] + #[derive(Debug, Serialize, Clone, Deserialize)] + struct Meta { + expiry: Option<NaiveDate>, + } + let unkey_client = Client::new("public"); + let verify_request = VerifyKeyRequest::new(pro_key, &UNKEY_API_ID.to_string()); + let validated_key = match unkey_client.verify_key(verify_request).await { + Ok(verify_response) => { + if !verify_response.valid { + ryot_log!(debug, "Pro key is no longer valid."); + return false; + } + verify_response + } + Err(verify_error) => { + ryot_log!(debug, "Pro key verification error: {:?}", verify_error); + return false; + } + }; + let key_meta = validated_key + .meta + .map(|meta| serde_json::from_value::<Meta>(meta).unwrap()); + ryot_log!(debug, "Expiry: {:?}", key_meta.clone().map(|m| m.expiry)); + if let Some(meta) = key_meta { + if let Some(expiry) = meta.expiry { + if compile_timestamp > convert_naive_to_utc(expiry) { + ryot_log!(warn, "Pro key has expired. Please renew your subscription."); + return false; + } + } + } + ryot_log!(debug, "Pro key verified successfully"); + true + } + + pub async fn core_details(&self) -> Result<CoreDetails> { + let cc = &self.cache_service; + if let Some(cached) = cc.get_value(ApplicationCacheKey::CoreDetails).await { + return Ok(cached); + } + let mut files_enabled = self.config.file_storage.is_enabled(); + if files_enabled && !self.file_storage_service.is_enabled().await { + files_enabled = false; + } + let download_required = Exercise::find().count(&self.db).await? == 0; + let core_details = CoreDetails { + page_size: PAGE_SIZE, + version: APP_VERSION.to_owned(), + file_storage_enabled: files_enabled, + frontend: self.config.frontend.clone(), + website_url: "https://ryot.io".to_owned(), + oidc_enabled: self.oidc_client.is_some(), + docs_link: "https://docs.ryot.io".to_owned(), + backend_errors: BackendError::iter().collect(), + disable_telemetry: self.config.disable_telemetry, + smtp_enabled: self.config.server.smtp.is_enabled(), + signup_allowed: self.config.users.allow_registration, + local_auth_disabled: self.config.users.disable_local_auth, + token_valid_for_days: self.config.users.token_valid_for_days, + repository_link: "https://github.com/ignisda/ryot".to_owned(), + is_server_key_validated: self.get_is_server_key_validated().await, + metadata_lot_source_mappings: MEDIA_LOT_MAPPINGS + .iter() + .map(|(lot, sources)| MetadataLotSourceMappings { + lot: *lot, + sources: sources.to_vec(), + }) + .collect(), + exercise_parameters: ExerciseParameters { + download_required, + filters: ExerciseFilters { + lot: ExerciseLot::iter().collect_vec(), + level: ExerciseLevel::iter().collect_vec(), + force: ExerciseForce::iter().collect_vec(), + mechanic: ExerciseMechanic::iter().collect_vec(), + equipment: ExerciseEquipment::iter().collect_vec(), + muscle: ExerciseMuscle::iter().collect_vec(), + }, + lot_mapping: EXERCISE_LOT_MAPPINGS + .iter() + .map(|(lot, pbs)| ExerciseParametersLotMapping { + lot: *lot, + bests: pbs.to_vec(), + }) + .collect(), + }, + metadata_provider_languages: MediaSource::iter() + .map(|source| { + let (supported, default) = match source { + MediaSource::YoutubeMusic => ( + LANGUAGES.iter().map(|l| l.name().to_owned()).collect(), + Language::En.name().to_owned(), + ), + MediaSource::Itunes => ( + ["en_us", "ja_jp"].into_iter().map(String::from).collect(), + "en_us".to_owned(), + ), + MediaSource::Audible => ( + ["au", "ca", "de", "es", "fr", "in", "it", "jp", "gb", "us"] + .into_iter() + .map(String::from) + .collect(), + "us".to_owned(), + ), + MediaSource::Openlibrary => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::Tmdb => ( + isolang::languages() + .filter_map(|l| l.to_639_1().map(String::from)) + .collect(), + "en".to_owned(), + ), + MediaSource::Listennotes => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::GoogleBooks => (vec!["us".to_owned()], "us".to_owned()), + MediaSource::Igdb => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::MangaUpdates => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::Anilist => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::Mal => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::Custom => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + MediaSource::Vndb => ( + ["us"].into_iter().map(String::from).collect(), + "us".to_owned(), + ), + }; + ProviderLanguageInformation { + source, + default, + supported, + } + }) + .collect(), + }; + cc.set_key( + ApplicationCacheKey::CoreDetails, + ApplicationCacheValue::CoreDetails(core_details.clone()), + ) + .await?; + Ok(core_details) + } + pub async fn is_server_key_validated(&self) -> Result<bool> { - self.cache_service - .get_value::<EmptyCacheValue>(ApplicationCacheKey::ServerKeyValidated) - .await - .map(|v| v.is_some()) + Ok(self.core_details().await?.is_server_key_validated) } } diff --git a/crates/services/user/src/lib.rs b/crates/services/user/src/lib.rs index 6ce81ff644..47e15e6cec 100644 --- a/crates/services/user/src/lib.rs +++ b/crates/services/user/src/lib.rs @@ -660,6 +660,10 @@ impl UserService { preferences.features_enabled.media.movie = value_bool.unwrap() } + "music" => { + preferences.features_enabled.media.music = + value_bool.unwrap() + } "podcast" => { preferences.features_enabled.media.podcast = value_bool.unwrap() diff --git a/crates/traits/src/lib.rs b/crates/traits/src/lib.rs index 7862e1dd15..2c3b984e51 100644 --- a/crates/traits/src/lib.rs +++ b/crates/traits/src/lib.rs @@ -8,11 +8,8 @@ use common_models::{BackendError, PersonSourceSpecifics}; use common_utils::ryot_log; use database_models::metadata_group::MetadataGroupWithoutId; use database_utils::check_token; -use dependent_models::SearchResults; -use media_models::{ - MetadataDetails, MetadataGroupSearchItem, MetadataPerson, MetadataSearchItem, - PartialMetadataWithoutId, PeopleSearchItem, -}; +use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults}; +use media_models::{MetadataDetails, MetadataPerson, MetadataSearchItem, PartialMetadataWithoutId}; use sea_orm::{prelude::DateTimeUtc, DatabaseConnection}; #[async_trait] @@ -48,7 +45,7 @@ pub trait MediaProvider { page: Option<i32>, source_specifics: &Option<PersonSourceSpecifics>, display_nsfw: bool, - ) -> Result<SearchResults<PeopleSearchItem>> { + ) -> Result<PeopleSearchResponse> { bail!("This provider does not support searching people") } @@ -56,7 +53,7 @@ pub trait MediaProvider { #[allow(unused_variables)] async fn person_details( &self, - identity: &str, + identifier: &str, source_specifics: &Option<PersonSourceSpecifics>, ) -> Result<MetadataPerson> { bail!("This provider does not support getting person details") @@ -69,7 +66,7 @@ pub trait MediaProvider { query: &str, page: Option<i32>, display_nsfw: bool, - ) -> Result<SearchResults<MetadataGroupSearchItem>> { + ) -> Result<MetadataGroupSearchResponse> { bail!("This provider does not support searching metadata groups") } @@ -92,14 +89,6 @@ pub trait MediaProvider { } } -pub trait MediaProviderLanguages { - /// Get all the languages that a provider supports. - fn supported_languages() -> Vec<String>; - - /// The default language to be used for this provider. - fn default_language() -> String; -} - #[async_trait] pub trait AuthProvider { #[allow(dead_code)] diff --git a/crates/utils/common/src/lib.rs b/crates/utils/common/src/lib.rs index 95f7d0dd52..17ac5c46c3 100644 --- a/crates/utils/common/src/lib.rs +++ b/crates/utils/common/src/lib.rs @@ -65,6 +65,7 @@ pub const MEDIA_LOT_MAPPINGS: &[(MediaLot, &[MediaSource])] = &[ ], ), (MediaLot::Movie, &[MediaSource::Tmdb]), + (MediaLot::Music, &[MediaSource::YoutubeMusic]), (MediaLot::Show, &[MediaSource::Tmdb]), (MediaLot::VisualNovel, &[MediaSource::Vndb]), ]; @@ -88,13 +89,6 @@ macro_rules! ryot_log { }; } -/// Determine whether a feature is enabled -pub trait IsFeatureEnabled { - fn is_enabled(&self) -> bool { - true - } -} - pub fn get_first_and_last_day_of_month(year: i32, month: u32) -> (NaiveDate, NaiveDate) { let first_day = NaiveDate::from_ymd_opt(year, month, 1).unwrap(); let last_day = NaiveDate::from_ymd_opt(year, month + 1, 1) diff --git a/crates/utils/database/src/lib.rs b/crates/utils/database/src/lib.rs index 34be1e3f4e..fea2ed60b1 100644 --- a/crates/utils/database/src/lib.rs +++ b/crates/utils/database/src/lib.rs @@ -10,7 +10,7 @@ use common_models::{ BackendError, ChangeCollectionToEntityInput, DailyUserActivityHourRecord, DailyUserActivityHourRecordEntity, DefaultCollection, IdAndNamedObject, StringIdObject, }; -use common_utils::{ryot_log, IsFeatureEnabled}; +use common_utils::ryot_log; use database_models::{ access_link, collection, collection_to_entity, daily_user_activity, functions::associate_user_with_entity, @@ -30,9 +30,9 @@ use jwt_service::{verify, Claims}; use markdown::to_html as markdown_to_html; use media_models::{ AnimeSpecifics, AudioBookSpecifics, BookSpecifics, CreateOrUpdateCollectionInput, - MangaSpecifics, MovieSpecifics, PodcastSpecifics, ReviewItem, SeenAnimeExtraInformation, - SeenMangaExtraInformation, SeenPodcastExtraInformation, SeenShowExtraInformation, - ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, + MangaSpecifics, MovieSpecifics, MusicSpecifics, PodcastSpecifics, ReviewItem, + SeenAnimeExtraInformation, SeenMangaExtraInformation, SeenPodcastExtraInformation, + SeenShowExtraInformation, ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, }; use migrations::AliasedCollectionToEntity; use rust_decimal::{prelude::ToPrimitive, Decimal}; @@ -65,26 +65,10 @@ pub fn ilike_sql(value: &str) -> String { } pub async fn user_by_id(user_id: &String, ss: &Arc<SupportingService>) -> Result<user::Model> { - let mut user = User::find_by_id(user_id) + let user = User::find_by_id(user_id) .one(&ss.db) .await? .ok_or_else(|| Error::new("No user found"))?; - let config = &ss.config; - let features_enabled = &mut user.preferences.features_enabled; - features_enabled.media.anime = - config.anime_and_manga.is_enabled() && features_enabled.media.anime; - features_enabled.media.audio_book = - config.audio_books.is_enabled() && features_enabled.media.audio_book; - features_enabled.media.book = config.books.is_enabled() && features_enabled.media.book; - features_enabled.media.show = - config.movies_and_shows.is_enabled() && features_enabled.media.show; - features_enabled.media.manga = - config.anime_and_manga.is_enabled() && features_enabled.media.manga; - features_enabled.media.movie = - config.movies_and_shows.is_enabled() && features_enabled.media.movie; - features_enabled.media.podcast = config.podcasts.is_enabled() && features_enabled.media.podcast; - features_enabled.media.video_game = - config.video_games.is_enabled() && features_enabled.media.video_game; Ok(user) } @@ -507,6 +491,7 @@ pub async fn calculate_user_activities_and_summary( audio_book_specifics: Option<AudioBookSpecifics>, book_specifics: Option<BookSpecifics>, movie_specifics: Option<MovieSpecifics>, + music_specifics: Option<MusicSpecifics>, podcast_specifics: Option<PodcastSpecifics>, show_specifics: Option<ShowSpecifics>, video_game_specifics: Option<VideoGameSpecifics>, @@ -601,6 +586,7 @@ pub async fn calculate_user_activities_and_summary( metadata::Column::AudioBookSpecifics, metadata::Column::BookSpecifics, metadata::Column::MovieSpecifics, + metadata::Column::MusicSpecifics, metadata::Column::PodcastSpecifics, metadata::Column::ShowSpecifics, metadata::Column::VideoGameSpecifics, @@ -648,6 +634,10 @@ pub async fn calculate_user_activities_and_summary( if let Some(runtime) = movie_extra.runtime { activity.movie_duration += runtime; } + } else if let Some(music_extra) = seen.music_specifics { + if let Some(runtime) = music_extra.duration { + activity.music_duration += runtime; + } } else if let Some(book_extra) = seen.book_specifics { if let Some(pages) = book_extra.pages { activity.book_pages += pages; @@ -665,6 +655,7 @@ pub async fn calculate_user_activities_and_summary( match seen.metadata_lot { MediaLot::Book => activity.book_count += 1, MediaLot::Show => activity.show_count += 1, + MediaLot::Music => activity.music_count += 1, MediaLot::Anime => activity.anime_count += 1, MediaLot::Movie => activity.movie_count += 1, MediaLot::Manga => activity.manga_count += 1, diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs index 02f89f8676..6dcc0dbc26 100644 --- a/crates/utils/dependent/src/lib.rs +++ b/crates/utils/dependent/src/lib.rs @@ -64,6 +64,7 @@ use providers::{ openlibrary::OpenlibraryService, tmdb::{NonMediaTmdbService, TmdbMovieService, TmdbShowService}, vndb::VndbService, + youtube_music::YoutubeMusicService, }; use rust_decimal::{ prelude::{FromPrimitive, One, ToPrimitive}, @@ -132,6 +133,7 @@ pub async fn get_metadata_provider( ) -> Result<Provider> { let err = || Err(Error::new("This source is not supported".to_owned())); let service: Provider = match source { + MediaSource::YoutubeMusic => Box::new(YoutubeMusicService::new().await), MediaSource::Vndb => Box::new(VndbService::new(&ss.config.visual_novels).await), MediaSource::Openlibrary => Box::new(get_openlibrary_service(&ss.config).await?), MediaSource::Itunes => Box::new(ITunesService::new(&ss.config.podcasts.itunes).await), @@ -415,7 +417,6 @@ pub async fn update_metadata( let notifications = match maybe_details { Ok(details) => { let mut notifications = vec![]; - let meta = Metadata::find_by_id(metadata_id) .one(&ss.db) .await @@ -594,9 +595,13 @@ pub async fn update_metadata( meta.title = ActiveValue::Set(details.title); meta.is_nsfw = ActiveValue::Set(details.is_nsfw); meta.is_partial = ActiveValue::Set(Some(false)); + meta.source_url = ActiveValue::Set(details.source_url); meta.provider_rating = ActiveValue::Set(details.provider_rating); meta.description = ActiveValue::Set(details.description); - meta.images = ActiveValue::Set(Some(images)); + meta.images = ActiveValue::Set(match images.is_empty() { + true => None, + false => Some(images), + }); meta.videos = ActiveValue::Set(Some(details.videos)); meta.production_status = ActiveValue::Set(details.production_status); meta.original_language = ActiveValue::Set(details.original_language); @@ -613,6 +618,7 @@ pub async fn update_metadata( meta.book_specifics = ActiveValue::Set(details.book_specifics); meta.video_game_specifics = ActiveValue::Set(details.video_game_specifics); meta.visual_novel_specifics = ActiveValue::Set(details.visual_novel_specifics); + meta.music_specifics = ActiveValue::Set(details.music_specifics); meta.external_identifiers = ActiveValue::Set(details.external_identifiers); let metadata = meta.update(&ss.db).await.unwrap(); @@ -771,11 +777,18 @@ pub async fn commit_metadata_internal( lot: ActiveValue::Set(details.lot), source: ActiveValue::Set(details.source), title: ActiveValue::Set(details.title), + source_url: ActiveValue::Set(details.source_url), description: ActiveValue::Set(details.description), publish_year: ActiveValue::Set(details.publish_year), publish_date: ActiveValue::Set(details.publish_date), - images: ActiveValue::Set(Some(images)), - videos: ActiveValue::Set(Some(details.videos)), + images: ActiveValue::Set(match images.is_empty() { + true => None, + false => Some(images), + }), + videos: ActiveValue::Set(match details.videos.is_empty() { + true => None, + false => Some(details.videos), + }), identifier: ActiveValue::Set(details.identifier), audio_book_specifics: ActiveValue::Set(details.audio_book_specifics), anime_specifics: ActiveValue::Set(details.anime_specifics), @@ -788,6 +801,7 @@ pub async fn commit_metadata_internal( visual_novel_specifics: ActiveValue::Set(details.visual_novel_specifics), provider_rating: ActiveValue::Set(details.provider_rating), production_status: ActiveValue::Set(details.production_status), + music_specifics: ActiveValue::Set(details.music_specifics), original_language: ActiveValue::Set(details.original_language), external_identifiers: ActiveValue::Set(details.external_identifiers), is_nsfw: ActiveValue::Set(details.is_nsfw), @@ -1055,8 +1069,17 @@ pub async fn commit_metadata_group_internal( let provider = get_metadata_provider(lot, source, ss).await?; let (group_details, associated_items) = provider.metadata_group_details(identifier).await?; let group_id = match existing_group { - Some(eg) => eg.id, + Some(eg) => { + let mut eg: metadata_group::ActiveModel = eg.into(); + eg.parts = ActiveValue::Set(group_details.parts); + eg.source_url = ActiveValue::Set(group_details.source_url); + eg.images = ActiveValue::Set(group_details.images.filter(|i| !i.is_empty())); + let eg = eg.update(&ss.db).await?; + eg.id + } None => { + let mut group_details = group_details.clone(); + group_details.images = group_details.images.filter(|i| !i.is_empty()); let mut db_group: metadata_group::ActiveModel = group_details.into_model("".to_string(), None).into(); db_group.id = ActiveValue::NotSet; @@ -1292,7 +1315,7 @@ pub async fn get_entity_recently_consumed( }, }, )) - .await? + .await .is_some()) } @@ -1319,7 +1342,7 @@ pub async fn progress_update( let in_cache = ss .cache_service .get_value::<EmptyCacheValue>(cache.clone()) - .await?; + .await; if in_cache.is_some() { ryot_log!(debug, "Seen is already in cache"); return Ok(ProgressUpdateResultUnion::Error(ProgressUpdateError { diff --git a/docs/includes/backend-config-schema.yaml b/docs/includes/backend-config-schema.yaml index e8b53224e2..5df653dcd7 100644 --- a/docs/includes/backend-config-schema.yaml +++ b/docs/includes/backend-config-schema.yaml @@ -129,6 +129,9 @@ movies_and_shows: # @envvar MOVIES_AND_SHOWS_TMDB_LOCALE locale: "en" +# Settings related to music. +music: {} + # Settings related to podcasts. podcasts: # Settings related to iTunes. diff --git a/docs/includes/export-schema.ts b/docs/includes/export-schema.ts index 7377e3f1f7..856a04df66 100644 --- a/docs/includes/export-schema.ts +++ b/docs/includes/export-schema.ts @@ -108,7 +108,7 @@ export interface UserMeasurement { } /** The different types of media that can be stored. */ -export type MediaLot = 'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'; +export type MediaLot = 'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel'; /** A specific instance when an entity was seen. */ export interface ImportOrExportMetadataItemSeen { @@ -135,7 +135,7 @@ export interface ImportOrExportMetadataItemSeen { } /** The different sources (or providers) from which data can be obtained from. */ -export type MediaSource = 'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'; +export type MediaSource = 'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'; /** Details about a specific media item that needs to be imported or exported. */ export interface ImportOrExportMetadataItem { @@ -147,7 +147,7 @@ export interface ImportOrExportMetadataItem { * The type of media. * * @default 'book' - * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'} + * @type {'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel'} */ lot: MediaLot; /** The review history for the user. */ @@ -157,8 +157,8 @@ export interface ImportOrExportMetadataItem { /** * The source of media. * - * @default 'audible' - * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'} + * @default 'custom' + * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'} */ source: MediaSource; /** An string to help identify it in the original source. */ @@ -175,7 +175,7 @@ export interface ImportOrExportMetadataGroupItem { * The type of media. * * @default 'book' - * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'} + * @type {'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel'} */ lot: MediaLot; /** The review history for the user. */ @@ -183,8 +183,8 @@ export interface ImportOrExportMetadataGroupItem { /** * The source of media. * - * @default 'audible' - * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'} + * @default 'custom' + * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'} */ source: MediaSource; /** Name of the group. */ @@ -209,8 +209,8 @@ export interface ImportOrExportPersonItem { /** * The source of data. * - * @default 'audible' - * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'} + * @default 'custom' + * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'} */ source: MediaSource; /** The source specific data. */ diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts index 8e21cb579b..4381a12bdf 100644 --- a/libs/generated/src/graphql/backend/gql.ts +++ b/libs/generated/src/graphql/backend/gql.ts @@ -15,14 +15,14 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ */ const documents = { "mutation RegisterUser($input: RegisterUserInput!) {\n registerUser(input: $input) {\n __typename\n ... on RegisterError {\n error\n }\n ... on StringIdObject {\n id\n }\n }\n}\n\nmutation LoginUser($input: AuthUserInput!) {\n loginUser(input: $input) {\n __typename\n ... on LoginError {\n error\n }\n ... on LoginResponse {\n apiKey\n }\n }\n}\n\nmutation AddEntityToCollection($input: ChangeCollectionToEntityInput!) {\n addEntityToCollection(input: $input)\n}\n\nmutation CommitMetadata($input: CommitMediaInput!) {\n commitMetadata(input: $input) {\n id\n }\n}\n\nmutation CommitMetadataGroup($input: CommitMediaInput!) {\n commitMetadataGroup(input: $input) {\n id\n }\n}\n\nmutation CommitPerson($input: CommitPersonInput!) {\n commitPerson(input: $input) {\n id\n }\n}\n\nmutation CreateCustomExercise($input: ExerciseInput!) {\n createCustomExercise(input: $input)\n}\n\nmutation UpdateCustomExercise($input: UpdateCustomExerciseInput!) {\n updateCustomExercise(input: $input)\n}\n\nmutation UpdateUserIntegration($input: UpdateUserIntegrationInput!) {\n updateUserIntegration(input: $input)\n}\n\nmutation CreateCustomMetadata($input: CreateCustomMetadataInput!) {\n createCustomMetadata(input: $input) {\n id\n }\n}\n\nmutation CreateOrUpdateCollection($input: CreateOrUpdateCollectionInput!) {\n createOrUpdateCollection(input: $input) {\n id\n }\n}\n\nmutation CreateReviewComment($input: CreateReviewCommentInput!) {\n createReviewComment(input: $input)\n}\n\nmutation CreateUserMeasurement($input: UserMeasurementInput!) {\n createUserMeasurement(input: $input)\n}\n\nmutation CreateUserNotificationPlatform($input: CreateUserNotificationPlatformInput!) {\n createUserNotificationPlatform(input: $input)\n}\n\nmutation CreateUserIntegration($input: CreateUserIntegrationInput!) {\n createUserIntegration(input: $input) {\n id\n }\n}\n\nmutation CreateOrUpdateUserWorkout($input: UserWorkoutInput!) {\n createOrUpdateUserWorkout(input: $input)\n}\n\nmutation CreateOrUpdateUserWorkoutTemplate($input: UserWorkoutInput!) {\n createOrUpdateUserWorkoutTemplate(input: $input)\n}\n\nmutation DeleteCollection($collectionName: String!) {\n deleteCollection(collectionName: $collectionName)\n}\n\nmutation DeleteReview($reviewId: String!) {\n deleteReview(reviewId: $reviewId)\n}\n\nmutation DeleteS3Object($key: String!) {\n deleteS3Object(key: $key)\n}\n\nmutation DeleteSeenItem($seenId: String!) {\n deleteSeenItem(seenId: $seenId) {\n id\n }\n}\n\nmutation DeleteUser($toDeleteUserId: String!) {\n deleteUser(toDeleteUserId: $toDeleteUserId)\n}\n\nmutation DeleteUserIntegration($integrationId: String!) {\n deleteUserIntegration(integrationId: $integrationId)\n}\n\nmutation DeleteUserMeasurement($timestamp: DateTime!) {\n deleteUserMeasurement(timestamp: $timestamp)\n}\n\nmutation DeleteUserNotificationPlatform($notificationId: String!) {\n deleteUserNotificationPlatform(notificationId: $notificationId)\n}\n\nmutation DeleteUserWorkout($workoutId: String!) {\n deleteUserWorkout(workoutId: $workoutId)\n}\n\nmutation DeleteUserWorkoutTemplate($workoutTemplateId: String!) {\n deleteUserWorkoutTemplate(workoutTemplateId: $workoutTemplateId)\n}\n\nmutation DeployBackgroundJob($jobName: BackgroundJob!) {\n deployBackgroundJob(jobName: $jobName)\n}\n\nmutation DeployBulkProgressUpdate($input: [ProgressUpdateInput!]!) {\n deployBulkProgressUpdate(input: $input)\n}\n\nmutation DeployExportJob {\n deployExportJob\n}\n\nmutation DeployImportJob($input: DeployImportJobInput!) {\n deployImportJob(input: $input)\n}\n\nmutation DeployUpdateMetadataJob($metadataId: String!) {\n deployUpdateMetadataJob(metadataId: $metadataId)\n}\n\nmutation DeployUpdatePersonJob($personId: String!) {\n deployUpdatePersonJob(personId: $personId)\n}\n\nmutation DeployUpdateMetadataGroupJob($metadataGroupId: String!) {\n deployUpdateMetadataGroupJob(metadataGroupId: $metadataGroupId)\n}\n\nmutation UpdateSeenItem($input: UpdateSeenItemInput!) {\n updateSeenItem(input: $input)\n}\n\nmutation UpdateUserNotificationPlatform($input: UpdateUserNotificationPlatformInput!) {\n updateUserNotificationPlatform(input: $input)\n}\n\nmutation UpdateUserWorkoutAttributes($input: UpdateUserWorkoutAttributesInput!) {\n updateUserWorkoutAttributes(input: $input)\n}\n\nmutation GenerateAuthToken {\n generateAuthToken\n}\n\nmutation MergeMetadata($mergeFrom: String!, $mergeInto: String!) {\n mergeMetadata(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation DisassociateMetadata($metadataId: String!) {\n disassociateMetadata(metadataId: $metadataId)\n}\n\nmutation CreateOrUpdateReview($input: CreateOrUpdateReviewInput!) {\n createOrUpdateReview(input: $input) {\n id\n }\n}\n\nmutation PresignedPutS3Url($input: PresignedPutUrlInput!) {\n presignedPutS3Url(input: $input) {\n key\n uploadUrl\n }\n}\n\nmutation RemoveEntityFromCollection($input: ChangeCollectionToEntityInput!) {\n removeEntityFromCollection(input: $input) {\n id\n }\n}\n\nmutation TestUserNotificationPlatforms {\n testUserNotificationPlatforms\n}\n\nmutation UpdateUser($input: UpdateUserInput!) {\n updateUser(input: $input) {\n id\n }\n}\n\nmutation UpdateUserPreference($input: UpdateComplexJsonInput!) {\n updateUserPreference(input: $input)\n}\n\nmutation CreateAccessLink($input: CreateAccessLinkInput!) {\n createAccessLink(input: $input) {\n id\n }\n}\n\nmutation ProcessAccessLink($input: ProcessAccessLinkInput!) {\n processAccessLink(input: $input) {\n __typename\n ... on ProcessAccessLinkError {\n error\n }\n ... on ProcessAccessLinkResponse {\n apiKey\n redirectTo\n tokenValidForDays\n }\n }\n}\n\nmutation RevokeAccessLink($accessLinkId: String!) {\n revokeAccessLink(accessLinkId: $accessLinkId)\n}\n\nmutation UpdateUserExerciseSettings($input: UpdateUserExerciseSettings!) {\n updateUserExerciseSettings(input: $input)\n}\n\nmutation MergeExercise($mergeFrom: String!, $mergeInto: String!) {\n mergeExercise(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}": types.RegisterUserDocument, - "query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n }\n}": types.MetadataDetailsDocument, - "query PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n sourceUrl\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}": types.PersonDetailsDocument, + "query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n musicSpecifics {\n duration\n viewCount\n byVariousArtists\n }\n }\n}": types.MetadataDetailsDocument, + "query PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n sourceUrl\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}": types.PersonDetailsDocument, "query UserAnalytics($input: UserAnalyticsInput!) {\n userAnalytics(input: $input) {\n hours {\n hour\n entities {\n entityId\n entityLot\n metadataLot\n }\n }\n activities {\n groupedBy\n totalCount\n totalDuration\n items {\n day\n bookCount\n showCount\n totalCount\n movieCount\n animeCount\n mangaCount\n workoutCount\n podcastCount\n totalDuration\n totalBookPages\n audioBookCount\n videoGameCount\n totalReviewCount\n visualNovelCount\n totalWorkoutReps\n totalShowDuration\n totalMovieDuration\n totalMetadataCount\n totalWorkoutWeight\n totalPodcastDuration\n totalWorkoutDistance\n totalWorkoutRestTime\n totalWorkoutDuration\n userMeasurementCount\n totalVideoGameDuration\n totalAudioBookDuration\n totalPersonReviewCount\n totalVisualNovelDuration\n totalMetadataReviewCount\n totalWorkoutPersonalBests\n totalCollectionReviewCount\n totalMetadataGroupReviewCount\n }\n }\n fitness {\n workoutReps\n workoutCount\n workoutWeight\n workoutDistance\n workoutDuration\n workoutRestTime\n measurementCount\n workoutPersonalBests\n workoutExercises {\n count\n exercise\n }\n workoutMuscles {\n count\n muscle\n }\n workoutEquipments {\n count\n equipment\n }\n }\n }\n}": types.UserAnalyticsDocument, - "query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\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 }\n }\n}": types.UserDetailsDocument, + "query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\n measurements\n }\n media {\n enabled\n show\n book\n anime\n manga\n music\n movie\n groups\n people\n genres\n podcast\n audioBook\n videoGame\n visualNovel\n }\n }\n }\n }\n }\n}": types.UserDetailsDocument, "query UserExerciseDetails($exerciseId: String!) {\n userExerciseDetails(exerciseId: $exerciseId) {\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n history {\n idx\n workoutId\n workoutEndOn\n bestSet {\n ...WorkoutSetRecordPart\n }\n }\n details {\n exerciseId\n createdOn\n lastUpdatedOn\n exerciseNumTimesInteracted\n exerciseExtraInformation {\n settings {\n excludeFromAnalytics\n setRestTimers {\n ...SetRestTimersPart\n }\n }\n lifetimeStats {\n weight\n reps\n distance\n duration\n personalBestsAchieved\n }\n personalBests {\n lot\n sets {\n setIdx\n workoutId\n exerciseIdx\n }\n }\n }\n }\n }\n}": types.UserExerciseDetailsDocument, "query UserMeasurementsList($input: UserMeasurementsListInput!) {\n userMeasurementsList(input: $input) {\n timestamp\n name\n comment\n stats {\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 custom\n }\n }\n}": types.UserMeasurementsListDocument, "query UserMetadataDetails($metadataId: String!) {\n userMetadataDetails(metadataId: $metadataId) {\n mediaReason\n hasInteracted\n averageRating\n seenByAllCount\n seenByUserCount\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n history {\n ...SeenPart\n }\n nextEntry {\n season\n volume\n episode\n chapter\n }\n inProgress {\n ...SeenPart\n }\n collections {\n ...CollectionPart\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 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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n sourceUrl\n details {\n id\n title\n lot\n source\n displayImages\n identifier\n parts\n isPartial\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n details {\n id\n lot\n title\n parts\n source\n isPartial\n sourceUrl\n identifier\n description\n displayImages\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 volume\n chapter\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 animeExtraInformation {\n ...SeenAnimeExtraInformationPart\n }\n}\n\nfragment SeenPart on Seen {\n id\n state\n progress\n reviewId\n startedOn\n finishedOn\n lastUpdatedOn\n manualTimeSpent\n numTimesUpdated\n providerWatchedOn\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 title\n image\n identifier\n publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n reps\n weight\n distance\n duration\n restTime\n personalBestsAchieved\n}\n\nfragment EntityAssetsPart on EntityAssets {\n images\n videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n reps\n pace\n oneRm\n weight\n volume\n duration\n distance\n}\n\nfragment WorkoutSetRecordPart on WorkoutSetRecord {\n lot\n personalBests\n statistic {\n ...WorkoutSetStatisticPart\n }\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n total {\n ...WorkoutOrExerciseTotalsPart\n }\n exercises {\n id\n lot\n numSets\n bestSet {\n ...WorkoutSetRecordPart\n }\n }\n focused {\n lots {\n lot\n exercises\n }\n levels {\n level\n exercises\n }\n forces {\n force\n exercises\n }\n muscles {\n muscle\n exercises\n }\n equipments {\n equipment\n exercises\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 postedOn\n isSpoiler\n visibility\n textOriginal\n textRendered\n seenItemsAssociatedWith\n postedBy {\n id\n name\n }\n comments {\n id\n text\n likedBy\n createdOn\n user {\n id\n name\n }\n }\n showExtraInformation {\n season\n episode\n }\n podcastExtraInformation {\n episode\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 supersets {\n color\n exercises\n }\n exercises {\n id\n lot\n notes\n total {\n ...WorkoutOrExerciseTotalsPart\n }\n assets {\n ...EntityAssetsPart\n }\n sets {\n lot\n rpe\n note\n restTime\n confirmedAt\n personalBests\n statistic {\n ...WorkoutSetStatisticPart\n }\n }\n }\n}\n\nfragment SetRestTimersPart on SetRestTimersSettings {\n drop\n warmup\n normal\n failure\n}": types.SeenPodcastExtraInformationPartFragmentDoc, }; @@ -47,11 +47,11 @@ export function graphql(source: "mutation RegisterUser($input: RegisterUserInput /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n }\n}"): (typeof documents)["query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n }\n}"]; +export function graphql(source: "query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n musicSpecifics {\n duration\n viewCount\n byVariousArtists\n }\n }\n}"): (typeof documents)["query MetadataDetails($metadataId: String!) {\n metadataDetails(metadataId: $metadataId) {\n id\n lot\n title\n source\n isNsfw\n isPartial\n sourceUrl\n identifier\n description\n suggestions\n publishYear\n publishDate\n providerRating\n productionStatus\n originalLanguage\n genres {\n id\n name\n }\n group {\n id\n name\n part\n }\n assets {\n images\n videos {\n videoId\n source\n }\n }\n creators {\n name\n items {\n id\n name\n image\n character\n }\n }\n watchProviders {\n name\n image\n languages\n }\n animeSpecifics {\n episodes\n }\n audioBookSpecifics {\n runtime\n }\n bookSpecifics {\n pages\n }\n movieSpecifics {\n runtime\n }\n mangaSpecifics {\n volumes\n chapters\n }\n podcastSpecifics {\n episodes {\n id\n title\n overview\n thumbnail\n number\n runtime\n publishDate\n }\n totalEpisodes\n }\n showSpecifics {\n totalSeasons\n totalEpisodes\n runtime\n seasons {\n id\n seasonNumber\n name\n overview\n backdropImages\n posterImages\n episodes {\n id\n name\n posterImages\n episodeNumber\n publishDate\n name\n overview\n runtime\n }\n }\n }\n visualNovelSpecifics {\n length\n }\n videoGameSpecifics {\n platforms\n }\n musicSpecifics {\n duration\n viewCount\n byVariousArtists\n }\n }\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 PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n sourceUrl\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n sourceUrl\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}"]; +export function graphql(source: "query PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n sourceUrl\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n personDetails(personId: $personId) {\n details {\n id\n name\n source\n identifier\n isPartial\n description\n birthDate\n deathDate\n place\n website\n gender\n displayImages\n sourceUrl\n }\n contents {\n name\n count\n items {\n character\n metadataId\n }\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -59,7 +59,7 @@ export function graphql(source: "query UserAnalytics($input: UserAnalyticsInput! /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\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 }\n }\n}"): (typeof documents)["query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\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 }\n }\n}"]; +export function graphql(source: "query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\n measurements\n }\n media {\n enabled\n show\n book\n anime\n manga\n music\n movie\n groups\n people\n genres\n podcast\n audioBook\n videoGame\n visualNovel\n }\n }\n }\n }\n }\n}"): (typeof documents)["query UserDetails {\n userDetails {\n __typename\n ... on User {\n id\n lot\n name\n isDisabled\n oidcIssuerId\n preferences {\n general {\n reviewScale\n gridPacking\n displayNsfw\n disableVideos\n persistQueries\n disableReviews\n disableIntegrations\n disableWatchProviders\n disableNavigationAnimation\n dashboard {\n hidden\n section\n numElements\n deduplicateMedia\n }\n watchProviders {\n lot\n values\n }\n }\n fitness {\n logging {\n muteSounds\n showDetailsWhileEditing\n }\n exercises {\n unitSystem\n setRestTimers {\n ...SetRestTimersPart\n }\n }\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 }\n notifications {\n toSend\n enabled\n }\n featuresEnabled {\n analytics {\n enabled\n }\n others {\n calendar\n collections\n }\n fitness {\n enabled\n workouts\n templates\n measurements\n }\n media {\n enabled\n show\n book\n anime\n manga\n music\n movie\n groups\n people\n genres\n podcast\n audioBook\n videoGame\n visualNovel\n }\n }\n }\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -75,7 +75,7 @@ export function graphql(source: "query UserMetadataDetails($metadataId: String!) /** * 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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n sourceUrl\n details {\n id\n title\n lot\n source\n displayImages\n identifier\n parts\n isPartial\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n sourceUrl\n details {\n id\n title\n lot\n source\n displayImages\n identifier\n parts\n isPartial\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n details {\n id\n lot\n title\n parts\n source\n isPartial\n sourceUrl\n identifier\n description\n displayImages\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 UserExports {\n userExports {\n url\n size\n endedAt\n startedAt\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 syncToOwnedCollection\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 UserRecommendations {\n userRecommendations\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: MetadataGroupsListInput!) {\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}\n\nquery UserAccessLinks {\n userAccessLinks {\n id\n name\n isDemo\n createdOn\n expiresOn\n timesUsed\n isRevoked\n maximumUses\n isAccountDefault\n isMutationAllowed\n }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n name\n level\n force\n source\n muscles\n mechanic\n equipment\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n name\n image\n muscle\n lastUpdatedOn\n numTimesInteracted\n }\n }\n}\n\nquery ImportReports {\n importReports {\n id\n source\n progress\n startedOn\n finishedOn\n wasSuccess\n details {\n import {\n total\n }\n failedItems {\n lot\n step\n error\n identifier\n }\n }\n }\n}\n\nquery GenresList($input: SearchInput!) {\n genresList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n name\n numItems\n }\n }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n genreDetails(input: $input) {\n details {\n id\n name\n numItems\n }\n contents {\n details {\n total\n nextPage\n }\n items\n }\n }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}\n\nquery CoreDetails {\n coreDetails {\n version\n docsLink\n pageSize\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n isServerKeyValidated\n metadataLotSourceMappings {\n lot\n sources\n }\n metadataProviderLanguages {\n source\n default\n supported\n }\n frontend {\n url\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n contents\n details {\n id\n lot\n title\n parts\n source\n isPartial\n sourceUrl\n identifier\n description\n displayImages\n }\n }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n metadataGroupSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n parts\n }\n }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n metadataList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n metadataSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n databaseId\n hasInteracted\n item {\n identifier\n title\n image\n publishYear\n }\n }\n }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n peopleSearch(input: $input) {\n details {\n total\n nextPage\n }\n items {\n identifier\n name\n image\n birthYear\n }\n }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n recentlyConsumed\n reviews {\n ...ReviewItemPart\n }\n collections {\n ...CollectionPart\n }\n }\n}\n\nquery UserPersonDetails($personId: String!) {\n userPersonDetails(personId: $personId) {\n recentlyConsumed\n collections {\n ...CollectionPart\n }\n reviews {\n ...ReviewItemPart\n }\n }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n userWorkoutDetails(workoutId: $workoutId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n endTime\n duration\n startTime\n templateId\n repeatedFrom\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n userWorkoutsList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n collections {\n ...CollectionPart\n }\n details {\n id\n name\n createdOn\n visibility\n summary {\n ...WorkoutSummaryPart\n }\n information {\n ...WorkoutInformationPart\n }\n }\n }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n userWorkoutTemplatesList(input: $input) {\n details {\n total\n nextPage\n }\n items\n }\n}\n\nquery UserAnalyticsParameters {\n userAnalyticsParameters {\n endDate\n startDate\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 5a72a4b41c..17410ceb09 100644 --- a/libs/generated/src/graphql/backend/graphql.ts +++ b/libs/generated/src/graphql/backend/graphql.ts @@ -270,6 +270,7 @@ export type CreateCustomMetadataInput = { lot: MediaLot; mangaSpecifics?: InputMaybe<MangaSpecificsInput>; movieSpecifics?: InputMaybe<MovieSpecificsInput>; + musicSpecifics?: InputMaybe<MusicSpecificsInput>; podcastSpecifics?: InputMaybe<PodcastSpecificsInput>; publishYear?: InputMaybe<Scalars['Int']['input']>; showSpecifics?: InputMaybe<ShowSpecificsInput>; @@ -665,10 +666,6 @@ export type ExportJob = { url: Scalars['String']['output']; }; -export type ExternalIdentifiers = { - tvdbId?: Maybe<Scalars['Int']['output']>; -}; - export type FitnessAnalyticsEquipment = { count: Scalars['Int']['output']; equipment: ExerciseEquipment; @@ -752,7 +749,6 @@ export type GraphqlMetadataDetails = { bookSpecifics?: Maybe<BookSpecifics>; creators: Array<MetadataCreatorGroupedByRole>; description?: Maybe<Scalars['String']['output']>; - externalIdentifiers?: Maybe<ExternalIdentifiers>; genres: Array<GenreListItem>; group?: Maybe<GraphqlMetadataGroup>; id: Scalars['String']['output']; @@ -762,6 +758,7 @@ export type GraphqlMetadataDetails = { lot: MediaLot; mangaSpecifics?: Maybe<MangaSpecifics>; movieSpecifics?: Maybe<MovieSpecifics>; + musicSpecifics?: Maybe<MusicSpecifics>; originalLanguage?: Maybe<Scalars['String']['output']>; podcastSpecifics?: Maybe<PodcastSpecifics>; productionStatus?: Maybe<Scalars['String']['output']>; @@ -995,6 +992,7 @@ export enum MediaLot { Book = 'BOOK', Manga = 'MANGA', Movie = 'MOVIE', + Music = 'MUSIC', Podcast = 'PODCAST', Show = 'SHOW', VideoGame = 'VIDEO_GAME', @@ -1028,7 +1026,8 @@ export enum MediaSource { MangaUpdates = 'MANGA_UPDATES', Openlibrary = 'OPENLIBRARY', Tmdb = 'TMDB', - Vndb = 'VNDB' + Vndb = 'VNDB', + YoutubeMusic = 'YOUTUBE_MUSIC' } export enum MediaStateChanged { @@ -1065,13 +1064,13 @@ export type MetadataGroup = { lot: MediaLot; parts: Scalars['Int']['output']; source: MediaSource; + sourceUrl?: Maybe<Scalars['String']['output']>; title: Scalars['String']['output']; }; export type MetadataGroupDetails = { contents: Array<Scalars['String']['output']>; details: MetadataGroup; - sourceUrl?: Maybe<Scalars['String']['output']>; }; export type MetadataGroupSearchInput = { @@ -1161,6 +1160,18 @@ export type MovieSpecificsInput = { runtime?: InputMaybe<Scalars['Int']['input']>; }; +export type MusicSpecifics = { + byVariousArtists?: Maybe<Scalars['Boolean']['output']>; + duration?: Maybe<Scalars['Int']['output']>; + viewCount?: Maybe<Scalars['Int']['output']>; +}; + +export type MusicSpecificsInput = { + byVariousArtists?: InputMaybe<Scalars['Boolean']['input']>; + duration?: InputMaybe<Scalars['Int']['input']>; + viewCount?: InputMaybe<Scalars['Int']['input']>; +}; + export type MutationRoot = { /** Add a entity to a collection if it is not there, otherwise do nothing. */ addEntityToCollection: Scalars['Boolean']['output']; @@ -1600,6 +1611,7 @@ export type Person = { name: Scalars['String']['output']; place?: Maybe<Scalars['String']['output']>; source: MediaSource; + sourceUrl?: Maybe<Scalars['String']['output']>; website?: Maybe<Scalars['String']['output']>; }; @@ -1611,7 +1623,6 @@ export enum PersonAndMetadataGroupsSortBy { export type PersonDetails = { contents: Array<PersonDetailsGroupedByRole>; details: Person; - sourceUrl?: Maybe<Scalars['String']['output']>; }; export type PersonDetailsGroupedByRole = { @@ -2486,6 +2497,7 @@ export type UserMediaFeaturesEnabledPreferences = { groups: Scalars['Boolean']['output']; manga: Scalars['Boolean']['output']; movie: Scalars['Boolean']['output']; + music: Scalars['Boolean']['output']; people: Scalars['Boolean']['output']; podcast: Scalars['Boolean']['output']; show: Scalars['Boolean']['output']; @@ -3194,14 +3206,14 @@ export type MetadataDetailsQueryVariables = Exact<{ }>; -export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group?: { id: string, name: string, part: number } | null, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null } }; +export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group?: { id: string, name: string, part: number } | null, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null, musicSpecifics?: { duration?: number | null, viewCount?: number | null, byVariousArtists?: boolean | null } | null } }; export type PersonDetailsQueryVariables = Exact<{ personId: Scalars['String']['input']; }>; -export type PersonDetailsQuery = { personDetails: { sourceUrl?: string | null, details: { id: string, name: string, source: MediaSource, identifier: string, isPartial?: boolean | null, description?: string | null, birthDate?: string | null, deathDate?: string | null, place?: string | null, website?: string | null, gender?: string | null, displayImages: Array<string> }, contents: Array<{ name: string, count: number, items: Array<{ character?: string | null, metadataId: string }> }> } }; +export type PersonDetailsQuery = { personDetails: { details: { id: string, name: string, source: MediaSource, identifier: string, isPartial?: boolean | null, description?: string | null, birthDate?: string | null, deathDate?: string | null, place?: string | null, website?: string | null, gender?: string | null, displayImages: Array<string>, sourceUrl?: string | null }, contents: Array<{ name: string, count: number, items: Array<{ character?: string | null, metadataId: string }> }> } }; export type UserAnalyticsQueryVariables = Exact<{ input: UserAnalyticsInput; @@ -3213,7 +3225,7 @@ export type UserAnalyticsQuery = { userAnalytics: { hours: Array<{ hour: number, export type UserDetailsQueryVariables = Exact<{ [key: string]: never; }>; -export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, 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 } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: 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 } } } } | { __typename: 'UserDetailsError' } }; +export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, 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 } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: boolean, measurements: boolean }, media: { enabled: boolean, show: boolean, book: boolean, anime: boolean, manga: boolean, music: boolean, movie: boolean, groups: boolean, people: boolean, genres: boolean, podcast: boolean, audioBook: boolean, videoGame: boolean, visualNovel: boolean } } } } | { __typename: 'UserDetailsError' } }; export type UserExerciseDetailsQueryVariables = Exact<{ exerciseId: Scalars['String']['input']; @@ -3386,7 +3398,7 @@ export type MetadataGroupDetailsQueryVariables = Exact<{ }>; -export type MetadataGroupDetailsQuery = { metadataGroupDetails: { contents: Array<string>, sourceUrl?: string | null, details: { id: string, title: string, lot: MediaLot, source: MediaSource, displayImages: Array<string>, identifier: string, parts: number, isPartial?: boolean | null } } }; +export type MetadataGroupDetailsQuery = { metadataGroupDetails: { contents: Array<string>, details: { id: string, lot: MediaLot, title: string, parts: number, source: MediaSource, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, displayImages: Array<string> } } }; export type MetadataGroupSearchQueryVariables = Exact<{ input: MetadataGroupSearchInput; @@ -3562,10 +3574,10 @@ export const ProcessAccessLinkDocument = {"kind":"Document","definitions":[{"kin export const RevokeAccessLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeAccessLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revokeAccessLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"accessLinkId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}}}]}]}}]} as unknown as DocumentNode<RevokeAccessLinkMutation, RevokeAccessLinkMutationVariables>; export const UpdateUserExerciseSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUserExerciseSettings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUserExerciseSettings"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUserExerciseSettings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<UpdateUserExerciseSettingsMutation, UpdateUserExerciseSettingsMutationVariables>; export const MergeExerciseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MergeExercise"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mergeExercise"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"mergeFrom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}}},{"kind":"Argument","name":{"kind":"Name","value":"mergeInto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}}}]}]}}]} as unknown as DocumentNode<MergeExerciseMutation, MergeExerciseMutationVariables>; -export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"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":"metadataDetails"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"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":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>; -export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"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":"personDetails"},"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":"sourceUrl"}},{"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":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>; +export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"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":"metadataDetails"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"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":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}},{"kind":"Field","name":{"kind":"Name","value":"musicSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"viewCount"}},{"kind":"Field","name":{"kind":"Name","value":"byVariousArtists"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>; +export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"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":"personDetails"},"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":"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":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>; export const UserAnalyticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserAnalytics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserAnalyticsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userAnalytics"},"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":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hour"}},{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"activities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groupedBy"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"day"}},{"kind":"Field","name":{"kind":"Name","value":"bookCount"}},{"kind":"Field","name":{"kind":"Name","value":"showCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"movieCount"}},{"kind":"Field","name":{"kind":"Name","value":"animeCount"}},{"kind":"Field","name":{"kind":"Name","value":"mangaCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"podcastCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalBookPages"}},{"kind":"Field","name":{"kind":"Name","value":"audioBookCount"}},{"kind":"Field","name":{"kind":"Name","value":"videoGameCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"totalShowDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMovieDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"totalPodcastDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"userMeasurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVideoGameDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalAudioBookDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalPersonReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVisualNovelDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"totalCollectionReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataGroupReviewCount"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"workoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"measurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"workoutExercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"exercise"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutMuscles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutEquipments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserAnalyticsQuery, UserAnalyticsQueryVariables>; -export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"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"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"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":"gridPacking"}},{"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":"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":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"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":"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":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"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":"templates"}},{"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"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>; +export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"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"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"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":"gridPacking"}},{"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":"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":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"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":"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":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"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":"templates"}},{"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":"show"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"music"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>; export const UserExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserExerciseDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userExerciseDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"exerciseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}}}],"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":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"idx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"workoutEndOn"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetRecordPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseId"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseNumTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"settings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excludeFromAnalytics"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"lifetimeStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"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":"personalBestsAchieved"}}]}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setIdx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseIdx"}}]}}]}}]}}]}}]}}]}},{"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}}]}},{"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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":"WorkoutSetRecordPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetRecord"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserExerciseDetailsQuery, UserExerciseDetailsQueryVariables>; export const UserMeasurementsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMeasurementsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserMeasurementsListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMeasurementsList"},"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":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"stats"},"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":"custom"}}]}}]}}]}}]} as unknown as DocumentNode<UserMeasurementsListQuery, UserMeasurementsListQueryVariables>; 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":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"recentlyConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"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":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"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":"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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":"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":"state"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"reviewId"}},{"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":"manualTimeSpent"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"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<UserMetadataDetailsQuery, UserMetadataDetailsQueryVariables>; @@ -3592,7 +3604,7 @@ export const GenresListDocument = {"kind":"Document","definitions":[{"kind":"Ope export const GenreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GenreDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GenreDetailsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"genreDetails"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"numItems"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"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"}}]}}]}}]}}]} as unknown as DocumentNode<GenreDetailsQuery, GenreDetailsQueryVariables>; export const CollectionContentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CollectionContents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CollectionContentsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collectionContents"},"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":"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":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"results"},"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":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}}]}}]}}]}},{"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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<CollectionContentsQuery, CollectionContentsQueryVariables>; export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"smtpEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"signupAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"disableTelemetry"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"fileStorageEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isServerKeyValidated"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLotSourceMappings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sources"}}]}},{"kind":"Field","name":{"kind":"Name","value":"metadataProviderLanguages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"default"}},{"kind":"Field","name":{"kind":"Name","value":"supported"}}]}},{"kind":"Field","name":{"kind":"Name","value":"frontend"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"oidcButtonLabel"}},{"kind":"Field","name":{"kind":"Name","value":"dashboardMessage"}},{"kind":"Field","name":{"kind":"Name","value":"umami"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"scriptUrl"}},{"kind":"Field","name":{"kind":"Name","value":"websiteId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"exerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"downloadRequired"}},{"kind":"Field","name":{"kind":"Name","value":"filters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lotMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"bests"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CoreDetailsQuery, CoreDetailsQueryVariables>; -export const MetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupDetails"},"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":"metadataGroupDetails"},"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":"contents"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupDetailsQuery, MetadataGroupDetailsQueryVariables>; +export const MetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupDetails"},"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":"metadataGroupDetails"},"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":"contents"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupDetailsQuery, MetadataGroupDetailsQueryVariables>; export const MetadataGroupSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataGroupSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupSearch"},"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":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupSearchQuery, MetadataGroupSearchQueryVariables>; export const MetadataListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataList"},"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"}}]}}]}}]} as unknown as DocumentNode<MetadataListQuery, MetadataListQueryVariables>; export const MetadataSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataSearch"},"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":"databaseId"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"item"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]}}]}}]}}]} as unknown as DocumentNode<MetadataSearchQuery, MetadataSearchQueryVariables>; diff --git a/libs/generated/src/graphql/backend/types.generated.ts b/libs/generated/src/graphql/backend/types.generated.ts index 3efe9d0b3f..31a3c4235f 100644 --- a/libs/generated/src/graphql/backend/types.generated.ts +++ b/libs/generated/src/graphql/backend/types.generated.ts @@ -256,6 +256,7 @@ export type CreateCustomMetadataInput = { lot: MediaLot; mangaSpecifics?: InputMaybe<MangaSpecificsInput>; movieSpecifics?: InputMaybe<MovieSpecificsInput>; + musicSpecifics?: InputMaybe<MusicSpecificsInput>; podcastSpecifics?: InputMaybe<PodcastSpecificsInput>; publishYear?: InputMaybe<Scalars['Int']['input']>; showSpecifics?: InputMaybe<ShowSpecificsInput>; @@ -666,11 +667,6 @@ export type ExportJob = { url: Scalars['String']['output']; }; -export type ExternalIdentifiers = { - __typename?: 'ExternalIdentifiers'; - tvdbId?: Maybe<Scalars['Int']['output']>; -}; - export type FitnessAnalyticsEquipment = { __typename?: 'FitnessAnalyticsEquipment'; count: Scalars['Int']['output']; @@ -765,7 +761,6 @@ export type GraphqlMetadataDetails = { bookSpecifics?: Maybe<BookSpecifics>; creators: Array<MetadataCreatorGroupedByRole>; description?: Maybe<Scalars['String']['output']>; - externalIdentifiers?: Maybe<ExternalIdentifiers>; genres: Array<GenreListItem>; group?: Maybe<GraphqlMetadataGroup>; id: Scalars['String']['output']; @@ -775,6 +770,7 @@ export type GraphqlMetadataDetails = { lot: MediaLot; mangaSpecifics?: Maybe<MangaSpecifics>; movieSpecifics?: Maybe<MovieSpecifics>; + musicSpecifics?: Maybe<MusicSpecifics>; originalLanguage?: Maybe<Scalars['String']['output']>; podcastSpecifics?: Maybe<PodcastSpecifics>; productionStatus?: Maybe<Scalars['String']['output']>; @@ -1023,6 +1019,7 @@ export enum MediaLot { Book = 'BOOK', Manga = 'MANGA', Movie = 'MOVIE', + Music = 'MUSIC', Podcast = 'PODCAST', Show = 'SHOW', VideoGame = 'VIDEO_GAME', @@ -1056,7 +1053,8 @@ export enum MediaSource { MangaUpdates = 'MANGA_UPDATES', Openlibrary = 'OPENLIBRARY', Tmdb = 'TMDB', - Vndb = 'VNDB' + Vndb = 'VNDB', + YoutubeMusic = 'YOUTUBE_MUSIC' } export enum MediaStateChanged { @@ -1096,6 +1094,7 @@ export type MetadataGroup = { lot: MediaLot; parts: Scalars['Int']['output']; source: MediaSource; + sourceUrl?: Maybe<Scalars['String']['output']>; title: Scalars['String']['output']; }; @@ -1103,7 +1102,6 @@ export type MetadataGroupDetails = { __typename?: 'MetadataGroupDetails'; contents: Array<Scalars['String']['output']>; details: MetadataGroup; - sourceUrl?: Maybe<Scalars['String']['output']>; }; export type MetadataGroupSearchInput = { @@ -1201,6 +1199,19 @@ export type MovieSpecificsInput = { runtime?: InputMaybe<Scalars['Int']['input']>; }; +export type MusicSpecifics = { + __typename?: 'MusicSpecifics'; + byVariousArtists?: Maybe<Scalars['Boolean']['output']>; + duration?: Maybe<Scalars['Int']['output']>; + viewCount?: Maybe<Scalars['Int']['output']>; +}; + +export type MusicSpecificsInput = { + byVariousArtists?: InputMaybe<Scalars['Boolean']['input']>; + duration?: InputMaybe<Scalars['Int']['input']>; + viewCount?: InputMaybe<Scalars['Int']['input']>; +}; + export type MutationRoot = { __typename?: 'MutationRoot'; /** Add a entity to a collection if it is not there, otherwise do nothing. */ @@ -1646,6 +1657,7 @@ export type Person = { name: Scalars['String']['output']; place?: Maybe<Scalars['String']['output']>; source: MediaSource; + sourceUrl?: Maybe<Scalars['String']['output']>; website?: Maybe<Scalars['String']['output']>; }; @@ -1658,7 +1670,6 @@ export type PersonDetails = { __typename?: 'PersonDetails'; contents: Array<PersonDetailsGroupedByRole>; details: Person; - sourceUrl?: Maybe<Scalars['String']['output']>; }; export type PersonDetailsGroupedByRole = { @@ -2578,6 +2589,7 @@ export type UserMediaFeaturesEnabledPreferences = { groups: Scalars['Boolean']['output']; manga: Scalars['Boolean']['output']; movie: Scalars['Boolean']['output']; + music: Scalars['Boolean']['output']; people: Scalars['Boolean']['output']; podcast: Scalars['Boolean']['output']; show: Scalars['Boolean']['output']; diff --git a/libs/graphql/src/backend/queries/MetadataDetails.gql b/libs/graphql/src/backend/queries/MetadataDetails.gql index caa2c9aea4..f9a366fc39 100644 --- a/libs/graphql/src/backend/queries/MetadataDetails.gql +++ b/libs/graphql/src/backend/queries/MetadataDetails.gql @@ -102,5 +102,10 @@ query MetadataDetails($metadataId: String!) { videoGameSpecifics { platforms } + musicSpecifics { + duration + viewCount + byVariousArtists + } } } diff --git a/libs/graphql/src/backend/queries/PersonDetails.gql b/libs/graphql/src/backend/queries/PersonDetails.gql index d8d31b67c3..16a397aaeb 100644 --- a/libs/graphql/src/backend/queries/PersonDetails.gql +++ b/libs/graphql/src/backend/queries/PersonDetails.gql @@ -1,6 +1,5 @@ query PersonDetails($personId: String!) { personDetails(personId: $personId) { - sourceUrl details { id name @@ -14,6 +13,7 @@ query PersonDetails($personId: String!) { website gender displayImages + sourceUrl } contents { name diff --git a/libs/graphql/src/backend/queries/UserDetails.gql b/libs/graphql/src/backend/queries/UserDetails.gql index b879d2f02d..dcc283e628 100644 --- a/libs/graphql/src/backend/queries/UserDetails.gql +++ b/libs/graphql/src/backend/queries/UserDetails.gql @@ -92,18 +92,19 @@ query UserDetails { } media { enabled - anime - audioBook + show book + anime manga + music movie + groups + people + genres podcast - show + audioBook videoGame visualNovel - people - groups - genres } } } diff --git a/libs/graphql/src/backend/queries/combined.gql b/libs/graphql/src/backend/queries/combined.gql index 66b6f85754..083eb64f55 100644 --- a/libs/graphql/src/backend/queries/combined.gql +++ b/libs/graphql/src/backend/queries/combined.gql @@ -320,16 +320,17 @@ query CoreDetails { query MetadataGroupDetails($metadataGroupId: String!) { metadataGroupDetails(metadataGroupId: $metadataGroupId) { contents - sourceUrl details { id - title lot - source - displayImages - identifier + title parts + source isPartial + sourceUrl + identifier + description + displayImages } } }