1 """Discord platform for notify component."""
3 from __future__
import annotations
8 from typing
import Any, cast
12 from nextcord.abc
import Messageable
17 BaseNotificationService,
24 _LOGGER = logging.getLogger(__name__)
27 ATTR_EMBED_AUTHOR =
"author"
28 ATTR_EMBED_COLOR =
"color"
29 ATTR_EMBED_DESCRIPTION =
"description"
30 ATTR_EMBED_FIELDS =
"fields"
31 ATTR_EMBED_FOOTER =
"footer"
32 ATTR_EMBED_TITLE =
"title"
33 ATTR_EMBED_THUMBNAIL =
"thumbnail"
34 ATTR_EMBED_IMAGE =
"image"
35 ATTR_EMBED_URL =
"url"
36 ATTR_IMAGES =
"images"
38 ATTR_VERIFY_SSL =
"verify_ssl"
40 MAX_ALLOWED_DOWNLOAD_SIZE_BYTES = 8000000
46 discovery_info: DiscoveryInfoType |
None =
None,
47 ) -> DiscordNotificationService |
None:
48 """Get the Discord notification service."""
49 if discovery_info
is None:
55 """Implement the notification service for Discord."""
57 def __init__(self, hass: HomeAssistant, token: str) ->
None:
58 """Initialize the service."""
63 """Check if a file exists on disk and is in authorized path."""
64 if not self.
hasshass.config.is_allowed_path(filename):
65 _LOGGER.warning(
"Path not allowed: %s", filename)
67 if not os.path.isfile(filename):
68 _LOGGER.warning(
"Not a file: %s", filename)
73 self, url: str, verify_ssl: bool, max_file_size: int
74 ) -> bytearray |
None:
75 """Retrieve file bytes from URL."""
76 if not self.
hasshass.config.is_allowed_external_url(url):
77 _LOGGER.error(
"URL not allowed: %s", url)
82 async
with session.get(
85 timeout=aiohttp.ClientTimeout(total=30),
86 raise_for_status=
True,
88 content_length = resp.headers.get(
"Content-Length")
90 if content_length
is not None and int(content_length) > max_file_size:
93 "Attachment too large (Content-Length reports %s). Max size: %s"
102 byte_chunks = bytearray()
104 async
for byte_chunk, _
in resp.content.iter_chunks():
105 file_size += len(byte_chunk)
106 if file_size > max_file_size:
108 "Attachment too large (Stream reports %s). Max size: %s bytes",
114 byte_chunks.extend(byte_chunk)
119 """Login to Discord, send message to channel(s) and log out."""
120 nextcord.VoiceClient.warn_nacl =
False
121 discord_bot = nextcord.Client()
125 if ATTR_TARGET
not in kwargs:
126 _LOGGER.error(
"No target specified")
129 data = kwargs.get(ATTR_DATA)
or {}
131 embeds: list[nextcord.Embed] = []
132 if ATTR_EMBED
in data:
133 embedding = data[ATTR_EMBED]
134 title = embedding.get(ATTR_EMBED_TITLE)
135 description = embedding.get(ATTR_EMBED_DESCRIPTION)
136 color = embedding.get(ATTR_EMBED_COLOR)
137 url = embedding.get(ATTR_EMBED_URL)
138 fields = embedding.get(ATTR_EMBED_FIELDS)
or []
141 embed = nextcord.Embed(
142 title=title, description=description, color=color, url=url
145 embed.add_field(**field)
146 if ATTR_EMBED_FOOTER
in embedding:
147 embed.set_footer(**embedding[ATTR_EMBED_FOOTER])
148 if ATTR_EMBED_AUTHOR
in embedding:
149 embed.set_author(**embedding[ATTR_EMBED_AUTHOR])
150 if ATTR_EMBED_THUMBNAIL
in embedding:
151 embed.set_thumbnail(**embedding[ATTR_EMBED_THUMBNAIL])
152 if ATTR_EMBED_IMAGE
in embedding:
153 embed.set_image(**embedding[ATTR_EMBED_IMAGE])
156 if ATTR_IMAGES
in data:
157 for image
in data.get(ATTR_IMAGES, []):
158 image_exists = await self.
hasshass.async_add_executor_job(
162 filename = os.path.basename(image)
165 images.append((image, filename))
167 if ATTR_URLS
in data:
168 for url
in data.get(ATTR_URLS, []):
171 data.get(ATTR_VERIFY_SSL,
True),
172 MAX_ALLOWED_DOWNLOAD_SIZE_BYTES,
176 filename = os.path.basename(url)
178 images.append((BytesIO(file), filename))
180 await discord_bot.login(self.
tokentoken)
183 for channelid
in kwargs[ATTR_TARGET]:
184 channelid =
int(channelid)
186 files = [nextcord.File(image, filename)
for image, filename
in images]
189 Messageable, await discord_bot.fetch_channel(channelid)
191 except nextcord.NotFound:
193 channel = await discord_bot.fetch_user(channelid)
194 except nextcord.NotFound:
195 _LOGGER.warning(
"Channel not found for ID: %s", channelid)
197 await channel.send(message, files=files, embeds=embeds)
198 except (nextcord.HTTPException, nextcord.NotFound)
as error:
199 _LOGGER.warning(
"Communication error: %s", error)
200 await discord_bot.close()
None __init__(self, HomeAssistant hass, str token)
bytearray|None async_get_file_from_url(self, str url, bool verify_ssl, int max_file_size)
None async_send_message(self, str message, **Any kwargs)
bool file_exists(self, str filename)
DiscordNotificationService|None async_get_service(HomeAssistant hass, ConfigType config, DiscoveryInfoType|None discovery_info=None)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)