Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support to send and receive Telegram messages."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import io
7 from ipaddress import ip_network
8 import logging
9 from typing import Any
10 
11 import httpx
12 from telegram import (
13  Bot,
14  CallbackQuery,
15  InlineKeyboardButton,
16  InlineKeyboardMarkup,
17  Message,
18  ReplyKeyboardMarkup,
19  ReplyKeyboardRemove,
20  Update,
21  User,
22 )
23 from telegram.constants import ParseMode
24 from telegram.error import TelegramError
25 from telegram.ext import CallbackContext, filters
26 from telegram.request import HTTPXRequest
27 import voluptuous as vol
28 
29 from homeassistant.const import (
30  ATTR_COMMAND,
31  ATTR_LATITUDE,
32  ATTR_LONGITUDE,
33  CONF_API_KEY,
34  CONF_PLATFORM,
35  CONF_URL,
36  HTTP_BEARER_AUTHENTICATION,
37  HTTP_DIGEST_AUTHENTICATION,
38 )
39 from homeassistant.core import Context, HomeAssistant, ServiceCall
40 from homeassistant.helpers import config_validation as cv, issue_registry as ir
41 from homeassistant.helpers.typing import ConfigType
42 from homeassistant.loader import async_get_loaded_integration
43 from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
44 
45 _LOGGER = logging.getLogger(__name__)
46 
47 ATTR_DATA = "data"
48 ATTR_MESSAGE = "message"
49 ATTR_TITLE = "title"
50 
51 ATTR_ARGS = "args"
52 ATTR_AUTHENTICATION = "authentication"
53 ATTR_CALLBACK_QUERY = "callback_query"
54 ATTR_CALLBACK_QUERY_ID = "callback_query_id"
55 ATTR_CAPTION = "caption"
56 ATTR_CHAT_ID = "chat_id"
57 ATTR_CHAT_INSTANCE = "chat_instance"
58 ATTR_DATE = "date"
59 ATTR_DISABLE_NOTIF = "disable_notification"
60 ATTR_DISABLE_WEB_PREV = "disable_web_page_preview"
61 ATTR_EDITED_MSG = "edited_message"
62 ATTR_FILE = "file"
63 ATTR_FROM_FIRST = "from_first"
64 ATTR_FROM_LAST = "from_last"
65 ATTR_KEYBOARD = "keyboard"
66 ATTR_RESIZE_KEYBOARD = "resize_keyboard"
67 ATTR_ONE_TIME_KEYBOARD = "one_time_keyboard"
68 ATTR_KEYBOARD_INLINE = "inline_keyboard"
69 ATTR_MESSAGEID = "message_id"
70 ATTR_MSG = "message"
71 ATTR_MSGID = "id"
72 ATTR_PARSER = "parse_mode"
73 ATTR_PASSWORD = "password"
74 ATTR_REPLY_TO_MSGID = "reply_to_message_id"
75 ATTR_REPLYMARKUP = "reply_markup"
76 ATTR_SHOW_ALERT = "show_alert"
77 ATTR_STICKER_ID = "sticker_id"
78 ATTR_TARGET = "target"
79 ATTR_TEXT = "text"
80 ATTR_URL = "url"
81 ATTR_USER_ID = "user_id"
82 ATTR_USERNAME = "username"
83 ATTR_VERIFY_SSL = "verify_ssl"
84 ATTR_TIMEOUT = "timeout"
85 ATTR_MESSAGE_TAG = "message_tag"
86 ATTR_CHANNEL_POST = "channel_post"
87 ATTR_QUESTION = "question"
88 ATTR_OPTIONS = "options"
89 ATTR_ANSWERS = "answers"
90 ATTR_OPEN_PERIOD = "open_period"
91 ATTR_IS_ANONYMOUS = "is_anonymous"
92 ATTR_ALLOWS_MULTIPLE_ANSWERS = "allows_multiple_answers"
93 ATTR_MESSAGE_THREAD_ID = "message_thread_id"
94 
95 CONF_ALLOWED_CHAT_IDS = "allowed_chat_ids"
96 CONF_PROXY_URL = "proxy_url"
97 CONF_PROXY_PARAMS = "proxy_params"
98 CONF_TRUSTED_NETWORKS = "trusted_networks"
99 
100 DOMAIN = "telegram_bot"
101 
102 SERVICE_SEND_MESSAGE = "send_message"
103 SERVICE_SEND_PHOTO = "send_photo"
104 SERVICE_SEND_STICKER = "send_sticker"
105 SERVICE_SEND_ANIMATION = "send_animation"
106 SERVICE_SEND_VIDEO = "send_video"
107 SERVICE_SEND_VOICE = "send_voice"
108 SERVICE_SEND_DOCUMENT = "send_document"
109 SERVICE_SEND_LOCATION = "send_location"
110 SERVICE_SEND_POLL = "send_poll"
111 SERVICE_EDIT_MESSAGE = "edit_message"
112 SERVICE_EDIT_CAPTION = "edit_caption"
113 SERVICE_EDIT_REPLYMARKUP = "edit_replymarkup"
114 SERVICE_ANSWER_CALLBACK_QUERY = "answer_callback_query"
115 SERVICE_DELETE_MESSAGE = "delete_message"
116 SERVICE_LEAVE_CHAT = "leave_chat"
117 
118 EVENT_TELEGRAM_CALLBACK = "telegram_callback"
119 EVENT_TELEGRAM_COMMAND = "telegram_command"
120 EVENT_TELEGRAM_TEXT = "telegram_text"
121 EVENT_TELEGRAM_SENT = "telegram_sent"
122 
123 PARSER_HTML = "html"
124 PARSER_MD = "markdown"
125 PARSER_MD2 = "markdownv2"
126 PARSER_PLAIN_TEXT = "plain_text"
127 
128 DEFAULT_TRUSTED_NETWORKS = [ip_network("149.154.160.0/20"), ip_network("91.108.4.0/22")]
129 
130 CONFIG_SCHEMA = vol.Schema(
131  {
132  DOMAIN: vol.All(
133  cv.ensure_list,
134  [
135  vol.Schema(
136  {
137  vol.Required(CONF_PLATFORM): vol.In(
138  ("broadcast", "polling", "webhooks")
139  ),
140  vol.Required(CONF_API_KEY): cv.string,
141  vol.Required(CONF_ALLOWED_CHAT_IDS): vol.All(
142  cv.ensure_list, [vol.Coerce(int)]
143  ),
144  vol.Optional(ATTR_PARSER, default=PARSER_MD): cv.string,
145  vol.Optional(CONF_PROXY_URL): cv.string,
146  vol.Optional(CONF_PROXY_PARAMS): dict,
147  # webhooks
148  vol.Optional(CONF_URL): cv.url,
149  vol.Optional(
150  CONF_TRUSTED_NETWORKS, default=DEFAULT_TRUSTED_NETWORKS
151  ): vol.All(cv.ensure_list, [ip_network]),
152  }
153  )
154  ],
155  )
156  },
157  extra=vol.ALLOW_EXTRA,
158 )
159 
160 BASE_SERVICE_SCHEMA = vol.Schema(
161  {
162  vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [vol.Coerce(int)]),
163  vol.Optional(ATTR_PARSER): cv.string,
164  vol.Optional(ATTR_DISABLE_NOTIF): cv.boolean,
165  vol.Optional(ATTR_DISABLE_WEB_PREV): cv.boolean,
166  vol.Optional(ATTR_RESIZE_KEYBOARD): cv.boolean,
167  vol.Optional(ATTR_ONE_TIME_KEYBOARD): cv.boolean,
168  vol.Optional(ATTR_KEYBOARD): vol.All(cv.ensure_list, [cv.string]),
169  vol.Optional(ATTR_KEYBOARD_INLINE): cv.ensure_list,
170  vol.Optional(ATTR_TIMEOUT): cv.positive_int,
171  vol.Optional(ATTR_MESSAGE_TAG): cv.string,
172  },
173  extra=vol.ALLOW_EXTRA,
174 )
175 
176 SERVICE_SCHEMA_SEND_MESSAGE = BASE_SERVICE_SCHEMA.extend(
177  {vol.Required(ATTR_MESSAGE): cv.string, vol.Optional(ATTR_TITLE): cv.string}
178 )
179 
180 SERVICE_SCHEMA_SEND_FILE = BASE_SERVICE_SCHEMA.extend(
181  {
182  vol.Optional(ATTR_URL): cv.string,
183  vol.Optional(ATTR_FILE): cv.string,
184  vol.Optional(ATTR_CAPTION): cv.string,
185  vol.Optional(ATTR_USERNAME): cv.string,
186  vol.Optional(ATTR_PASSWORD): cv.string,
187  vol.Optional(ATTR_AUTHENTICATION): cv.string,
188  vol.Optional(ATTR_VERIFY_SSL): cv.boolean,
189  }
190 )
191 
192 SERVICE_SCHEMA_SEND_STICKER = SERVICE_SCHEMA_SEND_FILE.extend(
193  {vol.Optional(ATTR_STICKER_ID): cv.string}
194 )
195 
196 SERVICE_SCHEMA_SEND_LOCATION = BASE_SERVICE_SCHEMA.extend(
197  {
198  vol.Required(ATTR_LONGITUDE): cv.string,
199  vol.Required(ATTR_LATITUDE): cv.string,
200  }
201 )
202 
203 SERVICE_SCHEMA_SEND_POLL = vol.Schema(
204  {
205  vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [vol.Coerce(int)]),
206  vol.Required(ATTR_QUESTION): cv.string,
207  vol.Required(ATTR_OPTIONS): vol.All(cv.ensure_list, [cv.string]),
208  vol.Optional(ATTR_OPEN_PERIOD): cv.positive_int,
209  vol.Optional(ATTR_IS_ANONYMOUS, default=True): cv.boolean,
210  vol.Optional(ATTR_ALLOWS_MULTIPLE_ANSWERS, default=False): cv.boolean,
211  vol.Optional(ATTR_DISABLE_NOTIF): cv.boolean,
212  vol.Optional(ATTR_TIMEOUT): cv.positive_int,
213  }
214 )
215 
216 SERVICE_SCHEMA_EDIT_MESSAGE = SERVICE_SCHEMA_SEND_MESSAGE.extend(
217  {
218  vol.Required(ATTR_MESSAGEID): vol.Any(
219  cv.positive_int, vol.All(cv.string, "last")
220  ),
221  vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
222  }
223 )
224 
225 SERVICE_SCHEMA_EDIT_CAPTION = vol.Schema(
226  {
227  vol.Required(ATTR_MESSAGEID): vol.Any(
228  cv.positive_int, vol.All(cv.string, "last")
229  ),
230  vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
231  vol.Required(ATTR_CAPTION): cv.string,
232  vol.Optional(ATTR_KEYBOARD_INLINE): cv.ensure_list,
233  },
234  extra=vol.ALLOW_EXTRA,
235 )
236 
237 SERVICE_SCHEMA_EDIT_REPLYMARKUP = vol.Schema(
238  {
239  vol.Required(ATTR_MESSAGEID): vol.Any(
240  cv.positive_int, vol.All(cv.string, "last")
241  ),
242  vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
243  vol.Required(ATTR_KEYBOARD_INLINE): cv.ensure_list,
244  },
245  extra=vol.ALLOW_EXTRA,
246 )
247 
248 SERVICE_SCHEMA_ANSWER_CALLBACK_QUERY = vol.Schema(
249  {
250  vol.Required(ATTR_MESSAGE): cv.string,
251  vol.Required(ATTR_CALLBACK_QUERY_ID): vol.Coerce(int),
252  vol.Optional(ATTR_SHOW_ALERT): cv.boolean,
253  },
254  extra=vol.ALLOW_EXTRA,
255 )
256 
257 SERVICE_SCHEMA_DELETE_MESSAGE = vol.Schema(
258  {
259  vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
260  vol.Required(ATTR_MESSAGEID): vol.Any(
261  cv.positive_int, vol.All(cv.string, "last")
262  ),
263  },
264  extra=vol.ALLOW_EXTRA,
265 )
266 
267 SERVICE_SCHEMA_LEAVE_CHAT = vol.Schema({vol.Required(ATTR_CHAT_ID): vol.Coerce(int)})
268 
269 SERVICE_MAP = {
270  SERVICE_SEND_MESSAGE: SERVICE_SCHEMA_SEND_MESSAGE,
271  SERVICE_SEND_PHOTO: SERVICE_SCHEMA_SEND_FILE,
272  SERVICE_SEND_STICKER: SERVICE_SCHEMA_SEND_STICKER,
273  SERVICE_SEND_ANIMATION: SERVICE_SCHEMA_SEND_FILE,
274  SERVICE_SEND_VIDEO: SERVICE_SCHEMA_SEND_FILE,
275  SERVICE_SEND_VOICE: SERVICE_SCHEMA_SEND_FILE,
276  SERVICE_SEND_DOCUMENT: SERVICE_SCHEMA_SEND_FILE,
277  SERVICE_SEND_LOCATION: SERVICE_SCHEMA_SEND_LOCATION,
278  SERVICE_SEND_POLL: SERVICE_SCHEMA_SEND_POLL,
279  SERVICE_EDIT_MESSAGE: SERVICE_SCHEMA_EDIT_MESSAGE,
280  SERVICE_EDIT_CAPTION: SERVICE_SCHEMA_EDIT_CAPTION,
281  SERVICE_EDIT_REPLYMARKUP: SERVICE_SCHEMA_EDIT_REPLYMARKUP,
282  SERVICE_ANSWER_CALLBACK_QUERY: SERVICE_SCHEMA_ANSWER_CALLBACK_QUERY,
283  SERVICE_DELETE_MESSAGE: SERVICE_SCHEMA_DELETE_MESSAGE,
284  SERVICE_LEAVE_CHAT: SERVICE_SCHEMA_LEAVE_CHAT,
285 }
286 
287 
288 def _read_file_as_bytesio(file_path: str) -> io.BytesIO:
289  """Read a file and return it as a BytesIO object."""
290  with open(file_path, "rb") as file:
291  data = io.BytesIO(file.read())
292  data.name = file_path
293  return data
294 
295 
296 async def load_data(
297  hass,
298  url=None,
299  filepath=None,
300  username=None,
301  password=None,
302  authentication=None,
303  num_retries=5,
304  verify_ssl=None,
305 ):
306  """Load data into ByteIO/File container from a source."""
307  try:
308  if url is not None:
309  # Load data from URL
310  params = {}
311  headers = {}
312  if authentication == HTTP_BEARER_AUTHENTICATION and password is not None:
313  headers = {"Authorization": f"Bearer {password}"}
314  elif username is not None and password is not None:
315  if authentication == HTTP_DIGEST_AUTHENTICATION:
316  params["auth"] = httpx.DigestAuth(username, password)
317  else:
318  params["auth"] = httpx.BasicAuth(username, password)
319  if verify_ssl is not None:
320  params["verify"] = verify_ssl
321 
322  retry_num = 0
323  async with httpx.AsyncClient(
324  timeout=15, headers=headers, **params
325  ) as client:
326  while retry_num < num_retries:
327  req = await client.get(url)
328  if req.status_code != 200:
329  _LOGGER.warning(
330  "Status code %s (retry #%s) loading %s",
331  req.status_code,
332  retry_num + 1,
333  url,
334  )
335  else:
336  data = io.BytesIO(req.content)
337  if data.read():
338  data.seek(0)
339  data.name = url
340  return data
341  _LOGGER.warning(
342  "Empty data (retry #%s) in %s)", retry_num + 1, url
343  )
344  retry_num += 1
345  if retry_num < num_retries:
346  await asyncio.sleep(
347  1
348  ) # Add a sleep to allow other async operations to proceed
349  _LOGGER.warning(
350  "Can't load data in %s after %s retries", url, retry_num
351  )
352  elif filepath is not None:
353  if hass.config.is_allowed_path(filepath):
354  return await hass.async_add_executor_job(
355  _read_file_as_bytesio, filepath
356  )
357 
358  _LOGGER.warning("'%s' are not secure to load data from!", filepath)
359  else:
360  _LOGGER.warning("Can't load data. No data found in params!")
361 
362  except (OSError, TypeError) as error:
363  _LOGGER.error("Can't load data into ByteIO: %s", error)
364 
365  return None
366 
367 
368 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
369  """Set up the Telegram bot component."""
370  domain_config: list[dict[str, Any]] = config[DOMAIN]
371 
372  if not domain_config:
373  return False
374 
375  platforms = await async_get_loaded_integration(hass, DOMAIN).async_get_platforms(
376  {p_config[CONF_PLATFORM] for p_config in domain_config}
377  )
378 
379  for p_config in domain_config:
380  # Each platform config gets its own bot
381  bot = await hass.async_add_executor_job(initialize_bot, hass, p_config)
382  p_type: str = p_config[CONF_PLATFORM]
383 
384  platform = platforms[p_type]
385 
386  _LOGGER.debug("Setting up %s.%s", DOMAIN, p_type)
387  try:
388  receiver_service = await platform.async_setup_platform(hass, bot, p_config)
389  if receiver_service is False:
390  _LOGGER.error("Failed to initialize Telegram bot %s", p_type)
391  return False
392 
393  except Exception:
394  _LOGGER.exception("Error setting up platform %s", p_type)
395  return False
396 
397  notify_service = TelegramNotificationService(
398  hass, bot, p_config.get(CONF_ALLOWED_CHAT_IDS), p_config.get(ATTR_PARSER)
399  )
400 
401  async def async_send_telegram_message(service: ServiceCall) -> None:
402  """Handle sending Telegram Bot message service calls."""
403 
404  msgtype = service.service
405  kwargs = dict(service.data)
406  _LOGGER.debug("New telegram message %s: %s", msgtype, kwargs)
407 
408  if msgtype == SERVICE_SEND_MESSAGE:
409  await notify_service.send_message(context=service.context, **kwargs)
410  elif msgtype in [
411  SERVICE_SEND_PHOTO,
412  SERVICE_SEND_ANIMATION,
413  SERVICE_SEND_VIDEO,
414  SERVICE_SEND_VOICE,
415  SERVICE_SEND_DOCUMENT,
416  ]:
417  await notify_service.send_file(msgtype, context=service.context, **kwargs)
418  elif msgtype == SERVICE_SEND_STICKER:
419  await notify_service.send_sticker(context=service.context, **kwargs)
420  elif msgtype == SERVICE_SEND_LOCATION:
421  await notify_service.send_location(context=service.context, **kwargs)
422  elif msgtype == SERVICE_SEND_POLL:
423  await notify_service.send_poll(context=service.context, **kwargs)
424  elif msgtype == SERVICE_ANSWER_CALLBACK_QUERY:
425  await notify_service.answer_callback_query(
426  context=service.context, **kwargs
427  )
428  elif msgtype == SERVICE_DELETE_MESSAGE:
429  await notify_service.delete_message(context=service.context, **kwargs)
430  else:
431  await notify_service.edit_message(
432  msgtype, context=service.context, **kwargs
433  )
434 
435  # Register notification services
436  for service_notif, schema in SERVICE_MAP.items():
437  hass.services.async_register(
438  DOMAIN, service_notif, async_send_telegram_message, schema=schema
439  )
440 
441  return True
442 
443 
444 def initialize_bot(hass: HomeAssistant, p_config: dict) -> Bot:
445  """Initialize telegram bot with proxy support."""
446  api_key: str = p_config[CONF_API_KEY]
447  proxy_url: str | None = p_config.get(CONF_PROXY_URL)
448  proxy_params: dict | None = p_config.get(CONF_PROXY_PARAMS)
449 
450  if proxy_url is not None:
451  auth = None
452  if proxy_params is None:
453  # CONF_PROXY_PARAMS has been kept for backwards compatibility.
454  proxy_params = {}
455  elif "username" in proxy_params and "password" in proxy_params:
456  # Auth can actually be stuffed into the URL, but the docs have previously
457  # indicated to put them here.
458  auth = proxy_params.pop("username"), proxy_params.pop("password")
459  ir.create_issue(
460  hass,
461  DOMAIN,
462  "proxy_params_auth_deprecation",
463  breaks_in_ha_version="2024.10.0",
464  is_persistent=False,
465  is_fixable=False,
466  severity=ir.IssueSeverity.WARNING,
467  translation_placeholders={
468  "proxy_params": CONF_PROXY_PARAMS,
469  "proxy_url": CONF_PROXY_URL,
470  "telegram_bot": "Telegram bot",
471  },
472  translation_key="proxy_params_auth_deprecation",
473  learn_more_url="https://github.com/home-assistant/core/pull/112778",
474  )
475  else:
476  ir.create_issue(
477  hass,
478  DOMAIN,
479  "proxy_params_deprecation",
480  breaks_in_ha_version="2024.10.0",
481  is_persistent=False,
482  is_fixable=False,
483  severity=ir.IssueSeverity.WARNING,
484  translation_placeholders={
485  "proxy_params": CONF_PROXY_PARAMS,
486  "proxy_url": CONF_PROXY_URL,
487  "httpx": "httpx",
488  "telegram_bot": "Telegram bot",
489  },
490  translation_key="proxy_params_deprecation",
491  learn_more_url="https://github.com/home-assistant/core/pull/112778",
492  )
493  proxy = httpx.Proxy(proxy_url, auth=auth, **proxy_params)
494  request = HTTPXRequest(connection_pool_size=8, proxy=proxy)
495  else:
496  request = HTTPXRequest(connection_pool_size=8)
497  return Bot(token=api_key, request=request)
498 
499 
501  """Implement the notification services for the Telegram Bot domain."""
502 
503  def __init__(self, hass, bot, allowed_chat_ids, parser):
504  """Initialize the service."""
505  self.allowed_chat_idsallowed_chat_ids = allowed_chat_ids
506  self._default_user_default_user = self.allowed_chat_idsallowed_chat_ids[0]
507  self._last_message_id_last_message_id = {user: None for user in self.allowed_chat_idsallowed_chat_ids}
508  self._parsers_parsers = {
509  PARSER_HTML: ParseMode.HTML,
510  PARSER_MD: ParseMode.MARKDOWN,
511  PARSER_MD2: ParseMode.MARKDOWN_V2,
512  PARSER_PLAIN_TEXT: None,
513  }
514  self._parse_mode_parse_mode = self._parsers_parsers.get(parser)
515  self.botbot = bot
516  self.hasshass = hass
517 
518  def _get_msg_ids(self, msg_data, chat_id):
519  """Get the message id to edit.
520 
521  This can be one of (message_id, inline_message_id) from a msg dict,
522  returning a tuple.
523  **You can use 'last' as message_id** to edit
524  the message last sent in the chat_id.
525  """
526  message_id = inline_message_id = None
527  if ATTR_MESSAGEID in msg_data:
528  message_id = msg_data[ATTR_MESSAGEID]
529  if (
530  isinstance(message_id, str)
531  and (message_id == "last")
532  and (self._last_message_id_last_message_id[chat_id] is not None)
533  ):
534  message_id = self._last_message_id_last_message_id[chat_id]
535  else:
536  inline_message_id = msg_data["inline_message_id"]
537  return message_id, inline_message_id
538 
539  def _get_target_chat_ids(self, target):
540  """Validate chat_id targets or return default target (first).
541 
542  :param target: optional list of integers ([12234, -12345])
543  :return list of chat_id targets (integers)
544  """
545  if target is not None:
546  if isinstance(target, int):
547  target = [target]
548  chat_ids = [t for t in target if t in self.allowed_chat_idsallowed_chat_ids]
549  if chat_ids:
550  return chat_ids
551  _LOGGER.warning(
552  "Disallowed targets: %s, using default: %s", target, self._default_user_default_user
553  )
554  return [self._default_user_default_user]
555 
556  def _get_msg_kwargs(self, data):
557  """Get parameters in message data kwargs."""
558 
559  def _make_row_inline_keyboard(row_keyboard):
560  """Make a list of InlineKeyboardButtons.
561 
562  It can accept:
563  - a list of tuples like:
564  `[(text_b1, data_callback_b1),
565  (text_b2, data_callback_b2), ...]
566  - a string like: `/cmd1, /cmd2, /cmd3`
567  - or a string like: `text_b1:/cmd1, text_b2:/cmd2`
568  - also supports urls instead of callback commands
569  """
570  buttons = []
571  if isinstance(row_keyboard, str):
572  for key in row_keyboard.split(","):
573  if ":/" in key:
574  # check if command or URL
575  if key.startswith("https://"):
576  label = key.split(",")[0]
577  url = key[len(label) + 1 :]
578  buttons.append(InlineKeyboardButton(label, url=url))
579  else:
580  # commands like: 'Label:/cmd' become ('Label', '/cmd')
581  label = key.split(":/")[0]
582  command = key[len(label) + 1 :]
583  buttons.append(
584  InlineKeyboardButton(label, callback_data=command)
585  )
586  else:
587  # commands like: '/cmd' become ('CMD', '/cmd')
588  label = key.strip()[1:].upper()
589  buttons.append(InlineKeyboardButton(label, callback_data=key))
590  elif isinstance(row_keyboard, list):
591  for entry in row_keyboard:
592  text_btn, data_btn = entry
593  if data_btn.startswith("https://"):
594  buttons.append(InlineKeyboardButton(text_btn, url=data_btn))
595  else:
596  buttons.append(
597  InlineKeyboardButton(text_btn, callback_data=data_btn)
598  )
599  else:
600  raise TypeError(str(row_keyboard))
601  return buttons
602 
603  # Defaults
604  params = {
605  ATTR_PARSER: self._parse_mode_parse_mode,
606  ATTR_DISABLE_NOTIF: False,
607  ATTR_DISABLE_WEB_PREV: None,
608  ATTR_REPLY_TO_MSGID: None,
609  ATTR_REPLYMARKUP: None,
610  ATTR_TIMEOUT: None,
611  ATTR_MESSAGE_TAG: None,
612  ATTR_MESSAGE_THREAD_ID: None,
613  }
614  if data is not None:
615  if ATTR_PARSER in data:
616  params[ATTR_PARSER] = self._parsers_parsers.get(
617  data[ATTR_PARSER], self._parse_mode_parse_mode
618  )
619  if ATTR_TIMEOUT in data:
620  params[ATTR_TIMEOUT] = data[ATTR_TIMEOUT]
621  if ATTR_DISABLE_NOTIF in data:
622  params[ATTR_DISABLE_NOTIF] = data[ATTR_DISABLE_NOTIF]
623  if ATTR_DISABLE_WEB_PREV in data:
624  params[ATTR_DISABLE_WEB_PREV] = data[ATTR_DISABLE_WEB_PREV]
625  if ATTR_REPLY_TO_MSGID in data:
626  params[ATTR_REPLY_TO_MSGID] = data[ATTR_REPLY_TO_MSGID]
627  if ATTR_MESSAGE_TAG in data:
628  params[ATTR_MESSAGE_TAG] = data[ATTR_MESSAGE_TAG]
629  if ATTR_MESSAGE_THREAD_ID in data:
630  params[ATTR_MESSAGE_THREAD_ID] = data[ATTR_MESSAGE_THREAD_ID]
631  # Keyboards:
632  if ATTR_KEYBOARD in data:
633  keys = data.get(ATTR_KEYBOARD)
634  keys = keys if isinstance(keys, list) else [keys]
635  if keys:
636  params[ATTR_REPLYMARKUP] = ReplyKeyboardMarkup(
637  [[key.strip() for key in row.split(",")] for row in keys],
638  resize_keyboard=data.get(ATTR_RESIZE_KEYBOARD, False),
639  one_time_keyboard=data.get(ATTR_ONE_TIME_KEYBOARD, False),
640  )
641  else:
642  params[ATTR_REPLYMARKUP] = ReplyKeyboardRemove(True)
643 
644  elif ATTR_KEYBOARD_INLINE in data:
645  keys = data.get(ATTR_KEYBOARD_INLINE)
646  keys = keys if isinstance(keys, list) else [keys]
647  params[ATTR_REPLYMARKUP] = InlineKeyboardMarkup(
648  [_make_row_inline_keyboard(row) for row in keys]
649  )
650  return params
651 
652  async def _send_msg(
653  self, func_send, msg_error, message_tag, *args_msg, context=None, **kwargs_msg
654  ):
655  """Send one message."""
656  try:
657  out = await func_send(*args_msg, **kwargs_msg)
658  if not isinstance(out, bool) and hasattr(out, ATTR_MESSAGEID):
659  chat_id = out.chat_id
660  message_id = out[ATTR_MESSAGEID]
661  self._last_message_id_last_message_id[chat_id] = message_id
662  _LOGGER.debug(
663  "Last message ID: %s (from chat_id %s)",
664  self._last_message_id_last_message_id,
665  chat_id,
666  )
667 
668  event_data = {
669  ATTR_CHAT_ID: chat_id,
670  ATTR_MESSAGEID: message_id,
671  }
672  if message_tag is not None:
673  event_data[ATTR_MESSAGE_TAG] = message_tag
674  if kwargs_msg.get(ATTR_MESSAGE_THREAD_ID) is not None:
675  event_data[ATTR_MESSAGE_THREAD_ID] = kwargs_msg[
676  ATTR_MESSAGE_THREAD_ID
677  ]
678  self.hasshass.bus.async_fire(
679  EVENT_TELEGRAM_SENT, event_data, context=context
680  )
681  elif not isinstance(out, bool):
682  _LOGGER.warning(
683  "Update last message: out_type:%s, out=%s", type(out), out
684  )
685  except TelegramError as exc:
686  _LOGGER.error(
687  "%s: %s. Args: %s, kwargs: %s", msg_error, exc, args_msg, kwargs_msg
688  )
689  return None
690  return out
691 
692  async def send_message(self, message="", target=None, context=None, **kwargs):
693  """Send a message to one or multiple pre-allowed chat IDs."""
694  title = kwargs.get(ATTR_TITLE)
695  text = f"{title}\n{message}" if title else message
696  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
697  for chat_id in self._get_target_chat_ids_get_target_chat_ids(target):
698  _LOGGER.debug("Send message in chat ID %s with params: %s", chat_id, params)
699  await self._send_msg_send_msg(
700  self.botbot.send_message,
701  "Error sending message",
702  params[ATTR_MESSAGE_TAG],
703  chat_id,
704  text,
705  parse_mode=params[ATTR_PARSER],
706  disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV],
707  disable_notification=params[ATTR_DISABLE_NOTIF],
708  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
709  reply_markup=params[ATTR_REPLYMARKUP],
710  read_timeout=params[ATTR_TIMEOUT],
711  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
712  context=context,
713  )
714 
715  async def delete_message(self, chat_id=None, context=None, **kwargs):
716  """Delete a previously sent message."""
717  chat_id = self._get_target_chat_ids_get_target_chat_ids(chat_id)[0]
718  message_id, _ = self._get_msg_ids_get_msg_ids(kwargs, chat_id)
719  _LOGGER.debug("Delete message %s in chat ID %s", message_id, chat_id)
720  deleted = await self._send_msg_send_msg(
721  self.botbot.delete_message,
722  "Error deleting message",
723  None,
724  chat_id,
725  message_id,
726  context=context,
727  )
728  # reduce message_id anyway:
729  if self._last_message_id_last_message_id[chat_id] is not None:
730  # change last msg_id for deque(n_msgs)?
731  self._last_message_id_last_message_id[chat_id] -= 1
732  return deleted
733 
734  async def edit_message(self, type_edit, chat_id=None, context=None, **kwargs):
735  """Edit a previously sent message."""
736  chat_id = self._get_target_chat_ids_get_target_chat_ids(chat_id)[0]
737  message_id, inline_message_id = self._get_msg_ids_get_msg_ids(kwargs, chat_id)
738  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
739  _LOGGER.debug(
740  "Edit message %s in chat ID %s with params: %s",
741  message_id or inline_message_id,
742  chat_id,
743  params,
744  )
745  if type_edit == SERVICE_EDIT_MESSAGE:
746  message = kwargs.get(ATTR_MESSAGE)
747  title = kwargs.get(ATTR_TITLE)
748  text = f"{title}\n{message}" if title else message
749  _LOGGER.debug("Editing message with ID %s", message_id or inline_message_id)
750  return await self._send_msg_send_msg(
751  self.botbot.edit_message_text,
752  "Error editing text message",
753  params[ATTR_MESSAGE_TAG],
754  text,
755  chat_id=chat_id,
756  message_id=message_id,
757  inline_message_id=inline_message_id,
758  parse_mode=params[ATTR_PARSER],
759  disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV],
760  reply_markup=params[ATTR_REPLYMARKUP],
761  read_timeout=params[ATTR_TIMEOUT],
762  context=context,
763  )
764  if type_edit == SERVICE_EDIT_CAPTION:
765  return await self._send_msg_send_msg(
766  self.botbot.edit_message_caption,
767  "Error editing message attributes",
768  params[ATTR_MESSAGE_TAG],
769  chat_id=chat_id,
770  message_id=message_id,
771  inline_message_id=inline_message_id,
772  caption=kwargs.get(ATTR_CAPTION),
773  reply_markup=params[ATTR_REPLYMARKUP],
774  read_timeout=params[ATTR_TIMEOUT],
775  parse_mode=params[ATTR_PARSER],
776  context=context,
777  )
778 
779  return await self._send_msg_send_msg(
780  self.botbot.edit_message_reply_markup,
781  "Error editing message attributes",
782  params[ATTR_MESSAGE_TAG],
783  chat_id=chat_id,
784  message_id=message_id,
785  inline_message_id=inline_message_id,
786  reply_markup=params[ATTR_REPLYMARKUP],
787  read_timeout=params[ATTR_TIMEOUT],
788  context=context,
789  )
790 
792  self, message, callback_query_id, show_alert=False, context=None, **kwargs
793  ):
794  """Answer a callback originated with a press in an inline keyboard."""
795  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
796  _LOGGER.debug(
797  "Answer callback query with callback ID %s: %s, alert: %s",
798  callback_query_id,
799  message,
800  show_alert,
801  )
802  await self._send_msg_send_msg(
803  self.botbot.answer_callback_query,
804  "Error sending answer callback query",
805  params[ATTR_MESSAGE_TAG],
806  callback_query_id,
807  text=message,
808  show_alert=show_alert,
809  read_timeout=params[ATTR_TIMEOUT],
810  context=context,
811  )
812 
813  async def send_file(
814  self, file_type=SERVICE_SEND_PHOTO, target=None, context=None, **kwargs
815  ):
816  """Send a photo, sticker, video, or document."""
817  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
818  file_content = await load_data(
819  self.hasshass,
820  url=kwargs.get(ATTR_URL),
821  filepath=kwargs.get(ATTR_FILE),
822  username=kwargs.get(ATTR_USERNAME),
823  password=kwargs.get(ATTR_PASSWORD),
824  authentication=kwargs.get(ATTR_AUTHENTICATION),
825  verify_ssl=(
827  if kwargs.get(ATTR_VERIFY_SSL, False)
829  ),
830  )
831 
832  if file_content:
833  for chat_id in self._get_target_chat_ids_get_target_chat_ids(target):
834  _LOGGER.debug("Sending file to chat ID %s", chat_id)
835 
836  if file_type == SERVICE_SEND_PHOTO:
837  await self._send_msg_send_msg(
838  self.botbot.send_photo,
839  "Error sending photo",
840  params[ATTR_MESSAGE_TAG],
841  chat_id=chat_id,
842  photo=file_content,
843  caption=kwargs.get(ATTR_CAPTION),
844  disable_notification=params[ATTR_DISABLE_NOTIF],
845  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
846  reply_markup=params[ATTR_REPLYMARKUP],
847  read_timeout=params[ATTR_TIMEOUT],
848  parse_mode=params[ATTR_PARSER],
849  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
850  context=context,
851  )
852 
853  elif file_type == SERVICE_SEND_STICKER:
854  await self._send_msg_send_msg(
855  self.botbot.send_sticker,
856  "Error sending sticker",
857  params[ATTR_MESSAGE_TAG],
858  chat_id=chat_id,
859  sticker=file_content,
860  disable_notification=params[ATTR_DISABLE_NOTIF],
861  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
862  reply_markup=params[ATTR_REPLYMARKUP],
863  read_timeout=params[ATTR_TIMEOUT],
864  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
865  context=context,
866  )
867 
868  elif file_type == SERVICE_SEND_VIDEO:
869  await self._send_msg_send_msg(
870  self.botbot.send_video,
871  "Error sending video",
872  params[ATTR_MESSAGE_TAG],
873  chat_id=chat_id,
874  video=file_content,
875  caption=kwargs.get(ATTR_CAPTION),
876  disable_notification=params[ATTR_DISABLE_NOTIF],
877  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
878  reply_markup=params[ATTR_REPLYMARKUP],
879  read_timeout=params[ATTR_TIMEOUT],
880  parse_mode=params[ATTR_PARSER],
881  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
882  context=context,
883  )
884  elif file_type == SERVICE_SEND_DOCUMENT:
885  await self._send_msg_send_msg(
886  self.botbot.send_document,
887  "Error sending document",
888  params[ATTR_MESSAGE_TAG],
889  chat_id=chat_id,
890  document=file_content,
891  caption=kwargs.get(ATTR_CAPTION),
892  disable_notification=params[ATTR_DISABLE_NOTIF],
893  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
894  reply_markup=params[ATTR_REPLYMARKUP],
895  read_timeout=params[ATTR_TIMEOUT],
896  parse_mode=params[ATTR_PARSER],
897  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
898  context=context,
899  )
900  elif file_type == SERVICE_SEND_VOICE:
901  await self._send_msg_send_msg(
902  self.botbot.send_voice,
903  "Error sending voice",
904  params[ATTR_MESSAGE_TAG],
905  chat_id=chat_id,
906  voice=file_content,
907  caption=kwargs.get(ATTR_CAPTION),
908  disable_notification=params[ATTR_DISABLE_NOTIF],
909  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
910  reply_markup=params[ATTR_REPLYMARKUP],
911  read_timeout=params[ATTR_TIMEOUT],
912  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
913  context=context,
914  )
915  elif file_type == SERVICE_SEND_ANIMATION:
916  await self._send_msg_send_msg(
917  self.botbot.send_animation,
918  "Error sending animation",
919  params[ATTR_MESSAGE_TAG],
920  chat_id=chat_id,
921  animation=file_content,
922  caption=kwargs.get(ATTR_CAPTION),
923  disable_notification=params[ATTR_DISABLE_NOTIF],
924  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
925  reply_markup=params[ATTR_REPLYMARKUP],
926  read_timeout=params[ATTR_TIMEOUT],
927  parse_mode=params[ATTR_PARSER],
928  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
929  context=context,
930  )
931 
932  file_content.seek(0)
933  else:
934  _LOGGER.error("Can't send file with kwargs: %s", kwargs)
935 
936  async def send_sticker(self, target=None, context=None, **kwargs):
937  """Send a sticker from a telegram sticker pack."""
938  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
939  stickerid = kwargs.get(ATTR_STICKER_ID)
940  if stickerid:
941  for chat_id in self._get_target_chat_ids_get_target_chat_ids(target):
942  await self._send_msg_send_msg(
943  self.botbot.send_sticker,
944  "Error sending sticker",
945  params[ATTR_MESSAGE_TAG],
946  chat_id=chat_id,
947  sticker=stickerid,
948  disable_notification=params[ATTR_DISABLE_NOTIF],
949  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
950  reply_markup=params[ATTR_REPLYMARKUP],
951  read_timeout=params[ATTR_TIMEOUT],
952  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
953  context=context,
954  )
955  else:
956  await self.send_filesend_file(SERVICE_SEND_STICKER, target, **kwargs)
957 
958  async def send_location(
959  self, latitude, longitude, target=None, context=None, **kwargs
960  ):
961  """Send a location."""
962  latitude = float(latitude)
963  longitude = float(longitude)
964  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
965  for chat_id in self._get_target_chat_ids_get_target_chat_ids(target):
966  _LOGGER.debug(
967  "Send location %s/%s to chat ID %s", latitude, longitude, chat_id
968  )
969  await self._send_msg_send_msg(
970  self.botbot.send_location,
971  "Error sending location",
972  params[ATTR_MESSAGE_TAG],
973  chat_id=chat_id,
974  latitude=latitude,
975  longitude=longitude,
976  disable_notification=params[ATTR_DISABLE_NOTIF],
977  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
978  read_timeout=params[ATTR_TIMEOUT],
979  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
980  context=context,
981  )
982 
983  async def send_poll(
984  self,
985  question,
986  options,
987  is_anonymous,
988  allows_multiple_answers,
989  target=None,
990  context=None,
991  **kwargs,
992  ):
993  """Send a poll."""
994  params = self._get_msg_kwargs_get_msg_kwargs(kwargs)
995  openperiod = kwargs.get(ATTR_OPEN_PERIOD)
996  for chat_id in self._get_target_chat_ids_get_target_chat_ids(target):
997  _LOGGER.debug("Send poll '%s' to chat ID %s", question, chat_id)
998  await self._send_msg_send_msg(
999  self.botbot.send_poll,
1000  "Error sending poll",
1001  params[ATTR_MESSAGE_TAG],
1002  chat_id=chat_id,
1003  question=question,
1004  options=options,
1005  is_anonymous=is_anonymous,
1006  allows_multiple_answers=allows_multiple_answers,
1007  open_period=openperiod,
1008  disable_notification=params[ATTR_DISABLE_NOTIF],
1009  reply_to_message_id=params[ATTR_REPLY_TO_MSGID],
1010  read_timeout=params[ATTR_TIMEOUT],
1011  message_thread_id=params[ATTR_MESSAGE_THREAD_ID],
1012  context=context,
1013  )
1014 
1015  async def leave_chat(self, chat_id=None, context=None):
1016  """Remove bot from chat."""
1017  chat_id = self._get_target_chat_ids_get_target_chat_ids(chat_id)[0]
1018  _LOGGER.debug("Leave from chat ID %s", chat_id)
1019  return await self._send_msg_send_msg(
1020  self.botbot.leave_chat, "Error leaving chat", None, chat_id, context=context
1021  )
1022 
1023 
1025  """The base class for the telegram bot."""
1026 
1027  def __init__(self, hass, config):
1028  """Initialize the bot base class."""
1029  self.allowed_chat_idsallowed_chat_ids = config[CONF_ALLOWED_CHAT_IDS]
1030  self.hasshass = hass
1031 
1032  async def handle_update(self, update: Update, context: CallbackContext) -> bool:
1033  """Handle updates from bot application set up by the respective platform."""
1034  _LOGGER.debug("Handling update %s", update)
1035  if not self.authorize_updateauthorize_update(update):
1036  return False
1037 
1038  # establish event type: text, command or callback_query
1039  if update.callback_query:
1040  # NOTE: Check for callback query first since effective message will be populated with the message
1041  # in .callback_query (python-telegram-bot docs are wrong)
1042  event_type, event_data = self._get_callback_query_event_data_get_callback_query_event_data(
1043  update.callback_query
1044  )
1045  elif update.effective_message:
1046  event_type, event_data = self._get_message_event_data_get_message_event_data(
1047  update.effective_message
1048  )
1049  else:
1050  _LOGGER.warning("Unhandled update: %s", update)
1051  return True
1052 
1053  event_context = Context()
1054 
1055  _LOGGER.debug("Firing event %s: %s", event_type, event_data)
1056  self.hasshass.bus.async_fire(event_type, event_data, context=event_context)
1057  return True
1058 
1059  @staticmethod
1060  def _get_command_event_data(command_text: str | None) -> dict[str, str | list]:
1061  if not command_text or not command_text.startswith("/"):
1062  return {}
1063  command_parts = command_text.split()
1064  command = command_parts[0]
1065  args = command_parts[1:]
1066  return {ATTR_COMMAND: command, ATTR_ARGS: args}
1067 
1068  def _get_message_event_data(self, message: Message) -> tuple[str, dict[str, Any]]:
1069  event_data: dict[str, Any] = {
1070  ATTR_MSGID: message.message_id,
1071  ATTR_CHAT_ID: message.chat.id,
1072  ATTR_DATE: message.date,
1073  }
1074  if filters.COMMAND.filter(message):
1075  # This is a command message - set event type to command and split data into command and args
1076  event_type = EVENT_TELEGRAM_COMMAND
1077  event_data.update(self._get_command_event_data_get_command_event_data(message.text))
1078  else:
1079  event_type = EVENT_TELEGRAM_TEXT
1080  event_data[ATTR_TEXT] = message.text
1081 
1082  if message.from_user:
1083  event_data.update(self._get_user_event_data_get_user_event_data(message.from_user))
1084 
1085  return event_type, event_data
1086 
1087  def _get_user_event_data(self, user: User) -> dict[str, Any]:
1088  return {
1089  ATTR_USER_ID: user.id,
1090  ATTR_FROM_FIRST: user.first_name,
1091  ATTR_FROM_LAST: user.last_name,
1092  }
1093 
1095  self, callback_query: CallbackQuery
1096  ) -> tuple[str, dict[str, Any]]:
1097  event_type = EVENT_TELEGRAM_CALLBACK
1098  event_data: dict[str, Any] = {
1099  ATTR_MSGID: callback_query.id,
1100  ATTR_CHAT_INSTANCE: callback_query.chat_instance,
1101  ATTR_DATA: callback_query.data,
1102  ATTR_MSG: None,
1103  ATTR_CHAT_ID: None,
1104  }
1105  if callback_query.message:
1106  event_data[ATTR_MSG] = callback_query.message.to_dict()
1107  event_data[ATTR_CHAT_ID] = callback_query.message.chat.id
1108 
1109  if callback_query.from_user:
1110  event_data.update(self._get_user_event_data_get_user_event_data(callback_query.from_user))
1111 
1112  # Split data into command and args if possible
1113  event_data.update(self._get_command_event_data_get_command_event_data(callback_query.data))
1114 
1115  return event_type, event_data
1116 
1117  def authorize_update(self, update: Update) -> bool:
1118  """Make sure either user or chat is in allowed_chat_ids."""
1119  from_user = update.effective_user.id if update.effective_user else None
1120  from_chat = update.effective_chat.id if update.effective_chat else None
1121  if from_user in self.allowed_chat_idsallowed_chat_ids or from_chat in self.allowed_chat_idsallowed_chat_ids:
1122  return True
1123  _LOGGER.error(
1124  (
1125  "Unauthorized update - neither user id %s nor chat id %s is in allowed"
1126  " chats: %s"
1127  ),
1128  from_user,
1129  from_chat,
1130  self.allowed_chat_idsallowed_chat_ids,
1131  )
1132  return False
tuple[str, dict[str, Any]] _get_message_event_data(self, Message message)
Definition: __init__.py:1068
tuple[str, dict[str, Any]] _get_callback_query_event_data(self, CallbackQuery callback_query)
Definition: __init__.py:1096
dict[str, str|list] _get_command_event_data(str|None command_text)
Definition: __init__.py:1060
dict[str, Any] _get_user_event_data(self, User user)
Definition: __init__.py:1087
bool handle_update(self, Update update, CallbackContext context)
Definition: __init__.py:1032
def send_message(self, message="", target=None, context=None, **kwargs)
Definition: __init__.py:692
def __init__(self, hass, bot, allowed_chat_ids, parser)
Definition: __init__.py:503
def send_poll(self, question, options, is_anonymous, allows_multiple_answers, target=None, context=None, **kwargs)
Definition: __init__.py:992
def delete_message(self, chat_id=None, context=None, **kwargs)
Definition: __init__.py:715
def send_location(self, latitude, longitude, target=None, context=None, **kwargs)
Definition: __init__.py:960
def edit_message(self, type_edit, chat_id=None, context=None, **kwargs)
Definition: __init__.py:734
def send_file(self, file_type=SERVICE_SEND_PHOTO, target=None, context=None, **kwargs)
Definition: __init__.py:815
def answer_callback_query(self, message, callback_query_id, show_alert=False, context=None, **kwargs)
Definition: __init__.py:793
def _send_msg(self, func_send, msg_error, message_tag, *args_msg, context=None, **kwargs_msg)
Definition: __init__.py:654
def send_sticker(self, target=None, context=None, **kwargs)
Definition: __init__.py:936
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None open(self, **Any kwargs)
Definition: lock.py:86
Bot initialize_bot(HomeAssistant hass, dict p_config)
Definition: __init__.py:444
def load_data(hass, url=None, filepath=None, username=None, password=None, authentication=None, num_retries=5, verify_ssl=None)
Definition: __init__.py:305
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:368
io.BytesIO _read_file_as_bytesio(str file_path)
Definition: __init__.py:288
list[EntityPlatform] async_get_platforms(HomeAssistant hass, str integration_name)
Integration async_get_loaded_integration(HomeAssistant hass, str domain)
Definition: loader.py:1341
ssl.SSLContext get_default_context()
Definition: ssl.py:118
ssl.SSLContext get_default_no_verify_context()
Definition: ssl.py:123