mirror of
https://github.com/home-assistant/core.git
synced 2025-05-05 06:29:16 +00:00

* Change datetime.now() to dt_util.now() in cases where the functionality should stay the same These changes should not affect the functionality, rather cleanup our codebase. In general we would like integrations to not to use datetime.now() unless there's a very good reason for it, rather use our own dt_util.now() which makes the code aware of our current time zone. * Use datetime.utcnow() for season sensor to get offset-naive utc time * Revert "Use datetime.utcnow() for season sensor to get offset-naive utc time" This reverts commit 5f36463d9c7d52f8e11ffcec7e57dfbc7b21bdd1. * BOM sensor last_updated should be UTC as well * Run black * Remove unused last_partition_update variable
157 lines
5.0 KiB
Python
157 lines
5.0 KiB
Python
"""Support for mobile_app push notifications."""
|
|
import asyncio
|
|
import logging
|
|
|
|
import async_timeout
|
|
|
|
from homeassistant.components.notify import (
|
|
ATTR_DATA,
|
|
ATTR_MESSAGE,
|
|
ATTR_TARGET,
|
|
ATTR_TITLE,
|
|
ATTR_TITLE_DEFAULT,
|
|
BaseNotificationService,
|
|
)
|
|
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from .const import (
|
|
ATTR_APP_DATA,
|
|
ATTR_APP_ID,
|
|
ATTR_APP_VERSION,
|
|
ATTR_DEVICE_NAME,
|
|
ATTR_OS_VERSION,
|
|
ATTR_PUSH_RATE_LIMITS,
|
|
ATTR_PUSH_RATE_LIMITS_ERRORS,
|
|
ATTR_PUSH_RATE_LIMITS_MAXIMUM,
|
|
ATTR_PUSH_RATE_LIMITS_RESETS_AT,
|
|
ATTR_PUSH_RATE_LIMITS_SUCCESSFUL,
|
|
ATTR_PUSH_TOKEN,
|
|
ATTR_PUSH_URL,
|
|
DATA_CONFIG_ENTRIES,
|
|
DOMAIN,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def push_registrations(hass):
|
|
"""Return a dictionary of push enabled registrations."""
|
|
targets = {}
|
|
for webhook_id, entry in hass.data[DOMAIN][DATA_CONFIG_ENTRIES].items():
|
|
data = entry.data
|
|
app_data = data[ATTR_APP_DATA]
|
|
if ATTR_PUSH_TOKEN in app_data and ATTR_PUSH_URL in app_data:
|
|
device_name = data[ATTR_DEVICE_NAME]
|
|
if device_name in targets:
|
|
_LOGGER.warning("Found duplicate device name %s", device_name)
|
|
continue
|
|
targets[device_name] = webhook_id
|
|
return targets
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
def log_rate_limits(hass, device_name, resp, level=logging.INFO):
|
|
"""Output rate limit log line at given level."""
|
|
if ATTR_PUSH_RATE_LIMITS not in resp:
|
|
return
|
|
|
|
rate_limits = resp[ATTR_PUSH_RATE_LIMITS]
|
|
resetsAt = rate_limits[ATTR_PUSH_RATE_LIMITS_RESETS_AT]
|
|
resetsAtTime = dt_util.parse_datetime(resetsAt) - dt_util.utcnow()
|
|
rate_limit_msg = (
|
|
"mobile_app push notification rate limits for %s: "
|
|
"%d sent, %d allowed, %d errors, "
|
|
"resets in %s"
|
|
)
|
|
_LOGGER.log(
|
|
level,
|
|
rate_limit_msg,
|
|
device_name,
|
|
rate_limits[ATTR_PUSH_RATE_LIMITS_SUCCESSFUL],
|
|
rate_limits[ATTR_PUSH_RATE_LIMITS_MAXIMUM],
|
|
rate_limits[ATTR_PUSH_RATE_LIMITS_ERRORS],
|
|
str(resetsAtTime).split(".")[0],
|
|
)
|
|
|
|
|
|
async def async_get_service(hass, config, discovery_info=None):
|
|
"""Get the mobile_app notification service."""
|
|
session = async_get_clientsession(hass)
|
|
return MobileAppNotificationService(session)
|
|
|
|
|
|
class MobileAppNotificationService(BaseNotificationService):
|
|
"""Implement the notification service for mobile_app."""
|
|
|
|
def __init__(self, session):
|
|
"""Initialize the service."""
|
|
self._session = session
|
|
|
|
@property
|
|
def targets(self):
|
|
"""Return a dictionary of registered targets."""
|
|
return push_registrations(self.hass)
|
|
|
|
async def async_send_message(self, message="", **kwargs):
|
|
"""Send a message to the Lambda APNS gateway."""
|
|
data = {ATTR_MESSAGE: message}
|
|
|
|
if kwargs.get(ATTR_TITLE) is not None:
|
|
# Remove default title from notifications.
|
|
if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT:
|
|
data[ATTR_TITLE] = kwargs.get(ATTR_TITLE)
|
|
|
|
targets = kwargs.get(ATTR_TARGET)
|
|
|
|
if not targets:
|
|
targets = push_registrations(self.hass).values()
|
|
|
|
if kwargs.get(ATTR_DATA) is not None:
|
|
data[ATTR_DATA] = kwargs.get(ATTR_DATA)
|
|
|
|
for target in targets:
|
|
entry = self.hass.data[DOMAIN][DATA_CONFIG_ENTRIES][target]
|
|
entry_data = entry.data
|
|
|
|
app_data = entry_data[ATTR_APP_DATA]
|
|
push_token = app_data[ATTR_PUSH_TOKEN]
|
|
push_url = app_data[ATTR_PUSH_URL]
|
|
|
|
data[ATTR_PUSH_TOKEN] = push_token
|
|
|
|
reg_info = {
|
|
ATTR_APP_ID: entry_data[ATTR_APP_ID],
|
|
ATTR_APP_VERSION: entry_data[ATTR_APP_VERSION],
|
|
}
|
|
if ATTR_OS_VERSION in entry_data:
|
|
reg_info[ATTR_OS_VERSION] = entry_data[ATTR_OS_VERSION]
|
|
|
|
data["registration_info"] = reg_info
|
|
|
|
try:
|
|
with async_timeout.timeout(10):
|
|
response = await self._session.post(push_url, json=data)
|
|
result = await response.json()
|
|
|
|
if response.status == 201:
|
|
log_rate_limits(self.hass, entry_data[ATTR_DEVICE_NAME], result)
|
|
return
|
|
|
|
fallback_error = result.get("errorMessage", "Unknown error")
|
|
fallback_message = (
|
|
"Internal server error, " "please try again later: " "{}"
|
|
).format(fallback_error)
|
|
message = result.get("message", fallback_message)
|
|
if response.status == 429:
|
|
_LOGGER.warning(message)
|
|
log_rate_limits(
|
|
self.hass, entry_data[ATTR_DEVICE_NAME], result, logging.WARNING
|
|
)
|
|
else:
|
|
_LOGGER.error(message)
|
|
|
|
except asyncio.TimeoutError:
|
|
_LOGGER.error("Timeout sending notification to %s", push_url)
|