diff --git a/homeassistant/components/ntfy/__init__.py b/homeassistant/components/ntfy/__init__.py index ccaf50ebef1..e2edc3354f3 100644 --- a/homeassistant/components/ntfy/__init__.py +++ b/homeassistant/components/ntfy/__init__.py @@ -15,13 +15,24 @@ from aiontfy.exceptions import ( from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .coordinator import NtfyConfigEntry, NtfyDataUpdateCoordinator +from .services import async_setup_services _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [Platform.EVENT, Platform.NOTIFY, Platform.SENSOR] +CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the ntfy services.""" + + async_setup_services(hass) + return True async def async_setup_entry(hass: HomeAssistant, entry: NtfyConfigEntry) -> bool: diff --git a/homeassistant/components/ntfy/notify.py b/homeassistant/components/ntfy/notify.py index 7f78206302e..cc3faba454a 100644 --- a/homeassistant/components/ntfy/notify.py +++ b/homeassistant/components/ntfy/notify.py @@ -12,97 +12,28 @@ from aiontfy.exceptions import ( NtfyHTTPError, NtfyUnauthorizedAuthenticationError, ) -import voluptuous as vol -from yarl import URL from homeassistant.components import camera, image from homeassistant.components.media_source import async_resolve_media from homeassistant.components.notify import ( - ATTR_MESSAGE, - ATTR_TITLE, NotifyEntity, NotifyEntityDescription, NotifyEntityFeature, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, ServiceValidationError -from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from homeassistant.helpers.selector import MediaSelector from .const import DOMAIN from .coordinator import NtfyConfigEntry from .entity import NtfyBaseEntity +from .services import ATTR_ATTACH_FILE, ATTR_FILENAME, ATTR_SEQUENCE_ID _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 0 -SERVICE_PUBLISH = "publish" -SERVICE_CLEAR = "clear" -SERVICE_DELETE = "delete" -ATTR_ATTACH = "attach" -ATTR_CALL = "call" -ATTR_CLICK = "click" -ATTR_DELAY = "delay" -ATTR_EMAIL = "email" -ATTR_ICON = "icon" -ATTR_MARKDOWN = "markdown" -ATTR_PRIORITY = "priority" -ATTR_TAGS = "tags" -ATTR_SEQUENCE_ID = "sequence_id" -ATTR_ATTACH_FILE = "attach_file" -ATTR_FILENAME = "filename" -GRP_ATTACHMENT = "attachment" -MSG_ATTACHMENT = "Only one attachment source is allowed: URL or local file" - - -def validate_filename(params: dict[str, Any]) -> dict[str, Any]: - """Validate filename.""" - if ATTR_FILENAME in params and not ( - ATTR_ATTACH_FILE in params or ATTR_ATTACH in params - ): - raise vol.Invalid("Filename only allowed when attachment is provided") - return params - - -SERVICE_PUBLISH_SCHEMA = vol.All( - cv.make_entity_service_schema( - { - vol.Optional(ATTR_TITLE): cv.string, - vol.Optional(ATTR_MESSAGE): cv.string, - vol.Optional(ATTR_MARKDOWN): cv.boolean, - vol.Optional(ATTR_TAGS): vol.All(cv.ensure_list, [str]), - vol.Optional(ATTR_PRIORITY): vol.All(vol.Coerce(int), vol.Range(1, 5)), - vol.Optional(ATTR_CLICK): vol.All(vol.Url(), vol.Coerce(URL)), - vol.Optional(ATTR_DELAY): vol.All( - cv.time_period, - vol.Range(min=timedelta(seconds=10), max=timedelta(days=3)), - ), - vol.Optional(ATTR_EMAIL): vol.Email(), - vol.Optional(ATTR_CALL): cv.string, - vol.Optional(ATTR_ICON): vol.All(vol.Url(), vol.Coerce(URL)), - vol.Optional(ATTR_SEQUENCE_ID): cv.string, - vol.Exclusive(ATTR_ATTACH, GRP_ATTACHMENT, MSG_ATTACHMENT): vol.All( - vol.Url(), vol.Coerce(URL) - ), - vol.Exclusive( - ATTR_ATTACH_FILE, GRP_ATTACHMENT, MSG_ATTACHMENT - ): MediaSelector({"accept": ["*/*"]}), - vol.Optional(ATTR_FILENAME): cv.string, - } - ), - validate_filename, -) - -SERVICE_CLEAR_DELETE_SCHEMA = cv.make_entity_service_schema( - { - vol.Required(ATTR_SEQUENCE_ID): cv.string, - } -) - - async def async_setup_entry( hass: HomeAssistant, config_entry: NtfyConfigEntry, @@ -115,29 +46,6 @@ async def async_setup_entry( [NtfyNotifyEntity(config_entry, subentry)], config_subentry_id=subentry_id ) - platform = entity_platform.async_get_current_platform() - platform.async_register_entity_service( - SERVICE_PUBLISH, - SERVICE_PUBLISH_SCHEMA, - "publish", - description_placeholders={ - "markdown_guide_url": "https://www.markdownguide.org/basic-syntax/", - "emoji_reference_url": "https://docs.ntfy.sh/emojis/", - }, - ) - - platform.async_register_entity_service( - SERVICE_CLEAR, - SERVICE_CLEAR_DELETE_SCHEMA, - "clear", - ) - - platform.async_register_entity_service( - SERVICE_DELETE, - SERVICE_CLEAR_DELETE_SCHEMA, - "delete", - ) - class NtfyNotifyEntity(NtfyBaseEntity, NotifyEntity): """Representation of a ntfy notification entity.""" diff --git a/homeassistant/components/ntfy/services.py b/homeassistant/components/ntfy/services.py new file mode 100644 index 00000000000..c3619f5f0b7 --- /dev/null +++ b/homeassistant/components/ntfy/services.py @@ -0,0 +1,116 @@ +"""Service registration for ntfy integration.""" + +from datetime import timedelta +from typing import Any + +import voluptuous as vol +from yarl import URL + +from homeassistant.components.notify import ( + ATTR_MESSAGE, + ATTR_TITLE, + DOMAIN as NOTIFY_DOMAIN, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import config_validation as cv, service +from homeassistant.helpers.selector import MediaSelector + +from .const import DOMAIN + +SERVICE_PUBLISH = "publish" +SERVICE_CLEAR = "clear" +SERVICE_DELETE = "delete" +ATTR_ATTACH = "attach" +ATTR_CALL = "call" +ATTR_CLICK = "click" +ATTR_DELAY = "delay" +ATTR_EMAIL = "email" +ATTR_ICON = "icon" +ATTR_MARKDOWN = "markdown" +ATTR_PRIORITY = "priority" +ATTR_TAGS = "tags" +ATTR_SEQUENCE_ID = "sequence_id" +ATTR_ATTACH_FILE = "attach_file" +ATTR_FILENAME = "filename" +GRP_ATTACHMENT = "attachment" +MSG_ATTACHMENT = "Only one attachment source is allowed: URL or local file" + + +def validate_filename(params: dict[str, Any]) -> dict[str, Any]: + """Validate filename.""" + if ATTR_FILENAME in params and not ( + ATTR_ATTACH_FILE in params or ATTR_ATTACH in params + ): + raise vol.Invalid("Filename only allowed when attachment is provided") + return params + + +SERVICE_PUBLISH_SCHEMA = vol.All( + cv.make_entity_service_schema( + { + vol.Optional(ATTR_TITLE): cv.string, + vol.Optional(ATTR_MESSAGE): cv.string, + vol.Optional(ATTR_MARKDOWN): cv.boolean, + vol.Optional(ATTR_TAGS): vol.All(cv.ensure_list, [str]), + vol.Optional(ATTR_PRIORITY): vol.All(vol.Coerce(int), vol.Range(1, 5)), + vol.Optional(ATTR_CLICK): vol.All(vol.Url(), vol.Coerce(URL)), + vol.Optional(ATTR_DELAY): vol.All( + cv.time_period, + vol.Range(min=timedelta(seconds=10), max=timedelta(days=3)), + ), + vol.Optional(ATTR_EMAIL): vol.Email(), + vol.Optional(ATTR_CALL): cv.string, + vol.Optional(ATTR_ICON): vol.All(vol.Url(), vol.Coerce(URL)), + vol.Optional(ATTR_SEQUENCE_ID): cv.string, + vol.Exclusive(ATTR_ATTACH, GRP_ATTACHMENT, MSG_ATTACHMENT): vol.All( + vol.Url(), vol.Coerce(URL) + ), + vol.Exclusive( + ATTR_ATTACH_FILE, GRP_ATTACHMENT, MSG_ATTACHMENT + ): MediaSelector({"accept": ["*/*"]}), + vol.Optional(ATTR_FILENAME): cv.string, + } + ), + validate_filename, +) + +SERVICE_CLEAR_DELETE_SCHEMA = cv.make_entity_service_schema( + { + vol.Required(ATTR_SEQUENCE_ID): cv.string, + } +) + + +@callback +def async_setup_services(hass: HomeAssistant) -> None: + """Set up services for ntfy integration.""" + + service.async_register_platform_entity_service( + hass, + DOMAIN, + SERVICE_PUBLISH, + entity_domain=NOTIFY_DOMAIN, + schema=SERVICE_PUBLISH_SCHEMA, + description_placeholders={ + "markdown_guide_url": "https://www.markdownguide.org/basic-syntax/", + "emoji_reference_url": "https://docs.ntfy.sh/emojis/", + }, + func="publish", + ) + service.async_register_platform_entity_service( + hass, + DOMAIN, + SERVICE_CLEAR, + entity_domain=NOTIFY_DOMAIN, + schema=SERVICE_CLEAR_DELETE_SCHEMA, + func="clear", + ) + + service.async_register_platform_entity_service( + hass, + DOMAIN, + SERVICE_DELETE, + entity_domain=NOTIFY_DOMAIN, + schema=SERVICE_CLEAR_DELETE_SCHEMA, + func="delete", + ) diff --git a/tests/components/ntfy/test_services.py b/tests/components/ntfy/test_services.py index 0e1ca6fc029..941e5af05b2 100644 --- a/tests/components/ntfy/test_services.py +++ b/tests/components/ntfy/test_services.py @@ -15,7 +15,7 @@ from yarl import URL from homeassistant.components import camera, image, media_source from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TITLE from homeassistant.components.ntfy.const import DOMAIN -from homeassistant.components.ntfy.notify import ( +from homeassistant.components.ntfy.services import ( ATTR_ATTACH, ATTR_ATTACH_FILE, ATTR_CALL,