Home Assistant Unofficial Reference 2024.12.1
notify.py
Go to the documentation of this file.
1 """Mastodon platform for notify component."""
2 
3 from __future__ import annotations
4 
5 import mimetypes
6 from typing import Any, cast
7 
8 from mastodon import Mastodon
9 from mastodon.Mastodon import MastodonAPIError
10 import voluptuous as vol
11 
13  ATTR_DATA,
14  PLATFORM_SCHEMA as NOTIFY_PLATFORM_SCHEMA,
15  BaseNotificationService,
16 )
17 from homeassistant.config_entries import SOURCE_IMPORT
18 from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_CLIENT_SECRET
19 from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
20 from homeassistant.data_entry_flow import FlowResultType
21 from homeassistant.helpers import config_validation as cv, issue_registry as ir
22 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
23 
24 from .const import CONF_BASE_URL, DEFAULT_URL, DOMAIN, LOGGER
25 
26 ATTR_MEDIA = "media"
27 ATTR_TARGET = "target"
28 ATTR_MEDIA_WARNING = "media_warning"
29 ATTR_CONTENT_WARNING = "content_warning"
30 
31 PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend(
32  {
33  vol.Required(CONF_ACCESS_TOKEN): cv.string,
34  vol.Required(CONF_CLIENT_ID): cv.string,
35  vol.Required(CONF_CLIENT_SECRET): cv.string,
36  vol.Optional(CONF_BASE_URL, default=DEFAULT_URL): cv.string,
37  }
38 )
39 
40 INTEGRATION_TITLE = "Mastodon"
41 
42 
44  hass: HomeAssistant,
45  config: ConfigType,
46  discovery_info: DiscoveryInfoType | None = None,
47 ) -> MastodonNotificationService | None:
48  """Get the Mastodon notification service."""
49 
50  if not discovery_info:
51  # Import config entry
52 
53  import_result = await hass.config_entries.flow.async_init(
54  DOMAIN,
55  context={"source": SOURCE_IMPORT},
56  data=config,
57  )
58 
59  if (
60  import_result["type"] == FlowResultType.ABORT
61  and import_result["reason"] != "already_configured"
62  ):
63  ir.async_create_issue(
64  hass,
65  DOMAIN,
66  f"deprecated_yaml_import_issue_{import_result["reason"]}",
67  breaks_in_ha_version="2025.2.0",
68  is_fixable=False,
69  issue_domain=DOMAIN,
70  severity=ir.IssueSeverity.WARNING,
71  translation_key=f"deprecated_yaml_import_issue_{import_result["reason"]}",
72  translation_placeholders={
73  "domain": DOMAIN,
74  "integration_title": INTEGRATION_TITLE,
75  },
76  )
77  return None
78 
79  ir.async_create_issue(
80  hass,
81  HOMEASSISTANT_DOMAIN,
82  f"deprecated_yaml_{DOMAIN}",
83  breaks_in_ha_version="2025.2.0",
84  is_fixable=False,
85  issue_domain=DOMAIN,
86  severity=ir.IssueSeverity.WARNING,
87  translation_key="deprecated_yaml",
88  translation_placeholders={
89  "domain": DOMAIN,
90  "integration_title": INTEGRATION_TITLE,
91  },
92  )
93 
94  return None
95 
96  client: Mastodon = discovery_info.get("client")
97 
98  return MastodonNotificationService(hass, client)
99 
100 
101 class MastodonNotificationService(BaseNotificationService):
102  """Implement the notification service for Mastodon."""
103 
104  def __init__(
105  self,
106  hass: HomeAssistant,
107  client: Mastodon,
108  ) -> None:
109  """Initialize the service."""
110 
111  self.clientclient = client
112 
113  def send_message(self, message: str = "", **kwargs: Any) -> None:
114  """Toot a message, with media perhaps."""
115 
116  target = None
117  if (target_list := kwargs.get(ATTR_TARGET)) is not None:
118  target = cast(list[str], target_list)[0]
119 
120  data = kwargs.get(ATTR_DATA)
121 
122  media = None
123  mediadata = None
124  sensitive = False
125  content_warning = None
126 
127  if data:
128  media = data.get(ATTR_MEDIA)
129  if media:
130  if not self.hass.config.is_allowed_path(media):
131  LOGGER.warning("'%s' is not a whitelisted directory", media)
132  return
133  mediadata = self._upload_media_upload_media(media)
134 
135  sensitive = data.get(ATTR_MEDIA_WARNING)
136  content_warning = data.get(ATTR_CONTENT_WARNING)
137 
138  if mediadata:
139  try:
140  self.clientclient.status_post(
141  message,
142  media_ids=mediadata["id"],
143  sensitive=sensitive,
144  visibility=target,
145  spoiler_text=content_warning,
146  )
147  except MastodonAPIError:
148  LOGGER.error("Unable to send message")
149  else:
150  try:
151  self.clientclient.status_post(
152  message, visibility=target, spoiler_text=content_warning
153  )
154  except MastodonAPIError:
155  LOGGER.error("Unable to send message")
156 
157  def _upload_media(self, media_path: Any = None) -> Any:
158  """Upload media."""
159  with open(media_path, "rb"):
160  media_type = self._media_type_media_type(media_path)
161  try:
162  mediadata = self.clientclient.media_post(media_path, mime_type=media_type)
163  except MastodonAPIError:
164  LOGGER.error(f"Unable to upload image {media_path}")
165 
166  return mediadata
167 
168  def _media_type(self, media_path: Any = None) -> Any:
169  """Get media Type."""
170  (media_type, _) = mimetypes.guess_type(media_path)
171 
172  return media_type
None send_message(self, str message="", **Any kwargs)
Definition: notify.py:113
None __init__(self, HomeAssistant hass, Mastodon client)
Definition: notify.py:108
MastodonNotificationService|None async_get_service(HomeAssistant hass, ConfigType config, DiscoveryInfoType|None discovery_info=None)
Definition: notify.py:47
None open(self, **Any kwargs)
Definition: lock.py:86