diff --git a/docsite/docs/configuration/clients/_env_configs/_discord.md b/docsite/docs/configuration/clients/_env_configs/_discord.md index f2a81607..35f8d1f8 100644 --- a/docsite/docs/configuration/clients/_env_configs/_discord.md +++ b/docsite/docs/configuration/clients/_env_configs/_discord.md @@ -1,12 +1,12 @@ -| Environmental Variable | Type | Default | Description | -| ---------------------------------- | ------- | --------------------- | --------------------------------------- | -| _**`DISCORD_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `DISCORD_NAME` | string | Value of `DISCORD_ID` | A vanity name EX `My Cool Component` | -| `DISCORD_ENABLE` | boolean | true | Should this component be used? | -| `DISCORD_TOKEN` | string | | | -| `DISCORD_ARTWORK` | union | | | -| `DISCORD_APPLICATION_ID` | string | | | -| `DISCORD_IPC_LOCATIONS` | union | | | -| `DISCORD_ARTWORK_DEFAULT_URL` | string | | | -| `DISCORD_STATUS_OVERRIDE_ALLOW` | union | | | -| `DISCORD_LISTENING_ACTIVITY_ALLOW` | union | | | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| ---------------------------------- | -------------------------- | --------------------- | --------------------------------------- | +| _**`DISCORD_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `DISCORD_NAME` | string | Value of `DISCORD_ID` | A vanity name EX `My Cool Component` | +| `DISCORD_ENABLE` | boolean | true | Should this component be used? | +| `DISCORD_TOKEN` | string | | | +| `DISCORD_ARTWORK` | boolean OR string OR array | | | +| `DISCORD_APPLICATION_ID` | string | | | +| `DISCORD_IPC_LOCATIONS` | string OR array | | | +| `DISCORD_ARTWORK_DEFAULT_URL` | string | | | +| `DISCORD_STATUS_OVERRIDE_ALLOW` | string OR array | | | +| `DISCORD_LISTENING_ACTIVITY_ALLOW` | string OR array | | | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_chromecast.md b/docsite/docs/configuration/sources/_env_configs/_chromecast.md index 6b6a5c8f..29b1342a 100644 --- a/docsite/docs/configuration/sources/_env_configs/_chromecast.md +++ b/docsite/docs/configuration/sources/_env_configs/_chromecast.md @@ -1,9 +1,9 @@ -| Environmental Variable | Type | Default | Description | -| ---------------------- | ------- | ---------------- | ------------------------------------------------------------------------------------ | -| _**`CC_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `CC_NAME` | string | Value of `CC_ID` | A vanity name EX `My Cool Component` | -| `CC_ENABLE` | boolean | true | Should this component be used? | -| `CC_BLACKLIST_DEVICES` | string | | DO NOT scrobble from any cast devices that START WITH these values, case-insensitive | -| `CC_WHITELIST_DEVICES` | string | | ONLY scrobble from any cast device that START WITH these values, case-insensitive | -| `CC_BLACKLIST_APPS` | string | | DO NOT scrobble from any application that START WITH these values, case-insensitive | -| `CC_WHITELIST_APPS` | string | | ONLY scrobble from any application that START WITH these values, case-insensitive | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| ---------------------- | ------- | ---------------- | ----------------------------------------------------------------------------------------------- | +| _**`CC_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `CC_NAME` | string | Value of `CC_ID` | A vanity name EX `My Cool Component` | +| `CC_ENABLE` | boolean | true | Should this component be used? | +| `CC_BLACKLIST_DEVICES` | string | | DO NOT scrobble from any cast devices on this comma-delimited list that START WITH these values | +| `CC_WHITELIST_DEVICES` | string | | ONLY scrobble from any cast device on this comma-delimited list that START WITH these values | +| `CC_BLACKLIST_APPS` | string | | DO NOT scrobble from any application on this comma-delimited list that START WITH these values | +| `CC_WHITELIST_APPS` | string | | ONLY scrobble from any application on this comma-delimited list that START WITH these values | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_endpointlfm.md b/docsite/docs/configuration/sources/_env_configs/_endpointlfm.md index 5f4a999c..df70de16 100644 --- a/docsite/docs/configuration/sources/_env_configs/_endpointlfm.md +++ b/docsite/docs/configuration/sources/_env_configs/_endpointlfm.md @@ -3,4 +3,4 @@ | _**`LFM_ID`**_ | string | | A globally unique ID EX `myComponentId` | | `LFM_NAME` | string | Value of `LFM_ID` | A vanity name EX `My Cool Component` | | `LFM_ENABLE` | boolean | true | Should this component be used? | -| `LFM_SLUG` | union | | The URL ending that should be used to identify scrobbles for this source | \ No newline at end of file +| `LFM_SLUG` | string | | The URL ending that should be used to identify scrobbles for this source | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_jellyfin.md b/docsite/docs/configuration/sources/_env_configs/_jellyfin.md index 92d7c0e7..6c6a1409 100644 --- a/docsite/docs/configuration/sources/_env_configs/_jellyfin.md +++ b/docsite/docs/configuration/sources/_env_configs/_jellyfin.md @@ -1,17 +1,17 @@ -| Environmental Variable | Type | Default | Description | -| -------------------------------- | ------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| _**`JELLYFIN_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `JELLYFIN_NAME` | string | Value of `JELLYFIN_ID` | A vanity name EX `My Cool Component` | -| `JELLYFIN_ENABLE` | boolean | true | Should this component be used? | -| _**`JELLYFIN_USER`**_ | string | | The username of the user to authenticate for or track scrobbles for | -| `JELLYFIN_PASSWORD` | string | | Password of the username to authenticate for | -| `JELLYFIN_APIKEY` | string | | API Key to authenticate with. | -| _**`JELLYFIN_URL`**_ | string | | HOST:PORT of the Jellyfin server to connect to | -| `JELLYFIN_USERS_ALLOW` | union | | Only scrobble for specific users (case-insensitive) | -| `JELLYFIN_USERS_BLOCK` | union | | Do not scrobble for these users (case-insensitive) | -| `JELLYFIN_DEVICES_ALLOW` | union | | Only scrobble if device or application name contains strings from this list (case-insensitive) | -| `JELLYFIN_DEVICES_BLOCK` | union | | Do not scrobble if device or application name contains strings from this list (case-insensitive) | -| `JELLYFIN_LIBRARIES_ALLOW` | union | | Only scrobble if library name contains string from this list (case-insensitive) | -| `JELLYFIN_LIBRARIES_BLOCK` | union | | Do not scrobble if library name contains strings from this list (case-insensitive) | -| `JELLYFIN_FRONTEND_URL_OVERRIDE` | string | | HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from) | -| `JELLYFIN_MEDIATYPES_ALLOW` | union | | Allow these media types to be scrobbled. | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| -------------------------------- | -------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| _**`JELLYFIN_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `JELLYFIN_NAME` | string | Value of `JELLYFIN_ID` | A vanity name EX `My Cool Component` | +| `JELLYFIN_ENABLE` | boolean | true | Should this component be used? | +| _**`JELLYFIN_USER`**_ | string | | The username of the user to authenticate for or track scrobbles for | +| `JELLYFIN_PASSWORD` | string | | Password of the username to authenticate for | +| `JELLYFIN_APIKEY` | string | | API Key to authenticate with. | +| _**`JELLYFIN_URL`**_ | string | | HOST:PORT of the Jellyfin server to connect to | +| `JELLYFIN_USERS_ALLOW` | string OR true | | Only scrobble for specific users from this comma-delimited list. If `true`, scrobble for all users. | +| `JELLYFIN_USERS_BLOCK` | string | | Do not scrobble for users from this comma-delimited list | +| `JELLYFIN_DEVICES_ALLOW` | string | | Only scrobble if device or application name contains strings from this comma-delimited list | +| `JELLYFIN_DEVICES_BLOCK` | string | | Do not scrobble if device or application name contains strings from this comma-delimited list | +| `JELLYFIN_LIBRARIES_ALLOW` | string | | Only scrobble if library name contains string from this comma-delimited list | +| `JELLYFIN_LIBRARIES_BLOCK` | string | | Do not scrobble if library name contains strings from this comma-delimited list | +| `JELLYFIN_FRONTEND_URL_OVERRIDE` | string | | HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from) | +| `JELLYFIN_MEDIATYPES_ALLOW` | string | | Allow media types from this comma-delimited list to be scrobbled | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_mpris.md b/docsite/docs/configuration/sources/_env_configs/_mpris.md index 160a018c..973fc9dc 100644 --- a/docsite/docs/configuration/sources/_env_configs/_mpris.md +++ b/docsite/docs/configuration/sources/_env_configs/_mpris.md @@ -1,7 +1,7 @@ -| Environmental Variable | Type | Default | Description | -| ---------------------- | ------- | ------------------- | ------------------------------------------------------------------------------- | -| _**`MPRIS_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `MPRIS_NAME` | string | Value of `MPRIS_ID` | A vanity name EX `My Cool Component` | -| `MPRIS_ENABLE` | boolean | true | Should this component be used? | -| `MPRIS_BLACKLIST` | union | | DO NOT scrobble from any players that START WITH these values, case-insensitive | -| `MPRIS_WHITELIST` | union | | ONLY from any players that START WITH these values, case-insensitive | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| ---------------------- | ------- | ------------------- | -------------------------------------------------------------------------------------- | +| _**`MPRIS_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `MPRIS_NAME` | string | Value of `MPRIS_ID` | A vanity name EX `My Cool Component` | +| `MPRIS_ENABLE` | boolean | true | Should this component be used? | +| `MPRIS_BLACKLIST` | string | | DO NOT scrobble from any players that START WITH values from this comma-delimited list | +| `MPRIS_WHITELIST` | string | | ONLY scrobble from any players that START WITH values from this comma-delimited list | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_plex.md b/docsite/docs/configuration/sources/_env_configs/_plex.md index 3d50bf1c..e91438a3 100644 --- a/docsite/docs/configuration/sources/_env_configs/_plex.md +++ b/docsite/docs/configuration/sources/_env_configs/_plex.md @@ -1,13 +1,13 @@ -| Environmental Variable | Type | Default | Description | -| ---------------------- | ------- | ------------------ | ------------------------------------------------------------------------------------------------ | -| _**`PLEX_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `PLEX_NAME` | string | Value of `PLEX_ID` | A vanity name EX `My Cool Component` | -| `PLEX_ENABLE` | boolean | true | Should this component be used? | -| _**`PLEX_URL`**_ | string | | http(s)://HOST:PORT of the Plex server to connect to | -| `PLEX_TOKEN` | string | | | -| `PLEX_USERS_ALLOW` | union | | Only scrobble for specific users (case-insensitive) | -| `PLEX_USERS_BLOCK` | union | | Do not scrobble for these users (case-insensitive) | -| `PLEX_DEVICES_ALLOW` | union | | Only scrobble if device or application name contains strings from this list (case-insensitive) | -| `PLEX_DEVICES_BLOCK` | union | | Do not scrobble if device or application name contains strings from this list (case-insensitive) | -| `PLEX_LIBRARIES_ALLOW` | union | | Only scrobble if library name contains string from this list (case-insensitive) | -| `PLEX_LIBRARIES_BLOCK` | union | | Do not scrobble if library name contains strings from this list (case-insensitive) | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| ---------------------- | ------- | ------------------ | --------------------------------------------------------------------------------------------- | +| _**`PLEX_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `PLEX_NAME` | string | Value of `PLEX_ID` | A vanity name EX `My Cool Component` | +| `PLEX_ENABLE` | boolean | true | Should this component be used? | +| _**`PLEX_URL`**_ | string | | http(s)://HOST:PORT of the Plex server to connect to | +| `PLEX_TOKEN` | string | | | +| `PLEX_USERS_ALLOW` | string | | Only scrobble for specific users from this comma-delimited list | +| `PLEX_USERS_BLOCK` | string | | Do not scrobble for users from this comma-delimited list | +| `PLEX_DEVICES_ALLOW` | string | | Do not scrobble if device or application name contains strings from this comma-delimited list | +| `PLEX_DEVICES_BLOCK` | string | | Only scrobble if device or application name contains strings from this comma-delimited list | +| `PLEX_LIBRARIES_ALLOW` | string | | Only scrobble if library name contains string from this comma-delimited list | +| `PLEX_LIBRARIES_BLOCK` | string | | Do not scrobble if library name contains strings from this comma-delimited list | \ No newline at end of file diff --git a/docsite/docs/configuration/sources/_env_configs/_sonos.md b/docsite/docs/configuration/sources/_env_configs/_sonos.md index 6608751d..24482159 100644 --- a/docsite/docs/configuration/sources/_env_configs/_sonos.md +++ b/docsite/docs/configuration/sources/_env_configs/_sonos.md @@ -1,10 +1,10 @@ -| Environmental Variable | Type | Default | Description | -| ---------------------- | ------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| _**`SONOS_ID`**_ | string | | A globally unique ID EX `myComponentId` | -| `SONOS_NAME` | string | Value of `SONOS_ID` | A vanity name EX `My Cool Component` | -| `SONOS_ENABLE` | boolean | true | Should this component be used? | -| _**`SONOS_HOST`**_ | string | | IP address of any connected Sonos speaker or device | -| `SONOS_DEVICES_ALLOW` | union | | Only scrobble if device name contains strings from this list (case-insensitive) | -| `SONOS_DEVICES_BLOCK` | union | | Do not scrobble if device name contains strings from this list (case-insensitive) | -| `SONOS_GROUPS_ALLOW` | union | | Only scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) | -| `SONOS_GROUPS_BLOCK` | union | | Do not scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) | \ No newline at end of file +| Environmental Variable | Type | Default | Description | +| ---------------------- | ------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | +| _**`SONOS_ID`**_ | string | | A globally unique ID EX `myComponentId` | +| `SONOS_NAME` | string | Value of `SONOS_ID` | A vanity name EX `My Cool Component` | +| `SONOS_ENABLE` | boolean | true | Should this component be used? | +| _**`SONOS_HOST`**_ | string | | IP address of any connected Sonos speaker or device | +| `SONOS_DEVICES_ALLOW` | string | | Only scrobble if device name contains strings from this comma-delimited list | +| `SONOS_DEVICES_BLOCK` | string | | Do not scrobble if device name contains strings from this comma-delimited list | +| `SONOS_GROUPS_ALLOW` | string | | Only scrobble if the name of a group the playing device belongs to contains strings from this comma-delimited list | +| `SONOS_GROUPS_BLOCK` | string | | Do not scrobble if the name of a group the playing device belongs to contains strings from this comma-delimited list | \ No newline at end of file diff --git a/src/backend/common/infrastructure/config/client/index.ts b/src/backend/common/infrastructure/config/client/index.ts index 9bb0d173..967d7381 100644 --- a/src/backend/common/infrastructure/config/client/index.ts +++ b/src/backend/common/infrastructure/config/client/index.ts @@ -2,6 +2,7 @@ import * as z from "zod"; import {playTransformOptionsSchema} from "../../../../../core/Transform.ts"; import {commonConfigSchema, requestRetryOptionsSchema, monitorOptionsSchema, type CommonComponentEnvShape} from "../common.ts"; import {retentionConfigDurationValueSchema} from "../database.ts"; +import type { PipeUnwrapDirection } from "../../../../utils/ZodUtils.ts"; /** * Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging. @@ -187,6 +188,7 @@ export type CommonClientConfig = Omit, export interface EnvClientSchema { env: Y + pipe?: PipeUnwrapDirection prefix: string toConfig: (parsed: z.output) => Partial> } \ No newline at end of file diff --git a/src/backend/common/infrastructure/config/source/chromecast.ts b/src/backend/common/infrastructure/config/source/chromecast.ts index dd70c271..f33e646e 100644 --- a/src/backend/common/infrastructure/config/source/chromecast.ts +++ b/src/backend/common/infrastructure/config/source/chromecast.ts @@ -1,6 +1,6 @@ import * as z from "zod"; import {commonSourceConfigSchema, commonSourceDataSchema, type EnvSourceSchema} from "./index.ts"; -import { transformSplitMaybeString } from "../../../../utils/ZodUtils.ts"; +import { envMetaNormalize, transformSplitMaybeString } from "../../../../utils/ZodUtils.ts"; export const chromecastDeviceInfoSchema = z.object({ /** @@ -35,12 +35,12 @@ export const chromecastDataSchema = z.object({ * @examples [["home-mini","family-tv"]] * */ blacklistDevices: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "DO NOT scrobble from any cast devices that START WITH these values, case-insensitive", + description: "DO NOT scrobble from any cast devices on this list that START WITH these values", examples: [["home-mini", "family-tv"]] }), /** - * ONLY scrobble from any cast device that START WITH these values, case-insensitive + * ONLY scrobble from any cast device that START WITH these values * * If whitelist is present then blacklist is ignored * @@ -49,29 +49,29 @@ export const chromecastDataSchema = z.object({ * @examples [["home-mini","family-tv"]] * */ whitelistDevices: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "ONLY scrobble from any cast device that START WITH these values, case-insensitive", + description: "ONLY scrobble from any cast device on this list that START WITH these values", examples: [["home-mini", "family-tv"]] }), /** - * DO NOT scrobble from any application that START WITH these values, case-insensitive + * DO NOT scrobble from any application that START WITH these values * * @examples [["spotify","pandora"]] * */ blacklistApps: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "DO NOT scrobble from any application that START WITH these values, case-insensitive", + description: "DO NOT scrobble from any application on this list that START WITH these values", examples: [["spotify", "pandora"]] }), /** - * ONLY scrobble from any application that START WITH these values, case-insensitive + * ONLY scrobble from any application that START WITH these values * * If whitelist is present then blacklist is ignored * * @examples [["spotify","pandora"]] * */ whitelistApps: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "ONLY scrobble from any application that START WITH these values, case-insensitive", + description: "ONLY scrobble from any application on this list that START WITH these values", examples: [["spotify", "pandora"]] }), @@ -134,10 +134,10 @@ export const chromecastDataSchema = z.object({ export type ChromecastData = z.infer; const envDataSchema = z.object({ - CC_BLACKLIST_DEVICES: z.string().optional().pipe(transformSplitMaybeString).meta(chromecastDataSchema.shape.blacklistDevices.meta()), - CC_WHITELIST_DEVICES: z.string().optional().pipe(transformSplitMaybeString).meta(chromecastDataSchema.shape.whitelistDevices.meta()), - CC_BLACKLIST_APPS: z.string().optional().pipe(transformSplitMaybeString).meta(chromecastDataSchema.shape.blacklistApps.meta()), - CC_WHITELIST_APPS: z.string().optional().pipe(transformSplitMaybeString).meta(chromecastDataSchema.shape.whitelistApps.meta()), + CC_BLACKLIST_DEVICES: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(chromecastDataSchema.shape.blacklistDevices.meta())), + CC_WHITELIST_DEVICES: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(chromecastDataSchema.shape.whitelistDevices.meta())), + CC_BLACKLIST_APPS: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(chromecastDataSchema.shape.blacklistApps.meta())), + CC_WHITELIST_APPS: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(chromecastDataSchema.shape.whitelistApps.meta())), }); export const envSchemas: EnvSourceSchema = { diff --git a/src/backend/common/infrastructure/config/source/endpointlfm.ts b/src/backend/common/infrastructure/config/source/endpointlfm.ts index e4d60591..eacea887 100644 --- a/src/backend/common/infrastructure/config/source/endpointlfm.ts +++ b/src/backend/common/infrastructure/config/source/endpointlfm.ts @@ -15,7 +15,7 @@ export const lastFmEndpointDataSchema = z.object({ * * If no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used * */ - slug: z.union([z.string(), z.null()]).optional().meta({ + slug: z.string().optional().meta({ description: "The URL ending that should be used to identify scrobbles for this source" }), }); diff --git a/src/backend/common/infrastructure/config/source/index.ts b/src/backend/common/infrastructure/config/source/index.ts index 7e6bd447..5698d38b 100644 --- a/src/backend/common/infrastructure/config/source/index.ts +++ b/src/backend/common/infrastructure/config/source/index.ts @@ -2,6 +2,7 @@ import * as z from "zod"; import {requestRetryOptionsSchema, commonConfigSchema, monitorOptionsSchema} from "../common.ts"; import {retentionConfigDurationValueSchema} from "../database.ts"; import {playTransformOptionsSchema} from "../../../../../core/Transform.ts"; +import type { PipeUnwrapDirection } from "../../../../utils/ZodUtils.ts"; export const sourceRetryOptionsSchema = z.object({ ...requestRetryOptionsSchema.shape, @@ -208,6 +209,7 @@ export type CommonSourceConfig = Omit, export interface EnvSourceSchema { env: Y + pipe?: PipeUnwrapDirection prefix: string, toConfig: (parsed: z.output) => Partial> } \ No newline at end of file diff --git a/src/backend/common/infrastructure/config/source/jellyfin.ts b/src/backend/common/infrastructure/config/source/jellyfin.ts index b006c135..d86acc4c 100644 --- a/src/backend/common/infrastructure/config/source/jellyfin.ts +++ b/src/backend/common/infrastructure/config/source/jellyfin.ts @@ -1,6 +1,8 @@ import * as z from "zod"; import {commonSourceConfigSchema, commonSourceDataSchema, commonSourceOptionsSchema, type EnvSourceSchema} from "./index.ts"; +import { envMetaNormalize, transformSplitMaybeString, transformSplitMaybeStringOrBoolean } from "../../../../utils/ZodUtils.ts"; +export const jellyfinMediaTypesSchema = z.enum(['unknown','video','audio','photo','book','musicvideo']); export const jellyApiDataSchema = z.object({ ...commonSourceDataSchema.shape, /** @@ -33,44 +35,44 @@ export const jellyApiDataSchema = z.object({ }), /** - * Only scrobble for specific users (case-insensitive) + * Only scrobble for specific users * * If `true` MS will scrobble activity from all users * */ usersAllow: z.union([z.string(), z.literal(true), z.array(z.string())]).optional().meta({ - description: "Only scrobble for specific users (case-insensitive)" + description: "Only scrobble for specific users from this list. If `true`, scrobble for all users." }), /** - * Do not scrobble for these users (case-insensitive) + * Do not scrobble for these users * */ usersBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble for these users (case-insensitive)" + description: "Do not scrobble for users from this list" }), /** - * Only scrobble if device or application name contains strings from this list (case-insensitive) + * Only scrobble if device or application name contains strings from this list * */ devicesAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if device or application name contains strings from this list (case-insensitive)" + description: "Only scrobble if device or application name contains strings from this list" }), /** - * Do not scrobble if device or application name contains strings from this list (case-insensitive) + * Do not scrobble if device or application name contains strings from this list * */ devicesBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if device or application name contains strings from this list (case-insensitive)" + description: "Do not scrobble if device or application name contains strings from this list" }), /** - * Only scrobble if library name contains string from this list (case-insensitive) + * Only scrobble if library name contains string from this list * */ librariesAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if library name contains string from this list (case-insensitive)" + description: "Only scrobble if library name contains string from this list" }), /** - * Do not scrobble if library name contains strings from this list (case-insensitive) + * Do not scrobble if library name contains strings from this list * */ librariesBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if library name contains strings from this list (case-insensitive)" + description: "Do not scrobble if library name contains strings from this list" }), /** @@ -103,8 +105,8 @@ export const jellyApiDataSchema = z.object({ * See https://github.com/jellyfin/jellyfin-sdk-typescript/blob/master/src/generated-client/models/media-type.ts#L22 for possible types * */ - allowMediaTypes: z.union([z.array(z.string()), z.string()]).optional().meta({ - description: "Allow these media types to be scrobbled." + allowMediaTypes: z.union([z.array(jellyfinMediaTypesSchema), jellyfinMediaTypesSchema]).optional().meta({ + description: "Allow media types from this list to be scrobbled" }), /** @@ -126,14 +128,14 @@ const envDataSchema = z.object({ JELLYFIN_PASSWORD: jellyApiDataSchema.shape.password, JELLYFIN_APIKEY: jellyApiDataSchema.shape.apiKey, JELLYFIN_URL: jellyApiDataSchema.shape.url, - JELLYFIN_USERS_ALLOW: jellyApiDataSchema.shape.usersAllow, - JELLYFIN_USERS_BLOCK: jellyApiDataSchema.shape.usersBlock, - JELLYFIN_DEVICES_ALLOW: jellyApiDataSchema.shape.devicesAllow, - JELLYFIN_DEVICES_BLOCK: jellyApiDataSchema.shape.devicesBlock, - JELLYFIN_LIBRARIES_ALLOW: jellyApiDataSchema.shape.librariesAllow, - JELLYFIN_LIBRARIES_BLOCK: jellyApiDataSchema.shape.librariesBlock, + JELLYFIN_USERS_ALLOW: z.union([z.string(),z.literal(true)]).optional().pipe(transformSplitMaybeStringOrBoolean).meta(envMetaNormalize(jellyApiDataSchema.shape.usersAllow.meta())), + JELLYFIN_USERS_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.usersBlock.meta())), + JELLYFIN_DEVICES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.devicesAllow.meta())), + JELLYFIN_DEVICES_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.devicesBlock.meta())), + JELLYFIN_LIBRARIES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.librariesAllow.meta())), + JELLYFIN_LIBRARIES_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.librariesBlock.meta())), JELLYFIN_FRONTEND_URL_OVERRIDE: jellyApiDataSchema.shape.frontendUrlOverride, - JELLYFIN_MEDIATYPES_ALLOW: jellyApiDataSchema.shape.allowMediaTypes, + JELLYFIN_MEDIATYPES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(jellyApiDataSchema.shape.allowMediaTypes.meta())), }); export const envSchemas: EnvSourceSchema = { diff --git a/src/backend/common/infrastructure/config/source/mpris.ts b/src/backend/common/infrastructure/config/source/mpris.ts index a3bfb83f..1189518d 100644 --- a/src/backend/common/infrastructure/config/source/mpris.ts +++ b/src/backend/common/infrastructure/config/source/mpris.ts @@ -1,5 +1,6 @@ import * as z from "zod"; import {commonSourceConfigSchema, commonSourceDataSchema, type EnvSourceSchema} from "./index.ts"; +import { envMetaNormalize, transformSplitMaybeString } from "../../../../utils/ZodUtils.ts"; export const PLAYBACK_STATUS_PLAYING = 'Playing'; export const PLAYBACK_STATUS_PAUSED = 'Paused'; @@ -37,7 +38,7 @@ export const mprisDataSchema = z.object({ * @examples [["spotify","vlc"]] * */ blacklist: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "DO NOT scrobble from any players that START WITH these values, case-insensitive", + description: "DO NOT scrobble from any players that START WITH values from this list", examples: [["spotify", "vlc"]] }), @@ -49,7 +50,7 @@ export const mprisDataSchema = z.object({ * @examples [["spotify","vlc"]] * */ whitelist: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "ONLY from any players that START WITH these values, case-insensitive", + description: "ONLY scrobble from any players that START WITH values from this list", examples: [["spotify", "vlc"]] }), }); @@ -57,8 +58,8 @@ export const mprisDataSchema = z.object({ export type MPRISData = z.infer; const envDataSchema = z.object({ - MPRIS_BLACKLIST: mprisDataSchema.shape.blacklist, - MPRIS_WHITELIST: mprisDataSchema.shape.whitelist, + MPRIS_BLACKLIST: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(mprisDataSchema.shape.blacklist.meta())), + MPRIS_WHITELIST: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(mprisDataSchema.shape.whitelist.meta())), }); export const envSchemas: EnvSourceSchema = { diff --git a/src/backend/common/infrastructure/config/source/plex.ts b/src/backend/common/infrastructure/config/source/plex.ts index 3660fd4b..4969836a 100644 --- a/src/backend/common/infrastructure/config/source/plex.ts +++ b/src/backend/common/infrastructure/config/source/plex.ts @@ -1,6 +1,8 @@ import * as z from "zod"; import {pollingOptionsSchema} from "../common.ts"; import {commonSourceConfigSchema, commonSourceDataSchema, commonSourceOptionsSchema, type EnvSourceSchema} from "./index.ts"; +import { envMetaNormalize, transformSplitMaybeString } from "../../../../utils/ZodUtils.ts"; +import { commaSeparatedListReplace } from "../../../../utils/StringUtils.ts"; export const plexApiDataSchema = z.object({ ...commonSourceDataSchema.shape, @@ -14,44 +16,44 @@ export const plexApiDataSchema = z.object({ }), /** - * Only scrobble for specific users (case-insensitive) + * Only scrobble for specific users * * If `true` MS will scrobble activity from all users * */ usersAllow: z.union([z.string(), z.literal(true), z.array(z.string())]).optional().meta({ - description: "Only scrobble for specific users (case-insensitive)" + description: "Only scrobble for specific users from this list" }), /** - * Do not scrobble for these users (case-insensitive) + * Do not scrobble for these users * */ usersBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble for these users (case-insensitive)" + description: "Do not scrobble for users from this list" }), /** - * Only scrobble if device or application name contains strings from this list (case-insensitive) + * Only scrobble if device or application name contains strings from this list * */ devicesAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if device or application name contains strings from this list (case-insensitive)" + description: "Only scrobble if device or application name contains strings from this list" }), /** - * Do not scrobble if device or application name contains strings from this list (case-insensitive) + * Do not scrobble if device or application name contains strings from this list * */ devicesBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if device or application name contains strings from this list (case-insensitive)" + description: "Do not scrobble if device or application name contains strings from this list" }), /** - * Only scrobble if library name contains string from this list (case-insensitive) + * Only scrobble if library name contains string from this list * */ librariesAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if library name contains string from this list (case-insensitive)" + description: "Only scrobble if library name contains string from this list" }), /** - * Do not scrobble if library name contains strings from this list (case-insensitive) + * Do not scrobble if library name contains strings from this list * */ librariesBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if library name contains strings from this list (case-insensitive)" + description: "Do not scrobble if library name contains strings from this list" }), }); @@ -60,16 +62,17 @@ export type PlexApiData = z.infer; const envDataSchema = z.object({ PLEX_URL: plexApiDataSchema.shape.url, PLEX_TOKEN: plexApiDataSchema.shape.token, - PLEX_USERS_ALLOW: plexApiDataSchema.shape.usersAllow, - PLEX_USERS_BLOCK: plexApiDataSchema.shape.usersBlock, - PLEX_DEVICES_ALLOW: plexApiDataSchema.shape.devicesAllow, - PLEX_DEVICES_BLOCK: plexApiDataSchema.shape.devicesBlock, - PLEX_LIBRARIES_ALLOW: plexApiDataSchema.shape.librariesAllow, - PLEX_LIBRARIES_BLOCK: plexApiDataSchema.shape.librariesBlock, + PLEX_USERS_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.usersAllow.meta())), + PLEX_USERS_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.usersBlock.meta())), + PLEX_DEVICES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.devicesBlock.meta())), + PLEX_DEVICES_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.devicesAllow.meta())), + PLEX_LIBRARIES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.librariesAllow.meta())), + PLEX_LIBRARIES_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(plexApiDataSchema.shape.librariesBlock.meta())), }); export const envSchemas: EnvSourceSchema = { env: envDataSchema, + pipe: 'in', prefix: 'PLEX', toConfig: (partial) => ({ data: { diff --git a/src/backend/common/infrastructure/config/source/sonos.ts b/src/backend/common/infrastructure/config/source/sonos.ts index b2548b43..62809260 100644 --- a/src/backend/common/infrastructure/config/source/sonos.ts +++ b/src/backend/common/infrastructure/config/source/sonos.ts @@ -1,6 +1,7 @@ import * as z from "zod"; import {pollingOptionsSchema} from "../common.ts"; import {commonSourceConfigSchema, commonSourceDataSchema, commonSourceOptionsSchema, type EnvSourceSchema} from "./index.ts"; +import { envMetaNormalize, transformSplitMaybeString } from "../../../../utils/ZodUtils.ts"; export const sonosDataSchema = z.object({ ...commonSourceDataSchema.shape, @@ -16,29 +17,29 @@ export const sonosDataSchema = z.object({ }), /** - * Only scrobble if device name contains strings from this list (case-insensitive) + * Only scrobble if device name contains strings from this list * */ devicesAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if device name contains strings from this list (case-insensitive)" + description: "Only scrobble if device name contains strings from this list" }), /** - * Do not scrobble if device name contains strings from this list (case-insensitive) + * Do not scrobble if device name contains strings from this list * */ devicesBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if device name contains strings from this list (case-insensitive)" + description: "Do not scrobble if device name contains strings from this list" }), /** - * Only scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) + * Only scrobble if the name of a group the playing device belongs to contains strings from this list * */ groupsAllow: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Only scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive)" + description: "Only scrobble if the name of a group the playing device belongs to contains strings from this list" }), /** - * Do not scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) + * Do not scrobble if the name of a group the playing device belongs to contains strings from this list * */ groupsBlock: z.union([z.string(), z.array(z.string())]).optional().meta({ - description: "Do not scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive)" + description: "Do not scrobble if the name of a group the playing device belongs to contains strings from this list" }), }); @@ -46,15 +47,16 @@ export type SonosData = z.infer; const envDataSchema = z.object({ SONOS_HOST: sonosDataSchema.shape.host, - SONOS_DEVICES_ALLOW: sonosDataSchema.shape.devicesAllow, - SONOS_DEVICES_BLOCK: sonosDataSchema.shape.devicesBlock, - SONOS_GROUPS_ALLOW: sonosDataSchema.shape.groupsAllow, - SONOS_GROUPS_BLOCK: sonosDataSchema.shape.groupsBlock, + SONOS_DEVICES_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(sonosDataSchema.shape.devicesAllow.meta())), + SONOS_DEVICES_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(sonosDataSchema.shape.devicesBlock.meta())), + SONOS_GROUPS_ALLOW: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(sonosDataSchema.shape.groupsAllow.meta())), + SONOS_GROUPS_BLOCK: z.string().optional().pipe(transformSplitMaybeString).meta(envMetaNormalize(sonosDataSchema.shape.groupsBlock.meta())), }); export const envSchemas: EnvSourceSchema = { env: envDataSchema, prefix: 'SONOS', + pipe: 'in', toConfig: (partial) => ({ data: { host: partial.SONOS_HOST,