core/homeassistant/components/pushbullet/notify.py

156 lines
5.7 KiB
Python
Raw Normal View History

"""Pushbullet platform for notify component."""
from __future__ import annotations
import logging
import mimetypes
from typing import TYPE_CHECKING, Any
from pushbullet import PushBullet, PushError
from pushbullet.channel import Channel
from pushbullet.device import Device
import voluptuous as vol
from homeassistant.components.notify import (
2019-07-31 19:25:30 +00:00
ATTR_DATA,
ATTR_TARGET,
ATTR_TITLE,
ATTR_TITLE_DEFAULT,
BaseNotificationService,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import ATTR_FILE, ATTR_FILE_URL, ATTR_URL, DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_get_service(
hass: HomeAssistant,
config: ConfigType,
discovery_info: DiscoveryInfoType | None = None,
) -> PushBulletNotificationService | None:
2017-03-27 08:35:27 +00:00
"""Get the Pushbullet notification service."""
if TYPE_CHECKING:
assert discovery_info is not None
pushbullet: PushBullet = hass.data[DOMAIN][discovery_info["entry_id"]].pushbullet
return PushBulletNotificationService(hass, pushbullet)
class PushBulletNotificationService(BaseNotificationService):
2016-03-08 10:46:32 +00:00
"""Implement the notification service for Pushbullet."""
def __init__(self, hass: HomeAssistant, pushbullet: PushBullet) -> None:
2016-03-08 10:46:32 +00:00
"""Initialize the service."""
self.hass = hass
self.pushbullet = pushbullet
@property
def pbtargets(self) -> dict[str, dict[str, Device | Channel]]:
"""Return device and channel detected targets."""
return {
2019-07-31 19:25:30 +00:00
"device": {tgt.nickname.lower(): tgt for tgt in self.pushbullet.devices},
"channel": {
tgt.channel_tag.lower(): tgt for tgt in self.pushbullet.channels
},
}
def send_message(self, message: str, **kwargs: Any) -> None:
2016-03-08 10:46:32 +00:00
"""Send a message to a specified target.
2015-11-15 20:49:42 +00:00
If no target specified, a 'normal' push will be sent to all devices
2017-03-27 08:35:27 +00:00
linked to the Pushbullet account.
Email is special, these are assumed to always exist. We use a special
2015-11-25 07:56:50 +00:00
call which doesn't require a push object.
2015-11-15 20:49:42 +00:00
"""
targets: list[str] = kwargs.get(ATTR_TARGET, [])
title: str = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
data: dict[str, Any] = kwargs[ATTR_DATA] or {}
2015-11-15 23:46:16 +00:00
if not targets:
# Backward compatibility, notify all devices in own account.
self._push_data(message, title, data, self.pushbullet)
_LOGGER.debug("Sent notification to self")
2015-11-15 23:46:16 +00:00
return
# refresh device and channel targets
self.pushbullet.refresh()
# Main loop, process all targets specified.
2015-11-15 23:46:16 +00:00
for target in targets:
try:
2019-07-31 19:25:30 +00:00
ttype, tname = target.split("/", 1)
except ValueError as err:
raise ValueError(f"Invalid target syntax: '{target}'") from err
2015-11-15 23:46:16 +00:00
# Target is email, send directly, don't use a target object.
# This also seems to work to send to all devices in own account.
2019-07-31 19:25:30 +00:00
if ttype == "email":
self._push_data(message, title, data, self.pushbullet, email=tname)
2017-03-27 08:35:27 +00:00
_LOGGER.info("Sent notification to email %s", tname)
continue
# Target is sms, send directly, don't use a target object.
if ttype == "sms":
self._push_data(
message, title, data, self.pushbullet, phonenumber=tname
)
_LOGGER.info("Sent sms notification to %s", tname)
continue
if ttype not in self.pbtargets:
raise ValueError(f"Invalid target syntax: {target}")
2015-11-28 23:41:30 +00:00
tname = tname.lower()
if tname not in self.pbtargets[ttype]:
raise ValueError(f"Target: {target} doesn't exist")
2015-11-15 23:46:16 +00:00
# Attempt push_note on a dict value. Keys are types & target
# name. Dict pbtargets has all *actual* targets.
self._push_data(message, title, data, self.pbtargets[ttype][tname])
_LOGGER.debug("Sent notification to %s/%s", ttype, tname)
def _push_data(
self,
message: str,
title: str,
data: dict[str, Any],
pusher: PushBullet,
email: str | None = None,
phonenumber: str | None = None,
):
"""Create the message content."""
kwargs = {"body": message, "title": title}
if email:
kwargs["email"] = email
2019-07-31 19:25:30 +00:00
try:
if phonenumber and pusher.devices:
pusher.push_sms(pusher.devices[0], phonenumber, message)
return
if url := data.get(ATTR_URL):
pusher.push_link(url=url, **kwargs)
return
if filepath := data.get(ATTR_FILE):
if not self.hass.config.is_allowed_path(filepath):
raise ValueError("Filepath is not valid or allowed")
2019-07-31 19:25:30 +00:00
with open(filepath, "rb") as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
if filedata.get("file_type") == "application/x-empty":
raise ValueError("Cannot send an empty file")
kwargs.update(filedata)
pusher.push_file(**kwargs)
elif (file_url := data.get(ATTR_FILE_URL)) and vol.Url(file_url):
2019-07-31 19:25:30 +00:00
pusher.push_file(
file_name=file_url,
file_url=file_url,
file_type=(mimetypes.guess_type(file_url)[0]),
**kwargs,
2019-07-31 19:25:30 +00:00
)
else:
pusher.push_note(**kwargs)
except PushError as err:
raise HomeAssistantError(f"Notify failed: {err}") from err