"""Config flow for Discord integration.""" from __future__ import annotations import logging from aiohttp.client_exceptions import ClientConnectorError import nextcord import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_TOKEN, CONF_NAME, CONF_TOKEN from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, URL_PLACEHOLDER _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({vol.Required(CONF_API_TOKEN): str}) class DiscordFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Discord.""" async def async_step_reauth(self, user_input: dict | None = None) -> FlowResult: """Handle a reauthorization flow request.""" if user_input is not None: return await self.async_step_reauth_confirm() self._set_confirm_only() return self.async_show_form(step_id="reauth") async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None ) -> FlowResult: """Confirm reauth dialog.""" errors = {} if user_input: error, info = await _async_try_connect(user_input[CONF_API_TOKEN]) if info and (entry := await self.async_set_unique_id(str(info.id))): self.hass.config_entries.async_update_entry( entry, data=entry.data | user_input ) await self.hass.config_entries.async_reload(entry.entry_id) return self.async_abort(reason="reauth_successful") if error: errors["base"] = error user_input = user_input or {} return self.async_show_form( step_id="reauth_confirm", data_schema=CONFIG_SCHEMA, description_placeholders=URL_PLACEHOLDER, errors=errors, ) async def async_step_user( self, user_input: dict[str, str] | None = None ) -> FlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: error, info = await _async_try_connect(user_input[CONF_API_TOKEN]) if error is not None: errors["base"] = error elif info is not None: await self.async_set_unique_id(str(info.id)) self._abort_if_unique_id_configured() return self.async_create_entry( title=info.name, data=user_input | {CONF_NAME: user_input.get(CONF_NAME, info.name)}, ) user_input = user_input or {} return self.async_show_form( step_id="user", data_schema=CONFIG_SCHEMA, description_placeholders=URL_PLACEHOLDER, errors=errors, ) async def async_step_import(self, import_config: dict[str, str]) -> FlowResult: """Import a config entry from configuration.yaml.""" _LOGGER.warning( "Configuration of the Discord integration in YAML is deprecated and " "will be removed in Home Assistant 2022.6; Your existing configuration " "has been imported into the UI automatically and can be safely removed " "from your configuration.yaml file" ) for entry in self._async_current_entries(): if entry.data[CONF_API_TOKEN] == import_config[CONF_TOKEN]: return self.async_abort(reason="already_configured") import_config[CONF_API_TOKEN] = import_config.pop(CONF_TOKEN) return await self.async_step_user(import_config) async def _async_try_connect(token: str) -> tuple[str | None, nextcord.AppInfo | None]: """Try connecting to Discord.""" discord_bot = nextcord.Client() try: await discord_bot.login(token) info = await discord_bot.application_info() except nextcord.LoginFailure: return "invalid_auth", None except (ClientConnectorError, nextcord.HTTPException, nextcord.NotFound): return "cannot_connect", None except Exception as ex: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception: %s", ex) return "unknown", None await discord_bot.close() return None, info