diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 98403e6f9d3..e4d3751f661 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -4,6 +4,7 @@ import logging from aiohttp import web_response import plexapi.exceptions +from plexapi.gdm import GDM from plexauth import PlexAuth import requests.exceptions import voluptuous as vol @@ -62,6 +63,18 @@ def configured_servers(hass): } +async def async_discover(hass): + """Scan for available Plex servers.""" + gdm = GDM() + await hass.async_add_executor_job(gdm.scan) + for server_data in gdm.entries: + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data=server_data, + ) + + class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Plex config flow.""" @@ -266,6 +279,19 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.debug("Imported Plex configuration") return await self.async_step_server_validate(import_config) + async def async_step_integration_discovery(self, discovery_info): + """Handle GDM discovery.""" + machine_identifier = discovery_info["data"]["Resource-Identifier"] + await self.async_set_unique_id(machine_identifier) + self._abort_if_unique_id_configured() + host = f"{discovery_info['from'][0]}:{discovery_info['data']['Port']}" + name = discovery_info["data"]["Name"] + self.context["title_placeholders"] = { # pylint: disable=no-member + "host": host, + "name": name, + } + return await self.async_step_user() + async def async_step_plex_website_auth(self): """Begin external auth flow on Plex website.""" self.hass.http.register_view(PlexAuthorizationCallbackView) diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 1a2baf50bea..09dbc9a8ecf 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "{name} ({host})", "step": { "user": { "title": "Plex Media Server", diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index ffaeaa1330c..6dc259d6515 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -22,6 +22,7 @@ _UNDEF: dict = {} SOURCE_DISCOVERY = "discovery" SOURCE_IMPORT = "import" +SOURCE_INTEGRATION_DISCOVERY = "integration_discovery" SOURCE_SSDP = "ssdp" SOURCE_USER = "user" SOURCE_ZEROCONF = "zeroconf" diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index b06e5651601..ec1b490ddf5 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -8,6 +8,32 @@ from homeassistant.const import CONF_URL from .const import DEFAULT_DATA, MOCK_SERVERS, MOCK_USERS +GDM_PAYLOAD = [ + { + "data": { + "Content-Type": "plex/media-server", + "Name": "plextest", + "Port": "32400", + "Resource-Identifier": "1234567890123456789012345678901234567890", + "Updated-At": "157762684800", + "Version": "1.0", + }, + "from": ("1.2.3.4", 32414), + } +] + + +class MockGDM: + """Mock a GDM instance.""" + + def __init__(self): + """Initialize the object.""" + self.entries = GDM_PAYLOAD + + def scan(self): + """Mock the scan call.""" + pass + class MockResource: """Mock a PlexAccount resource.""" diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 878b13bccc9..b43448b4d97 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -22,7 +22,10 @@ from homeassistant.components.plex.const import ( SERVERS, ) from homeassistant.config import async_process_ha_core_config -from homeassistant.config_entries import ENTRY_STATE_LOADED +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + SOURCE_INTEGRATION_DISCOVERY, +) from homeassistant.const import ( CONF_HOST, CONF_PORT, @@ -34,7 +37,7 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN -from .mock_classes import MockPlexAccount, MockPlexServer +from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer from tests.async_mock import patch from tests.common import MockConfigEntry @@ -746,3 +749,25 @@ async def test_setup_with_limited_credentials(hass): assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state == ENTRY_STATE_LOADED + + +async def test_integration_discovery(hass): + """Test integration self-discovery.""" + mock_gdm = MockGDM() + + with patch("homeassistant.components.plex.config_flow.GDM", return_value=mock_gdm): + await config_flow.async_discover(hass) + + flows = hass.config_entries.flow.async_progress() + + assert len(flows) == 1 + + flow = flows[0] + + assert flow["handler"] == DOMAIN + assert flow["context"]["source"] == SOURCE_INTEGRATION_DISCOVERY + assert ( + flow["context"]["unique_id"] + == mock_gdm.entries[0]["data"]["Resource-Identifier"] + ) + assert flow["step_id"] == "user"