mirror of https://github.com/home-assistant/core
119 lines
3.7 KiB
Python
119 lines
3.7 KiB
Python
"""Support for iOS push notifications."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from http import HTTPStatus
|
|
import logging
|
|
from typing import Any
|
|
|
|
import requests
|
|
|
|
from homeassistant.components.notify import (
|
|
ATTR_DATA,
|
|
ATTR_MESSAGE,
|
|
ATTR_TARGET,
|
|
ATTR_TITLE,
|
|
ATTR_TITLE_DEFAULT,
|
|
BaseNotificationService,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from . import device_name_for_push_id, devices_with_push, enabled_push_ids
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
PUSH_URL = "https://ios-push.home-assistant.io/push"
|
|
|
|
|
|
def log_rate_limits(
|
|
hass: HomeAssistant, target: str, resp: dict[str, Any], level: int = 20
|
|
) -> None:
|
|
"""Output rate limit log line at given level."""
|
|
rate_limits = resp["rateLimits"]
|
|
resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"])
|
|
resetsAtTime = resetsAt - dt_util.utcnow() if resetsAt is not None else "---"
|
|
rate_limit_msg = (
|
|
"iOS push notification rate limits for %s: "
|
|
"%d sent, %d allowed, %d errors, "
|
|
"resets in %s"
|
|
)
|
|
_LOGGER.log(
|
|
level,
|
|
rate_limit_msg,
|
|
device_name_for_push_id(hass, target),
|
|
rate_limits["successful"],
|
|
rate_limits["maximum"],
|
|
rate_limits["errors"],
|
|
str(resetsAtTime).split(".", maxsplit=1)[0],
|
|
)
|
|
|
|
|
|
def get_service(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> iOSNotificationService | None:
|
|
"""Get the iOS notification service."""
|
|
if "ios.notify" not in hass.config.components:
|
|
# Need this to enable requirements checking in the app.
|
|
hass.config.components.add("ios.notify")
|
|
|
|
if not devices_with_push(hass):
|
|
return None
|
|
|
|
return iOSNotificationService()
|
|
|
|
|
|
class iOSNotificationService(BaseNotificationService):
|
|
"""Implement the notification service for iOS."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize the service."""
|
|
|
|
@property
|
|
def targets(self) -> dict[str, str]:
|
|
"""Return a dictionary of registered targets."""
|
|
return devices_with_push(self.hass)
|
|
|
|
def send_message(self, message: str = "", **kwargs: Any) -> None:
|
|
"""Send a message to the Lambda APNS gateway."""
|
|
data: dict[str, Any] = {ATTR_MESSAGE: message}
|
|
|
|
# Remove default title from notifications.
|
|
if (
|
|
kwargs.get(ATTR_TITLE) is not None
|
|
and kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT
|
|
):
|
|
data[ATTR_TITLE] = kwargs.get(ATTR_TITLE)
|
|
|
|
if not (targets := kwargs.get(ATTR_TARGET)):
|
|
targets = enabled_push_ids(self.hass)
|
|
|
|
if kwargs.get(ATTR_DATA) is not None:
|
|
data[ATTR_DATA] = kwargs.get(ATTR_DATA)
|
|
|
|
for target in targets:
|
|
if target not in enabled_push_ids(self.hass):
|
|
_LOGGER.error("The target (%s) does not exist in .ios.conf", targets)
|
|
return
|
|
|
|
data[ATTR_TARGET] = target
|
|
|
|
req = requests.post(PUSH_URL, json=data, timeout=10)
|
|
|
|
if req.status_code != HTTPStatus.CREATED:
|
|
fallback_error = req.json().get("errorMessage", "Unknown error")
|
|
fallback_message = (
|
|
f"Internal server error, please try again later: {fallback_error}"
|
|
)
|
|
message = req.json().get("message", fallback_message)
|
|
if req.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
|
_LOGGER.warning(message)
|
|
log_rate_limits(self.hass, target, req.json(), 30)
|
|
else:
|
|
_LOGGER.error(message)
|
|
else:
|
|
log_rate_limits(self.hass, target, req.json())
|