From cce878213f869392c6ca1d7bd0f30be7c8dd9d7e Mon Sep 17 00:00:00 2001 From: Aviad Levy Date: Mon, 16 Jun 2025 16:48:59 +0300 Subject: [PATCH] Add Telegram Bot message reactions (#146354) --- .../components/telegram_bot/__init__.py | 19 +++++++ homeassistant/components/telegram_bot/bot.py | 33 +++++++++++ .../components/telegram_bot/const.py | 3 + .../components/telegram_bot/icons.json | 3 + .../components/telegram_bot/services.yaml | 26 +++++++++ .../components/telegram_bot/strings.json | 26 +++++++++ .../telegram_bot/test_telegram_bot.py | 55 +++++++++++++++++-- 7 files changed, 161 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index f9472c50cae..554ddd8fc4e 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -46,6 +46,7 @@ from .const import ( ATTR_DISABLE_WEB_PREV, ATTR_FILE, ATTR_IS_ANONYMOUS, + ATTR_IS_BIG, ATTR_KEYBOARD, ATTR_KEYBOARD_INLINE, ATTR_MESSAGE, @@ -58,6 +59,7 @@ from .const import ( ATTR_PARSER, ATTR_PASSWORD, ATTR_QUESTION, + ATTR_REACTION, ATTR_RESIZE_KEYBOARD, ATTR_SHOW_ALERT, ATTR_STICKER_ID, @@ -94,6 +96,7 @@ from .const import ( SERVICE_SEND_STICKER, SERVICE_SEND_VIDEO, SERVICE_SEND_VOICE, + SERVICE_SET_MESSAGE_REACTION, ) _LOGGER = logging.getLogger(__name__) @@ -250,6 +253,19 @@ SERVICE_SCHEMA_LEAVE_CHAT = vol.Schema( } ) +SERVICE_SCHEMA_SET_MESSAGE_REACTION = vol.Schema( + { + vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string, + vol.Required(ATTR_MESSAGEID): vol.Any( + cv.positive_int, vol.All(cv.string, "last") + ), + vol.Required(ATTR_CHAT_ID): vol.Coerce(int), + vol.Required(ATTR_REACTION): cv.string, + vol.Optional(ATTR_IS_BIG, default=False): cv.boolean, + }, + extra=vol.ALLOW_EXTRA, +) + SERVICE_MAP = { SERVICE_SEND_MESSAGE: SERVICE_SCHEMA_SEND_MESSAGE, SERVICE_SEND_PHOTO: SERVICE_SCHEMA_SEND_FILE, @@ -266,6 +282,7 @@ SERVICE_MAP = { SERVICE_ANSWER_CALLBACK_QUERY: SERVICE_SCHEMA_ANSWER_CALLBACK_QUERY, SERVICE_DELETE_MESSAGE: SERVICE_SCHEMA_DELETE_MESSAGE, SERVICE_LEAVE_CHAT: SERVICE_SCHEMA_LEAVE_CHAT, + SERVICE_SET_MESSAGE_REACTION: SERVICE_SCHEMA_SET_MESSAGE_REACTION, } @@ -378,6 +395,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: messages = await notify_service.leave_chat( context=service.context, **kwargs ) + elif msgtype == SERVICE_SET_MESSAGE_REACTION: + await notify_service.set_message_reaction(context=service.context, **kwargs) else: await notify_service.edit_message( msgtype, context=service.context, **kwargs diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index 7749c7f1183..534923b3568 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -786,6 +786,39 @@ class TelegramNotificationService: self.bot.leave_chat, "Error leaving chat", None, chat_id, context=context ) + async def set_message_reaction( + self, + chat_id: int, + reaction: str, + is_big: bool = False, + context: Context | None = None, + **kwargs, + ) -> None: + """Set the bot's reaction for a given message.""" + chat_id = self._get_target_chat_ids(chat_id)[0] + message_id, _ = self._get_msg_ids(kwargs, chat_id) + params = self._get_msg_kwargs(kwargs) + + _LOGGER.debug( + "Set reaction to message %s in chat ID %s to %s with params: %s", + message_id, + chat_id, + reaction, + params, + ) + + await self._send_msg( + self.bot.set_message_reaction, + "Error setting message reaction", + params[ATTR_MESSAGE_TAG], + chat_id, + message_id, + reaction=reaction, + is_big=is_big, + read_timeout=params[ATTR_TIMEOUT], + context=context, + ) + def initialize_bot(hass: HomeAssistant, p_config: MappingProxyType[str, Any]) -> Bot: """Initialize telegram bot with proxy support.""" diff --git a/homeassistant/components/telegram_bot/const.py b/homeassistant/components/telegram_bot/const.py index ca79fc868cf..4abdbaf9738 100644 --- a/homeassistant/components/telegram_bot/const.py +++ b/homeassistant/components/telegram_bot/const.py @@ -43,6 +43,7 @@ SERVICE_SEND_VOICE = "send_voice" SERVICE_SEND_DOCUMENT = "send_document" SERVICE_SEND_LOCATION = "send_location" SERVICE_SEND_POLL = "send_poll" +SERVICE_SET_MESSAGE_REACTION = "set_message_reaction" SERVICE_EDIT_MESSAGE = "edit_message" SERVICE_EDIT_CAPTION = "edit_caption" SERVICE_EDIT_REPLYMARKUP = "edit_replymarkup" @@ -87,6 +88,8 @@ ATTR_MSG = "message" ATTR_MSGID = "id" ATTR_PARSER = "parse_mode" ATTR_PASSWORD = "password" +ATTR_REACTION = "reaction" +ATTR_IS_BIG = "is_big" ATTR_REPLY_TO_MSGID = "reply_to_message_id" ATTR_REPLYMARKUP = "reply_markup" ATTR_SHOW_ALERT = "show_alert" diff --git a/homeassistant/components/telegram_bot/icons.json b/homeassistant/components/telegram_bot/icons.json index 8deecfb9c27..3a53e2b4118 100644 --- a/homeassistant/components/telegram_bot/icons.json +++ b/homeassistant/components/telegram_bot/icons.json @@ -44,6 +44,9 @@ }, "leave_chat": { "service": "mdi:exit-run" + }, + "set_message_reaction": { + "service": "mdi:emoticon-happy" } } } diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index 1577d76b527..d5fc0e134d5 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -787,3 +787,29 @@ leave_chat: example: 12345 selector: text: + +set_message_reaction: + fields: + config_entry_id: + selector: + config_entry: + integration: telegram_bot + message_id: + required: true + example: 54321 + selector: + text: + chat_id: + required: true + example: 12345 + selector: + text: + reaction: + required: true + example: 👍 + selector: + text: + is_big: + required: false + selector: + boolean: diff --git a/homeassistant/components/telegram_bot/strings.json b/homeassistant/components/telegram_bot/strings.json index d772edf1945..9fcc0740970 100644 --- a/homeassistant/components/telegram_bot/strings.json +++ b/homeassistant/components/telegram_bot/strings.json @@ -857,6 +857,32 @@ "description": "Chat ID of the group from which the bot should be removed." } } + }, + "set_message_reaction": { + "name": "Set message reaction", + "description": "Sets the bot's reaction for a given message.", + "fields": { + "config_entry_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::config_entry_id::name%]", + "description": "The config entry representing the Telegram bot to set the message reaction." + }, + "message_id": { + "name": "[%key:component::telegram_bot::services::edit_message::fields::message_id::name%]", + "description": "ID of the message to react to." + }, + "chat_id": { + "name": "[%key:component::telegram_bot::services::edit_message::fields::chat_id::name%]", + "description": "ID of the chat containing the message." + }, + "reaction": { + "name": "Reaction", + "description": "Emoji reaction to use." + }, + "is_big": { + "name": "Large animation", + "description": "Whether the reaction animation should be large." + } + } } }, "exceptions": { diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index d276d72c8a6..24b6deb27b5 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -30,6 +30,7 @@ from homeassistant.components.telegram_bot import ( ATTR_OPTIONS, ATTR_PASSWORD, ATTR_QUESTION, + ATTR_SHOW_ALERT, ATTR_STICKER_ID, ATTR_TARGET, ATTR_URL, @@ -752,20 +753,27 @@ async def test_answer_callback_query( await hass.async_block_till_done() with patch( - "homeassistant.components.telegram_bot.bot.TelegramNotificationService.answer_callback_query" + "homeassistant.components.telegram_bot.bot.Bot.answer_callback_query" ) as mock: await hass.services.async_call( DOMAIN, SERVICE_ANSWER_CALLBACK_QUERY, { ATTR_MESSAGE: "mock message", - ATTR_CALLBACK_QUERY_ID: 12345, + ATTR_CALLBACK_QUERY_ID: 123456, + ATTR_SHOW_ALERT: True, }, blocking=True, ) await hass.async_block_till_done() mock.assert_called_once() + mock.assert_called_with( + 123456, + text="mock message", + show_alert=True, + read_timeout=None, + ) async def test_leave_chat( @@ -779,20 +787,23 @@ async def test_leave_chat( await hass.async_block_till_done() with patch( - "homeassistant.components.telegram_bot.bot.TelegramNotificationService.leave_chat", + "homeassistant.components.telegram_bot.bot.Bot.leave_chat", AsyncMock(return_value=True), ) as mock: await hass.services.async_call( DOMAIN, SERVICE_LEAVE_CHAT, { - ATTR_CHAT_ID: 12345, + ATTR_CHAT_ID: 123456, }, blocking=True, ) await hass.async_block_till_done() mock.assert_called_once() + mock.assert_called_with( + 123456, + ) async def test_send_video( @@ -974,3 +985,39 @@ async def test_send_video( await hass.async_block_till_done() assert mock_get.call_count > 0 assert response["chats"][0]["message_id"] == 12345 + + +async def test_set_message_reaction( + hass: HomeAssistant, + mock_broadcast_config_entry: MockConfigEntry, + mock_external_calls: None, +) -> None: + """Test set message reaction.""" + mock_broadcast_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_broadcast_config_entry.entry_id) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.telegram_bot.bot.Bot.set_message_reaction", + AsyncMock(return_value=True), + ) as mock: + await hass.services.async_call( + DOMAIN, + "set_message_reaction", + { + ATTR_CHAT_ID: 123456, + ATTR_MESSAGEID: 54321, + "reaction": "👍", + "is_big": True, + }, + blocking=True, + ) + + await hass.async_block_till_done() + mock.assert_called_once_with( + 123456, + 54321, + reaction="👍", + is_big=True, + read_timeout=None, + )