diff --git a/config/sonos.json.example b/config/sonos.json.example index 2dd62eae..1eab0cc5 100644 --- a/config/sonos.json.example +++ b/config/sonos.json.example @@ -3,7 +3,11 @@ "enable": true, "name": "MySonos", "data": { - "host": "192.168.0.150" + "host": "192.168.0.150", + "devicesAllow": ["move","beam"], + "devicesBlock": ["roam"], + "groupsAllow": ["family"], + "groupsBlock": ["bedroom"] } } ] diff --git a/docsite/docs/configuration/sources/sonos.mdx b/docsite/docs/configuration/sources/sonos.mdx index 2f48a63b..81d96d5e 100644 --- a/docsite/docs/configuration/sources/sonos.mdx +++ b/docsite/docs/configuration/sources/sonos.mdx @@ -81,12 +81,20 @@ Please include these additional logs when creating a bug report. They are essent +### Filtering Activity + +The **optional** `devices` and `groups` related properties found in the config below can be used to include or exclude Sonos devices/groups by name, case-insensitive. + ## Configuration You **must** define the IP address of at least one Sonos/accessory in order for Multi-scrobbler to connect to your Sonos system. | Environmental Variable | Required? | Default | Description | - | ---------------------- | --------- | ------- | ---------------------------------------------------------------------- | + | ---------------------- | --------- | ------- | :--------------------------------------------------------------------- | | `SONOS_HOST` | Yes | | The IP address of any active Sonos device/accessory EX `192.168.0.150` | + | `SONOS_DEVICES_ALLOW` | No | | Comma-separated list of Sonos device names to scrobble from | + | `SONOS_DEVICES_BLOCK` | No | | Comma-separated list of Sonos device names to disallow scrobbles from | + | `SONOS_GROUPS_ALLOW` | No | | Comma-separated list of device group names to scrobble from | + | `SONOS_GROUPS_BLOCK` | No | | Comma-separated list of devices group names to disallow scrobbles from | \ No newline at end of file diff --git a/src/backend/common/infrastructure/config/source/sonos.ts b/src/backend/common/infrastructure/config/source/sonos.ts index d76d6b52..c2a5575c 100644 --- a/src/backend/common/infrastructure/config/source/sonos.ts +++ b/src/backend/common/infrastructure/config/source/sonos.ts @@ -8,6 +8,24 @@ export interface SonosData extends CommonSourceData, PollingOptions { * @examples ["192.168.0.170"] * */ host: string + + /** + * Only scrobble if device name contains strings from this list (case-insensitive) + * */ + devicesAllow?: string | string[] + /** + * Do not scrobble if device name contains strings from this list (case-insensitive) + * */ + devicesBlock?: string | string[] + + /** + * Only scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) + * */ + groupsAllow?: string | string[] + /** + * Do not scrobble if the name of a group the playing device belongs to contains strings from this list (case-insensitive) + * */ + groupsBlock?: string | string[] } export interface SonosSourceConfig extends CommonSourceConfig { data: SonosData diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 6e4ccd59..abfca60a 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -798,6 +798,10 @@ export default class ScrobbleSources { case 'sonos': { const sonos = { host: process.env.SONOS_HOST, + devicesAllow: process.env.SONOS_DEVICES_ALLOW, + devicesBlock: process.env.SONOS_DEVICES_BLOCK, + groupsAllow: process.env.SONOS_GROUPS_ALLOW, + groupsBlocks: process.env.SONOS_GROUPS_BLOCK } if (!Object.values(sonos).every(x => x === undefined)) { configs.push({ diff --git a/src/backend/sources/SonosSource.ts b/src/backend/sources/SonosSource.ts index 87354b74..9fec8ab2 100644 --- a/src/backend/sources/SonosSource.ts +++ b/src/backend/sources/SonosSource.ts @@ -19,7 +19,7 @@ import { GroupTransportState } from "@svrooij/sonos/lib/models/transport-state.j import { Track } from "@svrooij/sonos/lib/models/track.js"; import { parseDurationFromTimestamp } from "../utils/TimeUtils.js"; import { FixedSizeList } from "fixed-size-list"; -import { buildStatePlayerPlayIdententifyingInfo } from "../utils/StringUtils.js"; +import { buildStatePlayerPlayIdententifyingInfo, parseArrayFromMaybeString } from "../utils/StringUtils.js"; import { isDebugMode } from "../utils.js"; export interface UniquePlay { @@ -46,6 +46,11 @@ export class SonosSource extends MemoryPositionalSource { uniqueDropReasons: FixedSizeList; logFilterFailure: false | 'debug' | 'warn'; + devicesAllow: string[] = []; + devicesBlock: string[] = []; + groupsAllow: string[] = []; + groupsBlock: string[] = []; + constructor(name: any, config: SonosSourceConfig, internal: InternalConfig, emitter: EventEmitter) { const { data, @@ -67,6 +72,12 @@ export class SonosSource extends MemoryPositionalSource { const { options: { logFilterFailure = (isDebugMode() ? 'debug' : 'warn'), + } = {}, + data: { + devicesAllow = [], + devicesBlock = [], + groupsAllow = [], + groupsBlock = [] } = {} } = this.config; if (logFilterFailure !== false && !['debug', 'warn'].includes(logFilterFailure)) { @@ -75,6 +86,11 @@ export class SonosSource extends MemoryPositionalSource { this.logFilterFailure = logFilterFailure; } + this.devicesAllow = parseArrayFromMaybeString(devicesAllow, {lower: true}); + this.devicesBlock = parseArrayFromMaybeString(devicesBlock, {lower: true}); + this.groupsAllow = parseArrayFromMaybeString(groupsAllow, {lower: true}); + this.groupsBlock = parseArrayFromMaybeString(groupsBlock, {lower: true}); + return true; } @@ -95,6 +111,19 @@ export class SonosSource extends MemoryPositionalSource { if (!data.state.positionInfo.TrackMetaData.UpnpClass.toLocaleLowerCase().includes('musictrack')) { return `UpnpClass does include 'musictrack', found '${data.state.positionInfo.TrackMetaData.UpnpClass}'`; } + + if(this.devicesAllow.length > 0 && !this.devicesAllow.some(x => data.device.Name.toLocaleLowerCase().includes(x))) { + return `'devicesAllow does not include a phrase found in ${data.device.Name}`; + } + if(this.devicesBlock.length > 0 && this.devicesBlock.some(x => data.device.Name.toLocaleLowerCase().includes(x))) { + return `'devicesBlock includes a phrase found in ${data.device.Name}`; + } + if(this.groupsAllow.length > 0 && !this.groupsAllow.some(x => data.device.GroupName.toLocaleLowerCase().includes(x))) { + return `'groupsAllow does not include a phrase found in ${data.device.GroupName}`; + } + if(this.groupsBlock.length > 0 && this.groupsBlock.some(x => data.device.GroupName.toLocaleLowerCase().includes(x))) { + return `'groupsBlock includes a phrase found in ${data.device.GroupName}`; + } return; }