1 """Support to send and receive Telegram messages."""
3 from __future__
import annotations
7 from ipaddress
import ip_network
12 from telegram
import (
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
36 HTTP_BEARER_AUTHENTICATION,
37 HTTP_DIGEST_AUTHENTICATION,
45 _LOGGER = logging.getLogger(__name__)
48 ATTR_MESSAGE =
"message"
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"
59 ATTR_DISABLE_NOTIF =
"disable_notification"
60 ATTR_DISABLE_WEB_PREV =
"disable_web_page_preview"
61 ATTR_EDITED_MSG =
"edited_message"
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"
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"
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"
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"
100 DOMAIN =
"telegram_bot"
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"
118 EVENT_TELEGRAM_CALLBACK =
"telegram_callback"
119 EVENT_TELEGRAM_COMMAND =
"telegram_command"
120 EVENT_TELEGRAM_TEXT =
"telegram_text"
121 EVENT_TELEGRAM_SENT =
"telegram_sent"
124 PARSER_MD =
"markdown"
125 PARSER_MD2 =
"markdownv2"
126 PARSER_PLAIN_TEXT =
"plain_text"
128 DEFAULT_TRUSTED_NETWORKS = [ip_network(
"149.154.160.0/20"), ip_network(
"91.108.4.0/22")]
130 CONFIG_SCHEMA = vol.Schema(
137 vol.Required(CONF_PLATFORM): vol.In(
138 (
"broadcast",
"polling",
"webhooks")
140 vol.Required(CONF_API_KEY): cv.string,
141 vol.Required(CONF_ALLOWED_CHAT_IDS): vol.All(
142 cv.ensure_list, [vol.Coerce(int)]
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,
148 vol.Optional(CONF_URL): cv.url,
150 CONF_TRUSTED_NETWORKS, default=DEFAULT_TRUSTED_NETWORKS
151 ): vol.All(cv.ensure_list, [ip_network]),
157 extra=vol.ALLOW_EXTRA,
160 BASE_SERVICE_SCHEMA = vol.Schema(
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,
173 extra=vol.ALLOW_EXTRA,
176 SERVICE_SCHEMA_SEND_MESSAGE = BASE_SERVICE_SCHEMA.extend(
177 {vol.Required(ATTR_MESSAGE): cv.string, vol.Optional(ATTR_TITLE): cv.string}
180 SERVICE_SCHEMA_SEND_FILE = BASE_SERVICE_SCHEMA.extend(
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,
192 SERVICE_SCHEMA_SEND_STICKER = SERVICE_SCHEMA_SEND_FILE.extend(
193 {vol.Optional(ATTR_STICKER_ID): cv.string}
196 SERVICE_SCHEMA_SEND_LOCATION = BASE_SERVICE_SCHEMA.extend(
198 vol.Required(ATTR_LONGITUDE): cv.string,
199 vol.Required(ATTR_LATITUDE): cv.string,
203 SERVICE_SCHEMA_SEND_POLL = vol.Schema(
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,
216 SERVICE_SCHEMA_EDIT_MESSAGE = SERVICE_SCHEMA_SEND_MESSAGE.extend(
218 vol.Required(ATTR_MESSAGEID): vol.Any(
219 cv.positive_int, vol.All(cv.string,
"last")
221 vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
225 SERVICE_SCHEMA_EDIT_CAPTION = vol.Schema(
227 vol.Required(ATTR_MESSAGEID): vol.Any(
228 cv.positive_int, vol.All(cv.string,
"last")
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,
234 extra=vol.ALLOW_EXTRA,
237 SERVICE_SCHEMA_EDIT_REPLYMARKUP = vol.Schema(
239 vol.Required(ATTR_MESSAGEID): vol.Any(
240 cv.positive_int, vol.All(cv.string,
"last")
242 vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
243 vol.Required(ATTR_KEYBOARD_INLINE): cv.ensure_list,
245 extra=vol.ALLOW_EXTRA,
248 SERVICE_SCHEMA_ANSWER_CALLBACK_QUERY = vol.Schema(
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,
254 extra=vol.ALLOW_EXTRA,
257 SERVICE_SCHEMA_DELETE_MESSAGE = vol.Schema(
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")
264 extra=vol.ALLOW_EXTRA,
267 SERVICE_SCHEMA_LEAVE_CHAT = vol.Schema({vol.Required(ATTR_CHAT_ID): vol.Coerce(int)})
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,
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
306 """Load data into ByteIO/File container from a source."""
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)
318 params[
"auth"] = httpx.BasicAuth(username, password)
319 if verify_ssl
is not None:
320 params[
"verify"] = verify_ssl
323 async
with httpx.AsyncClient(
324 timeout=15, headers=headers, **params
326 while retry_num < num_retries:
327 req = await client.get(url)
328 if req.status_code != 200:
330 "Status code %s (retry #%s) loading %s",
336 data = io.BytesIO(req.content)
342 "Empty data (retry #%s) in %s)", retry_num + 1, url
345 if retry_num < num_retries:
350 "Can't load data in %s after %s retries", url, retry_num
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
358 _LOGGER.warning(
"'%s' are not secure to load data from!", filepath)
360 _LOGGER.warning(
"Can't load data. No data found in params!")
362 except (OSError, TypeError)
as error:
363 _LOGGER.error(
"Can't load data into ByteIO: %s", error)
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]
372 if not domain_config:
376 {p_config[CONF_PLATFORM]
for p_config
in domain_config}
379 for p_config
in domain_config:
381 bot = await hass.async_add_executor_job(initialize_bot, hass, p_config)
382 p_type: str = p_config[CONF_PLATFORM]
384 platform = platforms[p_type]
386 _LOGGER.debug(
"Setting up %s.%s", DOMAIN, p_type)
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)
394 _LOGGER.exception(
"Error setting up platform %s", p_type)
398 hass, bot, p_config.get(CONF_ALLOWED_CHAT_IDS), p_config.get(ATTR_PARSER)
401 async
def async_send_telegram_message(service: ServiceCall) ->
None:
402 """Handle sending Telegram Bot message service calls."""
404 msgtype = service.service
405 kwargs =
dict(service.data)
406 _LOGGER.debug(
"New telegram message %s: %s", msgtype, kwargs)
408 if msgtype == SERVICE_SEND_MESSAGE:
409 await notify_service.send_message(context=service.context, **kwargs)
412 SERVICE_SEND_ANIMATION,
415 SERVICE_SEND_DOCUMENT,
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
428 elif msgtype == SERVICE_DELETE_MESSAGE:
429 await notify_service.delete_message(context=service.context, **kwargs)
431 await notify_service.edit_message(
432 msgtype, context=service.context, **kwargs
436 for service_notif, schema
in SERVICE_MAP.items():
437 hass.services.async_register(
438 DOMAIN, service_notif, async_send_telegram_message, schema=schema
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)
450 if proxy_url
is not None:
452 if proxy_params
is None:
455 elif "username" in proxy_params
and "password" in proxy_params:
458 auth = proxy_params.pop(
"username"), proxy_params.pop(
"password")
462 "proxy_params_auth_deprecation",
463 breaks_in_ha_version=
"2024.10.0",
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",
472 translation_key=
"proxy_params_auth_deprecation",
473 learn_more_url=
"https://github.com/home-assistant/core/pull/112778",
479 "proxy_params_deprecation",
480 breaks_in_ha_version=
"2024.10.0",
483 severity=ir.IssueSeverity.WARNING,
484 translation_placeholders={
485 "proxy_params": CONF_PROXY_PARAMS,
486 "proxy_url": CONF_PROXY_URL,
488 "telegram_bot":
"Telegram bot",
490 translation_key=
"proxy_params_deprecation",
491 learn_more_url=
"https://github.com/home-assistant/core/pull/112778",
493 proxy = httpx.Proxy(proxy_url, auth=auth, **proxy_params)
494 request = HTTPXRequest(connection_pool_size=8, proxy=proxy)
496 request = HTTPXRequest(connection_pool_size=8)
497 return Bot(token=api_key, request=request)
501 """Implement the notification services for the Telegram Bot domain."""
503 def __init__(self, hass, bot, allowed_chat_ids, parser):
504 """Initialize the service."""
509 PARSER_HTML: ParseMode.HTML,
510 PARSER_MD: ParseMode.MARKDOWN,
511 PARSER_MD2: ParseMode.MARKDOWN_V2,
512 PARSER_PLAIN_TEXT:
None,
519 """Get the message id to edit.
521 This can be one of (message_id, inline_message_id) from a msg dict,
523 **You can use 'last' as message_id** to edit
524 the message last sent in the chat_id.
526 message_id = inline_message_id =
None
527 if ATTR_MESSAGEID
in msg_data:
528 message_id = msg_data[ATTR_MESSAGEID]
530 isinstance(message_id, str)
531 and (message_id ==
"last")
536 inline_message_id = msg_data[
"inline_message_id"]
537 return message_id, inline_message_id
540 """Validate chat_id targets or return default target (first).
542 :param target: optional list of integers ([12234, -12345])
543 :return list of chat_id targets (integers)
545 if target
is not None:
546 if isinstance(target, int):
548 chat_ids = [t
for t
in target
if t
in self.
allowed_chat_idsallowed_chat_ids]
552 "Disallowed targets: %s, using default: %s", target, self.
_default_user_default_user
557 """Get parameters in message data kwargs."""
559 def _make_row_inline_keyboard(row_keyboard):
560 """Make a list of InlineKeyboardButtons.
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
571 if isinstance(row_keyboard, str):
572 for key
in row_keyboard.split(
","):
575 if key.startswith(
"https://"):
576 label = key.split(
",")[0]
577 url = key[len(label) + 1 :]
578 buttons.append(InlineKeyboardButton(label, url=url))
581 label = key.split(
":/")[0]
582 command = key[len(label) + 1 :]
584 InlineKeyboardButton(label, callback_data=command)
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))
597 InlineKeyboardButton(text_btn, callback_data=data_btn)
600 raise TypeError(
str(row_keyboard))
606 ATTR_DISABLE_NOTIF:
False,
607 ATTR_DISABLE_WEB_PREV:
None,
608 ATTR_REPLY_TO_MSGID:
None,
609 ATTR_REPLYMARKUP:
None,
611 ATTR_MESSAGE_TAG:
None,
612 ATTR_MESSAGE_THREAD_ID:
None,
615 if ATTR_PARSER
in data:
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]
632 if ATTR_KEYBOARD
in data:
633 keys = data.get(ATTR_KEYBOARD)
634 keys = keys
if isinstance(keys, list)
else [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),
642 params[ATTR_REPLYMARKUP] = ReplyKeyboardRemove(
True)
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]
653 self, func_send, msg_error, message_tag, *args_msg, context=None, **kwargs_msg
655 """Send one message."""
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]
663 "Last message ID: %s (from chat_id %s)",
669 ATTR_CHAT_ID: chat_id,
670 ATTR_MESSAGEID: message_id,
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
678 self.
hasshass.bus.async_fire(
679 EVENT_TELEGRAM_SENT, event_data, context=context
681 elif not isinstance(out, bool):
683 "Update last message: out_type:%s, out=%s", type(out), out
685 except TelegramError
as exc:
687 "%s: %s. Args: %s, kwargs: %s", msg_error, exc, args_msg, kwargs_msg
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
698 _LOGGER.debug(
"Send message in chat ID %s with params: %s", chat_id, params)
700 self.
botbot.send_message,
701 "Error sending message",
702 params[ATTR_MESSAGE_TAG],
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],
716 """Delete a previously sent message."""
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)
721 self.
botbot.delete_message,
722 "Error deleting message",
734 async
def edit_message(self, type_edit, chat_id=None, context=None, **kwargs):
735 """Edit a previously sent message."""
737 message_id, inline_message_id = self.
_get_msg_ids_get_msg_ids(kwargs, chat_id)
740 "Edit message %s in chat ID %s with params: %s",
741 message_id
or inline_message_id,
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)
751 self.
botbot.edit_message_text,
752 "Error editing text message",
753 params[ATTR_MESSAGE_TAG],
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],
764 if type_edit == SERVICE_EDIT_CAPTION:
766 self.
botbot.edit_message_caption,
767 "Error editing message attributes",
768 params[ATTR_MESSAGE_TAG],
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],
780 self.
botbot.edit_message_reply_markup,
781 "Error editing message attributes",
782 params[ATTR_MESSAGE_TAG],
784 message_id=message_id,
785 inline_message_id=inline_message_id,
786 reply_markup=params[ATTR_REPLYMARKUP],
787 read_timeout=params[ATTR_TIMEOUT],
792 self, message, callback_query_id, show_alert=False, context=None, **kwargs
794 """Answer a callback originated with a press in an inline keyboard."""
797 "Answer callback query with callback ID %s: %s, alert: %s",
803 self.
botbot.answer_callback_query,
804 "Error sending answer callback query",
805 params[ATTR_MESSAGE_TAG],
808 show_alert=show_alert,
809 read_timeout=params[ATTR_TIMEOUT],
814 self, file_type=SERVICE_SEND_PHOTO, target=None, context=None, **kwargs
816 """Send a photo, sticker, video, or document."""
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),
827 if kwargs.get(ATTR_VERIFY_SSL,
False)
834 _LOGGER.debug(
"Sending file to chat ID %s", chat_id)
836 if file_type == SERVICE_SEND_PHOTO:
838 self.
botbot.send_photo,
839 "Error sending photo",
840 params[ATTR_MESSAGE_TAG],
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],
853 elif file_type == SERVICE_SEND_STICKER:
855 self.
botbot.send_sticker,
856 "Error sending sticker",
857 params[ATTR_MESSAGE_TAG],
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],
868 elif file_type == SERVICE_SEND_VIDEO:
870 self.
botbot.send_video,
871 "Error sending video",
872 params[ATTR_MESSAGE_TAG],
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],
884 elif file_type == SERVICE_SEND_DOCUMENT:
886 self.
botbot.send_document,
887 "Error sending document",
888 params[ATTR_MESSAGE_TAG],
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],
900 elif file_type == SERVICE_SEND_VOICE:
902 self.
botbot.send_voice,
903 "Error sending voice",
904 params[ATTR_MESSAGE_TAG],
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],
915 elif file_type == SERVICE_SEND_ANIMATION:
917 self.
botbot.send_animation,
918 "Error sending animation",
919 params[ATTR_MESSAGE_TAG],
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],
934 _LOGGER.error(
"Can't send file with kwargs: %s", kwargs)
937 """Send a sticker from a telegram sticker pack."""
939 stickerid = kwargs.get(ATTR_STICKER_ID)
943 self.
botbot.send_sticker,
944 "Error sending sticker",
945 params[ATTR_MESSAGE_TAG],
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],
956 await self.
send_filesend_file(SERVICE_SEND_STICKER, target, **kwargs)
959 self, latitude, longitude, target=None, context=None, **kwargs
961 """Send a location."""
962 latitude =
float(latitude)
963 longitude =
float(longitude)
967 "Send location %s/%s to chat ID %s", latitude, longitude, chat_id
970 self.
botbot.send_location,
971 "Error sending location",
972 params[ATTR_MESSAGE_TAG],
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],
988 allows_multiple_answers,
995 openperiod = kwargs.get(ATTR_OPEN_PERIOD)
997 _LOGGER.debug(
"Send poll '%s' to chat ID %s", question, chat_id)
999 self.
botbot.send_poll,
1000 "Error sending poll",
1001 params[ATTR_MESSAGE_TAG],
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],
1016 """Remove bot from chat."""
1018 _LOGGER.debug(
"Leave from chat ID %s", chat_id)
1020 self.
botbot.leave_chat,
"Error leaving chat",
None, chat_id, context=context
1025 """The base class for the telegram bot."""
1028 """Initialize the bot base class."""
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)
1039 if update.callback_query:
1043 update.callback_query
1045 elif update.effective_message:
1047 update.effective_message
1050 _LOGGER.warning(
"Unhandled update: %s", update)
1055 _LOGGER.debug(
"Firing event %s: %s", event_type, event_data)
1056 self.
hasshass.bus.async_fire(event_type, event_data, context=event_context)
1061 if not command_text
or not command_text.startswith(
"/"):
1063 command_parts = command_text.split()
1064 command = command_parts[0]
1065 args = command_parts[1:]
1066 return {ATTR_COMMAND: command, ATTR_ARGS: args}
1069 event_data: dict[str, Any] = {
1070 ATTR_MSGID: message.message_id,
1071 ATTR_CHAT_ID: message.chat.id,
1072 ATTR_DATE: message.date,
1074 if filters.COMMAND.filter(message):
1076 event_type = EVENT_TELEGRAM_COMMAND
1079 event_type = EVENT_TELEGRAM_TEXT
1080 event_data[ATTR_TEXT] = message.text
1082 if message.from_user:
1085 return event_type, event_data
1089 ATTR_USER_ID: user.id,
1090 ATTR_FROM_FIRST: user.first_name,
1091 ATTR_FROM_LAST: user.last_name,
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,
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
1109 if callback_query.from_user:
1115 return event_type, event_data
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
1125 "Unauthorized update - neither user id %s nor chat id %s is in allowed"
tuple[str, dict[str, Any]] _get_message_event_data(self, Message message)
tuple[str, dict[str, Any]] _get_callback_query_event_data(self, CallbackQuery callback_query)
dict[str, str|list] _get_command_event_data(str|None command_text)
def __init__(self, hass, config)
dict[str, Any] _get_user_event_data(self, User user)
bool authorize_update(self, Update update)
bool handle_update(self, Update update, CallbackContext context)
def send_message(self, message="", target=None, context=None, **kwargs)
def _get_target_chat_ids(self, target)
def __init__(self, hass, bot, allowed_chat_ids, parser)
def _get_msg_ids(self, msg_data, chat_id)
def send_poll(self, question, options, is_anonymous, allows_multiple_answers, target=None, context=None, **kwargs)
def _get_msg_kwargs(self, data)
def delete_message(self, chat_id=None, context=None, **kwargs)
def send_location(self, latitude, longitude, target=None, context=None, **kwargs)
def edit_message(self, type_edit, chat_id=None, context=None, **kwargs)
def send_file(self, file_type=SERVICE_SEND_PHOTO, target=None, context=None, **kwargs)
def leave_chat(self, chat_id=None, context=None)
def answer_callback_query(self, message, callback_query_id, show_alert=False, context=None, **kwargs)
def _send_msg(self, func_send, msg_error, message_tag, *args_msg, context=None, **kwargs_msg)
def send_sticker(self, target=None, context=None, **kwargs)
web.Response get(self, web.Request request, str config_key)
None open(self, **Any kwargs)
Bot initialize_bot(HomeAssistant hass, dict p_config)
def load_data(hass, url=None, filepath=None, username=None, password=None, authentication=None, num_retries=5, verify_ssl=None)
bool async_setup(HomeAssistant hass, ConfigType config)
io.BytesIO _read_file_as_bytesio(str file_path)
Integration async_get_loaded_integration(HomeAssistant hass, str domain)
ssl.SSLContext get_default_context()
ssl.SSLContext get_default_no_verify_context()