diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 78d1677f338..6cf93464707 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -30,6 +30,7 @@ from .const import ( # pylint: disable=unused-import AUTOMATIC_SETUP_STRING, CONF_CLIENT_IDENTIFIER, CONF_IGNORE_NEW_SHARED_USERS, + CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, CONF_SERVER, CONF_SERVER_IDENTIFIER, @@ -329,6 +330,9 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow): self.options[MP_DOMAIN][CONF_IGNORE_NEW_SHARED_USERS] = user_input[ CONF_IGNORE_NEW_SHARED_USERS ] + self.options[MP_DOMAIN][CONF_IGNORE_PLEX_WEB_CLIENTS] = user_input[ + CONF_IGNORE_PLEX_WEB_CLIENTS + ] account_data = { user: {"enabled": bool(user in user_input[CONF_MONITORED_USERS])} @@ -373,6 +377,10 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow): CONF_IGNORE_NEW_SHARED_USERS, default=plex_server.option_ignore_new_shared_users, ): bool, + vol.Required( + CONF_IGNORE_PLEX_WEB_CLIENTS, + default=plex_server.option_ignore_plexweb_clients, + ): bool, } ), ) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 416c994d2be..e8bcfb42ca6 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -30,6 +30,7 @@ CONF_SERVER_IDENTIFIER = "server_id" CONF_USE_EPISODE_ART = "use_episode_art" CONF_SHOW_ALL_CONTROLS = "show_all_controls" CONF_IGNORE_NEW_SHARED_USERS = "ignore_new_shared_users" +CONF_IGNORE_PLEX_WEB_CLIENTS = "ignore_plex_web_clients" CONF_MONITORED_USERS = "monitored_users" AUTH_CALLBACK_PATH = "/auth/plex/callback" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 66f02881b78..a1f5af321f3 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -19,6 +19,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( CONF_CLIENT_IDENTIFIER, CONF_IGNORE_NEW_SHARED_USERS, + CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, CONF_SERVER, CONF_USE_EPISODE_ART, @@ -50,6 +51,7 @@ class PlexServer: """Initialize a Plex server instance.""" self.hass = hass self._plex_server = None + self._created_clients = set() self._known_clients = set() self._known_idle = set() self._url = server_config.get(CONF_URL) @@ -217,7 +219,23 @@ class PlexServer: self._known_idle.discard(device.machineIdentifier) available_clients.setdefault(device.machineIdentifier, {"device": device}) - if device.machineIdentifier not in self._known_clients: + if device.machineIdentifier not in ignored_clients: + if self.option_ignore_plexweb_clients and device.product == "Plex Web": + ignored_clients.add(device.machineIdentifier) + if device.machineIdentifier not in self._known_clients: + _LOGGER.debug( + "Ignoring %s %s: %s", + "Plex Web", + source, + device.machineIdentifier, + ) + return + + if ( + device.machineIdentifier not in self._created_clients + and device.machineIdentifier not in ignored_clients + and device.machineIdentifier not in new_clients + ): new_clients.add(device.machineIdentifier) _LOGGER.debug( "New %s %s: %s", device.product, source, device.machineIdentifier @@ -250,6 +268,7 @@ class PlexServer: continue if client_id in new_clients: new_entity_configs.append(client_data) + self._created_clients.add(client_id) else: self.async_refresh_entity( client_id, client_data["device"], client_data.get("session") @@ -327,6 +346,11 @@ class PlexServer: """Return dict of monitored users option.""" return self.options[MP_DOMAIN].get(CONF_MONITORED_USERS, {}) + @property + def option_ignore_plexweb_clients(self): + """Return ignore_plex_web_clients option.""" + return self.options[MP_DOMAIN].get(CONF_IGNORE_PLEX_WEB_CLIENTS, False) + @property def library(self): """Return library attribute from server object.""" diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 71e3db0fdbb..761f8e2551b 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -51,7 +51,8 @@ "data": { "use_episode_art": "Use episode art", "ignore_new_shared_users": "Ignore new managed/shared users", - "monitored_users": "Monitored users" + "monitored_users": "Monitored users", + "ignore_plex_web_clients": "Ignore Plex Web clients" } } } diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 9b59190173f..b06e5651601 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -134,6 +134,7 @@ class MockPlexClient: """Initialize the object.""" self.machineIdentifier = f"client-{index+1}" self._baseurl = url + self._index = index def url(self, key): """Mock the url method.""" @@ -152,6 +153,8 @@ class MockPlexClient: @property def product(self): """Mock the product attribute.""" + if self._index == 1: + return "Plex Web" return "PRODUCT" @property diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 78f2af55183..878b13bccc9 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -10,6 +10,7 @@ from homeassistant.components.plex import config_flow from homeassistant.components.plex.const import ( AUTOMATIC_SETUP_STRING, CONF_IGNORE_NEW_SHARED_USERS, + CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, CONF_SERVER, CONF_SERVER_IDENTIFIER, @@ -428,6 +429,7 @@ async def test_option_flow(hass): CONF_MONITORED_USERS: { user: {"enabled": True} for user in mock_plex_server.accounts }, + CONF_IGNORE_PLEX_WEB_CLIENTS: False, } } diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index 7cb34b4fcca..694fcc4885e 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -4,6 +4,7 @@ import copy from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.plex.const import ( CONF_IGNORE_NEW_SHARED_USERS, + CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, DOMAIN, PLEX_UPDATE_PLATFORMS_SIGNAL, @@ -94,7 +95,7 @@ async def test_new_ignored_users_available(hass, caplog): ignored_client = [ x.players[0] for x in mock_plex_server.sessions() - if x.usernames[0] in ignored_users + if x.usernames[0] == ignored_user ][0] assert ( f"Ignoring {ignored_client.product} client owned by '{ignored_user}'" @@ -208,3 +209,38 @@ async def test_new_ignored_users_available(hass, caplog): # await self.advance(DEBOUNCE_TIMEOUT) # await hass.async_block_till_done() # assert mock_update.call_count == 3 + + +async def test_ignore_plex_web_client(hass): + """Test option to ignore Plex Web clients.""" + + OPTIONS = copy.deepcopy(DEFAULT_OPTIONS) + OPTIONS[MP_DOMAIN][CONF_IGNORE_PLEX_WEB_CLIENTS] = True + + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + mock_plex_server = MockPlexServer(config_entry=entry) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(mock_plex_server.accounts)) + + media_players = hass.states.async_entity_ids("media_player") + + assert len(media_players) == int(sensor.state) - 1