Home Assistant Unofficial Reference 2024.12.1
notify.py
Go to the documentation of this file.
1 """Pushbullet platform for notify component."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import mimetypes
7 from typing import TYPE_CHECKING, Any
8 
9 from pushbullet import PushBullet, PushError
10 from pushbullet.channel import Channel
11 from pushbullet.device import Device
12 import voluptuous as vol
13 
15  ATTR_DATA,
16  ATTR_TARGET,
17  ATTR_TITLE,
18  ATTR_TITLE_DEFAULT,
19  BaseNotificationService,
20 )
21 from homeassistant.core import HomeAssistant
22 from homeassistant.exceptions import HomeAssistantError
23 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
24 
25 from .api import PushBulletNotificationProvider
26 from .const import ATTR_FILE, ATTR_FILE_URL, ATTR_URL, DOMAIN
27 
28 _LOGGER = logging.getLogger(__name__)
29 
30 
32  hass: HomeAssistant,
33  config: ConfigType,
34  discovery_info: DiscoveryInfoType | None = None,
35 ) -> PushBulletNotificationService | None:
36  """Get the Pushbullet notification service."""
37  if TYPE_CHECKING:
38  assert discovery_info is not None
39  pb_provider: PushBulletNotificationProvider = hass.data[DOMAIN][
40  discovery_info["entry_id"]
41  ]
42  return PushBulletNotificationService(hass, pb_provider.pushbullet)
43 
44 
45 class PushBulletNotificationService(BaseNotificationService):
46  """Implement the notification service for Pushbullet."""
47 
48  def __init__(self, hass: HomeAssistant, pushbullet: PushBullet) -> None:
49  """Initialize the service."""
50  self.hasshass = hass
51  self.pushbulletpushbullet = pushbullet
52 
53  @property
54  def pbtargets(self) -> dict[str, dict[str, Device | Channel]]:
55  """Return device and channel detected targets."""
56  return {
57  "device": {tgt.nickname.lower(): tgt for tgt in self.pushbulletpushbullet.devices},
58  "channel": {
59  tgt.channel_tag.lower(): tgt for tgt in self.pushbulletpushbullet.channels
60  },
61  }
62 
63  def send_message(self, message: str, **kwargs: Any) -> None:
64  """Send a message to a specified target.
65 
66  If no target specified, a 'normal' push will be sent to all devices
67  linked to the Pushbullet account.
68  Email is special, these are assumed to always exist. We use a special
69  call which doesn't require a push object.
70  """
71  targets: list[str] = kwargs.get(ATTR_TARGET, [])
72  title: str = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
73  data: dict[str, Any] = kwargs[ATTR_DATA] or {}
74 
75  if not targets:
76  # Backward compatibility, notify all devices in own account.
77  self._push_data_push_data(message, title, data, self.pushbulletpushbullet)
78  _LOGGER.debug("Sent notification to self")
79  return
80 
81  # refresh device and channel targets
82  self.pushbulletpushbullet.refresh()
83 
84  # Main loop, process all targets specified.
85  for target in targets:
86  try:
87  ttype, tname = target.split("/", 1)
88  except ValueError as err:
89  raise ValueError(f"Invalid target syntax: '{target}'") from err
90 
91  # Target is email, send directly, don't use a target object.
92  # This also seems to work to send to all devices in own account.
93  if ttype == "email":
94  self._push_data_push_data(message, title, data, self.pushbulletpushbullet, email=tname)
95  _LOGGER.debug("Sent notification to email %s", tname)
96  continue
97 
98  # Target is sms, send directly, don't use a target object.
99  if ttype == "sms":
100  self._push_data_push_data(
101  message, title, data, self.pushbulletpushbullet, phonenumber=tname
102  )
103  _LOGGER.debug("Sent sms notification to %s", tname)
104  continue
105 
106  if ttype not in self.pbtargetspbtargets:
107  raise ValueError(f"Invalid target syntax: {target}")
108 
109  tname = tname.lower()
110 
111  if tname not in self.pbtargetspbtargets[ttype]:
112  raise ValueError(f"Target: {target} doesn't exist")
113 
114  # Attempt push_note on a dict value. Keys are types & target
115  # name. Dict pbtargets has all *actual* targets.
116  self._push_data_push_data(message, title, data, self.pbtargetspbtargets[ttype][tname])
117  _LOGGER.debug("Sent notification to %s/%s", ttype, tname)
118 
120  self,
121  message: str,
122  title: str,
123  data: dict[str, Any],
124  pusher: PushBullet,
125  email: str | None = None,
126  phonenumber: str | None = None,
127  ) -> None:
128  """Create the message content."""
129  kwargs = {"body": message, "title": title}
130  if email:
131  kwargs["email"] = email
132 
133  try:
134  if phonenumber and pusher.devices:
135  pusher.push_sms(pusher.devices[0], phonenumber, message)
136  return
137  if url := data.get(ATTR_URL):
138  pusher.push_link(url=url, **kwargs)
139  return
140  if filepath := data.get(ATTR_FILE):
141  if not self.hasshass.config.is_allowed_path(filepath):
142  raise ValueError("Filepath is not valid or allowed")
143  with open(filepath, "rb") as fileh:
144  filedata = self.pushbulletpushbullet.upload_file(fileh, filepath)
145  if filedata.get("file_type") == "application/x-empty":
146  raise ValueError("Cannot send an empty file")
147  kwargs.update(filedata)
148  pusher.push_file(**kwargs)
149  elif (file_url := data.get(ATTR_FILE_URL)) and vol.Url(file_url):
150  pusher.push_file(
151  file_name=file_url,
152  file_url=file_url,
153  file_type=(mimetypes.guess_type(file_url)[0]),
154  **kwargs,
155  )
156  else:
157  pusher.push_note(**kwargs)
158  except PushError as err:
159  raise HomeAssistantError(f"Notify failed: {err}") from err
dict[str, dict[str, Device|Channel]] pbtargets(self)
Definition: notify.py:54
None __init__(self, HomeAssistant hass, PushBullet pushbullet)
Definition: notify.py:48
None _push_data(self, str message, str title, dict[str, Any] data, PushBullet pusher, str|None email=None, str|None phonenumber=None)
Definition: notify.py:127
None open(self, **Any kwargs)
Definition: lock.py:86
PushBulletNotificationService|None async_get_service(HomeAssistant hass, ConfigType config, DiscoveryInfoType|None discovery_info=None)
Definition: notify.py:35